diff --git a/calm-calatheas/.devcontainer/devcontainer.json b/calm-calatheas/.devcontainer/devcontainer.json new file mode 100644 index 00000000..66071997 --- /dev/null +++ b/calm-calatheas/.devcontainer/devcontainer.json @@ -0,0 +1,93 @@ +{ + "customizations": { + "vscode": { + "extensions": [ + "bierner.markdown-mermaid", + "charliermarsh.ruff", + "DavidAnson.vscode-markdownlint", + "esbenp.prettier-vscode", + "GitHub.vscode-pull-request-github", + "tamasfe.even-better-toml", + "-ms-python.autopep8" + ], + "settings": { + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + }, + "[toml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "files.exclude": { + "**/.pytest_cache": true, + "**/.ruff_cache": true, + "**/__pycache__": true, + ".venv": true, + "node_modules": true, + "site": true + }, + "files.insertFinalNewline": true, + "files.watcherExclude": { + "**/.pytest_cache": true, + "**/.ruff_cache": true, + "**/__pycache__": true, + "**/dist": true, + ".git/objects/**": true, + ".git/subtree-cache/**": true, + ".venv": true, + "node_modules": true, + "site": true + }, + "python.analysis.typeCheckingMode": "standard", + "python.defaultInterpreterPath": "${containerWorkspaceFolder}/.venv/bin/python", + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false + } + } + }, + "features": { + "ghcr.io/devcontainers-extra/features/apt-packages": { + "packages": ["gnupg2", "graphviz"] + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + }, + "ghcr.io/devcontainers/features/python:1": { + "toolsToInstall": ["uv"], + "version": "3.13" + } + }, + "forwardPorts": [8000, 9000], + "image": "mcr.microsoft.com/devcontainers/base:noble", + "name": "Calm Calatheas 🪴", + "onCreateCommand": { + "npm": "bash .devcontainer/npm.sh", + "uv": "bash .devcontainer/uv.sh" + }, + "portsAttributes": { + "8000": { + "label": "Development Server", + "onAutoForward": "notify" + }, + "9000": { + "label": "Documentation Server", + "onAutoForward": "notify" + } + }, + "postCreateCommand": { + "playwright": "bash .devcontainer/playwright.sh", + "pre-commit": "bash .devcontainer/pre-commit.sh" + }, + "runArgs": ["--gpus", "all"] +} diff --git a/calm-calatheas/.devcontainer/npm.sh b/calm-calatheas/.devcontainer/npm.sh new file mode 100755 index 00000000..7ffc1b66 --- /dev/null +++ b/calm-calatheas/.devcontainer/npm.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Install or update dependencies +npm install diff --git a/calm-calatheas/.devcontainer/playwright.sh b/calm-calatheas/.devcontainer/playwright.sh new file mode 100755 index 00000000..0aa5ad7a --- /dev/null +++ b/calm-calatheas/.devcontainer/playwright.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Install playwright dependencies +uv run playwright install --with-deps diff --git a/calm-calatheas/.devcontainer/pre-commit.sh b/calm-calatheas/.devcontainer/pre-commit.sh new file mode 100755 index 00000000..cc98e504 --- /dev/null +++ b/calm-calatheas/.devcontainer/pre-commit.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Mark the current directory as safe for Git operations +git config --global --add safe.directory $PWD + +# Install pre-commit hooks using uv +uv run pre-commit install diff --git a/calm-calatheas/.devcontainer/uv.sh b/calm-calatheas/.devcontainer/uv.sh new file mode 100755 index 00000000..e24759bd --- /dev/null +++ b/calm-calatheas/.devcontainer/uv.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Install Python dependencies using uv +uv venv --allow-existing +uv sync diff --git a/calm-calatheas/.envrc b/calm-calatheas/.envrc new file mode 100644 index 00000000..894571bf --- /dev/null +++ b/calm-calatheas/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k=" + +use devenv diff --git a/calm-calatheas/.gitattributes b/calm-calatheas/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/calm-calatheas/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/calm-calatheas/.github/workflows/lint.yaml b/calm-calatheas/.github/workflows/lint.yaml new file mode 100644 index 00000000..71d6e3ef --- /dev/null +++ b/calm-calatheas/.github/workflows/lint.yaml @@ -0,0 +1,57 @@ +name: Lint + +on: + push: + branches: + - main + pull_request: + +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + PYTHON_VERSION: "3.13" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + run: python -m pip install uv + + - name: Cache the virtualenv + uses: actions/cache@v4 + with: + path: ./.venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} + + - name: Install Python dependencies + run: uv venv --allow-existing && uv sync + + - name: Update GITHUB_PATH + run: echo "$(uv python find)" >> $GITHUB_PATH + + - name: Setup Node.js and dependencies + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + registry-url: "https://npm.pkg.github.com" + + - name: Install Node.js dependencies + run: npm install + shell: bash + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 + + - name: Build the documentation + run: uv run task build-docs diff --git a/calm-calatheas/.github/workflows/publish-docs.yaml b/calm-calatheas/.github/workflows/publish-docs.yaml new file mode 100644 index 00000000..6701fa28 --- /dev/null +++ b/calm-calatheas/.github/workflows/publish-docs.yaml @@ -0,0 +1,50 @@ +name: Publish Documentation + +concurrency: + group: "docs" + +on: + push: + branches: + - main + +jobs: + publish: + runs-on: ubuntu-latest + + permissions: + contents: write + pages: write + + env: + PYTHON_VERSION: "3.13" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - run: git config --global user.email "calm-calatheas@github.com" + - run: git config --global user.name "Calm Calatheas" + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + run: python -m pip install uv + + - name: Cache the virtualenv + uses: actions/cache@v4 + with: + path: ./.venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} + + - name: Install Python dependencies + run: uv venv --allow-existing && uv sync + + - name: Publish documentation + run: uv run mkdocs gh-deploy + shell: bash diff --git a/calm-calatheas/.gitignore b/calm-calatheas/.gitignore new file mode 100644 index 00000000..4d8efb00 --- /dev/null +++ b/calm-calatheas/.gitignore @@ -0,0 +1,321 @@ +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$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 + +# 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 + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.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/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# Playwright +test-results/ + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-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 + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# 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 + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# Devenv +.devenv* +devenv.local.nix + +# Direnv +.direnv diff --git a/calm-calatheas/.markdownlint.json b/calm-calatheas/.markdownlint.json new file mode 100644 index 00000000..76de1448 --- /dev/null +++ b/calm-calatheas/.markdownlint.json @@ -0,0 +1,16 @@ +{ + "MD013": { + "code_blocks": false, + "line_length": 119, + "tables": false + }, + "MD024": { + "siblings_only": true + }, + "MD030": false, + "MD033": { + "allowed_elements": ["div", "img", "figcaption", "figure", "source", "video"] + }, + "MD046": false, + "default": true +} diff --git a/calm-calatheas/.pre-commit-config.yaml b/calm-calatheas/.pre-commit-config.yaml new file mode 100644 index 00000000..518d9e4c --- /dev/null +++ b/calm-calatheas/.pre-commit-config.yaml @@ -0,0 +1,47 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + name: Check for merge conflicts + - id: check-json + name: Check for JSON syntax errors + - id: check-toml + name: Check for TOML syntax errors + - id: check-yaml + name: Check for YAML syntax errors + args: [--unsafe] + - id: end-of-file-fixer + name: Ensure files end with a newline + - id: trailing-whitespace + name: Trim trailing whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.1.0 + hooks: + - id: prettier + name: Format code with Prettier + additional_dependencies: + - prettier@3.6.2 + - prettier-plugin-sort-json@4.1.1 + - prettier-plugin-toml@2.0.6 + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.3 + hooks: + - id: ruff + name: Check for Python linting errors + args: [--fix] + - id: ruff-format + name: Format Python code + + - repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.403 + hooks: + - id: pyright + name: Check for Python type errors + entry: uv run pyright + language: system + "types_or": [python, pyi] + require_serial: true diff --git a/calm-calatheas/.prettierignore b/calm-calatheas/.prettierignore new file mode 100644 index 00000000..8cc9258d --- /dev/null +++ b/calm-calatheas/.prettierignore @@ -0,0 +1,3 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage diff --git a/calm-calatheas/.prettierrc b/calm-calatheas/.prettierrc new file mode 100644 index 00000000..a17940f3 --- /dev/null +++ b/calm-calatheas/.prettierrc @@ -0,0 +1,11 @@ +{ + "jsonRecursiveSort": true, + "plugins": ["prettier-plugin-sort-json", "prettier-plugin-toml"], + "printWidth": 119, + "proseWrap": "preserve", + "reorderKeys": true, + "semi": true, + "singleQuote": false, + "tabWidth": 4, + "useTabs": false +} diff --git a/calm-calatheas/Dockerfile b/calm-calatheas/Dockerfile new file mode 100644 index 00000000..94d6fb3f --- /dev/null +++ b/calm-calatheas/Dockerfile @@ -0,0 +1,56 @@ +# Use the official Python image as a base image for building the application +FROM python:3.13 AS build-image + +# Set the working directory +WORKDIR /build + +# Copy the wheel file into the container. +COPY --chown=root:root --chmod=0755 ./dist/*.whl . + +# Install package dependencies, then clean up. Use a cache mount to speed up the process. +RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \ + pip install --target=./ ./*.whl && \ + rm ./*.whl + +# Use a slim version of the base Python image to reduce the final image size +FROM python:3.13-slim + +# Add image metadata +LABEL org.opencontainers.image.authors="Calm Calatheas" +LABEL org.opencontainers.image.description="This is the app built by the Calm Calatheas team for the Python Discord Code Jam 2025" +LABEL org.opencontainers.image.licenses=MIT +LABEL org.opencontainers.image.source=https://github.com/cj12-calm-calatheas/code-jam-12 +LABEL org.opencontainers.image.title="Calm Calatheas App" + +# Add a non-root user and group +RUN addgroup calm-calatheas && \ + adduser --ingroup calm-calatheas calm-calatheas + +# Install required system dependencies +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install --no-install-recommends --no-install-suggests -y curl + +# Set the working directory +WORKDIR /application + +# Copy in the static assets for the app +COPY --chown=root:root --chmod=0755 ./app ./app + +# Copy in the built dependencies +COPY --chown=root:root --chmod=0755 --from=build-image /build ./ + +# Switch to the non-root user +USER calm-calatheas + +# Set default values for the environment variables +ENV HOST=0.0.0.0 +ENV PORT=8000 + +# Configure a healthcheck to ensure the application is running +HEALTHCHECK --interval=30s --timeout=5s --start-period=120s --retries=6 \ + CMD ["sh", "-c", "curl --fail \"http://localhost:${PORT}/healthcheck\" || exit 1"] + +# Start the application +ENTRYPOINT [ "python", "-m", "calm_calatheas" ] diff --git a/calm-calatheas/LICENSE.txt b/calm-calatheas/LICENSE.txt new file mode 100644 index 00000000..a3c2eed9 --- /dev/null +++ b/calm-calatheas/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2025 Calm Calatheas 🪴 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/calm-calatheas/README.md b/calm-calatheas/README.md new file mode 100644 index 00000000..05822f50 --- /dev/null +++ b/calm-calatheas/README.md @@ -0,0 +1,37 @@ +# Pokedexter + +_Who's that Pokémon?_ + +Ever wondered if your cat could be a Pokémon? Curious about what’s hiding in your cupboard? **Pokedexter** is an AI-powered +Pokédex that helps you discover Pokémon wherever you are. Just snap a photo, and Pokedexter will identify the Pokémon—maybe +even ones you never expected! + +Open Pokedexter on your phone and start discovering Pokémon all around you! + +## Documentation + +For detailed documentation, please refer to our [GitHub Pages site](https://cj12-calm-calatheas.github.io/code-jam-12/). + +## Quick Start + +Get started quickly with the following resources: + +- [Introduction](https://cj12-calm-calatheas.github.io/code-jam-12/) +- [User Guide](https://cj12-calm-calatheas.github.io/code-jam-12/user-guide/) +- [Setup Guide](https://cj12-calm-calatheas.github.io/code-jam-12/setup-guide/) +- [Contributor Guide](https://cj12-calm-calatheas.github.io/code-jam-12/contributor-guide/) +- [Design Documentation](https://cj12-calm-calatheas.github.io/code-jam-12/design/) +- [Code Reference](https://cj12-calm-calatheas.github.io/code-jam-12/code/) + +## About the Team + +This project has been built by the Calm Calatheas team for the [Python Discord Code Jam 2025](https://pythondiscord.com/events/code-jams/12/). +Please feel free to reach out if you have any questions, or need a hand with anything! + +| | Name | Contributions | +| --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------ | +| [TFBlunt](https://github.com/thijsfranck) | [TFBlunt](https://github.com/thijsfranck) | Team Lead, Frontend, Description Generation, Docs | +| [leoluy](https://github.com/leolhuile) | [leoluy](https://github.com/leolhuile) | Description Generation, Model Selection, Frontend Mockup, Ideation | +| [zike01](https://github.com/Zike01) | [Zike01](https://github.com/Zike01) | Ideation | +| [Flonc](https://github.com/FloncDev) | [Flonc](https://github.com/FloncDev) | Initial Frontend Prototype | +| [esmaycat](https://github.com/esmaycat) | [esmaycat](https://github.com/esmaycat) | Object Detection, Transformers.js integration, Favourites Feature | diff --git a/calm-calatheas/app/assets/logo-128x128.png b/calm-calatheas/app/assets/logo-128x128.png new file mode 100644 index 00000000..51375e09 Binary files /dev/null and b/calm-calatheas/app/assets/logo-128x128.png differ diff --git a/calm-calatheas/app/assets/logo-16x16.png b/calm-calatheas/app/assets/logo-16x16.png new file mode 100644 index 00000000..c156031c Binary files /dev/null and b/calm-calatheas/app/assets/logo-16x16.png differ diff --git a/calm-calatheas/app/assets/logo-48x48.png b/calm-calatheas/app/assets/logo-48x48.png new file mode 100644 index 00000000..287c4ca3 Binary files /dev/null and b/calm-calatheas/app/assets/logo-48x48.png differ diff --git a/calm-calatheas/app/assets/pokemongb.ttf b/calm-calatheas/app/assets/pokemongb.ttf new file mode 100644 index 00000000..b5025f06 Binary files /dev/null and b/calm-calatheas/app/assets/pokemongb.ttf differ diff --git a/calm-calatheas/app/favicon.ico b/calm-calatheas/app/favicon.ico new file mode 100644 index 00000000..c156031c Binary files /dev/null and b/calm-calatheas/app/favicon.ico differ diff --git a/calm-calatheas/app/frontend/__init__.py b/calm-calatheas/app/frontend/__init__.py new file mode 100644 index 00000000..3fd20621 --- /dev/null +++ b/calm-calatheas/app/frontend/__init__.py @@ -0,0 +1,3 @@ +from .app import App + +__all__ = ["App"] diff --git a/calm-calatheas/app/frontend/app.py b/calm-calatheas/app/frontend/app.py new file mode 100644 index 00000000..c1aa0204 --- /dev/null +++ b/calm-calatheas/app/frontend/app.py @@ -0,0 +1,95 @@ +from typing import override + +import reactivex.operators as op +from js import Event, document +from pyodide.ffi.wrappers import add_event_listener + +from frontend.base import Component +from frontend.components import Footer, Header, LoadingCaptionModel, Pokemon +from frontend.services import pokemon + +TEMPLATE = """ +
+
+
+
+
+
+
+

Welcome to your Pokedex!

+

Take a picture or upload an image to discover the Pokemon inside.

+
+
+
+

Your Pokemon

+
+
+ +
+
+
+
+
+
+ +
+""" + + +class App(Component): + """The main application class.""" + + @override + def build(self) -> str: + return TEMPLATE + + @override + def pre_destroy(self) -> None: + self._footer.destroy() + self._header.destroy() + self._loading_caption_model.destroy() + + @override + def on_render(self) -> None: + self._footer = Footer(document.getElementById("app-footer")) + self._footer.render() + + self._header = Header(document.getElementById("app-header")) + self._header.render() + + self._notifications = document.getElementById("notifications") + self._loading_caption_model = LoadingCaptionModel(self._notifications) + + self._pokemon = Pokemon(document.getElementById("pokemon")) + self._pokemon.render() + + self._pokemon_refresh = document.getElementById("pokemon-refresh") + self._pokemon_refresh_icon = document.getElementById("pokemon-refresh-icon") + add_event_listener(self._pokemon_refresh, "click", self._on_pokemon_refresh) + + # Update the UI whenever the loading state changes + pokemon.is_refreshing.pipe( + op.take_until(self.destroyed), + ).subscribe(lambda is_refreshing: self._handle_pokemon_is_refreshing(is_refreshing=is_refreshing)) + + def _on_pokemon_refresh(self, event: Event) -> None: + """Refresh the list of Pokemon.""" + if event.currentTarget.hasAttribute("disabled"): # type: ignore[currentTarget is available] + return + + pokemon.refresh() + + def _handle_pokemon_is_refreshing(self, *, is_refreshing: bool) -> None: + """Spin the refresh icon while the Pokemon list is being refreshed.""" + if is_refreshing: + self._pokemon_refresh.setAttribute("disabled", "") + self._pokemon_refresh_icon.classList.add("fa-spin") + else: + self._pokemon_refresh.removeAttribute("disabled") + self._pokemon_refresh_icon.classList.remove("fa-spin") diff --git a/calm-calatheas/app/frontend/base/__init__.py b/calm-calatheas/app/frontend/base/__init__.py new file mode 100644 index 00000000..18b1cb04 --- /dev/null +++ b/calm-calatheas/app/frontend/base/__init__.py @@ -0,0 +1,4 @@ +from .component import Component +from .service import Service + +__all__ = ["Component", "Service"] diff --git a/calm-calatheas/app/frontend/base/component.py b/calm-calatheas/app/frontend/base/component.py new file mode 100644 index 00000000..33a7b96f --- /dev/null +++ b/calm-calatheas/app/frontend/base/component.py @@ -0,0 +1,65 @@ +from abc import ABC, abstractmethod +from typing import cast +from uuid import uuid4 + +from js import DOMParser +from pyodide.ffi import JsDomElement +from reactivex import Subject + + +class Component(ABC): + """A base class for all components.""" + + parser = DOMParser.new() # type: ignore[] + + def __init__(self, root: JsDomElement) -> None: + self.destroyed = Subject[None]() + self.element: JsDomElement | None = None + self.guid = uuid4() + self.root = root + + @abstractmethod + def build(self) -> str: + """Build the component's template and output it as an HTML string.""" + + def destroy(self) -> None: + """Destroy the component and clean up resources.""" + self.destroyed.on_next(None) + self.destroyed.dispose() + self.remove() + self.on_destroy() + + def on_destroy(self) -> None: + """Hook to perform actions after the component is destroyed.""" + return + + def on_render(self) -> None: + """Hook to perform actions after rendering the component.""" + return + + def pre_destroy(self) -> None: + """Hook to perform actions before the component is destroyed.""" + return + + def pre_render(self) -> None: + """Hook to perform actions before rendering the component.""" + return + + def remove(self) -> None: + """Remove the component's element from the DOM.""" + if self.element: + self.element.remove() # type: ignore[remove method is available] + + def render(self) -> None: + """Create a new DOM element for the component and append it to the root element.""" + self.pre_render() + + # Render the given template as an HTML document + template = self.build() + document = self.parser.parseFromString(template, "text/html") + + # Take the first child of the body and append it to the root element + self.element = cast("JsDomElement", document.body.firstChild) + self.root.appendChild(self.element) + + self.on_render() diff --git a/calm-calatheas/app/frontend/base/service.py b/calm-calatheas/app/frontend/base/service.py new file mode 100644 index 00000000..b234b3f3 --- /dev/null +++ b/calm-calatheas/app/frontend/base/service.py @@ -0,0 +1,18 @@ +from reactivex import Subject + + +class Service: + """Base class for all services.""" + + def __init__(self) -> None: + self.destroyed = Subject[None]() + + def destroy(self) -> None: + """Destroy the service.""" + self.destroyed.on_next(None) + self.destroyed.dispose() + self.on_destroy() + + def on_destroy(self) -> None: + """Hook to perform actions after the service is destroyed.""" + return diff --git a/calm-calatheas/app/frontend/components/__init__.py b/calm-calatheas/app/frontend/components/__init__.py new file mode 100644 index 00000000..13275351 --- /dev/null +++ b/calm-calatheas/app/frontend/components/__init__.py @@ -0,0 +1,9 @@ +from .description import Description +from .description_dropdown import DescriptionDropdown +from .footer import Footer +from .header import Header +from .loading_caption_model import LoadingCaptionModel +from .pokemon import Pokemon +from .theme import Theme + +__all__ = ["Description", "DescriptionDropdown", "Footer", "Header", "LoadingCaptionModel", "Pokemon", "Theme"] diff --git a/calm-calatheas/app/frontend/components/camera.py b/calm-calatheas/app/frontend/components/camera.py new file mode 100644 index 00000000..ae7e1a5e --- /dev/null +++ b/calm-calatheas/app/frontend/components/camera.py @@ -0,0 +1,135 @@ +from typing import TYPE_CHECKING, Optional, cast, override + +import reactivex.operators as op +from js import Blob, Event, MediaStream, document +from pyodide.ffi import JsDomElement, create_once_callable +from pyodide.ffi.wrappers import add_event_listener + +from frontend.base import Component +from frontend.services import Camera as CameraService +from frontend.services import reader + +if TYPE_CHECKING: + from js import JsVideoElement + + +TEMPLATE = """ + +""" + + +class Camera(Component): + """Component for displaying the camera feed.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root) + self._camera = CameraService() + + @override + def build(self) -> str: + return TEMPLATE + + @override + def on_destroy(self) -> None: + self._camera.destroy() + + @override + def on_render(self) -> None: + self._camera_capture = document.getElementById("camera-capture") + self._camera_container = document.getElementById("camera-container") + self._camera_close = document.getElementById("camera-close") + self._camera_stream = cast("JsVideoElement", document.getElementById("camera-stream")) + self._camera_switch = document.getElementById("camera-switch") + + add_event_listener(self._camera_capture, "click", self._handle_capture) + add_event_listener(self._camera_close, "click", self._handle_close) + add_event_listener(self._camera_switch, "click", self._handle_toggle_facing_mode) + + # Update the UI whenever the media stream is being acquired + self._camera.is_acquiring_media_stream.pipe( + op.take_until(self.destroyed), + ).subscribe(lambda status: self._handle_is_acquiring_media_stream(status=status)) + + # Update the UI whenever the media stream is available + self._camera.media_stream.pipe( + op.take_until(self.destroyed), + ).subscribe(self._handle_media_stream) + + self._camera.acquire_media_stream() + + def _handle_capture(self, event: Event) -> None: + """Capture a snapshot from the camera stream.""" + if event.currentTarget.hasAttribute("disabled"): # type: ignore[currentTarget is available] + return + + canvas = document.createElement("canvas") + canvas.width = self._camera_stream.videoWidth + canvas.height = self._camera_stream.videoHeight + + context = canvas.getContext("2d") + + context.drawImage( + self._camera_stream, + 0, + 0, + self._camera_stream.videoWidth, + self._camera_stream.videoHeight, + ) + + canvas.toBlob(create_once_callable(self._handle_capture_success), "image/png") + + def _handle_capture_success(self, blob: Blob) -> None: + """Send the captured image to the reader.""" + reader.read(blob) + self.destroy() + + def _handle_close(self, _: Event) -> None: + """Close the camera modal.""" + self.destroy() + + def _handle_is_acquiring_media_stream(self, *, status: bool) -> None: + """Set the spinner on the capture button.""" + if status: + self._camera_capture.classList.add("is-loading") + else: + self._camera_capture.classList.remove("is-loading") + + def _handle_media_stream(self, stream: Optional[MediaStream]) -> None: + """ + Set the camera stream source. + + If no source is given, disable the controls and show a loading indicator. + """ + self._camera_stream.srcObject = stream + + if not stream: + self._camera_capture.setAttribute("disabled", "") + self._camera_switch.setAttribute("disabled", "") + self._camera_container.classList.add("is-skeleton") + else: + self._camera_capture.removeAttribute("disabled") + self._camera_switch.removeAttribute("disabled") + self._camera_container.classList.remove("is-skeleton") + + def _handle_toggle_facing_mode(self, event: Event) -> None: + """Switch the preferred facing mode between user and environment.""" + if event.currentTarget.hasAttribute("disabled"): # type: ignore[currentTarget is available] + return + + self._camera.toggle_facing_mode() diff --git a/calm-calatheas/app/frontend/components/description.py b/calm-calatheas/app/frontend/components/description.py new file mode 100644 index 00000000..bcae56d0 --- /dev/null +++ b/calm-calatheas/app/frontend/components/description.py @@ -0,0 +1,173 @@ +from typing import override + +from js import document +from pyodide.ffi import JsDomElement + +from frontend.base import Component +from frontend.components.description_dropdown import DescriptionDropdown +from frontend.models import PokemonRecord + +LOADING_TEMPLATE = """ +
+
+
+

+
+
+
+

Name

+

Category

+
+ Type +
+
+
+
+ +
+
+
+
+
+
+ Ability +
+
+
+
+ Habitat +
+
+
+
+ Height +
+
+
+
+ Weight +
+
+
+
+""" + +TYPE_TEMPLATE = """ +{type_name} +""" + +TEMPLATE = """ +
+
+
+

+ {name} +

+
+
+
+

+ {name} + + + +

+

The {category} Pokemon

+
{types}
+
+
+
+ +
+
+
+

{flavor_text}

+
+
+
+
+ Ability + {ability} +
+
+
+
+ Habitat + {habitat} +
+
+
+
+ Height + {height} m +
+
+
+
+ Weight + {weight} kg +
+
+
+
+""" + + +class Description(Component): + """Test component to demonstrate the descriptions service.""" + + def __init__(self, root: JsDomElement, description: PokemonRecord | None) -> None: + super().__init__(root) + self._description = description + + @override + def build(self) -> str: + if not self._description: + return LOADING_TEMPLATE + + types = "\n".join( + TYPE_TEMPLATE.format(type_class=type_, type_name=type_.capitalize()) for type_ in self._description.types + ) + + return TEMPLATE.format( + guid=self.guid, + favourite_icon_class="" if self._description.favourite else "is-hidden", + image_url=self._description.img_url, + name=self._description.name, + category=self._description.category.capitalize(), + types=types, + flavor_text=self._description.flavor_text, + ability=self._description.ability.capitalize(), + habitat=self._description.habitat.capitalize(), + height=self._description.height, + weight=self._description.weight, + ) + + @override + def on_render(self) -> None: + if not self._description: + return + + self._description_dropdown = DescriptionDropdown( + document.getElementById(f"dropdown-{self.guid}"), + self._description, + ) + + self._description_dropdown.render() + + @override + def pre_destroy(self) -> None: + self._description_dropdown.destroy() diff --git a/calm-calatheas/app/frontend/components/description_dropdown.py b/calm-calatheas/app/frontend/components/description_dropdown.py new file mode 100644 index 00000000..2b5e199a --- /dev/null +++ b/calm-calatheas/app/frontend/components/description_dropdown.py @@ -0,0 +1,61 @@ +from typing import override +from uuid import uuid4 + +from js import Event, document +from pyodide.ffi import JsDomElement +from pyodide.ffi.wrappers import add_event_listener + +from frontend.base import Component +from frontend.models import PokemonRecord +from frontend.services import pokemon + +TEMPLATE = """ + +""" + + +class DescriptionDropdown(Component): + """Dropdown for Pokemon descriptions.""" + + def __init__(self, root: JsDomElement, description: PokemonRecord) -> None: + super().__init__(root) + self._description = description + self._favourite_guid = uuid4() + self._delete_guid = uuid4() + + @override + def build(self) -> str: + return TEMPLATE.format( + favourite_guid=self._favourite_guid, + favourite_text="Unfavourite" if self._description.favourite else "Favourite", + delete_guid=self._delete_guid, + ) + + @override + def on_render(self) -> None: + self._delete_button = document.getElementById(f"delete-{self._delete_guid}") + self._favourite_button = document.getElementById(f"favourite-{self._favourite_guid}") + + add_event_listener(self._delete_button, "click", self._on_delete_button_click) + add_event_listener(self._favourite_button, "click", self._on_favourite_button_click) + + def _on_delete_button_click(self, _: Event) -> None: + pokemon.delete(self._description.name) + + def _on_favourite_button_click(self, _: Event) -> None: + self._description.favourite = not self._description.favourite + pokemon.put(self._description) diff --git a/calm-calatheas/app/frontend/components/footer.py b/calm-calatheas/app/frontend/components/footer.py new file mode 100644 index 00000000..a4cf42ae --- /dev/null +++ b/calm-calatheas/app/frontend/components/footer.py @@ -0,0 +1,112 @@ +from typing import TYPE_CHECKING, cast, override + +import reactivex.operators as op +from js import Event, document +from pyodide.ffi import JsDomElement +from pyodide.ffi.wrappers import add_event_listener +from reactivex import combine_latest + +from frontend.base import Component +from frontend.services import caption, pokemon, reader + +from .camera import Camera + +if TYPE_CHECKING: + from js import JsButtonElement, JsFileInputElement + +TEMPLATE = """ + +""" + + +class Footer(Component): + """Footer for the application.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root) + self._overlay: Camera | None = None + + @override + def build(self) -> str: + return TEMPLATE + + @override + def pre_destroy(self) -> None: + if self._overlay: + self._overlay.destroy() + + @override + def on_render(self) -> None: + self._camera_button = cast("JsButtonElement", document.getElementById("camera-button")) + self._file_input = cast("JsFileInputElement", document.getElementById("file-input")) + self._upload_button = cast("JsButtonElement", document.getElementById("upload-button")) + + add_event_listener(self._camera_button, "click", self._on_camera_button_click) + add_event_listener(self._file_input, "change", self._on_file_input_change) + add_event_listener(self._upload_button, "click", self._on_upload_button_click) + + # Disable the controls while the model is loading or generating + combine_latest(caption.is_loading_model, pokemon.is_generating).pipe( + op.map(lambda is_loading: any(is_loading)), + op.take_until(self.destroyed), + ).subscribe(lambda is_loading: self._handle_is_loading(is_loading=is_loading)) + + def _handle_is_loading(self, *, is_loading: bool) -> None: + """Handle the loading state of the footer.""" + if is_loading: + self._camera_button.setAttribute("disabled", "") + self._file_input.setAttribute("disabled", "") + self._upload_button.setAttribute("disabled", "") + else: + self._camera_button.removeAttribute("disabled") + self._file_input.removeAttribute("disabled") + self._upload_button.removeAttribute("disabled") + + def _on_camera_button_click(self, event: Event) -> None: + """Open the camera modal.""" + if event.currentTarget.hasAttribute("disabled"): # type: ignore[currentTarget is available] + return + + self._overlay = Camera(self.root) + self._overlay.render() + + def _on_file_input_change(self, event: Event) -> None: + """Send the selected file to the reader.""" + if event.target.hasAttribute("disabled"): + return + + files = self._file_input.files + + if files.length: + reader.read(files.item(0)) + + def _on_upload_button_click(self, event: Event) -> None: + """Trigger the hidden file input.""" + if event.currentTarget.hasAttribute("disabled"): # type: ignore[currentTarget is available] + return + + self._file_input.click() diff --git a/calm-calatheas/app/frontend/components/header.py b/calm-calatheas/app/frontend/components/header.py new file mode 100644 index 00000000..acefa3d1 --- /dev/null +++ b/calm-calatheas/app/frontend/components/header.py @@ -0,0 +1,78 @@ +from typing import TYPE_CHECKING, cast, override + +from js import Event, document +from pyodide.ffi import JsDomElement +from pyodide.ffi.wrappers import add_event_listener + +from frontend.base import Component + +from .theme import Theme + +if TYPE_CHECKING: + from js import JsAnchorElement + +TEMPLATE = """ + +""" + + +class Header(Component): + """The main header for the application.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root) + self._expanded = False + + @override + def build(self) -> str: + return TEMPLATE + + @override + def pre_destroy(self) -> None: + self._theme_selector.destroy() + + @override + def on_render(self) -> None: + self._theme_selector = Theme(document.getElementById("navbar-end")) + self._theme_selector.render() + + self._main_navigation = document.getElementById("main-navigation") + self._navbar_burger = cast("JsAnchorElement", document.getElementById("navbar-burger")) + + add_event_listener(self._navbar_burger, "click", self._toggle_navbar) + + @property + def expanded(self) -> bool: + """Whether or not the navbar menu is expanded.""" + return self._expanded + + @expanded.setter + def expanded(self, value: bool) -> None: + self._expanded = value + + if value: + self._main_navigation.classList.add("is-active") + self._navbar_burger.classList.add("is-active") + else: + self._main_navigation.classList.remove("is-active") + self._navbar_burger.classList.remove("is-active") + + def _toggle_navbar(self, _: Event) -> None: + """Toggle the navbar menu.""" + self.expanded = not self.expanded diff --git a/calm-calatheas/app/frontend/components/loading_caption_model.py b/calm-calatheas/app/frontend/components/loading_caption_model.py new file mode 100644 index 00000000..c361edd4 --- /dev/null +++ b/calm-calatheas/app/frontend/components/loading_caption_model.py @@ -0,0 +1,40 @@ +from typing import override + +import reactivex.operators as op +from pyodide.ffi import JsDomElement + +from frontend.base import Component +from frontend.services import caption + +TEMPLATE = """ +
+

Loading the model for generating captions

+ +
+""" + + +class LoadingCaptionModel(Component): + """A component that shows a loading indicator while the caption model is being loaded.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root) + + # Update the UI whenever the loading state changes + caption.is_loading_model.pipe( + op.distinct_until_changed(), + op.take_until(self.destroyed), + ).subscribe( + lambda is_loading: self._handle_is_loading_update(is_loading=is_loading), + ) + + @override + def build(self) -> str: + return TEMPLATE + + def _handle_is_loading_update(self, *, is_loading: bool) -> None: + """Show the notification while the model is loading.""" + if is_loading: + self.render() + else: + self.remove() diff --git a/calm-calatheas/app/frontend/components/pokemon.py b/calm-calatheas/app/frontend/components/pokemon.py new file mode 100644 index 00000000..7ea309f4 --- /dev/null +++ b/calm-calatheas/app/frontend/components/pokemon.py @@ -0,0 +1,85 @@ +from typing import override + +import reactivex.operators as op +from js import document +from pyodide.ffi import JsDomElement +from reactivex import combine_latest + +from frontend.base import Component +from frontend.components import Description +from frontend.models import PokemonRecord +from frontend.services import pokemon + +EMPTY_PLACEHOLDER_TEMPLATE = """ +

Nothing to show here yet!

+""" + +TEMPLATE = """ +
+

Nothing to show here yet!

+
+""" + + +class Pokemon(Component): + """The list of Pokemon.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root) + self._current_pokemon = [] + + @override + def build(self) -> str: + return TEMPLATE + + @override + def on_render(self) -> None: + self._pokemon_grid = document.getElementById("pokemon-grid") + + # Update the UI whenever the list of pokemon or the loading state changes + combine_latest(pokemon.pokemon, pokemon.is_generating).pipe( + op.take_until(self.destroyed), + ).subscribe(lambda params: self._render_pokemon(params[0], is_generating=params[1])) + + def _render_pokemon(self, pokemon: list[PokemonRecord], *, is_generating: bool) -> None: + """Render the given list of Pokemon.""" + for component in self._current_pokemon: + component.destroy() + + while cell := self._pokemon_grid.firstChild: + self._pokemon_grid.removeChild(cell) + + self._current_pokemon = [] + + if not (pokemon or is_generating): + self._pokemon_grid.innerHTML = EMPTY_PLACEHOLDER_TEMPLATE # type: ignore[innerHTML is available] + return + + for item in pokemon: + cell = document.createElement("div") + cell.classList.add("cell") + + description = Description(cell, item) + + self._pokemon_grid.appendChild(cell) + self._current_pokemon.append(description) + + description.render() + + if is_generating: + self._render_generating_placeholder() + + def _render_generating_placeholder(self) -> None: + """Render a placeholder in the Pokemon grid while generating.""" + if placeholder := document.getElementById("pokemon-empty-placeholder"): + placeholder.remove() # type: ignore[remove is available] + + cell = document.createElement("div") + cell.classList.add("cell") + + # Create a description component with no data to show loading state + description = Description(cell, None) + description.render() + + self._pokemon_grid.prepend(cell) # type: ignore[prepend is available] + self._current_pokemon.append(description) diff --git a/calm-calatheas/app/frontend/components/theme.py b/calm-calatheas/app/frontend/components/theme.py new file mode 100644 index 00000000..a78b96cd --- /dev/null +++ b/calm-calatheas/app/frontend/components/theme.py @@ -0,0 +1,84 @@ +from typing import TYPE_CHECKING, cast, override + +import reactivex.operators as op +from js import Event, document +from pyodide.ffi.wrappers import add_event_listener + +from frontend.base import Component +from frontend.services import Theme_, theme + +if TYPE_CHECKING: + from js import JsAnchorElement + +TEMPLATE = """ + +""" + + +class Theme(Component): + """A component for selecting the theme.""" + + @override + def build(self) -> str: + return TEMPLATE + + @override + def on_destroy(self) -> None: + self._current_theme_listener.dispose() + + @override + def on_render(self) -> None: + self._select_theme_light = cast("JsAnchorElement", document.getElementById("select-theme-light")) + self._select_theme_dark = cast("JsAnchorElement", document.getElementById("select-theme-dark")) + self._select_theme_auto = cast("JsAnchorElement", document.getElementById("select-theme-auto")) + + add_event_listener(self._select_theme_light, "click", self._set_theme_light) + add_event_listener(self._select_theme_dark, "click", self._set_theme_dark) + add_event_listener(self._select_theme_auto, "click", self._set_theme_auto) + + # Update the UI whenever the current theme changes + self._current_theme_listener = theme.current.pipe( + op.take_until(self.destroyed), + ).subscribe(lambda theme: self._update_current_theme(theme)) + + def _set_theme_light(self, _: Event) -> None: + """Set the theme to light.""" + theme.current.on_next("light") + + def _set_theme_dark(self, _: Event) -> None: + """Set the theme to dark.""" + theme.current.on_next("dark") + + def _set_theme_auto(self, _: Event) -> None: + """Set the theme to auto.""" + theme.current.on_next(None) + + def _update_current_theme(self, theme: Theme_) -> None: + """Set the active state on the appropriate theme selector.""" + if theme == "light": + self._select_theme_light.classList.add("is-active") + self._select_theme_dark.classList.remove("is-active") + self._select_theme_auto.classList.remove("is-active") + elif theme == "dark": + self._select_theme_dark.classList.add("is-active") + self._select_theme_light.classList.remove("is-active") + self._select_theme_auto.classList.remove("is-active") + else: + self._select_theme_auto.classList.add("is-active") + self._select_theme_light.classList.remove("is-active") + self._select_theme_dark.classList.remove("is-active") diff --git a/calm-calatheas/app/frontend/models/__init__.py b/calm-calatheas/app/frontend/models/__init__.py new file mode 100644 index 00000000..bab02b48 --- /dev/null +++ b/calm-calatheas/app/frontend/models/__init__.py @@ -0,0 +1,3 @@ +from .pokemon_description import PokemonDescription, PokemonRecord, PokemonType + +__all__ = ["PokemonDescription", "PokemonRecord", "PokemonType"] diff --git a/calm-calatheas/app/frontend/models/pokemon_description.py b/calm-calatheas/app/frontend/models/pokemon_description.py new file mode 100644 index 00000000..13554d42 --- /dev/null +++ b/calm-calatheas/app/frontend/models/pokemon_description.py @@ -0,0 +1,48 @@ +from datetime import UTC, datetime +from enum import StrEnum, auto + +from pydantic import BaseModel, Field + + +class PokemonType(StrEnum): + """An enumeration of Pokemon types.""" + + BUG = auto() + DARK = auto() + DRAGON = auto() + ELECTRIC = auto() + FAIRY = auto() + FIGHTING = auto() + FIRE = auto() + FLYING = auto() + GHOST = auto() + GRASS = auto() + GROUND = auto() + ICE = auto() + NORMAL = auto() + POISON = auto() + PSYCHIC = auto() + ROCK = auto() + STEEL = auto() + WATER = auto() + + +class PokemonDescription(BaseModel): + """A description of a Pokemon, as generated by the API.""" + + ability: str = Field() + category: str = Field() + flavor_text: str = Field() + habitat: str = Field() + height: float = Field() + name: str = Field() + types: set[PokemonType] = Field() + weight: float = Field() + + +class PokemonRecord(PokemonDescription): + """A description of a Pokemon with an image and timestamp, as stored in the database.""" + + img_url: str = Field() + favourite: bool = Field(default=False) + timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC)) diff --git a/calm-calatheas/app/frontend/services/__init__.py b/calm-calatheas/app/frontend/services/__init__.py new file mode 100644 index 00000000..4410555a --- /dev/null +++ b/calm-calatheas/app/frontend/services/__init__.py @@ -0,0 +1,24 @@ +from .camera import Camera +from .caption import Caption, caption +from .database import Database, database +from .description import Description, description +from .pokemon import Pokemon, pokemon +from .reader import Reader, reader +from .theme import Theme, Theme_, theme + +__all__ = [ + "Camera", + "Caption", + "Database", + "Description", + "Pokemon", + "Reader", + "Theme", + "Theme_", + "caption", + "database", + "description", + "pokemon", + "reader", + "theme", +] diff --git a/calm-calatheas/app/frontend/services/camera.py b/calm-calatheas/app/frontend/services/camera.py new file mode 100644 index 00000000..723deca0 --- /dev/null +++ b/calm-calatheas/app/frontend/services/camera.py @@ -0,0 +1,96 @@ +from typing import Literal, Optional, cast, override + +from js import MediaStream, console, localStorage, navigator +from pyodide.webloop import PyodideFuture +from reactivex import Observable, Subject, empty +from reactivex import operators as op +from reactivex.subject import BehaviorSubject + +from frontend.base import Service + +FACING_MODES = {"user", "environment"} +LOCAL_STORAGE_KEY = "preferred_facing_mode" + +type FacingMode = Literal["user", "environment"] + + +class Camera(Service): + """A service for accessing the user's camera.""" + + def __init__(self) -> None: + super().__init__() + + self.media_stream = BehaviorSubject[Optional[MediaStream]](value=None) + self.is_acquiring_media_stream = BehaviorSubject[bool](value=False) + + self._acquire = Subject[None]() + + # Whenever acquisition is triggered, attempt to acquire the media stream + self._acquire.pipe( + op.do_action(lambda _: self.is_acquiring_media_stream.on_next(value=True)), + op.flat_map_latest( + lambda _: self._acquire_media_stream().finally_( + lambda: self.is_acquiring_media_stream.on_next(value=False), + ), + ), + op.catch(lambda err, _: self._handle_acquisition_error(err)), + op.take_until(self.destroyed), + ).subscribe(self.media_stream) + + def dispose_media_stream(self) -> None: + """Stop all tracks in the media stream and notify subscribers.""" + if not (camera_stream := self.media_stream.value): + return + + for track in camera_stream.getTracks(): + track.stop() + + self.media_stream.on_next(None) + + @override + def on_destroy(self) -> None: + self.dispose_media_stream() + self._acquire.dispose() + self.media_stream.dispose() + + def acquire_media_stream(self) -> None: + """Trigger the process of acquiring the media stream.""" + if self.media_stream.value: + return + + self._acquire.on_next(None) + + def toggle_facing_mode(self) -> None: + """Switch the preferred facing mode between user and environment.""" + self._preferred_facing_mode = "environment" if self._preferred_facing_mode == "user" else "user" + + self.dispose_media_stream() + self.acquire_media_stream() + + def _acquire_media_stream(self) -> PyodideFuture[MediaStream]: + """ + Get the user's media stream. + + Requests video access from the user. If access is not granted, a DOMException will be raised. + """ + constraints = {"video": {"facingMode": self._preferred_facing_mode}} + return navigator.mediaDevices.getUserMedia(constraints) + + def _handle_acquisition_error(self, err: Exception) -> Observable: + """Handle errors that occur while acquiring the media stream.""" + console.error("Error acquiring media stream:", err) + return empty() + + @property + def _preferred_facing_mode(self) -> FacingMode: + """ + Return the preferred facing mode for the camera. + + Save the user preference in local storage to ensure it persists across sessions. + """ + mode = localStorage.getItem(LOCAL_STORAGE_KEY) + return cast("FacingMode", mode) if mode in FACING_MODES else "user" + + @_preferred_facing_mode.setter + def _preferred_facing_mode(self, value: FacingMode) -> None: + localStorage.setItem(LOCAL_STORAGE_KEY, value) diff --git a/calm-calatheas/app/frontend/services/caption.py b/calm-calatheas/app/frontend/services/caption.py new file mode 100644 index 00000000..f7fd9ac5 --- /dev/null +++ b/calm-calatheas/app/frontend/services/caption.py @@ -0,0 +1,89 @@ +from asyncio import Future, create_task +from collections.abc import Callable +from typing import TYPE_CHECKING, override + +from js import console, window +from reactivex import Observable, combine_latest, empty, from_future, of +from reactivex import operators as op +from reactivex.subject import BehaviorSubject, ReplaySubject + +from frontend.base import Service + +from .reader import reader + +if TYPE_CHECKING: + from transformers_js import ModelOutput + +type Model = Callable[[str], Future[ModelOutput]] + +MODEL_NAME = "Xenova/vit-gpt2-image-captioning" + + +class Caption(Service): + """Service to generate captions for images.""" + + def __init__(self) -> None: + super().__init__() + + self.captions = ReplaySubject[str]() + self.model = ReplaySubject[Model]() + + self.is_generating_caption = BehaviorSubject[bool](value=False) + self.is_loading_model = BehaviorSubject[bool](value=False) + + # Load the captioning model on startup + of(MODEL_NAME).pipe( + op.do_action(lambda _: self.is_loading_model.on_next(value=True)), + op.flat_map_latest( + lambda model_name: from_future(create_task(self._load_model(model_name))).pipe( + op.finally_action( + lambda: self.is_loading_model.on_next(value=False), + ), + ), + ), + op.catch(lambda err, _: self._handle_load_model_error(err)), + op.take_until(self.destroyed), + ).subscribe(self.model) + + # Generate captions whenever an image is available and the model is loaded + combine_latest(reader.object_urls, self.model).pipe( + op.do_action(lambda _: self.is_generating_caption.on_next(value=True)), + op.flat_map_latest( + lambda params: from_future(create_task(self._caption(*params))).pipe( + op.finally_action( + lambda: self.is_generating_caption.on_next(value=False), + ), + ), + ), + op.catch(lambda err, _: self._handle_caption_error(err)), + op.take_until(self.destroyed), + ).subscribe(self.captions) + + @override + def on_destroy(self) -> None: + self.captions.dispose() + self.model.dispose() + self.is_generating_caption.dispose() + self.is_loading_model.dispose() + + async def _caption(self, url: str, model: Model) -> str: + """Generate a caption for the image at the given URL.""" + output = await model(url) + return output.at(0).generated_text + + def _handle_caption_error(self, err: Exception) -> Observable: + """Handle errors that occur while generating captions.""" + console.error("Failed to generate caption:", err) + return empty() + + def _handle_load_model_error(self, err: Exception) -> Observable: + """Handle errors that occur while loading the model.""" + console.error("Failed to load model:", err) + return empty() + + async def _load_model(self, model_name: str) -> Model: + """Load the given model.""" + return await window.pipeline("image-to-text", model_name, {"dtype": "q8", "device": "wasm"}) + + +caption = Caption() diff --git a/calm-calatheas/app/frontend/services/database.py b/calm-calatheas/app/frontend/services/database.py new file mode 100644 index 00000000..d2fcf1f3 --- /dev/null +++ b/calm-calatheas/app/frontend/services/database.py @@ -0,0 +1,192 @@ +import asyncio +from typing import TYPE_CHECKING, override + +from js import JSON, Event, console, indexedDB +from pyodide.ffi.wrappers import add_event_listener + +from frontend.base import Service +from frontend.models import PokemonRecord + +if TYPE_CHECKING: + from js import IDBDatabase + +_COLLECTION_NAME = "pokemon" +_DB_NAME = "calm_calatheas" +_DB_VERSION = 1 +_READY = asyncio.Event() + + +class DatabaseNotInitializedError(Exception): + """Error raised when the database is not initialized.""" + + def __init__(self) -> None: + super().__init__("Database is not initialized.") + + +class Database(Service): + """Service for interacting with IndexedDB.""" + + def __init__(self) -> None: + super().__init__() + + self._db: IDBDatabase | None = None + + open_ = indexedDB.open(_DB_NAME, _DB_VERSION) + + add_event_listener(open_, "success", self._handle_open_success) + add_event_listener(open_, "upgradeneeded", self._handle_open_upgrade_needed) + + @override + def on_destroy(self) -> None: + if self._db: + self._db.close() + + async def delete(self, name: str) -> None: + """Delete a Pokemon.""" + await _READY.wait() + + if not self._db: + raise DatabaseNotInitializedError + + future = asyncio.Future[None]() + + transaction = self._db.transaction(_COLLECTION_NAME, "readwrite") + store = transaction.objectStore(_COLLECTION_NAME) + + query = store.delete(name) + + def on_complete(_: Event) -> None: + transaction.close() + + def on_error(_: Event) -> None: + future.set_exception(Exception(f"Failed to delete pokemon {name}")) + + def on_success(_: Event) -> None: + future.set_result(None) + + add_event_listener(query, "complete", on_complete) + add_event_listener(query, "error", on_error) + add_event_listener(query, "success", on_success) + + return await future + + async def find_all(self) -> list[PokemonRecord]: + """Find all Pokemon.""" + await _READY.wait() + + if not self._db: + raise DatabaseNotInitializedError + + future = asyncio.Future[list[PokemonRecord]]() + + transaction = self._db.transaction(_COLLECTION_NAME, "readonly") + store = transaction.objectStore(_COLLECTION_NAME) + + query = store.getAll() + + def on_complete(_: Event) -> None: + transaction.close() + + def on_error(_: Event) -> None: + future.set_result([]) + + def on_success(event: Event) -> None: + # Some serialization and deserialization magic to ensure the data is accepted by Pydantic + result = [ + PokemonRecord.model_validate_json(JSON.stringify(item)) + for item in event.target.result # type: ignore[result is available] + ] + future.set_result(result) + + add_event_listener(query, "complete", on_complete) + add_event_listener(query, "error", on_error) + add_event_listener(query, "success", on_success) + + return await future + + async def find_one(self, name: str) -> PokemonRecord | None: + """Find a single Pokemon.""" + await _READY.wait() + + if not self._db: + raise DatabaseNotInitializedError + + future = asyncio.Future[PokemonRecord | None]() + + transaction = self._db.transaction(_COLLECTION_NAME, "readonly") + store = transaction.objectStore(_COLLECTION_NAME) + + query = store.get(name) + + def on_complete(_: Event) -> None: + transaction.close() + + def on_error(_: Event) -> None: + future.set_result(None) + + def on_success(event: Event) -> None: + # Some serialization and deserialization magic to ensure the data is accepted by Pydantic + result = PokemonRecord.model_validate_json(JSON.stringify(event.target.result)) # type: ignore[result is available] + future.set_result(result) + + add_event_listener(query, "complete", on_complete) + add_event_listener(query, "error", on_error) + add_event_listener(query, "success", on_success) + + return await future + + async def put(self, description: PokemonRecord) -> None: + """Store a Pokemon.""" + await _READY.wait() + + if not self._db: + raise DatabaseNotInitializedError + + future = asyncio.Future[None]() + + transaction = self._db.transaction(_COLLECTION_NAME, "readwrite") + store = transaction.objectStore(_COLLECTION_NAME) + + # Some serialization and deserialization magic to ensure the data is accepted by IndexedDB + query = store.put(JSON.parse(description.model_dump_json())) + + def on_complete(_: Event) -> None: + transaction.close() + + def on_error(_: Event) -> None: + future.set_exception(Exception(f"Failed to store pokemon {description.name}")) + + def on_success(_: Event) -> None: + future.set_result(None) + + add_event_listener(query, "complete", on_complete) + add_event_listener(query, "error", on_error) + add_event_listener(query, "success", on_success) + + return await future + + def _handle_open_success(self, event: Event) -> None: + """Handle the successful opening of the database.""" + self._db = event.target.result # type: ignore[result is available] + console.log("Opened IndexedDB.") + + _READY.set() + + def _handle_open_upgrade_needed(self, event: Event) -> None: + """Handle the upgrade needed event.""" + self._db = event.target.result # type: ignore[result is available] + + if not self._db: + raise DatabaseNotInitializedError + + self._db.createObjectStore(_COLLECTION_NAME, {"keyPath": "name"}) + + add_event_listener(event.target.transaction, "complete", self._handle_upgrade_transaction_complete) # type: ignore[transaction is available] + + def _handle_upgrade_transaction_complete(self, _: Event) -> None: + """Handle the completion of the upgrade transaction.""" + console.log("Initialized IndexedDB.") + _READY.set() + + +database = Database() diff --git a/calm-calatheas/app/frontend/services/description.py b/calm-calatheas/app/frontend/services/description.py new file mode 100644 index 00000000..4a2185de --- /dev/null +++ b/calm-calatheas/app/frontend/services/description.py @@ -0,0 +1,58 @@ +from asyncio import create_task + +from js import console +from pyodide.http import pyfetch +from reactivex import Observable, empty, from_future +from reactivex import operators as op +from reactivex.subject import BehaviorSubject, ReplaySubject + +from frontend.base import Service +from frontend.models import PokemonDescription + +from .caption import caption + + +class Description(Service): + """Service to generate descriptions from captions.""" + + def __init__(self) -> None: + super().__init__() + + self.is_generating_description = BehaviorSubject[bool](value=False) + self.descriptions = ReplaySubject[PokemonDescription]() + + # Generate descriptions whenever a new caption is available + caption.captions.pipe( + op.do_action(lambda _: self.is_generating_description.on_next(value=True)), + op.flat_map_latest( + lambda caption: from_future(create_task(self._describe(caption))).pipe( + op.finally_action( + lambda: self.is_generating_description.on_next(value=False), + ), + ), + ), + op.catch(lambda err, _: self._handle_description_error(err)), + op.take_until(self.destroyed), + ).subscribe(self.descriptions) + + async def _describe(self, caption: str) -> PokemonDescription: + """Generate a description from the given caption.""" + console.log("Generating description for caption:", caption) + + response = await pyfetch(f"/describe?prompt={caption}") + response.raise_for_status() + + data = await response.json() + description = PokemonDescription.model_validate(data) + + console.log("Generated description:", description.model_dump_json()) + + return description + + def _handle_description_error(self, err: Exception) -> Observable: + """Handle errors that occur while generating descriptions.""" + console.error("Failed to generate description:", err) + return empty() + + +description = Description() diff --git a/calm-calatheas/app/frontend/services/pokemon.py b/calm-calatheas/app/frontend/services/pokemon.py new file mode 100644 index 00000000..7a8561b1 --- /dev/null +++ b/calm-calatheas/app/frontend/services/pokemon.py @@ -0,0 +1,121 @@ +from asyncio import create_task +from typing import override + +from js import console +from reactivex import Observable, combine_latest, empty, from_future +from reactivex import operators as op +from reactivex.subject import BehaviorSubject, Subject + +from frontend.base import Service +from frontend.models import PokemonRecord + +from .caption import caption +from .database import database +from .description import description +from .reader import reader + + +class Pokemon(Service): + """Service that maintains a list of the user's current Pokemon.""" + + def __init__(self) -> None: + super().__init__() + + self.is_generating = BehaviorSubject[bool](value=False) + self.is_refreshing = BehaviorSubject[bool](value=False) + self.pokemon = BehaviorSubject[list[PokemonRecord]](value=[]) + + self._delete = Subject[str]() + self._put = Subject[PokemonRecord]() + self._refresh = Subject[None]() + + # Combine the loading states from all relevant sources + combine_latest( + caption.is_generating_caption, + description.is_generating_description, + reader.is_reading, + ).pipe( + op.map(lambda is_loading: any(is_loading)), + op.distinct_until_changed(), + op.take_until(self.destroyed), + ).subscribe(self.is_generating) + + # Whenever a new description is available, get the corresponding image url and create an new database record + description.descriptions.pipe( + op.with_latest_from(reader.object_urls), + op.map(lambda params: PokemonRecord(**params[0].model_dump(), img_url=params[1])), + op.take_until(self.destroyed), + ).subscribe(lambda pokemon: self.put(pokemon)) + + # On put, update the database with the given record + self._put.pipe( + op.flat_map_latest(lambda pokemon: from_future(create_task(database.put(pokemon)))), + op.catch(lambda err, _: self._handle_update_error(err)), + op.take_until(self.destroyed), + ).subscribe(lambda _: self.refresh()) + + # On delete, remove the Pokemon from the list and trigger a refresh + self._delete.pipe( + op.flat_map_latest(lambda name: from_future(create_task(database.delete(name)))), + op.catch(lambda err, _: self._handle_delete_error(err)), + op.take_until(self.destroyed), + ).subscribe(lambda _: self.refresh()) + + # On refresh, retrieve the current list of Pokemon from the database. Sort the list by timestamp + self._refresh.pipe( + op.do_action(lambda _: self.is_refreshing.on_next(value=True)), + op.flat_map_latest( + lambda _: from_future(create_task(database.find_all())).pipe( + op.finally_action(lambda: self.is_refreshing.on_next(value=False)), + ), + ), + op.catch(lambda err, _: self._handle_refresh_error(err)), + op.map(lambda pokemon: sorted(pokemon, key=lambda p: p.timestamp, reverse=True)), + op.take_until(self.destroyed), + ).subscribe(self.pokemon) + + # Trigger a refresh on startup + self.refresh() + + @override + def on_destroy(self) -> None: + self.is_generating.dispose() + self.is_refreshing.dispose() + self.pokemon.dispose() + self._delete.dispose() + self._put.dispose() + self._refresh.dispose() + + def delete(self, name: str) -> None: + """Delete the pokemon with the given name.""" + self._delete.on_next(name) + + def put(self, pokemon: PokemonRecord) -> None: + """Update the database with the given pokemon.""" + self._put.on_next(pokemon) + + def refresh(self) -> None: + """Trigger a refresh of the list.""" + self._refresh.on_next(None) + + def _handle_delete_error(self, err: Exception) -> Observable: + """Handle errors that occur while deleting a Pokemon.""" + console.error("Failed to delete pokemon:", err) + return empty() + + def _handle_favourite_error(self, err: Exception) -> Observable: + console.error("Failed to favourite pokemon", err) + return empty() + + def _handle_refresh_error(self, err: Exception) -> Observable: + """Handle errors that occur while refreshing the list of Pokemon.""" + console.error("Failed to refresh list of pokemon:", err) + return empty() + + def _handle_update_error(self, err: Exception) -> Observable: + """Handle errors that occur while updating a Pokemon.""" + console.error("Failed to update pokemon:", err) + return empty() + + +pokemon = Pokemon() diff --git a/calm-calatheas/app/frontend/services/reader.py b/calm-calatheas/app/frontend/services/reader.py new file mode 100644 index 00000000..58f33a02 --- /dev/null +++ b/calm-calatheas/app/frontend/services/reader.py @@ -0,0 +1,67 @@ +from asyncio import Future +from typing import Union, override + +from js import Blob, File, FileReader, console +from pyodide.ffi.wrappers import add_event_listener +from reactivex import Observable, empty, from_future +from reactivex import operators as op +from reactivex.subject import BehaviorSubject, ReplaySubject, Subject + +from frontend.base import Service + +type Readable = Union[Blob, File] + + +class Reader(Service): + """Service for reading files and generating object URLs.""" + + def __init__(self) -> None: + super().__init__() + + self.is_reading = BehaviorSubject[bool](value=False) + self.object_urls = ReplaySubject[str]() + + self._read = Subject[Readable]() + + # On read, generate an object URL for the object + self._read.pipe( + op.do_action(lambda _: self.is_reading.on_next(value=True)), + op.flat_map_latest( + lambda file_: from_future(self._generate_object_url(file_)).pipe( + op.finally_action(lambda: self.is_reading.on_next(value=False)), + ), + ), + op.catch(lambda err, _: self._handle_reader_error(err)), + op.take_until(self.destroyed), + ).subscribe(self.object_urls) + + @override + def on_destroy(self) -> None: + self._read.dispose() + self.is_reading.dispose() + self.object_urls.dispose() + + def read(self, object_: Readable) -> None: + """Upload an object and trigger further processing.""" + self._read.on_next(object_) + + def _handle_reader_error(self, err: Exception) -> Observable: + """Handle errors that occur while reading objects.""" + console.error("Error reading object:", err) + return empty() + + def _generate_object_url(self, object_: Readable) -> Future[str]: + """Read an object and return its object URL.""" + result = Future() + + reader = FileReader.new() + + add_event_listener(reader, "load", lambda _: result.set_result(reader.result)) # type: ignore[FileReader also supported] + add_event_listener(reader, "error", lambda e: result.set_exception(e)) # type: ignore[FileReader also supported] + + reader.readAsDataURL(object_) + + return result + + +reader = Reader() diff --git a/calm-calatheas/app/frontend/services/theme.py b/calm-calatheas/app/frontend/services/theme.py new file mode 100644 index 00000000..decdbe23 --- /dev/null +++ b/calm-calatheas/app/frontend/services/theme.py @@ -0,0 +1,61 @@ +from typing import Literal, cast, override + +import reactivex.operators as op +from js import document, localStorage +from reactivex.subject import BehaviorSubject + +from frontend.base import Service + +type Theme_ = Literal["light", "dark"] | None + +ATTRIBUTE_NAME = "data-theme" + + +def _update_document_theme(theme: Theme_) -> None: + """Set the theme of the document.""" + if theme: + document.documentElement.setAttribute(ATTRIBUTE_NAME, theme) # type: ignore[setAttribute not defined] + else: + document.documentElement.removeAttribute(ATTRIBUTE_NAME) # type: ignore[removeAttribute not defined] + + +LOCAL_STORAGE_KEY = "theme" + + +def _update_local_storage(theme: Theme_) -> None: + """Set the theme in local storage.""" + if theme: + localStorage.setItem(LOCAL_STORAGE_KEY, theme) + else: + localStorage.removeItem(LOCAL_STORAGE_KEY) + + +class Theme(Service): + """Service to manage the theme of the application.""" + + def __init__(self) -> None: + super().__init__() + + self.current = BehaviorSubject[Theme_]( + cast("Theme_", theme) + if (theme := localStorage.getItem(LOCAL_STORAGE_KEY)) and theme in {"light", "dark"} + else None, + ) + + # Update the document theme whenever the current theme changes + self.current.pipe( + op.take_until(self.destroyed), + ).subscribe(_update_document_theme) + + # Update the local storage whenever the current theme changes + self.current.pipe( + op.take_until(self.destroyed), + ).subscribe(_update_local_storage) + + @override + def on_destroy(self) -> None: + """Clean up the theme service.""" + self.current.dispose() + + +theme = Theme() diff --git a/calm-calatheas/app/index.html b/calm-calatheas/app/index.html new file mode 100644 index 00000000..e3b859ed --- /dev/null +++ b/calm-calatheas/app/index.html @@ -0,0 +1,31 @@ + + + + + + Calm Calatheas + + + + + + + + + + + diff --git a/calm-calatheas/app/main.py b/calm-calatheas/app/main.py new file mode 100644 index 00000000..a5a35686 --- /dev/null +++ b/calm-calatheas/app/main.py @@ -0,0 +1,12 @@ +from frontend import App +from js import document + + +def bootstrap() -> None: + """Bootstrap the application to the DOM.""" + app = App(document.body) + app.render() + + +if __name__ == "__main__": + bootstrap() diff --git a/calm-calatheas/app/manifest.json b/calm-calatheas/app/manifest.json new file mode 100644 index 00000000..36de9e18 --- /dev/null +++ b/calm-calatheas/app/manifest.json @@ -0,0 +1,25 @@ +{ + "background_color": "white", + "display": "standalone", + "icons": [ + { + "sizes": "16x16", + "src": "/assets/logo-16x16.png", + "type": "image/png" + }, + { + "sizes": "48x48", + "src": "/assets/logo-48x48.png", + "type": "image/png" + }, + { + "sizes": "128x128", + "src": "/assets/logo-128x128.png", + "type": "image/png" + } + ], + "name": "Pokedexter", + "short_name": "Pokedexter", + "start_url": ".", + "theme_color": "black" +} diff --git a/calm-calatheas/app/pyscript.toml b/calm-calatheas/app/pyscript.toml new file mode 100644 index 00000000..b31310a8 --- /dev/null +++ b/calm-calatheas/app/pyscript.toml @@ -0,0 +1,34 @@ +docked = false +terminal = false + +packages = ['pydantic', 'reactivex'] + +[[fetch]] +files = [ + '__init__.py', + 'app.py', + 'base/__init__.py', + 'base/component.py', + 'base/service.py', + 'components/__init__.py', + 'components/camera.py', + 'components/footer.py', + 'components/header.py', + 'components/loading_caption_model.py', + 'components/pokemon.py', + 'components/theme.py', + 'components/description.py', + 'components/description_dropdown.py', + 'models/__init__.py', + 'models/pokemon_description.py', + 'services/__init__.py', + 'services/camera.py', + 'services/caption.py', + 'services/database.py', + 'services/description.py', + 'services/pokemon.py', + 'services/reader.py', + 'services/theme.py', +] +from = 'frontend' +to_folder = 'frontend' diff --git a/calm-calatheas/app/styles/theme.css b/calm-calatheas/app/styles/theme.css new file mode 100644 index 00000000..85dc9856 --- /dev/null +++ b/calm-calatheas/app/styles/theme.css @@ -0,0 +1,165 @@ +@import "https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css"; + +:root { + --bulma-primary-h: 359deg; + --bulma-primary-s: 91%; + --bulma-primary-l: 56%; + --bulma-success-h: 120deg; + --bulma-success-s: 50%; + --bulma-success-l: 40%; + --bulma-warning-h: 52deg; + --bulma-warning-l: 50%; + --bulma-danger-h: 0deg; + --bulma-danger-l: 50%; + + --type-bug: #94a034; + --type-dark: #4d3f3e; + --type-dragon: #4c60a9; + --type-electric: #f2c340; + --type-fairy: #ba7fb5; + --type-fighting: #f08833; + --type-fire: #d43a30; + --type-flying: #8eb8e3; + --type-ghost: #6b426e; + --type-grass: #5d9d3c; + --type-ground: #895229; + --type-ice: #78ccf0; + --type-normal: #a2a2a2; + --type-poison: #6d4b97; + --type-psychic: #dc4d79; + --type-rock: #ada984; + --type-steel: #74a2b9; + --type-water: #4c79bc; +} + +@font-face { + font-family: "pokemongb"; + src: url("/assets/pokemongb.ttf"); +} + +body { + font-family: "pokemongb"; +} + +#app-container { + max-height: 100vh; +} + +#app-body { + align-items: unset; + display: block; + flex-shrink: unset; + overflow-y: auto; +} + +.modal { + animation: fadeIn 0.2s; +} + +.notification { + animation: fadeIn 0.2s; +} + +.pokemon-description { + animation: fadeIn 0.2s; +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.tag.type-bug { + background-color: var(--type-bug); + color: hsl(0, 0%, 100%); +} + +.tag.type-dark { + background-color: var(--type-dark); + color: hsl(0, 0%, 100%); +} + +.tag.type-dragon { + background-color: var(--type-dragon); + color: hsl(0, 0%, 100%); +} + +.tag.type-electric { + background-color: var(--type-electric); + color: hsl(0, 0%, 4%); +} + +.tag.type-fairy { + background-color: var(--type-fairy); + color: hsl(0, 0%, 4%); +} + +.tag.type-fighting { + background-color: var(--type-fighting); + color: hsl(0, 0%, 4%); +} + +.tag.type-fire { + background-color: var(--type-fire); + color: hsl(0, 0%, 100%); +} + +.tag.type-flying { + background-color: var(--type-flying); + color: hsl(0, 0%, 4%); +} + +.tag.type-ghost { + background-color: var(--type-ghost); + color: hsl(0, 0%, 100%); +} + +.tag.type-grass { + background-color: var(--type-grass); + color: hsl(0, 0%, 100%); +} + +.tag.type-ground { + background-color: var(--type-ground); + color: hsl(0, 0%, 100%); +} + +.tag.type-ice { + background-color: var(--type-ice); + color: hsl(0, 0%, 4%); +} + +.tag.type-normal { + background-color: var(--type-normal); + color: hsl(0, 0%, 4%); +} + +.tag.type-poison { + background-color: var(--type-poison); + color: hsl(0, 0%, 100%); +} + +.tag.type-psychic { + background-color: var(--type-psychic); + color: hsl(0, 0%, 100%); +} + +.tag.type-rock { + background-color: var(--type-rock); + color: hsl(0, 0%, 4%); +} + +.tag.type-steel { + background-color: var(--type-steel); + color: hsl(0, 0%, 4%); +} + +.tag.type-water { + background-color: var(--type-water); + color: hsl(0, 0%, 100%); +} diff --git a/calm-calatheas/calm_calatheas/__init__.py b/calm-calatheas/calm_calatheas/__init__.py new file mode 100644 index 00000000..cd0ecd76 --- /dev/null +++ b/calm-calatheas/calm_calatheas/__init__.py @@ -0,0 +1,4 @@ +from .app import app +from .settings import settings + +__all__ = ["app", "settings"] diff --git a/calm-calatheas/calm_calatheas/__main__.py b/calm-calatheas/calm_calatheas/__main__.py new file mode 100644 index 00000000..7be6879b --- /dev/null +++ b/calm-calatheas/calm_calatheas/__main__.py @@ -0,0 +1,5 @@ +from uvicorn import run + +from calm_calatheas import app, settings + +run(app=app, host=settings.host, port=settings.port) diff --git a/calm-calatheas/calm_calatheas/app.py b/calm-calatheas/calm_calatheas/app.py new file mode 100644 index 00000000..6f25da76 --- /dev/null +++ b/calm-calatheas/calm_calatheas/app.py @@ -0,0 +1,34 @@ +from starlette.applications import Starlette +from starlette.requests import Request +from starlette.responses import Response +from starlette.routing import Mount, Route +from starlette.staticfiles import StaticFiles + +from .model import generate_description +from .settings import settings + + +async def describe(request: Request) -> Response: + """Handle GET requests to the /describe endpoint.""" + user_prompt = request.query_params.get("prompt", "") + + if not user_prompt: + return Response("Missing prompt", status_code=400) + + description = generate_description(user_prompt) + + return Response(description.model_dump_json(), media_type="application/json") + + +async def healthcheck(_: Request) -> Response: + """Handle GET requests to the /healthcheck endpoint.""" + return Response("OK", media_type="text/plain") + + +routes = [ + Route("/describe", endpoint=describe, methods=["GET"]), + Route("/healthcheck", endpoint=healthcheck, methods=["GET"]), + Mount("/", app=StaticFiles(directory=settings.static_files_path, html=True), name="static"), +] + +app = Starlette(routes=routes) diff --git a/calm-calatheas/calm_calatheas/logger.py b/calm-calatheas/calm_calatheas/logger.py new file mode 100644 index 00000000..454366f8 --- /dev/null +++ b/calm-calatheas/calm_calatheas/logger.py @@ -0,0 +1,6 @@ +import logging + +from .settings import settings + +logging.basicConfig(level=settings.log_level) +LOGGER = logging.getLogger("calm_calatheas") diff --git a/calm-calatheas/calm_calatheas/model.py b/calm-calatheas/calm_calatheas/model.py new file mode 100644 index 00000000..63eab33f --- /dev/null +++ b/calm-calatheas/calm_calatheas/model.py @@ -0,0 +1,194 @@ +import json +from enum import StrEnum, auto +from functools import lru_cache +from typing import override + +from pydantic import BaseModel, Field, ValidationError +from transformers import AutoModelForCausalLM, AutoTokenizer + +from .logger import LOGGER + + +class PokemonType(StrEnum): + """An enumeration of Pokemon types.""" + + BUG = auto() + DARK = auto() + DRAGON = auto() + ELECTRIC = auto() + FAIRY = auto() + FIGHTING = auto() + FIRE = auto() + FLYING = auto() + GHOST = auto() + GRASS = auto() + GROUND = auto() + ICE = auto() + NORMAL = auto() + POISON = auto() + PSYCHIC = auto() + ROCK = auto() + STEEL = auto() + WATER = auto() + + @classmethod + @override + def _missing_(cls, value: object) -> "PokemonType | None": + """ + The model will sometimes generate types that don't match the enum exactly. + + Try normalizing the input by converting it to lowercase. + """ + if isinstance(value, str): + value = value.lower() + + for member in cls: + if member.lower() == value: + return member + + return None + + +class PokemonDescription(BaseModel): + """A description of a Pokemon.""" + + ability: str = Field( + description="The primary ability of the Pokemon, which can affect its performance in battles.", + ) + + category: str = Field( + description="The category of the Pokemon, phrased as a noun.", + ) + + flavor_text: str = Field( + description="Flavor text to add characterization or lore to the Pokemon in question.", + max_length=255, + ) + + habitat: str = Field( + description="The natural habitat where the Pokemon can typically be found, phrased as a noun.", + max_length=15, + ) + + height: float = Field( + description="The height of the Pokemon in meters.", + ) + + name: str = Field( + description="The creative name for the Pokemon. Avoid using real names or actual Pokemon names.", + ) + + types: set[PokemonType] = Field( + description="The type(s) of the Pokemon.", + max_length=2, + min_length=1, + ) + + weight: float = Field( + description="The weight of the Pokemon in kilograms.", + ) + + +MODEL_NAME = "Qwen/Qwen3-1.7B" + +TOKENIZER = AutoTokenizer.from_pretrained(MODEL_NAME) +MODEL = AutoModelForCausalLM.from_pretrained( + MODEL_NAME, + torch_dtype="auto", + device_map="auto", +) + +DESCRIPTION_PROMPT = f""" +You are a helpful Pokemon professor. +The user is a Pokemon trainer seeking information. +The user will prompt you with a caption for a picture of a Pokemon. +Answer using the following schema: {json.dumps(PokemonDescription.model_json_schema())} +""" + +REPAIR_PROMPT = f""" +You are a helpful Pokemon professor. +The input is a Pokemon description and a validation error. +The description needs to be repaired based on the error. +Leave fields not mentioned in the error unchanged. +Answer using the following schema: {json.dumps(PokemonDescription.model_json_schema())} +""" + + +@lru_cache +def generate_description(user_prompt: str) -> PokemonDescription: + """Generate a Pokemon description based on the user's prompt.""" + LOGGER.debug('Generating a description based on user prompt: "%s"', user_prompt) + + messages = [ + { + "role": "system", + "content": DESCRIPTION_PROMPT, + }, + { + "role": "user", + "content": user_prompt, + }, + ] + + thinking_content, content = _prompt(messages) + + LOGGER.debug(thinking_content) + + try: + result = PokemonDescription.model_validate_json(content) + except ValidationError as e: + result = _repair(content, e) + + return result + + +def _repair(content: str, validation_error: ValidationError) -> PokemonDescription: + """Attempt to repair the given content based on the given validation error.""" + LOGGER.debug("Repairing content based on validation error: %s", validation_error) + LOGGER.debug("Original content: %s", content) + + messages = [ + { + "role": "system", + "content": REPAIR_PROMPT, + }, + { + "role": "user", + "content": f"Description: {content}\n\nError: {validation_error}", + }, + ] + + thinking_content, content = _prompt(messages) + + LOGGER.debug(thinking_content) + + return PokemonDescription.model_validate_json(content) + + +def _prompt(messages: list[dict[str, str]]) -> tuple[str, str]: + """Prompt the model with the given messages and return the generated text.""" + text = TOKENIZER.apply_chat_template( + messages, + tokenize=False, + add_generation_prompt=True, + enable_thinking=True, + ) + + model_inputs = TOKENIZER([text], return_tensors="pt").to(MODEL.device) + + # The magic numbers below taken from the model documentation, see https://huggingface.co/Qwen/Qwen3-1.7B#quickstart + generated_ids = MODEL.generate(**model_inputs, max_new_tokens=32768) + output_ids = generated_ids[0][len(model_inputs.input_ids[0]) :].tolist() + + try: + index = len(output_ids) - output_ids[::-1].index(151668) + except ValueError: + index = 0 + + thinking_content = TOKENIZER.decode( + output_ids[:index], + skip_special_tokens=True, + ).strip("\n") + content = TOKENIZER.decode(output_ids[index:], skip_special_tokens=True).strip("\n") + + return thinking_content, content diff --git a/calm-calatheas/calm_calatheas/settings.py b/calm-calatheas/calm_calatheas/settings.py new file mode 100644 index 00000000..7082121c --- /dev/null +++ b/calm-calatheas/calm_calatheas/settings.py @@ -0,0 +1,31 @@ +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """Settings for the application.""" + + host: str = Field( + default="localhost", + description="Host to bind the server to", + ) + + log_level: str = Field( + default="DEBUG", + description="Logging level for the application", + ) + + port: int = Field( + default=8000, + description="Port to bind the server to", + ) + + static_files_path: str = Field( + default="app", + description="Path to the static files directory", + ) + + model_config = SettingsConfigDict(extra="ignore") + + +settings = Settings() diff --git a/calm-calatheas/devenv.lock b/calm-calatheas/devenv.lock new file mode 100644 index 00000000..2250f543 --- /dev/null +++ b/calm-calatheas/devenv.lock @@ -0,0 +1,103 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1754418859, + "owner": "cachix", + "repo": "devenv", + "rev": "e13cd53579f6a0f441ac09230178dccb3008dd36", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1754416808, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "9c52372878df6911f9afc1e2a1391f55e4dfc864", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1753719760, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "0f871fffdc0e5852ec25af99ea5f09ca7be9b632", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} diff --git a/calm-calatheas/devenv.nix b/calm-calatheas/devenv.nix new file mode 100644 index 00000000..2c50bc3e --- /dev/null +++ b/calm-calatheas/devenv.nix @@ -0,0 +1,18 @@ +{ pkgs, ... }: +{ + packages = [ + pkgs.pre-commit + ]; + + languages.python = { + enable = true; + uv = { + enable = true; + sync.enable = true; + }; + }; + + languages.javascript = { + enable = true; + }; +} diff --git a/calm-calatheas/docs/assets/code/backend.svg b/calm-calatheas/docs/assets/code/backend.svg new file mode 100644 index 00000000..8cfedad5 --- /dev/null +++ b/calm-calatheas/docs/assets/code/backend.svg @@ -0,0 +1,103 @@ + + + + + + +G + + + +calm_calatheas + +calm_calatheas + + + +calm_calatheas___main__ + +calm_calatheas. +__main__ + + + +calm_calatheas_app + +calm_calatheas. +app + + + +calm_calatheas_app->calm_calatheas + + + + + +calm_calatheas_app->calm_calatheas___main__ + + + + + +calm_calatheas_logger + +calm_calatheas. +logger + + + +calm_calatheas_model + +calm_calatheas. +model + + + +calm_calatheas_logger->calm_calatheas_model + + + + + +calm_calatheas_model->calm_calatheas_app + + + + + +calm_calatheas_settings + +calm_calatheas. +settings + + + +calm_calatheas_settings->calm_calatheas + + + + + +calm_calatheas_settings->calm_calatheas___main__ + + + + + +calm_calatheas_settings->calm_calatheas_app + + + + + + +calm_calatheas_settings->calm_calatheas_logger + + + + + diff --git a/calm-calatheas/docs/assets/code/frontend.svg b/calm-calatheas/docs/assets/code/frontend.svg new file mode 100644 index 00000000..84d1c3d0 --- /dev/null +++ b/calm-calatheas/docs/assets/code/frontend.svg @@ -0,0 +1,471 @@ + + + + + + +G + + + +frontend + +frontend + + + +frontend_app + +frontend.app + + + +frontend_app->frontend + + + + + +frontend_components + +frontend. +components + + + +frontend_components->frontend_app + + + + + +frontend_components_camera + +frontend. +components. +camera + + + +frontend_components_footer + +frontend. +components. +footer + + + +frontend_components_camera->frontend_components_footer + + + + + +frontend_components_description + +frontend. +components. +description + + + +frontend_components_description->frontend_components + + + + + +frontend_components_description_dropdown + +frontend. +components. +description_dropdown + + + +frontend_components_description_dropdown->frontend_components + + + + + + +frontend_components_description_dropdown->frontend_components_description + + + + + +frontend_components_footer->frontend_components + + + + + +frontend_components_header + +frontend. +components. +header + + + +frontend_components_header->frontend_components + + + + + +frontend_components_loading_caption_model + +frontend. +components. +loading_caption_model + + + +frontend_components_loading_caption_model->frontend_components + + + + + +frontend_components_pokemon + +frontend. +components. +pokemon + + + +frontend_components_pokemon->frontend_components + + + + +frontend_components_theme + +frontend. +components. +theme + + + +frontend_components_theme->frontend_components + + + + +frontend_components_theme->frontend_components_header + + + + + +frontend_models + +frontend. +models + + + +frontend_models->frontend_components_description + + + + + + + + + +frontend_models->frontend_components_description_dropdown + + + + + + +frontend_models->frontend_components_pokemon + + + + + + +frontend_services_database + +frontend. +services. +database + + + +frontend_models->frontend_services_database + + + + + +frontend_services_description + +frontend. +services. +description + + + +frontend_models->frontend_services_description + + + + + +frontend_services_pokemon + +frontend. +services. +pokemon + + + +frontend_models->frontend_services_pokemon + + + + + +frontend_models_pokemon_description + +frontend. +models. +pokemon_description + + + +frontend_models_pokemon_description->frontend_models + + + + + +frontend_services + +frontend. +services + + + +frontend_services->frontend_app + + + + + +frontend_services->frontend_components_camera + + + + + + +frontend_services->frontend_components_description_dropdown + + + + + +frontend_services->frontend_components_footer + + + + +frontend_services->frontend_components_loading_caption_model + + + + + + +frontend_services->frontend_components_pokemon + + + + + + +frontend_services->frontend_components_theme + + + + + +frontend_services_camera + +frontend. +services. +camera + + + +frontend_services_camera->frontend_services + + + + + +frontend_services_caption + +frontend. +services. +caption + + + +frontend_services_caption->frontend_components_footer + + + + + + +frontend_services_caption->frontend_components_loading_caption_model + + + + + + +frontend_services_caption->frontend_services + + + + + + +frontend_services_caption->frontend_services_description + + + + + +frontend_services_caption->frontend_services_pokemon + + + + + +frontend_services_database->frontend_services + + + + + + +frontend_services_database->frontend_services_pokemon + + + + + +frontend_services_description->frontend_services + + + + +frontend_services_description->frontend_services_pokemon + + + + + +frontend_services_pokemon->frontend_app + + + + + +frontend_services_pokemon->frontend_components_description_dropdown + + + + + +frontend_services_pokemon->frontend_components_footer + + + + + + +frontend_services_pokemon->frontend_components_pokemon + + + + +frontend_services_pokemon->frontend_services + + + + + +frontend_services_reader + +frontend. +services. +reader + + + +frontend_services_reader->frontend_components_camera + + + + + +frontend_services_reader->frontend_components_footer + + + + +frontend_services_reader->frontend_services + + + + + +frontend_services_reader->frontend_services_caption + + + + + +frontend_services_reader->frontend_services_pokemon + + + + + +frontend_services_theme + +frontend. +services. +theme + + + +frontend_services_theme->frontend_components_theme + + + + + +frontend_services_theme->frontend_services + + + + + diff --git a/calm-calatheas/docs/assets/design/mvp.png b/calm-calatheas/docs/assets/design/mvp.png new file mode 100644 index 00000000..9dd87a7f Binary files /dev/null and b/calm-calatheas/docs/assets/design/mvp.png differ diff --git a/calm-calatheas/docs/assets/design/overview.drawio b/calm-calatheas/docs/assets/design/overview.drawio new file mode 100644 index 00000000..fda9d9bc --- /dev/null +++ b/calm-calatheas/docs/assets/design/overview.drawio @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/calm-calatheas/docs/assets/logo-128x128.png b/calm-calatheas/docs/assets/logo-128x128.png new file mode 100644 index 00000000..51375e09 Binary files /dev/null and b/calm-calatheas/docs/assets/logo-128x128.png differ diff --git a/calm-calatheas/docs/assets/logo-16x16.png b/calm-calatheas/docs/assets/logo-16x16.png new file mode 100644 index 00000000..c156031c Binary files /dev/null and b/calm-calatheas/docs/assets/logo-16x16.png differ diff --git a/calm-calatheas/docs/assets/pokemon/aquapuff.png b/calm-calatheas/docs/assets/pokemon/aquapuff.png new file mode 100644 index 00000000..31d74331 Binary files /dev/null and b/calm-calatheas/docs/assets/pokemon/aquapuff.png differ diff --git a/calm-calatheas/docs/assets/pokemon/chronoclock.png b/calm-calatheas/docs/assets/pokemon/chronoclock.png new file mode 100644 index 00000000..5644f15b Binary files /dev/null and b/calm-calatheas/docs/assets/pokemon/chronoclock.png differ diff --git a/calm-calatheas/docs/assets/pokemon/lumina.png b/calm-calatheas/docs/assets/pokemon/lumina.png new file mode 100644 index 00000000..e17b2e7c Binary files /dev/null and b/calm-calatheas/docs/assets/pokemon/lumina.png differ diff --git a/calm-calatheas/docs/assets/pokemon/shadowclaw.png b/calm-calatheas/docs/assets/pokemon/shadowclaw.png new file mode 100644 index 00000000..07c333f6 Binary files /dev/null and b/calm-calatheas/docs/assets/pokemon/shadowclaw.png differ diff --git a/calm-calatheas/docs/assets/user-guide/camera-active.png b/calm-calatheas/docs/assets/user-guide/camera-active.png new file mode 100644 index 00000000..a4e898bd Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/camera-active.png differ diff --git a/calm-calatheas/docs/assets/user-guide/camera-loading.gif b/calm-calatheas/docs/assets/user-guide/camera-loading.gif new file mode 100644 index 00000000..8711918e Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/camera-loading.gif differ diff --git a/calm-calatheas/docs/assets/user-guide/collection.png b/calm-calatheas/docs/assets/user-guide/collection.png new file mode 100644 index 00000000..3533c707 Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/collection.png differ diff --git a/calm-calatheas/docs/assets/user-guide/dark.png b/calm-calatheas/docs/assets/user-guide/dark.png new file mode 100644 index 00000000..7bfcceb9 Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/dark.png differ diff --git a/calm-calatheas/docs/assets/user-guide/details.png b/calm-calatheas/docs/assets/user-guide/details.png new file mode 100644 index 00000000..ed2132fb Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/details.png differ diff --git a/calm-calatheas/docs/assets/user-guide/favourite.png b/calm-calatheas/docs/assets/user-guide/favourite.png new file mode 100644 index 00000000..2068fadc Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/favourite.png differ diff --git a/calm-calatheas/docs/assets/user-guide/home.png b/calm-calatheas/docs/assets/user-guide/home.png new file mode 100644 index 00000000..5a2f4fde Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/home.png differ diff --git a/calm-calatheas/docs/assets/user-guide/light.png b/calm-calatheas/docs/assets/user-guide/light.png new file mode 100644 index 00000000..c86e9a88 Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/light.png differ diff --git a/calm-calatheas/docs/assets/user-guide/processing.gif b/calm-calatheas/docs/assets/user-guide/processing.gif new file mode 100644 index 00000000..6588bcb0 Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/processing.gif differ diff --git a/calm-calatheas/docs/assets/user-guide/results.png b/calm-calatheas/docs/assets/user-guide/results.png new file mode 100644 index 00000000..465c0967 Binary files /dev/null and b/calm-calatheas/docs/assets/user-guide/results.png differ diff --git a/calm-calatheas/docs/code/backend.md b/calm-calatheas/docs/code/backend.md new file mode 100644 index 00000000..dcb29b29 --- /dev/null +++ b/calm-calatheas/docs/code/backend.md @@ -0,0 +1,16 @@ +# Backend + +Below is a high-level overview of the backend architecture: + +[![Backend Architecture Overview](../assets/code/backend.svg)](../assets/code/backend.svg) + +This diagram was generated automatically using [`pydeps`](https://pypi.org/project/pydeps/). The detailed documentation +below was generated with [`mkdocstrings`](https://mkdocstrings.github.io/). + +::: calm_calatheas.app + +::: calm_calatheas.logger + +::: calm_calatheas.model + +::: calm_calatheas.settings diff --git a/calm-calatheas/docs/code/frontend.md b/calm-calatheas/docs/code/frontend.md new file mode 100644 index 00000000..e1c10931 --- /dev/null +++ b/calm-calatheas/docs/code/frontend.md @@ -0,0 +1,23 @@ +# Frontend + +Below is a high-level overview of the frontend architecture: + +[![Frontend Architecture Overview](../assets/code/frontend.svg)](../assets/code/frontend.svg) + +This diagram was generated automatically using [`pydeps`](https://pypi.org/project/pydeps/). + +!!! NOTE "Diagram completeness" + + To keep the diagram clear, relationships between components, services, and their base classes are not shown. + +The following sections provide detailed documentation, generated automatically with [`mkdocstrings`](https://mkdocstrings.github.io/). + +::: app.frontend + +::: app.frontend.base + +::: app.frontend.components + +::: app.frontend.models + +::: app.frontend.services diff --git a/calm-calatheas/docs/code/index.md b/calm-calatheas/docs/code/index.md new file mode 100644 index 00000000..15379c91 --- /dev/null +++ b/calm-calatheas/docs/code/index.md @@ -0,0 +1,7 @@ +# Code + +This section provides automatically generated documentation for the codebase, including an overview of the project's modules, +classes, and functions. + +It is intended for developers who want to understand or contribute to the project, as well as for code jam judges reviewing +the implementation. diff --git a/calm-calatheas/docs/contributor-guide/development-environment.md b/calm-calatheas/docs/contributor-guide/development-environment.md new file mode 100644 index 00000000..abd732b3 --- /dev/null +++ b/calm-calatheas/docs/contributor-guide/development-environment.md @@ -0,0 +1,147 @@ +# Development Environment + +Follow the steps below to set up your development environment. + +## Configure your SSH Key + +Follow the steps below to configure your SSH key for accessing the repository: + +1. [Generate an SSH key](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). +2. [Add the SSH key to your GitHub account](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account). + +## Clone the Repository + +To clone the repository, run the following command: + +```bash +git clone git@github.com:cj12-calm-calatheas/code-jam-12.git +``` + +This will clone the repository to your local machine using SSH. + +## Environment Setup + +To get started with the project, you can either install the [devcontainer](https://containers.dev) or follow the manual +setup instructions below. + +### Using the Devcontainer + +This project includes a [devcontainer](https://containers.dev) to automatically set up your development +environment, including the all tools and dependencies required for local development. + +??? NOTE "Prerequisites" + + Please ensure you have the following prerequisites installed: + + - [Docker](https://www.docker.com) must be installed on your system to use the devcontainer. + + - The [Remote Development Extension Pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) for Visual Studio Code must be installed to work with devcontainers. + +??? TIP "Use WSL on Windows" + + If you are using Windows, we **strongly** recommend cloning the repository into the [WSL](https://learn.microsoft.com/en-us/windows/wsl/) + filesystem instead of the Windows filesystem. This significantly improves I/O performance when running the devcontainer. + +#### Configure your SSH Agent + +The devcontainer will attempt to pick up your SSH key from your `ssh-agent` when it starts. Follow the +guide on [sharing git credentials with the devcontainer](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials) +to ensure your SSH key is available inside the container. + +#### Open the Repository + +To get started, navigate to the folder where you cloned the repository and run: + +```bash +code . +``` + +This will open the current directory in Visual Studio Code. + +#### Build the Environment + +Once Visual Studio Code is open, you will see a notification at the bottom right corner of the window asking if +you want to open the project in a devcontainer. Select `Reopen in Container`. + +Your development environment will now be set up automatically. + +??? QUESTION "What if I don't see the notification?" + + You can manually open the devcontainer by pressing `F1` to open the command pallette. Type + `>Dev Containers: Reopen in Container` and press `Enter` to select the command. + +??? EXAMPLE "Detailed Setup Guides" + + For more details, refer to the setup guide for your IDE: + + - [Visual Studio Code](https://code.visualstudio.com/docs/devcontainers/tutorial) + - [PyCharm](https://www.jetbrains.com/help/pycharm/connect-to-devcontainer.html) + +### Manual Setup + +Alternatively, you can set up the development environment manually by following the steps below. + +??? NOTE "Prerequisites" + + Please ensure you have the following prerequisites installed: + + - [Python 3.13](https://www.python.org/downloads/) must be installed on your system. + - [Node.js](https://nodejs.org) must be installed on your system for linting non-Python files. + + You can check your Python version with: + + ```bash + python --version + ``` + +#### Open the Repository + +Start by opening the repository in your terminal or command prompt. + +```bash +cd path/to/your/repository +``` + +#### Set up your Python Environment + +This project uses [uv](https://docs.astral.sh/uv/) for dependency management. If you don't have `uv` installed, you can +install it using pip: + +```bash +python -m pip install uv +``` + +To install the dependencies, run: + +```bash +uv venv --allow-existing && uv sync +``` + +This sets up a virtual environment and installs all required packages. + +#### Install Node.js Dependencies + +For linting non-Python files, we also require some Node.js dependencies. To install them, run: + +```bash +npm install +``` + +#### Set up Pre-commit Hooks + +To ensure code quality, this project uses pre-commit hooks. Install them by running: + +```bash +uv run pre-commit install +``` + +This will set up the pre-commit hooks to run automatically on each commit. + +#### Install Playwright + +This project uses [Playwright](https://playwright.dev/python/) to simulate user interactions for testing. To install the +required dependencies, run the following command: + +```bash +uv run playwright install --with-deps +``` diff --git a/calm-calatheas/docs/contributor-guide/documentation.md b/calm-calatheas/docs/contributor-guide/documentation.md new file mode 100644 index 00000000..7da0a531 --- /dev/null +++ b/calm-calatheas/docs/contributor-guide/documentation.md @@ -0,0 +1,67 @@ +# Documentation + +This page provides guidelines for contributing to the documentation. + +## Tools + +The documentation is built using [MkDocs](https://www.mkdocs.org/), a static site generator that converts Markdown +files into a website. + +Markdown is a lightweight markup language with plain-text formatting syntax. Refer to the [Markdown Guide](https://www.markdownguide.org) +for more information on how to use Markdown. + +This project uses the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme to generate the +documentation. Please review the theme documentation for guidance on how to use its various features. + +## Running the Documentation + +!!! NOTE "Prerequisites" + + Ensure you have [set up your development environment](./development-environment.md) before running the documentation. + +To view the documentation locally, you can use the following command: + +```bash +uv run mkdocs serve +``` + +Open your browser and navigate to [`http://localhost:8000`](http://localhost:8000) to view the documentation. +The changes you make to the documentation will be automatically reflected in the browser. + +## Adding a New Page + +To add a new page to the documentation, create a new Markdown file in the `docs` directory. + +Next, update the `nav` section in the `mkdocs.yaml` file to include the new page. The `nav` section defines the +structure of the documentation and the order in which the pages are displayed in the navigation bar. + +Please ensure that the folder structure in the `docs` directory matches the structure defined in the `nav` section. + +## Linting + +This project is configured to use [markdownlint](https://github.com/DavidAnson/markdownlint) to ensure consistent +Markdown styling and formatting across the documentation. The linter is automatically run when you commit changes +to the repository. + +You can configure the linter rules in the `.markdownlint.json` file. Refer to the [markdownlint rules](https://github.com/DavidAnson/markdownlint?tab=readme-ov-file#rules--aliases) +for more information on the available rules. + +!!! TIP "Use a Markdown Linter Extension" + + We recommend installing a Markdown linter extension in your editor to help identify and fix issues as you write. + The devcontainer is pre-configured with the [`markdownlint`](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) + extension for Visual Studio Code. + +## Formatting + +The documentation is formatted using [Prettier](https://prettier.io/), an opinionated code formatter that ensures +consistent style across the project. Prettier is automatically run when you save a Markdown file in the editor. + +You can configure the formatting rules in the `.prettierrc.json` file. Refer to the [Prettier options](https://prettier.io/docs/en/options.html) +for more information on the available options. + +## Publishing the Documentation + +The documentation is published automatically when changes are merged into the `main` branch. A GitHub Action workflow +is triggered to build the documentation and push it to the `gh-pages` branch. The published documentation is hosted +on GitHub Pages. diff --git a/calm-calatheas/docs/contributor-guide/index.md b/calm-calatheas/docs/contributor-guide/index.md new file mode 100644 index 00000000..4623788d --- /dev/null +++ b/calm-calatheas/docs/contributor-guide/index.md @@ -0,0 +1,6 @@ +# Contributor Guide + +This guide provides information on how to contribute to the project, including setting up your development environment, +using version control, and contributing to the documentation. + +It is intended for project members as well as the code jam judges. diff --git a/calm-calatheas/docs/contributor-guide/version-control.md b/calm-calatheas/docs/contributor-guide/version-control.md new file mode 100644 index 00000000..246c0e87 --- /dev/null +++ b/calm-calatheas/docs/contributor-guide/version-control.md @@ -0,0 +1,165 @@ +# Version Control + +Follow the steps below when contributing to the project. These steps ensure that all changes are properly tracked and reviewed. + +## Create a New Branch + +Always create a new branch for your changes. This makes it easier to handle multiple contributions simultaneously. + +??? QUESTION "Why should I create a new branch?" + + Creating a new branch allows you to work on your changes without affecting the `main` branch. This makes it + easier to collaborate with others and keep the codebase clean. + +First, pull the latest changes from the `main` branch: + +```bash +git pull main +``` + +Next, create a new branch with the following command: + +```bash +git checkout -b "" +``` + +Replace `` with a short, descriptive name for your branch. For example, `add-uptime-command`. + +## Commit your Changes + +On your local branch, you can make changes to the code such as adding new features, fixing bugs, or updating documentation. +Once you have made your changes, you can commit them to your branch. + +```bash +git add . +git commit -m "feat: add uptime command" +``` + +Make sure to write a clear and concise commit message that describes the changes you have made. + +??? QUESTION "How often should I commit my changes?" + + It's a good practice to commit your changes often. This allows you to track your progress and revert changes if needed. + +### Automated Checks + +The project includes pre-commit hooks to ensure your code meets the quality standards. These hooks run automatically +before each commit. + +??? QUESTION "What if the pre-commit hooks fail?" + + If the pre-commit hooks fail, you will need to address the issues before committing your changes. Follow the + instructions provided by the pre-commit hooks to identify and fix the issues. + +??? QUESTION "How do I run the pre-commit hooks manually?" + + Pre-commit hooks can also be run manually using the following command: + + ```bash + uv run pre-commit + ``` + +The pre-commit hooks are intended to help us keep the codebase maintainable. If there are rules that you believe +are too strict, please discuss them with the team. + +## Create a Pull Request + +Once you have completed your changes, it's time to create a pull request. A pull request allows your changes to +be reviewed and merged into the `main` branch. + +Before creating a pull request, ensure your branch is up to date with the latest changes from the `main` branch: + +```bash +git pull main +``` + +Next, push your changes to the repository: + +```bash +git push +``` + +Finally, [create a pull request on GitHub](https://github.com/cj12-calm-calatheas/code-jam-12/compare). Select +your branch as the source and the `main` branch as the base. + +Give your pull request a descriptive title that summarizes the changes you have made. In the pull request description, +provide a brief overview of the changes and any relevant information for reviewers. + +??? EXAMPLE "Pull Request Description" + + Here's an example of a good pull request description: + + ```plaintext + # feat: add uptime command + + This pull request adds a new uptime command to display the bot's uptime. + + ## Changes + + - Added a new command to display the bot's uptime + - Updated the help command to include information about the new command + + ## Notes + + - The new command is implemented in a separate file for better organization + - The command has been tested locally and works as expected + ``` + +### Automated Checks + +The same pre-commit hooks that run locally will also run automatically on the pull request. The workflow also +runs the tests to ensure everything is working correctly, and checks the docs for any broken links. + +??? QUESTION "What if the checks fail on the pull request?" + + If the checks fail on the pull request, you will need to address the issues in your branch and push + the changes. The checks will run again automatically. + + Please address any issues identified by the checks before requesting a review. + +## Ask for a Review + +All pull requests should be reviewed by at least one other team member before merging. The reviewer will provide +feedback and suggestions for improvement. + +Once the reviewer approves the pull request, you can merge it into the `main` branch. + +??? QUESTION "How do I request a review?" + + Request a review from a team member by [assigning them as a reviewer](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) + to your pull request. + +### Giving Feedback + +When providing feedback on a pull request, be constructive and specific. Point out areas for improvement and suggest +possible solutions. If you have any questions or concerns, don't hesitate to ask the author for clarification. + +A code review should focus on the following aspects: + +- Correctness and functionality +- Code quality and readability +- Adherence to the project guidelines + +??? EXAMPLE "Good Code Review Feedback" + + Here are some examples of good code review feedback: + + ```plaintext + - Great work on the new command! The implementation looks good overall. + - I noticed a small typo in the docstring. Could you update it to fix the typo? + - The logic in the new command is a bit complex. Consider breaking it down into smaller functions for clarity. + - The tests cover most of the functionality, but we are missing a test case for edge case X. Could you add a test for that? + ``` + +Always be respectful and considerate when giving feedback. Remember that the goal is to improve the code and help +the author grow as a developer. + +!!! SUCCESS "Be Positive" + + Don't forget to acknowledge the positive aspects of the contribution as well! + +## Merge the Pull Request + +Once the pull request has been approved and all checks have passed, you can merge it into the `main` branch. +To merge the pull request, click the "Merge" button on the pull request page. After merging, your branch will be automatically +deleted. diff --git a/calm-calatheas/docs/design/backend.md b/calm-calatheas/docs/design/backend.md new file mode 100644 index 00000000..c0c808c1 --- /dev/null +++ b/calm-calatheas/docs/design/backend.md @@ -0,0 +1,63 @@ +# Backend + +The Pokedexter backend is primarily responsible for serving the machine learning model for generating descriptions and serving +the static files for the frontend application. + +## Web Server + +The web server is built with [Starlette](https://www.starlette.io/), a lightweight [ASGI](https://en.wikipedia.org/wiki/Asynchronous_Server_Gateway_Interface) +framework for Python web applications. Starlette offers essential features for handling HTTP requests, routing, and middleware, +making it a straightforward choice for our backend. While we considered [FastAPI](https://fastapi.tiangolo.com/), we ultimately +selected Starlette for its simplicity and minimalism. + +The server serves static frontend files and exposes two main endpoints: + +- **Description Generation:** An endpoint that uses the machine learning model to generate Pokémon descriptions. +- **Healthcheck:** An endpoint for monitoring the server’s status. Used by the Docker container to ensure the service + is running. + +This setup keeps the backend focused and efficient, aligning with our design goals. + +## Description Generation + +The backend hosts a machine learning model that generates Pokémon descriptions. This model is accessed through the Description +Generation endpoint, allowing the frontend to request descriptions based on captions created in the browser. + +We use the [`Qwen/Qwen3-1.7B`](https://huggingface.co/Qwen/Qwen3-1.7B) model, a general-purpose text generator. After +testing various prompts and settings, we found that this model produces high-quality Pokémon descriptions from image captions. +However, the model is quite large. While it can run on a laptop or desktop for limited use, it does not scale well to many +users and user experience will degrade under heavy load. + +For best results, we recommend running the model on a machine that has a [GPU with CUDA support](https://en.wikipedia.org/wiki/CUDA#GPUs_supported) +(the oldest version we tested was CUDA 6.5 on an NVIDIA GeForce GTX 1080ti) and 16GB of RAM. In our experience, generating +a description typically takes less than a minute. + +We would have preferred to use a more lightweight model that could run directly in the browser. However, the lightweight +models we tested did not generate high-quality Pokémon descriptions. One possible solution would be to fine-tune or train +a smaller model specifically for this task, but this would have required more time and a dataset of high-quality Pokémon +descriptions, which were beyond our resources for the code jam. + +## Reverse Proxy + +We recommend deploying Pokedexter behind a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) acting as a +[TLS termination proxy](https://en.wikipedia.org/wiki/TLS_termination_proxy). Both the camera and PWA features require +a [secure browser context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts), which is only available +when the app is served over HTTPS. + +!!! DANGER "Secure browser context required" + + The camera and PWA features will not work outside of a secure browser context! + +A reverse proxy is not included in our stack, as most users will already have their own solution or use a managed reverse +proxy provided by their cloud platform. The deployment guide includes instructions for setting up a TLS termination proxy +with [Caddy](https://caddyserver.com/). + +If you do not have a valid TLS certificate, the app can only be used on `localhost`, since browsers treat it as a secure +context. + +## Docker + +Pokedexter can be easily deployed using [Docker](https://www.docker.com/). We provide a `Dockerfile` that sets up the necessary +environment and dependencies for running the application. The Docker image includes the web server, the machine learning +model, and all static files needed for the frontend. We do not publish the Docker image to a public registry, so users +will need to build it locally. diff --git a/calm-calatheas/docs/design/design-goals.md b/calm-calatheas/docs/design/design-goals.md new file mode 100644 index 00000000..468e906e --- /dev/null +++ b/calm-calatheas/docs/design/design-goals.md @@ -0,0 +1,13 @@ +# Design Goals + +Our primary design goals for the code jam project were: + +- **Browser-first:** The application should run entirely in the browser, using technologies like PyScript and Pyodide and + leveraging the browser APIs where possible. +- **Python-centric:** The project should be developed mainly in Python, minimizing reliance on other languages. +- **Modern web architecture:** The app’s structure should follow typical web application patterns, allowing us to assess + Python’s suitability for building modern web apps. +- **Mobile-friendly:** The application should provide a seamless experience on mobile devices, similar to an authentic Pokédex. + +As the project progressed, we aimed to add **offline capability** as a key feature. However, this goal was only partially +achieved, as some components ultimately did not run in the browser. diff --git a/calm-calatheas/docs/design/index.md b/calm-calatheas/docs/design/index.md new file mode 100644 index 00000000..5642df1f --- /dev/null +++ b/calm-calatheas/docs/design/index.md @@ -0,0 +1,4 @@ +# Design + +This section describes the design of the Pokedexter application. It is intended for developers, the code jam judges, and +anyone interested in understanding the architecture, components, and design decisions behind the system. diff --git a/calm-calatheas/docs/design/system-overview.md b/calm-calatheas/docs/design/system-overview.md new file mode 100644 index 00000000..a730c9cb --- /dev/null +++ b/calm-calatheas/docs/design/system-overview.md @@ -0,0 +1,12 @@ +# System Overview + +The diagram below illustrates the architecture of the Pokedexter system: + +![System Overview](../assets/design/overview.drawio) + +Most of the system runs in the browser, using technologies such as PyScript and Pyodide, and makes extensive use of browser +APIs. Features like object recognition and the database, which are often implemented as backend services, are handled in-browser +where possible. + +The backend is intentionally minimal to align with our design goals and the code jam theme. It mainly serves static frontend +assets and processes tasks that cannot be handled in the browser due to resource constraints. diff --git a/calm-calatheas/docs/design/web-app.md b/calm-calatheas/docs/design/web-app.md new file mode 100644 index 00000000..b60f3c17 --- /dev/null +++ b/calm-calatheas/docs/design/web-app.md @@ -0,0 +1,166 @@ +# Web App + +The web app is the core of the system, responsible for the user experience, interface, and interactions. Built with [PyScript](https://pyscript.net/) +and [Pyodide](https://pyodide.org/en/stable/), it runs Python code directly in the browser. The app includes a presentation +layer and modules for camera access, image processing, and database management. + +## Presentation Layer + +The presentation layer manages the user interface and interactions. It is structured as a [Single Page Application (SPA)](https://en.wikipedia.org/wiki/Single-page_application) +and follows a [Model-View-Presenter (MVP)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) architecture, +which separates UI components from business logic and state management. This design keeps the application architecture +similar to modern web frameworks like React or Angular, aligning with our key design goals. + +[![Model-View-Presenter Pattern](../assets/design/mvp.png)](../assets/design/mvp.png) + +### Components + +Views are organized as individual components, each responsible for a specific part of the user interface. They are all grouped +in the [`components`][app.frontend.components] module and inherit from a base [`Component`][app.frontend.base.component.Component] +class that implements rendering logic and lifecycle methods. + +The lifecycle of a component includes _initialization_, _rendering_, and _destruction_. This lifecycle is typically managed +by a parent component that coordinates these phases. + +```mermaid +graph TB + subgraph Component Lifecycle + direction LR + A[Initialization] --> B[Rendering] + B --> C[Destruction] + end +``` + +**Initialization**: The component is created but not yet displayed. This phase sets up the component’s internal state +and prepares it for rendering. + +**Rendering**: The component generates its HTML and inserts it into the DOM at its designated root element. Most components +also attach event listeners at this stage to handle user interactions and state changes. + +**Destruction**: The component is removed from the DOM and any resources it used are released. This includes detaching +event listeners and cleaning up internal state. + +Lifecycle hooks enable components to run custom logic before and after each phase of their lifecycle. These hooks provide +flexibility for setup, teardown, and responding to changes during initialization, rendering, and destruction. + +Components define their HTML structure using text-based templates, which are processed by a simple, custom-built templating +engine. This engine is intentionally minimal and does not support advanced features such as conditionals or loops, unlike +the templating systems in frameworks like React or Angular. Looking ahead, the upcoming [template strings](https://peps.python.org/pep-0750) +feature in Python 3.14 is expected to improve Python’s native HTML templating capabilities, as supporting this use case +is specifically mentioned in the PEP. + +### Services + +Services are responsible for providing business logic and state management for the application. They encapsulate the core +functionality and can be reused across different components. Most services exist globally and can be accessed by any component +that needs them, although they can also be owned by specific components if needed. + +All services are organized into the [`services`][app.frontend.services] module and inherit from the [`Service`][app.frontend.base.service.Service] +base class, which provides lifecycle management for all services. + +A service’s lifecycle consists of two main phases: _initialization_ and _destruction_. Global services are managed centrally, +while services owned by specific components are managed by those components. + +```mermaid +graph TB + subgraph Service Lifecycle + direction LR + A[Initialization] --> B[Destruction] + end +``` + +**Initialization**: The service is instantiated and its internal state is set up. This may include fetching initial data +or configuring dependencies. + +**Destruction**: The service is cleaned up when it is no longer needed. This involves releasing resources such as event +listeners, database sessions, or network connections. + +Lifecycle hooks allow services to execute custom logic before and after each phase, providing flexibility for setup, teardown, +and responding to changes during initialization and destruction. + +### State Management + +State is managed by services, which implement the observer pattern. This allows components to subscribe to state changes +and react as needed, decoupling state management from the presentation layer and ensuring a clean separation of concerns. + +Unlike frameworks such as React or Angular, which provide built-in concepts like signals and effects, PyScript does not +offer these features natively. Instead, we use the [reactivex](https://github.com/ReactiveX/RxPY) library to manage asynchronous +data streams and events, enabling reactive programming patterns within the application. + +## Camera + +The camera service manages access to the user's camera and handles image capture and permissions. + +Integrating camera functionality directly into the application ensures a seamless experience for users as they explore +and search for Pokémon, without needing to switch to a separate camera app. This approach also accommodates devices that +may not have a dedicated camera application. + +For users without a camera, or for those who wish to analyze existing images, the application also provides an image upload +feature. This allows users to select and process images from their device, ensuring accessibility and flexibility for all +users. + +## Object Recognition + +The object recognition service identifies and classifies objects within images, then generates captions describing them. +These captions are passed to the description generation model, which creates Pokémon-themed descriptions based on the recognized +objects. + +The object recognition service uses the [`Xenova/vit-gpt2-image-captioning`](https://huggingface.co/Xenova/vit-gpt2-image-captioning) +model, an image-to-text machine learning model. This lightweight model runs well even on devices with limited computing +power, such as smartphones. While it provides reasonable results for image captioning tasks, our experimentation shows +that its accuracy may vary depending on the input. + +We originally planned to use the [`transformers`](https://pypi.org/project/transformers/) library for Python to run the +model. Unfortunately, some of its dependencies are incompatible with Pyodide because they cannot be compiled to WebAssembly. + +We considered two options: moving the object recognition service to the backend to keep it in Python, or running the model +in the browser using [`transformers.js`](https://huggingface.co/docs/transformers.js/en/index) and accessing it from Python +via Pyodide. To keep with the spirit of the code jam, we chose to run the model entirely in the browser using `transformers.js`. + +While we would have preferred to leverage Python’s rich machine learning ecosystem for this feature, current limitations +prevent us from doing so. Enabling advanced machine learning capabilities directly in the browser would make Python a much +stronger choice for web development and help it move beyond experimental or hobby projects. + +There is a noticeable slowdown during model initialization and occasionally when processing images. To address this, we +tried running the model in a separate [web worker](https://docs.pyscript.net/2025.8.1/user-guide/workers/) to offload +processing from the main thread. However, we encountered errors loading the model in the worker and were unable to resolve +them within our timeframe. With more time, we believe this issue could be solved, but we chose to focus on other features. + +## Database + +The database is responsible for storing each user's Pokémon collection. + +We use [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) as our database solution because it +is a widely supported web standard that offers a robust, asynchronous API for managing large amounts of structured data +directly in the browser. + +Storing each user's collection in a client-side database means the data is kept locally in their browser. This approach +has some limitations: collections cannot be shared across devices or with other users, and all data will be lost if the +user clears their browser storage. We accept these trade-offs because using a browser-based database fits the code jam +theme and our design goals. For a future version, we would consider synchronizing the database with a remote server to +enable cross-device access and backups. + +## Progressive Web App + +Pokedexter is a [Progressive Web App (PWA)](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps), meaning +it can be installed on a user's device and run like a native app. When installed, it operates in standalone mode, providing +easier access and a familiar user experience. + +While some PWAs offer offline capabilities, Pokedexter currently relies on the backend for description generation and requires +an internet connection. A key improvement could be to explore or train a model that can run entirely on-device, enabling +a fully offline experience. This would enhance the fantasy of carrying a real Pokédex with you outdoors or in nature. + +## Browser Support + +Pokedexter is tested and works reliably on all major modern browsers, including Chromium-based browsers (such as Chrome +and Edge), WebKit-based browsers (Safari), and Firefox. To ensure consistent behavior across environments, we use [Playwright](https://playwright.dev/python/) +together with [Pytest](https://pytest.org/) and [Testcontainers](https://testcontainers.com/) for automated cross-browser +testing in a production-like environment. + +You can find the test scenarios in the `tests` folder. + +## CSS Framework + +We chose [Bulma](https://bulma.io/) as our CSS framework because it is a modern, responsive framework that makes it easy +to create visually appealing and responsive layouts. The choice for a CSS framework rather than writing custom styles from +scratch saved us some time to focus on the functionality of the app. diff --git a/calm-calatheas/docs/index.md b/calm-calatheas/docs/index.md new file mode 100644 index 00000000..14fce7cd --- /dev/null +++ b/calm-calatheas/docs/index.md @@ -0,0 +1,63 @@ +# Pokedexter + +_Who's that Pokémon?_ + +Ever wondered if your cat could be a Pokémon? Curious about what’s hiding in your cupboard? **Pokedexter** is an AI-powered +Pokédex that helps you discover Pokémon wherever you are. Just snap a photo, and Pokedexter will identify the Pokémon—maybe +even ones you never expected! + +Open Pokedexter on your phone and start discovering Pokémon all around you! + +
+ + + Shadowclaw + + + + AquaPuff + + + + ChronoClock + + + + Lumina + + +
+ +## Features + +- Identify Pokémon anywhere using AI image recognition and description generation. +- Build your personal Pokémon collection—your progress is saved locally. +- Use Pokedexter on any device that supports a modern web browser. +- Designed for mobile use with a responsive layout. +- Installable as a Progressive Web App (PWA). +- Powered by a technology stack that’s 99% Python. + +## Fit with the theme + +The theme for this year's code jam is to write a **browser-based** Python application that is **the wrong tool for the job**. + +Pokedexter is an intentionally out of place tool for Pokémon identification. It neither identifies real-world objects nor +actual Pokémon. Instead, it invites users to view their everyday surroundings through a playful, imaginative Pokémon perspective. + +Most modern web apps are built with JavaScript frameworks like React or Angular. With Pokedexter, we set out to see how +closely we could replicate a typical web app experience using Python instead of JavaScript. Our goal was to apply common +web development architecture and design patterns, while also taking advantage of Python’s strengths in data processing +and machine learning. + +## About the team + +This project has been built by the Calm Calatheas team for the [Python Discord Code Jam 2025](https://pythondiscord.com/events/code-jams/12/). +Please feel free to reach out if you have any questions, or need a hand with anything! + +| | Name | Contributions | +| --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------ | +| [TFBlunt](https://github.com/thijsfranck) | [TFBlunt](https://github.com/thijsfranck) | Team Lead, Frontend, Description Generation, Docs | +| [leoluy](https://github.com/leolhuile) | [leoluy](https://github.com/leolhuile) | Description Generation, Model Selection, Frontend Mockup, Ideation | +| [zike01](https://github.com/Zike01) | [Zike01](https://github.com/Zike01) | Ideation | +| [Flonc](https://github.com/FloncDev) | [Flonc](https://github.com/FloncDev) | Initial Frontend Prototype | +| [esmaycat](https://github.com/esmaycat) | [esmaycat](https://github.com/esmaycat) | Object Detection, Transformers.js integration, Favourites Feature | diff --git a/calm-calatheas/docs/setup-guide.md b/calm-calatheas/docs/setup-guide.md new file mode 100644 index 00000000..5fb64095 --- /dev/null +++ b/calm-calatheas/docs/setup-guide.md @@ -0,0 +1,163 @@ +# Setup Guide + +This guide provides a step-by-step approach on how to run Pokedexter. + +## Set up the development environment + +Follow the instructions in the [development environment setup guide](./contributor-guide/development-environment.md) to +set up your local environment. + +## Run the app locally + +The easiest way to run Pokedexter locally is to use the included [`taskipy`](https://pypi.org/project/taskipy/) configuration. +Run the following command: + +```bash +uv run task serve +``` + +This runs a development server that you can use to access the app from your local machine. This is great for trying out +the app yourself on the device where you are running the server. + +Keep reading if you'd like to deploy Pokedexter for production use, or if you'd like to access the app from another device +like a mobile phone or tablet. + +## Local HTTPS for Mobile Testing + +You may just want to test the application on a mobile device without setting up a full reverse proxy. Here's how to create a simple, self-signed HTTPS server for local testing. + +First, you'll need to create your own SSL/TLS certificate. This certificate will be used to encrypt the connection between your computer and the mobile device. To generate it, run the following command in the project's root directory: + +```bash +openssl req -x509 -keyout key.pem -out cert.pem -nodes +``` + +The command will prompt you for some information. When asked for the "Common Name", enter your computer's local IP address. For all other prompts, press Enter to accept the default values. This process will generate two files in your project's root directory: `key.pem` (your private key) and `cert.pem` (your self-signed certificate). + +You can now start the server with the following command: + +```bash +uv run uvicorn calm_calatheas.app:app \ + --host "0.0.0.0" \ + --port 4443 \ + --ssl-keyfile key.pem \ + --ssl-certfile cert.pem +``` + +Because the certificate is self-signed (i.e. not issued by a trusted authority), your browser will likely display a "certificate not trusted" warning. This is expected. You can safely bypass this warning to continue to your application. + +## Build the Docker image + +The easiest way to deploy Pokedexter is to use [Docker](https://www.docker.com/). To deploy Pokedexter, you must first +build the Docker image. + +!!! INFO "Prerequisite" + + Make sure you have [Docker](https://www.docker.com/) installed before proceeding. + +The project has a `taskipy` configuration that makes it easy to build the Docker image. Run the following command: + +```bash +uv run task build-docker +``` + +This first builds a `.whl` file for the project, and then uses that file to build the Docker image based on the included +`Dockerfile`. The docker image will be called `calm-calatheas:latest`. + +## Set the environment variables + +Pokedexter can be configured using environment variables. The following configuration options are available: + +| Environment Variable | Description | Default | +| -------------------- | --------------------------------------- | --------- | +| `HOST` | The address to bind the server to. | `0.0.0.0` | +| `LOG_LEVEL` | The logging level for the application. | `DEBUG` | +| `PORT` | The port to run the server on. | `8000` | +| `STATIC_FILES_PATH` | The path to the static files directory. | `app` | + +!!! NOTE "All settings are optional" + + You can run the app using the default settings without specifying any environment variables. + +See the [`Settings`][calm_calatheas.settings.Settings] documentation for more information. + +## Run the Docker container + +Once the image is built, you can deploy the app to an environment of your choice. + +!!! INFO "Minimum system specs" + + For a minimal deployment, we recommend **2 CPU cores** and **8GB of RAM**. We also recommend a GPU with at least + **4GB of VRAM** and **CUDA 6.5** support or higher. + +**If you are deploying Pokedexter to the cloud**, refer to your cloud provider's documentation on how to deploy a Docker +container. + +**If you are hosting Pokedexter yourself**, you can run the Docker container with the following command: + +```bash +docker run -p 8000:8000 calm-calatheas:latest +``` + +This runs the container and maps the default port `8000` to the host machine, allowing you to access the app at `http://localhost:8000`. + +!!! DANGER "Secure browser context required" + + Both the camera and PWA features require a [secure browser context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts), which is only available when the app is served over HTTPS or on `localhost`. + + Keep reading if your deployment will be accessed outside of `localhost`. + +## Set up a reverse proxy + +We recommend deploying Pokedexter behind a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) acting as a +[TLS termination proxy](https://en.wikipedia.org/wiki/TLS_termination_proxy). + +!!! INFO "Prerequisite" + + Make sure you have a registered [domain name](https://en.wikipedia.org/wiki/Domain_name) and that you have access + to the [DNS](https://en.wikipedia.org/wiki/Domain_Name_System) settings for that domain. + +**If you are deploying Pokedexter to the cloud**, we recommend that you use your cloud provider's gateway solution to set +up HTTPS for the app. + +**If you are hosting Pokedexter yourself**, here's a sample `docker-compose.yaml` file using [Caddy](https://caddyserver.com/): + +```yaml +name: pokedexter + +services: + reverse-proxy: + image: caddy:latest + command: caddy reverse-proxy --from :8000 --to app:8000 + depends_on: + app: + condition: service_healthy + ports: + - 8000:8000 + + app: + image: calm-calatheas:latest + ports: + - 8000 +``` + +This configuration sets up Caddy as a reverse proxy for your app, allowing you to access it securely over HTTPS. Caddy +will automatically obtain and renew SSL certificates for your domain using [Let's Encrypt](https://letsencrypt.org/). + +## Set up DNS + +Finally, set up a DNS record for your domain that points to the server where the reverse proxy is running: + +```plaintext +Type: A +Host: +Value: +TTL: 3600 +``` + +Replace `` with the public IP address of the machine running your reverse proxy. This will direct +traffic for `` to your server. + +!!! SUCCESS "Deployment complete" + + You can now access the app from any device at `https://:8000`! diff --git a/calm-calatheas/docs/stylesheets/table.css b/calm-calatheas/docs/stylesheets/table.css new file mode 100644 index 00000000..b7354a34 --- /dev/null +++ b/calm-calatheas/docs/stylesheets/table.css @@ -0,0 +1,11 @@ +/* + * Make tables full width by default + */ + +.md-typeset__table { + min-width: 100%; +} + +.md-typeset table:not([class]) { + display: table; +} diff --git a/calm-calatheas/docs/user-guide.md b/calm-calatheas/docs/user-guide.md new file mode 100644 index 00000000..91f3e734 --- /dev/null +++ b/calm-calatheas/docs/user-guide.md @@ -0,0 +1,151 @@ +# User Guide + +Welcome to the user guide for Pokedexter! This guide will help you get started building your Pokémon collection. + +Open Pokedexter in your favorite web browser to get started! + +!!! TIP "Use Pokedexter on your phone" + + Open Pokedexter on your phone to identify Pokémon wherever you are! + +## Home Page + +The home page is the main screen of the app. Let's go through its step by step: + +
+ + + Home + + +
+ +At the top of the page is a welcome message and some instructions on how to get started. Below that is your Pokémon collection, +which is empty for now. + +The app is telling us to take a picture or upload an image to discover the Pokémon inside. Let's do that now! + +## Taking a Picture + +To start taking a picture, click the camera icon at the bottom of the screen. This will pop up the camera interface, allowing +you to take a picture of the object you would like to identify. + +
+ + + Camera Loading + + + + Camera Active + + +
+ +While loading, you can see the camera interface preparing to take a picture. You may at this point be prompted to allow +camera access if you haven't already done so. + +!!! INFO "Grant Camera Access" + + You will not be able to use the camera feature until you grant access. If you deny access by mistake, you can refresh the page to + be prompted again, or enable it again in your device settings. + +Once ready, the camera image will appear on the screen. You can now take a picture by clicking the **capture** button. +This will process the image and attempt to identify the Pokémon within it. + +!!! TIP "Switching Cameras" + + If your device has more than one camera (such as front and back cameras), you can switch between them by clicking the **switch camera** + button in the camera interface, located next to the **capture** button. + +## Uploading an Image + +If you prefer to upload an image instead of using the camera, you can do so by clicking the **upload** button at the bottom +of the screen. This will open a file dialog, allowing you to select an image file from your device. + +!!! TIP "Access your Camera App" + + If you prefer to use your phone's camera app, it will also be available as an option in the upload dialog. + +Once you have selected an image, the app will process it and attempt to identify the Pokémon within it. + +## Analyzing the Image + +Now, the app will analyze the image and try to detect any Pokémon present. This may take a few moments, so please be patient. +While you wait, you can try to guess which Pokémon it might come up with! + +Once the image has been processed, the app will display the results, showing the identified Pokémon along with their details. + +
+ + + Processing + + + + Results + + +
+ +## Building your Collection + +Congratulations on capturing your first Pokémon! You can now view it in your collection and continue to add more Pokémon +as you discover them. + +!!! INFO "Saving Your Collection" + + Your collection is automatically saved on your device so you don't lose your progress. + +Here's what it looks like when there's a few more Pokémon in your collection: + +
+ + + Collection + + +
+ +## Managing your Collection + +Once you've started building your collection, you may want to show your love for a specific Pokémon. You can mark it as +a favorite by clicking the **favourite** button in the Pokémon details view. A heart icon will appear next to the Pokémon's +name! + +If you ever change your mind, you can also unfavourite a Pokémon by clicking the **unfavourite** button. If you'd like +to remove a Pokémon from your collection entirely, you can do so by clicking the **delete** button at the bottom of the +details view. + +
+ + + Details + + + + Favourite + + +
+ +## Switching the Theme + +By default, the app will use your system's theme preference (light or dark). However, you can change the theme manually +by clicking the **theme** buttons in the app settings. + +You can switch back to your system default by selecting **auto**. + +
+ + + Light Theme + + + + Dark Theme + + +
+ +Happy exploring! diff --git a/calm-calatheas/mkdocs.yaml b/calm-calatheas/mkdocs.yaml new file mode 100644 index 00000000..32b5e423 --- /dev/null +++ b/calm-calatheas/mkdocs.yaml @@ -0,0 +1,116 @@ +copyright: © 2025 Calm Calatheas 🪴 +repo_name: cj12-calm-calatheas/code-jam-12 +repo_url: https://github.com/cj12-calm-calatheas/code-jam-12 +site_name: Pokedexter +site_url: https://cj12-calm-calatheas.github.io/code-jam-12/ +dev_addr: 127.0.0.1:9000 + +theme: + favicon: assets/logo-16x16.png + logo: assets/logo-128x128.png + name: material + search: true + + features: + - content.code.copy + - navigation.indexes + - navigation.instant + - navigation.instant.progress + - navigation.path + - navigation.sections + - navigation.tabs + - search.highlight + + icon: + repo: fontawesome/brands/github + + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + primary: white + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + primary: black + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + +markdown_extensions: + - admonition + - attr_list + - codehilite + - md_in_html + - pymdownx.details + - pymdownx.snippets + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + clickable_checkbox: true + custom_checkbox: true + - toc: + permalink: true + +nav: + - User Guide: user-guide.md + - Setup Guide: setup-guide.md + - Contributor Guide: + - contributor-guide/index.md + - Development Environment: contributor-guide/development-environment.md + - Version Control: contributor-guide/version-control.md + - Documentation: contributor-guide/documentation.md + - Design: + - design/index.md + - Design Goals: design/design-goals.md + - System Overview: design/system-overview.md + - Web App: design/web-app.md + - Backend: design/backend.md + - Code: + - code/index.md + - Frontend: code/frontend.md + - Backend: code/backend.md + +plugins: + - autorefs + - drawio + - mkdocstrings: + handlers: + python: + options: + show_root_heading: true + show_object_full_path: false + show_symbol_type_heading: true + show_symbol_type_toc: true + show_signature: true + separate_signature: true + show_signature_annotations: true + signature_crossrefs: true + show_source: false + show_if_no_docstring: true + show_docstring_examples: true + - search + - tags + +extra: + generator: false + social: + - icon: fontawesome/brands/github + link: https://github.com/cj12-calm-calatheas/code-jam-12 + +extra_css: + - stylesheets/table.css diff --git a/calm-calatheas/package-lock.json b/calm-calatheas/package-lock.json new file mode 100644 index 00000000..dce2767f --- /dev/null +++ b/calm-calatheas/package-lock.json @@ -0,0 +1,454 @@ +{ + "name": "calm-calatheas", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "calm-calatheas", + "devDependencies": { + "nodemon": "3.1.10", + "prettier": "3.6.2", + "prettier-plugin-sort-json": "4.1.1", + "prettier-plugin-toml": "2.0.6" + } + }, + "node_modules/@taplo/core": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@taplo/core/-/core-0.2.0.tgz", + "integrity": "sha512-r8bl54Zj1In3QLkiW/ex694bVzpPJ9EhwqT9xkcUVODnVUGirdB1JTsmiIv0o1uwqZiwhi8xNnTOQBRQCpizrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@taplo/lib": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@taplo/lib/-/lib-0.5.0.tgz", + "integrity": "sha512-+xIqpQXJco3T+VGaTTwmhxLa51qpkQxCjRwezjFZgr+l21ExlywJFcDfTrNmL6lG6tqb0h8GyJKO3UPGPtSCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@taplo/core": "^0.2.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "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/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "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-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-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-sort-json": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-4.1.1.tgz", + "integrity": "sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/prettier-plugin-toml": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/prettier-plugin-toml/-/prettier-plugin-toml-2.0.6.tgz", + "integrity": "sha512-12N/wBuHa9jd/KVy9pRP20NMKxQfQLMseQCt66lIbLaPLItvGUcSIryE1eZZMJ7loSws6Ig3M2Elc2EreNh76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@taplo/lib": "^0.5.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/calm-calatheas/package.json b/calm-calatheas/package.json new file mode 100644 index 00000000..973ab7f8 --- /dev/null +++ b/calm-calatheas/package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "nodemon": "3.1.10", + "prettier": "3.6.2", + "prettier-plugin-sort-json": "4.1.1", + "prettier-plugin-toml": "2.0.6" + }, + "name": "calm-calatheas", + "private": true +} diff --git a/calm-calatheas/pyproject.toml b/calm-calatheas/pyproject.toml new file mode 100644 index 00000000..2bda1094 --- /dev/null +++ b/calm-calatheas/pyproject.toml @@ -0,0 +1,89 @@ +[project] +authors = [{ name = "esmecat" }, { name = "floncdev" }, { name = "leoluy" }, { name = "tfblunt" }, { name = "zike01" }] +description = "This is the project of the Calm Calatheas team for the Python Discord Code Jam 2025" +name = "calm-calatheas" +readme = "README.md" +requires-python = ">=3.13" +version = "0.0.0" + +dependencies = [ + "accelerate~=1.10", + "kernels~=0.9", + "pydantic~=2.11", + "pydantic-settings~=2.10", + "starlette~=0.47", + "transformers~=4.55", + "torch~=2.6", + "uvicorn~=0.35.0", +] + +[dependency-groups] +dev = [ + "bitsandbytes~=0.47", + "mkdocs~=1.6", + "mkdocs-drawio~=1.11", + "mkdocs-material~=9.6", + "mkdocstrings~=0.29", + "mkdocstrings-python~=1.16", + "playwright~=1.54", + "pre-commit~=4.2", + "pydeps~=3.0", + "pyodide-py~=0.28", + "pyright==1.1.403", + "pyscript~=0.3", + "pytest~=8.4", + "pytest-asyncio~=1.0", + "pytest-playwright~=0.7", + "pytest-sugar~=1.0", + "python-dotenv~=1.1", + "reactivex~=4.0", + "ruff~=0.12", + "taskipy~=1.14", + "testcontainers~=4.12", +] + +[tool.pyright] +exclude = [".venv"] +pythonVersion = "3.13" +reportMissingModuleSource = "none" # Pyodide "js" module is dynamic. +reportUnnecessaryTypeIgnoreComment = "error" +typeCheckingMode = "standard" +venv = ".venv" +venvPath = "." + +[tool.pytest.ini_options] +addopts = "--browser chromium --browser firefox --browser webkit --tracing retain-on-failure" +asyncio_default_fixture_loop_scope = "session" +asyncio_mode = "auto" +required_plugins = ["pytest-asyncio"] +testpaths = ["tests"] + +[tool.ruff] +line-length = 119 +target-version = "py313" + +[tool.ruff.lint] +ignore = ["D100", "D104", "D105", "D212", "D107", "FIX002", "LOG015", "PT001", "UP007", "UP045", "S311"] +select = ["ALL"] + +[tool.ruff.lint.per-file-ignores] +"**/conftest.py" = ["INP001"] +"**/test__*.py" = ["INP001", "S101", "PLR2004"] +"app/calm_calatheas/services/image_captioner.py" = ["RUF006"] +"app/frontend/components/description_dropdown.py" = ["SLF001"] +"app/main.py" = ["INP001"] +"typings/js.pyi" = ["ALL"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.setuptools] +packages = ["calm_calatheas"] + +[tool.taskipy.tasks] +build = "uv build" +build-docker = "task build && docker build . -t calm-calatheas:latest" +build-docs = "mkdocs build --strict" +docs = "mkdocs serve" +serve = "python -m calm_calatheas --log_level DEBUG" +test = "pytest" diff --git a/calm-calatheas/tests/assets/elephant.jpg b/calm-calatheas/tests/assets/elephant.jpg new file mode 100644 index 00000000..63321aeb Binary files /dev/null and b/calm-calatheas/tests/assets/elephant.jpg differ diff --git a/calm-calatheas/tests/conftest.py b/calm-calatheas/tests/conftest.py new file mode 100644 index 00000000..47a803f1 --- /dev/null +++ b/calm-calatheas/tests/conftest.py @@ -0,0 +1,49 @@ +from collections.abc import Generator +from pathlib import Path + +import pytest +from playwright.sync_api import Page, expect +from testcontainers.compose import DockerCompose + +DESCRIPTION_GENERATED_TIMEOUT_MS = 600000 +CAPTION_MODEL_LOADED_TIMEOUT_MS = 30000 +PYSCRIPT_READY_TIMEOUT_MS = 30000 + + +@pytest.fixture(scope="session") +def compose() -> Generator[DockerCompose]: + """Return a Docker Compose instance.""" + with DockerCompose(context=Path(__file__).parent.absolute(), build=True) as compose: + yield compose + + +@pytest.fixture(scope="session") +def base_url(compose: DockerCompose) -> str: + """Return the base URL for the application.""" + port = compose.get_service_port("app", 8000) + return f"http://localhost:{port}" + + +@pytest.fixture() +def app(base_url: str, page: Page) -> Page: + """Navigate to the home page, wait for PyScript load and return the page instance.""" + page.goto(base_url) + + page.wait_for_event( + event="console", + predicate=lambda event: "PyScript Ready" in event.text, + timeout=PYSCRIPT_READY_TIMEOUT_MS, + ) + + return page + + +@pytest.fixture() +def model_loaded(app: Page) -> Page: + """Wait for the caption model to be loaded.""" + notification = app.get_by_text("Loading the model for generating captions") + + expect(notification).to_be_visible() + expect(notification).not_to_be_visible(timeout=CAPTION_MODEL_LOADED_TIMEOUT_MS) + + return app diff --git a/calm-calatheas/tests/docker-compose.yaml b/calm-calatheas/tests/docker-compose.yaml new file mode 100644 index 00000000..653046d6 --- /dev/null +++ b/calm-calatheas/tests/docker-compose.yaml @@ -0,0 +1,7 @@ +name: calm-calatheas-test + +services: + app: + image: calm-calatheas:latest + ports: + - 8000 diff --git a/calm-calatheas/tests/test__calm_calatheas.py b/calm-calatheas/tests/test__calm_calatheas.py new file mode 100644 index 00000000..492fe9da --- /dev/null +++ b/calm-calatheas/tests/test__calm_calatheas.py @@ -0,0 +1,218 @@ +import re +from pathlib import Path + +import pytest +from conftest import DESCRIPTION_GENERATED_TIMEOUT_MS, PYSCRIPT_READY_TIMEOUT_MS +from playwright.sync_api import Page, expect + + +def test__main_page_has_welcome_message(app: Page) -> None: + """ + Test that the main page has a welcome message. + + Asserts: + - The welcome message is visible on the page. + """ + expect(app.get_by_text(re.compile("Welcome to your Pokedex!"))).to_be_visible() + + +def test__default_theme_is_system_preferred(app: Page) -> None: + """ + Test that the default theme is the system preferred theme. + + Asserts: + - The system preferred theme is applied by default. + """ + expect(app.locator("html")).not_to_have_attribute("data-theme", re.compile("dark|light")) + + +def test__switch_to_dark_theme(app: Page) -> None: + """ + Test that switching to the dark theme works. + + Asserts: + - The dark theme is applied when the user selects it. + """ + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + dark_mode_selector = theme_selector.locator(".navbar-item", has_text="Dark") + expect(dark_mode_selector).to_be_visible() + dark_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "dark") + + +def test__switch_to_light_theme(app: Page) -> None: + """ + Test that switching to the light theme works. + + Asserts: + - The light theme is applied when the user selects it. + """ + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + light_mode_selector = theme_selector.locator(".navbar-item", has_text="Light") + expect(light_mode_selector).to_be_visible() + light_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "light") + + +def test__switch_to_system_theme(app: Page) -> None: + """ + Test that switching to the system theme works. + + Asserts: + - The system theme is applied when the user selects it. + """ + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + # First switch to light theme to ensure a theme is applied + dark_mode_selector = theme_selector.locator(".navbar-item", has_text="Dark") + expect(dark_mode_selector).to_be_visible() + dark_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "dark") + + # Next switch (back) to the system theme + auto_mode_selector = theme_selector.locator(".navbar-item", has_text="Auto") + expect(auto_mode_selector).to_be_visible() + auto_mode_selector.click() + + expect(app.locator("html")).not_to_have_attribute("data-theme", re.compile("dark|light")) + + +def test__theme_is_restored_after_refresh(app: Page) -> None: + """ + Test that the selected theme is restored after a page refresh. + + Asserts: + - The theme remains consistent after refreshing the page. + """ + # Switch to dark theme + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + dark_mode_selector = theme_selector.locator(".navbar-item", has_text="Dark") + expect(dark_mode_selector).to_be_visible() + dark_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "dark") + + # Refresh the page + app.reload() + + app.wait_for_event( + event="console", + predicate=lambda event: "PyScript Ready" in event.text, + timeout=PYSCRIPT_READY_TIMEOUT_MS, + ) + + # Check that the dark theme is still applied + expect(app.locator("html")).to_have_attribute("data-theme", "dark") + + +@pytest.mark.parametrize( + "path", + [ + Path(__file__).parent / "assets/elephant.jpg", + ], +) +def test__description_is_generated_after_uploading_an_image(model_loaded: Page, path: Path) -> None: + """ + Test that a description is generated after uploading an image through the file input. + + Asserts: + - The description is generated and displayed after the image is uploaded. + - A placeholder is shown while the description is being generated. + """ + model_loaded.locator("input[type='file']").set_input_files(path) + + # Expect a placeholder to appear while the description is being generated + placeholder = model_loaded.locator(".pokemon-description", has=model_loaded.locator(".is-skeleton")) + expect(placeholder).to_be_visible() + + # Wait for the description to be generated + description = model_loaded.locator(".pokemon-description", has_not=model_loaded.locator(".is-skeleton")) + expect(description).to_be_visible(timeout=DESCRIPTION_GENERATED_TIMEOUT_MS) + + +@pytest.mark.parametrize( + "path", + [ + Path(__file__).parent / "assets/elephant.jpg", + ], +) +def test__description_is_still_available_after_refresh(model_loaded: Page, path: Path) -> None: + """ + Test that the generated description is still available after refreshing the page. + + Asserts: + - The description is still visible after a page refresh. + """ + model_loaded.locator("input[type='file']").set_input_files(path) + + # Expect a placeholder to appear while the description is being generated + placeholder = model_loaded.locator(".pokemon-description", has=model_loaded.locator(".is-skeleton")) + expect(placeholder).to_be_visible() + + # Wait for the description to be generated + description = model_loaded.locator(".pokemon-description", has_not=model_loaded.locator(".is-skeleton")) + expect(description).to_be_visible(timeout=DESCRIPTION_GENERATED_TIMEOUT_MS) + + # Refresh the page + model_loaded.reload() + + model_loaded.wait_for_event( + event="console", + predicate=lambda event: "PyScript Ready" in event.text, + timeout=PYSCRIPT_READY_TIMEOUT_MS, + ) + + # Check that the description is still visible + after_refresh = model_loaded.locator(".pokemon-description", has_not=model_loaded.locator(".is-skeleton")) + expect(after_refresh).to_be_visible() + + +@pytest.mark.parametrize( + "path", + [ + Path(__file__).parent / "assets/elephant.jpg", + ], +) +def test__description_can_be_deleted(model_loaded: Page, path: Path) -> None: + """ + Test that the generated description can be deleted. + + Asserts: + - The description is removed from the DOM after deletion. + """ + model_loaded.locator("input[type='file']").set_input_files(path) + + # Expect a placeholder to appear while the description is being generated + placeholder = model_loaded.locator(".pokemon-description", has=model_loaded.locator(".is-skeleton")) + expect(placeholder).to_be_visible() + + # Wait for the description to be generated + description = model_loaded.locator(".pokemon-description", has_not=model_loaded.locator(".is-skeleton")) + expect(description).to_be_visible(timeout=DESCRIPTION_GENERATED_TIMEOUT_MS) + + # Open the context menu + context_menu = description.locator(".dropdown") + expect(context_menu).to_be_visible() + context_menu.hover() + + # Delete the description + delete_button = context_menu.locator("button", has_text="Delete") + expect(delete_button).to_be_visible() + delete_button.click() + + # Check that the description is removed + expect(description).not_to_be_visible() diff --git a/calm-calatheas/typings/js.pyi b/calm-calatheas/typings/js.pyi new file mode 100644 index 00000000..e7c68ba8 --- /dev/null +++ b/calm-calatheas/typings/js.pyi @@ -0,0 +1,323 @@ +# ruff: noqa +# Pyodide already has a js.pyi but it is not complete for us, +# and is not even able to be used. + +# Use https://developer.mozilla.org/en-US/docs/Web/API as reference. + +from collections.abc import Callable, Iterable +from typing import Any, Coroutine, Generic, Literal, Sequence, TypeAlias, TypeVar, overload, Self + +from _pyodide._core_docs import _JsProxyMetaClass +from pyodide.ffi import JsArray, JsDomElement as OldJSDomElement, JsException, JsFetchResponse, JsProxy, JsTypedArray +from pyodide.webloop import PyodideFuture + +class JsDomElement(OldJSDomElement): + classList: DOMTokenList + innerText: str + + @property + def children(self) -> Sequence[JsDomElement]: ... + def getAttribute(self, name: str) -> str: ... + def setAttribute(self, name: str, value: str) -> None: ... + def closest(self, selectors: str) -> JsDomElement: ... + def hasAttribute(self, attrName: str) -> bool: ... + def removeAttribute(self, attrName: str) -> None: ... + def getBoundingClientRect(self) -> DOMRect: ... + # These are on Node, which an Element is. + # As far as this project is concerned, they are just elements. + firstChild: JsDomElement | None + nextSibling: JsDomElement | None + def removeChild(self, child: JsDomElement) -> None: ... + def insertBefore(self, newChild: JsDomElement, refChild: JsDomElement) -> None: ... + def contains(self, otherNode: JsDomElement) -> bool: ... + +class JsButtonElement(JsDomElement): + def click(self) -> None: ... + +class JsVideoElement(JsDomElement): + autoplay: bool + controls: bool + src: str + srcObject: MediaStream | None + videoHeight: int + videoWidth: int + def play(self) -> PyodideFuture[None]: ... + +class JsImgElement(JsDomElement): + src: str + width: int + height: int + naturalWidth: int + naturalHeight: int + +class JsInputElement(JsDomElement): + value: str + def click(self) -> None: ... + +class JsFileInputElement(JsInputElement): + files: FileList + +class JsCanvasElement(JsDomElement): + width: int + height: int + @overload + def getContext(self, contextId: Literal["2d"]) -> CanvasRenderingContext2D: ... + @overload + def getContext(self, contextId: str) -> JsProxy: ... + def getContext(self, contextId: str) -> JsProxy: ... + def toBlob(self, callback: Callable[[Blob], None], mimeType: str) -> None: ... + +class JsAnchorElement(JsDomElement): + href: str + target: str + download: str + def click(self) -> None: ... + +class DOMRect(JsProxy): + top: float + left: float + bottom: float + right: float + width: float + height: float + +class CanvasRenderingContext2D(JsProxy): + canvas: JsCanvasElement + @overload + def drawImage(self, image: JsImgElement | JsVideoElement, dx: int, dy: int) -> None: ... + @overload + def drawImage(self, image: JsImgElement | JsVideoElement, dx: int, dy: int, dWidth: int, dHeight: int) -> None: ... + def drawImage( + self, image: JsImgElement | JsVideoElement, dx: int, dy: int, dWidth: int = ..., dHeight: int = ... + ) -> None: ... + def translate(self, x: int | float, y: int | float) -> None: ... + def rotate(self, angle: int | float) -> None: ... + +class DOMTokenList(JsProxy): + def add(self, token: str) -> None: ... + def remove(self, *tokens: str) -> None: ... + def contains(self, token: str) -> bool: ... + +class CSSStyleDeclaration(JsProxy): + def removeProperty(self, name: str) -> None: ... + def setProperty(self, name: str, value: str) -> None: ... + def __setattr__(self, name: str, value: str) -> None: ... + +ElementT = TypeVar("ElementT", bound=JsDomElement) + +class LoadEvent(JsProxy, Generic[ElementT]): + target: ElementT + +class Event(JsProxy): + target: JsDomElement + def preventDefault(self) -> None: ... + +class UIEvent(Event): ... + +class MouseEvent(Event): + relatedTarget: JsDomElement + pageX: float + pageY: float + +class DragEvent(MouseEvent): + dataTransfer: DataTransfer + +class DataTransfer(JsProxy): + dropEffect: str + def setData(self, format: str, data: str) -> None: ... + def setDragImage(self, image: JsDomElement, x: int, y: int) -> None: ... + def getData(self, format: str) -> str: ... + +class FileList: + length: int + def item(self, index: int) -> File: ... + +class File(Blob): + name: str + +class FileReader(JsProxy): + result: str + onload: Callable[[Event], Any] + onerror: Callable[[Event], Any] + @classmethod + def new(cls) -> Self: ... + def readAsDataURL(self, file: File) -> None: ... + +def eval(code: str) -> Any: ... + +# in browser the cancellation token is an int, in node it's a special opaque +# object. +_CancellationToken: TypeAlias = int | JsProxy + +def setTimeout(cb: Callable[[], Any], timeout: int | float) -> _CancellationToken: ... +def clearTimeout(id: _CancellationToken) -> None: ... +def setInterval(cb: Callable[[], Any], interval: int | float) -> _CancellationToken: ... +def clearInterval(id: _CancellationToken) -> None: ... +def fetch( + url: str, + options: JsProxy | None = None, +) -> PyodideFuture[JsFetchResponse]: ... + +indexedDB: Any = ... +type IDBDatabase = Any +localStorage: LocalStorage = ... +sessionStorage: SessionStorage = ... +console: Console = ... +self: Any = ... +window: Any = ... + +class DOMParser(JsProxy): + def parseFromString(self, string: str, type: str) -> document: ... + +class LocalStorage: + def getItem(self, key: str) -> str | None: ... + def setItem(self, key: str, value: str) -> None: ... + def removeItem(self, key: str) -> None: ... + def clear(self) -> None: ... + +class SessionStorage: + def getItem(self, key: str) -> str | None: ... + def setItem(self, key: str, value: str) -> None: ... + def removeItem(self, key: str) -> None: ... + def clear(self) -> None: ... + +class Console: + def log(self, *values: object) -> None: ... + def error(self, *values: object) -> None: ... + +# Shenanigans to convince skeptical type system to behave correctly: +# +# These classes we are declaring are actually JavaScript objects, so the class +# objects themselves need to be instances of JsProxy. So their type needs to +# subclass JsProxy. We do this with a custom metaclass. + +class _JsMeta(_JsProxyMetaClass, JsProxy): ... +class _JsObject(metaclass=_JsMeta): ... + +class XMLHttpRequest(_JsObject): + response: str + + @staticmethod + def new() -> XMLHttpRequest: ... + def open(self, method: str, url: str, sync: bool) -> None: ... + def send(self, body: JsProxy | None = None) -> None: ... + +class Object(_JsObject): + @staticmethod + def fromEntries(it: Iterable[JsArray[Any]]) -> JsProxy: ... + +class Array(_JsObject): + @staticmethod + def new() -> JsArray[Any]: ... + +class ImageData(_JsObject): + @staticmethod + def new(width: int, height: int, settings: JsProxy | None = None) -> ImageData: ... + + width: int + height: int + +class _TypedArray(_JsObject): + @staticmethod + def new( + a: int | Iterable[int | float] | JsProxy | None, + byteOffset: int = 0, + length: int = 0, + ) -> JsTypedArray: ... + +class Uint8Array(_TypedArray): + BYTES_PER_ELEMENT = 1 + +class Float64Array(_TypedArray): + BYTES_PER_ELEMENT = 8 + +class JSON(_JsObject): + @staticmethod + def stringify(a: JsProxy) -> str: ... + @staticmethod + def parse(a: str) -> JsProxy: ... + +class document(_JsObject): + body: JsDomElement + children: list[JsDomElement] + @staticmethod + def getElementById(id: str) -> JsDomElement: ... + @overload + @staticmethod + def createElement(tagName: Literal["a"]) -> JsAnchorElement: ... + @overload + @staticmethod + def createElement(tagName: Literal["img"]) -> JsImgElement: ... + @overload + @staticmethod + def createElement(tagName: Literal["canvas"]) -> JsCanvasElement: ... + @overload + @staticmethod + def createElement(tagName: str) -> JsDomElement: ... + @staticmethod + def createElement(tagName: str) -> JsDomElement: ... + @staticmethod + def appendChild(child: JsDomElement) -> None: ... + +class navigator(_JsObject): + mediaDevices: MediaDevices + +class MediaDevices(_JsObject): + def enumerateDevices(self) -> PyodideFuture[JsArray[MediaDeviceInfo]]: ... + def getUserMedia(self, constraints: dict[str, Any]) -> PyodideFuture[MediaStream]: ... + +class MediaDeviceInfo(_JsObject): + @property + def deviceId(self) -> str: ... + @property + def kind(self) -> str: ... + @property + def label(self) -> str: ... + +class MediaStream(JsProxy): + @property + def active(self) -> bool: ... + @property + def id(self) -> str: ... + def addTrack(self, track: MediaStreamTrack) -> None: ... + def clone(self) -> MediaStream: ... + def getAudioTracks(self) -> JsArray[MediaStreamTrack]: ... + def getTrackById(self, id: str) -> MediaStreamTrack | None: ... + def getTracks(self) -> JsArray[MediaStreamTrack]: ... + def getVideoTracks(self) -> JsArray[MediaStreamTrack]: ... + def removeTrack(self, track: MediaStreamTrack) -> None: ... + +class MediaStreamTrack(_JsObject): + @property + def id(self) -> str: ... + @property + def kind(self) -> str: ... + @property + def label(self) -> str: ... + @property + def muted(self) -> bool: ... + @property + def readyState(self) -> str: ... + def stop(self) -> None: ... + +class ImageCapture(JsProxy): + def takePhoto(self) -> PyodideFuture[Blob]: ... + +class URL(_JsObject): + @staticmethod + def createObjectURL(blob: Blob) -> str: ... + @staticmethod + def revokeObjectURL(url: str) -> None: ... + +class DOMException(JsException): ... + +type Blob = Any + +# Transformers.js + +class _Pipeline(JsProxy): + def __call__( + self, action: str, model: str, options: dict + ) -> Coroutine[None, None, Callable[[Any], Coroutine[None, None, Any]]]: ... + +pipeline: _Pipeline = ... diff --git a/calm-calatheas/typings/pyscript.pyi b/calm-calatheas/typings/pyscript.pyi new file mode 100644 index 00000000..66689652 --- /dev/null +++ b/calm-calatheas/typings/pyscript.pyi @@ -0,0 +1,12 @@ +from collections.abc import Awaitable +from typing import Any + +sync: Any = ... +window: Any = ... +workers: dict[str, Awaitable[PyWorker]] = ... + +class PyWorker: + sync: Any = ... + def __init__(self, file: str) -> None: ... + @property + async def ready(self) -> bool: ... diff --git a/calm-calatheas/typings/transformers_js.pyi b/calm-calatheas/typings/transformers_js.pyi new file mode 100644 index 00000000..d9db66fb --- /dev/null +++ b/calm-calatheas/typings/transformers_js.pyi @@ -0,0 +1,5 @@ +class ModelOutputItem: + generated_text: str + +class ModelOutput: + def at(self, index: int) -> ModelOutputItem: ... diff --git a/calm-calatheas/uv.lock b/calm-calatheas/uv.lock new file mode 100644 index 00000000..36cce27b --- /dev/null +++ b/calm-calatheas/uv.lock @@ -0,0 +1,1623 @@ +requires-python = ">=3.13" +revision = 3 +version = 1 + +[[package]] +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, +] +name = "accelerate" +sdist = { url = "https://files.pythonhosted.org/packages/f7/66/be171836d86dc5b8698b3a9bf4b9eb10cb53369729939f88bf650167588b/accelerate-1.10.0.tar.gz", hash = "sha256:8270568fda9036b5cccdc09703fef47872abccd56eb5f6d53b54ea5fb7581496", size = 392261, upload-time = "2025-08-07T10:54:51.664Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.10.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/dd/0107f0aa179869ee9f47ef5a2686abd5e022fdc82af901d535e52fe91ce1/accelerate-1.10.0-py3-none-any.whl", hash = "sha256:260a72b560e100e839b517a331ec85ed495b3889d12886e79d1913071993c5a3", size = 374718, upload-time = "2025-08-07T10:54:49.988Z" }, +] + +[[package]] +name = "annotated-types" +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.7.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +dependencies = [{ name = "idna" }, { name = "sniffio" }] +name = "anyio" +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.10.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "babel" +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.17.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backrefs" +sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" } +source = { registry = "https://pypi.org/simple" } +version = "5.9" +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b", size = 399843, upload-time = "2025-06-22T19:34:09.68Z" }, + { url = "https://files.pythonhosted.org/packages/fc/24/b29af34b2c9c41645a9f4ff117bae860291780d73880f449e0b5d948c070/backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9", size = 411762, upload-time = "2025-06-22T19:34:11.037Z" }, + { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" }, +] + +[[package]] +dependencies = [{ name = "soupsieve" }, { name = "typing-extensions" }] +name = "beautifulsoup4" +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.13.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, +] + +[[package]] +dependencies = [{ name = "numpy" }, { name = "torch" }] +name = "bitsandbytes" +source = { registry = "https://pypi.org/simple" } +version = "0.47.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/eb/477d6b5602f469c7305fd43eec71d890c39909f615c1d7138f6e7d226eff/bitsandbytes-0.47.0-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:2f805b76891a596025e9e13318b675d08481b9ee650d65e5d2f9d844084c6521", size = 30004641, upload-time = "2025-08-11T18:51:20.524Z" }, + { url = "https://files.pythonhosted.org/packages/9c/40/91f1a5a694f434bc13cba160045fdc4e867032e627b001bf411048fefd9c/bitsandbytes-0.47.0-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:68f3fffd494a47ed1fd7593bfc5dd2ac69b68260599b71b4c4b3a32f90f3b184", size = 61284639, upload-time = "2025-08-11T18:51:23.581Z" }, + { url = "https://files.pythonhosted.org/packages/18/a9/e07a227f1cd6562844cea2f05ee576b0991a9a91f45965c06034178ba0f6/bitsandbytes-0.47.0-py3-none-win_amd64.whl", hash = "sha256:4880a6d42ca9628b5a571c8cc3093dc3f5f52511e5a9e47d52d569807975531a", size = 60725121, upload-time = "2025-08-11T18:51:27.543Z" }, +] + +[[package]] +dependencies = [ + { name = "accelerate" }, + { name = "kernels" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "starlette" }, + { name = "torch" }, + { name = "transformers" }, + { name = "uvicorn" }, +] +name = "calm-calatheas" +source = { virtual = "." } +version = "0.0.0" + +[package.dev-dependencies] +dev = [ + { name = "bitsandbytes" }, + { name = "mkdocs" }, + { name = "mkdocs-drawio" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings" }, + { name = "mkdocstrings-python" }, + { name = "playwright" }, + { name = "pre-commit" }, + { name = "pydeps" }, + { name = "pyodide-py" }, + { name = "pyright" }, + { name = "pyscript" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-playwright" }, + { name = "pytest-sugar" }, + { name = "python-dotenv" }, + { name = "reactivex" }, + { name = "ruff" }, + { name = "taskipy" }, + { name = "testcontainers" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate", specifier = "~=1.10" }, + { name = "kernels", specifier = "~=0.9" }, + { name = "pydantic", specifier = "~=2.11" }, + { name = "pydantic-settings", specifier = "~=2.10" }, + { name = "starlette", specifier = "~=0.47" }, + { name = "torch", specifier = "~=2.6" }, + { name = "transformers", specifier = "~=4.55" }, + { name = "uvicorn", specifier = "~=0.35.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "bitsandbytes", specifier = "~=0.47" }, + { name = "mkdocs", specifier = "~=1.6" }, + { name = "mkdocs-drawio", specifier = "~=1.11" }, + { name = "mkdocs-material", specifier = "~=9.6" }, + { name = "mkdocstrings", specifier = "~=0.29" }, + { name = "mkdocstrings-python", specifier = "~=1.16" }, + { name = "playwright", specifier = "~=1.54" }, + { name = "pre-commit", specifier = "~=4.2" }, + { name = "pydeps", specifier = "~=3.0" }, + { name = "pyodide-py", specifier = "~=0.28" }, + { name = "pyright", specifier = "==1.1.403" }, + { name = "pyscript", specifier = "~=0.3" }, + { name = "pytest", specifier = "~=8.4" }, + { name = "pytest-asyncio", specifier = "~=1.0" }, + { name = "pytest-playwright", specifier = "~=0.7" }, + { name = "pytest-sugar", specifier = "~=1.0" }, + { name = "python-dotenv", specifier = "~=1.1" }, + { name = "reactivex", specifier = "~=4.0" }, + { name = "ruff", specifier = "~=0.12" }, + { name = "taskipy", specifier = "~=1.14" }, + { name = "testcontainers", specifier = "~=4.12" }, +] + +[[package]] +name = "certifi" +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +source = { registry = "https://pypi.org/simple" } +version = "2025.8.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cfgv" +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.4.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "charset-normalizer" +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.4.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +dependencies = [{ name = "colorama", marker = "sys_platform == 'win32'" }] +name = "click" +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +source = { registry = "https://pypi.org/simple" } +version = "8.2.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.4.6" +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "distlib" +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.4.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +dependencies = [{ name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "requests" }, { name = "urllib3" }] +name = "docker" +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +source = { registry = "https://pypi.org/simple" } +version = "7.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + +[[package]] +name = "filelock" +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.19.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "fsspec" +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } +source = { registry = "https://pypi.org/simple" } +version = "2025.7.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, +] + +[[package]] +dependencies = [{ name = "python-dateutil" }] +name = "ghp-import" +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "greenlet" +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.2.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +dependencies = [{ name = "colorama" }] +name = "griffe" +sdist = { url = "https://files.pythonhosted.org/packages/81/ca/29f36e00c74844ae50d139cf5a8b1751887b2f4d5023af65d460268ad7aa/griffe-1.12.1.tar.gz", hash = "sha256:29f5a6114c0aeda7d9c86a570f736883f8a2c5b38b57323d56b3d1c000565567", size = 411863, upload-time = "2025-08-14T21:08:15.38Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.12.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/f2/4fab6c3e5bcaf38a44cc8a974d2752eaad4c129e45d6533d926a30edd133/griffe-1.12.1-py3-none-any.whl", hash = "sha256:2d7c12334de00089c31905424a00abcfd931b45b8b516967f224133903d302cc", size = 138940, upload-time = "2025-08-14T21:08:13.382Z" }, +] + +[[package]] +name = "h11" +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.16.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +sdist = { url = "https://files.pythonhosted.org/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719, upload-time = "2025-08-06T00:30:55.741Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1.7" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743, upload-time = "2025-08-06T00:30:50.634Z" }, + { url = "https://files.pythonhosted.org/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331, upload-time = "2025-08-06T00:30:49.212Z" }, + { url = "https://files.pythonhosted.org/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844, upload-time = "2025-08-06T00:30:47.582Z" }, + { url = "https://files.pythonhosted.org/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209, upload-time = "2025-08-06T00:30:45.509Z" }, + { url = "https://files.pythonhosted.org/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602, upload-time = "2025-08-06T00:30:52.41Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184, upload-time = "2025-08-06T00:30:54.105Z" }, + { url = "https://files.pythonhosted.org/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008, upload-time = "2025-08-06T00:30:57.056Z" }, +] + +[[package]] +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +name = "huggingface-hub" +sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.34.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, +] + +[[package]] +name = "identify" +sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243, upload-time = "2025-08-09T19:35:00.6Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.6.13" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153, upload-time = "2025-08-09T19:34:59.1Z" }, +] + +[[package]] +name = "idna" +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.10" +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +dependencies = [{ name = "markupsafe" }] +name = "jinja2" +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.1.6" +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +dependencies = [{ name = "huggingface-hub" }, { name = "packaging" }, { name = "pyyaml" }] +name = "kernels" +sdist = { url = "https://files.pythonhosted.org/packages/26/4e/626c2155efa978bdec45e57bf0cfd0a76682942964fdc166cab2306d56ed/kernels-0.9.0.tar.gz", hash = "sha256:42a77d824d71f76084f7dd52dcb6d8823e243088117dc5d66006779b10f43bfb", size = 42859, upload-time = "2025-08-01T14:46:29.647Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.9.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/60/3f9b0fba0b78bcc9ba4fa0cd3c5132402838bdb42dc69bf81fb89980d978/kernels-0.9.0-py3-none-any.whl", hash = "sha256:81705176b3488d8eb04fa2aff1a4be8dc10eeb6a4338a510e36a6b3aeeb75969", size = 37421, upload-time = "2025-08-01T14:46:28.478Z" }, +] + +[[package]] +name = "lxml" +sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } +source = { registry = "https://pypi.org/simple" } +version = "6.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" }, + { url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" }, + { url = "https://files.pythonhosted.org/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559, upload-time = "2025-06-28T18:47:31.091Z" }, + { url = "https://files.pythonhosted.org/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143, upload-time = "2025-06-28T18:47:33.612Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/cf03b412f3763d4ca23b25e70c96a74cfece64cec3addf1c4ec639586b13/lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", size = 5645469, upload-time = "2025-07-03T19:19:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" }, + { url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" }, + { url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" }, + { url = "https://files.pythonhosted.org/packages/06/d4/fd216f3cd6625022c25b336c7570d11f4a43adbaf0a56106d3d496f727a7/lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", size = 5662049, upload-time = "2025-07-03T19:19:16.409Z" }, + { url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" }, + { url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" }, + { url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" }, +] + +[[package]] +name = "markdown" +sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.8.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, +] + +[[package]] +dependencies = [{ name = "mdurl" }] +name = "markdown-it-py" +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.0.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mdurl" +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.1.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mergedeep" +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +name = "mkdocs" +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.6.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +dependencies = [{ name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }] +name = "mkdocs-autorefs" +sdist = { url = "https://files.pythonhosted.org/packages/47/0c/c9826f35b99c67fa3a7cddfa094c1a6c43fafde558c309c6e4403e5b37dc/mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749", size = 54961, upload-time = "2025-05-20T13:09:09.886Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.4.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/dc/fc063b78f4b769d1956319351704e23ebeba1e9e1d6a41b4b602325fd7e4/mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13", size = 24969, upload-time = "2025-05-20T13:09:08.237Z" }, +] + +[[package]] +dependencies = [ + { name = "beautifulsoup4" }, + { name = "jinja2" }, + { name = "lxml" }, + { name = "mkdocs" }, + { name = "requests" }, +] +name = "mkdocs-drawio" +sdist = { url = "https://files.pythonhosted.org/packages/6c/bc/0ea7ba44e4f4f065554e35b3e32a4d82d0c408bbe8f1d67ea36baa4d47cb/mkdocs_drawio-1.11.2.tar.gz", hash = "sha256:73f23f7aac3147807908afa800463ab415a714d11df1cd12424500f375c9ebfa", size = 5767, upload-time = "2025-05-23T09:33:38.154Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.11.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/0c/17a9c98ec2b71ab523f0dce728b81ee0d567857da1b4d5908ab1de188c09/mkdocs_drawio-1.11.2-py3-none-any.whl", hash = "sha256:754f224dd467a90d76d170c41d544322daa1ba1f3bd322a33f98b9884d96ec71", size = 7169, upload-time = "2025-05-23T09:33:36.476Z" }, +] + +[[package]] +dependencies = [{ name = "mergedeep" }, { name = "platformdirs" }, { name = "pyyaml" }] +name = "mkdocs-get-deps" +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.2.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "click" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +name = "mkdocs-material" +sdist = { url = "https://files.pythonhosted.org/packages/47/02/51115cdda743e1551c5c13bdfaaf8c46b959acc57ba914d8ec479dd2fe1f/mkdocs_material-9.6.17.tar.gz", hash = "sha256:48ae7aec72a3f9f501a70be3fbd329c96ff5f5a385b67a1563e5ed5ce064affe", size = 4032898, upload-time = "2025-08-15T16:09:21.412Z" } +source = { registry = "https://pypi.org/simple" } +version = "9.6.17" +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/7c/0f0d44c92c8f3068930da495b752244bd59fd87b5b0f9571fa2d2a93aee7/mkdocs_material-9.6.17-py3-none-any.whl", hash = "sha256:221dd8b37a63f52e580bcab4a7e0290e4a6f59bd66190be9c3d40767e05f9417", size = 9229230, upload-time = "2025-08-15T16:09:18.301Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +name = "mkdocstrings" +sdist = { url = "https://files.pythonhosted.org/packages/e2/0a/7e4776217d4802009c8238c75c5345e23014a4706a8414a62c0498858183/mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444", size = 106597, upload-time = "2025-07-22T23:48:45.998Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.30.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/b4/3c5eac68f31e124a55d255d318c7445840fa1be55e013f507556d6481913/mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2", size = 36579, upload-time = "2025-07-22T23:48:44.152Z" }, +] + +[[package]] +dependencies = [{ name = "griffe" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }] +name = "mkdocstrings-python" +sdist = { url = "https://files.pythonhosted.org/packages/39/7c/6dfd8ad59c0eebae167168528ed6cad00116f58ef2327686149f7b25d175/mkdocstrings_python-1.17.0.tar.gz", hash = "sha256:c6295962b60542a9c7468a3b515ce8524616ca9f8c1a38c790db4286340ba501", size = 200408, upload-time = "2025-08-14T21:18:14.568Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.17.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/ac/b1fcc937f4ecd372f3e857162dea67c45c1e2eedbac80447be516e3372bb/mkdocstrings_python-1.17.0-py3-none-any.whl", hash = "sha256:49903fa355dfecc5ad0b891e78ff5d25d30ffd00846952801bbe8331e123d4b0", size = 124778, upload-time = "2025-08-14T21:18:12.821Z" }, +] + +[[package]] +name = "mpmath" +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "mslex" +sdist = { url = "https://files.pythonhosted.org/packages/e0/97/7022667073c99a0fe028f2e34b9bf76b49a611afd21b02527fbfd92d4cd5/mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d", size = 11583, upload-time = "2024-10-16T13:16:18.523Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/f2/66bd65ca0139675a0d7b18f0bada6e12b51a984e41a76dbe44761bf1b3ee/mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4", size = 7820, upload-time = "2024-10-16T13:16:17.566Z" }, +] + +[[package]] +name = "networkx" +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.5" +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "nodeenv" +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.9.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "numpy" +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.3.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +source = { registry = "https://pypi.org/simple" } +version = "12.4.5.8" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805, upload-time = "2024-04-03T20:57:06.025Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +source = { registry = "https://pypi.org/simple" } +version = "12.4.127" +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957, upload-time = "2024-04-03T20:55:01.564Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +source = { registry = "https://pypi.org/simple" } +version = "12.4.127" +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306, upload-time = "2024-04-03T20:56:01.463Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +source = { registry = "https://pypi.org/simple" } +version = "12.4.127" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737, upload-time = "2024-04-03T20:54:51.355Z" }, +] + +[[package]] +dependencies = [{ name = "nvidia-cublas-cu12" }] +name = "nvidia-cudnn-cu12" +source = { registry = "https://pypi.org/simple" } +version = "9.1.0.70" +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741, upload-time = "2024-04-22T15:24:15.253Z" }, +] + +[[package]] +dependencies = [{ name = "nvidia-nvjitlink-cu12" }] +name = "nvidia-cufft-cu12" +source = { registry = "https://pypi.org/simple" } +version = "11.2.1.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117, upload-time = "2024-04-03T20:57:40.402Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +source = { registry = "https://pypi.org/simple" } +version = "10.3.5.147" +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206, upload-time = "2024-04-03T20:58:08.722Z" }, +] + +[[package]] +dependencies = [{ name = "nvidia-cublas-cu12" }, { name = "nvidia-cusparse-cu12" }, { name = "nvidia-nvjitlink-cu12" }] +name = "nvidia-cusolver-cu12" +source = { registry = "https://pypi.org/simple" } +version = "11.6.1.9" +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057, upload-time = "2024-04-03T20:58:28.735Z" }, +] + +[[package]] +dependencies = [{ name = "nvidia-nvjitlink-cu12" }] +name = "nvidia-cusparse-cu12" +source = { registry = "https://pypi.org/simple" } +version = "12.3.1.170" +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763, upload-time = "2024-04-03T20:58:59.995Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +source = { registry = "https://pypi.org/simple" } +version = "0.6.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/a8/bcbb63b53a4b1234feeafb65544ee55495e1bb37ec31b999b963cbccfd1d/nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:df2c24502fd76ebafe7457dbc4716b2fec071aabaed4fb7691a201cde03704d9", size = 150057751, upload-time = "2024-07-23T02:35:53.074Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +source = { registry = "https://pypi.org/simple" } +version = "2.21.5" +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414, upload-time = "2024-04-03T15:32:57.427Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +source = { registry = "https://pypi.org/simple" } +version = "12.4.127" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810, upload-time = "2024-04-03T20:59:46.957Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +source = { registry = "https://pypi.org/simple" } +version = "12.4.127" +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144, upload-time = "2024-04-03T20:56:12.406Z" }, +] + +[[package]] +name = "packaging" +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +source = { registry = "https://pypi.org/simple" } +version = "25.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "paginate" +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.5.7" +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pathspec" +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.12.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "platformdirs" +sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916, upload-time = "2024-05-15T03:18:23.372Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.2.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146, upload-time = "2024-05-15T03:18:21.209Z" }, +] + +[[package]] +dependencies = [{ name = "greenlet" }, { name = "pyee" }] +name = "playwright" +source = { registry = "https://pypi.org/simple" } +version = "1.54.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/09/33d5bfe393a582d8dac72165a9e88b274143c9df411b65ece1cc13f42988/playwright-1.54.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bf3b845af744370f1bd2286c2a9536f474cc8a88dc995b72ea9a5be714c9a77d", size = 40439034, upload-time = "2025-07-22T13:58:04.816Z" }, + { url = "https://files.pythonhosted.org/packages/e1/7b/51882dc584f7aa59f446f2bb34e33c0e5f015de4e31949e5b7c2c10e54f0/playwright-1.54.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:780928b3ca2077aea90414b37e54edd0c4bbb57d1aafc42f7aa0b3fd2c2fac02", size = 38702308, upload-time = "2025-07-22T13:58:08.211Z" }, + { url = "https://files.pythonhosted.org/packages/73/a1/7aa8ae175b240c0ec8849fcf000e078f3c693f9aa2ffd992da6550ea0dff/playwright-1.54.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:81d0b6f28843b27f288cfe438af0a12a4851de57998009a519ea84cee6fbbfb9", size = 40439037, upload-time = "2025-07-22T13:58:11.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/a9/45084fd23b6206f954198296ce39b0acf50debfdf3ec83a593e4d73c9c8a/playwright-1.54.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:09919f45cc74c64afb5432646d7fef0d19fff50990c862cb8d9b0577093f40cc", size = 45920135, upload-time = "2025-07-22T13:58:14.494Z" }, + { url = "https://files.pythonhosted.org/packages/02/d4/6a692f4c6db223adc50a6e53af405b45308db39270957a6afebddaa80ea2/playwright-1.54.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ae206c55737e8e3eae51fb385d61c0312eeef31535643bb6232741b41b6fdc", size = 45302695, upload-time = "2025-07-22T13:58:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/4ee60a1c3714321db187bebbc40d52cea5b41a856925156325058b5fca5a/playwright-1.54.0-py3-none-win32.whl", hash = "sha256:0b108622ffb6906e28566f3f31721cd57dda637d7e41c430287804ac01911f56", size = 35469309, upload-time = "2025-07-22T13:58:21.917Z" }, + { url = "https://files.pythonhosted.org/packages/aa/77/8f8fae05a242ef639de963d7ae70a69d0da61d6d72f1207b8bbf74ffd3e7/playwright-1.54.0-py3-none-win_amd64.whl", hash = "sha256:9e5aee9ae5ab1fdd44cd64153313a2045b136fcbcfb2541cc0a3d909132671a2", size = 35469311, upload-time = "2025-07-22T13:58:24.707Z" }, + { url = "https://files.pythonhosted.org/packages/33/ff/99a6f4292a90504f2927d34032a4baf6adb498dc3f7cf0f3e0e22899e310/playwright-1.54.0-py3-none-win_arm64.whl", hash = "sha256:a975815971f7b8dca505c441a4c56de1aeb56a211290f8cc214eeef5524e8d75", size = 31239119, upload-time = "2025-07-22T13:58:27.56Z" }, +] + +[[package]] +name = "pluggy" +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.5.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +name = "pre-commit" +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.3.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "psutil" +sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502, upload-time = "2024-12-19T18:21:20.568Z" } +source = { registry = "https://pypi.org/simple" } +version = "6.1.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511, upload-time = "2024-12-19T18:21:45.163Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985, upload-time = "2024-12-19T18:21:49.254Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488, upload-time = "2024-12-19T18:21:51.638Z" }, + { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477, upload-time = "2024-12-19T18:21:55.306Z" }, + { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017, upload-time = "2024-12-19T18:21:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602, upload-time = "2024-12-19T18:22:08.808Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444, upload-time = "2024-12-19T18:22:11.335Z" }, +] + +[[package]] +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +name = "pydantic" +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.11.7" +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +dependencies = [{ name = "typing-extensions" }] +name = "pydantic-core" +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.33.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +dependencies = [{ name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }] +name = "pydantic-settings" +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.10.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +dependencies = [{ name = "stdlib-list" }] +name = "pydeps" +sdist = { url = "https://files.pythonhosted.org/packages/5a/03/ce4baba41362297576f84f2d1906af25e43b46cc368afda4ac8bfe4bfd81/pydeps-3.0.1.tar.gz", hash = "sha256:a57415a8fae2ff6840a199b7dfcfecb90c37e4b9b54b58a111808a3440bc03bc", size = 53070, upload-time = "2025-02-04T11:50:10.167Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.0.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/ea/663366200286a95fa6ac0ea3a67510cc5799983b102bddc845d9370bf1c8/pydeps-3.0.1-py3-none-any.whl", hash = "sha256:7c86ee63c9ee6ddd088c840364981c5aa214a994d323bb7fa4724fca30829bee", size = 47596, upload-time = "2025-02-04T11:50:07.717Z" }, +] + +[[package]] +dependencies = [{ name = "typing-extensions" }] +name = "pyee" +sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +source = { registry = "https://pypi.org/simple" } +version = "13.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, +] + +[[package]] +name = "pygments" +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.19.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +dependencies = [{ name = "markdown" }, { name = "pyyaml" }] +name = "pymdown-extensions" +sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91", size = 853277, upload-time = "2025-07-28T16:19:34.167Z" } +source = { registry = "https://pypi.org/simple" } +version = "10.16.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, +] + +[[package]] +name = "pyodide-py" +sdist = { url = "https://files.pythonhosted.org/packages/85/3d/bdb623dc6b1165c906b958b25f81ea2b0ba2f06ded5e491d272f9a54c35d/pyodide_py-0.28.1.tar.gz", hash = "sha256:11464c6e0e3063e7c0bfc2802f53c74f75fb0834190b86bb78769f84c635a18e", size = 52569, upload-time = "2025-08-04T21:31:55.431Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.28.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/f9/a2533ac6831763c36dd1f4cb6ba9eebb8f41b1880be9e89e3b5994c6c1f8/pyodide_py-0.28.1-py3-none-any.whl", hash = "sha256:e158e58da4cee77d1dc825bdeddd689335a41b8a79b4fa4483a0f3c50e0454e7", size = 58478, upload-time = "2025-08-04T21:31:54.07Z" }, +] + +[[package]] +dependencies = [{ name = "nodeenv" }, { name = "typing-extensions" }] +name = "pyright" +sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1.403" +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" }, +] + +[[package]] +dependencies = [ + { name = "jinja2" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "requests" }, + { name = "rich" }, + { name = "toml" }, + { name = "typer" }, +] +name = "pyscript" +sdist = { url = "https://files.pythonhosted.org/packages/9d/4d/ee8606f71049fe29666e5541f0dd1a4f861dd3a6824334fac3c5aec3e8d9/pyscript-0.3.3.tar.gz", hash = "sha256:11fc64a3f187d8645c601ae6a80e3f0142e0dd9e0c5d3244b0ec508ca0d373f9", size = 20791, upload-time = "2024-09-09T13:02:34.234Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.3.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/d8/1881edf3b8653cf2f3b8005704126c738c151b6f8168a5806ea61f1efb5f/pyscript-0.3.3-py3-none-any.whl", hash = "sha256:320383f38e9eec6515dbe0c184d4ad9d9c58e2c98fb82ec09e8d8b2e93c9e62f", size = 15556, upload-time = "2024-09-09T13:02:32.756Z" }, +] + +[[package]] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +name = "pytest" +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +source = { registry = "https://pypi.org/simple" } +version = "8.4.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +dependencies = [{ name = "pytest" }] +name = "pytest-asyncio" +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, +] + +[[package]] +dependencies = [{ name = "pytest" }, { name = "requests" }] +name = "pytest-base-url" +sdist = { url = "https://files.pythonhosted.org/packages/ae/1a/b64ac368de6b993135cb70ca4e5d958a5c268094a3a2a4cac6f0021b6c4f/pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45", size = 6702, upload-time = "2024-01-31T22:43:00.81Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/1c/b00940ab9eb8ede7897443b771987f2f4a76f06be02f1b3f01eb7567e24a/pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6", size = 5302, upload-time = "2024-01-31T22:42:58.897Z" }, +] + +[[package]] +dependencies = [ + { name = "playwright" }, + { name = "pytest" }, + { name = "pytest-base-url" }, + { name = "python-slugify" }, +] +name = "pytest-playwright" +sdist = { url = "https://files.pythonhosted.org/packages/e3/47/38e292ad92134a00ea05e6fc4fc44577baaa38b0922ab7ea56312b7a6663/pytest_playwright-0.7.0.tar.gz", hash = "sha256:b3f2ea514bbead96d26376fac182f68dcd6571e7cb41680a89ff1673c05d60b6", size = 16666, upload-time = "2025-01-31T11:06:05.453Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.7.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/96/5f8a4545d783674f3de33f0ebc4db16cc76ce77a4c404d284f43f09125e3/pytest_playwright-0.7.0-py3-none-any.whl", hash = "sha256:2516d0871fa606634bfe32afbcc0342d68da2dbff97fe3459849e9c428486da2", size = 16618, upload-time = "2025-01-31T11:06:08.075Z" }, +] + +[[package]] +dependencies = [{ name = "packaging" }, { name = "pytest" }, { name = "termcolor" }] +name = "pytest-sugar" +sdist = { url = "https://files.pythonhosted.org/packages/f5/ac/5754f5edd6d508bc6493bc37d74b928f102a5fff82d9a80347e180998f08/pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a", size = 14992, upload-time = "2024-02-01T18:30:36.735Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171, upload-time = "2024-02-01T18:30:29.395Z" }, +] + +[[package]] +dependencies = [{ name = "six" }] +name = "python-dateutil" +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.9.0.post0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +dependencies = [{ name = "text-unidecode" }] +name = "python-slugify" +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +source = { registry = "https://pypi.org/simple" } +version = "8.0.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + +[[package]] +name = "pywin32" +source = { registry = "https://pypi.org/simple" } +version = "311" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +source = { registry = "https://pypi.org/simple" } +version = "6.0.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +dependencies = [{ name = "pyyaml" }] +name = "pyyaml-env-tag" +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +dependencies = [{ name = "typing-extensions" }] +name = "reactivex" +sdist = { url = "https://files.pythonhosted.org/packages/ef/63/f776322df4d7b456446eff78c4e64f14c3c26d57d46b4e06c18807d5d99c/reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8", size = 119177, upload-time = "2022-07-16T07:11:53.689Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.0.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/3f/2ed8c1b8fe3fc2ed816ba40554ef703aad8c51700e2606c139fcf9b7f791/reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a", size = 217791, upload-time = "2022-07-16T07:11:52.061Z" }, +] + +[[package]] +name = "regex" +sdist = { url = "https://files.pythonhosted.org/packages/0b/de/e13fa6dc61d78b30ba47481f99933a3b49a57779d625c392d8036770a60d/regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a", size = 400714, upload-time = "2025-07-31T00:21:16.262Z" } +source = { registry = "https://pypi.org/simple" } +version = "2025.7.34" +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/16/b709b2119975035169a25aa8e4940ca177b1a2e25e14f8d996d09130368e/regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5", size = 485334, upload-time = "2025-07-31T00:19:56.58Z" }, + { url = "https://files.pythonhosted.org/packages/94/a6/c09136046be0595f0331bc58a0e5f89c2d324cf734e0b0ec53cf4b12a636/regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd", size = 289942, upload-time = "2025-07-31T00:19:57.943Z" }, + { url = "https://files.pythonhosted.org/packages/36/91/08fc0fd0f40bdfb0e0df4134ee37cfb16e66a1044ac56d36911fd01c69d2/regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b", size = 285991, upload-time = "2025-07-31T00:19:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/99dc8f6f756606f0c214d14c7b6c17270b6bbe26d5c1f05cde9dbb1c551f/regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad", size = 797415, upload-time = "2025-07-31T00:20:01.668Z" }, + { url = "https://files.pythonhosted.org/packages/62/cf/2fcdca1110495458ba4e95c52ce73b361cf1cafd8a53b5c31542cde9a15b/regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59", size = 862487, upload-time = "2025-07-31T00:20:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/90/38/899105dd27fed394e3fae45607c1983e138273ec167e47882fc401f112b9/regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415", size = 910717, upload-time = "2025-07-31T00:20:04.727Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f6/4716198dbd0bcc9c45625ac4c81a435d1c4d8ad662e8576dac06bab35b17/regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f", size = 801943, upload-time = "2025-07-31T00:20:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/40/5d/cff8896d27e4e3dd11dd72ac78797c7987eb50fe4debc2c0f2f1682eb06d/regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1", size = 786664, upload-time = "2025-07-31T00:20:08.818Z" }, + { url = "https://files.pythonhosted.org/packages/10/29/758bf83cf7b4c34f07ac3423ea03cee3eb3176941641e4ccc05620f6c0b8/regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c", size = 856457, upload-time = "2025-07-31T00:20:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/d7/30/c19d212b619963c5b460bfed0ea69a092c6a43cba52a973d46c27b3e2975/regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a", size = 849008, upload-time = "2025-07-31T00:20:11.823Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b8/3c35da3b12c87e3cc00010ef6c3a4ae787cff0bc381aa3d251def219969a/regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0", size = 788101, upload-time = "2025-07-31T00:20:13.729Z" }, + { url = "https://files.pythonhosted.org/packages/47/80/2f46677c0b3c2b723b2c358d19f9346e714113865da0f5f736ca1a883bde/regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1", size = 264401, upload-time = "2025-07-31T00:20:15.233Z" }, + { url = "https://files.pythonhosted.org/packages/be/fa/917d64dd074682606a003cba33585c28138c77d848ef72fc77cbb1183849/regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997", size = 275368, upload-time = "2025-07-31T00:20:16.711Z" }, + { url = "https://files.pythonhosted.org/packages/65/cd/f94383666704170a2154a5df7b16be28f0c27a266bffcd843e58bc84120f/regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f", size = 268482, upload-time = "2025-07-31T00:20:18.189Z" }, + { url = "https://files.pythonhosted.org/packages/ac/23/6376f3a23cf2f3c00514b1cdd8c990afb4dfbac3cb4a68b633c6b7e2e307/regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a", size = 485385, upload-time = "2025-07-31T00:20:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/73/5b/6d4d3a0b4d312adbfd6d5694c8dddcf1396708976dd87e4d00af439d962b/regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435", size = 289788, upload-time = "2025-07-31T00:20:21.941Z" }, + { url = "https://files.pythonhosted.org/packages/92/71/5862ac9913746e5054d01cb9fb8125b3d0802c0706ef547cae1e7f4428fa/regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac", size = 286136, upload-time = "2025-07-31T00:20:26.146Z" }, + { url = "https://files.pythonhosted.org/packages/27/df/5b505dc447eb71278eba10d5ec940769ca89c1af70f0468bfbcb98035dc2/regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72", size = 797753, upload-time = "2025-07-31T00:20:27.919Z" }, + { url = "https://files.pythonhosted.org/packages/86/38/3e3dc953d13998fa047e9a2414b556201dbd7147034fbac129392363253b/regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e", size = 863263, upload-time = "2025-07-31T00:20:29.803Z" }, + { url = "https://files.pythonhosted.org/packages/68/e5/3ff66b29dde12f5b874dda2d9dec7245c2051f2528d8c2a797901497f140/regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751", size = 910103, upload-time = "2025-07-31T00:20:31.313Z" }, + { url = "https://files.pythonhosted.org/packages/9e/fe/14176f2182125977fba3711adea73f472a11f3f9288c1317c59cd16ad5e6/regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4", size = 801709, upload-time = "2025-07-31T00:20:33.323Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0d/80d4e66ed24f1ba876a9e8e31b709f9fd22d5c266bf5f3ab3c1afe683d7d/regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98", size = 786726, upload-time = "2025-07-31T00:20:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/c3ebb30e04a56c046f5c85179dc173818551037daae2c0c940c7b19152cb/regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7", size = 857306, upload-time = "2025-07-31T00:20:37.12Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b2/a4dc5d8b14f90924f27f0ac4c4c4f5e195b723be98adecc884f6716614b6/regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47", size = 848494, upload-time = "2025-07-31T00:20:38.818Z" }, + { url = "https://files.pythonhosted.org/packages/0d/21/9ac6e07a4c5e8646a90b56b61f7e9dac11ae0747c857f91d3d2bc7c241d9/regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e", size = 787850, upload-time = "2025-07-31T00:20:40.478Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/d51204e28e7bc54f9a03bb799b04730d7e54ff2718862b8d4e09e7110a6a/regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb", size = 269730, upload-time = "2025-07-31T00:20:42.253Z" }, + { url = "https://files.pythonhosted.org/packages/74/52/a7e92d02fa1fdef59d113098cb9f02c5d03289a0e9f9e5d4d6acccd10677/regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae", size = 278640, upload-time = "2025-07-31T00:20:44.42Z" }, + { url = "https://files.pythonhosted.org/packages/d1/78/a815529b559b1771080faa90c3ab401730661f99d495ab0071649f139ebd/regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64", size = 271757, upload-time = "2025-07-31T00:20:46.355Z" }, +] + +[[package]] +dependencies = [{ name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }] +name = "requests" +sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794, upload-time = "2023-05-22T15:12:44.175Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.31.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574, upload-time = "2023-05-22T15:12:42.313Z" }, +] + +[[package]] +dependencies = [{ name = "markdown-it-py" }, { name = "pygments" }] +name = "rich" +sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248, upload-time = "2024-02-28T14:51:19.472Z" } +source = { registry = "https://pypi.org/simple" } +version = "13.7.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681, upload-time = "2024-02-28T14:51:14.353Z" }, +] + +[[package]] +name = "ruff" +sdist = { url = "https://files.pythonhosted.org/packages/4a/45/2e403fa7007816b5fbb324cb4f8ed3c7402a927a0a0cb2b6279879a8bfdc/ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", size = 5254702, upload-time = "2025-08-14T16:08:55.2Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.12.9" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/20/53bf098537adb7b6a97d98fcdebf6e916fcd11b2e21d15f8c171507909cc/ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e", size = 11759705, upload-time = "2025-08-14T16:08:12.968Z" }, + { url = "https://files.pythonhosted.org/packages/20/4d/c764ee423002aac1ec66b9d541285dd29d2c0640a8086c87de59ebbe80d5/ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", size = 12527042, upload-time = "2025-08-14T16:08:16.54Z" }, + { url = "https://files.pythonhosted.org/packages/8b/45/cfcdf6d3eb5fc78a5b419e7e616d6ccba0013dc5b180522920af2897e1be/ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", size = 11724457, upload-time = "2025-08-14T16:08:18.686Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/44615c754b55662200c48bebb02196dbb14111b6e266ab071b7e7297b4ec/ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", size = 11949446, upload-time = "2025-08-14T16:08:21.059Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d1/9b7d46625d617c7df520d40d5ac6cdcdf20cbccb88fad4b5ecd476a6bb8d/ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", size = 11566350, upload-time = "2025-08-14T16:08:23.433Z" }, + { url = "https://files.pythonhosted.org/packages/59/20/b73132f66f2856bc29d2d263c6ca457f8476b0bbbe064dac3ac3337a270f/ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", size = 13270430, upload-time = "2025-08-14T16:08:25.837Z" }, + { url = "https://files.pythonhosted.org/packages/a2/21/eaf3806f0a3d4c6be0a69d435646fba775b65f3f2097d54898b0fd4bb12e/ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", size = 14264717, upload-time = "2025-08-14T16:08:27.907Z" }, + { url = "https://files.pythonhosted.org/packages/d2/82/1d0c53bd37dcb582b2c521d352fbf4876b1e28bc0d8894344198f6c9950d/ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", size = 13684331, upload-time = "2025-08-14T16:08:30.352Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2f/1c5cf6d8f656306d42a686f1e207f71d7cebdcbe7b2aa18e4e8a0cb74da3/ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", size = 12739151, upload-time = "2025-08-14T16:08:32.55Z" }, + { url = "https://files.pythonhosted.org/packages/47/09/25033198bff89b24d734e6479e39b1968e4c992e82262d61cdccaf11afb9/ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", size = 12954992, upload-time = "2025-08-14T16:08:34.816Z" }, + { url = "https://files.pythonhosted.org/packages/52/8e/d0dbf2f9dca66c2d7131feefc386523404014968cd6d22f057763935ab32/ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", size = 12899569, upload-time = "2025-08-14T16:08:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b614d7c08515b1428ed4d3f1d4e3d687deffb2479703b90237682586fa66/ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", size = 11751983, upload-time = "2025-08-14T16:08:39.314Z" }, + { url = "https://files.pythonhosted.org/packages/58/d6/383e9f818a2441b1a0ed898d7875f11273f10882f997388b2b51cb2ae8b5/ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", size = 11538635, upload-time = "2025-08-14T16:08:41.297Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/56f869d314edaa9fc1f491706d1d8a47747b9d714130368fbd69ce9024e9/ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", size = 12534346, upload-time = "2025-08-14T16:08:43.39Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4b/d8b95c6795a6c93b439bc913ee7a94fda42bb30a79285d47b80074003ee7/ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", size = 13017021, upload-time = "2025-08-14T16:08:45.889Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c1/5f9a839a697ce1acd7af44836f7c2181cdae5accd17a5cb85fcbd694075e/ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", size = 11734785, upload-time = "2025-08-14T16:08:48.062Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/cdddc2d1d9a9f677520b7cfc490d234336f523d4b429c1298de359a3be08/ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", size = 12840654, upload-time = "2025-08-14T16:08:50.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/fd/669816bc6b5b93b9586f3c1d87cd6bc05028470b3ecfebb5938252c47a35/ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", size = 11949623, upload-time = "2025-08-14T16:08:52.233Z" }, +] + +[[package]] +name = "safetensors" +sdist = { url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9", size = 197968, upload-time = "2025-08-08T13:13:58.654Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.6.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba", size = 454797, upload-time = "2025-08-08T13:13:52.066Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b", size = 432206, upload-time = "2025-08-08T13:13:50.931Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd", size = 473261, upload-time = "2025-08-08T13:13:41.259Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a", size = 485117, upload-time = "2025-08-08T13:13:43.506Z" }, + { url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1", size = 616154, upload-time = "2025-08-08T13:13:45.096Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda", size = 520713, upload-time = "2025-08-08T13:13:46.25Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f", size = 485835, upload-time = "2025-08-08T13:13:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19", size = 521503, upload-time = "2025-08-08T13:13:47.651Z" }, + { url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce", size = 652256, upload-time = "2025-08-08T13:13:53.167Z" }, + { url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7", size = 747281, upload-time = "2025-08-08T13:13:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5", size = 692286, upload-time = "2025-08-08T13:13:55.884Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac", size = 655957, upload-time = "2025-08-08T13:13:57.029Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1", size = 308926, upload-time = "2025-08-08T13:14:01.095Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, +] + +[[package]] +name = "setuptools" +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +source = { registry = "https://pypi.org/simple" } +version = "80.9.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.17.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.7" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[package]] +dependencies = [{ name = "anyio" }] +name = "starlette" +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.47.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, +] + +[[package]] +name = "stdlib-list" +sdist = { url = "https://files.pythonhosted.org/packages/5d/09/8d5c564931ae23bef17420a6c72618463a59222ca4291a7dd88de8a0d490/stdlib_list-0.11.1.tar.gz", hash = "sha256:95ebd1d73da9333bba03ccc097f5bac05e3aa03e6822a0c0290f87e1047f1857", size = 60442, upload-time = "2025-02-18T15:39:38.769Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.11.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c7/4102536de33c19d090ed2b04e90e7452e2e3dc653cf3323208034eaaca27/stdlib_list-0.11.1-py3-none-any.whl", hash = "sha256:9029ea5e3dfde8cd4294cfd4d1797be56a67fc4693c606181730148c3fd1da29", size = 83620, upload-time = "2025-02-18T15:39:37.02Z" }, +] + +[[package]] +dependencies = [{ name = "mpmath" }] +name = "sympy" +sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040, upload-time = "2024-07-19T09:26:51.238Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.13.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177, upload-time = "2024-07-19T09:26:48.863Z" }, +] + +[[package]] +dependencies = [ + { name = "colorama" }, + { name = "mslex", marker = "sys_platform == 'win32'" }, + { name = "psutil" }, + { name = "tomli", marker = "python_full_version < '4'" }, +] +name = "taskipy" +sdist = { url = "https://files.pythonhosted.org/packages/c7/44/572261df3db9c6c3332f8618fafeb07a578fd18b06673c73f000f3586749/taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed", size = 14475, upload-time = "2024-11-26T16:37:46.155Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.14.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/97/4e4cfb1391c81e926bebe3d68d5231b5dbc3bb41c6ba48349e68a881462d/taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1", size = 13052, upload-time = "2024-11-26T16:37:44.546Z" }, +] + +[[package]] +name = "termcolor" +sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, +] + +[[package]] +dependencies = [ + { name = "docker" }, + { name = "python-dotenv" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "wrapt" }, +] +name = "testcontainers" +sdist = { url = "https://files.pythonhosted.org/packages/d3/62/01d9f648e9b943175e0dcddf749cf31c769665d8ba08df1e989427163f33/testcontainers-4.12.0.tar.gz", hash = "sha256:13ee89cae995e643f225665aad8b200b25c4f219944a6f9c0b03249ec3f31b8d", size = 66631, upload-time = "2025-07-21T20:32:26.37Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.12.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/e8/9e2c392e5d671afda47b917597cac8fde6a452f5776c4c9ceb93fbd2889f/testcontainers-4.12.0-py3-none-any.whl", hash = "sha256:26caef57e642d5e8c5fcc593881cf7df3ab0f0dc9170fad22765b184e226ab15", size = 111791, upload-time = "2025-07-21T20:32:25.038Z" }, +] + +[[package]] +name = "text-unidecode" +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +dependencies = [{ name = "huggingface-hub" }] +name = "tokenizers" +sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.21.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" }, + { url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" }, + { url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" }, + { url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" }, + { url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" }, + { url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, +] + +[[package]] +name = "toml" +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.10.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "tomli" +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.2.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +name = "torch" +source = { registry = "https://pypi.org/simple" } +version = "2.6.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/85/ead1349fc30fe5a32cadd947c91bda4a62fbfd7f8c34ee61f6398d38fb48/torch-2.6.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4874a73507a300a5d089ceaff616a569e7bb7c613c56f37f63ec3ffac65259cf", size = 766626191, upload-time = "2025-01-29T16:17:26.26Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b0/26f06f9428b250d856f6d512413e9e800b78625f63801cbba13957432036/torch-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a0d5e1b9874c1a6c25556840ab8920569a7a4137afa8a63a32cee0bc7d89bd4b", size = 95611439, upload-time = "2025-01-29T16:21:21.061Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9c/fc5224e9770c83faed3a087112d73147cd7c7bfb7557dcf9ad87e1dda163/torch-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:510c73251bee9ba02ae1cb6c9d4ee0907b3ce6020e62784e2d7598e0cfa4d6cc", size = 204126475, upload-time = "2025-01-29T16:21:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/8b/d60c0491ab63634763be1537ad488694d316ddc4a20eaadd639cedc53971/torch-2.6.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:ff96f4038f8af9f7ec4231710ed4549da1bdebad95923953a25045dcf6fd87e2", size = 66536783, upload-time = "2025-01-29T16:22:08.559Z" }, +] + +[[package]] +dependencies = [{ name = "colorama", marker = "sys_platform == 'win32'" }] +name = "tqdm" +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.67.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +name = "transformers" +sdist = { url = "https://files.pythonhosted.org/packages/70/a5/d8b8a1f3a051daeb5f11253bb69fc241f193d1c0566e299210ed9220ff4e/transformers-4.55.2.tar.gz", hash = "sha256:a45ec60c03474fd67adbce5c434685051b7608b3f4f167c25aa6aeb1cad16d4f", size = 9571466, upload-time = "2025-08-13T18:25:43.767Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.55.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/5a/022ac010bedfb5119734cf9d743cf1d830cb4c604f53bb1552216f4344dc/transformers-4.55.2-py3-none-any.whl", hash = "sha256:097e3c2e2c0c9681db3da9d748d8f9d6a724c644514673d0030e8c5a1109f1f1", size = 11269748, upload-time = "2025-08-13T18:25:40.394Z" }, +] + +[[package]] +name = "triton" +source = { registry = "https://pypi.org/simple" } +version = "3.2.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/30/37a3384d1e2e9320331baca41e835e90a3767303642c7a80d4510152cbcf/triton-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5dfa23ba84541d7c0a531dfce76d8bcd19159d50a4a8b14ad01e91734a5c1b0", size = 253154278, upload-time = "2025-01-22T19:13:54.221Z" }, +] + +[[package]] +dependencies = [{ name = "click" }, { name = "typing-extensions" }] +name = "typer" +sdist = { url = "https://files.pythonhosted.org/packages/5b/49/39f10d0f75886439ab3dac889f14f8ad511982a754e382c9b6ca895b29e9/typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2", size = 273985, upload-time = "2023-05-02T05:20:57.63Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.9.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/0e/c68adf10adda05f28a6ed7b9f4cd7b8e07f641b44af88ba72d9c89e4de7a/typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee", size = 45861, upload-time = "2023-05-02T05:20:55.675Z" }, +] + +[[package]] +name = "typing-extensions" +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.14.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +dependencies = [{ name = "typing-extensions" }] +name = "typing-inspection" +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.4.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "urllib3" +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.5.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +dependencies = [{ name = "click" }, { name = "h11" }] +name = "uvicorn" +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.35.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +dependencies = [{ name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }] +name = "virtualenv" +sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" } +source = { registry = "https://pypi.org/simple" } +version = "20.34.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" }, +] + +[[package]] +name = "watchdog" +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +source = { registry = "https://pypi.org/simple" } +version = "6.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "wrapt" +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.17.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] diff --git a/cool-cacti/.github/workflows/lint.yaml b/cool-cacti/.github/workflows/lint.yaml new file mode 100644 index 00000000..7f67e803 --- /dev/null +++ b/cool-cacti/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/cool-cacti/.gitignore b/cool-cacti/.gitignore new file mode 100644 index 00000000..12ea8c3c --- /dev/null +++ b/cool-cacti/.gitignore @@ -0,0 +1,39 @@ +horizons_data/* +!horizons_data/template.json +!horizons_data/planets.json + +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +.VSCodeCounter/ +# MacOS +.DS_Store + +# Ruff check +.ruff_cache/ \ No newline at end of file diff --git a/cool-cacti/.pre-commit-config.yaml b/cool-cacti/.pre-commit-config.yaml new file mode 100644 index 00000000..80547ab2 --- /dev/null +++ b/cool-cacti/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + args: [ --fix ] + - id: ruff-format diff --git a/cool-cacti/ATTRIBUTIONS.txt b/cool-cacti/ATTRIBUTIONS.txt new file mode 100644 index 00000000..8373639a --- /dev/null +++ b/cool-cacti/ATTRIBUTIONS.txt @@ -0,0 +1,30 @@ +Music: "25 oh 20 and to the stars", "something about space", "death screen clip" +Composed and created by Elemeno Peter https://kimeklover.newgrounds.com/ +Licensed under CC BY-NC-SA + +Planet sprites generated with Pixel Planet Generator by Deep Fold +https://deep-fold.itch.io/pixel-planet-generator +Software licensed under the MIT License + +Explosion sprites by LinkNinja +https://linkninja.itch.io/simple-explosion-animation +Licensed under CC-0 + +Recycle items art by Clint Bellanger +https://opengameart.org/content/recycle-items-set +Licensed under CC-BY 3.0 + +QR Code Scanner +https://www.hiclipart.com/free-transparent-background-png-clipart-pjnbg +HiClipart is an open community for users to share PNG images, all PNG cliparts in HiClipart are for Non-Commercial Use + +3 Explosion Bangs Copyright 2012 Iwan 'qubodup' Gabovitch +https://opengameart.org/content/3-background-crash-explosion-bang-sounds +Licensed under CC-BY 3.0 + +Rock breaking sound +https://opengameart.org/content/rockbreaking +Licensed under CC-BY 3.0 + +Scan sound from https://www.zapsplat.com +Use with attribution \ No newline at end of file diff --git a/cool-cacti/LICENSE.txt b/cool-cacti/LICENSE.txt new file mode 100644 index 00000000..5a04926b --- /dev/null +++ b/cool-cacti/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/cool-cacti/README.md b/cool-cacti/README.md new file mode 100644 index 00000000..ef910d55 --- /dev/null +++ b/cool-cacti/README.md @@ -0,0 +1,132 @@ +# Planet Scanners: Python Discord Code Jam 2025 + +We created a simple sprite-based, space-themed game using PyScript backed by Pyodide and its JavaScript wrapper +capabilities to leverage JavaScript's convenient API for rendering graphics on HTML canvas elements, playing +audio, and accessing user input. We hope this can demo as an alternative to the use of a pygame wrapper such as +pygbag, without the need for its extra layer of abstraction, as the browser-side execution of our game doesn't use anything +but the Python standard library and the PyScript and Pyodide modules for accessing JavaScript functionality. + +### The Game + +The introduction depicts aliens flying around space past the speed of light, about to turn on their super +advanced intergalactic planet scanner. Today, it fails! Luckily, one of the aliens has an old Earth-based +barcode scanner - the "wrong tool for the +job" - that will have to do today. In the solar system overview screen, seen below, the player can select each of the solar system's planets in turn and must complete +a scan for that planet to complete a mission. + +![Planet selection screen](/readme_images/game1.png) + +The gameplay (shown in the image below) is a variation of the classic asteroids game, where +the player must dodge incoming asteroids to avoid ship damage and stay immobile while holding down the Spacebar +to progress scanning. +An animated rotating planet combined with the movement of stars off-screen in the opposite direction creates a +simple but effective perspective of the player ship orbiting a planet. + +![Planet selection screen](/readme_images/game2.png) + +![Planet selection screen](/readme_images/game3.png) + +## Intended Event Frameworks + +We used PyScript backed by the Pyodide interpreter. We were pleasantly surprised by how few PyScript-specific +oddities we ran into. Using the provided JavaScript wrapper capabilities felt almost as natural as writing +JavaScript directly into the Python files, except writing in Python! To serve our single-page game app, we +included an ultra-minimalistic Flask backend for convenience in designing, though with a little refactoring our page could +also be served statically. There is some very minimalistic HTML and CSS to more or less create an HTML canvas to +draw on with the appropriate styling and create the initial setup for importing our in-browser Python scripts. +It was necessary to provide the contents of a [PyScript.json](/static/PyScript.json) file to the config attribute of the tag +of our [index.html](/templates/index.html) to let the PyScript environment allow the proper imports of modules +into one another. + +One of the few things that adds a bit of awkwardness is needing to wrap function references passed to to JS callbacks by +using `create_proxy`, instead of passing a reference to the `game_loop` function directly: +```py +from Pyodide.ffi import create_proxy + +def game_loop(timestamp: float) -> None: + ... + +game_loop_proxy = create_proxy(game_loop) +window.requestAnimationFrame(game_loop_proxy) +``` + +On the other hand, "writing JavaScript" in Python can feel very elegant sometimes. A CanvasRenderingContext2D's +drawing methods for +example often take a lot of arguments to define the coordinates of objects being draw. There's heavy use of +rectangular bounds given as four parameters: left, top, width, height. Defining a Python Rect class implementing +the iterator protocol... + +```py +@dataclass +class Rect: + left: float + top: float + width: float + height: float + + def __iter__(self) -> Iterator[float]: + yield self.left + yield self.top + yield self.width + yield self.height +``` + +...allows for some drawing calls to be very succinct with unpacking: + +```py +# sprite_coords = Rect(0, 0, sprite_width, sprite_height) +# dest_coords = Rect(dest_left, dest_top, dest_width, dest_height) +ctx.fillRect(*dest_coords) +ctx.drawImage(sprite_image, *sprite_coords, *dest_coords) +``` + +## Installation and Usage + +### Prerequisites to Run +- Python 3.12 +- [uv](https://github.com/astral-sh/uv) is recommended for the package manager +- An active internet connection (to fetch the Pyodide interpreter and PyScript modules from the PyScript CDN) +### Installation +1. Clone the repository: + ```bash + git clone https://github.com/fluffy-marmot/codejam2025 + ``` + +2. Install dependencies using uv: + ```bash + uv sync + ``` +### Without uv +The dependencies are listed in [`pyproject.toml`](pyproject.toml). Since the only server-side dependency for running the +project is flask (PyScript is obtained automatically in browser as needed via CDN), the +project can be run after cloning it by simply using +```bash +pip install flask +python app.py +``` +### Running the Game +Running the [app.py](/app.py) file starts the simple flask server to serve the single html page, which should be at +[http://127.0.0.1:5000](http://127.0.0.1:5000) if testing it locally. We also have a version of our game hosted +at [https://caius.pythonanywhere.com/codejam/](https://caius.pythonanywhere.com/codejam/) although this has been +slightly modified from the current repository to run as a single app within an already existing Django project. +None of the files in the `/static/` directory of the hosted version have been modified, therefore in-browser functionality +should be the same. + +## Individual Contributions + +RealisticTurtle: storyboarding, intro scene and story, game scene, star system, scanning mechanics + +Soosh: library research, core functionality (audio and input modules, gameloop, scene system), code integration, debris +system + +Dark Zero: planet selection scene, sprites, spritesheet helper scripts, player mechanics, asteroid +implementation, collision logic, end scene + +Doomy: dynamic textboxes, end scene and credits, Horizons API functionality, +refactoring and maintenance, scanner refinement, experimented with Marimo + +## Game Demonstration + +This video is a quick demonstration of our game and its mechanics by our teammate RealisticTurtle + +View the demo on [Youtube](https://www.youtube.com/watch?v=J8LKGUsTeAo) \ No newline at end of file diff --git a/cool-cacti/app.py b/cool-cacti/app.py new file mode 100644 index 00000000..69774e8e --- /dev/null +++ b/cool-cacti/app.py @@ -0,0 +1,46 @@ +import json +from pathlib import Path + +from flask import Flask, render_template + +""" +using a flask backend to serve a very simple html file containing a canvas that we draw on using +very various pyscript scripts. We can send the planets_info variable along with the render_template +request so that it will be accessible in the index.html template and afterwards the pyscript scripts +""" +app = Flask(__name__) + +base_dir = Path(__file__).resolve().parent +static_dir = base_dir / "static" +sprite_dir = static_dir / "sprites" +audio_dir = static_dir / "audio" + +# contains various information and game data about planets +with Path.open(base_dir / "horizons_data" / "planets.json", encoding='utf-8') as f: + planets_info = json.load(f) + +# create a list of available sprite files +sprite_list = [sprite_file.stem for sprite_file in sprite_dir.iterdir() if sprite_file.is_file()] + +# create a list of available audio files +audio_list = [audio_file.name for audio_file in audio_dir.iterdir()] + +with Path.open(static_dir / "lore.txt") as f: + lore = f.read() + +with Path.open(static_dir / "credits.txt") as f: + credits = f.read() + +@app.route("/") +def index(): + return render_template( + "index.html", + planets_info=planets_info, + sprite_list=sprite_list, + audio_list=audio_list, + lore=lore, + credits=credits + ) + +if __name__ == "__main__": + app.run(debug=True) diff --git a/cool-cacti/horizons_data/planets.json b/cool-cacti/horizons_data/planets.json new file mode 100644 index 00000000..d0100903 --- /dev/null +++ b/cool-cacti/horizons_data/planets.json @@ -0,0 +1,267 @@ +[ + { + "id": 10, + "name": "Sun", + "sprite": "sun.png" + }, + { + "id": 199, + "name": "Mercury", + "sprite": "mercury.png", + "x": 50464198.3250268, + "y": 9059796.87177754, + "info": "PLANETARY SCAN COMPLETE: Mercury\n\n*******************************************************************************\n\n PHYSICAL DATA:\n Vol. Mean Radius (km) = 2439.4+-0.1 Density (g cm^-3) = 5.427\n Mass x10^23 (kg) = 3.302 Volume (x10^10 km^3) = 6.085 \n Sidereal rot. period = 58.6463 d Sid. rot. rate (rad/s)= 0.00000124001\n Mean solar day = 175.9421 d Core radius (km) = ~1600 \n Geometric Albedo = 0.106 Surface emissivity = 0.77+-0.06\n GM (km^3/s^2) = 22031.86855 Equatorial radius, Re = 2440.53 km\n GM 1-sigma (km^3/s^2) = Mass ratio (Sun/plnt) = 6023682\n Mom. of Inertia = 0.33 Equ. gravity m/s^2 = 3.701 \n Atmos. pressure (bar) = < 5x10^-15 Max. angular diam. = 11.0\" \n Mean Temperature (K) = 440 Visual mag. V(1,0) = -0.42 \n Obliquity to orbit[1] = 2.11' +/- 0.1' Hill's sphere rad. Rp = 94.4 \n Sidereal orb. per. = 0.2408467 y Mean Orbit vel. km/s = 47.362 \n Sidereal orb. per. = 87.969257 d Escape vel. km/s = 4.435\n Perihelion Aphelion Mean\n Solar Constant (W/m^2) 14462 6278 9126\n Maximum Planetary IR (W/m^2) 12700 5500 8000\n Minimum Planetary IR (W/m^2) 6 6 6\n*******************************************************************************\n", + "level": [ + "Mercury - Scan Required", + "\n", + "Asteroid counts : *******", + "Asteroid speed : **********", + "Asteroid damage : *******", + "Asteroid durability: ****", + "Scan difficulty : ********************", + "\n", + "Mercury's sparse asteroid field is more forgiving than most, but the", + "Sun's proximity, and the constant bombardment of high-energy radiation", + "particles will gnaw at the ship over time and slow damage it.", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 3.6, + "asteroid": { + "count": 7, + "speed": 10, + "damage": 7, + "durability": 4 + } + }, + { + "id": 299, + "name": "Venus", + "sprite": "venus.png", + "x": 62265012.79592998, + "y": 88255225.26065554, + "info": "PLANETARY SCAN COMPLETE: Venus\n\n*******************************************************************************\n\n PHYSICAL DATA:\n Vol. Mean Radius (km) = 6051.84+-0.01 Density (g/cm^3) = 5.204\n Mass x10^23 (kg) = 48.685 Volume (x10^10 km^3) = 92.843\n Sidereal rot. period = 243.018484 d Sid. Rot. Rate (rad/s)= -0.00000029924\n Mean solar day = 116.7490 d Equ. gravity m/s^2 = 8.870\n Mom. of Inertia = 0.33 Core radius (km) = ~3200\n Geometric Albedo = 0.65 Potential Love # k2 = ~0.25\n GM (km^3/s^2) = 324858.592 Equatorial Radius, Re = 6051.893 km\n GM 1-sigma (km^3/s^2) = +-0.006 Mass ratio (Sun/Venus)= 408523.72\n Atmos. pressure (bar) = 90 Max. angular diam. = 60.2\"\n Mean Temperature (K) = 735 Visual mag. V(1,0) = -4.40\n Obliquity to orbit = 177.3 deg Hill's sphere rad.,Rp = 167.1\n Sidereal orb. per., y = 0.61519726 Orbit speed, km/s = 35.021\n Sidereal orb. per., d = 224.70079922 Escape speed, km/s = 10.361\n Perihelion Aphelion Mean\n Solar Constant (W/m^2) 2759 2614 2650\n Maximum Planetary IR (W/m^2) 153 153 153\n Minimum Planetary IR (W/m^2) 153 153 153\n*******************************************************************************\n", + "level": [ + "Venus - Scan Required", + "\n", + "Asteroid counts : ************", + "Asteroid speed : ***************", + "Asteroid damage : ************", + "Asteroid durability: ********", + "Scan difficulty : ************", + "\n", + "Piloting this dense asteroid field will keep you too busy to", + "ponder the mysteries of what lies beneath Venus's veil of toxic clouds.", + "Even the difficulties of navigating its orbit are better than the searing", + "heat and crushing atmosphere of its surface.", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 1.6, + "asteroid": { + "count": 12, + "speed": 15, + "damage": 12, + "durability": 8 + } + }, + { + "id": 399, + "name": "Mars", + "sprite": "mars.png", + "x": 121056565.2223383, + "y": -91071517.42399806, + "info": "PLANETARY SCAN COMPLETE: Mars\n\n*******************************************************************************\n\n PHYSICAL DATA:\n Vol. mean radius (km) = 3389.92+-0.04 Density (g/cm^3) = 3.933(5+-4)\n Mass x10^23 (kg) = 6.4171 Flattening, f = 1/169.779\n Volume (x10^10 km^3) = 16.318 Equatorial radius (km)= 3396.19\n Sidereal rot. period = 24.622962 hr Sid. rot. rate, rad/s = 0.0000708822 \n Mean solar day (sol) = 88775.24415 s Polar gravity m/s^2 = 3.758\n Core radius (km) = ~1700 Equ. gravity m/s^2 = 3.71\n Geometric Albedo = 0.150 \n\n GM (km^3/s^2) = 42828.375662 Mass ratio (Sun/Mars) = 3098703.59\n GM 1-sigma (km^3/s^2) = +- 0.00028 Mass of atmosphere, kg= ~ 2.5 x 10^16\n Mean temperature (K) = 210 Atmos. pressure (bar) = 0.0056 \n Obliquity to orbit = 25.19 deg Max. angular diam. = 17.9\"\n Mean sidereal orb per = 1.88081578 y Visual mag. V(1,0) = -1.52\n Mean sidereal orb per = 686.98 d Orbital speed, km/s = 24.13\n Hill's sphere rad. Rp = 319.8 Escape speed, km/s = 5.027\n Perihelion Aphelion Mean\n Solar Constant (W/m^2) 717 493 589\n Maximum Planetary IR (W/m^2) 470 315 390\n Minimum Planetary IR (W/m^2) 30 30 30\n*******************************************************************************\n", + "level": [ + "Mars - Scan Required", + "\n", + "Asteroid counts : *****", + "Asteroid speed : ******************", + "Asteroid damage : ********************", + "Asteroid durability: *******************", + "Scan difficulty : ****************", + "\n", + "Though Mars's asteroid belt is deceptively thin, its dense icy rocks", + "strike with brutal force. Here, survival depends not on dodging many,", + "but on avoiding the few that could end you instantly.", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 2.6, + "asteroid": { + "count": 5, + "speed": 20, + "damage": 40, + "durability": 19 + } + }, + { + "id": 499, + "name": "Earth", + "sprite": "earth.png", + "x": -205766295.2832569, + "y": -121439888.7499874, + "info": "PLANETARY SCAN COMPLETE: Earth\n\n*******************************************************************************\n\n GEOPHYSICAL PROPERTIES:\n Vol. Mean Radius (km) = 6371.01+-0.02 Mass x10^24 (kg)= 5.97219+-0.0006\n Equ. radius, km = 6378.137 Mass layers:\n Polar axis, km = 6356.752 Atmos = 5.1 x 10^18 kg\n Flattening = 1/298.257223563 oceans = 1.4 x 10^21 kg\n Density, g/cm^3 = 5.51 crust = 2.6 x 10^22 kg\n J2 (IERS 2010) = 0.00108262545 mantle = 4.043 x 10^24 kg\n g_p, m/s^2 (polar) = 9.8321863685 outer core = 1.835 x 10^24 kg\n g_e, m/s^2 (equatorial) = 9.7803267715 inner core = 9.675 x 10^22 kg\n g_o, m/s^2 = 9.82022 Fluid core rad = 3480 km\n GM, km^3/s^2 = 398600.435436 Inner core rad = 1215 km\n GM 1-sigma, km^3/s^2 = 0.0014 Escape velocity = 11.186 km/s\n Rot. Rate (rad/s) = 0.00007292115 Surface area:\n Mean sidereal day, hr = 23.9344695944 land = 1.48 x 10^8 km\n Mean solar day 2000.0, s = 86400.002 sea = 3.62 x 10^8 km\n Mean solar day 1820.0, s = 86400.0 Love no., k2 = 0.299\n Moment of inertia = 0.3308 Atm. pressure = 1.0 bar\n Mean surface temp (Ts), K= 287.6 Volume, km^3 = 1.08321 x 10^12\n Mean effect. temp (Te), K= 255 Magnetic moment = 0.61 gauss Rp^3\n Geometric albedo = 0.367 Vis. mag. V(1,0)= -3.86\n Solar Constant (W/m^2) = 1367.6 (mean), 1414 (perihelion), 1322 (aphelion)\n HELIOCENTRIC ORBIT CHARACTERISTICS:\n Obliquity to orbit, deg = 23.4392911 Sidereal orb period = 1.0000174 y\n Orbital speed, km/s = 29.79 Sidereal orb period = 365.25636 d\n Mean daily motion, deg/d = 0.9856474 Hill's sphere radius = 234.9 \n*******************************************************************************\n", + "level": [ + "Earth - Scan Required", + "\n", + "Asteroid counts : ************", + "Asteroid speed : *********", + "Asteroid damage : *****", + "Asteroid durability: *****************", + "Scan difficulty : *********", + "\n", + "In high orbit over Earth, safely above the new asteroid fields, lies", + "Chiaki Spacestation, which will afford you a unique opportunity for a", + "ship repair upon completing this mission.", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 1.3, + "asteroid": { + "count": 12, + "speed": 9, + "damage": 17, + "durability": 3 + } + + }, + { + "id": 599, + "name": "Jupiter", + "sprite": "jupiter.png", + "x": -100070012.8507128, + "y": 765661098.764396, + "info": "PLANETARY SCAN COMPLETE: Jupiter\n\n*******************************************************************************\n\n PHYSICAL DATA:\n Mass x 10^26 (kg) = 18.9819 Density (g/cm^3) = 1.3262 +- .0003\n Equat. radius (1 bar) = 71492+-4 km Polar radius (km) = 66854+-10\n Vol. Mean Radius (km) = 69911+-6 Flattening = 0.06487\n Geometric Albedo = 0.52 Rocky core mass (Mc/M)= 0.0261\n Sid. rot. period (III)= 9h 55m 29.711 s Sid. rot. rate (rad/s)= 0.00017585\n Mean solar day, hrs = ~9.9259 \n GM (km^3/s^2) = 126686531.900 GM 1-sigma (km^3/s^2) = +- 1.2732\n Equ. grav, ge (m/s^2) = 24.79 Pol. grav, gp (m/s^2) = 28.34\n Vis. magnitude V(1,0) = -9.40\n Vis. mag. (opposition)= -2.70 Obliquity to orbit = 3.13 deg\n Sidereal orbit period = 11.861982204 y Sidereal orbit period = 4332.589 d\n Mean daily motion = 0.0831294 deg/d Mean orbit speed, km/s= 13.0697\n Atmos. temp. (1 bar) = 165+-5 K Escape speed, km/s = 59.5 \n A_roche(ice)/Rp = 2.76 Hill's sphere rad. Rp = 740\n Perihelion Aphelion Mean\n Solar Constant (W/m^2) 56 46 51\n Maximum Planetary IR (W/m^2) 13.7 13.4 13.6\n Minimum Planetary IR (W/m^2) 13.7 13.4 13.6\n*******************************************************************************\n", + "level": [ + "Jupiter - Scan Required", + "\n", + "Asteroid counts : ************", + "Asteroid speed : ***************", + "Asteroid damage : **********", + "Asteroid durability: **********", + "Scan difficulty : ************", + "\n", + "Besides Jupiter's dangerous asteroid fields, the solar system's", + "largest planet has a strong gravitational field that you will", + "constantly need to fight against lest it claims you for its endless", + "storms.", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 1.8, + "asteroid": { + "count": 12, + "speed": 15, + "damage": 10, + "durability": 10 + } + }, + { + "id": 699, + "name": "Saturn", + "sprite": "saturn.png", + "x": 1427205111.636179, + "y": -76337597.30005142, + "info": "PLANETARY SCAN COMPLETE: Saturn\n\n*******************************************************************************\n\n PHYSICAL DATA:\n Mass x10^26 (kg) = 5.6834 Density (g/cm^3) = 0.687+-.001\n Equat. radius (1 bar) = 60268+-4 km Polar radius (km) = 54364+-10\n Vol. Mean Radius (km) = 58232+-6 Flattening = 0.09796\n Geometric Albedo = 0.47 Rocky core mass (Mc/M) = 0.1027\n Sid. rot. period (III)= 10h 39m 22.4s Sid. rot. rate (rad/s) = 0.000163785 \n Mean solar day, hrs =~10.656 \n GM (km^3/s^2) = 37931206.234 GM 1-sigma (km^3/s^2) = +- 98\n Equ. grav, ge (m/s^2) = 10.44 Pol. grav, gp (m/s^2) = 12.14+-0.01\n Vis. magnitude V(1,0) = -8.88 \n Vis. mag. (opposition)= +0.67 Obliquity to orbit = 26.73 deg\n Sidereal orbit period = 29.447498 yr Sidereal orbit period = 10755.698 d\n Mean daily motion = 0.0334979 deg/d Mean orbit velocity = 9.68 km/s\n Atmos. temp. (1 bar) = 134+-4 K Escape speed, km/s = 35.5 \n Aroche(ice)/Rp = 2.71 Hill's sphere rad. Rp = 1100\n Perihelion Aphelion Mean\n Solar Constant (W/m^2) 16.8 13.6 15.1\n Maximum Planetary IR (W/m^2) 4.7 4.5 4.6\n Minimum Planetary IR (W/m^2) 4.7 4.5 4.6\n*******************************************************************************\n", + "level": [ + "Saturn - Scan Required", + "\n", + "Asteroid counts : ************", + "Asteroid speed : ****************", + "Asteroid damage : ******************", + "Asteroid durability: *****************", + "Scan difficulty : ****************", + "\n", + "When Sol's system passed through a vast galactic asteroid field,", + "sometime in the early 23rd century, each planet captured its own", + "share of asteroids into its orbit. The collisions between Saturn's", + "newly captured asteroids and its already existing rings have created", + "the perfect rock and ice maelstrom to test even the most daring pilots.", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 2.6, + "asteroid": { + "count": 12, + "speed": 16, + "damage": 18, + "durability": 17 + } + }, + { + "id": 799, + "name": "Uranus", + "sprite": "uranus.png", + "x": 1548241220.947309, + "y": 2474904952.161897, + "info": "PLANETARY SCAN COMPLETE: Uranus\n\n*******************************************************************************\n\n PHYSICAL DATA:\n Mass x10^24 (kg) = 86.813 Density (g/cm^3) = 1.271\n Equat. radius (1 bar) = 25559+-4 km Polar radius (km) = 24973+-20\n Vol. Mean Radius (km) = 25362+-12 Flattening = 0.02293\n Geometric Albedo = 0.51\n Sid. rot. period (III)= 17.24+-0.01 h Sid. rot. rate (rad/s) = -0.000101237\n Mean solar day, h =~17.24 Rocky core mass (Mc/M) = 0.0012 \n GM (km^3/s^2) = 5793950.6103 GM 1-sigma (km^3/s^2) = +-4.3 \n Equ. grav, ge (m/s^2) = 8.87 Pol. grav, gp (m/s^2) = 9.19+-0.02\n Visual magnitude V(1,0)= -7.11\n Vis. mag. (opposition)= +5.52 Obliquity to orbit = 97.77 deg\n Sidereal orbit period = 84.0120465 y Sidereal orbit period = 30685.4 d\n Mean daily motion = 0.01176904 dg/d Mean orbit velocity = 6.8 km/s\n Atmos. temp. (1 bar) = 76+-2 K Escape speed, km/s = 21.3 \n Aroche(ice)/Rp = 2.20 Hill's sphere rad., Rp = 2700\n Perihelion Aphelion Mean\n Solar Constant (W/m^2) 4.09 3.39 3.71\n Maximum Planetary IR (W/m^2) 0.72 0.55 0.63\n Minimum Planetary IR (W/m^2) 0.72 0.55 0.63\n*******************************************************************************\n", + "level": [ + "Uranus - Scan Required", + "\n", + "Asteroid counts : ************", + "Asteroid speed : ****************", + "Asteroid damage : ******************", + "Asteroid durability: *****************", + "Scan difficulty : ****************", + "\n", + "Viewed from far above, the tranquil appearance of Uranus masks a", + "world of ever-present swirling storms and harsh winds laden with", + "icy particles. ", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 2.6, + "asteroid": { + "count": 12, + "speed": 16, + "damage": 18, + "durability": 17 + } + }, + { + "id": 899, + "name": "Neptune", + "sprite": "neptune.png", + "x": 4470024777.676497, + "y": 12434785.46605173, + "info": "PLANETARY SCAN COMPLETE: Neptune\n\n*******************************************************************************\n\n PHYSICAL DATA (update 2021-May-03):\n Mass x10^24 (kg) = 102.409 Density (g/cm^3) = 1.638\n Equat. radius (1 bar) = 24766+-15 km Volume, 10^10 km^3 = 6254 \n Vol. mean radius (km) = 24624+-21 Polar radius (km) = 24342+-30\n Geometric Albedo = 0.41 Flattening = 0.0171\n Sid. rot. period (III)= 16.11+-0.01 hr Sid. rot. rate (rad/s) = 0.000108338 \n Mean solar day, h =~16.11 h \n GM (km^3/s^2) = 6835099.97 GM 1-sigma (km^3/s^2) = +-10 \n Equ. grav, ge (m/s^2) = 11.15 Pol. grav, gp (m/s^2) = 11.41+-0.03\n Visual magnitude V(1,0)= -6.87\n Vis. mag. (opposition)= +7.84 Obliquity to orbit = 28.32 deg\n Sidereal orbit period = 164.788501027 y Sidereal orbit period = 60189 d\n Mean daily motion = 0.006020076dg/d Mean orbit velocity = 5.43 km/s \n Atmos. temp. (1 bar) = 72+-2 K Escape speed (1 bar) = 23.5 km/s \n Aroche(ice)/Rp = 2.98 Hill's sphere rad., Rp = 4700\n Perihelion Aphelion Mean\n Solar Constant (W/m^2) 1.54 1.49 1.51\n Maximum Planetary IR (W/m^2) 0.52 0.52 0.52\n Minimum Planetary IR (W/m^2) 0.52 0.52 0.52\n*******************************************************************************\n", + "level": [ + "Neptune - Scan Required", + "\n", + "Asteroid counts : **************", + "Asteroid speed : ************", + "Asteroid damage : ******************", + "Asteroid durability: *****************", + "Scan difficulty : ************", + "\n", + "Neptune reigns at the edge of humanity's realm of reasonable exploration;", + "You may be too busy dodging asteroids to fully its deep, arresting blue.", + "\n", + "Ship controls:", + "\tMovement: Arrow Keys or WASD", + "\tUse scanner: Hold Spacebar" + ], + "scan_multiplier": 2.2, + "asteroid": { + "count": 14, + "speed": 12, + "damage": 18, + "durability": 17 + } + } +] diff --git a/cool-cacti/pyproject.toml b/cool-cacti/pyproject.toml new file mode 100644 index 00000000..b220588f --- /dev/null +++ b/cool-cacti/pyproject.toml @@ -0,0 +1,87 @@ +[project] +# This section contains metadata about your project. +# Don't forget to change the name, description, and authors to match your project! +name = "code-jam-soon-to-be-awesome-project" +description = "no idea yet :)" +authors = [ + { name ="https://github.com/fluffy-marmot", email="10621013+fluffy-marmot@users.noreply.github.com"}, + { name ="https://github.com/Prorammer-4090", email="email@mail.com"}, + { name ="https://github.com/TheRatLord", email="email@mail.com"}, + { name ="https://github.com/spirledaxis", email="email@mail.com"}, +] +version = "0.1.0" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "flask>=3.1.1", +] + +[dependency-groups] +# This `dev` group contains all the development requirements for our linting toolchain. +# Don't forget to pin your dependencies! +# This list will have to be migrated if you wish to use another dependency manager. +dev = [ + "numpy>=2.3.2", + "pillow>=11.3.0", + "pre-commit~=4.2.0", + "ruff~=0.12.2", +] + +[tool.ruff] +# Increase the line length. This breaks PEP8 but it is way easier to work with. +# The original reason for this limit was a standard vim terminal is only 79 characters, +# but this doesn't really apply anymore. +line-length = 119 +# Target Python 3.12. If you decide to use a different version of Python +# you will need to update this value. +target-version = "py312" +# Automatically fix auto-fixable issues. +fix = true +# Show applied fixes. +show-fixes = true +# The directory containing the source code. If you choose a different project layout +# you will need to update this value. +src = ["src"] + +[tool.ruff.lint] +# Enable all linting rules. +select = ["ALL"] +fixable = ["I", "F401"] # I = Sorts imports, F401 = Deletes unused imports +# Ignore some of the most obnoxious linting errors. +ignore = [ + # Trailing comma rule, not compatible with formatter + "COM812", + # Missing docstrings. + "D100", + "D104", + "D105", + "D106", + "D107", + # Docstring whitespace. + "D203", + "D213", + # Docstring punctuation. + "D415", + # Docstring quotes. + "D301", + # Builtins. + "A", + # Print statements. + "T20", + # TODOs. + "TD002", + "TD003", + "FIX", + # Security + "S311" +] + +[tool.ruff.lint.isort] +combine-as-imports = true +section-order = [ + "future", + "standard-library", + "third-party", + "first-party", + "local-folder", +] diff --git a/cool-cacti/readme_images/game1.png b/cool-cacti/readme_images/game1.png new file mode 100644 index 00000000..121864c3 Binary files /dev/null and b/cool-cacti/readme_images/game1.png differ diff --git a/cool-cacti/readme_images/game2.png b/cool-cacti/readme_images/game2.png new file mode 100755 index 00000000..706a7833 Binary files /dev/null and b/cool-cacti/readme_images/game2.png differ diff --git a/cool-cacti/readme_images/game3.png b/cool-cacti/readme_images/game3.png new file mode 100755 index 00000000..41a85216 Binary files /dev/null and b/cool-cacti/readme_images/game3.png differ diff --git a/cool-cacti/static/audio/bang1.ogg b/cool-cacti/static/audio/bang1.ogg new file mode 100644 index 00000000..3dfa1339 Binary files /dev/null and b/cool-cacti/static/audio/bang1.ogg differ diff --git a/cool-cacti/static/audio/bang2.ogg b/cool-cacti/static/audio/bang2.ogg new file mode 100644 index 00000000..bfeb9617 Binary files /dev/null and b/cool-cacti/static/audio/bang2.ogg differ diff --git a/cool-cacti/static/audio/bang3.ogg b/cool-cacti/static/audio/bang3.ogg new file mode 100644 index 00000000..5bd826b6 Binary files /dev/null and b/cool-cacti/static/audio/bang3.ogg differ diff --git a/cool-cacti/static/audio/death.ogg b/cool-cacti/static/audio/death.ogg new file mode 100644 index 00000000..3510fc12 Binary files /dev/null and b/cool-cacti/static/audio/death.ogg differ diff --git a/cool-cacti/static/audio/explosion.ogg b/cool-cacti/static/audio/explosion.ogg new file mode 100644 index 00000000..f7f9552a Binary files /dev/null and b/cool-cacti/static/audio/explosion.ogg differ diff --git a/cool-cacti/static/audio/music_main.ogg b/cool-cacti/static/audio/music_main.ogg new file mode 100644 index 00000000..d62013a1 Binary files /dev/null and b/cool-cacti/static/audio/music_main.ogg differ diff --git a/cool-cacti/static/audio/music_thematic.ogg b/cool-cacti/static/audio/music_thematic.ogg new file mode 100644 index 00000000..84b8f34a Binary files /dev/null and b/cool-cacti/static/audio/music_thematic.ogg differ diff --git a/cool-cacti/static/audio/scan.ogg b/cool-cacti/static/audio/scan.ogg new file mode 100644 index 00000000..9e3c53dd Binary files /dev/null and b/cool-cacti/static/audio/scan.ogg differ diff --git a/cool-cacti/static/audio/text.ogg b/cool-cacti/static/audio/text.ogg new file mode 100644 index 00000000..4390feb8 Binary files /dev/null and b/cool-cacti/static/audio/text.ogg differ diff --git a/cool-cacti/static/credits.txt b/cool-cacti/static/credits.txt new file mode 100644 index 00000000..ea729290 --- /dev/null +++ b/cool-cacti/static/credits.txt @@ -0,0 +1,49 @@ +MISSION COMPLETE! + +A Space Exploration Adventure +Made for Python Discord's 2025 CodeJam +"Wrong Tool for the Job" + +Developer Team (Cool Cacti) + +Dark_Zero +Doomy +RealisticTurtle +Soosh + +Special Thanks (Music by) +Elemeno Peter + + +Thank you for playing! + + + + +Great job on scanning a grand total 8e0 planets! + + + + +Are you still here? + + + + + + + + +The credits are already over. + + + +...can we move on? + + + + + + + +42 \ No newline at end of file diff --git a/cool-cacti/static/favicon.ico b/cool-cacti/static/favicon.ico new file mode 100644 index 00000000..c8aa787c Binary files /dev/null and b/cool-cacti/static/favicon.ico differ diff --git a/cool-cacti/static/lore.txt b/cool-cacti/static/lore.txt new file mode 100644 index 00000000..dc42c987 --- /dev/null +++ b/cool-cacti/static/lore.txt @@ -0,0 +1,24 @@ +Why does boss want so much info on random planets...? +Yeah man...this mission seems pointless. We already scanned 2.3e+15 planets yesterday, including rogue ones...what could he possibly do with all that data? +Well, at least we can scan efficiently. Remember yesterday? We were scanning 8e+10 planets per second! +That's true. I heard some civilizations actually need to be in orbit to get a planet's data! +Seriously?? I remember a few years ago we only had a range of a light-year, even that was terrible... +I can't imagine such primitive technology! +Yeah, if you had to orbit each planet, you couldn't travel 200x light speed! +I bet their technology is so bad that they have to stay in orbit for a minute or so just to scan one planet. +Really now? Then they could only get hundreds of planets a day then. +Boss won't even consider a number without scientific notation nowadays... +Yeah, at least we're advanced...anyway, let's get the day started. Turn on the planet scanner! +*Flips switch* Hey! The terminal is reading SyntaxError: invalid syntax on line 42: print(hello world). +??? What do you mean, shouldn't the console print 'hello world'? +No, dingus. There aren't quotes. No wonder you failed programming 101... +Since you're soooo smart, why don't you fix the error? +We don't have access to the source code of this ship... +What does that mean...? +I can't fix the error, we can't scan planets. +ARE YOU SERIOUS!!! We are 12 TRILLION universes away from home, and NOW you tell me that? +Not my fault, man. We'll be fine though. I just remembered that I have this wireless barcode scanner from work that I accidentally took from my shift. It only has a range of several hundred km though, so we'll have to go orbit planets for a minute or so to scan them. +Uhm, excuse me? That sounds just like that primitive technology! I am NOT having it! +Boss will kill us if we return home with no planet data... +But..a barcode scanner? To scan a planet? That's INSANE! +We'll have to make do with......THE WRONG TOOL FOR THE JOB. \ No newline at end of file diff --git a/cool-cacti/static/pyscript.json b/cool-cacti/static/pyscript.json new file mode 100644 index 00000000..611bdf05 --- /dev/null +++ b/cool-cacti/static/pyscript.json @@ -0,0 +1,20 @@ +{ + "files": { + "/static/scripts/asteroid.py": "", + "/static/scripts/audio.py": "", + "/static/scripts/common.py": "", + "/static/scripts/consolelogger.py": "", + "/static/scripts/controls.py": "", + "/static/scripts/debris.py": "", + "/static/scripts/game.py": "", + "/static/scripts/overlay.py": "", + "/static/scripts/player.py": "", + "/static/scripts/scene_classes.py": "", + "/static/scripts/scene_descriptions.py": "", + "/static/scripts/solar_system.py": "", + "/static/scripts/spacemass.py": "", + "/static/scripts/sprites.py": "", + "/static/scripts/stars.py": "", + "/static/scripts/window.py": "" + } +} \ No newline at end of file diff --git a/cool-cacti/static/scripts/asteroid.py b/cool-cacti/static/scripts/asteroid.py new file mode 100644 index 00000000..ec688e97 --- /dev/null +++ b/cool-cacti/static/scripts/asteroid.py @@ -0,0 +1,274 @@ +import math +import random + +from js import document # type: ignore[attr-defined] +from common import Position, PlanetData +from scene_classes import SceneObject +from window import window, SpriteSheet +from consolelogger import getLogger + +log = getLogger(__name__) + +# Canvas dimensions +canvas = document.getElementById("gameCanvas") +container = document.getElementById("canvasContainer") +SCREEN_W, SCREEN_H = container.clientWidth, container.clientHeight + +ASTEROID_SHEET = window.sprites["asteroids"] + +# "magic numbers" obtained via a script in assets/make_spritesheets.py, end of the printout +# Updated to include recycle sprite collision radii (positions 104-119) +ASTEROID_RADII = [22, 26, 18, 19, 21, 25, 18, 23, 26, 20, 24, 13, 22, 18, 21, 23, 30, 19, 18, 18, 18, 21, 26, + 20, 21, 16, 24, 22, 18, 25, 18, 20, 19, 21, 22, 18, 24, 20, 23, 20, 22, 20, 24, 17, 16, 16, + 18, 21, 17, 22, 24, 25, 14, 24, 25, 14, 22, 23, 21, 18, 20, 18, 18, 19, 24, 23, 23, 27, 19, + 24, 25, 20, 23, 21, 25, 22, 19, 25, 21, 16, 30, 26, 24, 30, 23, 21, 20, 18, 25, 16, 24, 21, + 23, 18, 21, 24, 20, 23, 29, 20, 24, 22, 22, 19, 21, 37, 31, 43, 31, 32, 23, 24, 22, 20, 24, 21, 25, 33, 23, 21] # noqa + + +class Asteroid(SceneObject): + def __init__( + self, sheet: SpriteSheet, + x: float, y: float, + vx: float, vy: float, + target_size_px: float, + sprite_index: int, + grid_cols: int = 11, + cell_size: float = 0, + grow_rate=6.0, + health: int = 450, + damage_mul: float= 1.0 + ): + super().__init__() + super().set_position(x, y) + self.sheet = sheet + self.velocity_x = vx + self.velocity_y = vy + self.rotation = 0.0 + self.rotation_speed = random.uniform(-0.5, 0.5) + self.target_size = target_size_px + self.size = 5.0 + self.grow_rate = target_size_px / random.uniform(grow_rate - 1.8, grow_rate + 2.5) + self.sprite_index = sprite_index + self.grid_cols = grid_cols + self.cell_size = cell_size + self.hitbox_scale = 0.45 + self.hitbox_radius = ASTEROID_RADII[sprite_index] + self._last_timestamp = None + self.linger_time = 0.5 + self.full_size_reached_at = None + self.health = random.uniform(health * 0.8, health * 1.2) + self.damage_mul = random.uniform(damage_mul * 0.9, damage_mul * 1.1) + + def _ensure_cell_size(self): + if not self.cell_size: + if self.sheet.width: + self.cell_size = max(1, int(self.sheet.width // self.grid_cols)) + + def _src_rect(self): + self._ensure_cell_size() + col = self.sprite_index % self.grid_cols + row = self.sprite_index // self.grid_cols + x = col * self.cell_size + y = row * self.cell_size + return x, y, self.cell_size, self.cell_size + + def update(self, timestamp: float): + if self._last_timestamp is None: + self._last_timestamp = timestamp + return + dt = (timestamp - self._last_timestamp) / 1000.0 # seconds + self._last_timestamp = timestamp + + # Movement + self.x += self.velocity_x * dt + self.y += self.velocity_y * dt + self.rotation += self.rotation_speed * dt + + # Growth towards target size + if self.size < self.target_size: + self.size = self.size + self.grow_rate * dt + if self.size >= self.target_size: + self.full_size_reached_at = timestamp + + def render(self, ctx, timestamp_ms: float): + self.update(timestamp_ms) + self._ensure_cell_size() + if not self.cell_size: + return + + x, y, w, h = self._src_rect() + size = self.size + + ctx.save() + ctx.translate(self.x, self.y) + ctx.rotate(self.rotation) + + # Draw centered + ctx.drawImage(self.sheet.image, x, y, w, h, -size / 2, -size / 2, size, size) + + # Debug hit circle + if getattr(window, "DEBUG_DRAW_HITBOXES", False): + ctx.beginPath() + ctx.strokeStyle = "#FF5555" + ctx.lineWidth = 2 + ctx.arc(0, 0, size * self.hitbox_radius / 100 * 1, 0, 2 * math.pi) + ctx.stroke() + ctx.restore() + + def is_off_screen(self, w=SCREEN_W, h=SCREEN_H, margin=50) -> bool: + return self.x < -margin or self.x > w + margin or self.y < -margin or self.y > h + margin + + def get_hit_circle(self): + return (self.x, self.y, self.size * self.hitbox_radius / 100 * 1) + + def should_be_removed(self): + """Check if asteroid should be removed (off screen or lingered too long)""" + if self.is_off_screen(): + return True + if self.full_size_reached_at and (self._last_timestamp - self.full_size_reached_at) > ( + self.linger_time * 1000 + ): + return True + if self.health <= 0: + window.debris.generate_debris(window.player.get_position(), self.get_position(), 4) + window.debris.generate_debris(window.player.get_position(), self.get_position(), 3.75) + return True + return False + +# updated spawn_on_player, it looked goofy near planets with high chance +class AsteroidAttack: + def __init__(self, spritesheet, width: int, height: int, max_size_px: float, spawnrate: int = 500, spawn_at_player_chance: int = 50): + self.sheet = spritesheet + self.w = width + self.h = height + self.max_size = max_size_px or 256 + self.spawnrate = spawnrate + self.asteroids: list[Asteroid] = [] + self._last_spawn = 0.0 + self._max_asteroids = 50 # default max asteroids that can appear on the screen + self.cell_size = 0 + self._use_grow_rate = 6.0 # default growth rate (how fast they appear to approach the player) + self._use_health = 450 # default durability (affects asteroids being destroyed by impacts w/ player) + self._use_damage_mul = 1.0 + self.spawn_at_player_chance = spawn_at_player_chance + def _spawn_one(self): + # Don't spawn if at the limit + if len(self.asteroids) >= self._max_asteroids: + return + + # Planet area (left side) + planet_width = self.w * 0.3 + space_start_x = planet_width + 50 + if random.randint(1, self.spawn_at_player_chance) == 1: + x = window.player.x + y = window.player.y + else: + x = random.uniform(space_start_x, self.w) + y = random.uniform(0, self.h) + + if x < (SCREEN_W / 2): + velocity_x = random.uniform(-15, -5) + if y < (SCREEN_H / 2): + velocity_y = random.uniform(-15, -5) + else: + velocity_y = random.uniform(5, 15) + else: + velocity_x = random.uniform(5, 15) + if y < (SCREEN_H / 2): + velocity_y = random.uniform(-15, -5) + else: + velocity_y = random.uniform(5, 15) + + # Use recycle sprites (104-119) for Earth, regular asteroids (0-103) for other planets + if hasattr(self, '_current_planet_name') and self._current_planet_name.lower() == 'earth': + idx = random.randint(104, 119) # Recycle sprites + # Scale recycle items smaller since they're items, not large asteroids + target = random.uniform(self.max_size * 0.25, self.max_size * 0.45) + # log.debug("Spawning recycle sprite %d for Earth with smaller target size %f", idx, target) + else: + idx = random.randint(0, 103) # Regular asteroid sprites + target = random.uniform(self.max_size * 0.7, self.max_size * 1.3) + # if hasattr(self, '_current_planet_name'): + # log.debug("Spawning asteroid sprite %d for %s", idx, self._current_planet_name) + + a = Asteroid( + self.sheet, x, y, velocity_x, velocity_y, target, idx, + grow_rate=self._use_grow_rate, + health=self._use_health, + damage_mul=self._use_damage_mul + ) + self.asteroids.append(a) + + # Spawn at interval and only if under limit + def spawn_and_update(self, timestamp: float): + # adjust spawnrate by a random factor so asteroids don't spawn at fixed intervals + spawnrate = self.spawnrate * random.uniform(0.2, 1.0) + + # Increase spawn rate for smaller recycle items on Earth + if hasattr(self, '_current_planet_name') and self._current_planet_name.lower() == 'earth': + spawnrate *= 0.1 # 10x faster spawn rate for Earth recycle items (1/10 = 0.1) + + # slow down spawnrate for this attempt a bit if there already many asteroids active + spawnrate = spawnrate * max(1, 1 + (len(self.asteroids) - 35) * 0.1) + if self._last_spawn == 0.0 or (timestamp - self._last_spawn) >= spawnrate: + if len(self.asteroids) < self._max_asteroids: + self._last_spawn = timestamp + self._spawn_one() + + # Remove asteroids + before_count = len(self.asteroids) + self.asteroids = [a for a in self.asteroids if not a.should_be_removed()] + after_count = len(self.asteroids) + + # If we removed asteroids, we can spawn new ones + if after_count < before_count: + self._last_spawn = timestamp - (self.spawnrate * 0.7) + + def update_and_render(self, ctx, timestamp: float): + self.spawn_and_update(timestamp) + for a in self.asteroids: + a.render(ctx, timestamp) + + def reset(self, planet_data: PlanetData): + """ reset the asteroid management system with the given difficulty parameters """ + + # Store the planet name for sprite selection + self._current_planet_name = planet_data.name + + # the asteroid difficulty settings are on a 1-20 scale of ints + asteroid_settings = planet_data.asteroid + + spawnrate = 500 + # clamp max between 10-80, default is 50 at difficulty 10 + max_asteroids = min(max(10, 5 * asteroid_settings.count), 80) + + # this determines how quickly asteroids seem to be approaching player (sprite growing in size) + # NOTE: the relationship is inverse, smaller growth rate = faster approaching asteroids + # a value of 6.0 feels like a pretty good rough default, not too slow + use_grow_rate = max(1.2, 10.5 - (asteroid_settings.speed - 5) * 0.5) + # how easily asteroids fall apart from collisions, default 450 health at level 10 + use_health = 50 + 40 * asteroid_settings.durability + # range of 0.3 to 2.2 multiplier + use_damage_mul = 0.2 + 0.1 * asteroid_settings.damage + + log.debug("Resetting asteroids with difficulty parameters for planet %s:", planet_data.name) + log.debug("Max asteroids: %s (%s), default 50", max_asteroids, asteroid_settings.count) + log.debug("Grow rate(approach speed): %s (%s), default 6.0", use_grow_rate, asteroid_settings.speed) + log.debug("Asteroid durability: %s (%s), default 450", use_health, asteroid_settings.durability) + log.debug("Damage multiplier: %s (%s), default 1.0", use_damage_mul, asteroid_settings.damage) + + # Special difficulty adjustments for Earth recycle items + if planet_data.name.lower() == 'earth': + max_asteroids = min(max_asteroids * 2, 120) # Allow up to 2x more recycle items + use_grow_rate *= 0.6 # Make them approach 40% faster + use_health *= 1.5 # Make them 50% more durable + use_damage_mul *= 0.7 # Decrease damage by 30% + + self._max_asteroids = max_asteroids + self._use_grow_rate = use_grow_rate + self._use_health = use_health + self._use_damage_mul = use_damage_mul + + self.asteroids.clear() + self._last_spawn = 0.0 + self.cell_size = 0 diff --git a/cool-cacti/static/scripts/audio.py b/cool-cacti/static/scripts/audio.py new file mode 100644 index 00000000..59f42b53 --- /dev/null +++ b/cool-cacti/static/scripts/audio.py @@ -0,0 +1,84 @@ +import random +from typing import Union +from functools import partial + +from js import Audio # type: ignore[attr-defined] + + +class AudioHandler: + def __init__(self, static_url: str) -> None: + self.static_url = static_url + self.volume: int = 1.0 + + self.text_sound = self.load_audio("text.ogg") + self.scan_sound = self.load_audio("scan.ogg") + self.explosion_sound = self.load_audio("explosion.ogg") + + self.music_main = self.load_audio("music_main.ogg") + self.music_thematic = self.load_audio("music_thematic.ogg") + self.music_death = self.load_audio("death.ogg") + + self.active_music = None + + def set_volume(self, volume: float) -> None: + """ set volume to somewhere between 0.0 and 1.0 if a valid value is given """ + if 0.0 <= volume <= 1.0: + self.volume = volume + + def load_audio(self, audio_name: str) -> Audio: + return Audio.new(f"{self.static_url}audio/{audio_name}") + + def play_sound(self, audio_name: Union[str, "Audio"], volume=1.0) -> None: + """ + play a sound file + audio_name: name of sound file, without any path included, as it appears in static/audio/ + volume: adjust this for loud sound files, 0.0 to 1.0, where 1.0 is full volume + """ + # sometimes we want to load new instances of Audio objcts, other times we want a persistent one + if isinstance(audio_name, str): + sound = self.load_audio(audio_name) + else: + sound = audio_name + sound.volume = volume * self.volume + sound.play() + + def play_bang(self) -> None: + # these bangs are kind of loud, playing it at reduced volume + self.play_sound(random.choice(["bang1.ogg", "bang2.ogg", "bang3.ogg"]), volume=0.4) + + def play_unique_sound(self, audio: Audio, pause_it=False, volume=1.0) -> None: + if not pause_it and audio.paused: + self.play_sound(audio, volume=volume) + elif pause_it: + audio.pause() + audio.currentTime = 0 + + def play_text(self, pause_it=False, volume=0.8) -> None: + self.play_unique_sound(self.text_sound, pause_it, volume=volume) + + def play_scan(self, pause_it=False, volume=0.4) -> None: + self.play_unique_sound(self.scan_sound, pause_it, volume=volume) + + def play_explosion(self, pause_it=False, volume=0.6) -> None: + self.play_unique_sound(self.explosion_sound, pause_it, volume=volume) + + def _play_music(self, music_audio, pause_it=False, volume=1.0) -> None: + if pause_it: + music_audio.pause() + music_audio.currentTime = 0 + self.active_music = None + return + # if another music file is playing, don't play this one + if self.active_music and not self.active_music.paused: + return + self.active_music = music_audio + self.play_unique_sound(music_audio, volume=volume) + + def play_music_main(self, pause_it=False, volume=0.65) -> None: + self._play_music(self.music_main, pause_it=pause_it, volume=volume) + + def play_music_death(self, pause_it=False, volume=1.0) -> None: + self._play_music(self.music_death, pause_it=pause_it, volume=volume) + + def play_music_thematic(self, pause_it=False, volume=1.0) -> None: + self._play_music(self.music_thematic, pause_it=pause_it, volume=volume) \ No newline at end of file diff --git a/cool-cacti/static/scripts/common.py b/cool-cacti/static/scripts/common.py new file mode 100644 index 00000000..e96be15f --- /dev/null +++ b/cool-cacti/static/scripts/common.py @@ -0,0 +1,155 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Iterator + +HTMLImageElement = Any +CanvasRenderingContext2D = Any + +@dataclass +class AsteroidData: + """Dataclass for asteroid data from asteroids.json. Difficulty stuff is on a 1-20 scale""" + + count: int + speed: int + damage: int + durability: int + +@dataclass +class PlanetData: + """Dataclass for planet data from planets.json""" + + id: int + name: str + sprite: str + x: float = 0.0 + y: float = 0.0 + info: str = "" + level: list[str] = field(default_factory=list) + scan_multiplier: float = 1.0 + asteroid: AsteroidData | None = None + spritesheet: SpriteSheet | None = None # JS Image object added in HTML + + @classmethod + def from_dict(cls, data: dict) -> 'PlanetData': + """Create PlanetData from dictionary, handling nested asteroid data.""" + data = data.copy() + # Handle nested asteroid data + asteroid_data = None + if 'asteroid' in data: + asteroid_dict = data.pop('asteroid') # Remove from data to avoid duplicate in **data + asteroid_data = AsteroidData(**asteroid_dict) + + # Create instance with unpacked dictionary + return cls(asteroid=asteroid_data, **data) + +@dataclass +class Rect: + left: float + top: float + width: float + height: float + + def __iter__(self) -> Iterator[float]: + yield self.left + yield self.top + yield self.width + yield self.height + + def contains(self, point: Position) -> bool: + return self.left <= point.x <= self.right and self.top <= point.y <= self.bottom + + @property + def right(self) -> float: + return self.left + self.width + + @right.setter + def right(self, value: float) -> None: + self.left = value - self.width + + @property + def bottom(self) -> float: + return self.top + self.height + + @bottom.setter + def bottom(self, value: float) -> None: + self.top = value - self.height + + +@dataclass +class Position: + x: float + y: float + + def __iter__(self) -> Iterator[float]: + yield self.x + yield self.y + + def __add__(self, other_pos: Position) -> Position: + return Position(self.x + other_pos.x, self.y + other_pos.y) + + def midpoint(self, other_pos: Position) -> Position: + return Position((self.x + other_pos.x) / 2, (self.y + other_pos.y) / 2) + + def distance(self, other_pos: Position) -> float: + return ((self.x - other_pos.x) ** 2 + (self.y - other_pos.y) ** 2) ** 0.5 + + +@dataclass +class PlanetState: + """State for planet""" + + mass: float + radius: float + initial_velocity: float = 0.0 + x: float = 0 + y: float = 0 + angle: float = 0.0 + velocity_x: float = 0.0 + velocity_y: float = 0.0 + + +class SpriteSheet: + """Wrapper for individual sprites with enhanced functionality.""" + + def __init__(self, key: str, image: "HTMLImageElement"): + self.key = key.lower() + self.image = image + + @property + def height(self): + """Height of the sprite image.""" + return self.image.height + + @property + def width(self): + """Width of the sprite image.""" + return self.image.width + + @property + def frame_size(self): + """Size of each frame (assuming square frames).""" + return self.height + + @property + def is_loaded(self): + return self.height > 0 and self.width > 0 + + @property + def num_frames(self): + """Number of frames in the spritesheet.""" + if not self.is_loaded: + return 1 + return self.width // self.frame_size + + def get_frame_position(self, frame: int) -> Position: + """Get the position of a specific frame in the spritesheet with overflow handling.""" + if self.num_frames == 0: + return Position(0, 0) + frame_index = frame % self.num_frames + x = frame_index * self.frame_size + return Position(x, 0) + + # Delegate other attributes to the underlying image + def __getattr__(self, name): + return getattr(self.image, name) diff --git a/cool-cacti/static/scripts/consolelogger.py b/cool-cacti/static/scripts/consolelogger.py new file mode 100644 index 00000000..ab1b183b --- /dev/null +++ b/cool-cacti/static/scripts/consolelogger.py @@ -0,0 +1,47 @@ +import logging + +from js import console # type: ignore[attr-defined] + + +class ConsoleHandler(logging.Handler): + def emit(self, record): + try: + msg = self.format(record) + + if record.levelno >= logging.ERROR: + console.error(msg) + elif record.levelno >= logging.WARNING: + console.warn(msg) + elif record.levelno >= logging.INFO: + console.info(msg) + else: + console.debug(msg) + except Exception: + self.handleError(record) + + +# why the heck does python's standard lib use camelCase? :( I'm just mimicking logging.getLogger ... +def getLogger(name, show_time: bool = False) -> logging.Logger: + """ + to get a logger in another file that outputs only to the browser javascript console and doesn't insert + its output into the webpage, simply use: + + from consolelogger import getLogger + log = getLogger(__name__) + log.debug. ("This is a log message") # etc. + """ + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + + # set up the logger so it only outputs to the browser's javascript console. Spiffy + handler = ConsoleHandler() + if not show_time: + handler.setFormatter(logging.Formatter("[%(levelname)s] %(name)s: %(message)s")) + else: + formatter = logging.Formatter("[%(levelname)s %(asctime)s] %(name)s: %(message)s", datefmt="%H:%M:%S") + handler.setFormatter(formatter) + + logger.handlers.clear() + logger.addHandler(handler) + + return logger diff --git a/cool-cacti/static/scripts/controls.py b/cool-cacti/static/scripts/controls.py new file mode 100644 index 00000000..5bbde962 --- /dev/null +++ b/cool-cacti/static/scripts/controls.py @@ -0,0 +1,134 @@ +from dataclasses import dataclass +from time import time + +from common import Position +from consolelogger import getLogger +from pyodide.ffi import create_proxy # type: ignore[attr-defined] + +log = getLogger(__name__) + + +@dataclass +class MousePositions: + mousedown: Position + mouseup: Position + click: Position + move: Position + + +class GameControls: + """ + in game.py using + controls = GameControls(canvas) + controls object gives access to what keys are being currently pressed, accessible properties: + - controls.pressed is a set of strings representing keys and mouse buttons currently held down + the strings for mouse buttons are given by GameControls.MOUSE_LEFT, etc. + - controls.mouse gives access to all the coordinates of the last registered mouse event of each kind as the + tuples controls.mouse.mousedown, controls.mouse.mouseup, controls.mouse.click, controls.mouse.move + - use controls.mouse.move for best current coordinates of the mouse + - additionally, controls.click is a boolean representing if a click just occurred. It is set to False at the + end of each game loop if nothing makes use of the click event + - use enable_logging=False if spam of mouse/key events in browser console gets annoying + """ + + MOUSE_LEFT = "mouse_left" + MOUSE_RIGHT = "mouse_right" + MOUSE_MIDDLE = "mouse_middle" + + # just to use internally in the class to translate the 0, 1, 2 javascript convention + mouse_button_map = {0: MOUSE_LEFT, 1: MOUSE_MIDDLE, 2: MOUSE_RIGHT} + + def __init__(self, canvas, enable_logging=False): + # keep track of what keys \ mouse buttons are currently pressed in this variable + self.pressed = set() + # keep track of the last coordinates used by all mouse events + self.mouse = MousePositions(Position(0, 0), Position(0, 0), Position(0, 0), Position(0, 0)) + # keep track of whether a click has occurred + self.click = False + + # enable logging of mouse and key events in the console for debug purposes + self._logging = enable_logging + self._last_mousemove_log = 0 + + on_canvas_mousedown_proxy = create_proxy(self.on_canvas_mousedown) + on_canvas_mouseup_proxy = create_proxy(self.on_canvas_mouseup) + on_canvas_click_proxy = create_proxy(self.on_canvas_click) + on_canvas_mousemove_proxy = create_proxy(self.on_canvas_mousemove) + on_keydown_proxy = create_proxy(self.on_keydown) + on_keyup_proxy = create_proxy(self.on_keyup) + + canvas.addEventListener("mousedown", on_canvas_mousedown_proxy) + canvas.addEventListener("mouseup", on_canvas_mouseup_proxy) + canvas.addEventListener("click", on_canvas_click_proxy) + canvas.addEventListener("mousemove", on_canvas_mousemove_proxy) + canvas.addEventListener("keydown", on_keydown_proxy) + canvas.addEventListener("keyup", on_keyup_proxy) + + # helper method so we don't need to copy and paste this to every mouse event + def get_mouse_event_coords(self, event) -> Position: + canvas_rect = event.target.getBoundingClientRect() + return Position(event.clientX - canvas_rect.left, event.clientY - canvas_rect.top) + + def on_canvas_mousedown(self, event): + pos = self.get_mouse_event_coords(event) + self.mouse.move = pos + self.mouse.mousedown = pos + + if event.button in self.mouse_button_map: + button = self.mouse_button_map[event.button] + self.pressed.add(button) + + if self._logging: + log.debug("mousedown %s %s, %s", button, pos.x, pos.y) + + def on_canvas_mouseup(self, event): + pos = self.get_mouse_event_coords(event) + self.mouse.move = pos + self.mouse.mouseup = pos + + if event.button in self.mouse_button_map: + button = self.mouse_button_map[event.button] + if button in self.pressed: + self.pressed.remove(button) + + if self._logging: + log.debug("mouseup %s %s, %s", button, pos.x, pos.y) + + def on_canvas_click(self, event): + pos = self.get_mouse_event_coords(event) + self.mouse.move = pos + self.mouse.click = pos + + self.click = True + if self._logging: + log.debug("click %s, %s", pos.x, pos.y) + + def on_canvas_mousemove(self, event): + pos = self.get_mouse_event_coords(event) + self.mouse.move = pos + + # throttle number of mousemove logs to prevent spamming the debug log + if self._logging and (now := time()) - self._last_mousemove_log > 2.5: + log.debug("mousemove %s, %s", pos.x, pos.y) + self._last_mousemove_log = now + + # TODO: check event.buttons here (tells which buttons are pressed during mouse move) if mouse is pressed + # down on canvas, then moved off, and button is unpressed while off the canvas, mouse buttons may be + # flagged as down when they aren't anymore, checking event.buttons would be a good way to 'unstuck' them + + def on_keydown(self, event): + if event.key in ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]: + event.preventDefault() + self.pressed.add(event.key) + if self._logging: + log.debug("keydown %s", event.key) + + def on_keyup(self, event): + if event.key in self.pressed: + self.pressed.remove(event.key) + if self._logging: + log.debug("keyup %s", event.key) + + # TODO: probably also need a way to handle canvas losing focus and missing key up events, for example if alt + # tabbing away, it registers a key down event, but the not a key up event since it has already lost focus by + # that point diff --git a/cool-cacti/static/scripts/debris.py b/cool-cacti/static/scripts/debris.py new file mode 100644 index 00000000..2474df35 --- /dev/null +++ b/cool-cacti/static/scripts/debris.py @@ -0,0 +1,128 @@ +import math +from random import randint + +from common import Position +from scene_classes import SceneObject +from window import window + + +class Debris(SceneObject): + def __init__(self, position: Position, color: str, radius: float, duration: int, rotation: float) -> None: + super().__init__() + super().set_position(position) + + self.color = color + self.radius = radius + # number of frames until this debris should decay + self.initial_duration = self.duration = duration + self.rotation = rotation + self.momentum = Position(0, 0) + + def update(self) -> None: + # decay duration by 1 frame + self.duration -= 1 + + # adjust position based on momentum and break Newton's laws a little + self.x += self.momentum.x * 0.4 + self.y += self.momentum.y * 0.4 + self.momentum.x *= 0.97 + self.momentum.y *= 0.97 + + def render(self, ctx, timestamp) -> None: + ctx.save() + + ctx.translate(*self.get_position()) + ctx.rotate(self.rotation) + + ctx.beginPath() + # Outer arc + ctx.arc(0, 0, self.radius, 0, 2 * math.pi, False) + # Inner cut arc (opposite winding, offset) + ctx.arc( + self.radius * 0.3 * self.duration / self.initial_duration, + 0, + self.radius * (1.2 - 0.8 * self.duration / self.initial_duration), + 0, + 2 * math.pi, + True, + ) + + ctx.closePath() + ctx.fillStyle = self.color + ctx.globalAlpha = min(self.duration / 255, 1.0) # normalize to 0..1 + ctx.fill() + + ctx.restore() + # DEBUGGING THE CIRCLES DEFINING CRESCENT ABOVE ^ + if window.DEBUG_DRAW_HITBOXES: + ctx.save() + ctx.translate(*self.get_position()) + ctx.rotate(self.rotation) + ctx.strokeStyle = "#FF0000" + ctx.beginPath() + ctx.arc(0, 0, self.radius, 0, 2 * math.pi, False) + ctx.stroke() + ctx.closePath() + + ctx.strokeStyle = "#00FF00" + ctx.beginPath() + ctx.arc( + self.radius * 0.3 * self.duration / self.initial_duration, + 0, + self.radius * (1.2 - 0.8 * self.duration / self.initial_duration), + 0, + 2 * math.pi, + True, + ) + ctx.stroke() + ctx.closePath() + ctx.restore() + + super().render(ctx, timestamp) + + +class DebrisSystem(SceneObject): + def __init__(self) -> None: + super().__init__() + + self.debris_list: list[Debris] = [] # will be filled with debris object instances + + def update(self) -> None: + # tick each debris' timer and discard any debris whose timer has run out + for debris in self.debris_list: + debris.update() + self.debris_list = list(filter(lambda deb: deb.duration > 0, self.debris_list)) + + def generate_debris(self, player_pos: Position, asteroid_pos: Position, max_size=3) -> None: + distance = player_pos.distance(asteroid_pos) + new_debris = [] + for _ in range(randint(3, 5)): + position = player_pos.midpoint(asteroid_pos) + Position(randint(-20, 20), randint(-20, 20)) + shade = randint(128, 255) + color = f"#{shade:x}{shade:x}{shade:x}" + radius = randint(15, 25) * min(50 / distance, max_size) + duration = randint(100, 200) + rotation = 0 + + new_debris.append(Debris(position, color, radius, duration, rotation)) + + new_debris_center = Position( + sum(debris.x for debris in new_debris) / len(new_debris), + sum(debris.y for debris in new_debris) / len(new_debris), + ) + + for debris in new_debris: + debris.momentum = Position((debris.x - new_debris_center.x) / 5.0, (debris.y - new_debris_center.y) / 5.0) + debris.rotation = math.atan2(-debris.y + new_debris_center.y, -debris.x + new_debris_center.x) + + self.debris_list.extend(new_debris) + + def render(self, ctx, timestamp) -> None: + """Render every debris""" + for debris in self.debris_list: + debris.render(ctx, timestamp) + + super().render(ctx, timestamp) + + def reset(self): + self.debris_list = [] diff --git a/cool-cacti/static/scripts/game.py b/cool-cacti/static/scripts/game.py new file mode 100644 index 00000000..554dce15 --- /dev/null +++ b/cool-cacti/static/scripts/game.py @@ -0,0 +1,76 @@ +from asteroid import AsteroidAttack +from consolelogger import getLogger +from controls import GameControls +from debris import DebrisSystem +from js import document # type: ignore[attr-defined] +from player import Player, Scanner +from pyodide.ffi import create_proxy # type: ignore[attr-defined] +from scene_classes import Scene +from scene_descriptions import create_scene_manager +from window import window + +log = getLogger(__name__) + +# References to the useful html elements +loadingLabel = document.getElementById("loadingLabel") +container = document.getElementById("canvasContainer") +width, height = container.clientWidth, container.clientHeight +canvas = window.canvas +ctx = window.ctx = window.canvas.getContext("2d") + +window.DEBUG_DRAW_HITBOXES = False + +# TODO: the resizing and margins needs work, I suck with CSS / html layout +def resize_canvas(event=None) -> None: + width, height = container.clientWidth, container.clientHeight + canvas.width = width + canvas.height = height + canvas.style.width = f"{width}px" + canvas.style.height = f"{height}px" + + +resize_proxy = create_proxy(resize_canvas) +window.addEventListener("resize", resize_proxy) +resize_canvas() + +""" +I'm not entirely clear on what this create_proxy is doing, but when passing python functions as callbacks to +"javascript" (well pyscript wrappers for javascript functionality) we need to wrap them in these proxy objects +instead of passing them as straight up python function references. +""" + +# setup of important systems, expose them globally via window object +controls = window.controls = GameControls(canvas) +scene_manager = window.scene_manager = create_scene_manager() +player = window.player = Player( + window.get_sprite("player"), window.get_sprite("health"), canvas.width / 2, canvas.height / 2, scale=0.1 +) +window.asteroids = AsteroidAttack(window.get_sprite("asteroids"), width, height, 256) +window.debris = DebrisSystem() + +scanner = window.scanner = Scanner(window.get_sprite("scanner"), player, min_x=width * 0.45, scan_mult=1) +log.info("Created player at position (%s, %s)", player.x, player.y) + +loadingLabel.style.display = "none" + + +def game_loop(timestamp: float) -> None: + """Timestamp argument will be time since the html document began to load, in miliseconds.""" + + # these should disable bilinear filtering smoothing, which isn't friendly to pixelated graphics + ctx.imageSmoothingEnabled = False + ctx.webkitImageSmoothingEnabled = False + ctx.mozImageSmoothingEnabled = False + ctx.msImageSmoothingEnabled = False + + active_scene: Scene = scene_manager.get_active_scene() + active_scene.render(ctx, timestamp) + + # if a click event occurred and nothing made use of it during this loop, clear the click flag + controls.click = False + # Schedule next frame + window.requestAnimationFrame(game_loop_proxy) + +# Start loop +game_loop_proxy = create_proxy(game_loop) +window.requestAnimationFrame(game_loop_proxy) \ No newline at end of file diff --git a/cool-cacti/static/scripts/overlay.py b/cool-cacti/static/scripts/overlay.py new file mode 100644 index 00000000..8e9840de --- /dev/null +++ b/cool-cacti/static/scripts/overlay.py @@ -0,0 +1,321 @@ +import re + +from window import window +from common import Position, CanvasRenderingContext2D, Rect +from consolelogger import getLogger +from scene_classes import Scene, SceneManager +from spacemass import SpaceMass + +log = getLogger(__name__) + +def rgba_to_hex(rgba_str): + """ + Convert "rgba(r, g, b, a)" to hex string "#RRGGBB". + Alpha is ignored. + """ + # Extract the numbers + match = re.match(r"rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\s*\)", rgba_str) + if not match: + raise ValueError(f"Invalid RGBA string: {rgba_str}") + + r, g, b = map(int, match.groups()) + return f"#{r:02X}{g:02X}{b:02X}" + +class TextOverlay(Scene): + DEFAULT = "No information found :(" + + def __init__(self, name: str, scene_manager: SceneManager, text: str, color="rgba(0, 255, 0, 0.8)", rect=None, hint=None): + super().__init__(name, scene_manager) + self.bold = False + self.color = color + self.calculate_and_set_font() + self.set_text(text) + self.char_delay = 10 # milliseconds between characters + self.margins = Position(200, 50) + self.button_label = None + self.button_click_callable = None + self.other_click_callable = None + self.deactivate() + self.rect = rect # tuple: (x, y, width, height) + self.muted = True + self.center = False + self.hint = hint + + def deactivate(self): + self.active = False + # pause text sound in case it was playing + window.audio_handler.play_text(pause_it=True) + + def set_text(self, text: str): + """ + Set a new text message for this object to display and resets relevant properties like the + current character position to be ready to start over. Text width is calculated for centered text + rendering. + """ + self.displayed_text = "" + self.text = text + self.char_index = 0 + self.last_char_time = 0 + + # calculate text width in case we want centered text, we won't have to calculate it every frame + self._prepare_font(window.ctx) + self._text_width = max(window.ctx.measureText(line).width for line in self.text.split("\n")) + + def set_button(self, button_label: str | None): + self.button_label = button_label + + def calculate_and_set_font(self) -> str: + # Set text style based on window size + base_size = min(window.canvas.width, window.canvas.height) / 50 + font_size = max(12, min(20, base_size)) # Scale between 12px and 20px + self.font = {"size": font_size, "font": "'Courier New', monospace"} + return self.font + + def update_textstream(self, timestamp): + """Update streaming text""" + + if timestamp - self.last_char_time > self.char_delay and self.char_index < len(self.text): + if not self.muted: + window.audio_handler.play_text() + + chars_to_add = min(3, len(self.text) - self.char_index) + self.displayed_text += self.text[self.char_index : self.char_index + chars_to_add] + self.char_index += chars_to_add + self.last_char_time = timestamp + if self.char_index == len(self.text): + window.audio_handler.play_text(pause_it=True) + + def _prepare_font(self, ctx): + font = self.font or self.calculate_and_set_font() + ctx.font = f"{'bold ' if self.bold else ''}{font['size']}px {font['font']}" + ctx.fillStyle = rgba_to_hex(self.color) + return font + + def render_and_handle_button(self, ctx: CanvasRenderingContext2D, overlay_bounds: Rect) -> Rect: + """ + this function returns the button's bounding Rect as a byproduct, so it can be + conveniently used to check for click events in the calling function + """ + if not self.button_label: + return None + + ctx.save() + ctx.font = "14px Courier New" + text_width = ctx.measureText(self.button_label).width + + button_bounds = Rect(overlay_bounds.right - (text_width + 30), overlay_bounds.bottom - 44, text_width + 20, 34) + + ctx.fillStyle = "rgba(0, 0, 0, 0.95)" + ctx.fillRect(*button_bounds) + + # check whether mouse is currently moving over the button + if button_bounds.contains(window.controls.mouse.move): + ctx.fillStyle = "#ffff00" + else: + ctx.fillStyle = "#00ff00" + + ctx.fillText(self.button_label, button_bounds.left + 10, button_bounds.bottom - 10) + ctx.strokeStyle = "rgba(0, 255, 0, 0.95)" + ctx.lineWidth = 2 + ctx.strokeRect(*button_bounds) + + ctx.restore() + return button_bounds + + def render(self, ctx: CanvasRenderingContext2D, timestamp): + if not self.active or not self.text: + return + + self.update_textstream(timestamp) + + if self.rect: + x, y, width, height = self.rect + overlay_bounds = Rect(x, y, width, height) + else: + overlay_width = window.canvas.width - 2 * self.margins.x + overlay_height = window.canvas.height - 2 * self.margins.y + overlay_bounds = Rect(self.margins.x, self.margins.y, overlay_width, overlay_height) + + # Draw transparent console background + ctx.fillStyle = "rgba(0, 0, 0, 0.8)" + + ctx.fillRect(*overlay_bounds) + + # Draw console border + ctx.strokeStyle = self.color + ctx.lineWidth = 2 + ctx.strokeRect(*overlay_bounds) + ctx.strokeRect( + overlay_bounds.left + 3, overlay_bounds.top + 3, overlay_bounds.width - 6, overlay_bounds.height - 6 + ) + + font = self._prepare_font(ctx) + + # Draw streaming text + lines = self.displayed_text.split("\n") + line_height = font["size"] + 4 + + if self.center: + # Center both horizontally and vertically + total_text_height = len(lines) * line_height + start_y = overlay_bounds.top + (overlay_bounds.height - total_text_height) / 2 + font["size"] + start_x = (window.canvas.width - self._text_width) / 2 + else: + start_y = overlay_bounds.top + font["size"] + 10 # use overlay_bounds.top + start_x = overlay_bounds.left + 10 + + for i, line in enumerate(lines): + y_pos = start_y + i * line_height + if y_pos < overlay_bounds.bottom - 10: # don't draw outside overlay + ctx.fillText(line, start_x, y_pos) + + # Draw hint if any at bottom left + if self.hint: + ctx.fillText(self.hint, overlay_bounds.left + 10, overlay_bounds.bottom - 10) + + button_bounds = self.render_and_handle_button(ctx, overlay_bounds) + if window.controls.click: + # log.debug(self.button_click_callable) + # log.debug(self.other_click_callable) + # if a click occurred and we don't have a button or we clicked outside the button + if button_bounds is None or not button_bounds.contains(window.controls.mouse.click): + if self.other_click_callable is not None: + self.other_click_callable() + # otherwise, button was clicked + elif self.button_click_callable is not None: + self.button_click_callable() + + +class ResultsScreen(TextOverlay): + def __init__(self, name: str, scene_manager: SceneManager, planet: SpaceMass): + self.planet_data = window.get_planet(planet.name) + text = self.planet_data.info if self.planet_data else "" + super().__init__(name, scene_manager, text) + # default sizing for scan results screen + self.margins = Position(200, 50) + +class DeathScreen(TextOverlay): + def __init__(self, name: str, scene_manager: SceneManager): + super().__init__(name, scene_manager, "GAME OVER", color="rgba(0, 255, 0, 0.9)") + # Center the death screen + self.margins = Position(150, 150) + self.center = True + self.muted = True # This only refers to text terminal sound, not audio in general + self.bold = True + + def calculate_and_set_font(self) -> str: + base_size = min(window.canvas.width, window.canvas.height) / 15 + font_size = max(32, min(72, base_size)) # Scale between 32px and 72px + self.font = {"size": font_size, "font": "'Courier New', monospace"} + return self.font + + def render(self, ctx: CanvasRenderingContext2D, timestamp): + window.audio_handler.play_music_death() + super().render(ctx, timestamp) + + +class Dialogue(TextOverlay): + def __init__(self, name: str, scene_manager: SceneManager, text: str): + # Initialize the first line using the TextOverlay constructor + lines = text.split("\n") + first_line = lines[0] if lines else "" + super().__init__(name, scene_manager, first_line) + + # Store all lines and keep track of current index + self.lines = lines + self.current_index = 0 + self.swap_color = False + self.is_col1 = False + self.switch_color() + self.done = False + + def next(self): + """Advance to the next line of dialogue.""" + self.current_index += 1 + if self.current_index < len(self.lines): + self.switch_color() + # Use the TextOverlay method to set the next line + self.set_text(self.lines[self.current_index].strip()) + self.active = True + else: + # No more lines + self.done = True + self.deactivate() + + def render(self, ctx: CanvasRenderingContext2D, timestamp): + """Render the currently active line.""" + + message_parts = self.lines[self.current_index].strip().split(' ') + split_message = [] + len_text_line = 0 + partial_message = '' + + for part in message_parts: + word_width = ctx.measureText(part + ' ').width # include space + if len_text_line + word_width <= self.rect[2]: + partial_message += part + ' ' + len_text_line += word_width + else: + # save current line before adding the new word + split_message.append(partial_message.rstrip()) + # start new line with current word + partial_message = part + ' ' + len_text_line = word_width + + if partial_message: + split_message.append(partial_message.rstrip()) + + formatted_message = '' + for part in split_message: + formatted_message += part + '\n' + self.text = formatted_message + + super().render(ctx, timestamp) + + def switch_color(self): + self.is_col1 = not self.is_col1 + if self.is_col1: + self.color = "rgba(0, 255, 0, 0.8)" + else: + self.color = "rgba(170, 255, 0, 0.8)" + +class Credits: + """Simple scrolling credits""" + def __init__(self, credits_text: str, fill_color: str): + self.credits_lines = credits_text.split("\n") if credits_text else ["No credits available"] + self.scroll_speed = 0.4 # pixels per frame + self.y_offset = window.canvas.height * 0.7 # Start near bottom of screen + self.line_height = 30 + self.fill_color = fill_color + self.finished = False + + def update(self, timestamp): + """Update the scroll position.""" + self.y_offset -= self.scroll_speed + + # Check if credits have finished scrolling + if not self.finished: + last_line_y = self.y_offset + (len(self.credits_lines) * self.line_height) + # log.debug("Credits Last Line Y Offset: %s", last_line_y) + if last_line_y < 0: + self.finished = True + + def render(self, ctx, timestamp): + """Render the scrolling credits.""" + if self.finished: + return + + ctx.save() + ctx.font = f"18px Courier New" + ctx.fillStyle = self.fill_color + ctx.textAlign = "center" + + # Draw each line of credits + for i, line in enumerate(self.credits_lines): + y_pos = self.y_offset + (i * self.line_height) + # Only render if the line is visible on screen + if -self.line_height <= y_pos <= window.canvas.height + self.line_height: + ctx.fillText(line, window.canvas.width / 2, y_pos) + + ctx.restore() + diff --git a/cool-cacti/static/scripts/player.py b/cool-cacti/static/scripts/player.py new file mode 100644 index 00000000..702f80f8 --- /dev/null +++ b/cool-cacti/static/scripts/player.py @@ -0,0 +1,575 @@ +import math +import time +from collections import deque +from dataclasses import dataclass +import random + +from asteroid import Asteroid +from common import Position +from consolelogger import getLogger +from scene_classes import SceneObject +from window import SpriteSheet, window + +log = getLogger(__name__) + +class Player(SceneObject): + """Controllable player sprite. + + Exposed globally as window.player so other modules can use it. + Movement keys: WASD or Arrow keys. + """ + + FULL_HEALTH = 1000 + + def __init__( + self, + sprite: SpriteSheet, + bar_icon: SpriteSheet, + x: float, + y: float, + speed: float = 100.0, + scale: float = 0.1, + hitbox_scale: float = 0.5, + ): + super().__init__() + + self.health = Player.FULL_HEALTH + self.health_history = deque([Player.FULL_HEALTH] * 200) + self.sprite = sprite + self.set_position(x, y) + self.default_pos = (x, y) + self.speed = speed + self.momentum = [0, 0] + self.scale = scale + self._half_w = 0 + self._half_h = 0 + self.hitbox_scale = hitbox_scale + self.rotation = 0.0 # rotation in radians + self.target_rotation = 0.0 + self.max_tilt = math.pi / 8 # Maximum tilt angle (22.5 degrees) + self.rotation_speed = 8.0 + self.is_moving = False + self.is_disabled = False + self.bar_icon = bar_icon + self.active = False + self.invincible = False + self.key_cooldown = {} + + def _update_sprite_dims(self): + w = self.sprite.width + h = self.sprite.height + if w and h: + self._half_w = (w * self.scale) / 2 + self._half_h = (h * self.scale) / 2 + + def update(self, timestamp: float): + """Update player position based on pressed keys. + + dt: time delta (seconds) + controls: GameControls instance for key state + """ + if not self.sprite: + return + + # update sprite dimensions if needed + if not self._half_w or not self._half_h: + self._update_sprite_dims() + + keys = window.controls.pressed + dx = dy = 0.0 + if not self.is_disabled: + if "w" in keys or "ArrowUp" in keys: + dy -= 1.75 + if "s" in keys or "ArrowDown" in keys: + dy += 1.75 + if "a" in keys or "ArrowLeft" in keys: + dx -= 1.75 + if "d" in keys or "ArrowRight" in keys: + dx += 1.75 + + # TODO: remove this, for testing momentum + if "m" in keys: + if timestamp - self.key_cooldown.setdefault("m", 0) < 1000: return + angle = random.uniform(0, 6.28) + self.momentum[0] = math.cos(angle) * 5 + self.momentum[1] = math.sin(angle) * 5 + self.key_cooldown["m"] = timestamp + # DEBUG: switch hitbox visibility + if "c" in keys: + if timestamp - self.key_cooldown.setdefault("c", 0) < 100: return + window.DEBUG_DRAW_HITBOXES = not window.DEBUG_DRAW_HITBOXES + self.key_cooldown["c"] = timestamp + # DEBUG: instant death for testing + if "k" in keys: + self.health = 0 + + # miliseconds to seconds since that's what was being used + dt = (timestamp - self.last_timestamp) / 1000 + + # Update target rotation based on horizontal movement + if dx < 0: # Moving left + self.target_rotation = -self.max_tilt # Tilt left + elif dx > 0: # Moving right + self.target_rotation = self.max_tilt # Tilt right + else: + self.target_rotation = 0.0 + + # Smoothly interpolate current rotation toward target + rotation_diff = self.target_rotation - self.rotation + self.rotation += rotation_diff * self.rotation_speed * dt + + if dx or dy: + # normalize diagonal movement + mag = (dx * dx + dy * dy) ** 0.5 + dx /= mag + dy /= mag + self.x += dx * self.speed * dt + self.y += dy * self.speed * dt + + self.is_moving = True + else: + self.is_moving = False + + # update player position based on momentum (after they were hit and bumped by an asteroid) + if self.momentum[0] or self.momentum[1]: + self.x += self.momentum[0] * self.speed * dt + self.y += self.momentum[1] * self.speed * dt + self.momentum[0] *= 0.97 + self.momentum[1] *= 0.97 + if abs(self.momentum[0]) < 0.5: + self.momentum[0] = 0 + if abs(self.momentum[1]) < 0.5: + self.momentum[1] = 0 + + # clamp inside canvas + canvas = getattr(window, "gameCanvas", None) + if canvas and self._half_w and self._half_h: + max_x = canvas.width - self._half_w + max_y = canvas.height - self._half_h + self.x = min(max(self._half_w, self.x), max_x) + self.y = min(max(self._half_h, self.y), max_y) + + def render(self, ctx, timestamp): + if not self.sprite: + log.debug("Player render: no sprite") + return + + self.update(timestamp) + + if not self._half_w or not self._half_h: + self._update_sprite_dims() + + scaled_w = self._half_w * 2 + scaled_h = self._half_h * 2 + + # Save the canvas state before applying rotation + ctx.save() + + # Move to player center and apply rotation + ctx.translate(self.x, self.y) + ctx.rotate(self.rotation) + + # Draw sprite centered at origin + ctx.drawImage(self.sprite.image, -self._half_w, -self._half_h, scaled_w, scaled_h) + + # Debug draw hitbox + if window.DEBUG_DRAW_HITBOXES: + ctx.strokeStyle = "white" + ctx.lineWidth = 2 + ctx.strokeRect(-self._half_w, -self._half_h, scaled_w, scaled_h) + + # Restore canvas state (removes rotation and translation) + ctx.restore() + + # Collision detection (done after restore so it's in world coordinates) + if self.active: + for asteroid in window.asteroids.asteroids: + self.check_collision(asteroid) + self.render_health_bar(ctx) + + super().render(ctx, timestamp) + + def render_health_bar(self, ctx): + outer_width = window.canvas.width // 4 + outer_height = 12 + inner_width = outer_width - 4 + inner_height = outer_height - 4 + padding = 30 + + ctx.drawImage( + self.bar_icon.image, + window.canvas.width - outer_width - padding - 30, + window.canvas.height - outer_height - padding - 2, + ) + + ctx.lineWidth = 1 + ctx.strokeStyle = "#FFFFFF" + ctx.strokeRect( + window.canvas.width - outer_width - padding, + window.canvas.height - outer_height - padding, + outer_width, + outer_height, + ) + + ctx.fillStyle = "#FF0000" + ctx.fillRect( + window.canvas.width - outer_width - padding + 2, + window.canvas.height - outer_height - padding + 2, + inner_width * self.health_history.popleft() / Player.FULL_HEALTH, + inner_height, + ) + self.health_history.append(self.health) + + ctx.fillStyle = "#00FF00" + ctx.fillRect( + window.canvas.width - outer_width - padding + 2, + window.canvas.height - outer_height - padding + 2, + inner_width * self.health / Player.FULL_HEALTH, + inner_height, + ) + + def check_collision(self, asteroid: Asteroid): + # skip if asteroid is too far in the background + if asteroid.size < asteroid.target_size * 0.70: + return + # use invicible flag (toggled when planet is done) + if self.invincible: + return + + ast_x, ast_y, ast_radius = asteroid.get_hit_circle() + player_x_min, player_x_max = self.x - self._half_w, self.x + self._half_w + player_y_min, player_y_max = self.y - self._half_h, self.y + self._half_h + + hitbox_closest_x = max(player_x_min, min(ast_x, player_x_max)) + hitbox_closest_y = max(player_y_min, min(ast_y, player_y_max)) + + # if the closest point on the rectangle is inside the asteroid's circle, we have collision: + if (hitbox_closest_x - ast_x) ** 2 + (hitbox_closest_y - ast_y) ** 2 < ast_radius**2: + distance_between_centers = math.dist((ast_x, ast_y), (self.x, self.y)) + # log.debug("Asteroid collision with distance %s", distance_between_centers) + asteroid.health -= max(80, 240 - distance_between_centers) + # Make Newton proud + self.momentum[0] = (self.x - ast_x) / distance_between_centers * 5.0 + self.momentum[1] = (self.y - ast_y) / distance_between_centers * 5.0 + asteroid.velocity_x += (ast_x - self.x) / 2.0 + asteroid.velocity_y += (ast_y - self.y) / 2.0 + self.health = max(0, self.health - 100 / distance_between_centers * 5 * asteroid.damage_mul) + + # # Reduce scanner progress when hit by asteroid + # if hasattr(window, 'scanner') and window.scanner: + # # Reduce progress by 2-6% of max progress based on damage taken + # damage_taken = 100 / (distance_between_centers * 5 * asteroid.damage_mul + # progress_loss = window.scanner._bar_max * (0.02 + (damage_taken / 1000) * 0.04) + # window.scanner.scanning_progress = max(0, window.scanner.scanning_progress - progress_loss) + # # log.debug("Scanner progress reduced by %f due to asteroid collision", progress_loss) + + window.audio_handler.play_bang() + window.debris.generate_debris(self.get_position(), Position(ast_x, ast_y)) + + def nudge_towards(self, pos: Position, gravity_strength: float = 0.75) -> None: + distance = self.get_position().distance(pos) + if distance == 0: return + + x_dir = (pos.x - self.x) / distance + y_dir = (pos.y - self.y) / distance + + self.x += x_dir * gravity_strength + self.y += y_dir * gravity_strength + + # x_dir = math.cos((pos.x - self.x )/(pos.y - self.y)) + # y_dir = math.sin((pos.x - self.x )/(pos.y - self.y)) + + # if abs(self.momentum[0]) < 1: + # self.momentum[0] += x_dir * momentum_amount + # if abs(self.momentum[1]) < 1: + # self.momentum[1] += y_dir * momentum_amount + + def get_hit_circle(self) -> tuple[float, float, float]: + """Get the hit circle for the player""" + if not self._half_w or not self._half_h: + self._update_sprite_dims() + r = min(self._half_w, self._half_h) * self.hitbox_scale + return (self.x, self.y, r) + + def get_aabb(self) -> tuple[float, float, float, float]: + """Get the axis-aligned bounding box (AABB) for the player""" + if not self._half_w or not self._half_h: + self._update_sprite_dims() + hw = self._half_w * self.hitbox_scale + hh = self._half_h * self.hitbox_scale + return (self.x - hw, self.y - hh, self.x + hw, self.y + hh) + + def reset_position(self): + self.x, self.y = self.default_pos + self.rotation = 0.0 + self.target_rotation = 0.0 + self.momentum = [0, 0] + + +@dataclass +class ScanStatus: + active: bool = False # Whether the scan is active + too_close: bool = False # Whether the scan is valid + player_interrupted: bool = False # Whether the scan was interrupted + locked: bool = False # Whether the scan is locked + + @property + def valid(self): + return not self.too_close and not self.player_interrupted and not self.locked + + +class Scanner: + def __init__( + self, + sprite: SpriteSheet, + player: Player, + min_x: float, + scan_mult: float = 1, + scale: float = 0.1, + disable_ship_ms: float = 1000, + beamwidth=100, + scanning_dur_s=15, + ): + self.sprite = sprite + self.scale = scale + self.player = player + self.min_x = min_x + self.disable_ship_ms = disable_ship_ms + self.disable_timer = 0 + + # Core scanning parameters + self.scanning_dur_ms = scanning_dur_s * 1000 + self.scan_mult = scan_mult + self.beamwidth = beamwidth + + # State variables + self.status = ScanStatus() + self.scanning_progress = 0 + self.finished = False + self._last_scan_tick = None + + # Calculate max based on current parameters + self._update_bar_max() + + def _update_bar_max(self): + """Update the maximum progress value based on current parameters""" + self._bar_max = self.scanning_dur_ms * self.scan_mult + + def set_scan_parameters(self, scan_mult: float | None = None, scanning_dur_s: float | None = None): + """Update scanning parameters and recalculate max value""" + if scan_mult is not None: + self.scan_mult = scan_mult + if scanning_dur_s is not None: + self.scanning_dur_ms = scanning_dur_s * 1000 + self._update_bar_max() + + def update(self, ctx, current_time): + if self.finished: + return + + keys = window.controls.pressed + + self.status.active = " " in keys + + self.status.too_close = self.player.x <= self.min_x + self.status.player_interrupted = self.player.momentum != [0, 0] + + # Lock if interrupted and stay locked until released + if self.status.player_interrupted: + self.status.locked = True + elif not self.status.active: + self.status.locked = False + + if self.status.active and self.status.valid: + self.player.is_disabled = True + + if self._last_scan_tick is None: + self._last_scan_tick = current_time + + elapsed_since_last = current_time - self._last_scan_tick + self.scanning_progress = min(self.scanning_progress + elapsed_since_last, self._bar_max) + self._last_scan_tick = current_time + else: + self._last_scan_tick = None + + # Re-enable player if disable_time has elapsed or player is not scanning + if current_time - self.disable_timer >= self.disable_ship_ms or not self.status.active: + if " " not in keys: + self.player.is_disabled = False + self.disable_timer = current_time + + def render_beam(self, ctx): # seprate function so it can go under the planet + + if not self.status.active or not self.status.valid: + window.audio_handler.play_scan(pause_it=True) + return + + window.audio_handler.play_scan() + + player_x, player_y = self.player.get_position() + origin_x = player_x - 150 + origin_y = player_y - 15 + + # Create animated pulsing effect based on time + pulse = (math.sin(time.time() * 8) + 1) / 2 # 0 to 1 + beam_alpha = 0.3 + pulse * 0.3 # Vary alpha from 0.3 to 0.6 + + # Create gradient for the beam + gradient = ctx.createLinearGradient(origin_x, origin_y, 0, player_y) + gradient.addColorStop(0, f"rgba(255, 100, 100, {beam_alpha})") + gradient.addColorStop(0.5, f"rgba(255, 50, 50, {beam_alpha * 0.8})") + gradient.addColorStop(1, f"rgba(255, 0, 0, {beam_alpha * 0.5})") + + # Main beam cone + ctx.fillStyle = gradient + ctx.beginPath() + ctx.moveTo(origin_x, origin_y) + ctx.lineTo(0, player_y - self.beamwidth) + ctx.lineTo(0, player_y + self.beamwidth) + ctx.closePath() + ctx.fill() + + # Add animated scanning lines + scan_cycle = (time.time() * 2) % 1 # 0 to 1, cycling every 0.5 seconds + num_lines = 5 + + for i in range(num_lines): + line_progress = (scan_cycle + i * 0.2) % 1 + line_x = origin_x - line_progress * origin_x + line_alpha = (1 - line_progress) * 0.8 + beam_height = self.beamwidth + + if line_alpha > 0.1: # Only draw visible lines + ctx.strokeStyle = f"rgba(255, 255, 255, {line_alpha})" + ctx.lineWidth = 2 + ctx.beginPath() + ctx.moveTo(line_x, player_y - beam_height) + ctx.lineTo(line_x, player_y + beam_height) + ctx.stroke() + + # Add edge glow effect + ctx.strokeStyle = f"rgba(255, 150, 150, {beam_alpha * 0.6})" + ctx.lineWidth = 3 + ctx.beginPath() + ctx.moveTo(origin_x, origin_y) + ctx.lineTo(0, player_y - self.beamwidth) + ctx.moveTo(origin_x, origin_y) + ctx.lineTo(0, player_y + self.beamwidth) + ctx.stroke() + + def render(self, ctx, current_time): + "Renders the scanner sprite and the progress bar" + if "f" in window.controls.pressed and window.player.active: + self.finished = True + + player_x, player_y = self.player.get_position() + # progress bar + outer_width = window.canvas.width // 4 + outer_height = 12 + inner_width = outer_width - 4 + inner_height = outer_height - 4 + padding = 30 + + ctx.drawImage( + self.sprite.image, + window.canvas.width - outer_width - padding - 30, + window.canvas.height + outer_height - padding - 2, + 16, + 16, + ) + + ctx.lineWidth = 1 + ctx.strokeStyle = "#FFFFFF" + ctx.strokeRect( + window.canvas.width - outer_width - padding, + window.canvas.height + outer_height - padding, + outer_width, + outer_height, + ) + + ctx.fillStyle = "#FF0000" + ctx.fillRect( + window.canvas.width - outer_width - padding + 2, + window.canvas.height + outer_height - padding + 2, + inner_width * self.scanning_progress / self._bar_max, + inner_height, + ) + + if self.finished: + return + + if self.status.active: + if self.status.valid: + scaled_w = self.sprite.width * self.scale + scaled_h = self.sprite.height * self.scale + ctx.drawImage(self.sprite.image, player_x - 175, player_y - 25, scaled_w, scaled_h) + elif self.status.too_close: + ctx.fillStyle = "white" + ctx.font = "15px Courier New" + ctx.fillText("Too close to planet!", player_x - 90, player_y - 50) + + if self.scanning_progress >= self._bar_max: + log.debug(f"Done scanning") + self.status.active = False + self.finished = True + + def reset(self): + self.finished = False + self.scanning_progress = 0 + self._update_bar_max() + +class PlayerExplosion(): + def __init__(self): + self.explosion_sprite = window.get_sprite("Explosion Animation") + self.active = False + self.current_frame = 0 + self.frame_count = 11 # Number of frames + self.frame_duration = 100 # milliseconds per frame + self.last_frame_time = 0 + self.position = (0, 0) + self.scale = 4.0 + self.finished = False + + def start_explosion(self, x: float, y: float): + """Start the explosion animation at the given position""" + self.active = True + self.current_frame = 0 + self.position = (x, y) + self.last_frame_time = 0 + self.finished = False + + def update(self, timestamp: float): + """Update the explosion animation""" + if not self.active or self.finished: + return + + if timestamp - self.last_frame_time >= self.frame_duration: + self.current_frame += 1 + self.last_frame_time = timestamp + + if self.current_frame >= self.frame_count: + self.finished = True + self.active = False + + def render(self, ctx, timestamp: float): + """Render the current explosion frame""" + if not self.active or self.finished: + return + + self.update(timestamp) + + frame_width = self.explosion_sprite.width // self.frame_count + frame_height = self.explosion_sprite.height + + source_x = self.current_frame * frame_width + source_y = 0 + + scaled_width = frame_width * self.scale + scaled_height = frame_height * self.scale + + ctx.drawImage( + self.explosion_sprite.image, + source_x, source_y, frame_width, frame_height, # source rectangle + self.position[0] - scaled_width/2, self.position[1] - scaled_height/2, # destination position + scaled_width, scaled_height # destination size + ) diff --git a/cool-cacti/static/scripts/scene_classes.py b/cool-cacti/static/scripts/scene_classes.py new file mode 100644 index 00000000..acd990b8 --- /dev/null +++ b/cool-cacti/static/scripts/scene_classes.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from typing import overload + +from common import CanvasRenderingContext2D, Position + +# ==================================================== +# Scene Object abstract class +# ==================================================== + + +class SceneObject: + def __init__(self): + """every scene object keeps track of the last milisecond timestamp when it was rendered""" + self.last_timestamp = 0 + + def render(self, ctx: CanvasRenderingContext2D, timestamp: float): + # update the last rendered timestamp + self.last_timestamp = timestamp + + """ + A few subclasses use these position methods so moved them here for shared functionality. + SceneObject subclasses where these don't make sense can just ignore them. (e.g. SolarSystem) + """ + + @overload + def set_position(self, x: float, y: float): ... + + @overload + def set_position(self, x: Position): ... + + def set_position(self, x_or_pos, y=None): + if y is not None: + x = x_or_pos + self.x = x + self.y = y + else: + pos = x_or_pos + self.x = pos.x + self.y = pos.y + + def get_position(self) -> Position: + return Position(self.x, self.y) + + +# -------------------- +# Scene Class +# -------------------- + + +class Scene(SceneObject): + def __init__(self, name: str, scene_manager: SceneManager): + super().__init__() + self.name = name + self.active = False + self.scene_manager = scene_manager + + +# -------------------- +# Scene Manager Class +# -------------------- + + +class SceneManager: + def __init__(self): + self._scenes: list[Scene] = [] + + def add_scene(self, scene: Scene): + self._scenes.append(scene) + + def activate_scene(self, scene_name): + """ + Deactivate all scenes, and only activate the one with the provided name + """ + for scene in self._scenes: + scene.active = False + next(scene for scene in self._scenes if scene.name == scene_name).active = True + + def get_active_scene(self): + return next(scene for scene in self._scenes if scene.active) diff --git a/cool-cacti/static/scripts/scene_descriptions.py b/cool-cacti/static/scripts/scene_descriptions.py new file mode 100644 index 00000000..9db636a6 --- /dev/null +++ b/cool-cacti/static/scripts/scene_descriptions.py @@ -0,0 +1,553 @@ +from functools import partial + +from player import Player, PlayerExplosion +from common import PlanetState, Position, Rect +from consolelogger import getLogger +from scene_classes import Scene, SceneManager +from solar_system import SolarSystem +from spacemass import SpaceMass +from stars import StarSystem, StarSystem3d +from window import window +from overlay import TextOverlay, ResultsScreen, DeathScreen, Dialogue, Credits + +from js import document #type:ignore +canvas = document.getElementById("gameCanvas") +container = document.getElementById("canvasContainer") +log = getLogger(__name__, False) + +# -------------------- +# methods useful across various scenes +# -------------------- + +ORBITING_PLANETS_SCENE = "orbiting-planets-scene" +FINAL_SCENE = "final-scene" +START_SCENE = "start-scene" + +def get_controls(): + return window.controls + +def get_player(): + return window.player + +def get_asteroid_system(): + return window.asteroids + +def get_debris_system(): + return window.debris + +def get_scanner(): + return window.scanner + +def draw_black_background(ctx): + ctx.fillStyle = "black" + ctx.fillRect(0, 0, window.canvas.width, window.canvas.height) + +# -------------------- +# our main scene with the planets orbiting the sun +# -------------------- + +class OrbitingPlanetsScene(Scene): + """ + Scene that handles the functionality of the part of the game where planets are orbiting around the sun + and the player can select a level by clicking planets + """ + + def __init__(self, name: str, scene_manager: SceneManager, solar_system: SolarSystem): + super().__init__(name, scene_manager) + + self.solar_sys = solar_system + + self.stars = StarSystem( + num_stars=400, # as number of stars increase, the radius should decrease + radius_min=1, + radius_max=2, + pulse_freq_min=3, + pulse_freq_max=6, + ) + self.planet_info_overlay = TextOverlay("planet-info-overlay", scene_manager, "") + # attach a behavior to click event outside the overlay's button - hide the overlay + self.planet_info_overlay.other_click_callable = self.planet_info_overlay.deactivate + self.planet_info_overlay.set_button("Travel") + self.planet_info_overlay.muted = False + self.planet_info_overlay.center = True + self.scene_manager = scene_manager + # Debug button label + self._debug_btn_label = "" # disable the extra button by default + + self.show_cheats_menu() + + # just a temporary function for demo-ing project + def show_cheats_menu(self): + cheats_info = """ +Hello, thanks for checking out our project! +In order to more easily demo the functionality +of different parts of the game, we have included +the following cheats: + +In planet overview screen: +[C] - Instantly jump to credits / victory screen + +During ship flight: +[C] - Toggle collision boxes (for fun) +[K] - Kill the player (can start a new game) +[F] - Finish the current planet scan +""" + self.planet_info_overlay.set_button(None) + self.planet_info_overlay.set_text(cheats_info) + self.planet_info_overlay.margins = Position(300, 150) + self.planet_info_overlay.active = True + self.planet_info_overlay.center = False + + def render(self, ctx, timestamp): + + # some temporary functionality for testing + if "c" in window.controls.pressed: + self.scene_manager.activate_scene(FINAL_SCENE) + window.audio_handler.play_music_main() + + draw_black_background(ctx) + self.highlight_hovered_planet() + + self.stars.render(ctx, timestamp) + self.solar_sys.update_orbits(0.20) + self.solar_sys.render(ctx, timestamp) + + # If all planets are complete, switch to the final scene + if all(p.complete for p in self.solar_sys.planets): + self.scene_manager.activate_scene(FINAL_SCENE) + self._debug_btn_label = "View Credits Again" + return + + # from this scene, be ready to switch to a big planet scene if planet is clicked + if self.planet_info_overlay.active: + self.planet_info_overlay.render(ctx, timestamp) + else: + self.check_planet_click() + + # Debug: button to set all planets to complete + self._render_debug_complete_all_button(ctx) + + def _render_debug_complete_all_button(self, ctx): + label = self._debug_btn_label + if not label: return + ctx.save() + ctx.font = "14px Courier New" + text_width = ctx.measureText(label).width + pad_x, pad_y = 10, 8 + x, y = 16, 16 + w, h = text_width + pad_x * 2, 30 + bounds = Rect(x, y, w, h) + + # Background + ctx.fillStyle = "rgba(0, 0, 0, 0.75)" + ctx.fillRect(*bounds) + + # Hover state + is_hover = bounds.contains(get_controls().mouse.move) + ctx.strokeStyle = "#ffff00" if is_hover else "#00ff00" + ctx.lineWidth = 2 + ctx.strokeRect(*bounds) + ctx.fillStyle = ctx.strokeStyle + ctx.fillText(label, x + pad_x, y + h - 10) + + # Click handling + if window.controls.click and bounds.contains(window.controls.mouse.click): + for p in self.solar_sys.planets: + p.complete = True + log.debug("Debug: set all planet completions to True") + ctx.restore() + + def check_planet_click(self): + """Check whether a UI action needs to occur due to a click event.""" + + planet = self.solar_sys.get_object_at_position(window.controls.mouse.click) + if window.controls.click and planet: + planet_data = window.get_planet(planet.name) + log.debug("Clicked on: %s", planet.name) + self.planet_info_overlay.hint = "Click anywhere to close" + if planet.complete: + self.planet_info_overlay.set_button(None) + self.planet_info_overlay.set_text(planet_data.info) + self.planet_info_overlay.margins = Position(200, 50) + self.planet_info_overlay.active = True + self.planet_info_overlay.center = True + else: + self.planet_info_overlay.set_button("Travel") + self.planet_info_overlay.button_click_callable = partial(self.switch_planet_scene, planet.name) + self.planet_info_overlay.set_text("\n".join(planet_data.level)) + self.planet_info_overlay.margins = Position(300, 120) + self.planet_info_overlay.active = True + self.planet_info_overlay.center = False + + def highlight_hovered_planet(self): + # Reset all planets' highlight state first + for planet in self.solar_sys.planets: + planet.highlighted = False + + planet = self.solar_sys.get_object_at_position(window.controls.mouse.move) + if planet is not None and not self.planet_info_overlay.active: + planet.highlighted = True + + def switch_planet_scene(self, planet_name): + """Prepare what is needed to transition to a gameplay scene.""" + + planet_scene_name = f"{planet_name}-planet-scene" + log.debug("Activating planet scene: %s", planet_scene_name) + + planet = window.get_planet(planet_name) + if planet is None: + log.error("Planet not found: %s", planet_name) + return + + log.debug(planet) + self.planet_info_overlay.deactivate() + self.scene_manager.activate_scene(planet_scene_name) + self.solar_sys.get_planet(planet_name).switch_view() + get_player().reset_position() + get_player().active = True + get_asteroid_system().reset(planet) + get_debris_system().reset() + get_scanner().set_scan_parameters(planet.scan_multiplier) + get_scanner().reset() + +# -------------------- +# game scene with zoomed in planet on left +# -------------------- + +class PlanetScene(Scene): + """ + Scene that handles the functionality of the part of the game where the player's ship is active and dodging + asteroids. Also handles the scan results display as a child scene. + """ + + def __init__(self, name: str, scene_manager: SceneManager, planet: SpaceMass): + super().__init__(name, scene_manager) + + self.stars = StarSystem( + num_stars=100, # as number of stars increase, the radius should decrease + radius_min=1, + radius_max=3, + pulse_freq_min=3, + pulse_freq_max=6, + ) + self.planet = planet + planet.set_position(0, window.canvas.height // 2) + self.results_overlay = ResultsScreen(f"{planet.name}-results", scene_manager, self.planet) + self.results_overlay.other_click_callable = self.handle_scene_completion + self.results_overlay.muted = False + self.results_overlay.center = True + self.results_overlay.hint = "Click anywhere to continue" + + # Add death screen + self.death_screen = DeathScreen(f"{planet.name}-death", scene_manager) + self.death_screen.button_click_callable = self.handle_player_death + self.death_screen.set_button("Play Again") + + # Add explosion animation + self.player_explosion = PlayerExplosion() + self.explosion_started = False + + def render(self, ctx, timestamp): + draw_black_background(ctx) + self.stars.star_shift(timestamp, 5) + self.stars.render(ctx, timestamp) + get_scanner().update(ctx, timestamp) + get_scanner().render_beam(ctx) + self.planet.render(ctx, timestamp) + + # Update + render handles spawn and drawing + get_asteroid_system().update_and_render(ctx, timestamp) + self.check_special_level_interactions(timestamp) + + # Check for player death first + if get_player().health <= 0: + if not self.explosion_started: + window.audio_handler.play_explosion() + # Start explosion animation at player position + player_x, player_y = get_player().get_position() + self.player_explosion.start_explosion(player_x, player_y) + self.explosion_started = True + get_player().invincible = True + window.audio_handler.play_music_main(pause_it=True) + + # Render explosion instead of player + if self.player_explosion.active: + self.player_explosion.render(ctx, timestamp) + # Only show death screen after explosion is finished + elif self.player_explosion.finished: + self.death_screen.active = True + else: + # Normal player rendering when alive + get_player().render(ctx, timestamp) + + get_debris_system().update() + get_debris_system().render(ctx, timestamp) + + get_scanner().render(ctx, timestamp) + + # Activate the results sub-scene if scanner progress is complete + if get_scanner().finished: + self.results_overlay.active = True + get_player().invincible = True + elif get_player().health > 0: # Only reset invincibility if player is alive + get_player().invincible = False + + # Handle death screen display and interaction + if self.death_screen.active: + self.death_screen.render(ctx, timestamp) + # Handle results screen display and interaction + self.results_overlay.render(ctx, timestamp) + + def check_special_level_interactions(self, timestamp: int): + """ + Handle special level interactions + + This is probably not best place to handle the special level stuff like Jupiter gravity affecting + player and Mercury slowly damaging player, but it's crunch time so whatever works :) + """ + # nudge player in the direction of jupiter if on the left 2/3 of the screen + if self.planet.name.lower() == "jupiter": + get_player().nudge_towards(self.planet.get_position(), 0.5) + elif self.planet.name.lower() == "mercury": + get_player().health = max(0, get_player().health - (timestamp - self.last_timestamp) / 1_200_000) + + def handle_scene_completion(self): + """Handle when the scanning is finished and planet is complete.""" + log.debug(f"Finished planet {self.planet.name}! Reactivating orbiting planets scene.") + self.scene_manager.activate_scene(ORBITING_PLANETS_SCENE) + get_player().active = False + self.results_overlay.active = True + get_player().health = min(get_player().health + Player.FULL_HEALTH / 3, Player.FULL_HEALTH) + self.planet.switch_view() + self.planet.complete = True + + def handle_player_death(self): + """Handle when the player dies and clicks on the death screen.""" + window.audio_handler.play_music_death(pause_it=True) + log.debug(f"Player died on {self.planet.name}! Returning to orbiting planets scene.") + + # Reset all planet completions when player dies + orbiting_scene = next(scene for scene in self.scene_manager._scenes if scene.name == ORBITING_PLANETS_SCENE) + for planet in orbiting_scene.solar_sys.planets: + planet.complete = False + log.debug("All planet completions reset due to player death") + + window.audio_handler.play_explosion(pause_it=True) + self.scene_manager.activate_scene(ORBITING_PLANETS_SCENE) + get_player().active = False + get_player().health = 1000 # Reset player health to FULL_HEALTH + self.death_screen.deactivate() + self.explosion_started = False # Reset explosion state + self.planet.switch_view() + + # special level interaction: finishing earth gives player full health back + if self.planet.name.lower() == "earth": + get_player().health = Player.FULL_HEALTH + log.debug(window.audio_handler.music_death.paused) + +# -------------------- +# game intro scene with dialogue +# -------------------- + +class StartScene(Scene): + """Scene for handling the alien dialogue for introducing the game.""" + + def __init__(self, name: str, scene_manager: SceneManager, bobbing_timer = 135, bobbing_max = 20): + super().__init__(name, scene_manager) + self.stars = StarSystem( + num_stars=100, # as number of stars increase, the radius should decrease + radius_min=1, + radius_max=1, + pulse_freq_min=3, + pulse_freq_max=6, + ) + + self.dialogue_manager = Dialogue('dialogue', scene_manager, window.lore) + self.dialogue_manager.active = True + self.dialogue_manager.margins = Position(300, 150) + self.dialogue_manager.rect=(0, window.canvas.height-150, window.canvas.width, 150) + self.dialogue_manager.set_button("Skip Intro") + self.dialogue_manager.button_click_callable = self.finalize_scene + self.starsystem = StarSystem3d(100, max_depth=100) + self.player = None + self.bobbing_timer = bobbing_timer + self.bobbing_max = bobbing_max + self.is_bobbing_up = True + self.bobbing_offset = 0 + self.animation_timer = 0 + + def render(self, ctx, timestamp): + if self.player is None: + player = get_player() + player.is_disabled = True + + if timestamp - self.animation_timer >= self.bobbing_timer: + # log.debug(f"bobbing, val={self.bobbing_offset}") + self.animation_timer = timestamp + if self.is_bobbing_up: + self.bobbing_offset += 1 + else: + self.bobbing_offset -= 1 + + player.y = (window.canvas.height // 2 + self.bobbing_offset) + + if abs(self.bobbing_offset) > self.bobbing_max: + self.is_bobbing_up = not self.is_bobbing_up + + draw_black_background(ctx) + #self.stars.render(ctx, timestamp) + self.dialogue_manager.render(ctx, timestamp) + + self.starsystem.render(ctx, speed=0.3, scale=70) + player.render(ctx, timestamp) + if window.controls.click: + self.dialogue_manager.next() + window.audio_handler.play_music_thematic() + + if self.dialogue_manager.done: + self.finalize_scene() + + def finalize_scene(self): + window.audio_handler.play_music_thematic(pause_it=True) + window.audio_handler.play_music_main() + self.scene_manager.activate_scene(ORBITING_PLANETS_SCENE) + +# -------------------- +# final \ credits scene +# -------------------- + +class FinalScene(Scene): + """Scene for the final credits.""" + def __init__(self, name: str, scene_manager: SceneManager): + super().__init__(name, scene_manager) + # Sparse stars for space backdrop + self.stars = StarSystem( + num_stars=200, + radius_min=1, + radius_max=2, + pulse_freq_min=10, + pulse_freq_max=50, + ) + # Rotating Earth spritesheet + self.earth_sprite = window.get_sprite("earth") + self.earth_frame = 0 + self.earth_frame_duration = 200 + self.earth_last_frame_time = 0 + self.fill_color = "#00FF00" + + # Moon sprite for lunar surface + try: + self.moon_sprite = window.get_sprite("moon") + except Exception: + self.moon_sprite = None + + self.credits = Credits(window.credits, self.fill_color) + + def _draw_earth(self, ctx, timestamp): + # Advance frame based on time + if self.earth_sprite and self.earth_sprite.is_loaded: + if self.earth_last_frame_time == 0: + self.earth_last_frame_time = timestamp + if timestamp - self.earth_last_frame_time >= self.earth_frame_duration: + self.earth_frame = (self.earth_frame + 1) % max(1, self.earth_sprite.num_frames) + self.earth_last_frame_time = timestamp + + frame_size = self.earth_sprite.frame_size if self.earth_sprite.num_frames > 1 else self.earth_sprite.height + sx = (self.earth_frame % max(1, self.earth_sprite.num_frames)) * frame_size + sy = 0 + + # Position Earth in upper-right, smaller size like the reference image + target_size = int(min(window.canvas.width, window.canvas.height) * 0.15) + dw = dh = target_size + dx = window.canvas.width * 0.65 # Right side of screen + dy = window.canvas.height * 0.15 # Upper portion + + ctx.drawImage( + self.earth_sprite.image, + sx, sy, frame_size, frame_size, + dx, dy, dw, dh + ) + + def _draw_lunar_surface(self, ctx): + # Draw lunar surface with the top portion visible, like looking across the lunar terrain + if self.moon_sprite and getattr(self.moon_sprite, "is_loaded", False): + # Position moon sprite so its upper portion is visible as foreground terrain + surface_height = window.canvas.height * 0.5 + + # Scale to fill screen width + scale = (window.canvas.width / self.moon_sprite.width) + sprite_scaled_height = self.moon_sprite.height * scale + + # Position so the moon extends below the screen, showing only the top portion + dy = window.canvas.height - surface_height + + ctx.drawImage( + self.moon_sprite.image, + 0, 0, self.moon_sprite.width, self.moon_sprite.height, + window.canvas.width - (window.canvas.width * scale)/1.25, dy, # target left, top + window.canvas.width * scale, sprite_scaled_height # target width, height + ) + + def render(self, ctx, timestamp): + window.audio_handler.play_music_main(pause_it=True) + window.audio_handler.play_music_thematic() + + draw_black_background(ctx) + + # Sparse stars + self.stars.render(ctx, timestamp) + + # Update and render scrolling credits before lunar surface + self.credits.update(timestamp) + self.credits.render(ctx, timestamp) + + # Draw lunar surface after credits so it appears as foreground + self._draw_lunar_surface(ctx) + + # Draw Earth in the distance + self._draw_earth(ctx, timestamp) + + if self.credits.finished: + ctx.font = f"{max(12, int(min(window.canvas.width, window.canvas.height)) * 0.025)}px Courier New" + instruction = "Click anywhere to return to solar system" + ctx.fillText(instruction, window.canvas.width * 0.05, window.canvas.height * 0.25) + ctx.restore() + + # Handle click to go back to orbiting planets scene + if window.controls.click: + # Reset all planet completions so we don't immediately return to final scene + orbiting_scene = next(scene for scene in self.scene_manager._scenes if scene.name == ORBITING_PLANETS_SCENE) + for planet in orbiting_scene.solar_sys.planets: + planet.complete = False + log.debug("Reset all planet completions when returning from final scene") + self.scene_manager.activate_scene(ORBITING_PLANETS_SCENE) + +# -------------------- +# create scene manager +# -------------------- + +def create_scene_manager() -> SceneManager: + """ + Create all the scenes and add them to a scene manager that can be used to switch between them The object + instance returned by this is used by the main game loop in game.py to check which scene is active when a + frame is drawn and that scene's render method is called. Only one scene listed in the scene manager is + active at a time, though scenes may have their own subscenes, such as textboxes that they render as part of + their routine. + """ + manager = SceneManager() + planet_scene_state = PlanetState(0, window.canvas.height, 120.0, x=0, y=window.canvas.height // 2) + solar_system = SolarSystem([window.canvas.width, window.canvas.height], planet_scene_state=planet_scene_state) + orbiting_planets_scene = OrbitingPlanetsScene(ORBITING_PLANETS_SCENE, manager, solar_system) + start_scene = StartScene(START_SCENE, manager) + manager.add_scene(start_scene) + manager.add_scene(orbiting_planets_scene) + # Final victory scene (activated when all planets complete) + final_scene = FinalScene(FINAL_SCENE, manager) + manager.add_scene(final_scene) + + for planet in solar_system.planets: + big_planet_scene = PlanetScene(f"{planet.name}-planet-scene", manager, planet) + manager.add_scene(big_planet_scene) + + manager.activate_scene(START_SCENE) # initial scene + return manager diff --git a/cool-cacti/static/scripts/solar_system.py b/cool-cacti/static/scripts/solar_system.py new file mode 100644 index 00000000..e4ef03d6 --- /dev/null +++ b/cool-cacti/static/scripts/solar_system.py @@ -0,0 +1,146 @@ +import math + +from common import PlanetState, Position +from scene_classes import SceneObject +from spacemass import SpaceMass +from window import window + +GRAVI_CONST = 0.67 + +from consolelogger import getLogger + +log = getLogger(__name__) + + +class SolarSystem(SceneObject): + def __init__(self, screen_size=[512, 512], *, planet_scene_state: PlanetState): + super().__init__() + + # Sun position (center of screen) + self.sun_pos: Position = Position(screen_size[0] // 2, screen_size[1] // 2) + + # Sun + self.sun = SpaceMass(window.get_sprite("sun"), PlanetState(1000.0, 120.0, 0.0), planet_scene_state) + self.sun.set_position(self.sun_pos) + + # Inner planets + self.mercury = SpaceMass(window.get_sprite("mercury"), PlanetState(3.3, 10, 2.5), planet_scene_state) + self.venus = SpaceMass(window.get_sprite("venus"), PlanetState(48.7, 14, 2.0), planet_scene_state) + self.earth = SpaceMass(window.get_sprite("earth"), PlanetState(59.7, 16, 1.8), planet_scene_state) + self.mars = SpaceMass(window.get_sprite("mars"), PlanetState(6.4, 12, 1.5), planet_scene_state) + + # Outer planets + self.jupiter = SpaceMass(window.get_sprite("jupiter"), PlanetState(1898.0, 64.0, 1.0), planet_scene_state) + self.saturn = SpaceMass(window.get_sprite("saturn"), PlanetState(568.0, 46.0, 0.8), planet_scene_state) + self.uranus = SpaceMass(window.get_sprite("uranus"), PlanetState(86.8, 36.0, 0.6), planet_scene_state) + self.neptune = SpaceMass(window.get_sprite("neptune"), PlanetState(102.0, 15.0, 0.4), planet_scene_state) + + self.planets = [ + self.mercury, + self.venus, + self.earth, + self.mars, + self.jupiter, + self.saturn, + self.uranus, + self.neptune, + ] + + # Initial positions (distance from sun in pixels) + self.planet_distances = [110, 140, 160, 200, 270, 350, 420, 470] + self.planet_angles: list[float] = [20, 220, 100, 45, 0, 155, 270, 15] + + # Initialize planet positions + for i, planet in enumerate(self.planets): + angle_rad = math.radians(self.planet_angles[i]) + x = self.sun_pos.x + self.planet_distances[i] * math.cos(angle_rad) + y = self.sun_pos.y + self.planet_distances[i] * math.sin(angle_rad) + planet.set_position(Position(x, y)) + planet.complete = False + + def update(self): + self.update_orbits(0.20) + + def get_planet(self, planet_name: str) -> SpaceMass | None: + for planet in self.planets: + if planet.name == planet_name: + return planet + + def update_orbits(self, dt: float): + """Update planet positions using simple circular orbits""" + for i, planet in enumerate(self.planets): + angular_velocity = planet.state.initial_velocity * 0.01 + + # Update angle + self.planet_angles[i] += angular_velocity * dt * 60 # Scale for 60 FPS + + # Keep angle in range [0, 360) + self.planet_angles[i] = self.planet_angles[i] % 360 + + # Calculate new position using circular motion + angle_rad = math.radians(self.planet_angles[i]) + x = self.sun_pos.x + self.planet_distances[i] * math.cos(angle_rad) + y = self.sun_pos.y + self.planet_distances[i] * math.sin(angle_rad) + + # Update position + self.planets[i].set_position(Position(x, y)) + + def render(self, ctx, timestamp): + """Render the entire solar system""" + # Render sun at center + self.sun.render(ctx, timestamp) + + # Render all planets + highlighted_planet = None + for planet in self.planets: + if planet.highlighted: + highlighted_planet = planet + continue + planet.render(ctx, timestamp) + + # If a planet is highlighted, draw it last, so its text label is in front of other planets + if highlighted_planet: + highlighted_planet.render(ctx, timestamp) + + super().render(ctx, timestamp) + + # I Couldn't get this to work 〒__〒 + def calculateGForce(self, planet_index: int) -> float: + """Calculate gravitational force between the sun and a planet""" + # Get planet position + planet_pos = self.planets[planet_index].get_position() + planet = self.planets[planet_index] + + # Calculate distance between sun and planet + distance = planet_pos.distance(self.sun_pos) + + # Prevent division by zero + if distance == 0: + return 0 + + # F = G * m1 * m2 / r^2 + force = GRAVI_CONST * self.sun.state.mass * planet.state.mass / (distance * distance) + + return force + + def get_object_at_position(self, pos: Position) -> SpaceMass | None: + """Get the space object at the specified position, excluding the sun. + + Arguments: + pos (Position): The position to check. + + Returns: + The space object at the position if found, otherwise None. + """ + closest_planet = None + closest_distance = float("inf") + for planet in self.planets: + rect = planet.get_bounding_box() + if rect.left <= pos.x <= rect.right and rect.top <= pos.y <= rect.bottom: + # Calculate distance from click point to planet center + planet_center = Position(rect.left + rect.width / 2, rect.top + rect.height / 2) + distance = planet_center.distance(pos) + if distance < closest_distance: + closest_distance = distance + closest_planet = planet + return closest_planet diff --git a/cool-cacti/static/scripts/spacemass.py b/cool-cacti/static/scripts/spacemass.py new file mode 100644 index 00000000..1203eaf5 --- /dev/null +++ b/cool-cacti/static/scripts/spacemass.py @@ -0,0 +1,111 @@ +from common import PlanetState, Rect +from consolelogger import getLogger +from scene_classes import SceneObject +from window import SpriteSheet + +log = getLogger(__name__) + + +class SpaceMass(SceneObject): + def __init__(self, spritesheet: SpriteSheet, orbit_state: PlanetState, planet_scene_state: PlanetState) -> None: + super().__init__() + + self.spritesheet = spritesheet + self.name = spritesheet.key + + self.state: PlanetState = orbit_state + self._saved_state: PlanetState = planet_scene_state + + self.x = self.state.x + self.y = self.state.y + + self.current_frame = 0 + self.animation_timer = 0 + self.frame_delay = 135 # (approximately 6 FPS) + + self.highlighted = False + self.complete = False + + # State management + + def get_bounding_box(self) -> Rect: + # Scale sprite based on radius + sprite_size = int(self.state.radius) / 80.0 + frame_size = self.spritesheet.height + + left = self.x - frame_size // 2 * sprite_size + top = self.y - frame_size // 2 * sprite_size + size = frame_size * sprite_size + + return Rect(left, top, size, size) + + def render(self, ctx, timestamp): + # Update animation timing + if timestamp - self.animation_timer >= self.frame_delay: + self.current_frame = (self.current_frame + 1) % self.spritesheet.num_frames + self.animation_timer = timestamp + + bounds = self.get_bounding_box() + frame_position = self.spritesheet.get_frame_position(self.current_frame) + ctx.drawImage( + self.spritesheet.image, + frame_position.x, + frame_position.y, + self.spritesheet.frame_size, + self.spritesheet.frame_size, + bounds.left, + bounds.top, + bounds.width, + bounds.height, + ) + if self.complete: + highlight = "#00ff00" + else: + highlight = "#ffff00" # yellow highlight + + offset = 5 + # Draw highlight effect if planet is highlighted + if self.highlighted: + if self.complete: + # log.debug("planet complete") + highlight = "#00ff00" + else: + # log.debug("planet not complete") + highlight = "#ffff00" # yellow highlight + ctx.save() + ctx.strokeStyle = highlight + ctx.shadowColor = highlight + ctx.lineWidth = 3 + ctx.shadowBlur = 10 + + # Draw a circle around the planet + center_x = bounds.left + bounds.width / 2 + center_y = bounds.top + bounds.height / 2 + radius = bounds.width / 2 + offset # Slightly larger than the planet + + ctx.beginPath() + ctx.arc(center_x, center_y, radius, 0, 2 * 3.14159) + ctx.stroke() + + # draw planet name labels when hovering over + ctx.shadowBlur = 0 + ctx.beginPath() + ctx.moveTo(center_x, center_y - radius) + ctx.lineTo(center_x + 10, center_y - radius - 10) + ctx.font = "14px Courier New" + ctx.fillStyle = highlight + text_width = ctx.measureText(self.name.capitalize()).width + ctx.lineTo(center_x + 15 + text_width, center_y - radius - 10) + ctx.fillText(self.name.capitalize(), center_x + 15, center_y - radius - 15) + ctx.stroke() + + ctx.restore() + + super().render(ctx, timestamp) + + def switch_view(self) -> None: + """Configure planet view""" + self.state.x, self.state.y = self.x, self.y + self.state, self._saved_state = self._saved_state, self.state + self.x, self.y = self.state.x, self.state.y + self.highlighted = False # Clear highlighting when switching views diff --git a/cool-cacti/static/scripts/sprites.py b/cool-cacti/static/scripts/sprites.py new file mode 100644 index 00000000..4e1a2f17 --- /dev/null +++ b/cool-cacti/static/scripts/sprites.py @@ -0,0 +1,53 @@ +from common import Position +from consolelogger import getLogger +from js import window as js_window # type: ignore[attr-defined] + +log = getLogger(__name__) + + +class SpriteSheet: + """Wrapper for individual sprites with enhanced functionality.""" + + def __init__(self, key: str): + self.key = key.lower() + # Get raw image from js_window.sprites directly to avoid circular import + self.image = js_window.sprites[self.key] + + @property + def height(self): + """Height of the sprite image.""" + return self.image.height + + @property + def width(self): + """Width of the sprite image.""" + return self.image.width + + @property + def frame_size(self): + """Size of each frame (assuming square frames).""" + return self.height + + @property + def is_loaded(self): + return self.height > 0 and self.width > 0 + + @property + def num_frames(self): + """Number of frames in the spritesheet.""" + if not self.is_loaded: + log.warning("Frame size is zero for sprite '%s'", self.key) + return 1 + return self.width // self.frame_size + + def get_frame_position(self, frame: int) -> Position: + """Get the position of a specific frame in the spritesheet with overflow handling.""" + if self.num_frames == 0: + return Position(0, 0) + frame_index = frame % self.num_frames + x = frame_index * self.frame_size + return Position(x, 0) + + # Delegate other attributes to the underlying image + def __getattr__(self, name): + return getattr(self.image, name) diff --git a/cool-cacti/static/scripts/stars.py b/cool-cacti/static/scripts/stars.py new file mode 100644 index 00000000..d87f1954 --- /dev/null +++ b/cool-cacti/static/scripts/stars.py @@ -0,0 +1,206 @@ +import math +import random + +from scene_classes import SceneObject +from window import window +from js import document #type: ignore +loadingLabel = document.getElementById("loadingLabel") +container = document.getElementById("canvasContainer") +width, height = container.clientWidth, container.clientHeight + + +class Star: + def __init__(self, radius, x, y, pulse_freq, color, shade=0, fade_in=True) -> None: + self.radius = radius + self.frame_delay = 135 + self.pulse_freq = pulse_freq # renaming of animation timer + self.x = x + self.y = y + self.shade = shade # defines r,g, and b + self.alpha = 1 + self.color = color + self.fade_in = fade_in + self.animation_timer = 0 + self.glisten = False + + def render(self, ctx, timestamp, num_stars) -> None: + # pulse + if timestamp - self.animation_timer >= self.pulse_freq: + self.animation_timer = timestamp + if self.fade_in: + self.shade += 1 + else: + self.shade -= 1 + + if self.shade > 255 or self.shade < 1: + self.fade_in = not self.fade_in + + self.render_color = self.rgba_to_str(*self.color, self.shade / 255.0) + + # draw star + ctx.fillStyle = self.render_color + ctx.beginPath() + ctx.ellipse(self.x, self.y, self.radius, self.radius, 0, 0, 2 * math.pi) + ctx.fill() + + chance_glisten = random.randint(1, num_stars * 4) + if chance_glisten == num_stars: + self.glisten = True + # glisten + if self.shade > 240 and self.glisten: + glisten_line_col = self.render_color + + ctx.strokeStyle = glisten_line_col # or any visible color + ctx.lineWidth = 2 # thick enough to see + ctx.beginPath() + ctx.moveTo(self.x, self.y - self.radius - 5) # start drawing curve a bit lower than star pos + ctx.bezierCurveTo( + self.x - self.radius, + self.y - self.radius, + self.x + self.radius, + self.y + self.radius, + self.x, + self.y + self.radius + 5, + ) + ctx.stroke() + else: + self.glisten = False + + def rgba_to_str(self, r: int, g: int, b: int, a: int) -> str: + return f"rgba({r}, {g}, {b}, {a})" + + +class StarSystem(SceneObject): + + WHITE = (255, 255, 255) + YELLOW = (255, 223, 79) + BLUE = (100, 149, 237) + RED = (255, 99, 71) + PURPLE = (186, 85, 211) + + COLORS = [WHITE, YELLOW, BLUE, RED, PURPLE] + # chance for each color to be used, most will be white but other colors can also occur + WEIGHTS = [100, 15, 15, 15, 3] + + def __init__(self, num_stars, radius_min, radius_max, pulse_freq_min, pulse_freq_max, num_frames=50): + super().__init__() + + self.num_frames = num_frames + self.radius_min = radius_min + self.radius_max = radius_max + self.pulse_freq_min = pulse_freq_min + self.pulse_freq_max = pulse_freq_max + self.frame_delay = 135 + self.num_stars = num_stars + self.animation_timer = 0 + self.stars: list[Star] = [] # will be filled with star object instances + + for _ in range(num_stars): + self.stars.append(self.create_star("random", "random")) + + def random_color(self) -> tuple: + return random.choices(StarSystem.COLORS, weights=StarSystem.WEIGHTS)[0] + + def render(self, ctx, timestamp) -> None: + """Render every star.""" + for star in self.stars: + star.render(ctx, timestamp, self.num_stars) + + if len(self.stars) == 0: + raise ValueError("There are no stars! Did you populate?") + + super().render(ctx, timestamp) + + def create_star(self, x="random", y="random"): + if x == "random": + x = random.randint(0, window.canvas.width) + if y == "random": + y = random.randint(0, window.canvas.height) + + pulse_freq = random.randint(self.pulse_freq_min, self.pulse_freq_max) + radius = random.randint(self.radius_min, self.radius_max) + shade = random.randint(0, 255) + fade_in = random.choice([True, False]) + return Star(radius, x, y, pulse_freq, self.random_color(), shade=shade, fade_in=fade_in) + + def star_shift(self, current_time, shift_time): + if current_time - self.animation_timer >= shift_time: + self.animation_timer = current_time + replacement_stars = [] + for index, star in enumerate(self.stars): + star.x += 1 + if abs(star.x) > window.canvas.width or abs(star.y) > window.canvas.height: + self.stars.pop(index) + replacement_star = self.create_star(0, "random") + replacement_stars.append(replacement_star) + + for star in replacement_stars: + self.stars.append(star) + + def star_scale(self, current_time, shift_time): + if current_time - self.animation_timer >= shift_time: + self.animation_timer = current_time + +class Star3d(Star): + def __init__(self, radius, x, y, z, pulse_freq, shade=0, fade_in=True): + super().__init__(radius, x, y, pulse_freq, shade, fade_in) + self.z = z + + def update(self, speed, max_depth): + """Move the star closer by reducing z.""" + self.z -= speed + if self.z <= 0: # if it passes the camera, recycle + self.z = max_depth + self.x = random.uniform(-1, 1) + self.y = random.uniform(-1, 1) + + def project(self, cx, cy, max_radius, scale): + """Project 3D coords to 2D screen coords.""" + screen_x = cx + (self.x / self.z) * scale + screen_y = cy + (self.y / self.z) * scale + size = max(1, (1 / self.z) * scale * 0.5) # star grows as z decreases + if size > max_radius: + size = max_radius + + return screen_x, screen_y, size + + +class StarSystem3d: + def __init__(self, num_stars, max_depth=5, max_radius = 20): + self.num_stars = num_stars + self.max_depth = max_depth + self.max_radius = max_radius + self.stars: list[Star3d] = [] + for _ in range(num_stars): + self.stars.append(self.create_star()) + + def create_star(self): + x = random.randint(-width//2, width//2) + y = random.randint(-height//2, height//2) + z = random.uniform(20, self.max_depth) + pulse_freq = random.randint(30, 80) # tweak as desired + radius = 1 + shade = random.randint(150, 255) + fade_in = True + return Star3d(radius, x, y, z, pulse_freq, shade=shade, fade_in=fade_in) + + def render(self, ctx, speed=0.4, scale=300): + cx = window.canvas.width / 2 + cy = window.canvas.height / 2 + + for index, star in enumerate(self.stars): + star.update(speed, self.max_depth) + sx, sy, size = star.project(cx, cy, self.max_radius, scale) + + # If star leaves screen, recycle it + if sx < 0 or sx > window.canvas.width or sy < 0 or sy > window.canvas.height: + self.stars.pop(index) + self.stars.append(self.create_star()) + + # Draw star (brightens as it approaches) + shade = int(255 * (1 - star.z / self.max_depth)) + ctx.fillStyle = f"rgba({shade}, {shade}, {shade}, 1)" + ctx.beginPath() + ctx.ellipse(sx, sy, size, size, 0, 0, 2 * math.pi) + ctx.fill() + diff --git a/cool-cacti/static/scripts/window.py b/cool-cacti/static/scripts/window.py new file mode 100644 index 00000000..0ec61255 --- /dev/null +++ b/cool-cacti/static/scripts/window.py @@ -0,0 +1,145 @@ +"""Typed wrapper over window and stored objects + +Use instead of importing directly from js + +Usage +------- +from window import window +""" + +from typing import TYPE_CHECKING, Any + +from js import window # type: ignore[attr-defined] + +if TYPE_CHECKING: + from asteroid import AsteroidAttack + from audio import AudioHandler + from common import HTMLImageElement + from controls import GameControls + from debris import DebrisSystem + from player import Player, Scanner + +from common import SpriteSheet, AsteroidData, PlanetData + + +class SpritesInterface: + """Interface for accessing window.sprites with SpriteSheet wrapping.""" + + def __init__(self, js_window: Any) -> None: + self._window = js_window + + def __getitem__(self, key: str) -> "SpriteSheet": + """Access sprites as SpriteSheet objects.""" + return SpriteSheet(key, self._window.sprites[key]) + + +class WindowInterface: + """Typed interface for accessing window object properties with dynamic fallback. + + Sprites, AudioHandler, and Planets are internally managed and changes to them are not + reflected in the underlying JS objects. Other properties are accessed directly from the JS + window object. + """ + + def __init__(self, js_window: Any) -> None: + self._window = js_window + self._sprites = SpritesInterface(js_window) # Wrap sprites in SpritesInterface + self.DEBUG_DRAW_HITBOXES: bool = getattr(js_window, "DEBUG_DRAW_HITBOXES", False) + self.audio_handler = js_window.audio_handler + self._planet_dataclasses: dict[str, PlanetData] = {} + self._serialize_planets() + + def _serialize_planets(self) -> None: + """Convert raw planet data from JS to PlanetData dataclass instances.""" + raw_planets = getattr(self._window, 'planets', []) + self._planet_dataclasses = {} + + for planet_dict in raw_planets: + planet = PlanetData.from_dict(planet_dict) + self._planet_dataclasses[planet.name] = planet + + @property + def audio_handler(self) -> "AudioHandler": + return self._window.audio_handler + + @audio_handler.setter + def audio_handler(self, value: "AudioHandler") -> None: + self._window.audio_handler = value + + @property + def controls(self) -> "GameControls": + return self._window.controls + + @controls.setter + def controls(self, value: "GameControls") -> None: + self._window.controls = value + + @property + def player(self) -> "Player": + return self._window.player + + @player.setter + def player(self, value: "Player") -> None: + self._window.player = value + + @property + def asteroids(self) -> "AsteroidAttack": + return self._window.asteroids + + @asteroids.setter + def asteroids(self, value: "AsteroidAttack") -> None: + self._window.asteroids = value + + @property + def debris(self) -> "DebrisSystem": + return self._window.debris + + @debris.setter + def debris(self, value: "DebrisSystem") -> None: + self._window.debris = value + + @property + def scanner(self) -> "Scanner": + return self._window.scanner + + @scanner.setter + def scanner(self, value: "Scanner") -> None: + self._window.scanner = value + + @property + def planets(self) -> dict[str, PlanetData]: + return self._planet_dataclasses + + @planets.setter + def planets(self, value: dict[str, PlanetData]) -> None: + self._planet_dataclasses = value + + def get_planet(self, name: str) -> PlanetData | None: + return self._planet_dataclasses.get(name.title()) + + @property + def sprites(self) -> SpritesInterface: + """Access sprites as SpriteSheet objects.""" + return self._sprites + + def get_sprite(self, key: str) -> SpriteSheet: + """Get a sprite by key - more intuitive than sprites[key].""" + return self._sprites[key] + + def __getattr__(self, name: str) -> Any: + """Dynamic fallback for accessing any window property.""" + return getattr(self._window, name) + + def __setattr__(self, name: str, value: Any) -> None: + """Dynamic fallback for setting any window property.""" + if name.startswith("_"): + super().__setattr__(name, value) + else: + setattr(self._window, name, value) + + +# Create typed interface instance +window_interface = WindowInterface(window) + +# Expose for backward compatibility +window = window_interface diff --git a/cool-cacti/static/sprites/Explosion Animation.png b/cool-cacti/static/sprites/Explosion Animation.png new file mode 100644 index 00000000..988560af Binary files /dev/null and b/cool-cacti/static/sprites/Explosion Animation.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1015757868.png b/cool-cacti/static/sprites/asteroid sprites/1015757868.png new file mode 100644 index 00000000..04d2fd7e Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1015757868.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1024695167.png b/cool-cacti/static/sprites/asteroid sprites/1024695167.png new file mode 100644 index 00000000..9d673523 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1024695167.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1057991680.png b/cool-cacti/static/sprites/asteroid sprites/1057991680.png new file mode 100644 index 00000000..a7aa6d6d Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1057991680.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1069494109.png b/cool-cacti/static/sprites/asteroid sprites/1069494109.png new file mode 100644 index 00000000..65c948f2 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1069494109.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/109305397.png b/cool-cacti/static/sprites/asteroid sprites/109305397.png new file mode 100644 index 00000000..38a214c7 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/109305397.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1145028418.png b/cool-cacti/static/sprites/asteroid sprites/1145028418.png new file mode 100644 index 00000000..b51dbe48 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1145028418.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1145439098.png b/cool-cacti/static/sprites/asteroid sprites/1145439098.png new file mode 100644 index 00000000..6d7e7ffd Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1145439098.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1150396768.png b/cool-cacti/static/sprites/asteroid sprites/1150396768.png new file mode 100644 index 00000000..f2b76c40 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1150396768.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1153566565.png b/cool-cacti/static/sprites/asteroid sprites/1153566565.png new file mode 100644 index 00000000..f45e07cd Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1153566565.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/118564947.png b/cool-cacti/static/sprites/asteroid sprites/118564947.png new file mode 100644 index 00000000..b5f9573b Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/118564947.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/123618070.png b/cool-cacti/static/sprites/asteroid sprites/123618070.png new file mode 100644 index 00000000..85330978 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/123618070.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/124465317.png b/cool-cacti/static/sprites/asteroid sprites/124465317.png new file mode 100644 index 00000000..d2b60b61 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/124465317.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1246437094.png b/cool-cacti/static/sprites/asteroid sprites/1246437094.png new file mode 100644 index 00000000..fd47cf88 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1246437094.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1329742594.png b/cool-cacti/static/sprites/asteroid sprites/1329742594.png new file mode 100644 index 00000000..854404d0 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1329742594.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1424204855.png b/cool-cacti/static/sprites/asteroid sprites/1424204855.png new file mode 100644 index 00000000..1725e7c1 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1424204855.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1446250313.png b/cool-cacti/static/sprites/asteroid sprites/1446250313.png new file mode 100644 index 00000000..bade0eb1 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1446250313.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1468273590.png b/cool-cacti/static/sprites/asteroid sprites/1468273590.png new file mode 100644 index 00000000..adb8b0ce Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1468273590.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1488973772.png b/cool-cacti/static/sprites/asteroid sprites/1488973772.png new file mode 100644 index 00000000..97aed693 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1488973772.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/150029986.png b/cool-cacti/static/sprites/asteroid sprites/150029986.png new file mode 100644 index 00000000..933676bd Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/150029986.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1691535978.png b/cool-cacti/static/sprites/asteroid sprites/1691535978.png new file mode 100644 index 00000000..15e43511 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1691535978.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1725661796.png b/cool-cacti/static/sprites/asteroid sprites/1725661796.png new file mode 100644 index 00000000..48c282fb Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1725661796.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1739499255.png b/cool-cacti/static/sprites/asteroid sprites/1739499255.png new file mode 100644 index 00000000..20a087c1 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1739499255.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/17996437.png b/cool-cacti/static/sprites/asteroid sprites/17996437.png new file mode 100644 index 00000000..0984f7e6 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/17996437.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1888508484.png b/cool-cacti/static/sprites/asteroid sprites/1888508484.png new file mode 100644 index 00000000..1d1b8c83 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1888508484.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1926091214.png b/cool-cacti/static/sprites/asteroid sprites/1926091214.png new file mode 100644 index 00000000..ec5d96ee Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1926091214.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/1952977097.png b/cool-cacti/static/sprites/asteroid sprites/1952977097.png new file mode 100644 index 00000000..a877ed46 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/1952977097.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/202411860.png b/cool-cacti/static/sprites/asteroid sprites/202411860.png new file mode 100644 index 00000000..3a9563c8 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/202411860.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2075950734.png b/cool-cacti/static/sprites/asteroid sprites/2075950734.png new file mode 100644 index 00000000..2dce77c3 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2075950734.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2079996232.png b/cool-cacti/static/sprites/asteroid sprites/2079996232.png new file mode 100644 index 00000000..0c3051ee Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2079996232.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2106171138.png b/cool-cacti/static/sprites/asteroid sprites/2106171138.png new file mode 100644 index 00000000..2c608da2 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2106171138.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2118383358.png b/cool-cacti/static/sprites/asteroid sprites/2118383358.png new file mode 100644 index 00000000..b9b80f1a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2118383358.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2183076256.png b/cool-cacti/static/sprites/asteroid sprites/2183076256.png new file mode 100644 index 00000000..d46e66e5 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2183076256.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2195883195.png b/cool-cacti/static/sprites/asteroid sprites/2195883195.png new file mode 100644 index 00000000..607913da Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2195883195.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/224063959.png b/cool-cacti/static/sprites/asteroid sprites/224063959.png new file mode 100644 index 00000000..353b7e43 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/224063959.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2241089568.png b/cool-cacti/static/sprites/asteroid sprites/2241089568.png new file mode 100644 index 00000000..2096ca4e Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2241089568.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2274699037.png b/cool-cacti/static/sprites/asteroid sprites/2274699037.png new file mode 100644 index 00000000..98f9c1b9 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2274699037.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2299188070.png b/cool-cacti/static/sprites/asteroid sprites/2299188070.png new file mode 100644 index 00000000..85330978 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2299188070.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/233236783.png b/cool-cacti/static/sprites/asteroid sprites/233236783.png new file mode 100644 index 00000000..5356e197 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/233236783.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2363091580.png b/cool-cacti/static/sprites/asteroid sprites/2363091580.png new file mode 100644 index 00000000..1822803a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2363091580.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2384002807.png b/cool-cacti/static/sprites/asteroid sprites/2384002807.png new file mode 100644 index 00000000..ef2f3cc3 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2384002807.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/238590564.png b/cool-cacti/static/sprites/asteroid sprites/238590564.png new file mode 100644 index 00000000..8e3c85ce Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/238590564.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2445912905.png b/cool-cacti/static/sprites/asteroid sprites/2445912905.png new file mode 100644 index 00000000..4075361e Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2445912905.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2450256434.png b/cool-cacti/static/sprites/asteroid sprites/2450256434.png new file mode 100644 index 00000000..509dd835 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2450256434.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2460470952.png b/cool-cacti/static/sprites/asteroid sprites/2460470952.png new file mode 100644 index 00000000..b91ee2ef Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2460470952.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2492836461.png b/cool-cacti/static/sprites/asteroid sprites/2492836461.png new file mode 100644 index 00000000..6363e944 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2492836461.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2516703929.png b/cool-cacti/static/sprites/asteroid sprites/2516703929.png new file mode 100644 index 00000000..18286a86 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2516703929.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2541679965.png b/cool-cacti/static/sprites/asteroid sprites/2541679965.png new file mode 100644 index 00000000..fc5d3958 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2541679965.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2607102593.png b/cool-cacti/static/sprites/asteroid sprites/2607102593.png new file mode 100644 index 00000000..23531159 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2607102593.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2633938101.png b/cool-cacti/static/sprites/asteroid sprites/2633938101.png new file mode 100644 index 00000000..dd0f46d1 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2633938101.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/271782734.png b/cool-cacti/static/sprites/asteroid sprites/271782734.png new file mode 100644 index 00000000..2dce77c3 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/271782734.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2745850591.png b/cool-cacti/static/sprites/asteroid sprites/2745850591.png new file mode 100644 index 00000000..848f32d7 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2745850591.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2764849719.png b/cool-cacti/static/sprites/asteroid sprites/2764849719.png new file mode 100644 index 00000000..ccac8517 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2764849719.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2785063265.png b/cool-cacti/static/sprites/asteroid sprites/2785063265.png new file mode 100644 index 00000000..e4221e8a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2785063265.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2789899429.png b/cool-cacti/static/sprites/asteroid sprites/2789899429.png new file mode 100644 index 00000000..41b4e6da Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2789899429.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2791378748.png b/cool-cacti/static/sprites/asteroid sprites/2791378748.png new file mode 100644 index 00000000..0fb2c6b9 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2791378748.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2844830477.png b/cool-cacti/static/sprites/asteroid sprites/2844830477.png new file mode 100644 index 00000000..7862dc07 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2844830477.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2896295212.png b/cool-cacti/static/sprites/asteroid sprites/2896295212.png new file mode 100644 index 00000000..b0beab28 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2896295212.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2924163492.png b/cool-cacti/static/sprites/asteroid sprites/2924163492.png new file mode 100644 index 00000000..ea65e49c Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2924163492.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/2925291501.png b/cool-cacti/static/sprites/asteroid sprites/2925291501.png new file mode 100644 index 00000000..f1f7d4ba Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/2925291501.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3077723619.png b/cool-cacti/static/sprites/asteroid sprites/3077723619.png new file mode 100644 index 00000000..a38560c2 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3077723619.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3130864082.png b/cool-cacti/static/sprites/asteroid sprites/3130864082.png new file mode 100644 index 00000000..a3bc860e Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3130864082.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3167532641.png b/cool-cacti/static/sprites/asteroid sprites/3167532641.png new file mode 100644 index 00000000..104533b1 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3167532641.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3168216978.png b/cool-cacti/static/sprites/asteroid sprites/3168216978.png new file mode 100644 index 00000000..15e43511 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3168216978.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3170379696.png b/cool-cacti/static/sprites/asteroid sprites/3170379696.png new file mode 100644 index 00000000..36c05e75 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3170379696.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3193832944.png b/cool-cacti/static/sprites/asteroid sprites/3193832944.png new file mode 100644 index 00000000..945ca9c2 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3193832944.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3288721588.png b/cool-cacti/static/sprites/asteroid sprites/3288721588.png new file mode 100644 index 00000000..17e90e25 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3288721588.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3297273057.png b/cool-cacti/static/sprites/asteroid sprites/3297273057.png new file mode 100644 index 00000000..c60001b7 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3297273057.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3396683436.png b/cool-cacti/static/sprites/asteroid sprites/3396683436.png new file mode 100644 index 00000000..02200593 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3396683436.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3398415923.png b/cool-cacti/static/sprites/asteroid sprites/3398415923.png new file mode 100644 index 00000000..de19da49 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3398415923.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3417753021.png b/cool-cacti/static/sprites/asteroid sprites/3417753021.png new file mode 100644 index 00000000..0870257b Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3417753021.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3428788332.png b/cool-cacti/static/sprites/asteroid sprites/3428788332.png new file mode 100644 index 00000000..0fa9096a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3428788332.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3482339427.png b/cool-cacti/static/sprites/asteroid sprites/3482339427.png new file mode 100644 index 00000000..c5550123 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3482339427.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3517180902.png b/cool-cacti/static/sprites/asteroid sprites/3517180902.png new file mode 100644 index 00000000..1a5c8a54 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3517180902.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3517671125.png b/cool-cacti/static/sprites/asteroid sprites/3517671125.png new file mode 100644 index 00000000..9d1d70c3 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3517671125.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3535940278.png b/cool-cacti/static/sprites/asteroid sprites/3535940278.png new file mode 100644 index 00000000..e4986f36 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3535940278.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3575891895.png b/cool-cacti/static/sprites/asteroid sprites/3575891895.png new file mode 100644 index 00000000..02ff6698 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3575891895.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3622594717.png b/cool-cacti/static/sprites/asteroid sprites/3622594717.png new file mode 100644 index 00000000..91cfe09a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3622594717.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3634889999.png b/cool-cacti/static/sprites/asteroid sprites/3634889999.png new file mode 100644 index 00000000..45a5225a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3634889999.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3636126819.png b/cool-cacti/static/sprites/asteroid sprites/3636126819.png new file mode 100644 index 00000000..910bbc70 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3636126819.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3642894544.png b/cool-cacti/static/sprites/asteroid sprites/3642894544.png new file mode 100644 index 00000000..c4af9c5e Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3642894544.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3700018417.png b/cool-cacti/static/sprites/asteroid sprites/3700018417.png new file mode 100644 index 00000000..6159edec Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3700018417.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3726866554.png b/cool-cacti/static/sprites/asteroid sprites/3726866554.png new file mode 100644 index 00000000..38f58c22 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3726866554.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3736627942.png b/cool-cacti/static/sprites/asteroid sprites/3736627942.png new file mode 100644 index 00000000..74abc7d4 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3736627942.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3763505851.png b/cool-cacti/static/sprites/asteroid sprites/3763505851.png new file mode 100644 index 00000000..206e20f8 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3763505851.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3776567904.png b/cool-cacti/static/sprites/asteroid sprites/3776567904.png new file mode 100644 index 00000000..4ac97c73 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3776567904.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3826558147.png b/cool-cacti/static/sprites/asteroid sprites/3826558147.png new file mode 100644 index 00000000..495fbfa7 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3826558147.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/3989211066.png b/cool-cacti/static/sprites/asteroid sprites/3989211066.png new file mode 100644 index 00000000..2dac3126 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/3989211066.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/401461502.png b/cool-cacti/static/sprites/asteroid sprites/401461502.png new file mode 100644 index 00000000..5a0591e6 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/401461502.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/4021916695.png b/cool-cacti/static/sprites/asteroid sprites/4021916695.png new file mode 100644 index 00000000..60dfb81c Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/4021916695.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/402551533.png b/cool-cacti/static/sprites/asteroid sprites/402551533.png new file mode 100644 index 00000000..f04c982c Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/402551533.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/4071461444.png b/cool-cacti/static/sprites/asteroid sprites/4071461444.png new file mode 100644 index 00000000..b31e2c8e Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/4071461444.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/4193216520.png b/cool-cacti/static/sprites/asteroid sprites/4193216520.png new file mode 100644 index 00000000..452eaf11 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/4193216520.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/4247189585.png b/cool-cacti/static/sprites/asteroid sprites/4247189585.png new file mode 100644 index 00000000..4214deac Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/4247189585.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/4279862316.png b/cool-cacti/static/sprites/asteroid sprites/4279862316.png new file mode 100644 index 00000000..a63e21aa Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/4279862316.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/465935841.png b/cool-cacti/static/sprites/asteroid sprites/465935841.png new file mode 100644 index 00000000..8e97fb0a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/465935841.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/483449870.png b/cool-cacti/static/sprites/asteroid sprites/483449870.png new file mode 100644 index 00000000..f51854e9 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/483449870.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/514412427.png b/cool-cacti/static/sprites/asteroid sprites/514412427.png new file mode 100644 index 00000000..c5550123 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/514412427.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/569870057.png b/cool-cacti/static/sprites/asteroid sprites/569870057.png new file mode 100644 index 00000000..c60001b7 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/569870057.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/605085139.png b/cool-cacti/static/sprites/asteroid sprites/605085139.png new file mode 100644 index 00000000..56a6410a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/605085139.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/687857303.png b/cool-cacti/static/sprites/asteroid sprites/687857303.png new file mode 100644 index 00000000..d79f5f44 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/687857303.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/767603859.png b/cool-cacti/static/sprites/asteroid sprites/767603859.png new file mode 100644 index 00000000..cd5df681 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/767603859.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/886635296.png b/cool-cacti/static/sprites/asteroid sprites/886635296.png new file mode 100644 index 00000000..2c59703f Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/886635296.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/938145699.png b/cool-cacti/static/sprites/asteroid sprites/938145699.png new file mode 100644 index 00000000..f49cccb9 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/938145699.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/950121403.png b/cool-cacti/static/sprites/asteroid sprites/950121403.png new file mode 100644 index 00000000..99b3d1af Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/950121403.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_00.png b/cool-cacti/static/sprites/asteroid sprites/recycle_00.png new file mode 100644 index 00000000..85ac1bfc Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_00.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_01.png b/cool-cacti/static/sprites/asteroid sprites/recycle_01.png new file mode 100644 index 00000000..db17320f Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_01.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_02.png b/cool-cacti/static/sprites/asteroid sprites/recycle_02.png new file mode 100644 index 00000000..710a1d93 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_02.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_03.png b/cool-cacti/static/sprites/asteroid sprites/recycle_03.png new file mode 100644 index 00000000..d98c1feb Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_03.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_04.png b/cool-cacti/static/sprites/asteroid sprites/recycle_04.png new file mode 100644 index 00000000..1b406fa1 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_04.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_05.png b/cool-cacti/static/sprites/asteroid sprites/recycle_05.png new file mode 100644 index 00000000..1b7e90a5 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_05.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_06.png b/cool-cacti/static/sprites/asteroid sprites/recycle_06.png new file mode 100644 index 00000000..b814a0e9 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_06.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_07.png b/cool-cacti/static/sprites/asteroid sprites/recycle_07.png new file mode 100644 index 00000000..ef3db48f Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_07.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_08.png b/cool-cacti/static/sprites/asteroid sprites/recycle_08.png new file mode 100644 index 00000000..04a15a24 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_08.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_09.png b/cool-cacti/static/sprites/asteroid sprites/recycle_09.png new file mode 100644 index 00000000..d19191c7 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_09.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_10.png b/cool-cacti/static/sprites/asteroid sprites/recycle_10.png new file mode 100644 index 00000000..b2331eac Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_10.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_11.png b/cool-cacti/static/sprites/asteroid sprites/recycle_11.png new file mode 100644 index 00000000..1c1be376 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_11.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_12.png b/cool-cacti/static/sprites/asteroid sprites/recycle_12.png new file mode 100644 index 00000000..9909ab53 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_12.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_13.png b/cool-cacti/static/sprites/asteroid sprites/recycle_13.png new file mode 100644 index 00000000..ffaf2808 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_13.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_14.png b/cool-cacti/static/sprites/asteroid sprites/recycle_14.png new file mode 100644 index 00000000..b0c5190b Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_14.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_15.png b/cool-cacti/static/sprites/asteroid sprites/recycle_15.png new file mode 100644 index 00000000..e2548d44 Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_15.png differ diff --git a/cool-cacti/static/sprites/asteroid sprites/recycle_items.png b/cool-cacti/static/sprites/asteroid sprites/recycle_items.png new file mode 100644 index 00000000..ee9b847a Binary files /dev/null and b/cool-cacti/static/sprites/asteroid sprites/recycle_items.png differ diff --git a/cool-cacti/static/sprites/asteroids.png b/cool-cacti/static/sprites/asteroids.png new file mode 100644 index 00000000..88dccb59 Binary files /dev/null and b/cool-cacti/static/sprites/asteroids.png differ diff --git a/cool-cacti/static/sprites/earth.png b/cool-cacti/static/sprites/earth.png new file mode 100644 index 00000000..3bd1d862 Binary files /dev/null and b/cool-cacti/static/sprites/earth.png differ diff --git a/cool-cacti/static/sprites/earthtest.png b/cool-cacti/static/sprites/earthtest.png new file mode 100644 index 00000000..18fcc8af Binary files /dev/null and b/cool-cacti/static/sprites/earthtest.png differ diff --git a/cool-cacti/static/sprites/health.png b/cool-cacti/static/sprites/health.png new file mode 100644 index 00000000..fbd57f8c Binary files /dev/null and b/cool-cacti/static/sprites/health.png differ diff --git a/cool-cacti/static/sprites/jupiter.png b/cool-cacti/static/sprites/jupiter.png new file mode 100644 index 00000000..feebe2db Binary files /dev/null and b/cool-cacti/static/sprites/jupiter.png differ diff --git a/cool-cacti/static/sprites/mars.png b/cool-cacti/static/sprites/mars.png new file mode 100644 index 00000000..bd0a5068 Binary files /dev/null and b/cool-cacti/static/sprites/mars.png differ diff --git a/cool-cacti/static/sprites/mercury.png b/cool-cacti/static/sprites/mercury.png new file mode 100644 index 00000000..c4c4a2ee Binary files /dev/null and b/cool-cacti/static/sprites/mercury.png differ diff --git a/cool-cacti/static/sprites/moon.png b/cool-cacti/static/sprites/moon.png new file mode 100644 index 00000000..11e7f1db Binary files /dev/null and b/cool-cacti/static/sprites/moon.png differ diff --git a/cool-cacti/static/sprites/neptune.png b/cool-cacti/static/sprites/neptune.png new file mode 100644 index 00000000..3680e83e Binary files /dev/null and b/cool-cacti/static/sprites/neptune.png differ diff --git a/cool-cacti/static/sprites/player.png b/cool-cacti/static/sprites/player.png new file mode 100644 index 00000000..b9c4b406 Binary files /dev/null and b/cool-cacti/static/sprites/player.png differ diff --git a/cool-cacti/static/sprites/saturn.png b/cool-cacti/static/sprites/saturn.png new file mode 100644 index 00000000..6986fc11 Binary files /dev/null and b/cool-cacti/static/sprites/saturn.png differ diff --git a/cool-cacti/static/sprites/scanner.png b/cool-cacti/static/sprites/scanner.png new file mode 100644 index 00000000..bb1509a2 Binary files /dev/null and b/cool-cacti/static/sprites/scanner.png differ diff --git a/cool-cacti/static/sprites/spaceship.png b/cool-cacti/static/sprites/spaceship.png new file mode 100644 index 00000000..0d9a34e4 Binary files /dev/null and b/cool-cacti/static/sprites/spaceship.png differ diff --git a/cool-cacti/static/sprites/sun.png b/cool-cacti/static/sprites/sun.png new file mode 100644 index 00000000..462d214b Binary files /dev/null and b/cool-cacti/static/sprites/sun.png differ diff --git a/cool-cacti/static/sprites/uranus.png b/cool-cacti/static/sprites/uranus.png new file mode 100644 index 00000000..5940fcd2 Binary files /dev/null and b/cool-cacti/static/sprites/uranus.png differ diff --git a/cool-cacti/static/sprites/venus.png b/cool-cacti/static/sprites/venus.png new file mode 100644 index 00000000..363171e1 Binary files /dev/null and b/cool-cacti/static/sprites/venus.png differ diff --git a/cool-cacti/static/styles.css b/cool-cacti/static/styles.css new file mode 100644 index 00000000..4864996e --- /dev/null +++ b/cool-cacti/static/styles.css @@ -0,0 +1,40 @@ +body { + margin: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background: black; + overflow: hidden; +} + +#canvasContainer { + margin: 20px; + display: flex; + justify-content: center; + align-items: center; + background-color: black; + flex: 1; +} + +#gameCanvas { + margin: 0px; + background: black; + flex: 1; +} + +#loadingLabel { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-family: 'Courier New', monospace; + font-weight: 900; + font-size: 1.5rem; + color: #ffff00; + z-index: 10; +} + +audio { + display: none; +} \ No newline at end of file diff --git a/cool-cacti/templates/index.html b/cool-cacti/templates/index.html new file mode 100644 index 00000000..55e347bb --- /dev/null +++ b/cool-cacti/templates/index.html @@ -0,0 +1,61 @@ + + + + + + Codejam 2025 Project + + + + + + + +
+ +
+ +
Loading...
+ + {% for audio_file in audio_list %} + + {% endfor %} + + + from js import Image, window + + sprites_url = "{{ url_for('static', filename='sprites/') }}" + + window.sprites = {} + for sprite in {{ sprite_list }}: + # Skip the folder name "asteroid sprites" which would request "asteroid%20sprites.png" + if sprite == "asteroid sprites": + continue + window.sprites[sprite] = Image.new() + window.sprites[sprite].src = sprites_url + sprite + ".png" + + window.audio_list = {{ audio_list }} + + window.sprites["asteroids"] = Image.new() + window.sprites["asteroids"].src = sprites_url + "asteroids.png" + + window.planets = {{ planets_info|tojson|safe }} + for planet in window.planets: + planet["spritesheet"] = Image.new() + planet["spritesheet"].src = sprites_url + planet["sprite"] + + window.credits = {{ credits | tojson | safe }} + + window.lore = {{ lore|tojson|safe }} + + # exposing canvas globally (used by Player clamp logic) + from js import document + window.canvas = document.getElementById('gameCanvas') + + # initialize game scripts + from audio import AudioHandler + window.audio_handler = AudioHandler("{{ url_for('static', filename='') }}") + import game + + + \ No newline at end of file diff --git a/cool-cacti/tools/fetch_horizons.py b/cool-cacti/tools/fetch_horizons.py new file mode 100644 index 00000000..c0a974e7 --- /dev/null +++ b/cool-cacti/tools/fetch_horizons.py @@ -0,0 +1,52 @@ +import json +import logging +from datetime import UTC, datetime, timedelta +from pathlib import Path + +from horizons_api import HorizonsClient, TimePeriod + +# api access point +HORIZONS_URL = "https://ssd.jpl.nasa.gov/api/horizons.api" +HORIZONS_DATA_DIR = "horizons_data" + +SUN_ID = 10 + +# set logging config here, since this is a standalone script +logging.basicConfig(level=logging.INFO) +log = logging.getLogger(__name__) + +if __name__ == "__main__": + client = HorizonsClient() + # create dir for horizons API data if it doesn't already exist + working_dir = Path.cwd() + horizons_path = working_dir / HORIZONS_DATA_DIR + if not horizons_path.exists(): + Path.mkdir(horizons_path, parents=True, exist_ok=True) + + with (horizons_path / "planets.json").open(encoding='utf-8') as f: + template = json.load(f) + + """ + This is a special query that returns info of major bodies ("MB") in the solar system, + useful for knowing the IDs of planets, moons etc. that horizons refers to things as internally. + """ + major_bodies = client.get_major_bodies(save_to=horizons_path / "major_bodies.txt") + + today = datetime.now(tz=UTC) + tomorrow = today + timedelta(days=1) + + for planet in template: + id: int = planet["id"] + name: str = planet["name"] + time_period = TimePeriod(start=today, end=tomorrow) + + object = client.get_object_data(id) + + if id == SUN_ID: + continue # skip sun since we don't need its position + + pos_response = client.get_vectors(id, time_period) + planet["info"] = object.text + + with(horizons_path / "planets.json").open("w", encoding='utf-8') as f: + json.dump(template, f, indent=4) diff --git a/cool-cacti/tools/generate_pyscript_config.py b/cool-cacti/tools/generate_pyscript_config.py new file mode 100644 index 00000000..6ff9977b --- /dev/null +++ b/cool-cacti/tools/generate_pyscript_config.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Script to automatically generate pyscript.json configuration file by scanning for all Python files. + +Pyscript config files don't allow directory replication, so it's necessary to map every file. +""" + +import argparse +import json +import logging +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +log = logging.getLogger(__name__) + +WORKING_DIR = Path(__file__).parent.parent +OUTPUT_DIR = WORKING_DIR / "static" +APPLICATION_DIR = OUTPUT_DIR / "scripts" + + +def generate_pyscript_config(base_path: Path, output_file: str = "pyscript.json") -> None: + """Generate pyscript.json configuration by scanning for Python files. + + Args: + base_path: Base directory to scan + output_file: Output path for pyscript.json + + """ + if not base_path.exists(): + print(f"Error: Base directory '{base_path}' does not exist") + return + + files_config = {} + + # Find all Python files recursively + for py_file in base_path.rglob("*.py"): + # Get relative path from the working directory + rel_from_working = py_file.relative_to(WORKING_DIR) + pyscript_path = "/" + str(rel_from_working).replace("\\", "/") + + # Check if file is in a subdirectory of base_path + if py_file.parent != base_path: + subdir = py_file.relative_to(base_path).parent + files_config[pyscript_path] = str(subdir).replace("\\", "/") + "/" + else: + files_config[pyscript_path] = "" + + files_config = dict(sorted(files_config.items())) + + config = {"files": files_config} + + output_path = Path(OUTPUT_DIR) / output_file + + with output_path.open("w") as f: + json.dump(config, f, indent=2) + + log.info("Generated %s with %d Python files at %s", output_file, len(files_config), output_path) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate pyscript.json configuration") + parser.add_argument( + "--base-dir", + type=Path, + default=APPLICATION_DIR, + help=f"Base directory to scan for Python files (default: static/scripts)", + ) + parser.add_argument( + "--output", type=str, default="pyscript.json", help="Output file name (default: pyscript.json)" + ) + + args = parser.parse_args() + + generate_pyscript_config(args.base_dir, args.output) diff --git a/cool-cacti/tools/horizons_api/__init__.py b/cool-cacti/tools/horizons_api/__init__.py new file mode 100644 index 00000000..2c97c28c --- /dev/null +++ b/cool-cacti/tools/horizons_api/__init__.py @@ -0,0 +1,3 @@ +from .client import * # noqa: F403 +from .exceptions import * # noqa: F403 +from .models import * # noqa: F403 diff --git a/cool-cacti/tools/horizons_api/client.py b/cool-cacti/tools/horizons_api/client.py new file mode 100644 index 00000000..0408cbdd --- /dev/null +++ b/cool-cacti/tools/horizons_api/client.py @@ -0,0 +1,119 @@ +import logging +from pathlib import Path +from urllib import parse, request + +from .exceptions import HorizonsAPIError, ParsingError +from .models import MajorBody, ObjectData, TimePeriod, VectorData +from .parsers import MajorBodyTableParser, ObjectDataParser, VectorDataParser + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +__all__ = ("HorizonsClient",) + + +class HorizonsClient: + """A client for the JPL Horizons API.""" + + BASE_URL = "https://ssd.jpl.nasa.gov/api/horizons.api" + TIME_FORMAT = "%Y-%m-%d" + + def _request(self, params: dict, save_to: Path | None = None) -> str: + """Make a request to the Horizons API and return the result string.""" + params["format"] = "text" + url = f"{self.BASE_URL}?{parse.urlencode(params)}" + logger.info("Horizons query from %s", url) + + try: + with request.urlopen(url) as response: # noqa: S310 + data = response.read().decode() + + if save_to: + with Path.open(save_to, "w") as f: + f.write(data) + + except Exception as e: + logger.exception("Horizon query raising %s", type(e).__name__) + msg = f"Failed to retrieve data from Horizons API: {e}" + raise HorizonsAPIError(msg) from e + + return data + + def get_major_bodies(self, save_to: Path | None = None) -> list[MajorBody]: + """Get a list of major bodies. + + Arguments: + save_to (Path | None): Optional path to save the raw response data. + + Returns: + list[MajorBody]: A list of major bodies. + + """ + result_text = self._request( + { + "COMMAND": "MB", + "OBJ_DATA": "YES", + "MAKE_EPHEM": "NO", + }, + save_to=save_to, + ) + return MajorBodyTableParser().parse(result_text) + + def get_object_data(self, object_id: int, *, small_body: bool = False, save_to: Path | None = None) -> ObjectData: + """Get physical data for a specific body. + + Arguments: + object_id (int): The ID of the object. + small_body (bool): Whether the object is a small body. + save_to (Path | None): Optional path to save the raw response data. + + Returns: + ObjectData: The physical data for the object. + + """ + result_text = self._request( + { + "COMMAND": str(object_id) + (";" if small_body else ""), + "OBJ_DATA": "YES", + "MAKE_EPHEM": "NO", + }, + save_to=save_to, + ) + + return ObjectDataParser().parse(result_text) + + def get_vectors( + self, object_id: int, time_options: TimePeriod, center: int = 10, save_to: Path | None = None + ) -> VectorData: + """Get positional vectors for a specific body. + + Arguments: + object_id (int): The ID of the object. + time_options (TimePeriod): The time period for the ephemeris. + center (int): The object id for center for the ephemeris. Default 10 for the sun. + save_to (Path | None): Optional path to save the raw response data. + + Returns: + VectorData: The positional vectors for the object. + + """ + result_text = self._request( + { + "COMMAND": str(object_id), + "OBJ_DATA": "NO", + "MAKE_EPHEM": "YES", + "EPHEM_TYPE": "VECTORS", + "CENTER": f"@{center}", + "START_TIME": time_options.start.strftime(self.TIME_FORMAT), + "STOP_TIME": time_options.end.strftime(self.TIME_FORMAT), + "STEP_SIZE": time_options.step, + }, + save_to=save_to, + ) + + vector_data = VectorDataParser().parse(result_text) + if vector_data is None: + msg = "Failed to find all vector components in the text." + logger.warning(msg) + raise ParsingError(msg) + return vector_data diff --git a/cool-cacti/tools/horizons_api/exceptions.py b/cool-cacti/tools/horizons_api/exceptions.py new file mode 100644 index 00000000..b07a4363 --- /dev/null +++ b/cool-cacti/tools/horizons_api/exceptions.py @@ -0,0 +1,9 @@ +__all__ = ("HorizonsAPIError", "ParsingError") + + +class HorizonsAPIError(Exception): + """Base exception for Horizons API errors.""" + + +class ParsingError(Exception): + """Base exception for parsing errors.""" diff --git a/cool-cacti/tools/horizons_api/models.py b/cool-cacti/tools/horizons_api/models.py new file mode 100644 index 00000000..fb5d459b --- /dev/null +++ b/cool-cacti/tools/horizons_api/models.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass +from datetime import datetime + +__all__ = ( + "MajorBody", + "ObjectData", + "TimePeriod", + "VectorData", +) + + +@dataclass +class MajorBody: + """Represents a major body in the solar system.""" + + id: str + name: str | None = None + designation: str | None = None + aliases: str | None = None + + +@dataclass +class ObjectData: + """Represents physical characteristics of a celestial body.""" + + text: str + radius: float | None = None + + +@dataclass +class VectorData: + """Represents position and velocity vectors.""" + + x: float + y: float + z: float | None = None + + +@dataclass +class TimePeriod: + """Represents a time period for ephemeris data.""" + + start: datetime + end: datetime + step: str = "2d" diff --git a/cool-cacti/tools/horizons_api/parsers.py b/cool-cacti/tools/horizons_api/parsers.py new file mode 100644 index 00000000..907fd1be --- /dev/null +++ b/cool-cacti/tools/horizons_api/parsers.py @@ -0,0 +1,134 @@ +import logging +import re +from abc import ABC, abstractmethod + +from .exceptions import ParsingError +from .models import MajorBody, ObjectData, VectorData + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class BaseParser(ABC): + """Abstract base class for all parsers.""" + + @abstractmethod + def parse(self, text: str) -> object: + """Parse the given text and return structured data.""" + raise NotImplementedError + + +class MajorBodyTableParser(BaseParser): + """Parse a table of major bodies from the Horizons API.""" + + def parse(self, text: str) -> list[MajorBody]: + """Parse the text output from the NASA/JPL Horizons API into a list of objects. + + This function orchestrates the parsing process by calling helper methods to: + 1. Find the data section. + 2. Determine column boundaries. + 3. Parse each data row individually. + + Arguments: + text: The multi-line string data from the Horizons API. + + Returns: + A list of MajorBody objects. + + """ + lines = text.strip().split("\n") + + separator_line_index = self._find_separator_index(lines) + if separator_line_index is None: + logger.warning("Could not find header or separator line. Unable to parse.") + return [] + data_start_index = separator_line_index + 1 + data_end_index = self._find_data_end_index(lines, data_start_index) + column_boundaries = self._get_column_boundaries(lines[separator_line_index]) + if not column_boundaries: + logger.warning("Could not determine column boundaries. Parsing may be incomplete.") + return [] + + data_lines = lines[data_start_index:data_end_index] + + parsed_objects = [] + for line in data_lines: + body = self._parse_row(line, column_boundaries) + if body: + parsed_objects.append(body) + + return parsed_objects + + def _find_separator_index(self, lines: list[str]) -> int | None: + """Find the separator line and the starting index of the data rows.""" + header_line_index = -1 + separator_line_index = -1 + for i, line in enumerate(lines): + if "ID#" in line and "Name" in line: + header_line_index = i + if "---" in line and header_line_index != -1: + separator_line_index = i + break + + if separator_line_index == -1 or header_line_index + 1 != separator_line_index: + return None + + return separator_line_index + + def _find_data_end_index(self, lines: list[str], start_index: int) -> int: + """Find the end index of the data rows.""" + for i in range(start_index, len(lines)): + if not lines[i].strip(): + return i + return len(lines) + + def _get_column_boundaries(self, separator_line: str) -> list[tuple[int, int]] | None: + """Determine column boundaries from the separator line using its dash groups.""" + dash_groups = re.finditer(r"-+", separator_line) + return [match.span() for match in dash_groups] + + def _parse_row(self, line: str, column_boundaries: list[tuple[int, int]]) -> MajorBody | None: + """Parse a single data row string into a MajorBody object.""" + if not line.strip(): + return None + + try: + body_data = [line[start:end].strip() for start, end in column_boundaries] + except IndexError: # Line is malformed or shorter than expected + return None + + if not body_data or not body_data[0]: + return None + + return MajorBody(*body_data) + + +class ObjectDataParser(BaseParser): + """Parses the physical characteristics of an object.""" + + def parse(self, text: str) -> ObjectData: + """Parse the text to find the object's radius.""" + radius_match = re.search(r"Radius \(km\)\s*=\s*([\d\.]+)", text) + radius = float(radius_match.group(1)) if radius_match else None + return ObjectData(text=text, radius=radius) + + +class VectorDataParser(BaseParser): + """Parses vector data from the API response.""" + + def parse(self, text: str) -> VectorData | None: + """Parse the text to find X, Y, and Z vector components.""" + # TODO: should probably add error checking for the re searches and horizons queries + # looking for patterns like "X =-2367823E+10" or "Y = 27178E-02" since the API returns coordinates + # in scientific notation + pattern = r"\s*=\s*(-?[\d\.]+E[\+-]\d\d)" + x_match = re.search("X" + pattern, text) + y_match = re.search("Y" + pattern, text) + z_match = re.search("Z" + pattern, text) + + if not (x_match and y_match and z_match): + msg = "Failed to find all vector components in the text." + logger.warning(msg) + raise ParsingError(msg) + + return VectorData(x=float(x_match.group(1)), y=float(y_match.group(1)), z=float(z_match.group(1))) diff --git a/cool-cacti/tools/make_spritesheets.py b/cool-cacti/tools/make_spritesheets.py new file mode 100644 index 00000000..ff23c97e --- /dev/null +++ b/cool-cacti/tools/make_spritesheets.py @@ -0,0 +1,74 @@ +""" +just a quick and dirty script to turn a series of 50 .png sprites into a single spritesheet file. We're not +using this otherwise and we may well not need it again, but this can live here just in case we generate more +planet sprites on that website +""" + +import os +from pathlib import Path + +import numpy as np +from PIL import Image + +cur_dir = Path(__file__).resolve().parent + +# Planet spritesheets +for planet in "earth jupiter mars mercury neptune saturn sun uranus venus".split(): + planet_dir = cur_dir / f"{planet} sprites" + if planet_dir.exists(): + first_frame = Image.open(planet_dir / "sprite_1.png") + width, height = first_frame.size + spritesheet = Image.new("RGBA", (width * 50, height), (0, 0, 0, 0)) + for fr in range(1, 51): + frame = Image.open(planet_dir / f"sprite_{fr}.png") + spritesheet.paste(frame, (width * (fr - 1), 0)) + + spritesheet.save(cur_dir.parent / "static" / "sprites" / f"{planet}.png") + +# Asteroid spritesheet +asteroid_dir = cur_dir.parent / "static" / "sprites" / "asteroid sprites" +if asteroid_dir.exists(): + # Get all PNG files in the asteroid directory + asteroid_files = sorted([f for f in os.listdir(asteroid_dir) if f.endswith(".png")]) + + if asteroid_files: + # Load first asteroid to get dimensions + first_asteroid = Image.open(asteroid_dir / asteroid_files[0]) + width, height = first_asteroid.size + + # Calculate grid layout (try to make roughly square) + num_asteroids = len(asteroid_files) + cols = int(num_asteroids**0.5) + 1 + rows = (num_asteroids + cols - 1) // cols + + print(f"Creating asteroid spritesheet: {cols}x{rows} grid for {num_asteroids} asteroids") + + # Create the spritesheet + spritesheet = Image.new("RGBA", (width * cols, height * rows), (0, 0, 0, 0)) + + collision_radii = [] + # Paste each asteroid + for i, filename in enumerate(asteroid_files): + asteroid = Image.open(asteroid_dir / filename) + pixel_alpha_values = np.array(asteroid)[:, :, 3] + non_transparent_count = np.sum(pixel_alpha_values > 0) + collision_radii.append(int(np.sqrt(non_transparent_count / np.pi))) + + # Calculate position in grid + col = i % cols + row = i // cols + x = col * width + y = row * height + + spritesheet.paste(asteroid, (x, y)) + print(f"Added {filename} at position ({col}, {row})") + + # Save the spritesheet + output_path = cur_dir.parent / "static" / "sprites" / "asteroids.png" + spritesheet.save(output_path) + print(f"Asteroid spritesheet saved to: {output_path}") + print(f"Grid dimensions: {cols} columns x {rows} rows") + print(f"Each sprite: {width}x{height} pixels") + + print("Collision radii:") + print(collision_radii) diff --git a/cool-cacti/tools/process_recycle_sprites.py b/cool-cacti/tools/process_recycle_sprites.py new file mode 100644 index 00000000..2b3c9a9a --- /dev/null +++ b/cool-cacti/tools/process_recycle_sprites.py @@ -0,0 +1,208 @@ +""" +Script to extract sprites from recycle_items.png, resize them to 100x100 with padding, +and add them to the asteroid spritesheet. +""" + +import os +from pathlib import Path + +import numpy as np +from PIL import Image + +cur_dir = Path(__file__).resolve().parent + +def extract_recycle_sprites(): + """Extract individual sprites from recycle_items.png""" + recycle_path = cur_dir.parent / "static" / "sprites" / "asteroid sprites" / "recycle_items.png" + + if not recycle_path.exists(): + print(f"Error: {recycle_path} not found") + return [] + + # Load the recycle items spritesheet + recycle_sheet = Image.open(recycle_path) + sheet_width, sheet_height = recycle_sheet.size + + print(f"Recycle spritesheet dimensions: {sheet_width}x{sheet_height}") + + # Estimate sprite size by looking at the image + # We'll assume it's a horizontal strip of sprites + # Let's try to detect individual sprites by looking for vertical gaps + + # Convert to numpy array for analysis + sheet_array = np.array(recycle_sheet) + + # Check if there are transparent columns that separate sprites + alpha_channel = sheet_array[:, :, 3] if sheet_array.shape[2] == 4 else np.ones((sheet_height, sheet_width)) * 255 + + # Find columns that are completely transparent + transparent_cols = np.all(alpha_channel == 0, axis=0) + + # Find transitions from non-transparent to transparent (sprite boundaries) + boundaries = [] + in_sprite = False + sprite_start = 0 + + for col in range(sheet_width): + if not transparent_cols[col] and not in_sprite: + # Start of a sprite + sprite_start = col + in_sprite = True + elif transparent_cols[col] and in_sprite: + # End of a sprite + boundaries.append((sprite_start, col)) + in_sprite = False + + # Handle case where last sprite goes to the edge + if in_sprite: + boundaries.append((sprite_start, sheet_width)) + + print(f"Found {len(boundaries)} sprites with boundaries: {boundaries}") + + # If we can't detect boundaries automatically, assume equal-width sprites + if not boundaries: + # Let's assume 16 sprites in a horizontal row (common for item spritesheets) + sprite_width = sheet_width // 16 + boundaries = [(i * sprite_width, (i + 1) * sprite_width) for i in range(16)] + print(f"Using equal-width assumption: {sprite_width}px wide sprites") + + # Extract each sprite + sprites = [] + for i, (start_x, end_x) in enumerate(boundaries): + # Extract the sprite + sprite = recycle_sheet.crop((start_x, 0, end_x, sheet_height)) + + # Resize to 100x100 with padding + resized_sprite = resize_with_padding(sprite, (100, 100)) + sprites.append(resized_sprite) + + # Save individual sprite for debugging + debug_path = cur_dir.parent / "static" / "sprites" / "asteroid sprites" / f"recycle_{i:02d}.png" + resized_sprite.save(debug_path) + print(f"Saved recycle sprite {i} to {debug_path}") + + return sprites + +def resize_with_padding(image, target_size): + """Resize image to target size while maintaining aspect ratio and adding transparent padding""" + target_width, target_height = target_size + + # Calculate scaling factor to fit within target size + width_ratio = target_width / image.width + height_ratio = target_height / image.height + scale_factor = min(width_ratio, height_ratio) + + # Calculate new size after scaling + new_width = int(image.width * scale_factor) + new_height = int(image.height * scale_factor) + + # Resize the image + resized = image.resize((new_width, new_height), Image.Resampling.LANCZOS) + + # Create new image with transparent background + result = Image.new("RGBA", target_size, (0, 0, 0, 0)) + + # Center the resized image + x_offset = (target_width - new_width) // 2 + y_offset = (target_height - new_height) // 2 + + result.paste(resized, (x_offset, y_offset), resized if resized.mode == 'RGBA' else None) + + return result + +def rebuild_asteroid_spritesheet(): + """Rebuild the asteroid spritesheet including the new recycle sprites""" + asteroid_dir = cur_dir.parent / "static" / "sprites" / "asteroid sprites" + + # Get all existing asteroid PNG files (excluding recycle_items.png and newly created recycle_XX.png) + all_files = [f for f in os.listdir(asteroid_dir) if f.endswith(".png")] + asteroid_files = [f for f in all_files if f != "recycle_items.png" and not f.startswith("recycle_")] + + print(f"Found {len(asteroid_files)} original asteroid files") + + # Extract recycle sprites + recycle_sprites = extract_recycle_sprites() + print(f"Extracted {len(recycle_sprites)} recycle sprites") + + # Load all asteroid images + all_sprites = [] + collision_radii = [] + + # Add original asteroids + for filename in sorted(asteroid_files): + asteroid = Image.open(asteroid_dir / filename) + all_sprites.append(asteroid) + + # Calculate collision radius based on non-transparent pixels + pixel_alpha_values = np.array(asteroid)[:, :, 3] if np.array(asteroid).shape[2] == 4 else np.ones(asteroid.size[::-1]) * 255 + non_transparent_count = np.sum(pixel_alpha_values > 0) + collision_radii.append(int(np.sqrt(non_transparent_count / np.pi))) + + # Add recycle sprites + for i, sprite in enumerate(recycle_sprites): + all_sprites.append(sprite) + + # Calculate collision radius for recycle sprites + pixel_alpha_values = np.array(sprite)[:, :, 3] + non_transparent_count = np.sum(pixel_alpha_values > 0) + collision_radii.append(int(np.sqrt(non_transparent_count / np.pi))) + + # Create the combined spritesheet + if all_sprites: + # Assume all sprites are now the same size (100x100 for recycle, variable for asteroids) + # We need to standardize - let's make everything 100x100 + standardized_sprites = [] + + for sprite in all_sprites: + if sprite.size != (100, 100): + # Resize asteroid sprites to 100x100 with padding + standardized_sprite = resize_with_padding(sprite, (100, 100)) + standardized_sprites.append(standardized_sprite) + else: + standardized_sprites.append(sprite) + + # Calculate grid layout + num_sprites = len(standardized_sprites) + cols = int(num_sprites**0.5) + 1 + rows = (num_sprites + cols - 1) // cols + + print(f"Creating combined spritesheet: {cols}x{rows} grid for {num_sprites} sprites") + + # Create the spritesheet + sprite_size = 100 # All sprites are now 100x100 + spritesheet = Image.new("RGBA", (sprite_size * cols, sprite_size * rows), (0, 0, 0, 0)) + + # Paste each sprite + for i, sprite in enumerate(standardized_sprites): + col = i % cols + row = i // cols + x = col * sprite_size + y = row * sprite_size + + spritesheet.paste(sprite, (x, y)) + + sprite_type = "recycle" if i >= len(asteroid_files) else "asteroid" + print(f"Added {sprite_type} sprite {i} at position ({col}, {row})") + + # Save the new spritesheet + output_path = cur_dir.parent / "static" / "sprites" / "asteroids.png" + spritesheet.save(output_path) + print(f"Combined spritesheet saved to: {output_path}") + print(f"Grid dimensions: {cols} columns x {rows} rows") + print(f"Each sprite: {sprite_size}x{sprite_size} pixels") + print(f"Total sprites: {len(asteroid_files)} asteroids + {len(recycle_sprites)} recycle items = {num_sprites}") + + print("Collision radii:") + print(collision_radii) + + return True + else: + print("No sprites found to process") + return False + +if __name__ == "__main__": + success = rebuild_asteroid_spritesheet() + if success: + print("Successfully rebuilt asteroid spritesheet with recycle items!") + else: + print("Failed to rebuild spritesheet") diff --git a/cool-cacti/uv.lock b/cool-cacti/uv.lock new file mode 100644 index 00000000..cce1186f --- /dev/null +++ b/cool-cacti/uv.lock @@ -0,0 +1,411 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, +] + +[[package]] +name = "code-jam-soon-to-be-awesome-project" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "flask" }, +] + +[package.dev-dependencies] +dev = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "pre-commit" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [{ name = "flask", specifier = ">=3.1.1" }] + +[package.metadata.requires-dev] +dev = [ + { name = "numpy", specifier = ">=2.3.2" }, + { name = "pillow", specifier = ">=11.3.0" }, + { name = "pre-commit", specifier = "~=4.2.0" }, + { name = "ruff", specifier = "~=0.12.2" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "flask" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305 }, +] + +[[package]] +name = "identify" +version = "2.6.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153 }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420 }, + { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660 }, + { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382 }, + { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258 }, + { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409 }, + { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317 }, + { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262 }, + { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342 }, + { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610 }, + { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292 }, + { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071 }, + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074 }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311 }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022 }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135 }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147 }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989 }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052 }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955 }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843 }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876 }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786 }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395 }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374 }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864 }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533 }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007 }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914 }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708 }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678 }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832 }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049 }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935 }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906 }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607 }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110 }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050 }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292 }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913 }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180 }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809 }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410 }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821 }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303 }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524 }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519 }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972 }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439 }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479 }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805 }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830 }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665 }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777 }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856 }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226 }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800 }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296 }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726 }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652 }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787 }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236 }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950 }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358 }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079 }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324 }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067 }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328 }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652 }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443 }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474 }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038 }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094 }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503 }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574 }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060 }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407 }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841 }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450 }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055 }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110 }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547 }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554 }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132 }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001 }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814 }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124 }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186 }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546 }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102 }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803 }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520 }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116 }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597 }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246 }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699 }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789 }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386 }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911 }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383 }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385 }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129 }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580 }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860 }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694 }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888 }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089 }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206 }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370 }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500 }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, +] + +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "ruff" +version = "0.12.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315 }, + { url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653 }, + { url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690 }, + { url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923 }, + { url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612 }, + { url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745 }, + { url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885 }, + { url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381 }, + { url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271 }, + { url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783 }, + { url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672 }, + { url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626 }, + { url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162 }, + { url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212 }, + { url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382 }, + { url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482 }, + { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718 }, +] + +[[package]] +name = "virtualenv" +version = "20.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/60/4f20960df6c7b363a18a55ab034c8f2bcd5d9770d1f94f9370ec104c1855/virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8", size = 6082160 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362 }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, +] diff --git a/daring-daffodils/.github/workflows/lint.yaml b/daring-daffodils/.github/workflows/lint.yaml new file mode 100644 index 00000000..7f67e803 --- /dev/null +++ b/daring-daffodils/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/daring-daffodils/.gitignore b/daring-daffodils/.gitignore new file mode 100644 index 00000000..233eb87e --- /dev/null +++ b/daring-daffodils/.gitignore @@ -0,0 +1,31 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store diff --git a/daring-daffodils/.pre-commit-config.yaml b/daring-daffodils/.pre-commit-config.yaml new file mode 100644 index 00000000..c0a8de23 --- /dev/null +++ b/daring-daffodils/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/daring-daffodils/LICENSE b/daring-daffodils/LICENSE new file mode 100644 index 00000000..5a04926b --- /dev/null +++ b/daring-daffodils/LICENSE @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/daring-daffodils/Makefile b/daring-daffodils/Makefile new file mode 100644 index 00000000..544fd4f3 --- /dev/null +++ b/daring-daffodils/Makefile @@ -0,0 +1,45 @@ +# --- CONFIG --- +VENV = .venv +PYTHON = python3 +ifeq ($(OS),Windows_NT) + ENVPYTHON := $(VENV)/Scripts/python + POETRY := $(VENV)/Scripts/poetry + PIP := $(VENV)/Scripts/pip +else + ENVPYTHON := $(VENV)/bin/python + POETRY := $(VENV)/bin/poetry + PIP := $(VENV)/bin/pip +endif + +PYTHON3_OK := $(shell type -P python3) +ifeq ('$(PYTHON3_OK)','') + PYTHON = python +endif + +# --- TARGETS --- + +.PHONY: all setup run clean + +all: run + +# Ensure venv exists +$(VENV): + @echo "👉 Creating virtual environment..." + @$(PYTHON) -m venv $(VENV) + +# Install Poetry inside venv if missing +$(POETRY): $(VENV) + @echo "👉 Ensuring Poetry is installed..." + @$(PIP) install poetry + +setup: $(POETRY) + @echo "👉 Installing dependencies..." + @$(POETRY) install --no-root + +run: setup + @echo "👉 Running uvicorn server..." + @$(ENVPYTHON) app.py + +clean: + @echo "🧹 Cleaning up..." + @rm -rf $(VENV) diff --git a/daring-daffodils/README.md b/daring-daffodils/README.md new file mode 100644 index 00000000..a6d9d88c --- /dev/null +++ b/daring-daffodils/README.md @@ -0,0 +1,264 @@ +# Misclick Misclick Logo + +

+ Mouse Pointer +

+

"Wrong mouse, for a reason"

+

+ + GitHub Stars + + + GitHub Forks + + + GitHub Issues + + + GitHub License +

+Sure, you have a perfectly good keyboard and mouse… but why not run Python in your browser with a mobile? +What about doing a scroll, drag and maybe a click or that's all ? + +That is Misclick - A way to control your mouse, **but in opposite way** + +--- + +## 📑 Table of Contents + +- [What can it do](#what-can-it-do) +- [Why is it the wrong tool](#why-is-it-the-wrong-tool-for-the-job) +- [How to install](#how-to-install) +- [How to use](#how-to-use) +- [How it is working](#how-it-is-working) +- [Limitations](#limitations) + +--- + +## What can it do + +- Oh, just casually pair your phone with your browser over WebSocket — because Bluetooth and USB are *too mainstream*. +- Control your browser’s mouse with your phone, but in reverse. Yes, swipe left to go right. Because logic is overrated. + - What’s on the menu? + - Swipe left, swipe right — like Tinder, but for your cursor. + - Swipe up or swipe down. + - Click links and sections — basically what your mouse already does, but now slower and more annoying. + - Scroll up, scroll down — congratulations, you’ve invented scrolling. +- Feeling idle? Don’t worry, the mouse entertains itself with *modes*: + - **Wander Mode** – your cursor takes itself on a joyride. You’re just a spectator. + - **Rage Mode** – because why not double the speed and lose control even faster? + - **Shadow Mode** – cursor goes invisible. Perfect for when you *really* want to rage-quit. + - Sometimes you get all of them combined, so best of luck. + - Bonus: it randomly clicks on stuff, so enjoy your surprise shopping carts and unexpected **easter eggs**. + - If you get redirected to another page, don’t worry — the chaos restarts automatically. +- **Drag text from your browser and send it to your phone. Groundbreaking. Nobel-worthy.** +- Install it as a browser extension, and enjoy the privilege of also opening a webpage on your phone. Wow. +- Cross-platform. Yes, it works everywhere, so nobody is safe. +- Runs locally, no hosting, no leaks — except your sanity. +- No accounts, no personal info — because who would *willingly* sign up for this anyway? + +--- +## Why is it the wrong tool for the job + +You already have a perfectly good **mouse**. +So why on earth would you want to turn your **mobile into one**? + +- You don’t *need* your phone as a mouse if you already own one. +- It doesn’t just move the cursor… it moves it the **opposite way** you tell it to. +- Scrolling? Clicks? Forget precision — this thing thrives on chaos. +- And to make sure you never get comfortable, we’ve added 3 **Chaotic Modes** that guarantee *you’re not in charge*. + + +--- + +> **In short:** It’s unnecessary, uncooperative, and completely backwards. +> Which is exactly why it deserves to exist. + +## How to install + +### Prerequisites + +- Python 3.12+ +- A modern web browser (Chrome recommended) + +### Steps + +1. Clone the repository: + ```bash + git clone https://github.com/SOORAJTS2001/daring-daffodils + cd daring-daffodils +2. Installing Extension + 1. Open [chrome://extensions/](chrome://extensions/) and enable **Developer mode** as shown below: + + ![Enable Developer Mode](./documentation/browser_developer_mode.png) + 2. Click Load unpacked button and select + the [browser_extension](https://github.com/SOORAJTS2001/daring-daffodils/tree/main/browser_extension) folder + inside + the **cloned repo** + ![Enable Developer Mode](./documentation/load_unpacked_button.png) +3. Starting up server — **Make sure your PC/Laptop and phone are connected via the same WiFi** + + 1. Option 1: Using [Makefile](https://en.wikipedia.org/wiki/Make_(software)) + 1. Just run the command: + - Linux/MacOS + ```bash + make + ``` + - Windows + - By default, Windows does not include GNU Make. Install via [this](https://www.msys2.org/) + ```bash + make + ``` + inside the root directory, where the `Makefile` is located. + 2. Option 2: Manual setup + 1. Create your environment: + ```bash + python3 -m venv .env + ``` + 2. Activate it: + - Linux/MacOS + ```bash + source .env/bin/activate + ``` + - Windows + ```bash + .env\Scripts\Activate.ps1 + ``` + 3. Install Poetry: + ```bash + pip install poetry + ``` + 4. Install dependencies: + ```bash + poetry install + ``` + 5. Start the server + ```bash + python3 app.py + ``` + 3. After the server starts, it will show you a QR + code. [Open that with your phone](https://www.android.com/articles/how-do-you-scan-qr-codes-on-android/) +

+ Mouse Pointer +

+ +## How to use + + + + + + + + + + + + + + +
+ Mobile Page
+ Mobile page +
+ Activating Browser Extension
+ Browser extension +
+ Browser extension - Basic movement
+ Browser extension - Basic movement +
+ Browser extension - Mode Activation
+ Browser extension - Mode Activation +
+ Browser extension - Selection
+ Browser extension - Selection +
+ Received by phone
+ Text Received by phone +
+

+ Mouse Pointer +

+ +**⚠️ If you see an error like above** +or the mouse is not showing up: + +- Hard refresh the page +- Restart the server + +## How it is working + +### Architecture Diagram + +

+ Mouse Pointer +

+ +### Tech used + +- Pyscript/pyodide for rendering UI +- `fastapi` for serving static pages and as a websocket server +- `qrcode` library for generating url for mobile page +- `pyodide` wrapper js to setup runtime for python in browser extension + +### Working + +The backend is bind to the port `8000` + +#### Mobile page + +- Once server is up, the mobile page url is shown in the terminal +- On opening the mobile page, the websocket connection between the server and the frontend is established via pyscript +- Inside the `mobile_page.py` it calls the Javascript native API's using pyodide/pyscript and manipulates the DOM +- We have eventListeners in js, which is used for listen and trigger to user events like drag, scroll and click +- We are having a proxy between the js and python for object and function transfer as + explained [here](https://jeff.glass/post/pyscript-why-create-proxy/) +- For every js `touchstart` event we would record it, until it ends with `touchend` and send the change ratio + rather than coordinates via + websocket to the browser + +#### Browser extension - This is where magic happens + +- By default, Chrome extensions cannot load remote code (JS, WASM, CSS) from CDNs because of Content Security Policy ( + CSP). +- Hence we had to package the runtime scripts for python inside the browser, which is + this [runtime](https://github.com/SOORAJTS2001/daring-daffodils/tree/main/browser_extension/runtime) +- Instead of making the entire extension in python which is very very hard (due to support),we are just injecting our + python files and + it's dependency into every website +- Upon activating (by default it has access to all web pages) it connects to our python web socket server, and + shows Mouse Pointer which turns-out to be your + new cursor! +- When a `delta` is received from the mobile page, it is rescaled to match the browser’s dimensions and projected onto the browser. This causes the cursor to move according to the user’s interaction. +- It would get the type of event's user has sent like scroll, drag, selection and corresponding actions are performed + +PS: All `delta` should be reversed inside the browser to do the opposite interaction on browser +##### Clicks +- if a click is fired from the user side, then a `MouseEvent` is fired on browser +#### Scroll +- `touchstart` event detection along with finger counting +##### Drag +- Tap for 300ms and scroll, is considered as the Drag +##### Selection +- It could send user selected text to their connected phone +- Defines a rectangle using the given screen coordinates (x1, y1) and (x2, y2). +- Walks through all text nodes in the document body. +- For each text node, checks whether any part of its bounding client rect intersects with the defined rectangle. +- If overlapping text is found: + - Collects and returns the text content (whitespace-trimmed). + - Visually highlights the region by overlaying a semi-transparent blue box. + - The highlight box automatically disappears after 2 seconds. + +**Although, all our core functionality and logic are in python**

+***You may have noticed that a significant part of our project is shown as JavaScript. This is because the Python runtime in the browser extension relies on JavaScript to bootstrap and interact with WebAssembly. +It mainly involves two key files:*** + +- **`pyodide.asm.js`** – Emscripten-generated “glue code” that initializes the WebAssembly (`.wasm`) binary and connects it to the browser’s JavaScript environment. +- **`pyscript.js`** – JavaScript glue for PyScript. It integrates Pyodide with HTML elements like `` and ``, enabling inline Python execution inside web pages or extensions. + +***Since extensions cannot load executable code directly from the internet (for security reasons), we had to package these files locally instead of relying on CDNs.***
+ +## Limitations +Sometimes the extension could not be used inside sites and restricts script injection, it will throw an error diff --git a/daring-daffodils/app.py b/daring-daffodils/app.py new file mode 100644 index 00000000..4e7cbab0 --- /dev/null +++ b/daring-daffodils/app.py @@ -0,0 +1,178 @@ +import socket +from contextlib import asynccontextmanager + +import qrcode +import uvicorn +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from rich.columns import Columns +from rich.console import Console +from rich.panel import Panel + +# The port on which the FastAPI server will listen +PORT = 8000 + +# The host address for the server +# "0.0.0.0" makes the server accessible from any network interface +HOST = "0.0.0.0" + +# Whether to enable auto-reload of the server on code changes +# Useful in development; should be False in production +RELOAD = False + +# Rich console object for styled terminal output +# Used to print QR codes, instructions, and panels in a readable format +console = Console() + + +def get_wifi_ip(): + """ + Retrieve the local IP address of the machine on the current Wi-Fi/network. + + Returns: + str: The local IP address (e.g., '192.168.1.10'). + + Notes: + - Uses a temporary UDP socket to determine the outbound interface. + - No actual network traffic is sent to the remote host (8.8.8.8). + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + finally: + s.close() + return ip + + +def get_server_url(): + """ + Construct the full HTTP URL of the server using the local Wi-Fi IP and port. + + Returns: + str: URL in the format "http://:". + + Notes: + - Relies on get_wifi_ip() to obtain the host IP. + """ + ip = get_wifi_ip() + return f"http://{ip}:{PORT}" + + +def generate_qr_ascii(url: str) -> str: + """ + Generate an ASCII representation of a QR code for a given URL. + + Parameters: + url (str): The URL or text to encode in the QR code. + + Returns: + str: QR code rendered as ASCII characters. + + Notes: + - Uses the qrcode library. + - The QR code is inverted for better visibility in terminal output. + """ + qr = qrcode.QRCode(border=1) + qr.add_data(url) + qr.make() + # Capture ASCII QR into a string + import io + import sys + + buf = io.StringIO() + sys_stdout = sys.stdout + sys.stdout = buf + qr.print_ascii(invert=True) + sys.stdout = sys_stdout + return buf.getvalue() + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """ + FastAPI lifespan context manager for displaying connection instructions. + + Parameters: + app (FastAPI): The FastAPI application instance. + + Yields: + None + + Effects: + - Prints an ASCII QR code to the terminal for connecting a mobile device. + - Prints step-by-step instructions in the terminal using rich panels. + - Runs once when the application starts and cleans up after shutdown. + """ + url = get_server_url() + mobile_page_url = f"{url}/mobile_page" + qr_ascii = generate_qr_ascii(mobile_page_url) + + qr_panel = Panel.fit(qr_ascii, title="Scan to Open", border_style="green") + steps_panel = Panel.fit( + "\n".join( + [ + "[bold cyan]1.[/bold cyan] Connect to the same Wi-Fi network", + "[bold cyan]2.[/bold cyan] Scan the QR code", + f"[bold cyan] [/bold cyan] Or [yellow]{mobile_page_url}[/yellow]", + "[bold cyan]3.[/bold cyan] That's it, Now do your Misclick!", + ] + ), + title="Steps", + border_style="blue", + ) + + console.print(Columns([qr_panel, steps_panel])) + yield + + +app = FastAPI(lifespan=lifespan) +app.mount("/resource", StaticFiles(directory="mobile_page/"), name="resource") + +# Store connected clients +connected_clients: list[WebSocket] = [] + + +@app.get("/mobile_page") +async def get_mobile_page(): + """ + Serve the main HTML page for mobile clients. + + Returns: + HTMLResponse: The contents of "mobile_page/index.html" as an HTML response. + + Notes: + - The HTML page allows the mobile device to interact with the WebSocket server. + """ + with open("mobile_page/index.html") as f: + return HTMLResponse(f.read()) + + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + """ + WebSocket endpoint to broadcast messages between connected clients. + + Parameters: + websocket (WebSocket): The incoming WebSocket connection. + + Effects: + - Accepts the WebSocket connection and adds it to the connected clients list. + - Receives messages from one client and forwards them to all other connected clients. + - Removes the client from the list when it disconnects. + """ + await websocket.accept() + connected_clients.append(websocket) + try: + while True: + data = await websocket.receive_text() + for client in connected_clients: + if client != websocket: + await client.send_text(data) + except WebSocketDisconnect: + connected_clients.remove(websocket) + + +if __name__ == "__main__": + uvicorn.run("app:app", host=HOST, port=PORT, reload=RELOAD) diff --git a/daring-daffodils/browser_extension/content.js b/daring-daffodils/browser_extension/content.js new file mode 100644 index 00000000..ba819d88 --- /dev/null +++ b/daring-daffodils/browser_extension/content.js @@ -0,0 +1,100 @@ +function injectWhenReady() { + // Ensure the DOM is ready before injecting anything + if (!document.head || !document.body) { + requestAnimationFrame(injectWhenReady); + return; + } + + // --- Load PyScript runtime CSS --- + const css = document.createElement('link'); + css.rel = 'stylesheet'; + css.href = chrome.runtime.getURL('runtime/pyscript.css'); + document.head.appendChild(css); + + // --- Load PyScript runtime JS --- + const pyscriptJs = document.createElement('script'); + pyscriptJs.src = chrome.runtime.getURL('runtime/pyscript.js'); + pyscriptJs.defer = true; + document.head.appendChild(pyscriptJs); + + // Wait until PyScript runtime is loaded before injecting Python code + pyscriptJs.onload = async () => { + // --- Load main.py --- + const mainCode = await fetch(chrome.runtime.getURL('main.py')).then(r => r.text()); + + // --- Load all utils/*.py files (helper modules) --- + const utilsFiles = [ + "__init__.py", + "easter_eggs.py", + "fake_cursor.py", + "make_highlights.py", + "move_and_click.py", + "toast.py" + // Add more utils/*.py files here if needed + ]; + + // Create Python code to build utils/ directory at runtime + let utilsLoader = ` +import os +os.makedirs("utils", exist_ok=True) +`; + + // For each Python util file, write its contents into the in-browser FS + for (const f of utilsFiles) { + const code = await fetch(chrome.runtime.getURL(`utils/${f}`)).then(r => r.text()); + utilsLoader += `with open("utils/${f}", "w") as fp:\n fp.write("""${code.replace(/"""/g, '\\"""')}""")\n\n`; + } + + // --- Inject everything into --- + // (PyScript tag runs Python code directly in the browser) + const pyTag = document.createElement('py-script'); + pyTag.textContent = utilsLoader + "\n\n" + mainCode; + document.body.appendChild(pyTag); + }; + + // --- Easter eggs loader --- + // Fetch JSON file with video links and create invisible elements on screen + fetch(chrome.runtime.getURL('static/easter_eggs.json')) + .then(res => res.json()) + .then(videoList => { + // Randomize video order using Fisher–Yates shuffle + for (let i = videoList.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [videoList[i], videoList[j]] = [videoList[j], videoList[i]]; + } + + // Distribute easter eggs along diagonal of the screen + const spacing = Math.min(window.innerWidth, window.innerHeight) / videoList.length; + const diagonalPositions = []; + + videoList.forEach((video, i) => { + const a = document.createElement('a'); + a.href = video.url; + a.target = "_blank"; // open in new tab + a.id = "pyscript-hidden-easter-eggs"; + + // Make link invisible but still clickable by fake cursor + a.style.position = "absolute"; + a.style.opacity = "0"; + a.style.pointerEvents = "auto"; + a.style.zIndex = "9999"; + + // Position each link on the diagonal + const x = Math.floor(i * spacing); + const y = Math.floor(i * spacing); + + a.style.left = `${x}px`; + a.style.top = `${y}px`; + + // Add to DOM and keep track of coordinates + document.body.appendChild(a); + diagonalPositions.push([x, y]); + }); + + // Expose diagonal positions globally for PyScript wandering mode + window.diagonalPositions = diagonalPositions; + }); +} + +// Kick off the injection once the page is ready +injectWhenReady(); diff --git a/daring-daffodils/browser_extension/main.py b/daring-daffodils/browser_extension/main.py new file mode 100644 index 00000000..70439253 --- /dev/null +++ b/daring-daffodils/browser_extension/main.py @@ -0,0 +1,259 @@ +import json +import random +import traceback + +# JS objects and APIs exposed to Pyodide +from js import WebSocket +from js import console +from js import document +from js import window +from pyodide.ffi import create_proxy + +# Local utility imports +from utils import create_fake_cursor +from utils import fetch_easter_eggs +from utils import get_and_highlight_text_in_rect +from utils import move_and_maybe_click +from utils import show_toast +from utils import trigger_click + +# Connect to backend WebSocket server (browser <-> Python extension bridge) +ws = WebSocket.new("ws://localhost:8000/ws") + +# Browser/environment metadata +BROWSER_HEIGHT = window.innerHeight +BROWSER_WIDTH = window.innerWidth +EASTER_EGGS_COORDINATES = fetch_easter_eggs() # pre-scanned Easter egg anchor points + +# State & timers +INACTIVITY_TIMER = None +WANDERING = False +WANDERING_PROXY = None +LAST_X = None +LAST_Y = None +LAST_CLICK = 0 +LAST_SCROLL_VALUE = None +NEXT_SCROLL_VALUE = 0 + +# Behavior constants +WANDERING_STEP_X = 100 +WANDERING_STEP_Y = 100 +WANDERING_STEP_TIME = 500 # ms between cursor hops +INACTIVITY_TIME = 30000 # 1 minute idle → wander mode kicks in +WANDERING_TIME_MAX_LIMIT = 60000 # wander lasts max 60s +WANDERING_TIME_MIN_LIMIT = 10000 # wander lasts min 10s +PROBABILITY_FOR_EASTER_EGG = 0.3 # 10% chance to snap to Easter egg location +PROBABILITY_FOR_SHADOW_MODE = 0.3 # 30% chance of toggling cursor visibility + + +def random_mode(modes: list): + """Pick a random subset of modes to activate.""" + k = random.randint(1, len(modes)) # pick at least one + return random.sample(modes, k) + + +def start_wandering(): + """ + Trigger wandering mode when no user activity/WebSocket events occur. + + The fake cursor moves around randomly across the screen, with special + 'modes': + - 'wandering': random movement + - 'rage': exaggerated fast jumps + - 'shadow': cursor flickers (visible/hidden) + It may also click on clickable elements or snap to Easter egg positions. + """ + global WANDERING, WANDERING_PROXY + if WANDERING: + return # already running + WANDERING = True + console.log("⚠️ No WebSocket messages — starting wandering mode") + + # Randomize which "flavors" of wandering get activated + modes = ["wandering", "rage", "shadow"] + mode = random_mode(modes) + show_toast(f"You have activated {', '.join(mode)} mode") + + def wander_step(): + """One movement step of wandering mode.""" + if not WANDERING: + return # stop if wandering was canceled + + # Pick a random new location within screen bounds + x = random.randint(0, BROWSER_WIDTH - 50) + y = random.randint(0, BROWSER_HEIGHT - 50) + + # Occasionally snap to an Easter egg anchor + if EASTER_EGGS_COORDINATES and random.random() < PROBABILITY_FOR_EASTER_EGG: + dx, dy = random.choice(EASTER_EGGS_COORDINATES) + else: + dx, dy = x, y + + # Shadow mode: flicker cursor visibility + if "shadow" in mode and random.random() < PROBABILITY_FOR_SHADOW_MODE: + console.log("Shadow enabled") + fake_cursor.style.visibility = "visible" if fake_cursor.style.visibility == "hidden" else "hidden" + + # Rage mode: amplify movement distance + if "rage" in mode: + console.log("Rage enabled") + dx *= 2 + dy *= 2 + + # Move fake cursor + fake_cursor.style.left = f"{dx}px" + fake_cursor.style.top = f"{dy}px" + + # Try clicking element under cursor (if clickable) + el = document.elementFromPoint(dx, dy) + if el: + tag = el.tagName.lower() + clickable = ( + tag in ["button", "a", "input", "select"] + or el.onclick + or window.getComputedStyle(el).cursor == "pointer" + ) + if clickable: + console.log("Clicking:", el) + trigger_click(el) + + # Schedule next step + window.setTimeout(WANDERING_PROXY, WANDERING_STEP_TIME) + + # Wrap wander_step for JS callbacks + WANDERING_PROXY = create_proxy(wander_step) + wander_step() # kick it off immediately + + def stop_wandering(): + """Stop wandering after timeout.""" + global WANDERING + WANDERING = False + fake_cursor.style.visibility = "visible" # reset cursor visibility + console.log("✅ Wandering mode ended — control back to user") + + # Stop wandering after a random duration (10–60s) + duration = random.randint(WANDERING_TIME_MIN_LIMIT, WANDERING_TIME_MAX_LIMIT) + window.setTimeout(create_proxy(stop_wandering), duration) + fake_cursor.style.visibility = "visible" + + +def reset_inactivity_timer(): + """Reset inactivity countdown — triggers wandering mode after idle time.""" + global INACTIVITY_TIMER + if INACTIVITY_TIMER is not None: + window.clearTimeout(INACTIVITY_TIMER) + + def on_timeout(*args): + start_wandering() + + INACTIVITY_TIMER = window.setTimeout(create_proxy(on_timeout), INACTIVITY_TIME) + console.log("finished_all") # debug marker + + +def send_text(text: str): + """Send highlighted/copied text to the server over WebSocket.""" + console.log("Sending copied text") + ws.send(json.dumps({"copied_text": text})) + + +def drag_and_copy(cursor, offset_x, offset_y): # noqa: ARG001 + """ + Drag the fake cursor, highlight text along the drag rectangle, and send it. + """ + # Current cursor coordinates + current_x = float(cursor.style.left.replace("px", "") or 0) + current_y = float(cursor.style.top.replace("px", "") or 0) + + # Apply movement offsets + new_x = current_x + offset_x + new_y = current_y + offset_y + + # Clamp inside screen + max_x = window.innerWidth - cursor.offsetWidth + max_y = window.innerHeight - cursor.offsetHeight + new_x = max(0, min(new_x, max_x)) + new_y = max(0, min(new_y, max_y)) + + # Update fake cursor position + cursor.style.left = f"{new_x}px" + cursor.style.top = f"{new_y}px" + + # Highlight & copy text inside drag rectangle + text = get_and_highlight_text_in_rect(current_x, current_y, new_x, new_y) + send_text(text) + + console.log(new_x, new_y) + + +def fetch_coordinates(data_x: float, data_y: float, fingers: int, data_type: str, click: int): + """ + Handle new coordinate data from the WebSocket (touch/mouse gestures). + Decides whether to move, click, drag, or scroll. + """ + global LAST_X, LAST_Y, LAST_CLICK, NEXT_SCROLL_VALUE, LAST_SCROLL_VALUE + + # Scale normalized coordinates into pixel values + data_x = data_x * BROWSER_WIDTH + data_y = data_y * BROWSER_HEIGHT + + try: + console.log("New Data", data_x, data_y, fingers, data_type, click) + if isinstance(data_x, (int, float)) and isinstance(data_y, (int, float)): + if fingers == 1: + # Single-finger gestures + if data_type in {"scroll", "touch"} and (data_x != LAST_X or data_y != LAST_Y or click != LAST_CLICK): + move_and_maybe_click(fake_cursor, -data_x, -data_y, bool(click)) + LAST_X, LAST_Y, LAST_CLICK = data_x, data_y, click + + elif data_type == "drag" and (data_x != LAST_X or data_y != LAST_Y or click != LAST_CLICK): + drag_and_copy(fake_cursor, data_x, data_y) + console.log(f"from [{LAST_X},{LAST_Y}] to [{data_x},{data_y}]") + LAST_X, LAST_Y, LAST_CLICK = data_x, data_y, click + else: + # Multi-finger gestures → scrolling + if data_y != 0: + NEXT_SCROLL_VALUE = window.scrollY + data_y + console.log(f"scroll to {NEXT_SCROLL_VALUE}") + window.scrollTo(0, NEXT_SCROLL_VALUE) + + except Exception as err: + console.error("Error fetching coordinates:", str(err)) + console.error(traceback.format_exc()) + + +# Start idle tracking immediately +reset_inactivity_timer() + + +# WebSocket event handlers +def onopen(event): # noqa: ARG001 + """ + When connection is established + """ + console.log("✅ Connection opened from extension") + + +def onmessage(event): # noqa: ARG001 + """ + When message is received + """ + data = json.loads(event.data) + console.log("Received coordinates", data) + reset_inactivity_timer() # reset idle timer on activity + fetch_coordinates(data["x"], data["y"], data["fingers"], data["type"], data["click"]) + + +def onclose(event): # noqa: ARG001 + """ + When connection is closed + """ + console.log("❌ Connection closed") + + +# Initialize fake cursor element +fake_cursor = create_fake_cursor() + +# Attach WebSocket event listeners +ws.addEventListener("open", create_proxy(onopen)) +ws.addEventListener("message", create_proxy(onmessage)) +ws.addEventListener("close", create_proxy(onclose)) diff --git a/daring-daffodils/browser_extension/manifest.json b/daring-daffodils/browser_extension/manifest.json new file mode 100644 index 00000000..28982a6d --- /dev/null +++ b/daring-daffodils/browser_extension/manifest.json @@ -0,0 +1,53 @@ +{ + "manifest_version": 3, + "name": "Misclick - Wrong mouse, on purpose", + "version": "1.0", + "description": "Why settle for a boring, predictable mouse when you can have pure chaos?\nMisclick is a “wireless mouse” that isn’t really wireless… or a mouse… or particularly useful. Instead of moving your actual cursor, it randomly wanders across the screen, misbehaves on touch, and occasionally copies text just to remind you who’s in charge.\nIn short: It’s not the right tool for the job — and that’s the whole point.", + "host_permissions": [ + "" + ], + "content_scripts": [ + { + "matches": [ + "" + ], + "js": [ + "content.js" + ], + "run_at": "document_start" + } + ], + "web_accessible_resources": [ + { + "resources": [ + "main.py", + "runtime/pyscript.js", + "runtime/pyscript.css", + "static/easter_eggs.json", + "static/icon16.png", + "static/icon32.png", + "static/icon48.png", + "static/icon128.png", + "utils/*" + ], + "matches": [ + "" + ] + } + ], + "icons": { + "16": "static/icon16.png", + "32": "static/icon32.png", + "48": "static/icon48.png", + "128": "static/icon128.png" + }, + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "static/icon16.png", + "32": "static/icon32.png", + "48": "static/icon48.png", + "128": "static/icon128.png" + } + } +} diff --git a/daring-daffodils/browser_extension/popup.html b/daring-daffodils/browser_extension/popup.html new file mode 100644 index 00000000..0e753e99 --- /dev/null +++ b/daring-daffodils/browser_extension/popup.html @@ -0,0 +1,37 @@ + + + + + + Misclick + + + + Misclick + + + diff --git a/daring-daffodils/browser_extension/runtime/distutils.tar b/daring-daffodils/browser_extension/runtime/distutils.tar new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/micropip-0.1-py3-none-any.whl b/daring-daffodils/browser_extension/runtime/micropip-0.1-py3-none-any.whl new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/packaging-21.3-py3-none-any.whl b/daring-daffodils/browser_extension/runtime/packaging-21.3-py3-none-any.whl new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/pyodide.asm.data b/daring-daffodils/browser_extension/runtime/pyodide.asm.data new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/pyodide.asm.js b/daring-daffodils/browser_extension/runtime/pyodide.asm.js new file mode 100644 index 00000000..04c67a1e --- /dev/null +++ b/daring-daffodils/browser_extension/runtime/pyodide.asm.js @@ -0,0 +1,17 @@ +"use strict"; + +var _createPyodideModule = (() => { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; + return ( +function(_createPyodideModule) { + _createPyodideModule = _createPyodideModule || {}; + +var Module=typeof _createPyodideModule!="undefined"?_createPyodideModule:{};if(typeof globalThis.BigInt64Array==="undefined"){function partsToBigIntSigned(lower,upper){return BigInt(lower)|BigInt(upper+2*(upper&2147483648))<<32n}function partsToBigIntUnsigned(lower,upper){return BigInt(lower)|BigInt(upper)<<32n}function bigIntToParts(value){var lower=Number(BigInt(value)&BigInt(4294967295))|0;var upper=Number(BigInt(value)>>32n)|0;return[lower,upper]}function createBigIntArrayShim(partsToBigInt){function createBigInt64Array(array){if(!ArrayBuffer.isView(array)){array=new Uint32Array(array)}var proxy=new Proxy({slice:function(min,max){var new_buf=array.slice(min*2,max*2);return createBigInt64Array(new_buf)},subarray:function(min,max){var new_buf=array.subarray(min*2,max*2);return createBigInt64Array(new_buf)},[Symbol.iterator]:function*(){for(var i=0;iarray.length){throw new RangeError("offset is out of bounds")}for(var i=0;i{cache&&(cache.leaked=!0,pyproxy_decref_cache(cache));try{Module._Py_DecRef(ptr)}catch(e){API.fatal_error(e)}}):Module.finalizationRegistry={register(){},unregister(){}};let trace_pyproxy_alloc,trace_pyproxy_dealloc,pyproxy_alloc_map=new Map;function _getPtr(jsobj){let ptr=jsobj.$$.ptr;if(0===ptr)throw new Error(jsobj.$$.destroyed_msg);return ptr}Module.pyproxy_alloc_map=pyproxy_alloc_map,Module.enable_pyproxy_allocation_tracing=function(){trace_pyproxy_alloc=function(proxy){pyproxy_alloc_map.set(proxy,Error().stack)},trace_pyproxy_dealloc=function(proxy){pyproxy_alloc_map.delete(proxy)}},Module.disable_pyproxy_allocation_tracing=function(){trace_pyproxy_alloc=function(proxy){},trace_pyproxy_dealloc=function(proxy){}},Module.disable_pyproxy_allocation_tracing(),Module.pyproxy_new=function(ptrobj,cache){let target,flags=Module._pyproxy_getflags(ptrobj),cls=Module.getPyProxyClass(flags);if(256&flags?(target=function(){},Object.setPrototypeOf(target,cls.prototype),delete target.length,delete target.name,target.prototype=void 0):target=Object.create(cls.prototype),!cache){cache={cacheId:Hiwire.new_value(new Map),refcnt:0}}cache.refcnt++,Object.defineProperty(target,"$$",{value:{ptr:ptrobj,type:"PyProxy",cache:cache}}),Module._Py_IncRef(ptrobj);let proxy=new Proxy(target,PyProxyHandlers);return trace_pyproxy_alloc(proxy),Module.finalizationRegistry.register(proxy,[ptrobj,cache],proxy),proxy};let pyproxyClassMap=new Map;Module.getPyProxyClass=function(flags){const FLAG_TYPE_PAIRS=[[1,PyProxyLengthMethods],[2,PyProxyGetItemMethods],[4,PyProxySetItemMethods],[8,PyProxyContainsMethods],[16,PyProxyIterableMethods],[32,PyProxyIteratorMethods],[64,PyProxyAwaitableMethods],[128,PyProxyBufferMethods],[256,PyProxyCallableMethods]];let result=pyproxyClassMap.get(flags);if(result)return result;let descriptors={};for(let[feature_flag,methods]of FLAG_TYPE_PAIRS)flags&feature_flag&&Object.assign(descriptors,Object.getOwnPropertyDescriptors(methods.prototype));descriptors.constructor=Object.getOwnPropertyDescriptor(PyProxyClass.prototype,"constructor"),Object.assign(descriptors,Object.getOwnPropertyDescriptors({$$flags:flags}));let new_proto=Object.create(PyProxyClass.prototype,descriptors);function NewPyProxyClass(){}return NewPyProxyClass.prototype=new_proto,pyproxyClassMap.set(flags,NewPyProxyClass),NewPyProxyClass},Module.PyProxy_getPtr=_getPtr;function pyproxy_decref_cache(cache){if(cache&&(cache.refcnt--,0===cache.refcnt)){let cache_map=Hiwire.pop_value(cache.cacheId);for(let proxy_id of cache_map.values()){const cache_entry=Hiwire.pop_value(proxy_id);cache.leaked||Module.pyproxy_destroy(cache_entry,"This borrowed attribute proxy was automatically destroyed in the process of destroying the proxy it was borrowed from. Try using the 'copy' method.")}}}Module.pyproxy_destroy=function(proxy,destroyed_msg){if(0===proxy.$$.ptr)return;let ptrobj=_getPtr(proxy);Module.finalizationRegistry.unregister(proxy),destroyed_msg=destroyed_msg||"Object has already been destroyed";let proxy_repr,proxy_type=proxy.type;try{proxy_repr=proxy.toString()}catch(e){if(e.pyodide_fatal_error)throw e}proxy.$$.ptr=0,destroyed_msg+=`\nThe object was of type "${proxy_type}" and `,destroyed_msg+=proxy_repr?`had repr "${proxy_repr}"`:"an error was raised when trying to generate its repr",proxy.$$.destroyed_msg=destroyed_msg,pyproxy_decref_cache(proxy.$$.cache);try{Module._Py_DecRef(ptrobj),trace_pyproxy_dealloc(proxy)}catch(e){API.fatal_error(e)}},Module.callPyObjectKwargs=function(ptrobj,jsargs,kwargs){let num_pos_args=jsargs.length,kwargs_names=Object.keys(kwargs),kwargs_values=Object.values(kwargs),num_kwargs=kwargs_names.length;jsargs.push(...kwargs_values);let idresult,idargs=Hiwire.new_value(jsargs),idkwnames=Hiwire.new_value(kwargs_names);try{idresult=Module.__pyproxy_apply(ptrobj,idargs,num_pos_args,idkwnames,num_kwargs)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idargs),Hiwire.decref(idkwnames)}0===idresult&&Module._pythonexc2js();let result=Hiwire.pop_value(idresult);return result&&"coroutine"===result.type&&result._ensure_future&&result._ensure_future(),result},Module.callPyObject=function(ptrobj,jsargs){return Module.callPyObjectKwargs(ptrobj,jsargs,{})};class PyProxyClass{constructor(){throw new TypeError("PyProxy is not a constructor")}get[Symbol.toStringTag](){return"PyProxy"}get type(){let ptrobj=_getPtr(this);return Hiwire.pop_value(Module.__pyproxy_type(ptrobj))}toString(){let jsref_repr,ptrobj=_getPtr(this);try{jsref_repr=Module.__pyproxy_repr(ptrobj)}catch(e){API.fatal_error(e)}return 0===jsref_repr&&Module._pythonexc2js(),Hiwire.pop_value(jsref_repr)}destroy(destroyed_msg){Module.pyproxy_destroy(this,destroyed_msg)}copy(){let ptrobj=_getPtr(this);return Module.pyproxy_new(ptrobj,this.$$.cache)}toJs({depth:depth=-1,pyproxies:pyproxies,create_pyproxies:create_pyproxies=!0,dict_converter:dict_converter,default_converter:default_converter}={}){let idresult,proxies_id,ptrobj=_getPtr(this),dict_converter_id=0,default_converter_id=0;proxies_id=create_pyproxies?pyproxies?Hiwire.new_value(pyproxies):Hiwire.new_value([]):0,dict_converter&&(dict_converter_id=Hiwire.new_value(dict_converter)),default_converter&&(default_converter_id=Hiwire.new_value(default_converter));try{idresult=Module._python2js_custom(ptrobj,depth,proxies_id,dict_converter_id,default_converter_id)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(proxies_id),Hiwire.decref(dict_converter_id),Hiwire.decref(default_converter_id)}return 0===idresult&&Module._pythonexc2js(),Hiwire.pop_value(idresult)}supportsLength(){return!!(1&this.$$flags)}supportsGet(){return!!(2&this.$$flags)}supportsSet(){return!!(4&this.$$flags)}supportsHas(){return!!(8&this.$$flags)}isIterable(){return!!(48&this.$$flags)}isIterator(){return!!(32&this.$$flags)}isAwaitable(){return!!(64&this.$$flags)}isBuffer(){return!!(128&this.$$flags)}isCallable(){return!!(256&this.$$flags)}}class PyProxyLengthMethods{get length(){let length,ptrobj=_getPtr(this);try{length=Module._PyObject_Size(ptrobj)}catch(e){API.fatal_error(e)}return-1===length&&Module._pythonexc2js(),length}}class PyProxyGetItemMethods{get(key){let idresult,ptrobj=_getPtr(this),idkey=Hiwire.new_value(key);try{idresult=Module.__pyproxy_getitem(ptrobj,idkey)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey)}if(0===idresult){if(!Module._PyErr_Occurred())return;Module._pythonexc2js()}return Hiwire.pop_value(idresult)}}class PyProxySetItemMethods{set(key,value){let errcode,ptrobj=_getPtr(this),idkey=Hiwire.new_value(key),idval=Hiwire.new_value(value);try{errcode=Module.__pyproxy_setitem(ptrobj,idkey,idval)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey),Hiwire.decref(idval)}-1===errcode&&Module._pythonexc2js()}delete(key){let errcode,ptrobj=_getPtr(this),idkey=Hiwire.new_value(key);try{errcode=Module.__pyproxy_delitem(ptrobj,idkey)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey)}-1===errcode&&Module._pythonexc2js()}}class PyProxyContainsMethods{has(key){let result,ptrobj=_getPtr(this),idkey=Hiwire.new_value(key);try{result=Module.__pyproxy_contains(ptrobj,idkey)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey)}return-1===result&&Module._pythonexc2js(),1===result}}class PyProxyIterableMethods{[Symbol.iterator](){let iterptr,ptrobj=_getPtr(this),token={};try{iterptr=Module._PyObject_GetIter(ptrobj)}catch(e){API.fatal_error(e)}0===iterptr&&Module._pythonexc2js();let result=function*(iterptr,token){try{let item;for(;item=Module.__pyproxy_iter_next(iterptr);)yield Hiwire.pop_value(item)}catch(e){API.fatal_error(e)}finally{Module.finalizationRegistry.unregister(token),Module._Py_DecRef(iterptr)}Module._PyErr_Occurred()&&Module._pythonexc2js()}(iterptr,token);return Module.finalizationRegistry.register(result,[iterptr,void 0],token),result}}class PyProxyIteratorMethods{[Symbol.iterator](){return this}next(arg){let status,done,idarg=Hiwire.new_value(arg),stackTop=Module.stackSave(),res_ptr=Module.stackAlloc(4);try{status=Module.__pyproxyGen_Send(_getPtr(this),idarg,res_ptr)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idarg)}let idresult=Module.HEAPU32[0+(res_ptr>>2)];return Module.stackRestore(stackTop),-1===status&&Module._pythonexc2js(),done=0===status,{done:done,value:Hiwire.pop_value(idresult)}}}let PyProxyHandlers={isExtensible:()=>!0,has:(jsobj,jskey)=>!!Reflect.has(jsobj,jskey)||"symbol"!=typeof jskey&&(jskey.startsWith("$")&&(jskey=jskey.slice(1)),function(jsobj,jskey){let result,ptrobj=_getPtr(jsobj),idkey=Hiwire.new_value(jskey);try{result=Module.__pyproxy_hasattr(ptrobj,idkey)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey)}return-1===result&&Module._pythonexc2js(),0!==result}(jsobj,jskey)),get(jsobj,jskey){if(jskey in jsobj||"symbol"==typeof jskey)return Reflect.get(jsobj,jskey);jskey.startsWith("$")&&(jskey=jskey.slice(1));let idresult=function(jsobj,jskey){let idresult,ptrobj=_getPtr(jsobj),idkey=Hiwire.new_value(jskey),cacheId=jsobj.$$.cache.cacheId;try{idresult=Module.__pyproxy_getattr(ptrobj,idkey,cacheId)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey)}return 0===idresult&&Module._PyErr_Occurred()&&Module._pythonexc2js(),idresult}(jsobj,jskey);return 0!==idresult?Hiwire.pop_value(idresult):void 0},set(jsobj,jskey,jsval){let descr=Object.getOwnPropertyDescriptor(jsobj,jskey);if(descr&&!descr.writable)throw new TypeError(`Cannot set read only field '${jskey}'`);return"symbol"==typeof jskey?Reflect.set(jsobj,jskey,jsval):(jskey.startsWith("$")&&(jskey=jskey.slice(1)),function(jsobj,jskey,jsval){let errcode,ptrobj=_getPtr(jsobj),idkey=Hiwire.new_value(jskey),idval=Hiwire.new_value(jsval);try{errcode=Module.__pyproxy_setattr(ptrobj,idkey,idval)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey),Hiwire.decref(idval)}-1===errcode&&Module._pythonexc2js()}(jsobj,jskey,jsval),!0)},deleteProperty(jsobj,jskey){let descr=Object.getOwnPropertyDescriptor(jsobj,jskey);if(descr&&!descr.writable)throw new TypeError(`Cannot delete read only field '${jskey}'`);return"symbol"==typeof jskey?Reflect.deleteProperty(jsobj,jskey):(jskey.startsWith("$")&&(jskey=jskey.slice(1)),function(jsobj,jskey){let errcode,ptrobj=_getPtr(jsobj),idkey=Hiwire.new_value(jskey);try{errcode=Module.__pyproxy_delattr(ptrobj,idkey)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(idkey)}-1===errcode&&Module._pythonexc2js()}(jsobj,jskey),!descr||!!descr.configurable)},ownKeys(jsobj){let idresult,ptrobj=_getPtr(jsobj);try{idresult=Module.__pyproxy_ownKeys(ptrobj)}catch(e){API.fatal_error(e)}0===idresult&&Module._pythonexc2js();let result=Hiwire.pop_value(idresult);return result.push(...Reflect.ownKeys(jsobj)),result},apply:(jsobj,jsthis,jsargs)=>jsobj.apply(jsthis,jsargs)};class PyProxyAwaitableMethods{_ensure_future(){if(this.$$.promise)return this.$$.promise;let resolveHandle,rejectHandle,errcode,ptrobj=_getPtr(this),promise=new Promise((resolve,reject)=>{resolveHandle=resolve,rejectHandle=reject}),resolve_handle_id=Hiwire.new_value(resolveHandle),reject_handle_id=Hiwire.new_value(rejectHandle);try{errcode=Module.__pyproxy_ensure_future(ptrobj,resolve_handle_id,reject_handle_id)}catch(e){API.fatal_error(e)}finally{Hiwire.decref(reject_handle_id),Hiwire.decref(resolve_handle_id)}return-1===errcode&&Module._pythonexc2js(),this.$$.promise=promise,this.destroy(),promise}then(onFulfilled,onRejected){return this._ensure_future().then(onFulfilled,onRejected)}catch(onRejected){return this._ensure_future().catch(onRejected)}finally(onFinally){return this._ensure_future().finally(onFinally)}}class PyProxyCallableMethods{apply(jsthis,jsargs){return jsargs=function(...args){return args}.apply(void 0,jsargs),Module.callPyObject(_getPtr(this),jsargs)}call(jsthis,...jsargs){return Module.callPyObject(_getPtr(this),jsargs)}callKwargs(...jsargs){if(0===jsargs.length)throw new TypeError("callKwargs requires at least one argument (the key word argument object)");let kwargs=jsargs.pop();if(void 0!==kwargs.constructor&&"Object"!==kwargs.constructor.name)throw new TypeError("kwargs argument is not an object");return Module.callPyObjectKwargs(_getPtr(this),jsargs,kwargs)}bind(placeholder){return this}}PyProxyCallableMethods.prototype.prototype=Function.prototype;let cdnURL,type_to_array_map=new Map([["i8",Int8Array],["u8",Uint8Array],["u8clamped",Uint8ClampedArray],["i16",Int16Array],["u16",Uint16Array],["i32",Int32Array],["u32",Uint32Array],["i32",Int32Array],["u32",Uint32Array],["i64",globalThis.BigInt64Array],["u64",globalThis.BigUint64Array],["f32",Float32Array],["f64",Float64Array],["dataview",DataView]]);class PyProxyBufferMethods{getBuffer(type){let ArrayType;if(type&&(ArrayType=type_to_array_map.get(type),void 0===ArrayType))throw new Error(`Unknown type ${type}`);let errcode,HEAPU32=Module.HEAPU32,orig_stack_ptr=Module.stackSave(),buffer_struct_ptr=Module.stackAlloc(HEAPU32[0+(Module._buffer_struct_size>>2)]),this_ptr=_getPtr(this);try{errcode=Module.__pyproxy_get_buffer(buffer_struct_ptr,this_ptr)}catch(e){API.fatal_error(e)}-1===errcode&&Module._pythonexc2js();let startByteOffset=HEAPU32[0+(buffer_struct_ptr>>2)],minByteOffset=HEAPU32[1+(buffer_struct_ptr>>2)],maxByteOffset=HEAPU32[2+(buffer_struct_ptr>>2)],readonly=!!HEAPU32[3+(buffer_struct_ptr>>2)],format_ptr=HEAPU32[4+(buffer_struct_ptr>>2)],itemsize=HEAPU32[5+(buffer_struct_ptr>>2)],shape=Hiwire.pop_value(HEAPU32[6+(buffer_struct_ptr>>2)]),strides=Hiwire.pop_value(HEAPU32[7+(buffer_struct_ptr>>2)]),view_ptr=HEAPU32[8+(buffer_struct_ptr>>2)],c_contiguous=!!HEAPU32[9+(buffer_struct_ptr>>2)],f_contiguous=!!HEAPU32[10+(buffer_struct_ptr>>2)],format=Module.UTF8ToString(format_ptr);Module.stackRestore(orig_stack_ptr);let success=!1;try{let bigEndian=!1;void 0===ArrayType&&([ArrayType,bigEndian]=Module.processBufferFormatString(format," In this case, you can pass an explicit type argument."));let alignment=parseInt(ArrayType.name.replace(/[^0-9]/g,""))/8||1;if(bigEndian&&alignment>1)throw new Error("Javascript has no native support for big endian buffers. In this case, you can pass an explicit type argument. For instance, `getBuffer('dataview')` will return a `DataView`which has native support for reading big endian data. Alternatively, toJs will automatically convert the buffer to little endian.");let numBytes=maxByteOffset-minByteOffset;if(0!==numBytes&&(startByteOffset%alignment!=0||minByteOffset%alignment!=0||maxByteOffset%alignment!=0))throw new Error(`Buffer does not have valid alignment for a ${ArrayType.name}`);let data,numEntries=numBytes/alignment,offset=(startByteOffset-minByteOffset)/alignment;data=0===numBytes?new ArrayType:new ArrayType(HEAPU32.buffer,minByteOffset,numEntries);for(let i of strides.keys())strides[i]/=alignment;return success=!0,Object.create(PyBuffer.prototype,Object.getOwnPropertyDescriptors({offset:offset,readonly:readonly,format:format,itemsize:itemsize,ndim:shape.length,nbytes:numBytes,shape:shape,strides:strides,data:data,c_contiguous:c_contiguous,f_contiguous:f_contiguous,_view_ptr:view_ptr,_released:!1}))}finally{if(!success)try{Module._PyBuffer_Release(view_ptr),Module._PyMem_Free(view_ptr)}catch(e){API.fatal_error(e)}}}}class PyBuffer{constructor(){throw new TypeError("PyBuffer is not a constructor")}release(){if(!this._released){try{Module._PyBuffer_Release(this._view_ptr),Module._PyMem_Free(this._view_ptr)}catch(e){API.fatal_error(e)}this._released=!0,this.data=null}}}API.packageIndexReady=async function(lockFileURL){let repodata;if(IN_NODE){await async function(){if(!IN_NODE)return;if((await import("url")).default,nodeFsPromisesMod=await import("fs/promises"),nodeFetch=globalThis.fetch?fetch:(await import("node-fetch")).default,(await import("vm")).default,nodePath=await import("path"),"undefined"!=typeof require)return;const node_modules={fs:await import("fs"),crypto:await import("crypto"),ws:await import("ws"),child_process:await import("child_process")};globalThis.require=function(mod){return node_modules[mod]}}();const package_string=await nodeFsPromisesMod.readFile(lockFileURL);repodata=JSON.parse(package_string)}else{let response=await fetch(lockFileURL);repodata=await response.json()}if(!repodata.packages)throw new Error("Loaded repodata.json does not contain the expected key 'packages'.");API.repodata_info=repodata.info,API.repodata_packages=repodata.packages,API._import_name_to_package_name=new Map;for(let name of Object.keys(API.repodata_packages))for(let import_name of API.repodata_packages[name].imports)API._import_name_to_package_name.set(import_name,name)}(API.config.lockFileURL),API.setCdnUrl=function(url){cdnURL=url};const package_uri_regexp=/^.*?([^\/]*)\.whl$/;function _uri_to_package_name(package_uri){let match=package_uri_regexp.exec(package_uri);if(match){return match[1].toLowerCase().split("-").slice(0,-4).join("-")}}function createDonePromise(){let _resolve=()=>{},_reject=()=>{};const p=new Promise((resolve,reject)=>{_resolve=resolve,_reject=reject});return p.resolve=_resolve,p.reject=_reject,p}function addPackageToLoad(name,toLoad){if(name=name.toLowerCase(),toLoad.has(name))return;const pkg_info=API.repodata_packages[name];if(!pkg_info)throw new Error(`No known package with name '${name}'`);if(toLoad.set(name,{name:name,channel:"default channel",depends:pkg_info.depends,installPromise:void 0,done:createDonePromise()}),void 0===loadedPackages[name])for(let dep_name of pkg_info.depends)addPackageToLoad(dep_name,toLoad)}async function downloadAndInstall(name,toLoad,loaded,failed){if(void 0!==loadedPackages[name])return;const pkg=toLoad.get(name);try{const buffer=await async function(name,channel){let file_name,uri,file_sub_resource_hash;if("default channel"===channel){if(!(name in API.repodata_packages))throw new Error(`Internal error: no entry for package named ${name}`);file_name=API.repodata_packages[name].file_name,uri=resolvePath(file_name,API.config.indexURL),file_sub_resource_hash=API.package_loader.sub_resource_hash(API.repodata_packages[name].sha256)}else uri=channel,file_sub_resource_hash=void 0;try{return await loadBinaryFile(uri,file_sub_resource_hash)}catch(e){if(!IN_NODE||"default channel"!==channel)throw e}console.log(`Didn't find package ${file_name} locally, attempting to load from ${cdnURL}`);let binary=await loadBinaryFile(cdnURL+file_name);return console.log(`Package ${file_name} loaded from ${cdnURL}, caching the wheel in node_modules for future use.`),await nodeFsPromisesMod.writeFile(uri,binary),binary}(pkg.name,pkg.channel),installPromisDependencies=pkg.depends.map(dependency=>toLoad.has(dependency)?toLoad.get(dependency).done:Promise.resolve());await Promise.all(installPromisDependencies),await async function(name,buffer,channel){let pkg=API.repodata_packages[name];pkg||(pkg={file_name:".whl",shared_library:!1,depends:[],imports:[]});const filename=pkg.file_name,dynlibs=API.package_loader.unpack_buffer.callKwargs({buffer:buffer,filename:filename,target:pkg.install_dir,calculate_dynlibs:!0,installer:"pyodide.loadPackage",source:"default channel"===channel?"pyodide":channel});for(const dynlib of dynlibs)await loadDynlib(dynlib,pkg.shared_library);loadedPackages[name]=pkg}(pkg.name,buffer,pkg.channel),loaded.add(pkg.name),loadedPackages[pkg.name]=pkg.channel}catch(err){failed.set(name,err)}finally{pkg.done.resolve()}}function createLock(){let _lock=Promise.resolve();return async function(){const old_lock=_lock;let releaseLock;return _lock=new Promise(resolve=>releaseLock=resolve),await old_lock,releaseLock}}const acquireDynlibLock=createLock();async function loadDynlib(lib,shared){let byteArray;byteArray=Module.FS.lookupPath(lib).node.mount.type==Module.FS.filesystems.MEMFS?Module.FS.filesystems.MEMFS.getFileDataAsTypedArray(Module.FS.lookupPath(lib).node):Module.FS.readFile(lib);const releaseDynlibLock=await acquireDynlibLock(),libraryFS={_ldLibraryPath:"/usr/lib",_resolvePath:path=>libraryFS._ldLibraryPath+"/"+path,findObject:(path,dontResolveLastLink)=>Module.FS.findObject(libraryFS._resolvePath(path),dontResolveLastLink),readFile:path=>Module.FS.readFile(libraryFS._resolvePath(path))};try{const module=await Module.loadWebAssemblyModule(byteArray,{loadAsync:!0,nodelete:!0,allowUndefined:!0,fs:libraryFS});Module.preloadedWasm[lib]=module,Module.preloadedWasm[lib.split("/").pop()]=module,shared&&Module.loadDynamicLibrary(lib,{global:!0,nodelete:!0})}catch(e){if(e&&e.message&&e.message.includes("need to see wasm magic number"))return void console.warn(`Failed to load dynlib ${lib}. We probably just tried to load a linux .so file or something.`);throw e}finally{releaseDynlibLock()}}API.loadDynlib=loadDynlib;const acquirePackageLock=createLock();async function loadPackage(names,messageCallback,errorCallback){messageCallback=messageCallback||console.log,errorCallback=errorCallback||console.error,isPyProxy(names)&&(names=names.toJs()),Array.isArray(names)||(names=[names]);const toLoad=function(names,errorCallback){const toLoad=new Map;for(let name of names){const pkgname=_uri_to_package_name(name);if(void 0===pkgname){addPackageToLoad(name,toLoad);continue}const channel=name;toLoad.has(pkgname)&&toLoad.get(pkgname).channel!==channel?errorCallback(`Loading same package ${pkgname} from ${channel} and ${toLoad.get(pkgname).channel}`):toLoad.set(pkgname,{name:pkgname,channel:channel,depends:[],installPromise:void 0,done:createDonePromise()})}return toLoad}(names,errorCallback);for(const[pkg,pkg_metadata]of toLoad){const loaded=loadedPackages[pkg];void 0!==loaded&&(toLoad.delete(pkg),loaded===pkg_metadata.channel||"default channel"===pkg_metadata.channel?messageCallback(`${pkg} already loaded from ${loaded}`):errorCallback(`URI mismatch, attempting to load package ${pkg} from ${pkg_metadata.channel} while it is already loaded from ${loaded}. To override a dependency, load the custom package first.`))}if(0===toLoad.size)return void messageCallback("No new packages to load");const packageNames=[...toLoad.keys()].join(", "),loaded=new Set,failed=new Map,releaseLock=await acquirePackageLock();try{messageCallback(`Loading ${packageNames}`);for(const[name]of toLoad)loadedPackages[name]?toLoad.delete(name):toLoad.get(name).installPromise=downloadAndInstall(name,toLoad,loaded,failed);if(await Promise.all(Array.from(toLoad.values()).map(({installPromise:installPromise})=>installPromise)),Module.reportUndefinedSymbols(),loaded.size>0){messageCallback(`Loaded ${Array.from(loaded).join(", ")}`)}if(failed.size>0){messageCallback(`Failed to load ${Array.from(failed.keys()).join(", ")}`);for(const[name,err]of failed)errorCallback(`The following error occurred while loading ${name}:`),errorCallback(err.message)}API.importlib.invalidate_caches()}finally{releaseLock()}}let loadedPackages={};"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;var errorStackParser={exports:{}},stackframe={exports:{}};stackframe.exports=function(){function _isNumber(n){return!isNaN(parseFloat(n))&&isFinite(n)}function _capitalize(str){return str.charAt(0).toUpperCase()+str.substring(1)}function _getter(p){return function(){return this[p]}}var booleanProps=["isConstructor","isEval","isNative","isToplevel"],numericProps=["columnNumber","lineNumber"],stringProps=["fileName","functionName","source"],arrayProps=["args"],objectProps=["evalOrigin"],props=booleanProps.concat(numericProps,stringProps,arrayProps,objectProps);function StackFrame(obj){if(obj)for(var i=0;i-1&&(line=line.replace(/eval code/g,"eval").replace(/(\(eval at [^()]*)|(,.*$)/g,""));var sanitizedLine=line.replace(/^\s+/,"").replace(/\(eval code/g,"(").replace(/^.*?\s+/,""),location=sanitizedLine.match(/ (\(.+\)$)/);sanitizedLine=location?sanitizedLine.replace(location[0],""):sanitizedLine;var locationParts=this.extractLocation(location?location[1]:sanitizedLine),functionName=location&&sanitizedLine||void 0,fileName=["eval",""].indexOf(locationParts[0])>-1?void 0:locationParts[0];return new StackFrame({functionName:functionName,fileName:fileName,lineNumber:locationParts[1],columnNumber:locationParts[2],source:line})},this)},parseFFOrSafari:function(error){return error.stack.split("\n").filter(function(line){return!line.match(SAFARI_NATIVE_CODE_REGEXP)},this).map(function(line){if(line.indexOf(" > eval")>-1&&(line=line.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,":$1")),-1===line.indexOf("@")&&-1===line.indexOf(":"))return new StackFrame({functionName:line});var functionNameRegex=/((.*".+"[^@]*)?[^@]*)(?:@)/,matches=line.match(functionNameRegex),functionName=matches&&matches[1]?matches[1]:void 0,locationParts=this.extractLocation(line.replace(functionNameRegex,""));return new StackFrame({functionName:functionName,fileName:locationParts[0],lineNumber:locationParts[1],columnNumber:locationParts[2],source:line})},this)},parseOpera:function(e){return!e.stacktrace||e.message.indexOf("\n")>-1&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(e){for(var lineRE=/Line (\d+).*script (?:in )?(\S+)/i,lines=e.message.split("\n"),result=[],i=2,len=lines.length;i/,"$2").replace(/\([^)]*\)/g,"")||void 0;functionCall.match(/\(([^)]*)\)/)&&(argsRaw=functionCall.replace(/^[^(]+\(([^)]*)\)$/,"$1"));var args=void 0===argsRaw||"[arguments not available]"===argsRaw?void 0:argsRaw.split(",");return new StackFrame({functionName:functionName,args:args,fileName:locationParts[0],lineNumber:locationParts[1],columnNumber:locationParts[2],source:line})},this)}})}(errorStackParser);var ErrorStackParser=errorStackParser.exports;function ensureCaughtObjectIsError(e){if("string"==typeof e)e=new Error(e);else if("object"!=typeof e||null===e||"string"!=typeof e.stack||"string"!=typeof e.message){let msg=`A value of type ${typeof e} with tag ${Object.prototype.toString.call(e)} was thrown as an error!`;try{msg+=`\nString interpolation of the thrown value gives "${e}".`}catch(e){msg+="\nString interpolation of the thrown value fails."}try{msg+=`\nThe thrown value's toString method returns "${e.toString()}".`}catch(e){msg+="\nThe thrown value's toString method fails."}e=new Error(msg)}return e}let fatal_error_occurred=!1;API.fatal_error=function(e){if(!e||!e.pyodide_fatal_error){if(fatal_error_occurred)return console.error("Recursive call to fatal_error. Inner error was:"),void console.error(e);(e="number"==typeof e?convertCppException(e):ensureCaughtObjectIsError(e)).pyodide_fatal_error=!0,fatal_error_occurred=!0,console.error("Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers."),console.error("The cause of the fatal error was:"),API.inTestHoist?(console.error(e.toString()),console.error(e.stack)):console.error(e);try{Module._dump_traceback();for(let key of Object.keys(API.public_api))key.startsWith("_")||"version"===key||Object.defineProperty(API.public_api,key,{enumerable:!0,configurable:!0,get:()=>{throw new Error("Pyodide already fatally failed and can no longer be used.")}});API.on_fatal&&API.on_fatal(e)}catch(err2){console.error("Another error occurred while handling the fatal error:"),console.error(err2)}throw e}};class CppException extends Error{constructor(ty,msg){super(msg),this.ty=ty}}function convertCppException(ptr){const[exc_type_name,is_exception_subclass,adjusted_ptr]=function(ptr){const base_exception_type=Module._exc_type(),caught_exception_type=new Module.ExceptionInfo(ptr).get_type(),stackTop=Module.stackSave(),exceptionThrowBuf=Module.stackAlloc(4);Module.HEAP32[exceptionThrowBuf/4]=ptr;const exc_type_name=Module.demangle(Module.UTF8ToString(Module._exc_typename(caught_exception_type))),is_exception_subclass=!!Module.___cxa_can_catch(base_exception_type,caught_exception_type,exceptionThrowBuf),adjusted_ptr=Module.HEAP32[exceptionThrowBuf/4];return Module.stackRestore(stackTop),[exc_type_name,is_exception_subclass,adjusted_ptr]}(ptr);let msg;if(is_exception_subclass){const msgPtr=Module._exc_what(adjusted_ptr);msg=Module.UTF8ToString(msgPtr)}else msg=`The exception is an object of type ${exc_type_name} at address ${ptr} which does not inherit from std::exception`;return new CppException(exc_type_name,msg)}function isPyodideFrame(frame){if(!frame)return!1;const fileName=frame.fileName||"";if(fileName.includes("wasm-function"))return!0;if(!fileName.includes("pyodide.asm.js"))return!1;let funcName=frame.functionName||"";return funcName.startsWith("Object.")&&(funcName=funcName.slice("Object.".length)),!(funcName in API.public_api)||"PythonError"===funcName||(frame.functionName=funcName,!1)}Object.defineProperty(CppException.prototype,"name",{get(){return`${this.constructor.name} ${this.ty}`}}),Tests.convertCppException=convertCppException,Module.handle_js_error=function(e){if(e&&e.pyodide_fatal_error)throw e;if(e instanceof Module._PropagatePythonError)return;let stack,weirdCatch,restored_error=!1;e instanceof API.PythonError&&(restored_error=Module._restore_sys_last_exception(e.__error_address));try{stack=ErrorStackParser.parse(e)}catch(_){weirdCatch=!0}if(weirdCatch&&(e=ensureCaughtObjectIsError(e)),!restored_error){let eidx=Hiwire.new_value(e),err=Module._JsProxy_create(eidx);Module._set_error(err),Module._Py_DecRef(err),Hiwire.decref(eidx)}if(!weirdCatch){if(function(frame){if(!isPyodideFrame(frame))return!1;const funcName=frame.functionName;return"PythonError"===funcName||"new_error"===funcName}(stack[0]))for(;isPyodideFrame(stack[0]);)stack.shift();for(const frame of stack){if(isPyodideFrame(frame))break;const funcnameAddr=Module.stringToNewUTF8(frame.functionName||"???"),fileNameAddr=Module.stringToNewUTF8(frame.fileName||"???.js");Module.__PyTraceback_Add(funcnameAddr,fileNameAddr,frame.lineNumber),Module._free(funcnameAddr),Module._free(fileNameAddr)}}};class PythonError extends Error{constructor(message,error_address){const oldLimit=Error.stackTraceLimit;Error.stackTraceLimit=1/0,super(message),Error.stackTraceLimit=oldLimit,this.__error_address=error_address}}Object.defineProperty(PythonError.prototype,"name",{value:PythonError.name}),API.PythonError=PythonError;class _PropagatePythonError extends Error{constructor(){API.fail_test=!0,super("If you are seeing this message, an internal Pyodide error has occurred. Please report it to the Pyodide maintainers.")}}Object.defineProperty(_PropagatePythonError.prototype,"name",{value:_PropagatePythonError.name}),Module._PropagatePythonError=_PropagatePythonError;function runPython(code,options={}){return options.globals||(options.globals=API.globals),API.pyodide_code.eval_code(code,options.globals)}async function loadPackagesFromImports(code,messageCallback,errorCallback){let imports,pyimports=API.pyodide_code.find_imports(code);try{imports=pyimports.toJs()}finally{pyimports.destroy()}if(0===imports.length)return;let packageNames=API._import_name_to_package_name,packages=new Set;for(let name of imports)packageNames.has(name)&&packages.add(packageNames.get(name));packages.size&&await loadPackage(Array.from(packages),messageCallback,errorCallback)}async function runPythonAsync(code,options={}){return options.globals||(options.globals=API.globals),await API.pyodide_code.eval_code_async(code,options.globals)}function registerJsModule(name,module){API.pyodide_ffi.register_js_module(name,module)}function registerComlink(Comlink){API._Comlink=Comlink}function unregisterJsModule(name){API.pyodide_ffi.unregister_js_module(name)}function toPy(obj,{depth:depth,defaultConverter:defaultConverter}={depth:-1}){switch(typeof obj){case"string":case"number":case"boolean":case"bigint":case"undefined":return obj}if(!obj||API.isPyProxy(obj))return obj;let obj_id=0,py_result=0,result=0;try{obj_id=Hiwire.new_value(obj);try{py_result=Module.js2python_convert(obj_id,{depth:depth,defaultConverter:defaultConverter})}catch(e){throw e instanceof Module._PropagatePythonError&&Module._pythonexc2js(),e}if(Module._JsProxy_Check(py_result))return obj;result=Module._python2js(py_result),0===result&&Module._pythonexc2js()}finally{Hiwire.decref(obj_id),Module._Py_DecRef(py_result)}return Hiwire.pop_value(result)}function pyimport(mod_name){return API.importlib.import_module(mod_name)}function unpackArchive(buffer,format,options={}){if(!ArrayBuffer.isView(buffer)&&"[object ArrayBuffer]"!==Object.prototype.toString.call(buffer))throw new TypeError("Expected argument 'buffer' to be an ArrayBuffer or an ArrayBuffer view");API.typedArrayAsUint8Array(buffer);let extract_dir=options.extractDir;API.package_loader.unpack_buffer.callKwargs({buffer:buffer,format:format,extract_dir:extract_dir,installer:"pyodide.unpackArchive"})}function setInterruptBuffer(interrupt_buffer){Module.HEAP8[Module._Py_EMSCRIPTEN_SIGNAL_HANDLING]=!!interrupt_buffer,Module.Py_EmscriptenSignalBuffer=interrupt_buffer}function checkInterrupt(){Module.__PyErr_CheckSignals()&&Module._pythonexc2js()}API.loadBinaryFile=loadBinaryFile,API.runPythonInternal=function(code){return API._pyodide._base.eval_code(code,API.runPythonInternal_dict)},API.runPython=runPython,API.runPythonAsync=runPythonAsync,API.saveState=()=>API.pyodide_py._state.save_state(),API.restoreState=state=>API.pyodide_py._state.restore_state(state),exports.FS=void 0,exports.PATH=void 0,exports.ERRNO_CODES=void 0,API.makePublicAPI=function(){exports.FS=Module.FS,exports.PATH=Module.PATH,exports.ERRNO_CODES=Module.ERRNO_CODES;let namespace={globals:undefined,FS:exports.FS,PATH:exports.PATH,ERRNO_CODES:exports.ERRNO_CODES,pyodide_py:undefined,version:"0.21.3",loadPackage:loadPackage,loadPackagesFromImports:loadPackagesFromImports,loadedPackages:loadedPackages,isPyProxy:isPyProxy,runPython:runPython,runPythonAsync:runPythonAsync,registerJsModule:registerJsModule,unregisterJsModule:unregisterJsModule,setInterruptBuffer:setInterruptBuffer,checkInterrupt:checkInterrupt,toPy:toPy,pyimport:pyimport,unpackArchive:unpackArchive,registerComlink:registerComlink,PythonError:PythonError,PyBuffer:PyBuffer,_module:Module,_api:API};return API.public_api=namespace,namespace},exports.checkInterrupt=checkInterrupt,exports.globals=undefined,exports.isPyProxy=isPyProxy,exports.loadPackage=loadPackage,exports.loadPackagesFromImports=loadPackagesFromImports,exports.loadedPackages=loadedPackages,exports.pyimport=pyimport,exports.pyodide_py=undefined,exports.registerComlink=registerComlink,exports.registerJsModule=registerJsModule,exports.runPython=runPython,exports.runPythonAsync=runPythonAsync,exports.setInterruptBuffer=setInterruptBuffer,exports.toPy=toPy,exports.unpackArchive=unpackArchive,exports.unregisterJsModule=unregisterJsModule,Object.defineProperty(exports,"__esModule",{value:!0})}({});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}var fs;var nodePath;var requireNodeFS;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}requireNodeFS=()=>{if(!nodePath){fs=require("fs");nodePath=require("path")}};read_=function shell_read(filename,binary){requireNodeFS();filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{requireNodeFS();filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;var POINTER_SIZE=4;function getNativeTypeSize(type){switch(type){case"i1":case"i8":case"u8":return 1;case"i16":case"u16":return 2;case"i32":case"u32":return 4;case"i64":case"u64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return POINTER_SIZE}else if(type[0]==="i"){const bits=Number(type.substr(1));assert(bits%8===0,"getNativeTypeSize invalid bits "+bits+", type "+type);return bits/8}else{return 0}}}}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function uleb128Encode(n){if(n<128){return[n]}return[n%128|128,n>>7]}function sigToWasmTypes(sig){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64","p":"i32"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i{tempRet0=value};var getTempRet0=()=>tempRet0;var dynamicLibraries=Module["dynamicLibraries"]||[];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}function getCFunc(ident){var func=Module["_"+ident];return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){var len=(str.length<<2)+1;ret=stackAlloc(len);stringToUTF8(str,ret,len)}return ret},"array":function(arr){var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}function AsciiToString(ptr){var str="";while(1){var ch=HEAPU8[ptr++>>0];if(!ch)return str;str+=String.fromCharCode(ch)}}function stringToAscii(str,outPtr){return writeAsciiToMemory(str,outPtr,false)}var UTF16Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function allocateUTF8OnStack(str){var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeStringToMemory(string,buffer,dontAddNull){warnOnce("writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!");var lastChar,end;if(dontAddNull){end=buffer+lengthBytesUTF8(string);lastChar=HEAP8[end]}stringToUTF8(string,buffer,Infinity);if(dontAddNull)HEAP8[end]=lastChar}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var HEAP,buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf);Module["HEAP64"]=HEAP64=new BigInt64Array(buf);Module["HEAPU64"]=HEAPU64=new BigUint64Array(buf)}var TOTAL_STACK=5242880;var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||20971520;if(Module["wasmMemory"]){wasmMemory=Module["wasmMemory"]}else{wasmMemory=new WebAssembly.Memory({"initial":INITIAL_MEMORY/65536,"maximum":2147483648/65536})}if(wasmMemory){buffer=wasmMemory.buffer}INITIAL_MEMORY=buffer.byteLength;updateGlobalBufferAndViews(buffer);var wasmTable=new WebAssembly.Table({"initial":6973,"element":"anyfunc"});var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();SOCKFS.root=FS.mount(SOCKFS,{},null);PIPEFS.root=FS.mount(PIPEFS,{},null);callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPreMain(cb){__ATMAIN__.unshift(cb)}function addOnExit(cb){}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="pyodide.asm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg,"GOT.mem":new Proxy(asmLibraryArg,GOTHandler),"GOT.func":new Proxy(asmLibraryArg,GOTHandler)};function receiveInstance(instance,module){var exports=instance.exports;exports=relocateExports(exports,1024);Module["asm"]=exports;var metadata=getDylinkMetadata(module);if(metadata.neededDynlibs){dynamicLibraries=metadata.neededDynlibs.concat(dynamicLibraries)}mergeLibSymbols(exports,"main");addOnInit(Module["asm"]["__wasm_call_ctors"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"],result["module"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;var ASM_CONSTS={3168479:()=>{throw new Error("intentionally triggered fatal error!")},3168536:$0=>{Hiwire.get_value($0)()},3168559:($0,$1)=>{Hiwire.get_value($1).push(...Hiwire.get_value($0))},3168615:$0=>{for(let v of Hiwire.get_value($0)){if(typeof v.destroy==="function"){try{v.destroy()}catch(e){console.warn("Weird error:",e)}}}},3168764:()=>{throw new Error("Fatal pyodide error")},3168803:()=>{throw new Error("Fatal pyodide error")},3168842:()=>{throw new Error("Fatal pyodide error")},3168881:()=>{throw new Error("Fatal pyodide error")},3168920:()=>{throw new Error("Fatal pyodide error")},3168959:()=>{throw new Error("Fatal pyodide error")},3168998:()=>{throw new Error("Fatal pyodide error")},3169037:()=>{throw new Error("Fatal pyodide error")},3169076:()=>{throw new Error("Fatal pyodide error")},3169115:()=>{throw new Error("Fatal pyodide error")},3169154:()=>{throw new Error("Fatal pyodide error")},3169193:()=>{throw new Error("Fatal pyodide error")},3169232:$0=>{API._pyodide=Hiwire.pop_value($0)},3169273:$0=>{if(!$0){AL.alcErr=40964;return 1}},3169321:$0=>{if(!AL.currentCtx){err("alGetProcAddress() called without a valid context");return 1}if(!$0){AL.currentCtx.err=40963;return 1}}};function JsArray_Check(idobj){"use strict";try{let obj=Hiwire.get_value(idobj);if(Array.isArray(obj)){return!!1}let typeTag=Object.prototype.toString.call(obj);if(typeTag==="[object HTMLCollection]"||typeTag==="[object NodeList]"){return!!1}if(ArrayBuffer.isView(obj)&&obj.constructor.name!=="DataView"){return!!1}return!!0}catch(e){return!!0}}function JsArray_Delete(idobj,idx){"use strict";try{let obj=Hiwire.get_value(idobj);if(idx<0||idx>=obj.length){return-1}obj.splice(idx,1)}catch(e){Module.handle_js_error(e);return-1}return 0}function JsArray_Get(idobj,idx){"use strict";try{let obj=Hiwire.get_value(idobj);let result=obj[idx];if(result===undefined&&!(idx in obj)){return 0}return Hiwire.new_value(result)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsArray_New(){"use strict";try{return Hiwire.new_value([])}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsArray_Push(idarr,idval){"use strict";try{Hiwire.get_value(idarr).push(Hiwire.get_value(idval))}catch(e){Module.handle_js_error(e);return-1}return 0}function JsArray_Push_unchecked(idarr,idval){const arr=Hiwire.get_value(idarr);arr.push(Hiwire.get_value(idval));return arr.length-1}function JsArray_Set(idobj,idx,idval){"use strict";try{Hiwire.get_value(idobj)[idx]=Hiwire.get_value(idval)}catch(e){Module.handle_js_error(e);return-1}return 0}function JsArray_slice(idobj,length,start,stop,step){"use strict";try{let obj=Hiwire.get_value(idobj);let result;if(step===1){result=obj.slice(start,stop)}else{result=Array.from({length:length},(_,i)=>obj[start+i*step])}return Hiwire.new_value(result)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsArray_slice_assign(idobj,slicelength,start,stop,step,values_length,values){"use strict";try{let obj=Hiwire.get_value(idobj);let jsvalues=[];for(let i=0;i>2)+i]);if(ref===0){return-1}jsvalues.push(Hiwire.pop_value(ref))}if(step===1){obj.splice(start,slicelength,...jsvalues)}else{if(values!==0){for(let i=0;i=0;i--){obj.splice(start+i*step,1)}}}}catch(e){Module.handle_js_error(e);return-1}return 0}function JsBuffer_DecodeString_js(jsbuffer_id,encoding){"use strict";try{let buffer=Hiwire.get_value(jsbuffer_id);let encoding_js;if(encoding){encoding_js=UTF8ToString(encoding)}let decoder=new TextDecoder(encoding_js,{fatal:!!1,ignoreBOM:!!1});let res;try{res=decoder.decode(buffer)}catch(e){if(e instanceof TypeError){return 0}throw e}return Hiwire.new_value(res)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsMap_New(){"use strict";try{return Hiwire.new_value(new Map)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsMap_Set(mapid,keyid,valueid){"use strict";try{let map=Hiwire.get_value(mapid);let key=Hiwire.get_value(keyid);let value=Hiwire.get_value(valueid);map.set(key,value)}catch(e){Module.handle_js_error(e);return-1}return 0}function JsObject_DeleteString(idobj,ptrkey){"use strict";try{let jsobj=Hiwire.get_value(idobj);let jskey=UTF8ToString(ptrkey);delete jsobj[jskey]}catch(e){Module.handle_js_error(e);return-1}return 0}function JsObject_Dir(idobj){"use strict";try{let jsobj=Hiwire.get_value(idobj);let result=[];do{result.push(...Object.getOwnPropertyNames(jsobj).filter(s=>{let c=s.charCodeAt(0);return c<48||c>57}))}while(jsobj=Object.getPrototypeOf(jsobj));return Hiwire.new_value(result)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsObject_Entries(idobj){"use strict";try{let jsobj=Hiwire.get_value(idobj);return Hiwire.new_value(Object.entries(jsobj))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsObject_GetString(idobj,ptrkey){"use strict";try{let jsobj=Hiwire.get_value(idobj);let jskey=UTF8ToString(ptrkey);let result=jsobj[jskey];if(result===undefined&&!(jskey in jsobj)){return 0}return Hiwire.new_value(result)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsObject_Keys(idobj){"use strict";try{let jsobj=Hiwire.get_value(idobj);return Hiwire.new_value(Object.keys(jsobj))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsObject_New(){"use strict";try{return Hiwire.new_value({})}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsObject_SetString(idobj,ptrkey,idval){"use strict";try{let jsobj=Hiwire.get_value(idobj);let jskey=UTF8ToString(ptrkey);let jsval=Hiwire.get_value(idval);jsobj[jskey]=jsval}catch(e){Module.handle_js_error(e);return-1}return 0}function JsObject_Values(idobj){"use strict";try{let jsobj=Hiwire.get_value(idobj);return Hiwire.new_value(Object.values(jsobj))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsProxy_array_detect(idobj){"use strict";try{try{let obj=Hiwire.get_value(idobj);if(Array.isArray(obj)){return 1<<10}let typeTag=Object.prototype.toString.call(obj);if(typeTag==="[object HTMLCollection]"||typeTag==="[object NodeList]"){return 1<<11}if(ArrayBuffer.isView(obj)&&obj.constructor.name!=="DataView"){return 1<<12}return 0}catch(e){return 0}}catch(e){Module.handle_js_error(e);return-1}return 0}function JsProxy_subscript_js(idobj,idkey){"use strict";try{let obj=Hiwire.get_value(idobj);let key=Hiwire.get_value(idkey);let result=obj.get(key);if(result===undefined){if(obj.has&&typeof obj.has==="function"&&!obj.has(key)){return 0}}return Hiwire.new_value(result)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsSet_Add(mapid,keyid){"use strict";try{let set=Hiwire.get_value(mapid);let key=Hiwire.get_value(keyid);set.add(key)}catch(e){Module.handle_js_error(e);return-1}return 0}function JsSet_New(){"use strict";try{return Hiwire.new_value(new Set)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function JsString_InternFromCString(str){"use strict";try{let jsstring=UTF8ToString(str);return Hiwire.intern_object(jsstring)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function _JsArray_PostProcess_helper(jscontext,array){"use strict";try{return Hiwire.new_value(Hiwire.get_value(jscontext).dict_converter(Hiwire.get_value(array)))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function _JsArray_PushEntry_helper(array,key,value){"use strict";try{Hiwire.get_value(array).push([Hiwire.get_value(key),Hiwire.get_value(value)])}catch(e){Module.handle_js_error(e);return-1}return 0}function _Py_CheckEmscriptenSignals_Helper(){if(!Module.Py_EmscriptenSignalBuffer){return 0}try{let result=Module.Py_EmscriptenSignalBuffer[0];Module.Py_EmscriptenSignalBuffer[0]=0;return result}catch(e){return 0}}function _python2js_add_to_cache(cacheid,pyparent,jsparent){"use strict";try{const cache=Hiwire.get_value(cacheid);const old_value=cache.get(pyparent);if(old_value!==undefined){Hiwire.decref(old_value)}Hiwire.incref(jsparent);cache.set(pyparent,jsparent)}catch(e){Module.handle_js_error(e);return-1}return 0}function _python2js_addto_postprocess_list(idlist,idparent,idkey,value){const list=Hiwire.get_value(idlist);const parent=Hiwire.get_value(idparent);const key=Hiwire.get_value(idkey);list.push([parent,key,value])}function _python2js_buffer_inner(buf,itemsize,ndim,format,shape,strides,suboffsets){"use strict";try{let converter=Module.get_converter(format,itemsize);let result=Module._python2js_buffer_recursive(buf,0,{ndim:ndim,format:format,itemsize:itemsize,shape:shape,strides:strides,suboffsets:suboffsets,converter:converter});return Hiwire.new_value(result)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function _python2js_cache_lookup(cacheid,pyparent){return Hiwire.get_value(cacheid).get(pyparent)}function _python2js_destroy_cache(cacheid){const cache=Hiwire.get_value(cacheid);for(const[k,v]of cache.entries()){Hiwire.decref(v)}}function _python2js_handle_postprocess_list(idlist,idcache){const list=Hiwire.get_value(idlist);const cache=Hiwire.get_value(idcache);for(const[parent,key,value]of list){let out_value=Hiwire.get_value(cache.get(value));if(parent.constructor.name==="Map"){parent.set(key,out_value)}else{parent[key]=out_value}}}function array_to_js(array,len){return Hiwire.new_value(Array.from(HEAP32.subarray(array/4,array/4+len)))}function console_error(msg){let jsmsg=UTF8ToString(msg);console.error(jsmsg)}function console_error_obj(obj){console.error(Hiwire.get_value(obj))}function create_once_callable(obj){"use strict";try{_Py_IncRef(obj);let alreadyCalled=!!0;function wrapper(...args){if(alreadyCalled){throw new Error("OnceProxy can only be called once")}try{return Module.callPyObject(obj,args)}finally{wrapper.destroy()}}wrapper.destroy=function(){if(alreadyCalled){throw new Error("OnceProxy has already been destroyed")}alreadyCalled=!!1;Module.finalizationRegistry.unregister(wrapper);_Py_DecRef(obj)};Module.finalizationRegistry.register(wrapper,[obj,undefined],wrapper);return Hiwire.new_value(wrapper)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function create_promise_handles(handle_result,handle_exception,done_callback_id){"use strict";try{if(handle_result){_Py_IncRef(handle_result)}if(handle_exception){_Py_IncRef(handle_exception)}let done_callback=x=>{};if(done_callback_id){done_callback=Hiwire.get_value(done_callback_id)}let used=!!0;function checkUsed(){if(used){throw new Error("One of the promise handles has already been called.")}}function destroy(){checkUsed();used=!!1;if(handle_result){_Py_DecRef(handle_result)}if(handle_exception){_Py_DecRef(handle_exception)}}function onFulfilled(res){checkUsed();try{if(handle_result){return Module.callPyObject(handle_result,[res])}}finally{done_callback(res);destroy()}}function onRejected(err){checkUsed();try{if(handle_exception){return Module.callPyObject(handle_exception,[err])}}finally{done_callback(undefined);destroy()}}onFulfilled.destroy=destroy;onRejected.destroy=destroy;return Hiwire.new_value([onFulfilled,onRejected])}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function destroy_proxies(proxies_id,msg_ptr){let msg=undefined;if(msg_ptr){msg=UTF8ToString(msg_ptr)}let proxies=Hiwire.get_value(proxies_id);for(let px of proxies){Module.pyproxy_destroy(px,msg)}}function destroy_proxies_js(proxies_id){"use strict";try{for(let proxy of Hiwire.get_value(proxies_id)){proxy.destroy()}}catch(e){Module.handle_js_error(e);return-1}return 0}function destroy_proxy(proxy_id,msg_ptr){let msg=undefined;if(msg_ptr){msg=UTF8ToString(msg_ptr)}Module.pyproxy_destroy(Module.hiwire.get_value(proxy_id),msg)}function em_call_init_function(f){return Module.wasmTable.get(f)()}function fail_test(){API.fail_test=true}function ffi_call(cif,fn,rvalue,avalue){var abi=HEAPU32[(cif>>2)+0];var nargs=HEAPU32[(cif>>2)+1];var nfixedargs=HEAPU32[(cif>>2)+6];var arg_types_ptr=HEAPU32[(cif>>2)+2];var rtype_unboxed=unbox_small_structs(HEAPU32[(cif>>2)+3]);var rtype_ptr=rtype_unboxed[0];var rtype_id=rtype_unboxed[1];var orig_stack_ptr=stackSave();var cur_stack_ptr=orig_stack_ptr;var args=[];var ret_by_arg=false;if(rtype_id===15){throw new Error("complex ret marshalling nyi")}if(rtype_id<0||rtype_id>15){throw new Error("Unexpected rtype "+rtype_id)}if(rtype_id===4||rtype_id===13){args.push(rvalue);ret_by_arg=true}for(var i=0;i>2)+i];var arg_unboxed=unbox_small_structs(HEAPU32[(arg_types_ptr>>2)+i]);var arg_type_ptr=arg_unboxed[0];var arg_type_id=arg_unboxed[1];switch(arg_type_id){case 1:case 10:case 9:case 14:args.push(HEAPU32[(arg_ptr>>2)+0]);break;case 2:args.push(HEAPF32[(arg_ptr>>2)+0]);break;case 3:args.push(HEAPF64[(arg_ptr>>3)+0]);break;case 5:args.push(HEAPU8[arg_ptr+0]);break;case 6:args.push(HEAP8[arg_ptr+0]);break;case 7:args.push(HEAPU16[(arg_ptr>>1)+0]);break;case 8:args.push(HEAP16[(arg_ptr>>1)+0]);break;case 11:case 12:args.push(BigInt(HEAPU32[(arg_ptr>>2)+0*2])|BigInt(HEAPU32[(arg_ptr>>2)+0*2+1])<>2)+0*2])|BigInt(HEAPU32[(arg_ptr>>2)+0*2+1])<>2)+1*2])|BigInt(HEAPU32[(arg_ptr>>2)+1*2+1])<>2)+0];var align=HEAPU16[(arg_type_ptr+4>>1)+0];cur_stack_ptr-=size,cur_stack_ptr&=~(align-1);HEAP8.subarray(cur_stack_ptr,cur_stack_ptr+size).set(HEAP8.subarray(arg_ptr,arg_ptr+size));args.push(cur_stack_ptr);break;case 15:throw new Error("complex marshalling nyi");default:throw new Error("Unexpected type "+arg_type_id)}}if(nfixedargs!=nargs){var struct_arg_info=[];for(var i=nargs-1;i>=nfixedargs;i--){var arg_ptr=HEAPU32[(avalue>>2)+i];var arg_unboxed=unbox_small_structs(HEAPU32[(arg_types_ptr>>2)+i]);var arg_type_ptr=arg_unboxed[0];var arg_type_id=arg_unboxed[1];switch(arg_type_id){case 5:case 6:cur_stack_ptr-=1,cur_stack_ptr&=~(1-1);HEAPU8[cur_stack_ptr+0]=HEAPU8[arg_ptr+0];break;case 7:case 8:cur_stack_ptr-=2,cur_stack_ptr&=~(2-1);HEAPU16[(cur_stack_ptr>>1)+0]=HEAPU16[(arg_ptr>>1)+0];break;case 1:case 9:case 10:case 14:case 2:cur_stack_ptr-=4,cur_stack_ptr&=~(4-1);HEAPU32[(cur_stack_ptr>>2)+0]=HEAPU32[(arg_ptr>>2)+0];break;case 3:case 11:case 12:cur_stack_ptr-=8,cur_stack_ptr&=~(8-1);HEAPU32[(cur_stack_ptr>>2)+0]=HEAPU32[(arg_ptr>>2)+0];HEAPU32[(cur_stack_ptr>>2)+1]=HEAPU32[(arg_ptr>>2)+1];break;case 4:cur_stack_ptr-=16,cur_stack_ptr&=~(8-1);HEAPU32[(cur_stack_ptr>>2)+0]=HEAPU32[(arg_ptr>>2)+0];HEAPU32[(cur_stack_ptr>>2)+1]=HEAPU32[(arg_ptr>>2)+1];HEAPU32[(cur_stack_ptr>>2)+2]=HEAPU32[(arg_ptr>>2)+1];HEAPU32[(cur_stack_ptr>>2)+3]=HEAPU32[(arg_ptr>>2)+1];break;case 13:cur_stack_ptr-=4,cur_stack_ptr&=~(4-1);struct_arg_info.push([cur_stack_ptr,arg_ptr,HEAPU32[(arg_type_ptr>>2)+0],HEAPU16[(arg_type_ptr+4>>1)+0]]);break;case 15:throw new Error("complex arg marshalling nyi");default:throw new Error("Unexpected argtype "+arg_type_id)}}args.push(cur_stack_ptr);for(var i=0;i>2)+0]=cur_stack_ptr}}cur_stack_ptr-=0,cur_stack_ptr&=~(8-1);stackRestore(cur_stack_ptr);var result=wasmTable.get(fn).apply(null,args);stackRestore(orig_stack_ptr);if(ret_by_arg){return}switch(rtype_id){case 0:break;case 1:case 9:case 10:case 14:HEAPU32[(rvalue>>2)+0]=result;break;case 2:HEAPF32[(rvalue>>2)+0]=result;break;case 3:HEAPF64[(rvalue>>3)+0]=result;break;case 5:case 6:HEAPU8[rvalue+0]=result;break;case 7:case 8:HEAPU16[(rvalue>>1)+0]=result;break;case 11:case 12:HEAPU32[(rvalue>>2)+0*2]=Number(result&BigInt(4294967295))|0,HEAPU32[(rvalue>>2)+0*2+1]=Number(result>>BigInt(32))|0;break;case 15:throw new Error("complex ret marshalling nyi");default:throw new Error("Unexpected rtype "+rtype_id)}}function ffi_closure_alloc_helper(size,code){var closure=_malloc(size);var index=getEmptyTableSlot();HEAPU32[(code>>2)+0]=index;HEAPU32[(closure>>2)+0]=index;return closure}function ffi_closure_free_helper(closure){var index=HEAPU32[(closure>>2)+0];freeTableIndexes.push(index);_free(closure)}function ffi_prep_closure_loc_helper(closure,cif,fun,user_data,codeloc){var abi=HEAPU32[(cif>>2)+0];var nargs=HEAPU32[(cif>>2)+1];var nfixedargs=HEAPU32[(cif>>2)+6];var arg_types_ptr=HEAPU32[(cif>>2)+2];var rtype_unboxed=unbox_small_structs(HEAPU32[(cif>>2)+3]);var rtype_ptr=rtype_unboxed[0];var rtype_id=rtype_unboxed[1];var sig;var ret_by_arg=false;switch(rtype_id){case 0:sig="v";break;case 13:case 4:sig="vi";ret_by_arg=true;break;case 1:case 5:case 6:case 7:case 8:case 9:case 10:case 14:sig="i";break;case 2:sig="f";break;case 3:sig="d";break;case 11:case 12:sig="j";break;case 15:throw new Error("complex ret marshalling nyi");default:throw new Error("Unexpected rtype "+rtype_id)}var unboxed_arg_type_id_list=[];var unboxed_arg_type_info_list=[];for(var i=0;i>2)+i]);var arg_type_ptr=arg_unboxed[0];var arg_type_id=arg_unboxed[1];unboxed_arg_type_id_list.push(arg_type_id);unboxed_arg_type_info_list.push([HEAPU32[(arg_type_ptr>>2)+0],HEAPU16[(arg_type_ptr+4>>1)+0]])}for(var i=0;i>2)+carg_idx]=cur_ptr;HEAPU8[cur_ptr+0]=cur_arg;break;case 7:case 8:cur_ptr-=2,cur_ptr&=~(4-1);HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr;HEAPU16[(cur_ptr>>1)+0]=cur_arg;break;case 1:case 9:case 10:case 14:cur_ptr-=4,cur_ptr&=~(4-1);HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr;HEAPU32[(cur_ptr>>2)+0]=cur_arg;break;case 13:cur_ptr-=arg_size,cur_ptr&=~(arg_align-1);HEAP8.subarray(cur_ptr,cur_ptr+arg_size).set(HEAP8.subarray(cur_arg,cur_arg+arg_size));HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr;break;case 2:cur_ptr-=4,cur_ptr&=~(4-1);HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr;HEAPF32[(cur_ptr>>2)+0]=cur_arg;break;case 3:cur_ptr-=8,cur_ptr&=~(8-1);HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr;HEAPF64[(cur_ptr>>3)+0]=cur_arg;break;case 11:case 12:cur_ptr-=8,cur_ptr&=~(8-1);HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr;HEAPU32[(cur_ptr>>2)+0*2]=Number(cur_arg&BigInt(4294967295))|0,HEAPU32[(cur_ptr>>2)+0*2+1]=Number(cur_arg>>BigInt(32))|0;break;case 4:cur_ptr-=16,cur_ptr&=~(8-1);HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr;HEAPU32[(cur_ptr>>2)+0*2]=Number(cur_arg&BigInt(4294967295))|0,HEAPU32[(cur_ptr>>2)+0*2+1]=Number(cur_arg>>BigInt(32))|0;cur_arg=args[jsarg_idx++];HEAPU32[(cur_ptr>>2)+1*2]=Number(cur_arg&BigInt(4294967295))|0,HEAPU32[(cur_ptr>>2)+1*2+1]=Number(cur_arg>>BigInt(32))|0;break}}var varargs=args[args.length-1];for(var carg_idx=nfixedargs;carg_idx>2)+0];cur_ptr-=arg_size,cur_ptr&=~(arg_align-1);HEAP8.subarray(cur_ptr,cur_ptr+arg_size).set(HEAP8.subarray(struct_ptr,struct_ptr+arg_size));HEAPU32[(args_ptr>>2)+carg_idx]=cur_ptr}else{HEAPU32[(args_ptr>>2)+carg_idx]=varargs}varargs+=4}cur_ptr-=0,cur_ptr&=~(8-1);stackRestore(cur_ptr);wasmTable.get(HEAPU32[(closure>>2)+2]).apply(null,[HEAPU32[(closure>>2)+1],ret_ptr,args_ptr,HEAPU32[(closure>>2)+3]]);stackRestore(orig_stack_ptr);if(!ret_by_arg){switch(sig[0]){case"i":return HEAPU32[(ret_ptr>>2)+0];case"j":return BigInt(HEAPU32[(ret_ptr>>2)+0*2])|BigInt(HEAPU32[(ret_ptr>>2)+0*2+1])<>3)+0];case"f":return HEAPF32[(ret_ptr>>2)+0]}}}try{var wasm_trampoline=convertJsFunctionToWasm(trampoline,sig)}catch(e){return FFI_BAD_TYPEDEF}wasmTable.set(codeloc,wasm_trampoline);HEAPU32[(closure>>2)+1]=cif;HEAPU32[(closure>>2)+2]=fun;HEAPU32[(closure>>2)+3]=user_data;return 0}function get_async_js_call_done_callback(proxies_id){"use strict";try{let proxies=Hiwire.get_value(proxies_id);return Hiwire.new_value(function(result){let msg="This borrowed proxy was automatically destroyed "+"at the end of an asynchronous function call. Try "+"using create_proxy or create_once_callable.";for(let px of proxies){Module.pyproxy_destroy(px,msg)}if(API.isPyProxy(result)){Module.pyproxy_destroy(result,msg)}})}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function getter_call_trampoline(get,obj,closure){return wasmTable.get(get)(obj,closure)}function hiwire_CallMethod(idobj,name,idargs){"use strict";try{let jsobj=Hiwire.get_value(idobj);let jsname=Hiwire.get_value(name);let jsargs=Hiwire.get_value(idargs);return Hiwire.new_value(jsobj[jsname](...jsargs))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_CallMethodString(idobj,name,idargs){"use strict";try{let jsobj=Hiwire.get_value(idobj);let jsname=UTF8ToString(name);let jsargs=Hiwire.get_value(idargs);return Hiwire.new_value(jsobj[jsname](...jsargs))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_CallMethod_OneArg(idobj,name,idarg){"use strict";try{let jsobj=Hiwire.get_value(idobj);let jsname=Hiwire.get_value(name);let jsarg=Hiwire.get_value(idarg);return Hiwire.new_value(jsobj[jsname](jsarg))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_HasMethod(obj_id,name){"use strict";try{let obj=Hiwire.get_value(obj_id);return obj&&typeof obj[Hiwire.get_value(name)]==="function"}catch(e){return!!0}}function hiwire_assign_from_ptr(idobj,ptr){"use strict";try{let jsobj=Hiwire.get_value(idobj);API.typedArrayAsUint8Array(jsobj).set(Module.HEAPU8.subarray(ptr,ptr+jsobj.byteLength))}catch(e){Module.handle_js_error(e);return-1}return 0}function hiwire_assign_to_ptr(idobj,ptr){"use strict";try{let jsobj=Hiwire.get_value(idobj);Module.HEAPU8.set(API.typedArrayAsUint8Array(jsobj),ptr)}catch(e){Module.handle_js_error(e);return-1}return 0}function hiwire_call(idfunc,idargs){"use strict";try{let jsfunc=Hiwire.get_value(idfunc);let jsargs=Hiwire.get_value(idargs);return Hiwire.new_value(jsfunc(...jsargs))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_call_OneArg(idfunc,idarg){"use strict";try{let jsfunc=Hiwire.get_value(idfunc);let jsarg=Hiwire.get_value(idarg);return Hiwire.new_value(jsfunc(jsarg))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_call_bound(idfunc,idthis,idargs){"use strict";try{let func=Hiwire.get_value(idfunc);let this_;if(idthis===0){this_=null}else{this_=Hiwire.get_value(idthis)}let args=Hiwire.get_value(idargs);return Hiwire.new_value(func.apply(this_,args))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_construct(idobj,idargs){"use strict";try{let jsobj=Hiwire.get_value(idobj);let jsargs=Hiwire.get_value(idargs);return Hiwire.new_value(Reflect.construct(jsobj,jsargs))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_constructor_name(idobj){"use strict";try{return stringToNewUTF8(Hiwire.get_value(idobj).constructor.name)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_decref(idval){Hiwire.decref(idval)}function hiwire_double(val){"use strict";try{return Hiwire.new_value(val)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_equal(ida,idb){"use strict";try{return!!(Hiwire.get_value(ida)===Hiwire.get_value(idb))}catch(e){return!!0}}function hiwire_get_bool(idobj){"use strict";try{let val=Hiwire.get_value(idobj);if(!val){return!!0}if(val.size===0){if(/HTML[A-Za-z]*Element/.test(Object.prototype.toString.call(val))){return!!1}return!!0}if(val.length===0&&JsArray_Check(idobj)){return!!0}if(val.byteLength===0){return!!0}return!!1}catch(e){return!!0}}function hiwire_get_buffer_info(idobj,byteLength_ptr,format_ptr,size_ptr,checked_ptr){let jsobj=Hiwire.get_value(idobj);let byteLength=jsobj.byteLength;let[format_utf8,size,checked]=Module.get_buffer_datatype(jsobj);HEAPU32[(byteLength_ptr>>2)+0]=byteLength;HEAPU32[(format_ptr>>2)+0]=format_utf8;HEAPU32[(size_ptr>>2)+0]=size;HEAPU8[checked_ptr+0]=checked}function hiwire_get_iterator(idobj){"use strict";try{let jsobj=Hiwire.get_value(idobj);return Hiwire.new_value(jsobj[Symbol.iterator]())}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_get_length_helper(idobj){"use strict";try{let val=Hiwire.get_value(idobj);let result;if(typeof val.size==="number"){result=val.size}else if(typeof val.length==="number"){result=val.length}else{return-2}if(result<0){return-3}if(result>2147483647){return-4}return result}catch(e){Module.handle_js_error(e);return-1}return 0}function hiwire_get_length_string(idobj){"use strict";try{const val=Hiwire.get_value(idobj);let result;if(typeof val.size==="number"){result=val.size}else if(typeof val.length==="number"){result=val.length}return stringToNewUTF8(" "+result.toString())}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_greater_than(ida,idb){"use strict";try{return!!(Hiwire.get_value(ida)>Hiwire.get_value(idb))}catch(e){return!!0}}function hiwire_greater_than_equal(ida,idb){"use strict";try{return!!(Hiwire.get_value(ida)>=Hiwire.get_value(idb))}catch(e){return!!0}}function hiwire_has_length(idobj){"use strict";try{let val=Hiwire.get_value(idobj);return typeof val.size==="number"||typeof val.length==="number"&&typeof val!=="function"}catch(e){return!!0}}function hiwire_incref(idval){if(idval&1){Hiwire.incref(idval)}return idval}function hiwire_init(){"use strict";try{let _hiwire={objects:new Map,obj_to_key:new Map,counter:new Uint32Array([1])};Hiwire.UNDEFINED=HEAPU8[_Js_undefined+0];_hiwire.objects.set(Hiwire.UNDEFINED,[undefined,-1]);_hiwire.obj_to_key.set(undefined,Hiwire.UNDEFINED);Hiwire.JSNULL=HEAPU8[_Js_null+0];_hiwire.objects.set(Hiwire.JSNULL,[null,-1]);_hiwire.obj_to_key.set(null,Hiwire.JSNULL);Hiwire.TRUE=HEAPU8[_Js_true+0];_hiwire.objects.set(Hiwire.TRUE,[!!1,-1]);_hiwire.obj_to_key.set(!!1,Hiwire.TRUE);Hiwire.FALSE=HEAPU8[_Js_false+0];_hiwire.objects.set(Hiwire.FALSE,[!!0,-1]);_hiwire.obj_to_key.set(!!0,Hiwire.FALSE);let hiwire_next_permanent=HEAPU8[_Js_novalue]+2;Hiwire.new_value=function(jsval){let idval=_hiwire.obj_to_key.get(jsval);if(idval!==undefined){_hiwire.objects.get(idval)[1]++;return idval}while(_hiwire.objects.has(_hiwire.counter[0])){_hiwire.counter[0]+=2}idval=_hiwire.counter[0];_hiwire.objects.set(idval,[jsval,1]);_hiwire.obj_to_key.set(jsval,idval);_hiwire.counter[0]+=2;return idval};Hiwire.intern_object=function(obj){let id=hiwire_next_permanent;hiwire_next_permanent+=2;_hiwire.objects.set(id,[obj,-1]);return id};Hiwire.num_keys=function(){return Array.from(_hiwire.objects.keys()).filter(x=>x%2).length};Hiwire.get_value=function(idval){if(!idval){API.fail_test=!!1;if(_PyErr_Occurred()){let exc=_wrap_exception();let e=Hiwire.pop_value(exc);console.error(`Pyodide internal error: Argument '${idval}' to hiwire.get_value is falsy. `+"This was probably because the Python error indicator was set when get_value was called. "+"The Python error that caused this was:",e);throw e}else{console.error(`Pyodide internal error: Argument '${idval}' to hiwire.get_value is falsy`+" (but error indicator is not set).");throw new Error(`Pyodide internal error: Argument '${idval}' to hiwire.get_value is falsy`+" (but error indicator is not set).")}}if(!_hiwire.objects.has(idval)){API.fail_test=!!1;console.error(`Pyodide internal error: Undefined id ${idval}`);throw new Error(`Undefined id ${idval}`)}return _hiwire.objects.get(idval)[0]};Hiwire.decref=function(idval){if((idval&1)===0){return}let pair=_hiwire.objects.get(idval);let new_refcnt=--pair[1];if(new_refcnt===0){_hiwire.objects.delete(idval);_hiwire.obj_to_key.delete(pair[0])}};Hiwire.incref=function(idval){if((idval&1)===0){return}_hiwire.objects.get(idval)[1]++};Hiwire.pop_value=function(idval){let result=Hiwire.get_value(idval);Hiwire.decref(idval);return result};Hiwire.isPromise=function(obj){try{return!!obj&&typeof obj.then==="function"}catch(e){return!!0}};API.typedArrayAsUint8Array=function(arg){if(ArrayBuffer.isView(arg)){return new Uint8Array(arg.buffer,arg.byteOffset,arg.byteLength)}else{return new Uint8Array(arg)}};{let dtypes_str=["b","B","h","H","i","I","f","d"].join(String.fromCharCode(0));let dtypes_ptr=stringToNewUTF8(dtypes_str);let dtypes_map={};for(let[idx,val]of Object.entries(dtypes_str)){dtypes_map[val]=dtypes_ptr+Number(idx)}let buffer_datatype_map=new Map([["Int8Array",[dtypes_map["b"],1,!!1]],["Uint8Array",[dtypes_map["B"],1,!!1]],["Uint8ClampedArray",[dtypes_map["B"],1,!!1]],["Int16Array",[dtypes_map["h"],2,!!1]],["Uint16Array",[dtypes_map["H"],2,!!1]],["Int32Array",[dtypes_map["i"],4,!!1]],["Uint32Array",[dtypes_map["I"],4,!!1]],["Float32Array",[dtypes_map["f"],4,!!1]],["Float64Array",[dtypes_map["d"],8,!!1]],["DataView",[dtypes_map["B"],1,!!0]],["ArrayBuffer",[dtypes_map["B"],1,!!0]]]);Module.get_buffer_datatype=function(jsobj){return buffer_datatype_map.get(jsobj.constructor.name)||[0,0,!!0]}}if(globalThis.BigInt){Module.BigInt=BigInt}else{Module.BigInt=Number}return 0}catch(e){Module.handle_js_error(e);return-1}return 0}function hiwire_int(val){"use strict";try{return Hiwire.new_value(val)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_int_from_digits(digits,ndigits){"use strict";try{let result=BigInt(0);for(let i=0;i>2)+i])<>2)+ndigits-1]&2147483648)<>2)+0]=result_id;return done}catch(e){Module.handle_js_error(e);return-1}return 0}function hiwire_not_equal(ida,idb){"use strict";try{return!!(Hiwire.get_value(ida)!==Hiwire.get_value(idb))}catch(e){return!!0}}function hiwire_read_from_file(idobj,fd){"use strict";try{let jsobj=Hiwire.get_value(idobj);let uint8_buffer=API.typedArrayAsUint8Array(jsobj);let stream=Module.FS.streams[fd];Module.FS.read(stream,uint8_buffer,0,uint8_buffer.byteLength)}catch(e){Module.handle_js_error(e);return-1}return 0}function hiwire_resolve_promise(idobj){"use strict";try{let obj=Hiwire.get_value(idobj);let result=Promise.resolve(obj);return Hiwire.new_value(result)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_string_ascii(ptr){"use strict";try{return Hiwire.new_value(AsciiToString(ptr))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_string_ucs1(ptr,len){"use strict";try{let jsstr="";for(let i=0;i>1)+i])}return Hiwire.new_value(jsstr)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_string_ucs4(ptr,len){"use strict";try{let jsstr="";for(let i=0;i>2)+i])}return Hiwire.new_value(jsstr)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_string_utf8(ptr){"use strict";try{return Hiwire.new_value(UTF8ToString(ptr))}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_subarray(idarr,start,end){"use strict";try{let jsarr=Hiwire.get_value(idarr);let jssub=jsarr.subarray(start,end);return Hiwire.new_value(jssub)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_throw_error(iderr){throw Hiwire.pop_value(iderr)}function hiwire_to_bool(val){return!!Hiwire.get_value(val)}function hiwire_to_string(idobj){"use strict";try{return Hiwire.new_value(Hiwire.get_value(idobj).toString())}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function hiwire_typeof(idobj){return Hiwire.new_value(typeof Hiwire.get_value(idobj))}function hiwire_write_to_file(idobj,fd){"use strict";try{let jsobj=Hiwire.get_value(idobj);let uint8_buffer=API.typedArrayAsUint8Array(jsobj);let stream=Module.FS.streams[fd];Module.FS.write(stream,uint8_buffer,0,uint8_buffer.byteLength)}catch(e){Module.handle_js_error(e);return-1}return 0}function js2python(id){"use strict";try{let value=Hiwire.get_value(id);let result=Module.js2python_convertImmutable(value);if(result!==undefined){return result}return _JsProxy_create(id)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function js2python_convert(id,depth,default_converter){"use strict";try{let defaultConverter=default_converter?Module.hiwire.get_value(default_converter):undefined;return Module.js2python_convert(id,{depth:depth,defaultConverter:defaultConverter})}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function js2python_init(){"use strict";try{{0;let PropagateError=Module._PropagatePythonError;function js2python_string(value){let max_code_point=0;let num_code_points=0;for(let c of value){num_code_points++;let code_point=c.codePointAt(0);max_code_point=code_point>max_code_point?code_point:max_code_point}let result=_PyUnicode_New(num_code_points,max_code_point);if(result===0){throw new PropagateError}let ptr=_PyUnicode_Data(result);if(max_code_point>65535){for(let c of value){HEAPU32[ptr/4]=c.codePointAt(0);ptr+=4}}else if(max_code_point>255){for(let c of value){HEAPU16[ptr/2]=c.codePointAt(0);ptr+=2}}else{for(let c of value){HEAPU8[ptr]=c.codePointAt(0);ptr+=1}}return result}function js2python_bigint(value){let value_orig=value;let length=0;if(value<0){value=-value}value<<=BigInt(1);while(value){length++;value>>=BigInt(32)}let stackTop=stackSave();let ptr=stackAlloc(length*4);value=value_orig;for(let i=0;i>2)+i]=Number(value&BigInt(4294967295));value>>=BigInt(32)}let result=__PyLong_FromByteArray(ptr,length*4,!!1,!!1);stackRestore(stackTop);return result}function js2python_convertImmutable(value){let result=js2python_convertImmutableInner(value);if(result===0){throw new PropagateError}return result}Module.js2python_convertImmutable=js2python_convertImmutable;function js2python_convertImmutableInner(value){let type=typeof value;if(type==="string"){return js2python_string(value)}else if(type==="number"){if(Number.isSafeInteger(value)){return _PyLong_FromDouble(value)}else{return _PyFloat_FromDouble(value)}}else if(type==="bigint"){return js2python_bigint(value)}else if(value===undefined||value===null){return __js2python_none()}else if(value===!!1){return __js2python_true()}else if(value===!!0){return __js2python_false()}else if(API.isPyProxy(value)){return __js2python_pyproxy(Module.PyProxy_getPtr(value))}return undefined}function js2python_convertList(obj,context){let list=_PyList_New(obj.length);if(list===0){return 0}let entryid=0;let item=0;try{context.cache.set(obj,list);for(let i=0;i2){throw new Error("Expected format string to have length <= 2, "+`got '${formatStr}'.`+errorMessage)}let formatChar=formatStr.slice(-1);let alignChar=formatStr.slice(0,-1);let bigEndian;switch(alignChar){case"!":case">":bigEndian=!!1;break;case"<":case"@":case"=":case"":bigEndian=!!0;break;default:throw new Error(`Unrecognized alignment character ${alignChar}.`+errorMessage)}let arrayType;switch(formatChar){case"b":arrayType=Int8Array;break;case"s":case"p":case"c":case"B":case"?":arrayType=Uint8Array;break;case"h":arrayType=Int16Array;break;case"H":arrayType=Uint16Array;break;case"i":case"l":case"n":arrayType=Int32Array;break;case"I":case"L":case"N":case"P":arrayType=Uint32Array;break;case"q":if(globalThis.BigInt64Array===undefined){throw new Error("BigInt64Array is not supported on this browser."+errorMessage)}arrayType=BigInt64Array;break;case"Q":if(globalThis.BigUint64Array===undefined){throw new Error("BigUint64Array is not supported on this browser."+errorMessage)}arrayType=BigUint64Array;break;case"f":arrayType=Float32Array;break;case"d":arrayType=Float64Array;break;case"e":throw new Error("Javascript has no Float16 support.");default:throw new Error(`Unrecognized format character '${formatChar}'.`+errorMessage)}return[arrayType,bigEndian]};Module.python2js_buffer_1d_contiguous=function(ptr,stride,n){"use strict";let byteLength=stride*n;return HEAP8.slice(ptr,ptr+byteLength).buffer};Module.python2js_buffer_1d_noncontiguous=function(ptr,stride,suboffset,n,itemsize){"use strict";let byteLength=itemsize*n;let buffer=new Uint8Array(byteLength);for(let i=0;i=0){curptr=HEAPU32[(curptr>>2)+0]+suboffset}buffer.set(HEAP8.subarray(curptr,curptr+itemsize),i*itemsize)}return buffer.buffer};Module._python2js_buffer_recursive=function(ptr,curdim,bufferData){"use strict";let n=HEAPU32[(bufferData.shape>>2)+curdim];let stride=HEAP32[(bufferData.strides>>2)+curdim];let suboffset=-1;if(bufferData.suboffsets!==0){suboffset=HEAP32[(bufferData.suboffsets>>2)+curdim]}if(curdim===bufferData.ndim-1){let arraybuffer;if(stride===bufferData.itemsize&&suboffset<0){arraybuffer=Module.python2js_buffer_1d_contiguous(ptr,stride,n)}else{arraybuffer=Module.python2js_buffer_1d_noncontiguous(ptr,stride,suboffset,n,bufferData.itemsize)}return bufferData.converter(arraybuffer)}let result=[];for(let i=0;i=0){curptr=HEAPU32[(curptr>>2)+0]+suboffset}result.push(Module._python2js_buffer_recursive(curPtr,curdim+1,bufferData))}return result};Module.get_converter=function(format,itemsize){"use strict";let formatStr=UTF8ToString(format);let[ArrayType,bigEndian]=Module.processBufferFormatString(formatStr);let formatChar=formatStr.slice(-1);switch(formatChar){case"s":let decoder=new TextDecoder("utf8",{ignoreBOM:!!1});return buff=>decoder.decode(buff);case"?":return buff=>Array.from(new Uint8Array(buff),x=>!!x)}if(!bigEndian){return buff=>new ArrayType(buff)}let getFuncName;let setFuncName;switch(itemsize){case 2:getFuncName="getUint16";setFuncName="setUint16";break;case 4:getFuncName="getUint32";setFuncName="setUint32";break;case 8:getFuncName="getFloat64";setFuncName="setFloat64";break;default:throw new Error(`Unexpected size ${itemsize}`)}function swapFunc(buff){let dataview=new DataView(buff);let getFunc=dataview[getFuncName].bind(dataview);let setFunc=dataview[setFuncName].bind(dataview);for(let byte=0;bytenew ArrayType(swapFunc(buff))}}return 0}catch(e){Module.handle_js_error(e);return-1}return 0}function python2js_custom__create_jscontext(context,idcache,dict_converter,default_converter){"use strict";try{let jscontext={};if(dict_converter!==0){jscontext.dict_converter=Hiwire.get_value(dict_converter)}if(default_converter!==0){jscontext.default_converter=Hiwire.get_value(default_converter);jscontext.cacheConversion=function(input,output){if(!API.isPyProxy(input)){throw new TypeError("The first argument to cacheConversion must be a PyProxy.")}let input_ptr=Module.PyProxy_getPtr(input);let output_key=Hiwire.new_value(output);Hiwire.get_value(idcache).set(input_ptr,output_key)};jscontext.converter=function(x){if(!API.isPyProxy(x)){return x}let ptr=Module.PyProxy_getPtr(x);let res=__python2js(context,ptr);return Hiwire.pop_value(res)}}return Hiwire.new_value(jscontext)}catch(e){Module.handle_js_error(e);return 0}throw new Error("Assertion error: control reached end of function without return")}function setter_call_trampoline(set,obj,value,closure){return wasmTable.get(set)(obj,value,closure)}function unbox_small_structs(type_ptr){var type_id=HEAPU16[(type_ptr+6>>1)+0];while(type_id===13){var elements=HEAPU32[(type_ptr+8>>2)+0];var first_element=HEAPU32[(elements>>2)+0];if(first_element===0){type_id=0;break}else if(HEAPU32[(elements>>2)+1]===0){type_ptr=first_element;type_id=HEAPU16[(first_element+6>>1)+0]}else{break}}return[type_ptr,type_id]}function _emscripten_set_main_loop_timing(mode,value){Browser.mainLoop.timingMode=mode;Browser.mainLoop.timingValue=value;if(!Browser.mainLoop.func){return 1}if(!Browser.mainLoop.running){Browser.mainLoop.running=true}if(mode==0){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setTimeout(){var timeUntilNextTick=Math.max(0,Browser.mainLoop.tickStartTime+value-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,timeUntilNextTick)};Browser.mainLoop.method="timeout"}else if(mode==1){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_rAF(){Browser.requestAnimationFrame(Browser.mainLoop.runner)};Browser.mainLoop.method="rAF"}else if(mode==2){if(typeof setImmediate=="undefined"){var setImmediates=[];var emscriptenMainLoopMessageId="setimmediate";var Browser_setImmediate_messageHandler=function(event){if(event.data===emscriptenMainLoopMessageId||event.data.target===emscriptenMainLoopMessageId){event.stopPropagation();setImmediates.shift()()}};addEventListener("message",Browser_setImmediate_messageHandler,true);setImmediate=function Browser_emulated_setImmediate(func){setImmediates.push(func);if(ENVIRONMENT_IS_WORKER){if(Module["setImmediates"]===undefined)Module["setImmediates"]=[];Module["setImmediates"].push(func);postMessage({target:emscriptenMainLoopMessageId})}else postMessage(emscriptenMainLoopMessageId,"*")}}Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setImmediate(){setImmediate(Browser.mainLoop.runner)};Browser.mainLoop.method="immediate"}return 0}Module["_emscripten_set_main_loop_timing"]=_emscripten_set_main_loop_timing;_emscripten_set_main_loop_timing.sig="iii";var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=()=>{var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else _emscripten_get_now=()=>performance.now();Module["_emscripten_get_now"]=_emscripten_get_now;_emscripten_get_now.sig="d";function runtimeKeepalivePush(){}Module["runtimeKeepalivePush"]=runtimeKeepalivePush;runtimeKeepalivePush.sig="v";function _exit(status){exit(status)}Module["_exit"]=_exit;_exit.sig="vi";function handleException(e){if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)}Module["handleException"]=handleException;function maybeExit(){}Module["maybeExit"]=maybeExit;function setMainLoop(browserIterationFunc,fps,simulateInfiniteLoop,arg,noSetTiming){assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.");Browser.mainLoop.func=browserIterationFunc;Browser.mainLoop.arg=arg;var thisMainLoopId=Browser.mainLoop.currentlyRunningMainloop;function checkIsRunning(){if(thisMainLoopId0){var start=Date.now();var blocker=Browser.mainLoop.queue.shift();blocker.func(blocker.arg);if(Browser.mainLoop.remainingBlockers){var remaining=Browser.mainLoop.remainingBlockers;var next=remaining%1==0?remaining-1:Math.floor(remaining);if(blocker.counted){Browser.mainLoop.remainingBlockers=next}else{next=next+.5;Browser.mainLoop.remainingBlockers=(8*remaining+next)/9}}out('main loop blocker "'+blocker.name+'" took '+(Date.now()-start)+" ms");Browser.mainLoop.updateStatus();if(!checkIsRunning())return;setTimeout(Browser.mainLoop.runner,0);return}if(!checkIsRunning())return;Browser.mainLoop.currentFrameNumber=Browser.mainLoop.currentFrameNumber+1|0;if(Browser.mainLoop.timingMode==1&&Browser.mainLoop.timingValue>1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else if(Browser.mainLoop.timingMode==0){Browser.mainLoop.tickStartTime=_emscripten_get_now()}Browser.mainLoop.runIter(browserIterationFunc);if(!checkIsRunning())return;if(typeof SDL=="object"&&SDL.audio&&SDL.audio.queueNewAudioData)SDL.audio.queueNewAudioData();Browser.mainLoop.scheduler()};if(!noSetTiming){if(fps&&fps>0)_emscripten_set_main_loop_timing(0,1e3/fps);else _emscripten_set_main_loop_timing(1,1);Browser.mainLoop.scheduler()}if(simulateInfiniteLoop){throw"unwind"}}Module["setMainLoop"]=setMainLoop;function callUserCallback(func,synchronous){if(ABORT){return}if(synchronous){func();return}try{func()}catch(e){handleException(e)}}Module["callUserCallback"]=callUserCallback;function runtimeKeepalivePop(){}Module["runtimeKeepalivePop"]=runtimeKeepalivePop;runtimeKeepalivePop.sig="v";function safeSetTimeout(func,timeout){return setTimeout(function(){callUserCallback(func)},timeout)}Module["safeSetTimeout"]=safeSetTimeout;var Browser={mainLoop:{running:false,scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null;Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var timingMode=Browser.mainLoop.timingMode;var timingValue=Browser.mainLoop.timingValue;var func=Browser.mainLoop.func;Browser.mainLoop.func=null;setMainLoop(func,0,false,Browser.mainLoop.arg,true);_emscripten_set_main_loop_timing(timingMode,timingValue);Browser.mainLoop.scheduler()},updateStatus:function(){if(Module["setStatus"]){var message=Module["statusMessage"]||"Please wait...";var remaining=Browser.mainLoop.remainingBlockers;var expected=Browser.mainLoop.expectedBlockers;if(remaining){if(remaining{assert(img.complete,"Image "+name+" could not be decoded");var canvas=document.createElement("canvas");canvas.width=img.width;canvas.height=img.height;var ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);preloadedImages[name]=canvas;Browser.URLObject.revokeObjectURL(url);if(onload)onload(byteArray)};img.onerror=event=>{out("Image "+url+" could not be decoded");if(onerror)onerror()};img.src=url};Module["preloadPlugins"].push(imagePlugin);var audioPlugin={};audioPlugin["canHandle"]=function audioPlugin_canHandle(name){return!Module.noAudioDecoding&&name.substr(-4)in{".ogg":1,".wav":1,".mp3":1}};audioPlugin["handle"]=function audioPlugin_handle(byteArray,name,onload,onerror){var done=false;function finish(audio){if(done)return;done=true;preloadedAudios[name]=audio;if(onload)onload(byteArray)}function fail(){if(done)return;done=true;preloadedAudios[name]=new Audio;if(onerror)onerror()}if(Browser.hasBlobConstructor){try{var b=new Blob([byteArray],{type:Browser.getMimetype(name)})}catch(e){return fail()}var url=Browser.URLObject.createObjectURL(b);var audio=new Audio;audio.addEventListener("canplaythrough",function(){finish(audio)},false);audio.onerror=function audio_onerror(event){if(done)return;out("warning: browser could not fully decode audio "+name+", trying slower base64 approach");function encode64(data){var BASE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var PAD="=";var ret="";var leftchar=0;var leftbits=0;for(var i=0;i=6){var curr=leftchar>>leftbits-6&63;leftbits-=6;ret+=BASE[curr]}}if(leftbits==2){ret+=BASE[(leftchar&3)<<4];ret+=PAD+PAD}else if(leftbits==4){ret+=BASE[(leftchar&15)<<2];ret+=PAD}return ret}audio.src="data:audio/x-"+name.substr(-3)+";base64,"+encode64(byteArray);finish(audio)};audio.src=url;safeSetTimeout(function(){finish(audio)},1e4)}else{return fail()}};Module["preloadPlugins"].push(audioPlugin);var wasmPlugin={"asyncWasmLoadPromise":new Promise(function(resolve,reject){return resolve()}),"canHandle":function(name){return!Module.noWasmDecoding&&name.endsWith(".so")},"handle":function(byteArray,name,onload,onerror){wasmPlugin["asyncWasmLoadPromise"]=wasmPlugin["asyncWasmLoadPromise"].then(function(){return loadWebAssemblyModule(byteArray,{loadAsync:true,nodelete:true})}).then(function(module){preloadedWasm[name]=module;onload()},function(err){console.warn("Couldn't instantiate wasm: "+name+" '"+err+"'");onerror()})}};Module["preloadPlugins"].push(wasmPlugin);function pointerLockChange(){Browser.pointerLock=document["pointerLockElement"]===Module["canvas"]||document["mozPointerLockElement"]===Module["canvas"]||document["webkitPointerLockElement"]===Module["canvas"]||document["msPointerLockElement"]===Module["canvas"]}var canvas=Module["canvas"];if(canvas){canvas.requestPointerLock=canvas["requestPointerLock"]||canvas["mozRequestPointerLock"]||canvas["webkitRequestPointerLock"]||canvas["msRequestPointerLock"]||function(){};canvas.exitPointerLock=document["exitPointerLock"]||document["mozExitPointerLock"]||document["webkitExitPointerLock"]||document["msExitPointerLock"]||function(){};canvas.exitPointerLock=canvas.exitPointerLock.bind(document);document.addEventListener("pointerlockchange",pointerLockChange,false);document.addEventListener("mozpointerlockchange",pointerLockChange,false);document.addEventListener("webkitpointerlockchange",pointerLockChange,false);document.addEventListener("mspointerlockchange",pointerLockChange,false);if(Module["elementPointerLock"]){canvas.addEventListener("click",function(ev){if(!Browser.pointerLock&&Module["canvas"].requestPointerLock){Module["canvas"].requestPointerLock();ev.preventDefault()}},false)}}},handledByPreloadPlugin:function(byteArray,fullname,finish,onerror){Browser.init();var handled=false;Module["preloadPlugins"].forEach(function(plugin){if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled},createContext:function(canvas,useWebGL,setInModule,webGLContextAttributes){if(useWebGL&&Module.ctx&&canvas==Module.canvas)return Module.ctx;var ctx;var contextHandle;if(useWebGL){var contextAttributes={antialias:false,alpha:false,majorVersion:1};if(webGLContextAttributes){for(var attribute in webGLContextAttributes){contextAttributes[attribute]=webGLContextAttributes[attribute]}}if(typeof GL!="undefined"){contextHandle=GL.createContext(canvas,contextAttributes);if(contextHandle){ctx=GL.getContext(contextHandle).GLctx}}}else{ctx=canvas.getContext("2d")}if(!ctx)return null;if(setInModule){if(!useWebGL)assert(typeof GLctx=="undefined","cannot set in module if GLctx is used, but we are a non-GL context that would replace it");Module.ctx=ctx;if(useWebGL)GL.makeContextCurrent(contextHandle);Module.useWebGL=useWebGL;Browser.moduleContextCreatedCallbacks.forEach(function(callback){callback()});Browser.init()}return ctx},destroyContext:function(canvas,useWebGL,setInModule){},fullscreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullscreen:function(lockPointer,resizeCanvas){Browser.lockPointer=lockPointer;Browser.resizeCanvas=resizeCanvas;if(typeof Browser.lockPointer=="undefined")Browser.lockPointer=true;if(typeof Browser.resizeCanvas=="undefined")Browser.resizeCanvas=false;var canvas=Module["canvas"];function fullscreenChange(){Browser.isFullscreen=false;var canvasContainer=canvas.parentNode;if((document["fullscreenElement"]||document["mozFullScreenElement"]||document["msFullscreenElement"]||document["webkitFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvasContainer){canvas.exitFullscreen=Browser.exitFullscreen;if(Browser.lockPointer)canvas.requestPointerLock();Browser.isFullscreen=true;if(Browser.resizeCanvas){Browser.setFullscreenCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}else{canvasContainer.parentNode.insertBefore(canvas,canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if(Browser.resizeCanvas){Browser.setWindowedCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}if(Module["onFullScreen"])Module["onFullScreen"](Browser.isFullscreen);if(Module["onFullscreen"])Module["onFullscreen"](Browser.isFullscreen)}if(!Browser.fullscreenHandlersInstalled){Browser.fullscreenHandlersInstalled=true;document.addEventListener("fullscreenchange",fullscreenChange,false);document.addEventListener("mozfullscreenchange",fullscreenChange,false);document.addEventListener("webkitfullscreenchange",fullscreenChange,false);document.addEventListener("MSFullscreenChange",fullscreenChange,false)}var canvasContainer=document.createElement("div");canvas.parentNode.insertBefore(canvasContainer,canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullscreen=canvasContainer["requestFullscreen"]||canvasContainer["mozRequestFullScreen"]||canvasContainer["msRequestFullscreen"]||(canvasContainer["webkitRequestFullscreen"]?function(){canvasContainer["webkitRequestFullscreen"](Element["ALLOW_KEYBOARD_INPUT"])}:null)||(canvasContainer["webkitRequestFullScreen"]?function(){canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"])}:null);canvasContainer.requestFullscreen()},exitFullscreen:function(){if(!Browser.isFullscreen){return false}var CFS=document["exitFullscreen"]||document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["msExitFullscreen"]||document["webkitCancelFullScreen"]||function(){};CFS.apply(document,[]);return true},nextRAF:0,fakeRequestAnimationFrame:function(func){var now=Date.now();if(Browser.nextRAF===0){Browser.nextRAF=now+1e3/60}else{while(now+2>=Browser.nextRAF){Browser.nextRAF+=1e3/60}}var delay=Math.max(Browser.nextRAF-now,0);setTimeout(func,delay)},requestAnimationFrame:function(func){if(typeof requestAnimationFrame=="function"){requestAnimationFrame(func);return}var RAF=Browser.fakeRequestAnimationFrame;RAF(func)},safeSetTimeout:function(func){return safeSetTimeout(func)},safeRequestAnimationFrame:function(func){return Browser.requestAnimationFrame(function(){callUserCallback(func)})},getMimetype:function(name){return{"jpg":"image/jpeg","jpeg":"image/jpeg","png":"image/png","bmp":"image/bmp","ogg":"audio/ogg","wav":"audio/wav","mp3":"audio/mpeg"}[name.substr(name.lastIndexOf(".")+1)]},getUserMedia:function(func){if(!window.getUserMedia){window.getUserMedia=navigator["getUserMedia"]||navigator["mozGetUserMedia"]}window.getUserMedia(func)},getMovementX:function(event){return event["movementX"]||event["mozMovementX"]||event["webkitMovementX"]||0},getMovementY:function(event){return event["movementY"]||event["mozMovementY"]||event["webkitMovementY"]||0},getMouseWheelDelta:function(event){var delta=0;switch(event.type){case"DOMMouseScroll":delta=event.detail/3;break;case"mousewheel":delta=event.wheelDelta/120;break;case"wheel":delta=event.deltaY;switch(event.deltaMode){case 0:delta/=100;break;case 1:delta/=3;break;case 2:delta*=80;break;default:throw"unrecognized mouse wheel delta mode: "+event.deltaMode}break;default:throw"unrecognized mouse wheel event: "+event.type}return delta},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(event){if(Browser.pointerLock){if(event.type!="mousemove"&&"mozMovementX"in event){Browser.mouseMovementX=Browser.mouseMovementY=0}else{Browser.mouseMovementX=Browser.getMovementX(event);Browser.mouseMovementY=Browser.getMovementY(event)}if(typeof SDL!="undefined"){Browser.mouseX=SDL.mouseX+Browser.mouseMovementX;Browser.mouseY=SDL.mouseY+Browser.mouseMovementY}else{Browser.mouseX+=Browser.mouseMovementX;Browser.mouseY+=Browser.mouseMovementY}}else{var rect=Module["canvas"].getBoundingClientRect();var cw=Module["canvas"].width;var ch=Module["canvas"].height;var scrollX=typeof window.scrollX!="undefined"?window.scrollX:window.pageXOffset;var scrollY=typeof window.scrollY!="undefined"?window.scrollY:window.pageYOffset;if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchmove"){var touch=event.touch;if(touch===undefined){return}var adjustedX=touch.pageX-(scrollX+rect.left);var adjustedY=touch.pageY-(scrollY+rect.top);adjustedX=adjustedX*(cw/rect.width);adjustedY=adjustedY*(ch/rect.height);var coords={x:adjustedX,y:adjustedY};if(event.type==="touchstart"){Browser.lastTouches[touch.identifier]=coords;Browser.touches[touch.identifier]=coords}else if(event.type==="touchend"||event.type==="touchmove"){var last=Browser.touches[touch.identifier];if(!last)last=coords;Browser.lastTouches[touch.identifier]=last;Browser.touches[touch.identifier]=coords}return}var x=event.pageX-(scrollX+rect.left);var y=event.pageY-(scrollY+rect.top);x=x*(cw/rect.width);y=y*(ch/rect.height);Browser.mouseMovementX=x-Browser.mouseX;Browser.mouseMovementY=y-Browser.mouseY;Browser.mouseX=x;Browser.mouseY=y}},resizeListeners:[],updateResizeListeners:function(){var canvas=Module["canvas"];Browser.resizeListeners.forEach(function(listener){listener(canvas.width,canvas.height)})},setCanvasSize:function(width,height,noUpdates){var canvas=Module["canvas"];Browser.updateCanvasDimensions(canvas,width,height);if(!noUpdates)Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags|8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags&~8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},updateCanvasDimensions:function(canvas,wNative,hNative){if(wNative&&hNative){canvas.widthNative=wNative;canvas.heightNative=hNative}else{wNative=canvas.widthNative;hNative=canvas.heightNative}var w=wNative;var h=hNative;if(Module["forcedAspectRatio"]&&Module["forcedAspectRatio"]>0){if(w/h0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func=="number"){if(callback.arg===undefined){getWasmTableEntry(func)()}else{getWasmTableEntry(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}Module["callRuntimeCallbacks"]=callRuntimeCallbacks;function withStackSave(f){var stack=stackSave();var ret=f();stackRestore(stack);return ret}Module["withStackSave"]=withStackSave;function demangle(func){demangle.recursionGuard=(demangle.recursionGuard|0)+1;if(demangle.recursionGuard>1)return func;var __cxa_demangle_func=Module["___cxa_demangle"]||Module["__cxa_demangle"];assert(__cxa_demangle_func);return withStackSave(function(){try{var s=func;if(s.startsWith("__Z"))s=s.substr(1);var len=lengthBytesUTF8(s)+1;var buf=stackAlloc(len);stringToUTF8(s,buf,len);var status=stackAlloc(4);var ret=__cxa_demangle_func(buf,0,0,status);if(HEAP32[status>>2]===0&&ret){return UTF8ToString(ret)}}catch(e){}finally{_free(ret);if(demangle.recursionGuard<2)--demangle.recursionGuard}return func})}Module["demangle"]=demangle;function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}Module["demangleAll"]=demangleAll;function getDylinkMetadata(binary){var offset=0;var end=0;function getU8(){return binary[offset++]}function getLEB(){var ret=0;var mul=1;while(1){var byte=binary[offset++];ret+=(byte&127)*mul;mul*=128;if(!(byte&128))break}return ret}function getString(){var len=getLEB();offset+=len;return UTF8ArrayToString(binary,offset-len,len)}function failIf(condition,message){if(condition)throw new Error(message)}var name="dylink.0";if(binary instanceof WebAssembly.Module){var dylinkSection=WebAssembly.Module.customSections(binary,name);if(dylinkSection.length===0){name="dylink";dylinkSection=WebAssembly.Module.customSections(binary,name)}failIf(dylinkSection.length===0,"need dylink section");binary=new Uint8Array(dylinkSection[0]);end=binary.length}else{var int32View=new Uint32Array(new Uint8Array(binary.subarray(0,24)).buffer);var magicNumberFound=int32View[0]==1836278016;failIf(!magicNumberFound,"need to see wasm magic number");failIf(binary[8]!==0,"need the dylink section to be first");offset=9;var section_size=getLEB();end=offset+section_size;name=getString()}var customSection={neededDynlibs:[],tlsExports:new Set,weakImports:new Set};if(name=="dylink"){customSection.memorySize=getLEB();customSection.memoryAlign=getLEB();customSection.tableSize=getLEB();customSection.tableAlign=getLEB();var neededDynlibsCount=getLEB();for(var i=0;i>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return Number(HEAP64[ptr>>3]);case"float":return HEAPF32[ptr>>2];case"double":return Number(HEAPF64[ptr>>3]);default:abort("invalid type for getValue: "+type)}return null}Module["getValue"]=getValue;var wasmTableMirror=[];Module["wasmTableMirror"]=wasmTableMirror;function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}Module["getWasmTableEntry"]=getWasmTableEntry;function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}Module["jsStackTrace"]=jsStackTrace;function asmjsMangle(x){var unmangledSymbols=["stackAlloc","stackSave","stackRestore"];return x.indexOf("dynCall_")==0||unmangledSymbols.includes(x)?x:"_"+x}Module["asmjsMangle"]=asmjsMangle;function mergeLibSymbols(exports,libName){for(var sym in exports){if(!exports.hasOwnProperty(sym)){continue}if(!asmLibraryArg.hasOwnProperty(sym)){asmLibraryArg[sym]=exports[sym]}var module_sym=asmjsMangle(sym);if(!Module.hasOwnProperty(module_sym)){Module[module_sym]=exports[sym]}if(sym=="__main_argc_argv"){Module["_main"]=exports[sym]}}}Module["mergeLibSymbols"]=mergeLibSymbols;var LDSO={loadedLibsByName:{},loadedLibsByHandle:{}};Module["LDSO"]=LDSO;function dynCall(sig,ptr,args){return getWasmTableEntry(ptr).apply(null,args)}Module["dynCall"]=dynCall;function createInvokeFunction(sig){return function(){var sp=stackSave();try{return dynCall(sig,arguments[0],Array.prototype.slice.call(arguments,1))}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}}Module["createInvokeFunction"]=createInvokeFunction;var ___heap_base=8480480;Module["___heap_base"]=___heap_base;function getMemory(size){if(runtimeInitialized)return _malloc(size);var ret=___heap_base;var end=ret+size+15&-16;___heap_base=end;GOT["__heap_base"].value=end;return ret}Module["getMemory"]=getMemory;function isInternalSym(symName){return["__cpp_exception","__c_longjmp","__wasm_apply_data_relocs","__dso_handle","__tls_size","__tls_align","__set_stack_limits","_emscripten_tls_init","__wasm_init_tls","__wasm_call_ctors"].includes(symName)}Module["isInternalSym"]=isInternalSym;function updateGOT(exports,replace){for(var symName in exports){if(isInternalSym(symName)){continue}var value=exports[symName];if(!GOT[symName]){GOT[symName]=new WebAssembly.Global({"value":"i32","mutable":true})}if(replace||GOT[symName].value==0){if(typeof value=="function"){GOT[symName].value=addFunction(value)}else if(typeof value=="number"){GOT[symName].value=value}else if(typeof value=="bigint"){GOT[symName].value=Number(value)}else{err("unhandled export type for `"+symName+"`: "+typeof value)}}}}Module["updateGOT"]=updateGOT;function relocateExports(exports,memoryBase,replace){var relocated={};for(var e in exports){var value=exports[e];if(typeof value=="object"){value=value.value}if(typeof value=="number"){value+=memoryBase}relocated[e]=value}updateGOT(relocated,replace);return relocated}Module["relocateExports"]=relocateExports;function resolveGlobalSymbol(symName,direct){var sym;if(!sym){sym=asmLibraryArg[symName];if(sym&&sym.stub)sym=undefined}if(!sym){sym=Module[asmjsMangle(symName)]}if(!sym&&symName.startsWith("invoke_")){sym=createInvokeFunction(symName.split("_")[1])}if(!sym&&symName.startsWith("__cxa_find_matching_catch")){sym=Module["___cxa_find_matching_catch"]}return sym}Module["resolveGlobalSymbol"]=resolveGlobalSymbol;function alignMemory(size,alignment){return Math.ceil(size/alignment)*alignment}Module["alignMemory"]=alignMemory;function zeroMemory(address,size){HEAPU8.fill(0,address,address+size)}Module["zeroMemory"]=zeroMemory;function loadWebAssemblyModule(binary,flags,handle){var metadata=getDylinkMetadata(binary);CurrentModuleWeakSymbols=metadata.weakImports;function loadModule(){var needsAllocation=!handle||!HEAP8[handle+24>>0];if(needsAllocation){var memAlign=Math.pow(2,metadata.memoryAlign);memAlign=Math.max(memAlign,STACK_ALIGN);var memoryBase=metadata.memorySize?alignMemory(getMemory(metadata.memorySize+memAlign),memAlign):0;var tableBase=metadata.tableSize?wasmTable.length:0;if(handle){HEAP8[handle+24>>0]=1;HEAPU32[handle+28>>2]=memoryBase;HEAP32[handle+32>>2]=metadata.memorySize;HEAPU32[handle+36>>2]=tableBase;HEAP32[handle+40>>2]=metadata.tableSize}}else{memoryBase=HEAPU32[handle+28>>2];tableBase=HEAPU32[handle+36>>2]}var tableGrowthNeeded=tableBase+metadata.tableSize-wasmTable.length;if(tableGrowthNeeded>0){wasmTable.grow(tableGrowthNeeded)}var moduleExports;function resolveSymbol(sym){var resolved=resolveGlobalSymbol(sym,false);if(!resolved){resolved=moduleExports[sym]}return resolved}var proxyHandler={"get":function(stubs,prop){switch(prop){case"__memory_base":return memoryBase;case"__table_base":return tableBase}if(prop in asmLibraryArg){return asmLibraryArg[prop]}if(!(prop in stubs)){var resolved;stubs[prop]=function(){if(!resolved)resolved=resolveSymbol(prop);return resolved.apply(null,arguments)}}return stubs[prop]}};var proxy=new Proxy({},proxyHandler);var info={"GOT.mem":new Proxy({},GOTHandler),"GOT.func":new Proxy({},GOTHandler),"env":proxy,wasi_snapshot_preview1:proxy};function postInstantiation(instance){updateTableMap(tableBase,metadata.tableSize);moduleExports=relocateExports(instance.exports,memoryBase);if(!flags.allowUndefined){reportUndefinedSymbols()}var init=moduleExports["__wasm_call_ctors"];if(init){if(runtimeInitialized){init()}else{__ATINIT__.push(init)}}return moduleExports}if(flags.loadAsync){if(binary instanceof WebAssembly.Module){var instance=new WebAssembly.Instance(binary,info);return Promise.resolve(postInstantiation(instance))}return WebAssembly.instantiate(binary,info).then(function(result){return postInstantiation(result.instance)})}var module=binary instanceof WebAssembly.Module?binary:new WebAssembly.Module(binary);var instance=new WebAssembly.Instance(module,info);return postInstantiation(instance)}if(flags.loadAsync){return metadata.neededDynlibs.reduce(function(chain,dynNeeded){return chain.then(function(){return loadDynamicLibrary(dynNeeded,flags)})},Promise.resolve()).then(function(){return loadModule()})}metadata.neededDynlibs.forEach(function(dynNeeded){loadDynamicLibrary(dynNeeded,flags)});return loadModule()}Module["loadWebAssemblyModule"]=loadWebAssemblyModule;function loadDynamicLibrary(lib,flags,handle){flags=flags||{global:true,nodelete:true};var dso=LDSO.loadedLibsByName[lib];if(dso){if(flags.global&&!dso.global){dso.global=true;if(dso.module!=="loading"){mergeLibSymbols(dso.module,lib)}}if(flags.nodelete&&dso.refcount!==Infinity){dso.refcount=Infinity}dso.refcount++;if(handle){LDSO.loadedLibsByHandle[handle]=dso}return flags.loadAsync?Promise.resolve(true):true}dso={refcount:flags.nodelete?Infinity:1,name:lib,module:"loading",global:flags.global};LDSO.loadedLibsByName[lib]=dso;if(handle){LDSO.loadedLibsByHandle[handle]=dso}function loadLibData(libFile){if(flags.fs&&flags.fs.findObject(libFile)){var libData=flags.fs.readFile(libFile,{encoding:"binary"});if(!(libData instanceof Uint8Array)){libData=new Uint8Array(libData)}return flags.loadAsync?Promise.resolve(libData):libData}if(flags.loadAsync){return new Promise(function(resolve,reject){readAsync(libFile,function(data){resolve(new Uint8Array(data))},reject)})}if(!readBinary){throw new Error(libFile+": file not found, and synchronous loading of external files is not available")}return readBinary(libFile)}function getLibModule(){if(typeof preloadedWasm!="undefined"&&preloadedWasm[lib]){var libModule=preloadedWasm[lib];return flags.loadAsync?Promise.resolve(libModule):libModule}if(flags.loadAsync){return loadLibData(lib).then(function(libData){return loadWebAssemblyModule(libData,flags,handle)})}return loadWebAssemblyModule(loadLibData(lib),flags,handle)}function moduleLoaded(libModule){if(dso.global){mergeLibSymbols(libModule,lib)}dso.module=libModule}if(flags.loadAsync){return getLibModule().then(function(libModule){moduleLoaded(libModule);return true})}moduleLoaded(getLibModule());return true}Module["loadDynamicLibrary"]=loadDynamicLibrary;function reportUndefinedSymbols(){for(var symName in GOT){if(GOT[symName].value==0){var value=resolveGlobalSymbol(symName,true);if(!value&&!GOT[symName].required){continue}if(typeof value=="function"){GOT[symName].value=addFunction(value,value.sig)}else if(typeof value=="number"){GOT[symName].value=value}else{throw new Error("bad export type for `"+symName+"`: "+typeof value)}}}}Module["reportUndefinedSymbols"]=reportUndefinedSymbols;function preloadDylibs(){if(!dynamicLibraries.length){reportUndefinedSymbols();return}addRunDependency("preloadDylibs");dynamicLibraries.reduce(function(chain,lib){return chain.then(function(){return loadDynamicLibrary(lib,{loadAsync:true,global:true,nodelete:true,allowUndefined:true})})},Promise.resolve()).then(function(){reportUndefinedSymbols();removeRunDependency("preloadDylibs")})}Module["preloadDylibs"]=preloadDylibs;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="i32";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;default:abort("invalid type for setValue: "+type)}}Module["setValue"]=setValue;function setWasmTableEntry(idx,func){wasmTable.set(idx,func);wasmTableMirror[idx]=wasmTable.get(idx)}Module["setWasmTableEntry"]=setWasmTableEntry;function stackTrace(){var js=jsStackTrace();if(Module["extraStackTrace"])js+="\n"+Module["extraStackTrace"]();return demangleAll(js)}Module["stackTrace"]=stackTrace;function ___assert_fail(condition,filename,line,func){abort("Assertion failed: "+UTF8ToString(condition)+", at: "+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])}Module["___assert_fail"]=___assert_fail;___assert_fail.sig="vppip";function ___call_sighandler(fp,sig){getWasmTableEntry(fp)(sig)}Module["___call_sighandler"]=___call_sighandler;___call_sighandler.sig="vpi";function ___cxa_allocate_exception(size){return _malloc(size+24)+24}Module["___cxa_allocate_exception"]=___cxa_allocate_exception;___cxa_allocate_exception.sig="pp";var exceptionCaught=[];Module["exceptionCaught"]=exceptionCaught;function exception_addRef(info){info.add_ref()}Module["exception_addRef"]=exception_addRef;var uncaughtExceptionCount=0;Module["uncaughtExceptionCount"]=uncaughtExceptionCount;function ___cxa_begin_catch(ptr){var info=new ExceptionInfo(ptr);if(!info.get_caught()){info.set_caught(true);uncaughtExceptionCount--}info.set_rethrown(false);exceptionCaught.push(info);exception_addRef(info);return info.get_exception_ptr()}Module["___cxa_begin_catch"]=___cxa_begin_catch;___cxa_begin_catch.sig="pp";function ___cxa_call_unexpected(exception){err("Unexpected exception thrown, this is not properly supported - aborting");ABORT=true;throw exception}Module["___cxa_call_unexpected"]=___cxa_call_unexpected;function ___cxa_current_primary_exception(){if(!exceptionCaught.length){return 0}var info=exceptionCaught[exceptionCaught.length-1];exception_addRef(info);return info.excPtr}Module["___cxa_current_primary_exception"]=___cxa_current_primary_exception;function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=prev-1;return prev===1};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=Module["___cxa_is_pointer_type"](this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}Module["ExceptionInfo"]=ExceptionInfo;function ___cxa_free_exception(ptr){return _free(new ExceptionInfo(ptr).ptr)}Module["___cxa_free_exception"]=___cxa_free_exception;___cxa_free_exception.sig="vp";function exception_decRef(info){if(info.release_ref()&&!info.get_rethrown()){var destructor=info.get_destructor();if(destructor){getWasmTableEntry(destructor)(info.excPtr)}___cxa_free_exception(info.excPtr)}}Module["exception_decRef"]=exception_decRef;function ___cxa_decrement_exception_refcount(ptr){if(!ptr)return;exception_decRef(new ExceptionInfo(ptr))}Module["___cxa_decrement_exception_refcount"]=___cxa_decrement_exception_refcount;___cxa_decrement_exception_refcount.sig="vp";var exceptionLast=0;Module["exceptionLast"]=exceptionLast;function ___cxa_end_catch(){_setThrew(0);var info=exceptionCaught.pop();exception_decRef(info);exceptionLast=0}Module["___cxa_end_catch"]=___cxa_end_catch;___cxa_end_catch.sig="v";function ___resumeException(ptr){if(!exceptionLast){exceptionLast=ptr}throw ptr}Module["___resumeException"]=___resumeException;___resumeException.sig="vp";function ___cxa_find_matching_catch_2(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}var typeArray=Array.prototype.slice.call(arguments);for(var i=0;ipath.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};Module["PATH"]=PATH;function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}Module["getRandomDevice"]=getRandomDevice;var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};Module["TTY"]=TTY;function mmapAlloc(size){size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(!ptr)return 0;zeroMemory(ptr,size);return ptr}Module["mmapAlloc"]=mmapAlloc;var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{if(typeof indexedDB!="undefined")return indexedDB;var ret=null;if(typeof window=="object")ret=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;assert(ret,"IDBFS used, but indexedDB not supported");return ret},DB_VERSION:21,DB_STORE_NAME:"FILE_DATA",mount:function(mount){return MEMFS.mount.apply(null,arguments)},syncfs:(mount,populate,callback)=>{IDBFS.getLocalSet(mount,(err,local)=>{if(err)return callback(err);IDBFS.getRemoteSet(mount,(err,remote)=>{if(err)return callback(err);var src=populate?remote:local;var dst=populate?local:remote;IDBFS.reconcile(src,dst,callback)})})},quit:()=>{Object.values(IDBFS.dbs).forEach(value=>value.close());IDBFS.dbs={}},getDB:(name,callback)=>{var db=IDBFS.dbs[name];if(db){return callback(null,db)}var req;try{req=IDBFS.indexedDB().open(name,IDBFS.DB_VERSION)}catch(e){return callback(e)}if(!req){return callback("Unable to connect to IndexedDB")}req.onupgradeneeded=e=>{var db=e.target.result;var transaction=e.target.transaction;var fileStore;if(db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)){fileStore=transaction.objectStore(IDBFS.DB_STORE_NAME)}else{fileStore=db.createObjectStore(IDBFS.DB_STORE_NAME)}if(!fileStore.indexNames.contains("timestamp")){fileStore.createIndex("timestamp","timestamp",{unique:false})}};req.onsuccess=()=>{db=req.result;IDBFS.dbs[name]=db;callback(null,db)};req.onerror=e=>{callback(this.error);e.preventDefault()}},getLocalSet:(mount,callback)=>{var entries={};function isRealDir(p){return p!=="."&&p!==".."}function toAbsolute(root){return p=>{return PATH.join2(root,p)}}var check=FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint));while(check.length){var path=check.pop();var stat;try{stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){check.push.apply(check,FS.readdir(path).filter(isRealDir).map(toAbsolute(path)))}entries[path]={"timestamp":stat.mtime}}return callback(null,{type:"local",entries:entries})},getRemoteSet:(mount,callback)=>{var entries={};IDBFS.getDB(mount.mountpoint,(err,db)=>{if(err)return callback(err);try{var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readonly");transaction.onerror=e=>{callback(this.error);e.preventDefault()};var store=transaction.objectStore(IDBFS.DB_STORE_NAME);var index=store.index("timestamp");index.openKeyCursor().onsuccess=event=>{var cursor=event.target.result;if(!cursor){return callback(null,{type:"remote",db:db,entries:entries})}entries[cursor.primaryKey]={"timestamp":cursor.key};cursor.continue()}}catch(e){return callback(e)}})},loadLocalEntry:(path,callback)=>{var stat,node;try{var lookup=FS.lookupPath(path);node=lookup.node;stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){return callback(null,{"timestamp":stat.mtime,"mode":stat.mode})}else if(FS.isFile(stat.mode)){node.contents=MEMFS.getFileDataAsTypedArray(node);return callback(null,{"timestamp":stat.mtime,"mode":stat.mode,"contents":node.contents})}else{return callback(new Error("node type not supported"))}},storeLocalEntry:(path,entry,callback)=>{try{if(FS.isDir(entry["mode"])){FS.mkdirTree(path,entry["mode"])}else if(FS.isFile(entry["mode"])){FS.writeFile(path,entry["contents"],{canOwn:true})}else{return callback(new Error("node type not supported"))}FS.chmod(path,entry["mode"]);FS.utime(path,entry["timestamp"],entry["timestamp"])}catch(e){return callback(e)}callback(null)},removeLocalEntry:(path,callback)=>{try{var stat=FS.stat(path);if(FS.isDir(stat.mode)){FS.rmdir(path)}else if(FS.isFile(stat.mode)){FS.unlink(path)}}catch(e){return callback(e)}callback(null)},loadRemoteEntry:(store,path,callback)=>{var req=store.get(path);req.onsuccess=event=>{callback(null,event.target.result)};req.onerror=e=>{callback(this.error);e.preventDefault()}},storeRemoteEntry:(store,path,entry,callback)=>{try{var req=store.put(entry,path)}catch(e){callback(e);return}req.onsuccess=()=>{callback(null)};req.onerror=e=>{callback(this.error);e.preventDefault()}},removeRemoteEntry:(store,path,callback)=>{var req=store.delete(path);req.onsuccess=()=>{callback(null)};req.onerror=e=>{callback(this.error);e.preventDefault()}},reconcile:(src,dst,callback)=>{var total=0;var create=[];Object.keys(src.entries).forEach(function(key){var e=src.entries[key];var e2=dst.entries[key];if(!e2||e["timestamp"].getTime()!=e2["timestamp"].getTime()){create.push(key);total++}});var remove=[];Object.keys(dst.entries).forEach(function(key){if(!src.entries[key]){remove.push(key);total++}});if(!total){return callback(null)}var errored=false;var db=src.type==="remote"?src.db:dst.db;var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readwrite");var store=transaction.objectStore(IDBFS.DB_STORE_NAME);function done(err){if(err&&!errored){errored=true;return callback(err)}}transaction.onerror=e=>{done(this.error);e.preventDefault()};transaction.oncomplete=e=>{if(!errored){callback(null)}};create.sort().forEach(path=>{if(dst.type==="local"){IDBFS.loadRemoteEntry(store,path,(err,entry)=>{if(err)return done(err);IDBFS.storeLocalEntry(path,entry,done)})}else{IDBFS.loadLocalEntry(path,(err,entry)=>{if(err)return done(err);IDBFS.storeRemoteEntry(store,path,entry,done)})}});remove.sort().reverse().forEach(path=>{if(dst.type==="local"){IDBFS.removeLocalEntry(path,done)}else{IDBFS.removeRemoteEntry(store,path,done)}})}};Module["IDBFS"]=IDBFS;var ERRNO_CODES={};Module["ERRNO_CODES"]=ERRNO_CODES;var NODEFS={isWindows:false,staticInit:()=>{NODEFS.isWindows=!!process.platform.match(/^win/);var flags=process["binding"]("constants");if(flags["fs"]){flags=flags["fs"]}NODEFS.flagsForNodeMap={1024:flags["O_APPEND"],64:flags["O_CREAT"],128:flags["O_EXCL"],256:flags["O_NOCTTY"],0:flags["O_RDONLY"],2:flags["O_RDWR"],4096:flags["O_SYNC"],512:flags["O_TRUNC"],1:flags["O_WRONLY"],131072:flags["O_NOFOLLOW"]}},convertNodeCode:e=>{var code=e.code;return ERRNO_CODES[code]},mount:mount=>{return NODEFS.createNode(null,"/",NODEFS.getMode(mount.opts.root),0)},createNode:(parent,name,mode,dev)=>{if(!FS.isDir(mode)&&!FS.isFile(mode)&&!FS.isLink(mode)){throw new FS.ErrnoError(28)}var node=FS.createNode(parent,name,mode);node.node_ops=NODEFS.node_ops;node.stream_ops=NODEFS.stream_ops;return node},getMode:path=>{var stat;try{stat=fs.lstatSync(path);if(NODEFS.isWindows){stat.mode=stat.mode|(stat.mode&292)>>2}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}return stat.mode},realPath:node=>{var parts=[];while(node.parent!==node){parts.push(node.name);node=node.parent}parts.push(node.mount.opts.root);parts.reverse();return PATH.join.apply(null,parts)},flagsForNode:flags=>{flags&=~2097152;flags&=~2048;flags&=~32768;flags&=~524288;flags&=~65536;var newFlags=0;for(var k in NODEFS.flagsForNodeMap){if(flags&k){newFlags|=NODEFS.flagsForNodeMap[k];flags^=k}}if(!flags){return newFlags}else{throw new FS.ErrnoError(28)}},node_ops:{getattr:node=>{var path=NODEFS.realPath(node);var stat;try{stat=fs.lstatSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}if(NODEFS.isWindows&&!stat.blksize){stat.blksize=4096}if(NODEFS.isWindows&&!stat.blocks){stat.blocks=(stat.size+stat.blksize-1)/stat.blksize|0}return{dev:stat.dev,ino:stat.ino,mode:stat.mode,nlink:stat.nlink,uid:stat.uid,gid:stat.gid,rdev:stat.rdev,size:stat.size,atime:stat.atime,mtime:stat.mtime,ctime:stat.ctime,blksize:stat.blksize,blocks:stat.blocks}},setattr:(node,attr)=>{var path=NODEFS.realPath(node);try{if(attr.mode!==undefined){fs.chmodSync(path,attr.mode);node.mode=attr.mode}if(attr.timestamp!==undefined){var date=new Date(attr.timestamp);fs.utimesSync(path,date,date)}if(attr.size!==undefined){fs.truncateSync(path,attr.size)}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},lookup:(parent,name)=>{var path=PATH.join2(NODEFS.realPath(parent),name);var mode=NODEFS.getMode(path);return NODEFS.createNode(parent,name,mode)},mknod:(parent,name,mode,dev)=>{var node=NODEFS.createNode(parent,name,mode,dev);var path=NODEFS.realPath(node);try{if(FS.isDir(node.mode)){fs.mkdirSync(path,node.mode)}else{fs.writeFileSync(path,"",{mode:node.mode})}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}return node},rename:(oldNode,newDir,newName)=>{var oldPath=NODEFS.realPath(oldNode);var newPath=PATH.join2(NODEFS.realPath(newDir),newName);try{fs.renameSync(oldPath,newPath)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}oldNode.name=newName},unlink:(parent,name)=>{var path=PATH.join2(NODEFS.realPath(parent),name);try{fs.unlinkSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},rmdir:(parent,name)=>{var path=PATH.join2(NODEFS.realPath(parent),name);try{fs.rmdirSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},readdir:node=>{var path=NODEFS.realPath(node);try{return fs.readdirSync(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},symlink:(parent,newName,oldPath)=>{var newPath=PATH.join2(NODEFS.realPath(parent),newName);try{fs.symlinkSync(oldPath,newPath)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},readlink:node=>{var path=NODEFS.realPath(node);try{path=fs.readlinkSync(path);path=nodePath.relative(nodePath.resolve(node.mount.opts.root),path);return path}catch(e){if(!e.code)throw e;if(e.code==="UNKNOWN")throw new FS.ErrnoError(28);throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}}},stream_ops:{open:stream=>{var path=NODEFS.realPath(stream.node);try{if(FS.isFile(stream.node.mode)){stream.nfd=fs.openSync(path,NODEFS.flagsForNode(stream.flags))}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},close:stream=>{try{if(FS.isFile(stream.node.mode)&&stream.nfd){fs.closeSync(stream.nfd)}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},read:(stream,buffer,offset,length,position)=>{if(length===0)return 0;try{return fs.readSync(stream.nfd,Buffer.from(buffer.buffer),offset,length,position)}catch(e){throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},write:(stream,buffer,offset,length,position)=>{try{return fs.writeSync(stream.nfd,Buffer.from(buffer.buffer),offset,length,position)}catch(e){throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}},llseek:(stream,offset,whence)=>{var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){try{var stat=fs.fstatSync(stream.nfd);position+=stat.size}catch(e){throw new FS.ErrnoError(NODEFS.convertNodeCode(e))}}}if(position<0){throw new FS.ErrnoError(28)}return position},mmap:(stream,length,position,prot,flags)=>{if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr=mmapAlloc(length);NODEFS.stream_ops.read(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(mmapFlags&2){return 0}var bytesWritten=NODEFS.stream_ops.write(stream,buffer,0,length,offset,false);return 0}}};Module["NODEFS"]=NODEFS;var WORKERFS={DIR_MODE:16895,FILE_MODE:33279,reader:null,mount:function(mount){assert(ENVIRONMENT_IS_WORKER);if(!WORKERFS.reader)WORKERFS.reader=new FileReaderSync;var root=WORKERFS.createNode(null,"/",WORKERFS.DIR_MODE,0);var createdParents={};function ensureParent(path){var parts=path.split("/");var parent=root;for(var i=0;i=stream.node.size)return 0;var chunk=stream.node.contents.slice(position,position+length);var ab=WORKERFS.reader.readAsArrayBuffer(chunk);buffer.set(new Uint8Array(ab),offset);return chunk.size},write:function(stream,buffer,offset,length,position){throw new FS.ErrnoError(29)},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.size}}if(position<0){throw new FS.ErrnoError(28)}return position}}};Module["WORKERFS"]=WORKERFS;var PROXYFS={mount:function(mount){return PROXYFS.createNode(null,"/",mount.opts.fs.lstat(mount.opts.root).mode,0)},createNode:function(parent,name,mode,dev){if(!FS.isDir(mode)&&!FS.isFile(mode)&&!FS.isLink(mode)){throw new FS.ErrnoError(ERRNO_CODES.EINVAL)}var node=FS.createNode(parent,name,mode);node.node_ops=PROXYFS.node_ops;node.stream_ops=PROXYFS.stream_ops;return node},realPath:function(node){var parts=[];while(node.parent!==node){parts.push(node.name);node=node.parent}parts.push(node.mount.opts.root);parts.reverse();return PATH.join.apply(null,parts)},node_ops:{getattr:function(node){var path=PROXYFS.realPath(node);var stat;try{stat=node.mount.opts.fs.lstat(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}return{dev:stat.dev,ino:stat.ino,mode:stat.mode,nlink:stat.nlink,uid:stat.uid,gid:stat.gid,rdev:stat.rdev,size:stat.size,atime:stat.atime,mtime:stat.mtime,ctime:stat.ctime,blksize:stat.blksize,blocks:stat.blocks}},setattr:function(node,attr){var path=PROXYFS.realPath(node);try{if(attr.mode!==undefined){node.mount.opts.fs.chmod(path,attr.mode);node.mode=attr.mode}if(attr.timestamp!==undefined){var date=new Date(attr.timestamp);node.mount.opts.fs.utime(path,date,date)}if(attr.size!==undefined){node.mount.opts.fs.truncate(path,attr.size)}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},lookup:function(parent,name){try{var path=PATH.join2(PROXYFS.realPath(parent),name);var mode=parent.mount.opts.fs.lstat(path).mode;var node=PROXYFS.createNode(parent,name,mode);return node}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},mknod:function(parent,name,mode,dev){var node=PROXYFS.createNode(parent,name,mode,dev);var path=PROXYFS.realPath(node);try{if(FS.isDir(node.mode)){node.mount.opts.fs.mkdir(path,node.mode)}else{node.mount.opts.fs.writeFile(path,"",{mode:node.mode})}}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}return node},rename:function(oldNode,newDir,newName){var oldPath=PROXYFS.realPath(oldNode);var newPath=PATH.join2(PROXYFS.realPath(newDir),newName);try{oldNode.mount.opts.fs.rename(oldPath,newPath)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},unlink:function(parent,name){var path=PATH.join2(PROXYFS.realPath(parent),name);try{parent.mount.opts.fs.unlink(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},rmdir:function(parent,name){var path=PATH.join2(PROXYFS.realPath(parent),name);try{parent.mount.opts.fs.rmdir(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},readdir:function(node){var path=PROXYFS.realPath(node);try{return node.mount.opts.fs.readdir(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},symlink:function(parent,newName,oldPath){var newPath=PATH.join2(PROXYFS.realPath(parent),newName);try{parent.mount.opts.fs.symlink(oldPath,newPath)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},readlink:function(node){var path=PROXYFS.realPath(node);try{return node.mount.opts.fs.readlink(path)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}}},stream_ops:{open:function(stream){var path=PROXYFS.realPath(stream.node);try{stream.nfd=stream.node.mount.opts.fs.open(path,stream.flags)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},close:function(stream){try{stream.node.mount.opts.fs.close(stream.nfd)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},read:function(stream,buffer,offset,length,position){try{return stream.node.mount.opts.fs.read(stream.nfd,buffer,offset,length,position)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},write:function(stream,buffer,offset,length,position){try{return stream.node.mount.opts.fs.write(stream.nfd,buffer,offset,length,position)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){try{var stat=stream.node.node_ops.getattr(stream.node);position+=stat.size}catch(e){throw new FS.ErrnoError(ERRNO_CODES[e.code])}}}if(position<0){throw new FS.ErrnoError(ERRNO_CODES.EINVAL)}return position}}};Module["PROXYFS"]=PROXYFS;var LZ4={DIR_MODE:16895,FILE_MODE:33279,CHUNK_SIZE:-1,codec:null,init:function(){if(LZ4.codec)return;LZ4.codec=function(){var MiniLZ4=function(){var exports={};exports.uncompress=function(input,output,sIdx,eIdx){sIdx=sIdx||0;eIdx=eIdx||input.length-sIdx;for(var i=sIdx,n=eIdx,j=0;i>4;if(literals_length>0){var l=literals_length+240;while(l===255){l=input[i++];literals_length+=l}var end=i+literals_length;while(ij)return-(i-2);var match_length=token&15;var l=match_length+240;while(l===255){l=input[i++];match_length+=l}var pos=j-offset;var end=j+match_length+4;while(jmaxInputSize?0:isize+isize/255+16|0};exports.compress=function(src,dst,sIdx,eIdx){hashTable.set(empty);return compressBlock(src,dst,0,sIdx||0,eIdx||dst.length)};function compressBlock(src,dst,pos,sIdx,eIdx){var dpos=sIdx;var dlen=eIdx-sIdx;var anchor=0;if(src.length>=maxInputSize)throw new Error("input too large");if(src.length>mfLimit){var n=exports.compressBound(src.length);if(dlen>>hashShift;var ref=hashTable[hash]-1;hashTable[hash]=pos+1;if(ref<0||pos-ref>>>16>0||((src[ref+3]<<8|src[ref+2])!=sequenceHighBits||(src[ref+1]<<8|src[ref])!=sequenceLowBits)){step=findMatchAttempts++>>skipStrength;pos+=step;continue}findMatchAttempts=(1<=runMask){dst[dpos++]=(runMask<254;len-=255){dst[dpos++]=255}dst[dpos++]=len}else{dst[dpos++]=(literals_length<>8;if(match_length>=mlMask){match_length-=mlMask;while(match_length>=255){match_length-=255;dst[dpos++]=255}dst[dpos++]=match_length}anchor=pos}}if(anchor==0)return 0;literals_length=src.length-anchor;if(literals_length>=runMask){dst[dpos++]=runMask<254;ln-=255){dst[dpos++]=255}dst[dpos++]=ln}else{dst[dpos++]=literals_length<0){assert(compressedSize<=bound);compressed=compressed.subarray(0,compressedSize);compressedChunks.push(compressed);total+=compressedSize;successes.push(1);if(verify){var back=exports.uncompress(compressed,temp);assert(back===chunk.length,[back,chunk.length]);for(var i=0;i=0){currChunk=compressedData["cachedChunks"][found]}else{compressedData["cachedIndexes"].pop();compressedData["cachedIndexes"].unshift(chunkIndex);currChunk=compressedData["cachedChunks"].pop();compressedData["cachedChunks"].unshift(currChunk);if(compressedData["debug"]){out("decompressing chunk "+chunkIndex);Module["decompressedChunks"]=(Module["decompressedChunks"]||0)+1}var compressed=compressedData["data"].subarray(compressedStart,compressedStart+compressedSize);var originalSize=LZ4.codec.uncompress(compressed,currChunk);if(chunkIndex{path=PATH_FS.resolve(FS.cwd(),path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(p=>!!p),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream||!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS,"IDBFS":IDBFS,"NODEFS":NODEFS,"WORKERFS":WORKERFS,"PROXYFS":PROXYFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;_fflush(0);for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(ret.exists){return ret.object}else{return null}},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};Module["FS"]=FS;var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=FS.getStream(dirfd);if(!dirstream)throw new FS.ErrnoError(8);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;HEAP64[buf+40>>3]=BigInt(stat.size);HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;HEAP64[buf+80>>3]=BigInt(stat.ino);return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};Module["SYSCALLS"]=SYSCALLS;function ___syscall__newselect(nfds,readfds,writefds,exceptfds,timeout){try{var total=0;var srcReadLow=readfds?HEAP32[readfds>>2]:0,srcReadHigh=readfds?HEAP32[readfds+4>>2]:0;var srcWriteLow=writefds?HEAP32[writefds>>2]:0,srcWriteHigh=writefds?HEAP32[writefds+4>>2]:0;var srcExceptLow=exceptfds?HEAP32[exceptfds>>2]:0,srcExceptHigh=exceptfds?HEAP32[exceptfds+4>>2]:0;var dstReadLow=0,dstReadHigh=0;var dstWriteLow=0,dstWriteHigh=0;var dstExceptLow=0,dstExceptHigh=0;var allLow=(readfds?HEAP32[readfds>>2]:0)|(writefds?HEAP32[writefds>>2]:0)|(exceptfds?HEAP32[exceptfds>>2]:0);var allHigh=(readfds?HEAP32[readfds+4>>2]:0)|(writefds?HEAP32[writefds+4>>2]:0)|(exceptfds?HEAP32[exceptfds+4>>2]:0);var check=function(fd,low,high,val){return fd<32?low&val:high&val};for(var fd=0;fd>2]=dstReadLow;HEAP32[readfds+4>>2]=dstReadHigh}if(writefds){HEAP32[writefds>>2]=dstWriteLow;HEAP32[writefds+4>>2]=dstWriteHigh}if(exceptfds){HEAP32[exceptfds>>2]=dstExceptLow;HEAP32[exceptfds+4>>2]=dstExceptHigh}return total}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall__newselect"]=___syscall__newselect;var SOCKFS={mount:function(mount){Module["websocket"]=Module["websocket"]&&"object"===typeof Module["websocket"]?Module["websocket"]:{};Module["websocket"]._callbacks={};Module["websocket"]["on"]=function(event,callback){if("function"===typeof callback){this._callbacks[event]=callback}return this};Module["websocket"].emit=function(event,param){if("function"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,"/",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return"socket["+SOCKFS.nextname.current+++"]"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module["websocket"]&&"object"===typeof Module["websocket"];var url="ws:#".replace("#","//");if(runtimeConfig){if("string"===typeof Module["websocket"]["url"]){url=Module["websocket"]["url"]}}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}var subProtocols="binary";if(runtimeConfig){if("string"===typeof Module["websocket"]["subprotocol"]){subProtocols=Module["websocket"]["subprotocol"]}}var opts=undefined;if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module["websocket"]["subprotocol"]){subProtocols="null";opts=undefined}var WebSocketConstructor;if(ENVIRONMENT_IS_NODE){WebSocketConstructor=require("ws")}else{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+":"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module["websocket"].emit("open",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}else{data=new Uint8Array(data)}}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module["websocket"].emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){Module["websocket"].emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module["websocket"].emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i>2]=value;return value}Module["setErrNo"]=setErrNo;var Sockets={BUFFER_SIZE:10240,MAX_BUFFER_SIZE:10485760,nextFd:1,fds:{},nextport:1,maxport:65535,peer:null,connections:{},portmap:{},localAddr:4261412874,addrPool:[33554442,50331658,67108874,83886090,100663306,117440522,134217738,150994954,167772170,184549386,201326602,218103818,234881034]};Module["Sockets"]=Sockets;function inetPton4(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}Module["inetPton4"]=inetPton4;function jstoi_q(str){return parseInt(str)}Module["jstoi_q"]=jstoi_q;function inetPton6(str){var words;var w,offset,z,i;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=16}HEAP16[sa>>1]=family;HEAP32[sa+4>>2]=addr;HEAP16[sa+2>>1]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){HEAP32[addrlen>>2]=28}HEAP32[sa>>2]=family;HEAP32[sa+8>>2]=addr[0];HEAP32[sa+12>>2]=addr[1];HEAP32[sa+16>>2]=addr[2];HEAP32[sa+20>>2]=addr[3];HEAP16[sa+2>>1]=_htons(port);break;default:return 5}return 0}Module["writeSockaddr"]=writeSockaddr;var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name:function(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;assert(id<65535,"exceeded max address mappings of 65535");addr="172.29."+(id&255)+"."+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr:function(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};Module["DNS"]=DNS;function ___syscall_accept4(fd,addr,addrlen,flags){try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen)}return newsock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_accept4"]=___syscall_accept4;function inetNtop4(addr){return(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255)}Module["inetNtop4"]=inetNtop4;function inetNtop6(ints){var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}Module["readSockaddr"]=readSockaddr;function getSocketAddress(addrp,addrlen,allowNull){if(allowNull&&addrp===0)return null;var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info}Module["getSocketAddress"]=getSocketAddress;function ___syscall_bind(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_bind"]=___syscall_bind;function ___syscall_chdir(path){try{path=SYSCALLS.getStr(path);FS.chdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_chdir"]=___syscall_chdir;___syscall_chdir.sig="ip";function ___syscall_chmod(path,mode){try{path=SYSCALLS.getStr(path);FS.chmod(path,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_chmod"]=___syscall_chmod;___syscall_chmod.sig="ipi";function ___syscall_connect(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_connect"]=___syscall_connect;function ___syscall_dup(fd){try{var old=SYSCALLS.getStreamFromFD(fd);return FS.createStream(old,0).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_dup"]=___syscall_dup;___syscall_dup.sig="ii";function ___syscall_dup3(fd,suggestFD,flags){try{var old=SYSCALLS.getStreamFromFD(fd);if(old.fd===suggestFD)return-28;var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.createStream(old,suggestFD,suggestFD+1).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_dup3"]=___syscall_dup3;function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_faccessat"]=___syscall_faccessat;___syscall_faccessat.sig="iipii";function ___syscall_fadvise64(fd,offset,len,advice){return 0}Module["___syscall_fadvise64"]=___syscall_fadvise64;var MAX_INT53=9007199254740992;Module["MAX_INT53"]=MAX_INT53;var MIN_INT53=-9007199254740992;Module["MIN_INT53"]=MIN_INT53;function bigintToI53Checked(num){return numMAX_INT53?NaN:Number(num)}Module["bigintToI53Checked"]=bigintToI53Checked;function ___syscall_fallocate(fd,mode,offset,len){try{offset=bigintToI53Checked(offset);if(isNaN(offset))return-61;len=bigintToI53Checked(len);if(isNaN(len))return-61;var stream=SYSCALLS.getStreamFromFD(fd);FS.allocate(stream,offset,len);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fallocate"]=___syscall_fallocate;function ___syscall_fchdir(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.chdir(stream.path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fchdir"]=___syscall_fchdir;function ___syscall_fchmod(fd,mode){try{FS.fchmod(fd,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fchmod"]=___syscall_fchmod;function ___syscall_fchmodat(dirfd,path,mode,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);FS.chmod(path,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fchmodat"]=___syscall_fchmodat;___syscall_fchmodat.sig="iipip";function ___syscall_fchown32(fd,owner,group){try{FS.fchown(fd,owner,group);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fchown32"]=___syscall_fchown32;function ___syscall_fchownat(dirfd,path,owner,group,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;flags=flags&~256;path=SYSCALLS.calculateAt(dirfd,path);(nofollow?FS.lchown:FS.chown)(path,owner,group);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fchownat"]=___syscall_fchownat;___syscall_fchownat.sig="iipiii";function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fcntl64"]=___syscall_fcntl64;___syscall_fcntl64.sig="iiip";function ___syscall_fdatasync(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fdatasync"]=___syscall_fdatasync;function ___syscall_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fstat64"]=___syscall_fstat64;___syscall_fstat64.sig="iip";function ___syscall_statfs64(path,size,buf){try{path=SYSCALLS.getStr(path);HEAP32[buf+4>>2]=4096;HEAP32[buf+40>>2]=4096;HEAP32[buf+8>>2]=1e6;HEAP32[buf+12>>2]=5e5;HEAP32[buf+16>>2]=5e5;HEAP32[buf+20>>2]=FS.nextInode;HEAP32[buf+24>>2]=1e6;HEAP32[buf+28>>2]=42;HEAP32[buf+44>>2]=2;HEAP32[buf+36>>2]=255;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_statfs64"]=___syscall_statfs64;___syscall_statfs64.sig="ippp";function ___syscall_fstatfs64(fd,size,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return ___syscall_statfs64(0,size,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_fstatfs64"]=___syscall_fstatfs64;function ___syscall_ftruncate64(fd,length){try{length=bigintToI53Checked(length);if(isNaN(length))return-61;FS.ftruncate(fd,length);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_ftruncate64"]=___syscall_ftruncate64;___syscall_ftruncate64.sig="iij";function ___syscall_getcwd(buf,size){try{if(size===0)return-28;var cwd=FS.cwd();var cwdLengthInBytes=lengthBytesUTF8(cwd)+1;if(size>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_getdents64"]=___syscall_getdents64;___syscall_getdents64.sig="iipi";function ___syscall_getpeername(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);if(!sock.daddr){return-53}var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.daddr),sock.dport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_getpeername"]=___syscall_getpeername;function ___syscall_getsockname(fd,addr,addrlen){try{err("__syscall_getsockname "+fd);var sock=getSocketFromFD(fd);var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.saddr||"0.0.0.0"),sock.sport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_getsockname"]=___syscall_getsockname;function ___syscall_getsockopt(fd,level,optname,optval,optlen){try{var sock=getSocketFromFD(fd);if(level===1){if(optname===4){HEAP32[optval>>2]=sock.error;HEAP32[optlen>>2]=4;sock.error=null;return 0}}return-50}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_getsockopt"]=___syscall_getsockopt;function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_ioctl"]=___syscall_ioctl;___syscall_ioctl.sig="iiip";function ___syscall_linkat(olddirfd,oldpath,newdirfd,newpath,flags){return-34}Module["___syscall_linkat"]=___syscall_linkat;function ___syscall_listen(fd,backlog){try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_listen"]=___syscall_listen;function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_lstat64"]=___syscall_lstat64;___syscall_lstat64.sig="ipp";function ___syscall_mkdirat(dirfd,path,mode){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_mkdirat"]=___syscall_mkdirat;___syscall_mkdirat.sig="iipi";function ___syscall_mknodat(dirfd,path,mode,dev){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_mknodat"]=___syscall_mknodat;___syscall_mknodat.sig="iipii";function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~4352;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_newfstatat"]=___syscall_newfstatat;___syscall_newfstatat.sig="iippi";function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_openat"]=___syscall_openat;___syscall_openat.sig="iipip";var PIPEFS={BUCKET_BUFFER_SIZE:8192,mount:function(mount){return FS.createNode(null,"/",16384|511,0)},createPipe:function(){var pipe={buckets:[],refcnt:2};pipe.buckets.push({buffer:new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE),offset:0,roffset:0});var rName=PIPEFS.nextname();var wName=PIPEFS.nextname();var rNode=FS.createNode(PIPEFS.root,rName,4096,0);var wNode=FS.createNode(PIPEFS.root,wName,4096,0);rNode.pipe=pipe;wNode.pipe=pipe;var readableStream=FS.createStream({path:rName,node:rNode,flags:0,seekable:false,stream_ops:PIPEFS.stream_ops});rNode.stream=readableStream;var writableStream=FS.createStream({path:wName,node:wNode,flags:1,seekable:false,stream_ops:PIPEFS.stream_ops});wNode.stream=writableStream;return{readable_fd:readableStream.fd,writable_fd:writableStream.fd}},stream_ops:{poll:function(stream){var pipe=stream.node.pipe;if((stream.flags&2097155)===1){return 256|4}else{if(pipe.buckets.length>0){for(var i=0;i0){return 64|1}}}}return 0},ioctl:function(stream,request,varargs){return 28},fsync:function(stream){return 28},read:function(stream,buffer,offset,length,position){var pipe=stream.node.pipe;var currentLength=0;for(var i=0;i=dataLen){currBucket.buffer.set(data,currBucket.offset);currBucket.offset+=dataLen;return dataLen}else if(freeBytesInCurrBuffer>0){currBucket.buffer.set(data.subarray(0,freeBytesInCurrBuffer),currBucket.offset);currBucket.offset+=freeBytesInCurrBuffer;data=data.subarray(freeBytesInCurrBuffer,data.byteLength)}var numBuckets=data.byteLength/PIPEFS.BUCKET_BUFFER_SIZE|0;var remElements=data.byteLength%PIPEFS.BUCKET_BUFFER_SIZE;for(var i=0;i0){var newBucket={buffer:new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE),offset:data.byteLength,roffset:0};pipe.buckets.push(newBucket);newBucket.buffer.set(data)}return dataLen},close:function(stream){var pipe=stream.node.pipe;pipe.refcnt--;if(pipe.refcnt===0){pipe.buckets=null}}},nextname:function(){if(!PIPEFS.nextname.current){PIPEFS.nextname.current=0}return"pipe["+PIPEFS.nextname.current+++"]"}};Module["PIPEFS"]=PIPEFS;function ___syscall_pipe(fdPtr){try{if(fdPtr==0){throw new FS.ErrnoError(21)}var res=PIPEFS.createPipe();HEAP32[fdPtr>>2]=res.readable_fd;HEAP32[fdPtr+4>>2]=res.writable_fd;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_pipe"]=___syscall_pipe;___syscall_pipe.sig="ip";function ___syscall_poll(fds,nfds,timeout){try{var nonzero=0;for(var i=0;i>2];var events=HEAP16[pollfd+4>>1];var mask=32;var stream=FS.getStream(fd);if(stream){mask=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){mask=stream.stream_ops.poll(stream)}}mask&=events|8|16;if(mask)nonzero++;HEAP16[pollfd+6>>1]=mask}return nonzero}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_poll"]=___syscall_poll;___syscall_poll.sig="ipii";function ___syscall_readlinkat(dirfd,path,buf,bufsize){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_readlinkat"]=___syscall_readlinkat;___syscall_readlinkat.sig="vippp";function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen)}HEAPU8.set(msg.buffer,buf);return msg.buffer.byteLength}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_recvfrom"]=___syscall_recvfrom;function ___syscall_recvmsg(fd,message,flags){try{var sock=getSocketFromFD(fd);var iov=HEAPU32[message+8>>2];var num=HEAP32[message+12>>2];var total=0;for(var i=0;i>2]}var msg=sock.sock_ops.recvmsg(sock,total);if(!msg)return 0;var name=HEAPU32[message>>2];if(name){var errno=writeSockaddr(name,sock.family,DNS.lookup_name(msg.addr),msg.port)}var bytesRead=0;var bytesRemaining=msg.buffer.byteLength;for(var i=0;bytesRemaining>0&&i>2];var iovlen=HEAP32[iov+(8*i+4)>>2];if(!iovlen){continue}var length=Math.min(iovlen,bytesRemaining);var buf=msg.buffer.subarray(bytesRead,bytesRead+length);HEAPU8.set(buf,iovbase+bytesRead);bytesRead+=length;bytesRemaining-=length}return bytesRead}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_recvmsg"]=___syscall_recvmsg;function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){try{oldpath=SYSCALLS.getStr(oldpath);newpath=SYSCALLS.getStr(newpath);oldpath=SYSCALLS.calculateAt(olddirfd,oldpath);newpath=SYSCALLS.calculateAt(newdirfd,newpath);FS.rename(oldpath,newpath);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_renameat"]=___syscall_renameat;___syscall_renameat.sig="iipip";function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_rmdir"]=___syscall_rmdir;___syscall_rmdir.sig="ip";function ___syscall_sendmsg(fd,message,flags){try{var sock=getSocketFromFD(fd);var iov=HEAPU32[message+8>>2];var num=HEAP32[message+12>>2];var addr,port;var name=HEAPU32[message>>2];var namelen=HEAP32[message+4>>2];if(name){var info=readSockaddr(name,namelen);if(info.errno)return-info.errno;port=info.port;addr=DNS.lookup_addr(info.addr)||info.addr}var total=0;for(var i=0;i>2]}var view=new Uint8Array(total);var offset=0;for(var i=0;i>2];var iovlen=HEAP32[iov+(8*i+4)>>2];for(var j=0;j>0]}}return sock.sock_ops.sendmsg(sock,view,0,total,addr,port)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_sendmsg"]=___syscall_sendmsg;function ___syscall_sendto(fd,message,length,flags,addr,addr_len){try{var sock=getSocketFromFD(fd);var dest=getSocketAddress(addr,addr_len,true);if(!dest){return FS.write(sock.stream,HEAP8,message,length)}else{return sock.sock_ops.sendmsg(sock,HEAP8,message,length,dest.addr,dest.port)}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_sendto"]=___syscall_sendto;function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_socket"]=___syscall_socket;function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_stat64"]=___syscall_stat64;___syscall_stat64.sig="ipp";function ___syscall_symlink(target,linkpath){try{target=SYSCALLS.getStr(target);linkpath=SYSCALLS.getStr(linkpath);FS.symlink(target,linkpath);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_symlink"]=___syscall_symlink;___syscall_symlink.sig="ipp";function ___syscall_symlinkat(target,newdirfd,linkpath){try{linkpath=SYSCALLS.calculateAt(newdirfd,linkpath);FS.symlink(target,linkpath);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_symlinkat"]=___syscall_symlinkat;function ___syscall_truncate64(path,length){try{length=bigintToI53Checked(length);if(isNaN(length))return-61;path=SYSCALLS.getStr(path);FS.truncate(path,length);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_truncate64"]=___syscall_truncate64;___syscall_truncate64.sig="ipj";function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(flags===0){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{abort("Invalid flags passed to unlinkat")}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_unlinkat"]=___syscall_unlinkat;___syscall_unlinkat.sig="iipi";function ___syscall_utimensat(dirfd,path,times,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path,true);if(!times){var atime=Date.now();var mtime=atime}else{var seconds=HEAP32[times>>2];var nanoseconds=HEAP32[times+4>>2];atime=seconds*1e3+nanoseconds/(1e3*1e3);times+=8;seconds=HEAP32[times>>2];nanoseconds=HEAP32[times+4>>2];mtime=seconds*1e3+nanoseconds/(1e3*1e3)}FS.utime(path,atime,mtime);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_utimensat"]=___syscall_utimensat;___syscall_utimensat.sig="iippi";var ___table_base=new WebAssembly.Global({"value":"i32","mutable":false},1);Module["___table_base"]=___table_base;function __dlinit(main_dso_handle){var dso={refcount:Infinity,name:"__main__",module:Module["asm"],global:true};LDSO.loadedLibsByName[dso.name]=dso;LDSO.loadedLibsByHandle[main_dso_handle]=dso}Module["__dlinit"]=__dlinit;var ENV={};Module["ENV"]=ENV;function dlSetError(msg){withStackSave(function(){var cmsg=allocateUTF8OnStack(msg);___dl_seterr(cmsg)})}Module["dlSetError"]=dlSetError;function dlopenInternal(handle,jsflags){var filename=UTF8ToString(handle+44);var flags=HEAP32[handle+20>>2];filename=PATH.normalize(filename);var searchpaths=[];var isValidFile=filename=>{var target=FS.findObject(filename);return target&&!target.isFolder&&!target.isDevice};if(!isValidFile(filename)){if(ENV["LD_LIBRARY_PATH"]){searchpaths=ENV["LD_LIBRARY_PATH"].split(":")}for(var ident in searchpaths){var searchfile=PATH.join2(searchpaths[ident],filename);if(isValidFile(searchfile)){filename=searchfile;break}}}var combinedFlags={global:Boolean(flags&256),nodelete:Boolean(flags&4096),loadAsync:jsflags.loadAsync,fs:jsflags.fs};if(jsflags.loadAsync){return loadDynamicLibrary(filename,combinedFlags,handle)}try{return loadDynamicLibrary(filename,combinedFlags,handle)}catch(e){dlSetError("Could not load dynamic lib: "+filename+"\n"+e);return 0}}Module["dlopenInternal"]=dlopenInternal;function __dlopen_js(handle){var jsflags={loadAsync:false,fs:FS};return dlopenInternal(handle,jsflags)}Module["__dlopen_js"]=__dlopen_js;__dlopen_js.sig="iiii";function __dlsym_js(handle,symbol){symbol=UTF8ToString(symbol);var result;if(handle==0){result=resolveGlobalSymbol(symbol,true);if(!result){dlSetError('Tried to lookup unknown symbol "'+symbol+'" in dynamic lib: RTLD_DEFAULT');return 0}}else{var lib=LDSO.loadedLibsByHandle[handle];if(!lib.module.hasOwnProperty(symbol)){dlSetError('Tried to lookup unknown symbol "'+symbol+'" in dynamic lib: '+lib.name);return 0}result=lib.module[symbol]}if(typeof result=="function"){result=addFunction(result,result.sig)}return result}Module["__dlsym_js"]=__dlsym_js;__dlsym_js.sig="iii";function __emscripten_date_now(){return Date.now()}Module["__emscripten_date_now"]=__emscripten_date_now;__emscripten_date_now.sig="j";function __emscripten_dlopen_js(handle,onsuccess,onerror){function errorCallback(e){var filename=UTF8ToString(HEAPU32[handle+44>>2]);dlSetError("Could not load dynamic lib: "+filename+"\n"+e);callUserCallback(function(){getWasmTableEntry(onerror)(handle)})}function successCallback(){callUserCallback(function(){getWasmTableEntry(onsuccess)(handle)})}var promise=dlopenInternal(handle,{loadAsync:true});if(promise){promise.then(successCallback,errorCallback)}else{errorCallback()}}Module["__emscripten_dlopen_js"]=__emscripten_dlopen_js;__emscripten_dlopen_js.sig="viiiii";function __emscripten_err(str){err(UTF8ToString(str))}Module["__emscripten_err"]=__emscripten_err;__emscripten_err.sig="vi";var nowIsMonotonic=true;Module["nowIsMonotonic"]=nowIsMonotonic;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}Module["__emscripten_get_now_is_monotonic"]=__emscripten_get_now_is_monotonic;function __emscripten_get_progname(str,len){stringToUTF8(thisProgram,str,len)}Module["__emscripten_get_progname"]=__emscripten_get_progname;__emscripten_get_progname.sig="vii";function __emscripten_out(str){out(UTF8ToString(str))}Module["__emscripten_out"]=__emscripten_out;__emscripten_out.sig="vi";function __emscripten_throw_longjmp(){throw Infinity}Module["__emscripten_throw_longjmp"]=__emscripten_throw_longjmp;__emscripten_throw_longjmp.sig="v";function __gmtime_js(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}Module["__gmtime_js"]=__gmtime_js;__gmtime_js.sig="ipp";function __localtime_js(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var start=new Date(date.getFullYear(),0,1);var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}Module["__localtime_js"]=__localtime_js;__localtime_js.sig="ipp";function __mktime_js(tmPtr){var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();return date.getTime()/1e3|0}Module["__mktime_js"]=__mktime_js;__mktime_js.sig="ip";function __mmap_js(len,prot,flags,fd,off,allocated){try{var stream=FS.getStream(fd);if(!stream)return-8;var res=FS.mmap(stream,len,off,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;return ptr}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["__mmap_js"]=__mmap_js;__mmap_js.sig="ppiiipp";function __msync_js(addr,len,flags,fd){try{SYSCALLS.doMsync(addr,FS.getStream(fd),len,flags,0);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["__msync_js"]=__msync_js;__msync_js.sig="ippii";function __munmap_js(addr,len,prot,flags,fd,offset){try{var stream=FS.getStream(fd);if(stream){if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}FS.munmap(stream)}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["__munmap_js"]=__munmap_js;__munmap_js.sig="vppiiip";function __timegm_js(tmPtr){var time=Date.UTC(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var date=new Date(time);HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;return date.getTime()/1e3|0}Module["__timegm_js"]=__timegm_js;__timegm_js.sig="ip";function _tzset_impl(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAP32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}Module["_tzset_impl"]=_tzset_impl;_tzset_impl.sig="viii";function __tzset_js(timezone,daylight,tzname){if(__tzset_js.called)return;__tzset_js.called=true;_tzset_impl(timezone,daylight,tzname)}Module["__tzset_js"]=__tzset_js;__tzset_js.sig="vppp";function _abort(){abort("")}Module["_abort"]=_abort;_abort.sig="v";var AL={QUEUE_INTERVAL:25,QUEUE_LOOKAHEAD:.1,DEVICE_NAME:"Emscripten OpenAL",CAPTURE_DEVICE_NAME:"Emscripten OpenAL capture",ALC_EXTENSIONS:{ALC_SOFT_pause_device:true,ALC_SOFT_HRTF:true},AL_EXTENSIONS:{AL_EXT_float32:true,AL_SOFT_loop_points:true,AL_SOFT_source_length:true,AL_EXT_source_distance_model:true,AL_SOFT_source_spatialize:true},_alcErr:0,alcErr:0,deviceRefCounts:{},alcStringCache:{},paused:false,stringCache:{},contexts:{},currentCtx:null,buffers:{0:{id:0,refCount:0,audioBuf:null,frequency:0,bytesPerSample:2,channels:1,length:0}},paramArray:[],_nextId:1,newId:function(){return AL.freeIds.length>0?AL.freeIds.pop():AL._nextId++},freeIds:[],scheduleContextAudio:function(ctx){if(Browser.mainLoop.timingMode===1&&document["visibilityState"]!="visible"){return}for(var i in ctx.sources){AL.scheduleSourceAudio(ctx.sources[i])}},scheduleSourceAudio:function(src,lookahead){if(Browser.mainLoop.timingMode===1&&document["visibilityState"]!="visible"){return}if(src.state!==4114){return}var currentTime=AL.updateSourceTime(src);var startTime=src.bufStartTime;var startOffset=src.bufOffset;var bufCursor=src.bufsProcessed;for(var i=0;i=src.bufQueue.length){if(src.looping){bufCursor%=src.bufQueue.length}else{break}}var buf=src.bufQueue[bufCursor%src.bufQueue.length];if(buf.length===0){skipCount++;if(skipCount===src.bufQueue.length){break}}else{var audioSrc=src.context.audioCtx.createBufferSource();audioSrc.buffer=buf.audioBuf;audioSrc.playbackRate.value=src.playbackRate;if(buf.audioBuf._loopStart||buf.audioBuf._loopEnd){audioSrc.loopStart=buf.audioBuf._loopStart;audioSrc.loopEnd=buf.audioBuf._loopEnd}var duration=0;if(src.type===4136&&src.looping){duration=Number.POSITIVE_INFINITY;audioSrc.loop=true;if(buf.audioBuf._loopStart){audioSrc.loopStart=buf.audioBuf._loopStart}if(buf.audioBuf._loopEnd){audioSrc.loopEnd=buf.audioBuf._loopEnd}}else{duration=(buf.audioBuf.duration-startOffset)/src.playbackRate}audioSrc._startOffset=startOffset;audioSrc._duration=duration;audioSrc._skipCount=skipCount;skipCount=0;audioSrc.connect(src.gain);if(typeof audioSrc.start!="undefined"){startTime=Math.max(startTime,src.context.audioCtx.currentTime);audioSrc.start(startTime,startOffset)}else if(typeof audioSrc.noteOn!="undefined"){startTime=Math.max(startTime,src.context.audioCtx.currentTime);audioSrc.noteOn(startTime)}audioSrc._startTime=startTime;src.audioQueue.push(audioSrc);startTime+=duration}startOffset=0;bufCursor++}},updateSourceTime:function(src){var currentTime=src.context.audioCtx.currentTime;if(src.state!==4114){return currentTime}if(!isFinite(src.bufStartTime)){src.bufStartTime=currentTime-src.bufOffset/src.playbackRate;src.bufOffset=0}var nextStartTime=0;while(src.audioQueue.length){var audioSrc=src.audioQueue[0];src.bufsProcessed+=audioSrc._skipCount;nextStartTime=audioSrc._startTime+audioSrc._duration;if(currentTime=src.bufQueue.length&&!src.looping){AL.setSourceState(src,4116)}else if(src.type===4136&&src.looping){var buf=src.bufQueue[0];if(buf.length===0){src.bufOffset=0}else{var delta=(currentTime-src.bufStartTime)*src.playbackRate;var loopStart=buf.audioBuf._loopStart||0;var loopEnd=buf.audioBuf._loopEnd||buf.audioBuf.duration;if(loopEnd<=loopStart){loopEnd=buf.audioBuf.duration}if(delta0){src.bufStartTime+=Math.floor((currentTime-src.bufStartTime)/srcDuration)*srcDuration}}for(var i=0;i=src.bufQueue.length){if(src.looping){src.bufsProcessed%=src.bufQueue.length}else{AL.setSourceState(src,4116);break}}var buf=src.bufQueue[src.bufsProcessed];if(buf.length>0){nextStartTime=src.bufStartTime+buf.audioBuf.duration/src.playbackRate;if(currentTime1){src.audioQueue.length=1}},stopSourceAudio:function(src){for(var i=0;isrc.bufQueue[src.bufsProcessed].audioBuf.duration){offset-=src.bufQueue[src.bufsProcessed].audiobuf.duration;src.bufsProcessed++}src.bufOffset=offset}if(playing){AL.setSourceState(src,4114)}},getGlobalParam:function(funcname,param){if(!AL.currentCtx){return null}switch(param){case 49152:return AL.currentCtx.dopplerFactor;case 49155:return AL.currentCtx.speedOfSound;case 53248:return AL.currentCtx.distanceModel;default:AL.currentCtx.err=40962;return null}},setGlobalParam:function(funcname,param,value){if(!AL.currentCtx){return}switch(param){case 49152:if(!Number.isFinite(value)||value<0){AL.currentCtx.err=40963;return}AL.currentCtx.dopplerFactor=value;AL.updateListenerSpace(AL.currentCtx);break;case 49155:if(!Number.isFinite(value)||value<=0){AL.currentCtx.err=40963;return}AL.currentCtx.speedOfSound=value;AL.updateListenerSpace(AL.currentCtx);break;case 53248:switch(value){case 0:case 53249:case 53250:case 53251:case 53252:case 53253:case 53254:AL.currentCtx.distanceModel=value;AL.updateContextGlobal(AL.currentCtx);break;default:AL.currentCtx.err=40963;return}break;default:AL.currentCtx.err=40962;return}},getListenerParam:function(funcname,param){if(!AL.currentCtx){return null}switch(param){case 4100:return AL.currentCtx.listener.position;case 4102:return AL.currentCtx.listener.velocity;case 4111:return AL.currentCtx.listener.direction.concat(AL.currentCtx.listener.up);case 4106:return AL.currentCtx.gain.gain.value;default:AL.currentCtx.err=40962;return null}},setListenerParam:function(funcname,param,value){if(!AL.currentCtx){return}if(value===null){AL.currentCtx.err=40962;return}var listener=AL.currentCtx.listener;switch(param){case 4100:if(!Number.isFinite(value[0])||!Number.isFinite(value[1])||!Number.isFinite(value[2])){AL.currentCtx.err=40963;return}listener.position[0]=value[0];listener.position[1]=value[1];listener.position[2]=value[2];AL.updateListenerSpace(AL.currentCtx);break;case 4102:if(!Number.isFinite(value[0])||!Number.isFinite(value[1])||!Number.isFinite(value[2])){AL.currentCtx.err=40963;return}listener.velocity[0]=value[0];listener.velocity[1]=value[1];listener.velocity[2]=value[2];AL.updateListenerSpace(AL.currentCtx);break;case 4106:if(!Number.isFinite(value)||value<0){AL.currentCtx.err=40963;return}AL.currentCtx.gain.gain.value=value;break;case 4111:if(!Number.isFinite(value[0])||!Number.isFinite(value[1])||!Number.isFinite(value[2])||!Number.isFinite(value[3])||!Number.isFinite(value[4])||!Number.isFinite(value[5])){AL.currentCtx.err=40963;return}listener.direction[0]=value[0];listener.direction[1]=value[1];listener.direction[2]=value[2];listener.up[0]=value[3];listener.up[1]=value[4];listener.up[2]=value[5];AL.updateListenerSpace(AL.currentCtx);break;default:AL.currentCtx.err=40962;return}},getBufferParam:function(funcname,bufferId,param){if(!AL.currentCtx){return}var buf=AL.buffers[bufferId];if(!buf||bufferId===0){AL.currentCtx.err=40961;return}switch(param){case 8193:return buf.frequency;case 8194:return buf.bytesPerSample*8;case 8195:return buf.channels;case 8196:return buf.length*buf.bytesPerSample*buf.channels;case 8213:if(buf.length===0){return[0,0]}else{return[(buf.audioBuf._loopStart||0)*buf.frequency,(buf.audioBuf._loopEnd||buf.length)*buf.frequency]}default:AL.currentCtx.err=40962;return null}},setBufferParam:function(funcname,bufferId,param,value){if(!AL.currentCtx){return}var buf=AL.buffers[bufferId];if(!buf||bufferId===0){AL.currentCtx.err=40961;return}if(value===null){AL.currentCtx.err=40962;return}switch(param){case 8196:if(value!==0){AL.currentCtx.err=40963;return}break;case 8213:if(value[0]<0||value[0]>buf.length||value[1]<0||value[1]>buf.Length||value[0]>=value[1]){AL.currentCtx.err=40963;return}if(buf.refCount>0){AL.currentCtx.err=40964;return}if(buf.audioBuf){buf.audioBuf._loopStart=value[0]/buf.frequency;buf.audioBuf._loopEnd=value[1]/buf.frequency}break;default:AL.currentCtx.err=40962;return}},getSourceParam:function(funcname,sourceId,param){if(!AL.currentCtx){return null}var src=AL.currentCtx.sources[sourceId];if(!src){AL.currentCtx.err=40961;return null}switch(param){case 514:return src.relative;case 4097:return src.coneInnerAngle;case 4098:return src.coneOuterAngle;case 4099:return src.pitch;case 4100:return src.position;case 4101:return src.direction;case 4102:return src.velocity;case 4103:return src.looping;case 4105:if(src.type===4136){return src.bufQueue[0].id}else{return 0}case 4106:return src.gain.gain.value;case 4109:return src.minGain;case 4110:return src.maxGain;case 4112:return src.state;case 4117:if(src.bufQueue.length===1&&src.bufQueue[0].id===0){return 0}else{return src.bufQueue.length}case 4118:if(src.bufQueue.length===1&&src.bufQueue[0].id===0||src.looping){return 0}else{return src.bufsProcessed}case 4128:return src.refDistance;case 4129:return src.rolloffFactor;case 4130:return src.coneOuterGain;case 4131:return src.maxDistance;case 4132:return AL.sourceTell(src);case 4133:var offset=AL.sourceTell(src);if(offset>0){offset*=src.bufQueue[0].frequency}return offset;case 4134:var offset=AL.sourceTell(src);if(offset>0){offset*=src.bufQueue[0].frequency*src.bufQueue[0].bytesPerSample}return offset;case 4135:return src.type;case 4628:return src.spatialize;case 8201:var length=0;var bytesPerFrame=0;for(var i=0;i0){var audioSrc=src.audioQueue[0];audioSrc.loop=true;audioSrc._duration=Number.POSITIVE_INFINITY}}else if(value===0){src.looping=false;var currentTime=AL.updateSourceTime(src);if(src.type===4136&&src.audioQueue.length>0){var audioSrc=src.audioQueue[0];audioSrc.loop=false;audioSrc._duration=src.bufQueue[0].audioBuf.duration/src.playbackRate;audioSrc._startTime=currentTime-src.bufOffset/src.playbackRate}}else{AL.currentCtx.err=40963;return}break;case 4105:if(src.state===4114||src.state===4115){AL.currentCtx.err=40964;return}if(value===0){for(var i in src.bufQueue){src.bufQueue[i].refCount--}src.bufQueue.length=1;src.bufQueue[0]=AL.buffers[0];src.bufsProcessed=0;src.type=4144}else{var buf=AL.buffers[value];if(!buf){AL.currentCtx.err=40963;return}for(var i in src.bufQueue){src.bufQueue[i].refCount--}src.bufQueue.length=0;buf.refCount++;src.bufQueue=[buf];src.bufsProcessed=0;src.type=4136}AL.initSourcePanner(src);AL.scheduleSourceAudio(src);break;case 4106:if(!Number.isFinite(value)||value<0){AL.currentCtx.err=40963;return}src.gain.gain.value=value;break;case 4109:if(!Number.isFinite(value)||value<0||value>Math.min(src.maxGain,1)){AL.currentCtx.err=40963;return}src.minGain=value;break;case 4110:if(!Number.isFinite(value)||value1){AL.currentCtx.err=40963;return}src.maxGain=value;break;case 4128:if(!Number.isFinite(value)||value<0){AL.currentCtx.err=40963;return}src.refDistance=value;if(src.panner){src.panner.refDistance=value}break;case 4129:if(!Number.isFinite(value)||value<0){AL.currentCtx.err=40963;return}src.rolloffFactor=value;if(src.panner){src.panner.rolloffFactor=value}break;case 4130:if(!Number.isFinite(value)||value<0||value>1){AL.currentCtx.err=40963;return}src.coneOuterGain=value;if(src.panner){src.panner.coneOuterGain=value}break;case 4131:if(!Number.isFinite(value)||value<0){AL.currentCtx.err=40963;return}src.maxDistance=value;if(src.panner){src.panner.maxDistance=value}break;case 4132:if(value<0||value>AL.sourceDuration(src)){AL.currentCtx.err=40963;return}AL.sourceSeek(src,value);break;case 4133:var srcLen=AL.sourceDuration(src);if(srcLen>0){var frequency;for(var bufId in src.bufQueue){if(bufId){frequency=src.bufQueue[bufId].frequency;break}}value/=frequency}if(value<0||value>srcLen){AL.currentCtx.err=40963;return}AL.sourceSeek(src,value);break;case 4134:var srcLen=AL.sourceDuration(src);if(srcLen>0){var bytesPerSec;for(var bufId in src.bufQueue){if(bufId){var buf=src.bufQueue[bufId];bytesPerSec=buf.frequency*buf.bytesPerSample*buf.channels;break}}value/=bytesPerSec}if(value<0||value>srcLen){AL.currentCtx.err=40963;return}AL.sourceSeek(src,value);break;case 4628:if(value!==0&&value!==1&&value!==2){AL.currentCtx.err=40963;return}src.spatialize=value;AL.initSourcePanner(src);break;case 8201:case 8202:case 8203:AL.currentCtx.err=40964;break;case 53248:switch(value){case 0:case 53249:case 53250:case 53251:case 53252:case 53253:case 53254:src.distanceModel=value;if(AL.currentCtx.sourceDistanceModel){AL.updateContextGlobal(AL.currentCtx)}break;default:AL.currentCtx.err=40963;return}break;default:AL.currentCtx.err=40962;return}},captures:{},sharedCaptureAudioCtx:null,requireValidCaptureDevice:function(deviceId,funcname){if(deviceId===0){AL.alcErr=40961;return null}var c=AL.captures[deviceId];if(!c){AL.alcErr=40961;return null}var err=c.mediaStreamError;if(err){AL.alcErr=40961;return null}return c}};Module["AL"]=AL;function _alBuffer3f(bufferId,param,value0,value1,value2){AL.setBufferParam("alBuffer3f",bufferId,param,null)}Module["_alBuffer3f"]=_alBuffer3f;_alBuffer3f.sig="viifff";function _alBuffer3i(bufferId,param,value0,value1,value2){AL.setBufferParam("alBuffer3i",bufferId,param,null)}Module["_alBuffer3i"]=_alBuffer3i;_alBuffer3i.sig="viiiii";function _alBufferData(bufferId,format,pData,size,freq){if(!AL.currentCtx){return}var buf=AL.buffers[bufferId];if(!buf){AL.currentCtx.err=40963;return}if(freq<=0){AL.currentCtx.err=40963;return}var audioBuf=null;try{switch(format){case 4352:if(size>0){audioBuf=AL.currentCtx.audioCtx.createBuffer(1,size,freq);var channel0=audioBuf.getChannelData(0);for(var i=0;i0){audioBuf=AL.currentCtx.audioCtx.createBuffer(1,size>>1,freq);var channel0=audioBuf.getChannelData(0);pData>>=1;for(var i=0;i>1;++i){channel0[i]=HEAP16[pData++]*30517578125e-15}}buf.bytesPerSample=2;buf.channels=1;buf.length=size>>1;break;case 4354:if(size>0){audioBuf=AL.currentCtx.audioCtx.createBuffer(2,size>>1,freq);var channel0=audioBuf.getChannelData(0);var channel1=audioBuf.getChannelData(1);for(var i=0;i>1;++i){channel0[i]=HEAPU8[pData++]*.0078125-1;channel1[i]=HEAPU8[pData++]*.0078125-1}}buf.bytesPerSample=1;buf.channels=2;buf.length=size>>1;break;case 4355:if(size>0){audioBuf=AL.currentCtx.audioCtx.createBuffer(2,size>>2,freq);var channel0=audioBuf.getChannelData(0);var channel1=audioBuf.getChannelData(1);pData>>=1;for(var i=0;i>2;++i){channel0[i]=HEAP16[pData++]*30517578125e-15;channel1[i]=HEAP16[pData++]*30517578125e-15}}buf.bytesPerSample=2;buf.channels=2;buf.length=size>>2;break;case 65552:if(size>0){audioBuf=AL.currentCtx.audioCtx.createBuffer(1,size>>2,freq);var channel0=audioBuf.getChannelData(0);pData>>=2;for(var i=0;i>2;++i){channel0[i]=HEAPF32[pData++]}}buf.bytesPerSample=4;buf.channels=1;buf.length=size>>2;break;case 65553:if(size>0){audioBuf=AL.currentCtx.audioCtx.createBuffer(2,size>>3,freq);var channel0=audioBuf.getChannelData(0);var channel1=audioBuf.getChannelData(1);pData>>=2;for(var i=0;i>3;++i){channel0[i]=HEAPF32[pData++];channel1[i]=HEAPF32[pData++]}}buf.bytesPerSample=4;buf.channels=2;buf.length=size>>3;break;default:AL.currentCtx.err=40963;return}buf.frequency=freq;buf.audioBuf=audioBuf}catch(e){AL.currentCtx.err=40963;return}}Module["_alBufferData"]=_alBufferData;_alBufferData.sig="viiiii";function _alBufferf(bufferId,param,value){AL.setBufferParam("alBufferf",bufferId,param,null)}Module["_alBufferf"]=_alBufferf;_alBufferf.sig="viif";function _alBufferfv(bufferId,param,pValues){if(!AL.currentCtx){return}if(!pValues){AL.currentCtx.err=40963;return}AL.setBufferParam("alBufferfv",bufferId,param,null)}Module["_alBufferfv"]=_alBufferfv;_alBufferfv.sig="viii";function _alBufferi(bufferId,param,value){AL.setBufferParam("alBufferi",bufferId,param,null)}Module["_alBufferi"]=_alBufferi;_alBufferi.sig="viii";function _alBufferiv(bufferId,param,pValues){if(!AL.currentCtx){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 8213:AL.paramArray[0]=HEAP32[pValues>>2];AL.paramArray[1]=HEAP32[pValues+4>>2];AL.setBufferParam("alBufferiv",bufferId,param,AL.paramArray);break;default:AL.setBufferParam("alBufferiv",bufferId,param,null);break}}Module["_alBufferiv"]=_alBufferiv;_alBufferiv.sig="viii";function _alDeleteBuffers(count,pBufferIds){if(!AL.currentCtx){return}for(var i=0;i>2];if(bufId===0){continue}if(!AL.buffers[bufId]){AL.currentCtx.err=40961;return}if(AL.buffers[bufId].refCount){AL.currentCtx.err=40964;return}}for(var i=0;i>2];if(bufId===0){continue}AL.deviceRefCounts[AL.buffers[bufId].deviceId]--;delete AL.buffers[bufId];AL.freeIds.push(bufId)}}Module["_alDeleteBuffers"]=_alDeleteBuffers;_alDeleteBuffers.sig="vii";function _alSourcei(sourceId,param,value){switch(param){case 514:case 4097:case 4098:case 4103:case 4105:case 4128:case 4129:case 4131:case 4132:case 4133:case 4134:case 4628:case 8201:case 8202:case 53248:AL.setSourceParam("alSourcei",sourceId,param,value);break;default:AL.setSourceParam("alSourcei",sourceId,param,null);break}}Module["_alSourcei"]=_alSourcei;_alSourcei.sig="viii";function _alDeleteSources(count,pSourceIds){if(!AL.currentCtx){return}for(var i=0;i>2];if(!AL.currentCtx.sources[srcId]){AL.currentCtx.err=40961;return}}for(var i=0;i>2];AL.setSourceState(AL.currentCtx.sources[srcId],4116);_alSourcei(srcId,4105,0);delete AL.currentCtx.sources[srcId];AL.freeIds.push(srcId)}}Module["_alDeleteSources"]=_alDeleteSources;_alDeleteSources.sig="vii";function _alDisable(param){if(!AL.currentCtx){return}switch(param){case"AL_SOURCE_DISTANCE_MODEL":AL.currentCtx.sourceDistanceModel=false;AL.updateContextGlobal(AL.currentCtx);break;default:AL.currentCtx.err=40962;return}}Module["_alDisable"]=_alDisable;_alDisable.sig="vi";function _alDistanceModel(model){AL.setGlobalParam("alDistanceModel",53248,model)}Module["_alDistanceModel"]=_alDistanceModel;_alDistanceModel.sig="vi";function _alDopplerFactor(value){AL.setGlobalParam("alDopplerFactor",49152,value)}Module["_alDopplerFactor"]=_alDopplerFactor;_alDopplerFactor.sig="vi";function _alDopplerVelocity(value){warnOnce("alDopplerVelocity() is deprecated, and only kept for compatibility with OpenAL 1.0. Use alSpeedOfSound() instead.");if(!AL.currentCtx){return}if(value<=0){AL.currentCtx.err=40963;return}}Module["_alDopplerVelocity"]=_alDopplerVelocity;_alDopplerVelocity.sig="vi";function _alEnable(param){if(!AL.currentCtx){return}switch(param){case"AL_SOURCE_DISTANCE_MODEL":AL.currentCtx.sourceDistanceModel=true;AL.updateContextGlobal(AL.currentCtx);break;default:AL.currentCtx.err=40962;return}}Module["_alEnable"]=_alEnable;_alEnable.sig="vi";function _alGenBuffers(count,pBufferIds){if(!AL.currentCtx){return}for(var i=0;i>2]=buf.id}}Module["_alGenBuffers"]=_alGenBuffers;_alGenBuffers.sig="vii";function _alGenSources(count,pSourceIds){if(!AL.currentCtx){return}for(var i=0;i>2]=src.id}}Module["_alGenSources"]=_alGenSources;_alGenSources.sig="vii";function _alGetBoolean(param){var val=AL.getGlobalParam("alGetBoolean",param);if(val===null){return 0}switch(param){case 49152:case 49155:case 53248:return val!==0?1:0;default:AL.currentCtx.err=40962;return 0}}Module["_alGetBoolean"]=_alGetBoolean;_alGetBoolean.sig="ii";function _alGetBooleanv(param,pValues){var val=AL.getGlobalParam("alGetBooleanv",param);if(val===null||!pValues){return}switch(param){case 49152:case 49155:case 53248:HEAP8[pValues>>0]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetBooleanv"]=_alGetBooleanv;_alGetBooleanv.sig="vii";function _alGetBuffer3f(bufferId,param,pValue0,pValue1,pValue2){var val=AL.getBufferParam("alGetBuffer3f",bufferId,param);if(val===null){return}if(!pValue0||!pValue1||!pValue2){AL.currentCtx.err=40963;return}AL.currentCtx.err=40962}Module["_alGetBuffer3f"]=_alGetBuffer3f;_alGetBuffer3f.sig="viiiii";function _alGetBuffer3i(bufferId,param,pValue0,pValue1,pValue2){var val=AL.getBufferParam("alGetBuffer3i",bufferId,param);if(val===null){return}if(!pValue0||!pValue1||!pValue2){AL.currentCtx.err=40963;return}AL.currentCtx.err=40962}Module["_alGetBuffer3i"]=_alGetBuffer3i;_alGetBuffer3i.sig="viiiii";function _alGetBufferf(bufferId,param,pValue){var val=AL.getBufferParam("alGetBufferf",bufferId,param);if(val===null){return}if(!pValue){AL.currentCtx.err=40963;return}AL.currentCtx.err=40962}Module["_alGetBufferf"]=_alGetBufferf;_alGetBufferf.sig="viii";function _alGetBufferfv(bufferId,param,pValues){var val=AL.getBufferParam("alGetBufferfv",bufferId,param);if(val===null){return}if(!pValues){AL.currentCtx.err=40963;return}AL.currentCtx.err=40962}Module["_alGetBufferfv"]=_alGetBufferfv;_alGetBufferfv.sig="viii";function _alGetBufferi(bufferId,param,pValue){var val=AL.getBufferParam("alGetBufferi",bufferId,param);if(val===null){return}if(!pValue){AL.currentCtx.err=40963;return}switch(param){case 8193:case 8194:case 8195:case 8196:HEAP32[pValue>>2]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetBufferi"]=_alGetBufferi;_alGetBufferi.sig="viii";function _alGetBufferiv(bufferId,param,pValues){var val=AL.getBufferParam("alGetBufferiv",bufferId,param);if(val===null){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 8193:case 8194:case 8195:case 8196:HEAP32[pValues>>2]=val;break;case 8213:HEAP32[pValues>>2]=val[0];HEAP32[pValues+4>>2]=val[1];break;default:AL.currentCtx.err=40962;return}}Module["_alGetBufferiv"]=_alGetBufferiv;_alGetBufferiv.sig="viii";function _alGetDouble(param){var val=AL.getGlobalParam("alGetDouble",param);if(val===null){return 0}switch(param){case 49152:case 49155:case 53248:return val;default:AL.currentCtx.err=40962;return 0}}Module["_alGetDouble"]=_alGetDouble;_alGetDouble.sig="di";function _alGetDoublev(param,pValues){var val=AL.getGlobalParam("alGetDoublev",param);if(val===null||!pValues){return}switch(param){case 49152:case 49155:case 53248:HEAPF64[pValues>>3]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetDoublev"]=_alGetDoublev;_alGetDoublev.sig="vii";function _alGetEnumValue(pEnumName){if(!AL.currentCtx){return 0}if(!pEnumName){AL.currentCtx.err=40963;return 0}var name=UTF8ToString(pEnumName);switch(name){case"AL_BITS":return 8194;case"AL_BUFFER":return 4105;case"AL_BUFFERS_PROCESSED":return 4118;case"AL_BUFFERS_QUEUED":return 4117;case"AL_BYTE_OFFSET":return 4134;case"AL_CHANNELS":return 8195;case"AL_CONE_INNER_ANGLE":return 4097;case"AL_CONE_OUTER_ANGLE":return 4098;case"AL_CONE_OUTER_GAIN":return 4130;case"AL_DIRECTION":return 4101;case"AL_DISTANCE_MODEL":return 53248;case"AL_DOPPLER_FACTOR":return 49152;case"AL_DOPPLER_VELOCITY":return 49153;case"AL_EXPONENT_DISTANCE":return 53253;case"AL_EXPONENT_DISTANCE_CLAMPED":return 53254;case"AL_EXTENSIONS":return 45060;case"AL_FORMAT_MONO16":return 4353;case"AL_FORMAT_MONO8":return 4352;case"AL_FORMAT_STEREO16":return 4355;case"AL_FORMAT_STEREO8":return 4354;case"AL_FREQUENCY":return 8193;case"AL_GAIN":return 4106;case"AL_INITIAL":return 4113;case"AL_INVALID":return-1;case"AL_ILLEGAL_ENUM":case"AL_INVALID_ENUM":return 40962;case"AL_INVALID_NAME":return 40961;case"AL_ILLEGAL_COMMAND":case"AL_INVALID_OPERATION":return 40964;case"AL_INVALID_VALUE":return 40963;case"AL_INVERSE_DISTANCE":return 53249;case"AL_INVERSE_DISTANCE_CLAMPED":return 53250;case"AL_LINEAR_DISTANCE":return 53251;case"AL_LINEAR_DISTANCE_CLAMPED":return 53252;case"AL_LOOPING":return 4103;case"AL_MAX_DISTANCE":return 4131;case"AL_MAX_GAIN":return 4110;case"AL_MIN_GAIN":return 4109;case"AL_NONE":return 0;case"AL_NO_ERROR":return 0;case"AL_ORIENTATION":return 4111;case"AL_OUT_OF_MEMORY":return 40965;case"AL_PAUSED":return 4115;case"AL_PENDING":return 8209;case"AL_PITCH":return 4099;case"AL_PLAYING":return 4114;case"AL_POSITION":return 4100;case"AL_PROCESSED":return 8210;case"AL_REFERENCE_DISTANCE":return 4128;case"AL_RENDERER":return 45059;case"AL_ROLLOFF_FACTOR":return 4129;case"AL_SAMPLE_OFFSET":return 4133;case"AL_SEC_OFFSET":return 4132;case"AL_SIZE":return 8196;case"AL_SOURCE_RELATIVE":return 514;case"AL_SOURCE_STATE":return 4112;case"AL_SOURCE_TYPE":return 4135;case"AL_SPEED_OF_SOUND":return 49155;case"AL_STATIC":return 4136;case"AL_STOPPED":return 4116;case"AL_STREAMING":return 4137;case"AL_UNDETERMINED":return 4144;case"AL_UNUSED":return 8208;case"AL_VELOCITY":return 4102;case"AL_VENDOR":return 45057;case"AL_VERSION":return 45058;case"AL_AUTO_SOFT":return 2;case"AL_SOURCE_DISTANCE_MODEL":return 512;case"AL_SOURCE_SPATIALIZE_SOFT":return 4628;case"AL_LOOP_POINTS_SOFT":return 8213;case"AL_BYTE_LENGTH_SOFT":return 8201;case"AL_SAMPLE_LENGTH_SOFT":return 8202;case"AL_SEC_LENGTH_SOFT":return 8203;case"AL_FORMAT_MONO_FLOAT32":return 65552;case"AL_FORMAT_STEREO_FLOAT32":return 65553;default:AL.currentCtx.err=40963;return 0}}Module["_alGetEnumValue"]=_alGetEnumValue;_alGetEnumValue.sig="ii";function _alGetError(){if(!AL.currentCtx){return 40964}else{var err=AL.currentCtx.err;AL.currentCtx.err=0;return err}}Module["_alGetError"]=_alGetError;_alGetError.sig="i";function _alGetFloat(param){var val=AL.getGlobalParam("alGetFloat",param);if(val===null){return 0}switch(param){case 49152:case 49155:case 53248:return val;default:return 0}}Module["_alGetFloat"]=_alGetFloat;_alGetFloat.sig="fi";function _alGetFloatv(param,pValues){var val=AL.getGlobalParam("alGetFloatv",param);if(val===null||!pValues){return}switch(param){case 49152:case 49155:case 53248:HEAPF32[pValues>>2]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetFloatv"]=_alGetFloatv;_alGetFloatv.sig="vii";function _alGetInteger(param){var val=AL.getGlobalParam("alGetInteger",param);if(val===null){return 0}switch(param){case 49152:case 49155:case 53248:return val;default:AL.currentCtx.err=40962;return 0}}Module["_alGetInteger"]=_alGetInteger;_alGetInteger.sig="ii";function _alGetIntegerv(param,pValues){var val=AL.getGlobalParam("alGetIntegerv",param);if(val===null||!pValues){return}switch(param){case 49152:case 49155:case 53248:HEAP32[pValues>>2]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetIntegerv"]=_alGetIntegerv;_alGetIntegerv.sig="vii";function _alGetListener3f(param,pValue0,pValue1,pValue2){var val=AL.getListenerParam("alGetListener3f",param);if(val===null){return}if(!pValue0||!pValue1||!pValue2){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4102:HEAPF32[pValue0>>2]=val[0];HEAPF32[pValue1>>2]=val[1];HEAPF32[pValue2>>2]=val[2];break;default:AL.currentCtx.err=40962;return}}Module["_alGetListener3f"]=_alGetListener3f;_alGetListener3f.sig="viiii";function _alGetListener3i(param,pValue0,pValue1,pValue2){var val=AL.getListenerParam("alGetListener3i",param);if(val===null){return}if(!pValue0||!pValue1||!pValue2){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4102:HEAP32[pValue0>>2]=val[0];HEAP32[pValue1>>2]=val[1];HEAP32[pValue2>>2]=val[2];break;default:AL.currentCtx.err=40962;return}}Module["_alGetListener3i"]=_alGetListener3i;_alGetListener3i.sig="viiii";function _alGetListenerf(param,pValue){var val=AL.getListenerParam("alGetListenerf",param);if(val===null){return}if(!pValue){AL.currentCtx.err=40963;return}switch(param){case 4106:HEAPF32[pValue>>2]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetListenerf"]=_alGetListenerf;_alGetListenerf.sig="vii";function _alGetListenerfv(param,pValues){var val=AL.getListenerParam("alGetListenerfv",param);if(val===null){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4102:HEAPF32[pValues>>2]=val[0];HEAPF32[pValues+4>>2]=val[1];HEAPF32[pValues+8>>2]=val[2];break;case 4111:HEAPF32[pValues>>2]=val[0];HEAPF32[pValues+4>>2]=val[1];HEAPF32[pValues+8>>2]=val[2];HEAPF32[pValues+12>>2]=val[3];HEAPF32[pValues+16>>2]=val[4];HEAPF32[pValues+20>>2]=val[5];break;default:AL.currentCtx.err=40962;return}}Module["_alGetListenerfv"]=_alGetListenerfv;_alGetListenerfv.sig="vii";function _alGetListeneri(param,pValue){var val=AL.getListenerParam("alGetListeneri",param);if(val===null){return}if(!pValue){AL.currentCtx.err=40963;return}AL.currentCtx.err=40962}Module["_alGetListeneri"]=_alGetListeneri;_alGetListeneri.sig="vii";function _alGetListeneriv(param,pValues){var val=AL.getListenerParam("alGetListeneriv",param);if(val===null){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4102:HEAP32[pValues>>2]=val[0];HEAP32[pValues+4>>2]=val[1];HEAP32[pValues+8>>2]=val[2];break;case 4111:HEAP32[pValues>>2]=val[0];HEAP32[pValues+4>>2]=val[1];HEAP32[pValues+8>>2]=val[2];HEAP32[pValues+12>>2]=val[3];HEAP32[pValues+16>>2]=val[4];HEAP32[pValues+20>>2]=val[5];break;default:AL.currentCtx.err=40962;return}}Module["_alGetListeneriv"]=_alGetListeneriv;_alGetListeneriv.sig="vii";function _alGetSource3f(sourceId,param,pValue0,pValue1,pValue2){var val=AL.getSourceParam("alGetSource3f",sourceId,param);if(val===null){return}if(!pValue0||!pValue1||!pValue2){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4101:case 4102:HEAPF32[pValue0>>2]=val[0];HEAPF32[pValue1>>2]=val[1];HEAPF32[pValue2>>2]=val[2];break;default:AL.currentCtx.err=40962;return}}Module["_alGetSource3f"]=_alGetSource3f;_alGetSource3f.sig="viiiii";function _alGetSource3i(sourceId,param,pValue0,pValue1,pValue2){var val=AL.getSourceParam("alGetSource3i",sourceId,param);if(val===null){return}if(!pValue0||!pValue1||!pValue2){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4101:case 4102:HEAP32[pValue0>>2]=val[0];HEAP32[pValue1>>2]=val[1];HEAP32[pValue2>>2]=val[2];break;default:AL.currentCtx.err=40962;return}}Module["_alGetSource3i"]=_alGetSource3i;_alGetSource3i.sig="viiiii";function _alGetSourcef(sourceId,param,pValue){var val=AL.getSourceParam("alGetSourcef",sourceId,param);if(val===null){return}if(!pValue){AL.currentCtx.err=40963;return}switch(param){case 4097:case 4098:case 4099:case 4106:case 4109:case 4110:case 4128:case 4129:case 4130:case 4131:case 4132:case 4133:case 4134:case 8203:HEAPF32[pValue>>2]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetSourcef"]=_alGetSourcef;_alGetSourcef.sig="viii";function _alGetSourcefv(sourceId,param,pValues){var val=AL.getSourceParam("alGetSourcefv",sourceId,param);if(val===null){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 4097:case 4098:case 4099:case 4106:case 4109:case 4110:case 4128:case 4129:case 4130:case 4131:case 4132:case 4133:case 4134:case 8203:HEAPF32[pValues>>2]=val[0];break;case 4100:case 4101:case 4102:HEAPF32[pValues>>2]=val[0];HEAPF32[pValues+4>>2]=val[1];HEAPF32[pValues+8>>2]=val[2];break;default:AL.currentCtx.err=40962;return}}Module["_alGetSourcefv"]=_alGetSourcefv;_alGetSourcefv.sig="viii";function _alGetSourcei(sourceId,param,pValue){var val=AL.getSourceParam("alGetSourcei",sourceId,param);if(val===null){return}if(!pValue){AL.currentCtx.err=40963;return}switch(param){case 514:case 4097:case 4098:case 4103:case 4105:case 4112:case 4117:case 4118:case 4128:case 4129:case 4131:case 4132:case 4133:case 4134:case 4135:case 4628:case 8201:case 8202:case 53248:HEAP32[pValue>>2]=val;break;default:AL.currentCtx.err=40962;return}}Module["_alGetSourcei"]=_alGetSourcei;_alGetSourcei.sig="viii";function _alGetSourceiv(sourceId,param,pValues){var val=AL.getSourceParam("alGetSourceiv",sourceId,param);if(val===null){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 514:case 4097:case 4098:case 4103:case 4105:case 4112:case 4117:case 4118:case 4128:case 4129:case 4131:case 4132:case 4133:case 4134:case 4135:case 4628:case 8201:case 8202:case 53248:HEAP32[pValues>>2]=val;break;case 4100:case 4101:case 4102:HEAP32[pValues>>2]=val[0];HEAP32[pValues+4>>2]=val[1];HEAP32[pValues+8>>2]=val[2];break;default:AL.currentCtx.err=40962;return}}Module["_alGetSourceiv"]=_alGetSourceiv;_alGetSourceiv.sig="viii";function _alGetString(param){if(AL.stringCache[param]){return AL.stringCache[param]}var ret;switch(param){case 0:ret="No Error";break;case 40961:ret="Invalid Name";break;case 40962:ret="Invalid Enum";break;case 40963:ret="Invalid Value";break;case 40964:ret="Invalid Operation";break;case 40965:ret="Out of Memory";break;case 45057:ret="Emscripten";break;case 45058:ret="1.1";break;case 45059:ret="WebAudio";break;case 45060:ret="";for(var ext in AL.AL_EXTENSIONS){ret=ret.concat(ext);ret=ret.concat(" ")}ret=ret.trim();break;default:if(AL.currentCtx){AL.currentCtx.err=40962}else{}return 0}ret=allocateUTF8(ret);AL.stringCache[param]=ret;return ret}Module["_alGetString"]=_alGetString;_alGetString.sig="ii";function _alIsBuffer(bufferId){if(!AL.currentCtx){return false}if(bufferId>AL.buffers.length){return false}if(!AL.buffers[bufferId]){return false}else{return true}}Module["_alIsBuffer"]=_alIsBuffer;_alIsBuffer.sig="ii";function _alIsEnabled(param){if(!AL.currentCtx){return 0}switch(param){case"AL_SOURCE_DISTANCE_MODEL":return AL.currentCtx.sourceDistanceModel?0:1;default:AL.currentCtx.err=40962;return 0}}Module["_alIsEnabled"]=_alIsEnabled;_alIsEnabled.sig="ii";function _alIsExtensionPresent(pExtName){var name=UTF8ToString(pExtName);return AL.AL_EXTENSIONS[name]?1:0}Module["_alIsExtensionPresent"]=_alIsExtensionPresent;_alIsExtensionPresent.sig="ii";function _alIsSource(sourceId){if(!AL.currentCtx){return false}if(!AL.currentCtx.sources[sourceId]){return false}else{return true}}Module["_alIsSource"]=_alIsSource;_alIsSource.sig="ii";function _alListener3f(param,value0,value1,value2){switch(param){case 4100:case 4102:AL.paramArray[0]=value0;AL.paramArray[1]=value1;AL.paramArray[2]=value2;AL.setListenerParam("alListener3f",param,AL.paramArray);break;default:AL.setListenerParam("alListener3f",param,null);break}}Module["_alListener3f"]=_alListener3f;_alListener3f.sig="vifff";function _alListener3i(param,value0,value1,value2){switch(param){case 4100:case 4102:AL.paramArray[0]=value0;AL.paramArray[1]=value1;AL.paramArray[2]=value2;AL.setListenerParam("alListener3i",param,AL.paramArray);break;default:AL.setListenerParam("alListener3i",param,null);break}}Module["_alListener3i"]=_alListener3i;_alListener3i.sig="viiii";function _alListenerf(param,value){switch(param){case 4106:AL.setListenerParam("alListenerf",param,value);break;default:AL.setListenerParam("alListenerf",param,null);break}}Module["_alListenerf"]=_alListenerf;_alListenerf.sig="vif";function _alListenerfv(param,pValues){if(!AL.currentCtx){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4102:AL.paramArray[0]=HEAPF32[pValues>>2];AL.paramArray[1]=HEAPF32[pValues+4>>2];AL.paramArray[2]=HEAPF32[pValues+8>>2];AL.setListenerParam("alListenerfv",param,AL.paramArray);break;case 4111:AL.paramArray[0]=HEAPF32[pValues>>2];AL.paramArray[1]=HEAPF32[pValues+4>>2];AL.paramArray[2]=HEAPF32[pValues+8>>2];AL.paramArray[3]=HEAPF32[pValues+12>>2];AL.paramArray[4]=HEAPF32[pValues+16>>2];AL.paramArray[5]=HEAPF32[pValues+20>>2];AL.setListenerParam("alListenerfv",param,AL.paramArray);break;default:AL.setListenerParam("alListenerfv",param,null);break}}Module["_alListenerfv"]=_alListenerfv;_alListenerfv.sig="vii";function _alListeneri(param,value){AL.setListenerParam("alListeneri",param,null)}Module["_alListeneri"]=_alListeneri;_alListeneri.sig="vii";function _alListeneriv(param,pValues){if(!AL.currentCtx){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 4100:case 4102:AL.paramArray[0]=HEAP32[pValues>>2];AL.paramArray[1]=HEAP32[pValues+4>>2];AL.paramArray[2]=HEAP32[pValues+8>>2];AL.setListenerParam("alListeneriv",param,AL.paramArray);break;case 4111:AL.paramArray[0]=HEAP32[pValues>>2];AL.paramArray[1]=HEAP32[pValues+4>>2];AL.paramArray[2]=HEAP32[pValues+8>>2];AL.paramArray[3]=HEAP32[pValues+12>>2];AL.paramArray[4]=HEAP32[pValues+16>>2];AL.paramArray[5]=HEAP32[pValues+20>>2];AL.setListenerParam("alListeneriv",param,AL.paramArray);break;default:AL.setListenerParam("alListeneriv",param,null);break}}Module["_alListeneriv"]=_alListeneriv;_alListeneriv.sig="vii";function _alSource3f(sourceId,param,value0,value1,value2){switch(param){case 4100:case 4101:case 4102:AL.paramArray[0]=value0;AL.paramArray[1]=value1;AL.paramArray[2]=value2;AL.setSourceParam("alSource3f",sourceId,param,AL.paramArray);break;default:AL.setSourceParam("alSource3f",sourceId,param,null);break}}Module["_alSource3f"]=_alSource3f;_alSource3f.sig="viifff";function _alSource3i(sourceId,param,value0,value1,value2){switch(param){case 4100:case 4101:case 4102:AL.paramArray[0]=value0;AL.paramArray[1]=value1;AL.paramArray[2]=value2;AL.setSourceParam("alSource3i",sourceId,param,AL.paramArray);break;default:AL.setSourceParam("alSource3i",sourceId,param,null);break}}Module["_alSource3i"]=_alSource3i;_alSource3i.sig="viiiii";function _alSourcePause(sourceId){if(!AL.currentCtx){return}var src=AL.currentCtx.sources[sourceId];if(!src){AL.currentCtx.err=40961;return}AL.setSourceState(src,4115)}Module["_alSourcePause"]=_alSourcePause;_alSourcePause.sig="vi";function _alSourcePausev(count,pSourceIds){if(!AL.currentCtx){return}if(!pSourceIds){AL.currentCtx.err=40963}for(var i=0;i>2]]){AL.currentCtx.err=40961;return}}for(var i=0;i>2];AL.setSourceState(AL.currentCtx.sources[srcId],4115)}}Module["_alSourcePausev"]=_alSourcePausev;_alSourcePausev.sig="vii";function _alSourcePlay(sourceId){if(!AL.currentCtx){return}var src=AL.currentCtx.sources[sourceId];if(!src){AL.currentCtx.err=40961;return}AL.setSourceState(src,4114)}Module["_alSourcePlay"]=_alSourcePlay;_alSourcePlay.sig="vi";function _alSourcePlayv(count,pSourceIds){if(!AL.currentCtx){return}if(!pSourceIds){AL.currentCtx.err=40963}for(var i=0;i>2]]){AL.currentCtx.err=40961;return}}for(var i=0;i>2];AL.setSourceState(AL.currentCtx.sources[srcId],4114)}}Module["_alSourcePlayv"]=_alSourcePlayv;_alSourcePlayv.sig="vii";function _alSourceQueueBuffers(sourceId,count,pBufferIds){if(!AL.currentCtx){return}var src=AL.currentCtx.sources[sourceId];if(!src){AL.currentCtx.err=40961;return}if(src.type===4136){AL.currentCtx.err=40964;return}if(count===0){return}var templateBuf=AL.buffers[0];for(var i=0;i>2];var buf=AL.buffers[bufId];if(!buf){AL.currentCtx.err=40961;return}if(templateBuf.id!==0&&(buf.frequency!==templateBuf.frequency||buf.bytesPerSample!==templateBuf.bytesPerSample||buf.channels!==templateBuf.channels)){AL.currentCtx.err=40964}}if(src.bufQueue.length===1&&src.bufQueue[0].id===0){src.bufQueue.length=0}src.type=4137;for(var i=0;i>2];var buf=AL.buffers[bufId];buf.refCount++;src.bufQueue.push(buf)}if(src.looping){AL.cancelPendingSourceAudio(src)}AL.initSourcePanner(src);AL.scheduleSourceAudio(src)}Module["_alSourceQueueBuffers"]=_alSourceQueueBuffers;_alSourceQueueBuffers.sig="viii";function _alSourceRewind(sourceId){if(!AL.currentCtx){return}var src=AL.currentCtx.sources[sourceId];if(!src){AL.currentCtx.err=40961;return}AL.setSourceState(src,4116);AL.setSourceState(src,4113)}Module["_alSourceRewind"]=_alSourceRewind;_alSourceRewind.sig="vi";function _alSourceRewindv(count,pSourceIds){if(!AL.currentCtx){return}if(!pSourceIds){AL.currentCtx.err=40963}for(var i=0;i>2]]){AL.currentCtx.err=40961;return}}for(var i=0;i>2];AL.setSourceState(AL.currentCtx.sources[srcId],4113)}}Module["_alSourceRewindv"]=_alSourceRewindv;_alSourceRewindv.sig="vii";function _alSourceStop(sourceId){if(!AL.currentCtx){return}var src=AL.currentCtx.sources[sourceId];if(!src){AL.currentCtx.err=40961;return}AL.setSourceState(src,4116)}Module["_alSourceStop"]=_alSourceStop;_alSourceStop.sig="vi";function _alSourceStopv(count,pSourceIds){if(!AL.currentCtx){return}if(!pSourceIds){AL.currentCtx.err=40963}for(var i=0;i>2]]){AL.currentCtx.err=40961;return}}for(var i=0;i>2];AL.setSourceState(AL.currentCtx.sources[srcId],4116)}}Module["_alSourceStopv"]=_alSourceStopv;_alSourceStopv.sig="vii";function _alSourceUnqueueBuffers(sourceId,count,pBufferIds){if(!AL.currentCtx){return}var src=AL.currentCtx.sources[sourceId];if(!src){AL.currentCtx.err=40961;return}if(count>(src.bufQueue.length===1&&src.bufQueue[0].id===0?0:src.bufsProcessed)){AL.currentCtx.err=40963;return}if(count===0){return}for(var i=0;i>2]=buf.id;src.bufsProcessed--}if(src.bufQueue.length===0){src.bufQueue.push(AL.buffers[0])}AL.initSourcePanner(src);AL.scheduleSourceAudio(src)}Module["_alSourceUnqueueBuffers"]=_alSourceUnqueueBuffers;_alSourceUnqueueBuffers.sig="viii";function _alSourcef(sourceId,param,value){switch(param){case 4097:case 4098:case 4099:case 4106:case 4109:case 4110:case 4128:case 4129:case 4130:case 4131:case 4132:case 4133:case 4134:case 8203:AL.setSourceParam("alSourcef",sourceId,param,value);break;default:AL.setSourceParam("alSourcef",sourceId,param,null);break}}Module["_alSourcef"]=_alSourcef;_alSourcef.sig="viif";function _alSourcefv(sourceId,param,pValues){if(!AL.currentCtx){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 4097:case 4098:case 4099:case 4106:case 4109:case 4110:case 4128:case 4129:case 4130:case 4131:case 4132:case 4133:case 4134:case 8203:var val=HEAPF32[pValues>>2];AL.setSourceParam("alSourcefv",sourceId,param,val);break;case 4100:case 4101:case 4102:AL.paramArray[0]=HEAPF32[pValues>>2];AL.paramArray[1]=HEAPF32[pValues+4>>2];AL.paramArray[2]=HEAPF32[pValues+8>>2];AL.setSourceParam("alSourcefv",sourceId,param,AL.paramArray);break;default:AL.setSourceParam("alSourcefv",sourceId,param,null);break}}Module["_alSourcefv"]=_alSourcefv;_alSourcefv.sig="viii";function _alSourceiv(sourceId,param,pValues){if(!AL.currentCtx){return}if(!pValues){AL.currentCtx.err=40963;return}switch(param){case 514:case 4097:case 4098:case 4103:case 4105:case 4128:case 4129:case 4131:case 4132:case 4133:case 4134:case 4628:case 8201:case 8202:case 53248:var val=HEAP32[pValues>>2];AL.setSourceParam("alSourceiv",sourceId,param,val);break;case 4100:case 4101:case 4102:AL.paramArray[0]=HEAP32[pValues>>2];AL.paramArray[1]=HEAP32[pValues+4>>2];AL.paramArray[2]=HEAP32[pValues+8>>2];AL.setSourceParam("alSourceiv",sourceId,param,AL.paramArray);break;default:AL.setSourceParam("alSourceiv",sourceId,param,null);break}}Module["_alSourceiv"]=_alSourceiv;_alSourceiv.sig="viii";function _alSpeedOfSound(value){AL.setGlobalParam("alSpeedOfSound",49155,value)}Module["_alSpeedOfSound"]=_alSpeedOfSound;_alSpeedOfSound.sig="vi";function _alarm(seconds){setTimeout(function(){callUserCallback(function(){_raise(14)})},seconds*1e3)}Module["_alarm"]=_alarm;function _alcCaptureCloseDevice(deviceId){var c=AL.requireValidCaptureDevice(deviceId,"alcCaptureCloseDevice");if(!c)return false;delete AL.captures[deviceId];AL.freeIds.push(deviceId);if(c.mediaStreamSourceNode)c.mediaStreamSourceNode.disconnect();if(c.mergerNode)c.mergerNode.disconnect();if(c.splitterNode)c.splitterNode.disconnect();if(c.scriptProcessorNode)c.scriptProcessorNode.disconnect();if(c.mediaStream){c.mediaStream.getTracks().forEach(function(track){track.stop()})}delete c.buffers;c.capturedFrameCount=0;c.isCapturing=false;return true}Module["_alcCaptureCloseDevice"]=_alcCaptureCloseDevice;_alcCaptureCloseDevice.sig="ii";function listenOnce(object,event,func){object.addEventListener(event,func,{"once":true})}Module["listenOnce"]=listenOnce;function autoResumeAudioContext(ctx,elements){if(!elements){elements=[document,document.getElementById("canvas")]}["keydown","mousedown","touchstart"].forEach(function(event){elements.forEach(function(element){if(element){listenOnce(element,event,function(){if(ctx.state==="suspended")ctx.resume()})}})})}Module["autoResumeAudioContext"]=autoResumeAudioContext;function _alcCaptureOpenDevice(pDeviceName,requestedSampleRate,format,bufferFrameCapacity){var resolvedDeviceName=AL.CAPTURE_DEVICE_NAME;if(pDeviceName!==0){resolvedDeviceName=UTF8ToString(pDeviceName);if(resolvedDeviceName!==AL.CAPTURE_DEVICE_NAME){AL.alcErr=40965;return 0}}if(bufferFrameCapacity<0){AL.alcErr=40964;return 0}navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia;var has_getUserMedia=navigator.getUserMedia||navigator.mediaDevices&&navigator.mediaDevices.getUserMedia;if(!has_getUserMedia){AL.alcErr=40965;return 0}var AudioContext=window.AudioContext||window.webkitAudioContext;if(!AL.sharedCaptureAudioCtx){try{AL.sharedCaptureAudioCtx=new AudioContext}catch(e){AL.alcErr=40965;return 0}}autoResumeAudioContext(AL.sharedCaptureAudioCtx);var outputChannelCount;switch(format){case 65552:case 4353:case 4352:outputChannelCount=1;break;case 65553:case 4355:case 4354:outputChannelCount=2;break;default:AL.alcErr=40964;return 0}function newF32Array(cap){return new Float32Array(cap)}function newI16Array(cap){return new Int16Array(cap)}function newU8Array(cap){return new Uint8Array(cap)}var requestedSampleType;var newSampleArray;switch(format){case 65552:case 65553:requestedSampleType="f32";newSampleArray=newF32Array;break;case 4353:case 4355:requestedSampleType="i16";newSampleArray=newI16Array;break;case 4352:case 4354:requestedSampleType="u8";newSampleArray=newU8Array;break}var buffers=[];try{for(var chan=0;chanoutputChannelCount){newCapture.mergerNode=newCapture.audioCtx.createChannelMerger(inputChannelCount);newCapture.mediaStreamSourceNode.connect(newCapture.mergerNode);newCapture.mergerNode.connect(newCapture.scriptProcessorNode)}else if(inputChannelCountc.capturedFrameCount/fratio){err("alcCaptureSamples() with invalid bufferSize");AL.alcErr=40964;return}function setF32Sample(i,sample){HEAPF32[pFrames+4*i>>2]=sample}function setI16Sample(i,sample){HEAP16[pFrames+2*i>>1]=sample}function setU8Sample(i,sample){HEAP8[pFrames+i>>0]=sample}var setSample;switch(c.requestedSampleType){case"f32":setSample=setF32Sample;break;case"i16":setSample=setI16Sample;break;case"u8":setSample=setU8Sample;break;default:return}if(Math.floor(fratio)==fratio){for(var i=0,frame_i=0;frame_i0){return 0}delete AL.deviceRefCounts[deviceId];AL.freeIds.push(deviceId);return 1}Module["_alcCloseDevice"]=_alcCloseDevice;_alcCloseDevice.sig="ii";function _alcCreateContext(deviceId,pAttrList){if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return 0}var options=null;var attrs=[];var hrtf=null;pAttrList>>=2;if(pAttrList){var attr=0;var val=0;while(true){attr=HEAP32[pAttrList++];attrs.push(attr);if(attr===0){break}val=HEAP32[pAttrList++];attrs.push(val);switch(attr){case 4103:if(!options){options={}}options.sampleRate=val;break;case 4112:case 4113:break;case 6546:switch(val){case 0:hrtf=false;break;case 1:hrtf=true;break;case 2:break;default:AL.alcErr=40964;return 0}break;case 6550:if(val!==0){AL.alcErr=40964;return 0}break;default:AL.alcErr=40964;return 0}}}var AudioContext=window.AudioContext||window.webkitAudioContext;var ac=null;try{if(options){ac=new AudioContext(options)}else{ac=new AudioContext}}catch(e){if(e.name==="NotSupportedError"){AL.alcErr=40964}else{AL.alcErr=40961}return 0}autoResumeAudioContext(ac);if(typeof ac.createGain=="undefined"){ac.createGain=ac.createGainNode}var gain=ac.createGain();gain.connect(ac.destination);var ctx={deviceId:deviceId,id:AL.newId(),attrs:attrs,audioCtx:ac,listener:{position:[0,0,0],velocity:[0,0,0],direction:[0,0,0],up:[0,0,0]},sources:[],interval:setInterval(function(){AL.scheduleContextAudio(ctx)},AL.QUEUE_INTERVAL),gain:gain,distanceModel:53250,speedOfSound:343.3,dopplerFactor:1,sourceDistanceModel:false,hrtf:hrtf||false,_err:0,get err(){return this._err},set err(val){if(this._err===0||val===0){this._err=val}}};AL.deviceRefCounts[deviceId]++;AL.contexts[ctx.id]=ctx;if(hrtf!==null){for(var ctxId in AL.contexts){var c=AL.contexts[ctxId];if(c.deviceId===deviceId){c.hrtf=hrtf;AL.updateContextGlobal(c)}}}return ctx.id}Module["_alcCreateContext"]=_alcCreateContext;_alcCreateContext.sig="iii";function _alcDestroyContext(contextId){var ctx=AL.contexts[contextId];if(AL.currentCtx===ctx){AL.alcErr=40962;return}if(AL.contexts[contextId].interval){clearInterval(AL.contexts[contextId].interval)}AL.deviceRefCounts[ctx.deviceId]--;delete AL.contexts[contextId];AL.freeIds.push(contextId)}Module["_alcDestroyContext"]=_alcDestroyContext;_alcDestroyContext.sig="vi";function _alcGetContextsDevice(contextId){if(contextId in AL.contexts){return AL.contexts[contextId].deviceId}else{return 0}}Module["_alcGetContextsDevice"]=_alcGetContextsDevice;_alcGetContextsDevice.sig="ii";function _alcGetCurrentContext(){if(AL.currentCtx!==null){return AL.currentCtx.id}else{return 0}}Module["_alcGetCurrentContext"]=_alcGetCurrentContext;_alcGetCurrentContext.sig="i";function _alcGetEnumValue(deviceId,pEnumName){if(deviceId!==0&&!(deviceId in AL.deviceRefCounts)){return 0}else if(!pEnumName){AL.alcErr=40964;return 0}var name=UTF8ToString(pEnumName);switch(name){case"ALC_NO_ERROR":return 0;case"ALC_INVALID_DEVICE":return 40961;case"ALC_INVALID_CONTEXT":return 40962;case"ALC_INVALID_ENUM":return 40963;case"ALC_INVALID_VALUE":return 40964;case"ALC_OUT_OF_MEMORY":return 40965;case"ALC_MAJOR_VERSION":return 4096;case"ALC_MINOR_VERSION":return 4097;case"ALC_ATTRIBUTES_SIZE":return 4098;case"ALC_ALL_ATTRIBUTES":return 4099;case"ALC_DEFAULT_DEVICE_SPECIFIER":return 4100;case"ALC_DEVICE_SPECIFIER":return 4101;case"ALC_EXTENSIONS":return 4102;case"ALC_FREQUENCY":return 4103;case"ALC_REFRESH":return 4104;case"ALC_SYNC":return 4105;case"ALC_MONO_SOURCES":return 4112;case"ALC_STEREO_SOURCES":return 4113;case"ALC_CAPTURE_DEVICE_SPECIFIER":return 784;case"ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER":return 785;case"ALC_CAPTURE_SAMPLES":return 786;case"ALC_HRTF_SOFT":return 6546;case"ALC_HRTF_ID_SOFT":return 6550;case"ALC_DONT_CARE_SOFT":return 2;case"ALC_HRTF_STATUS_SOFT":return 6547;case"ALC_NUM_HRTF_SPECIFIERS_SOFT":return 6548;case"ALC_HRTF_SPECIFIER_SOFT":return 6549;case"ALC_HRTF_DISABLED_SOFT":return 0;case"ALC_HRTF_ENABLED_SOFT":return 1;case"ALC_HRTF_DENIED_SOFT":return 2;case"ALC_HRTF_REQUIRED_SOFT":return 3;case"ALC_HRTF_HEADPHONES_DETECTED_SOFT":return 4;case"ALC_HRTF_UNSUPPORTED_FORMAT_SOFT":return 5;default:AL.alcErr=40964;return 0}}Module["_alcGetEnumValue"]=_alcGetEnumValue;_alcGetEnumValue.sig="iii";function _alcGetError(deviceId){var err=AL.alcErr;AL.alcErr=0;return err}Module["_alcGetError"]=_alcGetError;_alcGetError.sig="ii";function _alcGetIntegerv(deviceId,param,size,pValues){if(size===0||!pValues){return}switch(param){case 4096:HEAP32[pValues>>2]=1;break;case 4097:HEAP32[pValues>>2]=1;break;case 4098:if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}if(!AL.currentCtx){AL.alcErr=40962;return}HEAP32[pValues>>2]=AL.currentCtx.attrs.length;break;case 4099:if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}if(!AL.currentCtx){AL.alcErr=40962;return}for(var i=0;i>2]=AL.currentCtx.attrs[i]}break;case 4103:if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}if(!AL.currentCtx){AL.alcErr=40962;return}HEAP32[pValues>>2]=AL.currentCtx.audioCtx.sampleRate;break;case 4112:case 4113:if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}if(!AL.currentCtx){AL.alcErr=40962;return}HEAP32[pValues>>2]=2147483647;break;case 6546:case 6547:if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}var hrtfStatus=0;for(var ctxId in AL.contexts){var ctx=AL.contexts[ctxId];if(ctx.deviceId===deviceId){hrtfStatus=ctx.hrtf?1:0}}HEAP32[pValues>>2]=hrtfStatus;break;case 6548:if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}HEAP32[pValues>>2]=1;break;case 131075:if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}if(!AL.currentCtx){AL.alcErr=40962;return}HEAP32[pValues>>2]=1;case 786:var c=AL.requireValidCaptureDevice(deviceId,"alcGetIntegerv");if(!c){return}var n=c.capturedFrameCount;var dstfreq=c.requestedSampleRate;var srcfreq=c.audioCtx.sampleRate;var nsamples=Math.floor(n*(dstfreq/srcfreq));HEAP32[pValues>>2]=nsamples;break;default:AL.alcErr=40963;return}}Module["_alcGetIntegerv"]=_alcGetIntegerv;_alcGetIntegerv.sig="viiii";function _alcGetString(deviceId,param){if(AL.alcStringCache[param]){return AL.alcStringCache[param]}var ret;switch(param){case 0:ret="No Error";break;case 40961:ret="Invalid Device";break;case 40962:ret="Invalid Context";break;case 40963:ret="Invalid Enum";break;case 40964:ret="Invalid Value";break;case 40965:ret="Out of Memory";break;case 4100:if(typeof AudioContext!="undefined"||typeof webkitAudioContext!="undefined"){ret=AL.DEVICE_NAME}else{return 0}break;case 4101:if(typeof AudioContext!="undefined"||typeof webkitAudioContext!="undefined"){ret=AL.DEVICE_NAME.concat("\0")}else{ret="\0"}break;case 785:ret=AL.CAPTURE_DEVICE_NAME;break;case 784:if(deviceId===0)ret=AL.CAPTURE_DEVICE_NAME.concat("\0");else{var c=AL.requireValidCaptureDevice(deviceId,"alcGetString");if(!c){return 0}ret=c.deviceName}break;case 4102:if(!deviceId){AL.alcErr=40961;return 0}ret="";for(var ext in AL.ALC_EXTENSIONS){ret=ret.concat(ext);ret=ret.concat(" ")}ret=ret.trim();break;default:AL.alcErr=40963;return 0}ret=allocateUTF8(ret);AL.alcStringCache[param]=ret;return ret}Module["_alcGetString"]=_alcGetString;_alcGetString.sig="iii";function _alcIsExtensionPresent(deviceId,pExtName){var name=UTF8ToString(pExtName);return AL.ALC_EXTENSIONS[name]?1:0}Module["_alcIsExtensionPresent"]=_alcIsExtensionPresent;_alcIsExtensionPresent.sig="iii";function _alcMakeContextCurrent(contextId){if(contextId===0){AL.currentCtx=null;return 0}else{AL.currentCtx=AL.contexts[contextId];return 1}}Module["_alcMakeContextCurrent"]=_alcMakeContextCurrent;_alcMakeContextCurrent.sig="ii";function _alcOpenDevice(pDeviceName){if(pDeviceName){var name=UTF8ToString(pDeviceName);if(name!==AL.DEVICE_NAME){return 0}}if(typeof AudioContext!="undefined"||typeof webkitAudioContext!="undefined"){var deviceId=AL.newId();AL.deviceRefCounts[deviceId]=0;return deviceId}else{return 0}}Module["_alcOpenDevice"]=_alcOpenDevice;_alcOpenDevice.sig="ii";function _alcProcessContext(contextId){}Module["_alcProcessContext"]=_alcProcessContext;_alcProcessContext.sig="vi";function _alcSuspendContext(contextId){}Module["_alcSuspendContext"]=_alcSuspendContext;_alcSuspendContext.sig="vi";function _emscripten_alcDevicePauseSOFT(deviceId){if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}if(AL.paused){return}AL.paused=true;for(var ctxId in AL.contexts){var ctx=AL.contexts[ctxId];if(ctx.deviceId!==deviceId){continue}ctx.audioCtx.suspend();clearInterval(ctx.interval);ctx.interval=null}}Module["_emscripten_alcDevicePauseSOFT"]=_emscripten_alcDevicePauseSOFT;_emscripten_alcDevicePauseSOFT.sig="vi";function _emscripten_alcDeviceResumeSOFT(deviceId){if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return}if(!AL.paused){return}AL.paused=false;for(var ctxId in AL.contexts){var ctx=AL.contexts[ctxId];if(ctx.deviceId!==deviceId){continue}ctx.interval=setInterval(function(){AL.scheduleContextAudio(ctx)},AL.QUEUE_INTERVAL);ctx.audioCtx.resume()}}Module["_emscripten_alcDeviceResumeSOFT"]=_emscripten_alcDeviceResumeSOFT;_emscripten_alcDeviceResumeSOFT.sig="vi";function _emscripten_alcGetStringiSOFT(deviceId,param,index){if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return 0}if(AL.alcStringCache[param]){return AL.alcStringCache[param]}var ret;switch(param){case 6549:if(index===0){ret="Web Audio HRTF"}else{AL.alcErr=40964;return 0}break;default:if(index===0){return _alcGetString(deviceId,param)}else{AL.alcErr=40963;return 0}}ret=allocateUTF8(ret);AL.alcStringCache[param]=ret;return ret}Module["_emscripten_alcGetStringiSOFT"]=_emscripten_alcGetStringiSOFT;_emscripten_alcGetStringiSOFT.sig="iiii";function _emscripten_alcResetDeviceSOFT(deviceId,pAttrList){if(!(deviceId in AL.deviceRefCounts)){AL.alcErr=40961;return 0}var hrtf=null;pAttrList>>=2;if(pAttrList){var attr=0;var val=0;while(true){attr=HEAP32[pAttrList++];if(attr===0){break}val=HEAP32[pAttrList++];switch(attr){case 6546:if(val===1){hrtf=true}else if(val===0){hrtf=false}break}}}if(hrtf!==null){for(var ctxId in AL.contexts){var ctx=AL.contexts[ctxId];if(ctx.deviceId===deviceId){ctx.hrtf=hrtf;AL.updateContextGlobal(ctx)}}}return 1}Module["_emscripten_alcResetDeviceSOFT"]=_emscripten_alcResetDeviceSOFT;_emscripten_alcResetDeviceSOFT.sig="iii";var readAsmConstArgsArray=[];Module["readAsmConstArgsArray"]=readAsmConstArgsArray;function readAsmConstArgs(sigPtr,buf){readAsmConstArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readAsmConstArgsArray.push(ch==105?HEAP32[buf]:(ch==106?HEAP64:HEAPF64)[buf++>>1]);++buf}return readAsmConstArgsArray}Module["readAsmConstArgs"]=readAsmConstArgs;function _emscripten_asm_const_int(code,sigPtr,argbuf){code-=1024;var args=readAsmConstArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}Module["_emscripten_asm_const_int"]=_emscripten_asm_const_int;_emscripten_asm_const_int.sig="ippp";function _emscripten_console_error(str){console.error(UTF8ToString(str))}Module["_emscripten_console_error"]=_emscripten_console_error;_emscripten_console_error.sig="vp";function _emscripten_console_log(str){console.log(UTF8ToString(str))}Module["_emscripten_console_log"]=_emscripten_console_log;_emscripten_console_log.sig="vp";function _emscripten_console_warn(str){console.warn(UTF8ToString(str))}Module["_emscripten_console_warn"]=_emscripten_console_warn;_emscripten_console_warn.sig="vp";function _emscripten_exit_with_live_runtime(){throw"unwind"}Module["_emscripten_exit_with_live_runtime"]=_emscripten_exit_with_live_runtime;_emscripten_exit_with_live_runtime.sig="v";function getHeapMax(){return 2147483648}Module["getHeapMax"]=getHeapMax;function _emscripten_get_heap_max(){return getHeapMax()}Module["_emscripten_get_heap_max"]=_emscripten_get_heap_max;_emscripten_get_heap_max.sig="p";function _emscripten_get_now_res(){if(ENVIRONMENT_IS_NODE){return 1}else return 1e3}Module["_emscripten_get_now_res"]=_emscripten_get_now_res;function __webgl_enable_ANGLE_instanced_arrays(ctx){var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=function(index,divisor){ext["vertexAttribDivisorANGLE"](index,divisor)};ctx["drawArraysInstanced"]=function(mode,first,count,primcount){ext["drawArraysInstancedANGLE"](mode,first,count,primcount)};ctx["drawElementsInstanced"]=function(mode,count,type,indices,primcount){ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount)};return 1}}Module["__webgl_enable_ANGLE_instanced_arrays"]=__webgl_enable_ANGLE_instanced_arrays;function __webgl_enable_OES_vertex_array_object(ctx){var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=function(){return ext["createVertexArrayOES"]()};ctx["deleteVertexArray"]=function(vao){ext["deleteVertexArrayOES"](vao)};ctx["bindVertexArray"]=function(vao){ext["bindVertexArrayOES"](vao)};ctx["isVertexArray"]=function(vao){return ext["isVertexArrayOES"](vao)};return 1}}Module["__webgl_enable_OES_vertex_array_object"]=__webgl_enable_OES_vertex_array_object;function __webgl_enable_WEBGL_draw_buffers(ctx){var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=function(n,bufs){ext["drawBuffersWEBGL"](n,bufs)};return 1}}Module["__webgl_enable_WEBGL_draw_buffers"]=__webgl_enable_WEBGL_draw_buffers;function __webgl_enable_WEBGL_multi_draw(ctx){return!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"))}Module["__webgl_enable_WEBGL_multi_draw"]=__webgl_enable_WEBGL_multi_draw;var GL={counter:1,buffers:[],programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],stringCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},createContext:function(canvas,webGLContextAttributes){if(!canvas.getContextSafariWebGL2Fixed){canvas.getContextSafariWebGL2Fixed=canvas.getContext;function fixedGetContext(ver,attrs){var gl=canvas.getContextSafariWebGL2Fixed(ver,attrs);return ver=="webgl"==gl instanceof WebGLRenderingContext?gl:null}canvas.getContext=fixedGetContext}var ctx=canvas.getContext("webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},registerContext:function(ctx,webGLContextAttributes){var handle=GL.getNewId(GL.contexts);var context={handle:handle,attributes:webGLContextAttributes,version:webGLContextAttributes.majorVersion,GLctx:ctx};if(ctx.canvas)ctx.canvas.GLctxObject=context;GL.contexts[handle]=context;if(typeof webGLContextAttributes.enableExtensionsByDefault=="undefined"||webGLContextAttributes.enableExtensionsByDefault){GL.initExtensions(context)}return handle},makeContextCurrent:function(contextHandle){GL.currentContext=GL.contexts[contextHandle];Module.ctx=GLctx=GL.currentContext&&GL.currentContext.GLctx;return!(contextHandle&&!GLctx)},getContext:function(contextHandle){return GL.contexts[contextHandle]},deleteContext:function(contextHandle){if(GL.currentContext===GL.contexts[contextHandle])GL.currentContext=null;if(typeof JSEvents=="object")JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas);if(GL.contexts[contextHandle]&&GL.contexts[contextHandle].GLctx.canvas)GL.contexts[contextHandle].GLctx.canvas.GLctxObject=undefined;GL.contexts[contextHandle]=null},initExtensions:function(context){if(!context)context=GL.currentContext;if(context.initExtensionsDone)return;context.initExtensionsDone=true;var GLctx=context.GLctx;__webgl_enable_ANGLE_instanced_arrays(GLctx);__webgl_enable_OES_vertex_array_object(GLctx);__webgl_enable_WEBGL_draw_buffers(GLctx);{GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}__webgl_enable_WEBGL_multi_draw(GLctx);var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}})}};Module["GL"]=GL;function _emscripten_glActiveTexture(x0){GLctx["activeTexture"](x0)}Module["_emscripten_glActiveTexture"]=_emscripten_glActiveTexture;_emscripten_glActiveTexture.sig="vi";function _emscripten_glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}Module["_emscripten_glAttachShader"]=_emscripten_glAttachShader;_emscripten_glAttachShader.sig="vii";function _emscripten_glBeginQueryEXT(target,id){GLctx.disjointTimerQueryExt["beginQueryEXT"](target,GL.queries[id])}Module["_emscripten_glBeginQueryEXT"]=_emscripten_glBeginQueryEXT;_emscripten_glBeginQueryEXT.sig="vii";function _emscripten_glBindAttribLocation(program,index,name){GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))}Module["_emscripten_glBindAttribLocation"]=_emscripten_glBindAttribLocation;_emscripten_glBindAttribLocation.sig="viii";function _emscripten_glBindBuffer(target,buffer){GLctx.bindBuffer(target,GL.buffers[buffer])}Module["_emscripten_glBindBuffer"]=_emscripten_glBindBuffer;_emscripten_glBindBuffer.sig="vii";function _emscripten_glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])}Module["_emscripten_glBindFramebuffer"]=_emscripten_glBindFramebuffer;_emscripten_glBindFramebuffer.sig="vii";function _emscripten_glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}Module["_emscripten_glBindRenderbuffer"]=_emscripten_glBindRenderbuffer;_emscripten_glBindRenderbuffer.sig="vii";function _emscripten_glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}Module["_emscripten_glBindTexture"]=_emscripten_glBindTexture;_emscripten_glBindTexture.sig="vii";function _emscripten_glBindVertexArrayOES(vao){GLctx["bindVertexArray"](GL.vaos[vao])}Module["_emscripten_glBindVertexArrayOES"]=_emscripten_glBindVertexArrayOES;_emscripten_glBindVertexArrayOES.sig="vi";function _emscripten_glBlendColor(x0,x1,x2,x3){GLctx["blendColor"](x0,x1,x2,x3)}Module["_emscripten_glBlendColor"]=_emscripten_glBlendColor;_emscripten_glBlendColor.sig="vffff";function _emscripten_glBlendEquation(x0){GLctx["blendEquation"](x0)}Module["_emscripten_glBlendEquation"]=_emscripten_glBlendEquation;_emscripten_glBlendEquation.sig="vi";function _emscripten_glBlendEquationSeparate(x0,x1){GLctx["blendEquationSeparate"](x0,x1)}Module["_emscripten_glBlendEquationSeparate"]=_emscripten_glBlendEquationSeparate;_emscripten_glBlendEquationSeparate.sig="vii";function _emscripten_glBlendFunc(x0,x1){GLctx["blendFunc"](x0,x1)}Module["_emscripten_glBlendFunc"]=_emscripten_glBlendFunc;_emscripten_glBlendFunc.sig="vii";function _emscripten_glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}Module["_emscripten_glBlendFuncSeparate"]=_emscripten_glBlendFuncSeparate;_emscripten_glBlendFuncSeparate.sig="viiii";function _emscripten_glBufferData(target,size,data,usage){GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}Module["_emscripten_glBufferData"]=_emscripten_glBufferData;_emscripten_glBufferData.sig="viiii";function _emscripten_glBufferSubData(target,offset,size,data){GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}Module["_emscripten_glBufferSubData"]=_emscripten_glBufferSubData;_emscripten_glBufferSubData.sig="viiii";function _emscripten_glCheckFramebufferStatus(x0){return GLctx["checkFramebufferStatus"](x0)}Module["_emscripten_glCheckFramebufferStatus"]=_emscripten_glCheckFramebufferStatus;_emscripten_glCheckFramebufferStatus.sig="ii";function _emscripten_glClear(x0){GLctx["clear"](x0)}Module["_emscripten_glClear"]=_emscripten_glClear;_emscripten_glClear.sig="vi";function _emscripten_glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}Module["_emscripten_glClearColor"]=_emscripten_glClearColor;_emscripten_glClearColor.sig="vffff";function _emscripten_glClearDepthf(x0){GLctx["clearDepth"](x0)}Module["_emscripten_glClearDepthf"]=_emscripten_glClearDepthf;_emscripten_glClearDepthf.sig="vf";function _emscripten_glClearStencil(x0){GLctx["clearStencil"](x0)}Module["_emscripten_glClearStencil"]=_emscripten_glClearStencil;_emscripten_glClearStencil.sig="vi";function _emscripten_glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}Module["_emscripten_glColorMask"]=_emscripten_glColorMask;_emscripten_glColorMask.sig="viiii";function _emscripten_glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}Module["_emscripten_glCompileShader"]=_emscripten_glCompileShader;_emscripten_glCompileShader.sig="vi";function _emscripten_glCompressedTexImage2D(target,level,internalFormat,width,height,border,imageSize,data){GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,data?HEAPU8.subarray(data,data+imageSize):null)}Module["_emscripten_glCompressedTexImage2D"]=_emscripten_glCompressedTexImage2D;_emscripten_glCompressedTexImage2D.sig="viiiiiiii";function _emscripten_glCompressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data){GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,data?HEAPU8.subarray(data,data+imageSize):null)}Module["_emscripten_glCompressedTexSubImage2D"]=_emscripten_glCompressedTexSubImage2D;_emscripten_glCompressedTexSubImage2D.sig="viiiiiiiii";function _emscripten_glCopyTexImage2D(x0,x1,x2,x3,x4,x5,x6,x7){GLctx["copyTexImage2D"](x0,x1,x2,x3,x4,x5,x6,x7)}Module["_emscripten_glCopyTexImage2D"]=_emscripten_glCopyTexImage2D;_emscripten_glCopyTexImage2D.sig="viiiiiiii";function _emscripten_glCopyTexSubImage2D(x0,x1,x2,x3,x4,x5,x6,x7){GLctx["copyTexSubImage2D"](x0,x1,x2,x3,x4,x5,x6,x7)}Module["_emscripten_glCopyTexSubImage2D"]=_emscripten_glCopyTexSubImage2D;_emscripten_glCopyTexSubImage2D.sig="viiiiiiii";function _emscripten_glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id}Module["_emscripten_glCreateProgram"]=_emscripten_glCreateProgram;_emscripten_glCreateProgram.sig="i";function _emscripten_glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}Module["_emscripten_glCreateShader"]=_emscripten_glCreateShader;_emscripten_glCreateShader.sig="ii";function _emscripten_glCullFace(x0){GLctx["cullFace"](x0)}Module["_emscripten_glCullFace"]=_emscripten_glCullFace;_emscripten_glCullFace.sig="vi";function _emscripten_glDeleteBuffers(n,buffers){for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null}}Module["_emscripten_glDeleteBuffers"]=_emscripten_glDeleteBuffers;_emscripten_glDeleteBuffers.sig="vii";function _emscripten_glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}Module["_emscripten_glDeleteFramebuffers"]=_emscripten_glDeleteFramebuffers;_emscripten_glDeleteFramebuffers.sig="vii";function _emscripten_glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null}Module["_emscripten_glDeleteProgram"]=_emscripten_glDeleteProgram;_emscripten_glDeleteProgram.sig="vi";function _emscripten_glDeleteQueriesEXT(n,ids){for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.disjointTimerQueryExt["deleteQueryEXT"](query);GL.queries[id]=null}}Module["_emscripten_glDeleteQueriesEXT"]=_emscripten_glDeleteQueriesEXT;_emscripten_glDeleteQueriesEXT.sig="vii";function _emscripten_glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}Module["_emscripten_glDeleteRenderbuffers"]=_emscripten_glDeleteRenderbuffers;_emscripten_glDeleteRenderbuffers.sig="vii";function _emscripten_glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}Module["_emscripten_glDeleteShader"]=_emscripten_glDeleteShader;_emscripten_glDeleteShader.sig="vi";function _emscripten_glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}Module["_emscripten_glDeleteTextures"]=_emscripten_glDeleteTextures;_emscripten_glDeleteTextures.sig="vii";function _emscripten_glDeleteVertexArraysOES(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}Module["_emscripten_glDeleteVertexArraysOES"]=_emscripten_glDeleteVertexArraysOES;_emscripten_glDeleteVertexArraysOES.sig="vii";function _emscripten_glDepthFunc(x0){GLctx["depthFunc"](x0)}Module["_emscripten_glDepthFunc"]=_emscripten_glDepthFunc;_emscripten_glDepthFunc.sig="vi";function _emscripten_glDepthMask(flag){GLctx.depthMask(!!flag)}Module["_emscripten_glDepthMask"]=_emscripten_glDepthMask;_emscripten_glDepthMask.sig="vi";function _emscripten_glDepthRangef(x0,x1){GLctx["depthRange"](x0,x1)}Module["_emscripten_glDepthRangef"]=_emscripten_glDepthRangef;_emscripten_glDepthRangef.sig="vii";function _emscripten_glDetachShader(program,shader){GLctx.detachShader(GL.programs[program],GL.shaders[shader])}Module["_emscripten_glDetachShader"]=_emscripten_glDetachShader;_emscripten_glDetachShader.sig="vii";function _emscripten_glDisable(x0){GLctx["disable"](x0)}Module["_emscripten_glDisable"]=_emscripten_glDisable;_emscripten_glDisable.sig="vi";function _emscripten_glDisableVertexAttribArray(index){GLctx.disableVertexAttribArray(index)}Module["_emscripten_glDisableVertexAttribArray"]=_emscripten_glDisableVertexAttribArray;_emscripten_glDisableVertexAttribArray.sig="vi";function _emscripten_glDrawArrays(mode,first,count){GLctx.drawArrays(mode,first,count)}Module["_emscripten_glDrawArrays"]=_emscripten_glDrawArrays;_emscripten_glDrawArrays.sig="viii";function _emscripten_glDrawArraysInstancedANGLE(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_emscripten_glDrawArraysInstancedANGLE"]=_emscripten_glDrawArraysInstancedANGLE;_emscripten_glDrawArraysInstancedANGLE.sig="viiii";var tempFixedLengthArray=[];Module["tempFixedLengthArray"]=tempFixedLengthArray;function _emscripten_glDrawBuffersWEBGL(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}Module["_emscripten_glDrawBuffersWEBGL"]=_emscripten_glDrawBuffersWEBGL;_emscripten_glDrawBuffersWEBGL.sig="vii";function _emscripten_glDrawElements(mode,count,type,indices){GLctx.drawElements(mode,count,type,indices)}Module["_emscripten_glDrawElements"]=_emscripten_glDrawElements;_emscripten_glDrawElements.sig="viiii";function _emscripten_glDrawElementsInstancedANGLE(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_emscripten_glDrawElementsInstancedANGLE"]=_emscripten_glDrawElementsInstancedANGLE;_emscripten_glDrawElementsInstancedANGLE.sig="viiiii";function _emscripten_glEnable(x0){GLctx["enable"](x0)}Module["_emscripten_glEnable"]=_emscripten_glEnable;_emscripten_glEnable.sig="vi";function _emscripten_glEnableVertexAttribArray(index){GLctx.enableVertexAttribArray(index)}Module["_emscripten_glEnableVertexAttribArray"]=_emscripten_glEnableVertexAttribArray;_emscripten_glEnableVertexAttribArray.sig="vi";function _emscripten_glEndQueryEXT(target){GLctx.disjointTimerQueryExt["endQueryEXT"](target)}Module["_emscripten_glEndQueryEXT"]=_emscripten_glEndQueryEXT;_emscripten_glEndQueryEXT.sig="vi";function _emscripten_glFinish(){GLctx["finish"]()}Module["_emscripten_glFinish"]=_emscripten_glFinish;_emscripten_glFinish.sig="v";function _emscripten_glFlush(){GLctx["flush"]()}Module["_emscripten_glFlush"]=_emscripten_glFlush;_emscripten_glFlush.sig="v";function _emscripten_glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}Module["_emscripten_glFramebufferRenderbuffer"]=_emscripten_glFramebufferRenderbuffer;_emscripten_glFramebufferRenderbuffer.sig="viiii";function _emscripten_glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}Module["_emscripten_glFramebufferTexture2D"]=_emscripten_glFramebufferTexture2D;_emscripten_glFramebufferTexture2D.sig="viiiii";function _emscripten_glFrontFace(x0){GLctx["frontFace"](x0)}Module["_emscripten_glFrontFace"]=_emscripten_glFrontFace;_emscripten_glFrontFace.sig="vi";function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i>2]=id}}Module["__glGenObject"]=__glGenObject;__glGenObject.sig="vii";function _emscripten_glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}Module["_emscripten_glGenBuffers"]=_emscripten_glGenBuffers;_emscripten_glGenBuffers.sig="vii";function _emscripten_glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}Module["_emscripten_glGenFramebuffers"]=_emscripten_glGenFramebuffers;_emscripten_glGenFramebuffers.sig="vii";function _emscripten_glGenQueriesEXT(n,ids){for(var i=0;i>2]=0;return}var id=GL.getNewId(GL.queries);query.name=id;GL.queries[id]=query;HEAP32[ids+i*4>>2]=id}}Module["_emscripten_glGenQueriesEXT"]=_emscripten_glGenQueriesEXT;_emscripten_glGenQueriesEXT.sig="vii";function _emscripten_glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}Module["_emscripten_glGenRenderbuffers"]=_emscripten_glGenRenderbuffers;_emscripten_glGenRenderbuffers.sig="vii";function _emscripten_glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}Module["_emscripten_glGenTextures"]=_emscripten_glGenTextures;_emscripten_glGenTextures.sig="vii";function _emscripten_glGenVertexArraysOES(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}Module["_emscripten_glGenVertexArraysOES"]=_emscripten_glGenVertexArraysOES;_emscripten_glGenVertexArraysOES.sig="vii";function _emscripten_glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}Module["_emscripten_glGenerateMipmap"]=_emscripten_glGenerateMipmap;_emscripten_glGenerateMipmap.sig="vi";function __glGetActiveAttribOrUniform(funcName,program,index,bufSize,length,size,type,name){program=GL.programs[program];var info=GLctx[funcName](program,index);if(info){var numBytesWrittenExclNull=name&&stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull;if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type}}Module["__glGetActiveAttribOrUniform"]=__glGetActiveAttribOrUniform;function _emscripten_glGetActiveAttrib(program,index,bufSize,length,size,type,name){__glGetActiveAttribOrUniform("getActiveAttrib",program,index,bufSize,length,size,type,name)}Module["_emscripten_glGetActiveAttrib"]=_emscripten_glGetActiveAttrib;_emscripten_glGetActiveAttrib.sig="viiiiiii";function _emscripten_glGetActiveUniform(program,index,bufSize,length,size,type,name){__glGetActiveAttribOrUniform("getActiveUniform",program,index,bufSize,length,size,type,name)}Module["_emscripten_glGetActiveUniform"]=_emscripten_glGetActiveUniform;_emscripten_glGetActiveUniform.sig="viiiiiii";function _emscripten_glGetAttachedShaders(program,maxCount,count,shaders){var result=GLctx.getAttachedShaders(GL.programs[program]);var len=result.length;if(len>maxCount){len=maxCount}HEAP32[count>>2]=len;for(var i=0;i>2]=id}}Module["_emscripten_glGetAttachedShaders"]=_emscripten_glGetAttachedShaders;_emscripten_glGetAttachedShaders.sig="viiii";function _emscripten_glGetAttribLocation(program,name){return GLctx.getAttribLocation(GL.programs[program],UTF8ToString(name))}Module["_emscripten_glGetAttribLocation"]=_emscripten_glGetAttribLocation;_emscripten_glGetAttribLocation.sig="iii";function writeI53ToI64(ptr,num){HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}Module["writeI53ToI64"]=writeI53ToI64;function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}Module["emscriptenWebGLGet"]=emscriptenWebGLGet;function _emscripten_glGetBooleanv(name_,p){emscriptenWebGLGet(name_,p,4)}Module["_emscripten_glGetBooleanv"]=_emscripten_glGetBooleanv;_emscripten_glGetBooleanv.sig="vii";function _emscripten_glGetBufferParameteriv(target,value,data){if(!data){GL.recordError(1281);return}HEAP32[data>>2]=GLctx.getBufferParameter(target,value)}Module["_emscripten_glGetBufferParameteriv"]=_emscripten_glGetBufferParameteriv;_emscripten_glGetBufferParameteriv.sig="viii";function _emscripten_glGetError(){var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error}Module["_emscripten_glGetError"]=_emscripten_glGetError;_emscripten_glGetError.sig="i";function _emscripten_glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}Module["_emscripten_glGetFloatv"]=_emscripten_glGetFloatv;_emscripten_glGetFloatv.sig="vii";function _emscripten_glGetFramebufferAttachmentParameteriv(target,attachment,pname,params){var result=GLctx.getFramebufferAttachmentParameter(target,attachment,pname);if(result instanceof WebGLRenderbuffer||result instanceof WebGLTexture){result=result.name|0}HEAP32[params>>2]=result}Module["_emscripten_glGetFramebufferAttachmentParameteriv"]=_emscripten_glGetFramebufferAttachmentParameteriv;_emscripten_glGetFramebufferAttachmentParameteriv.sig="viiii";function _emscripten_glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}Module["_emscripten_glGetIntegerv"]=_emscripten_glGetIntegerv;_emscripten_glGetIntegerv.sig="vii";function _emscripten_glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}Module["_emscripten_glGetProgramInfoLog"]=_emscripten_glGetProgramInfoLog;_emscripten_glGetProgramInfoLog.sig="viiii";function _emscripten_glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}}Module["_emscripten_glGetProgramiv"]=_emscripten_glGetProgramiv;_emscripten_glGetProgramiv.sig="viii";function _emscripten_glGetQueryObjecti64vEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param;{param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname)}var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)}Module["_emscripten_glGetQueryObjecti64vEXT"]=_emscripten_glGetQueryObjecti64vEXT;_emscripten_glGetQueryObjecti64vEXT.sig="viii";function _emscripten_glGetQueryObjectivEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}Module["_emscripten_glGetQueryObjectivEXT"]=_emscripten_glGetQueryObjectivEXT;_emscripten_glGetQueryObjectivEXT.sig="viii";function _emscripten_glGetQueryObjectui64vEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param;{param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname)}var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)}Module["_emscripten_glGetQueryObjectui64vEXT"]=_emscripten_glGetQueryObjectui64vEXT;_emscripten_glGetQueryObjectui64vEXT.sig="viii";function _emscripten_glGetQueryObjectuivEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}Module["_emscripten_glGetQueryObjectuivEXT"]=_emscripten_glGetQueryObjectuivEXT;_emscripten_glGetQueryObjectuivEXT.sig="viii";function _emscripten_glGetQueryivEXT(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.disjointTimerQueryExt["getQueryEXT"](target,pname)}Module["_emscripten_glGetQueryivEXT"]=_emscripten_glGetQueryivEXT;_emscripten_glGetQueryivEXT.sig="viii";function _emscripten_glGetRenderbufferParameteriv(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getRenderbufferParameter(target,pname)}Module["_emscripten_glGetRenderbufferParameteriv"]=_emscripten_glGetRenderbufferParameteriv;_emscripten_glGetRenderbufferParameteriv.sig="viii";function _emscripten_glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}Module["_emscripten_glGetShaderInfoLog"]=_emscripten_glGetShaderInfoLog;_emscripten_glGetShaderInfoLog.sig="viiii";function _emscripten_glGetShaderPrecisionFormat(shaderType,precisionType,range,precision){var result=GLctx.getShaderPrecisionFormat(shaderType,precisionType);HEAP32[range>>2]=result.rangeMin;HEAP32[range+4>>2]=result.rangeMax;HEAP32[precision>>2]=result.precision}Module["_emscripten_glGetShaderPrecisionFormat"]=_emscripten_glGetShaderPrecisionFormat;_emscripten_glGetShaderPrecisionFormat.sig="viiii";function _emscripten_glGetShaderSource(shader,bufSize,length,source){var result=GLctx.getShaderSource(GL.shaders[shader]);if(!result)return;var numBytesWrittenExclNull=bufSize>0&&source?stringToUTF8(result,source,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}Module["_emscripten_glGetShaderSource"]=_emscripten_glGetShaderSource;_emscripten_glGetShaderSource.sig="viiii";function _emscripten_glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}Module["_emscripten_glGetShaderiv"]=_emscripten_glGetShaderiv;_emscripten_glGetShaderiv.sig="viii";function stringToNewUTF8(jsString){var length=lengthBytesUTF8(jsString)+1;var cString=_malloc(length);stringToUTF8(jsString,cString,length);return cString}Module["stringToNewUTF8"]=stringToNewUTF8;function _emscripten_glGetString(name_){var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret}Module["_emscripten_glGetString"]=_emscripten_glGetString;_emscripten_glGetString.sig="ii";function _emscripten_glGetTexParameterfv(target,pname,params){if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getTexParameter(target,pname)}Module["_emscripten_glGetTexParameterfv"]=_emscripten_glGetTexParameterfv;_emscripten_glGetTexParameterfv.sig="viii";function _emscripten_glGetTexParameteriv(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getTexParameter(target,pname)}Module["_emscripten_glGetTexParameteriv"]=_emscripten_glGetTexParameteriv;_emscripten_glGetTexParameteriv.sig="viii";function webglGetLeftBracePos(name){return name.slice(-1)=="]"&&name.lastIndexOf("[")}Module["webglGetLeftBracePos"]=webglGetLeftBracePos;function webglPrepareUniformLocationsBeforeFirstUse(program){var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex0?"["+webglLoc+"]":""))}return webglLoc}else{GL.recordError(1282)}}Module["webglGetUniformLocation"]=webglGetUniformLocation;function emscriptenWebGLGetUniform(program,location,params,type){if(!params){GL.recordError(1281);return}program=GL.programs[program];webglPrepareUniformLocationsBeforeFirstUse(program);var data=GLctx.getUniform(program,webglGetUniformLocation(location));if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break}}}}Module["emscriptenWebGLGetUniform"]=emscriptenWebGLGetUniform;function _emscripten_glGetUniformfv(program,location,params){emscriptenWebGLGetUniform(program,location,params,2)}Module["_emscripten_glGetUniformfv"]=_emscripten_glGetUniformfv;_emscripten_glGetUniformfv.sig="viii";function _emscripten_glGetUniformiv(program,location,params){emscriptenWebGLGetUniform(program,location,params,0)}Module["_emscripten_glGetUniformiv"]=_emscripten_glGetUniformiv;_emscripten_glGetUniformiv.sig="viii";function _emscripten_glGetVertexAttribPointerv(index,pname,pointer){if(!pointer){GL.recordError(1281);return}HEAP32[pointer>>2]=GLctx.getVertexAttribOffset(index,pname)}Module["_emscripten_glGetVertexAttribPointerv"]=_emscripten_glGetVertexAttribPointerv;_emscripten_glGetVertexAttribPointerv.sig="viii";function emscriptenWebGLGetVertexAttrib(index,pname,params,type){if(!params){GL.recordError(1281);return}var data=GLctx.getVertexAttrib(index,pname);if(pname==34975){HEAP32[params>>2]=data&&data["name"]}else if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break;case 5:HEAP32[params>>2]=Math.fround(data);break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break;case 5:HEAP32[params+i*4>>2]=Math.fround(data[i]);break}}}}Module["emscriptenWebGLGetVertexAttrib"]=emscriptenWebGLGetVertexAttrib;function _emscripten_glGetVertexAttribfv(index,pname,params){emscriptenWebGLGetVertexAttrib(index,pname,params,2)}Module["_emscripten_glGetVertexAttribfv"]=_emscripten_glGetVertexAttribfv;_emscripten_glGetVertexAttribfv.sig="viii";function _emscripten_glGetVertexAttribiv(index,pname,params){emscriptenWebGLGetVertexAttrib(index,pname,params,5)}Module["_emscripten_glGetVertexAttribiv"]=_emscripten_glGetVertexAttribiv;_emscripten_glGetVertexAttribiv.sig="viii";function _emscripten_glHint(x0,x1){GLctx["hint"](x0,x1)}Module["_emscripten_glHint"]=_emscripten_glHint;_emscripten_glHint.sig="vii";function _emscripten_glIsBuffer(buffer){var b=GL.buffers[buffer];if(!b)return 0;return GLctx.isBuffer(b)}Module["_emscripten_glIsBuffer"]=_emscripten_glIsBuffer;_emscripten_glIsBuffer.sig="ii";function _emscripten_glIsEnabled(x0){return GLctx["isEnabled"](x0)}Module["_emscripten_glIsEnabled"]=_emscripten_glIsEnabled;_emscripten_glIsEnabled.sig="ii";function _emscripten_glIsFramebuffer(framebuffer){var fb=GL.framebuffers[framebuffer];if(!fb)return 0;return GLctx.isFramebuffer(fb)}Module["_emscripten_glIsFramebuffer"]=_emscripten_glIsFramebuffer;_emscripten_glIsFramebuffer.sig="ii";function _emscripten_glIsProgram(program){program=GL.programs[program];if(!program)return 0;return GLctx.isProgram(program)}Module["_emscripten_glIsProgram"]=_emscripten_glIsProgram;_emscripten_glIsProgram.sig="ii";function _emscripten_glIsQueryEXT(id){var query=GL.queries[id];if(!query)return 0;return GLctx.disjointTimerQueryExt["isQueryEXT"](query)}Module["_emscripten_glIsQueryEXT"]=_emscripten_glIsQueryEXT;_emscripten_glIsQueryEXT.sig="ii";function _emscripten_glIsRenderbuffer(renderbuffer){var rb=GL.renderbuffers[renderbuffer];if(!rb)return 0;return GLctx.isRenderbuffer(rb)}Module["_emscripten_glIsRenderbuffer"]=_emscripten_glIsRenderbuffer;_emscripten_glIsRenderbuffer.sig="ii";function _emscripten_glIsShader(shader){var s=GL.shaders[shader];if(!s)return 0;return GLctx.isShader(s)}Module["_emscripten_glIsShader"]=_emscripten_glIsShader;_emscripten_glIsShader.sig="ii";function _emscripten_glIsTexture(id){var texture=GL.textures[id];if(!texture)return 0;return GLctx.isTexture(texture)}Module["_emscripten_glIsTexture"]=_emscripten_glIsTexture;_emscripten_glIsTexture.sig="ii";function _emscripten_glIsVertexArrayOES(array){var vao=GL.vaos[array];if(!vao)return 0;return GLctx["isVertexArray"](vao)}Module["_emscripten_glIsVertexArrayOES"]=_emscripten_glIsVertexArrayOES;_emscripten_glIsVertexArrayOES.sig="ii";function _emscripten_glLineWidth(x0){GLctx["lineWidth"](x0)}Module["_emscripten_glLineWidth"]=_emscripten_glLineWidth;_emscripten_glLineWidth.sig="vf";function _emscripten_glLinkProgram(program){program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}}Module["_emscripten_glLinkProgram"]=_emscripten_glLinkProgram;_emscripten_glLinkProgram.sig="vi";function _emscripten_glPixelStorei(pname,param){if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)}Module["_emscripten_glPixelStorei"]=_emscripten_glPixelStorei;_emscripten_glPixelStorei.sig="vii";function _emscripten_glPolygonOffset(x0,x1){GLctx["polygonOffset"](x0,x1)}Module["_emscripten_glPolygonOffset"]=_emscripten_glPolygonOffset;_emscripten_glPolygonOffset.sig="vff";function _emscripten_glQueryCounterEXT(id,target){GLctx.disjointTimerQueryExt["queryCounterEXT"](GL.queries[id],target)}Module["_emscripten_glQueryCounterEXT"]=_emscripten_glQueryCounterEXT;_emscripten_glQueryCounterEXT.sig="vii";function computeUnpackAlignedImageSize(width,height,sizePerPixel,alignment){function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=width*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,alignment);return height*alignedRowSize}Module["computeUnpackAlignedImageSize"]=computeUnpackAlignedImageSize;function __colorChannelsInGlTextureFormat(format){var colorChannels={5:3,6:4,8:2,29502:3,29504:4};return colorChannels[format-6402]||1}Module["__colorChannelsInGlTextureFormat"]=__colorChannelsInGlTextureFormat;function heapObjectForWebGLType(type){type-=5120;if(type==1)return HEAPU8;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922)return HEAPU32;return HEAPU16}Module["heapObjectForWebGLType"]=heapObjectForWebGLType;function heapAccessShiftForWebGLHeap(heap){return 31-Math.clz32(heap.BYTES_PER_ELEMENT)}Module["heapAccessShiftForWebGLHeap"]=heapAccessShiftForWebGLHeap;function emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat){var heap=heapObjectForWebGLType(type);var shift=heapAccessShiftForWebGLHeap(heap);var byteSize=1<>shift,pixels+bytes>>shift)}Module["emscriptenWebGLGetTexPixelData"]=emscriptenWebGLGetTexPixelData;function _emscripten_glReadPixels(x,y,width,height,format,type,pixels){var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}Module["_emscripten_glReadPixels"]=_emscripten_glReadPixels;_emscripten_glReadPixels.sig="viiiiiii";function _emscripten_glReleaseShaderCompiler(){}Module["_emscripten_glReleaseShaderCompiler"]=_emscripten_glReleaseShaderCompiler;_emscripten_glReleaseShaderCompiler.sig="v";function _emscripten_glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}Module["_emscripten_glRenderbufferStorage"]=_emscripten_glRenderbufferStorage;_emscripten_glRenderbufferStorage.sig="viiii";function _emscripten_glSampleCoverage(value,invert){GLctx.sampleCoverage(value,!!invert)}Module["_emscripten_glSampleCoverage"]=_emscripten_glSampleCoverage;_emscripten_glSampleCoverage.sig="vii";function _emscripten_glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}Module["_emscripten_glScissor"]=_emscripten_glScissor;_emscripten_glScissor.sig="viiii";function _emscripten_glShaderBinary(){GL.recordError(1280)}Module["_emscripten_glShaderBinary"]=_emscripten_glShaderBinary;_emscripten_glShaderBinary.sig="v";function _emscripten_glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}Module["_emscripten_glShaderSource"]=_emscripten_glShaderSource;_emscripten_glShaderSource.sig="viiii";function _emscripten_glStencilFunc(x0,x1,x2){GLctx["stencilFunc"](x0,x1,x2)}Module["_emscripten_glStencilFunc"]=_emscripten_glStencilFunc;_emscripten_glStencilFunc.sig="viii";function _emscripten_glStencilFuncSeparate(x0,x1,x2,x3){GLctx["stencilFuncSeparate"](x0,x1,x2,x3)}Module["_emscripten_glStencilFuncSeparate"]=_emscripten_glStencilFuncSeparate;_emscripten_glStencilFuncSeparate.sig="viiii";function _emscripten_glStencilMask(x0){GLctx["stencilMask"](x0)}Module["_emscripten_glStencilMask"]=_emscripten_glStencilMask;_emscripten_glStencilMask.sig="vi";function _emscripten_glStencilMaskSeparate(x0,x1){GLctx["stencilMaskSeparate"](x0,x1)}Module["_emscripten_glStencilMaskSeparate"]=_emscripten_glStencilMaskSeparate;_emscripten_glStencilMaskSeparate.sig="vii";function _emscripten_glStencilOp(x0,x1,x2){GLctx["stencilOp"](x0,x1,x2)}Module["_emscripten_glStencilOp"]=_emscripten_glStencilOp;_emscripten_glStencilOp.sig="viii";function _emscripten_glStencilOpSeparate(x0,x1,x2,x3){GLctx["stencilOpSeparate"](x0,x1,x2,x3)}Module["_emscripten_glStencilOpSeparate"]=_emscripten_glStencilOpSeparate;_emscripten_glStencilOpSeparate.sig="viiii";function _emscripten_glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}Module["_emscripten_glTexImage2D"]=_emscripten_glTexImage2D;_emscripten_glTexImage2D.sig="viiiiiiiii";function _emscripten_glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}Module["_emscripten_glTexParameterf"]=_emscripten_glTexParameterf;_emscripten_glTexParameterf.sig="viii";function _emscripten_glTexParameterfv(target,pname,params){var param=HEAPF32[params>>2];GLctx.texParameterf(target,pname,param)}Module["_emscripten_glTexParameterfv"]=_emscripten_glTexParameterfv;_emscripten_glTexParameterfv.sig="viii";function _emscripten_glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}Module["_emscripten_glTexParameteri"]=_emscripten_glTexParameteri;_emscripten_glTexParameteri.sig="viii";function _emscripten_glTexParameteriv(target,pname,params){var param=HEAP32[params>>2];GLctx.texParameteri(target,pname,param)}Module["_emscripten_glTexParameteriv"]=_emscripten_glTexParameteriv;_emscripten_glTexParameteriv.sig="viii";function _emscripten_glTexSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels){var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)}Module["_emscripten_glTexSubImage2D"]=_emscripten_glTexSubImage2D;_emscripten_glTexSubImage2D.sig="viiiiiiiii";function _emscripten_glUniform1f(location,v0){GLctx.uniform1f(webglGetUniformLocation(location),v0)}Module["_emscripten_glUniform1f"]=_emscripten_glUniform1f;_emscripten_glUniform1f.sig="vif";var miniTempWebGLFloatBuffers=[];Module["miniTempWebGLFloatBuffers"]=miniTempWebGLFloatBuffers;function _emscripten_glUniform1fv(location,count,value){if(count<=288){var view=miniTempWebGLFloatBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1fv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform1fv"]=_emscripten_glUniform1fv;_emscripten_glUniform1fv.sig="viii";function _emscripten_glUniform1i(location,v0){GLctx.uniform1i(webglGetUniformLocation(location),v0)}Module["_emscripten_glUniform1i"]=_emscripten_glUniform1i;_emscripten_glUniform1i.sig="vii";var __miniTempWebGLIntBuffers=[];Module["__miniTempWebGLIntBuffers"]=__miniTempWebGLIntBuffers;function _emscripten_glUniform1iv(location,count,value){if(count<=288){var view=__miniTempWebGLIntBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform1iv"]=_emscripten_glUniform1iv;_emscripten_glUniform1iv.sig="viii";function _emscripten_glUniform2f(location,v0,v1){GLctx.uniform2f(webglGetUniformLocation(location),v0,v1)}Module["_emscripten_glUniform2f"]=_emscripten_glUniform2f;_emscripten_glUniform2f.sig="viff";function _emscripten_glUniform2fv(location,count,value){if(count<=144){var view=miniTempWebGLFloatBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform2fv"]=_emscripten_glUniform2fv;_emscripten_glUniform2fv.sig="viii";function _emscripten_glUniform2i(location,v0,v1){GLctx.uniform2i(webglGetUniformLocation(location),v0,v1)}Module["_emscripten_glUniform2i"]=_emscripten_glUniform2i;_emscripten_glUniform2i.sig="viii";function _emscripten_glUniform2iv(location,count,value){if(count<=144){var view=__miniTempWebGLIntBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform2iv"]=_emscripten_glUniform2iv;_emscripten_glUniform2iv.sig="viii";function _emscripten_glUniform3f(location,v0,v1,v2){GLctx.uniform3f(webglGetUniformLocation(location),v0,v1,v2)}Module["_emscripten_glUniform3f"]=_emscripten_glUniform3f;_emscripten_glUniform3f.sig="vifff";function _emscripten_glUniform3fv(location,count,value){if(count<=96){var view=miniTempWebGLFloatBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform3fv"]=_emscripten_glUniform3fv;_emscripten_glUniform3fv.sig="viii";function _emscripten_glUniform3i(location,v0,v1,v2){GLctx.uniform3i(webglGetUniformLocation(location),v0,v1,v2)}Module["_emscripten_glUniform3i"]=_emscripten_glUniform3i;_emscripten_glUniform3i.sig="viiii";function _emscripten_glUniform3iv(location,count,value){if(count<=96){var view=__miniTempWebGLIntBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3iv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform3iv"]=_emscripten_glUniform3iv;_emscripten_glUniform3iv.sig="viii";function _emscripten_glUniform4f(location,v0,v1,v2,v3){GLctx.uniform4f(webglGetUniformLocation(location),v0,v1,v2,v3)}Module["_emscripten_glUniform4f"]=_emscripten_glUniform4f;_emscripten_glUniform4f.sig="viffff";function _emscripten_glUniform4fv(location,count,value){if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<4*count;i+=4){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4fv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform4fv"]=_emscripten_glUniform4fv;_emscripten_glUniform4fv.sig="viii";function _emscripten_glUniform4i(location,v0,v1,v2,v3){GLctx.uniform4i(webglGetUniformLocation(location),v0,v1,v2,v3)}Module["_emscripten_glUniform4i"]=_emscripten_glUniform4i;_emscripten_glUniform4i.sig="viiiii";function _emscripten_glUniform4iv(location,count,value){if(count<=72){var view=__miniTempWebGLIntBuffers[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2];view[i+3]=HEAP32[value+(4*i+12)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4iv(webglGetUniformLocation(location),view)}Module["_emscripten_glUniform4iv"]=_emscripten_glUniform4iv;_emscripten_glUniform4iv.sig="viii";function _emscripten_glUniformMatrix2fv(location,count,transpose,value){if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,view)}Module["_emscripten_glUniformMatrix2fv"]=_emscripten_glUniformMatrix2fv;_emscripten_glUniformMatrix2fv.sig="viiii";function _emscripten_glUniformMatrix3fv(location,count,transpose,value){if(count<=32){var view=miniTempWebGLFloatBuffers[9*count-1];for(var i=0;i<9*count;i+=9){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,view)}Module["_emscripten_glUniformMatrix3fv"]=_emscripten_glUniformMatrix3fv;_emscripten_glUniformMatrix3fv.sig="viiii";function _emscripten_glUniformMatrix4fv(location,count,transpose,value){if(count<=18){var view=miniTempWebGLFloatBuffers[16*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<16*count;i+=16){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3];view[i+4]=heap[dst+4];view[i+5]=heap[dst+5];view[i+6]=heap[dst+6];view[i+7]=heap[dst+7];view[i+8]=heap[dst+8];view[i+9]=heap[dst+9];view[i+10]=heap[dst+10];view[i+11]=heap[dst+11];view[i+12]=heap[dst+12];view[i+13]=heap[dst+13];view[i+14]=heap[dst+14];view[i+15]=heap[dst+15]}}else{var view=HEAPF32.subarray(value>>2,value+count*64>>2)}GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,view)}Module["_emscripten_glUniformMatrix4fv"]=_emscripten_glUniformMatrix4fv;_emscripten_glUniformMatrix4fv.sig="viiii";function _emscripten_glUseProgram(program){program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program}Module["_emscripten_glUseProgram"]=_emscripten_glUseProgram;_emscripten_glUseProgram.sig="vi";function _emscripten_glValidateProgram(program){GLctx.validateProgram(GL.programs[program])}Module["_emscripten_glValidateProgram"]=_emscripten_glValidateProgram;_emscripten_glValidateProgram.sig="vi";function _emscripten_glVertexAttrib1f(x0,x1){GLctx["vertexAttrib1f"](x0,x1)}Module["_emscripten_glVertexAttrib1f"]=_emscripten_glVertexAttrib1f;_emscripten_glVertexAttrib1f.sig="vif";function _emscripten_glVertexAttrib1fv(index,v){GLctx.vertexAttrib1f(index,HEAPF32[v>>2])}Module["_emscripten_glVertexAttrib1fv"]=_emscripten_glVertexAttrib1fv;_emscripten_glVertexAttrib1fv.sig="vii";function _emscripten_glVertexAttrib2f(x0,x1,x2){GLctx["vertexAttrib2f"](x0,x1,x2)}Module["_emscripten_glVertexAttrib2f"]=_emscripten_glVertexAttrib2f;_emscripten_glVertexAttrib2f.sig="viff";function _emscripten_glVertexAttrib2fv(index,v){GLctx.vertexAttrib2f(index,HEAPF32[v>>2],HEAPF32[v+4>>2])}Module["_emscripten_glVertexAttrib2fv"]=_emscripten_glVertexAttrib2fv;_emscripten_glVertexAttrib2fv.sig="vii";function _emscripten_glVertexAttrib3f(x0,x1,x2,x3){GLctx["vertexAttrib3f"](x0,x1,x2,x3)}Module["_emscripten_glVertexAttrib3f"]=_emscripten_glVertexAttrib3f;_emscripten_glVertexAttrib3f.sig="vifff";function _emscripten_glVertexAttrib3fv(index,v){GLctx.vertexAttrib3f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2])}Module["_emscripten_glVertexAttrib3fv"]=_emscripten_glVertexAttrib3fv;_emscripten_glVertexAttrib3fv.sig="vii";function _emscripten_glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}Module["_emscripten_glVertexAttrib4f"]=_emscripten_glVertexAttrib4f;_emscripten_glVertexAttrib4f.sig="viffff";function _emscripten_glVertexAttrib4fv(index,v){GLctx.vertexAttrib4f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2],HEAPF32[v+12>>2])}Module["_emscripten_glVertexAttrib4fv"]=_emscripten_glVertexAttrib4fv;_emscripten_glVertexAttrib4fv.sig="vii";function _emscripten_glVertexAttribDivisorANGLE(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_emscripten_glVertexAttribDivisorANGLE"]=_emscripten_glVertexAttribDivisorANGLE;_emscripten_glVertexAttribDivisorANGLE.sig="vii";function _emscripten_glVertexAttribPointer(index,size,type,normalized,stride,ptr){GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}Module["_emscripten_glVertexAttribPointer"]=_emscripten_glVertexAttribPointer;_emscripten_glVertexAttribPointer.sig="viiiiii";function _emscripten_glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}Module["_emscripten_glViewport"]=_emscripten_glViewport;_emscripten_glViewport.sig="viiii";function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}Module["_emscripten_memcpy_big"]=_emscripten_memcpy_big;_emscripten_memcpy_big.sig="vppp";function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}Module["emscripten_realloc_buffer"]=emscripten_realloc_buffer;function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}Module["_emscripten_resize_heap"]=_emscripten_resize_heap;_emscripten_resize_heap.sig="ip";function getExecutableName(){return thisProgram||"./this.program"}Module["getExecutableName"]=getExecutableName;function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}Module["getEnvStrings"]=getEnvStrings;function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}Module["_environ_get"]=_environ_get;_environ_get.sig="ipp";function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}Module["_environ_sizes_get"]=_environ_sizes_get;_environ_sizes_get.sig="ipp";function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_close"]=_fd_close;_fd_close.sig="ii";function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_fdstat_get"]=_fd_fdstat_get;_fd_fdstat_get.sig="iip";function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_pread"]=_fd_pread;_fd_pread.sig="iippjp";function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}Module["doWritev"]=doWritev;function _fd_pwrite(fd,iov,iovcnt,offset,pnum){try{offset=bigintToI53Checked(offset);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt,offset);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_pwrite"]=_fd_pwrite;function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_read"]=_fd_read;_fd_read.sig="iippp";function _fd_seek(fd,offset,whence,newOffset){try{offset=bigintToI53Checked(offset);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_seek"]=_fd_seek;_fd_seek.sig="iijip";function _fd_sync(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);if(stream.stream_ops&&stream.stream_ops.fsync){return-stream.stream_ops.fsync(stream)}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_sync"]=_fd_sync;_fd_sync.sig="ii";function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}Module["_fd_write"]=_fd_write;_fd_write.sig="iippp";function _getTempRet0(){return getTempRet0()}Module["_getTempRet0"]=_getTempRet0;_getTempRet0.sig="i";function _getaddrinfo(node,service,hint,out){var addrs=[];var canon=null;var addr=0;var port=0;var flags=0;var family=0;var type=0;var proto=0;var ai,last;function allocaddrinfo(family,type,proto,canon,addr,port){var sa,salen,ai;var errno;salen=family===10?28:16;addr=family===10?inetNtop6(addr):inetNtop4(addr);sa=_malloc(salen);errno=writeSockaddr(sa,family,addr,port);assert(!errno);ai=_malloc(32);HEAP32[ai+4>>2]=family;HEAP32[ai+8>>2]=type;HEAP32[ai+12>>2]=proto;HEAP32[ai+24>>2]=canon;HEAPU32[ai+20>>2]=sa;if(family===10){HEAP32[ai+16>>2]=28}else{HEAP32[ai+16>>2]=16}HEAP32[ai+28>>2]=0;return ai}if(hint){flags=HEAP32[hint>>2];family=HEAP32[hint+4>>2];type=HEAP32[hint+8>>2];proto=HEAP32[hint+12>>2]}if(type&&!proto){proto=type===2?17:6}if(!type&&proto){type=proto===17?2:1}if(proto===0){proto=6}if(type===0){type=1}if(!node&&!service){return-2}if(flags&~(1|2|4|1024|8|16|32)){return-1}if(hint!==0&&HEAP32[hint>>2]&2&&!node){return-1}if(flags&32){return-2}if(type!==0&&type!==1&&type!==2){return-7}if(family!==0&&family!==2&&family!==10){return-6}if(service){service=UTF8ToString(service);port=parseInt(service,10);if(isNaN(port)){if(flags&1024){return-2}return-8}}if(!node){if(family===0){family=2}if((flags&1)===0){if(family===2){addr=_htonl(2130706433)}else{addr=[0,0,0,1]}}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}node=UTF8ToString(node);addr=inetPton4(node);if(addr!==null){if(family===0||family===2){family=2}else if(family===10&&flags&8){addr=[0,0,_htonl(65535),addr];family=10}else{return-2}}else{addr=inetPton6(node);if(addr!==null){if(family===0||family===10){family=10}else{return-2}}}if(addr!=null){ai=allocaddrinfo(family,type,proto,node,addr,port);HEAPU32[out>>2]=ai;return 0}if(flags&4){return-2}node=DNS.lookup_name(node);addr=inetPton4(node);if(family===0){family=2}else if(family===10){addr=[0,0,_htonl(65535),addr]}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}Module["_getaddrinfo"]=_getaddrinfo;_getaddrinfo.sig="iiiii";function _getentropy(buffer,size){if(!_getentropy.randomDevice){_getentropy.randomDevice=getRandomDevice()}for(var i=0;i>0]=_getentropy.randomDevice()}return 0}Module["_getentropy"]=_getentropy;_getentropy.sig="ipp";function getHostByName(name){var ret=_malloc(20);var nameBuf=_malloc(name.length+1);stringToUTF8(name,nameBuf,name.length+1);HEAPU32[ret>>2]=nameBuf;var aliasesBuf=_malloc(4);HEAPU32[aliasesBuf>>2]=0;HEAPU32[ret+4>>2]=aliasesBuf;var afinet=2;HEAP32[ret+8>>2]=afinet;HEAP32[ret+12>>2]=4;var addrListBuf=_malloc(12);HEAPU32[addrListBuf>>2]=addrListBuf+8;HEAPU32[addrListBuf+4>>2]=0;HEAP32[addrListBuf+8>>2]=inetPton4(DNS.lookup_name(name));HEAPU32[ret+16>>2]=addrListBuf;return ret}Module["getHostByName"]=getHostByName;function _gethostbyaddr(addr,addrlen,type){if(type!==2){setErrNo(5);return null}addr=HEAP32[addr>>2];var host=inetNtop4(addr);var lookup=DNS.lookup_addr(host);if(lookup){host=lookup}return getHostByName(host)}Module["_gethostbyaddr"]=_gethostbyaddr;_gethostbyaddr.sig="iiii";function _gethostbyname(name){return getHostByName(UTF8ToString(name))}Module["_gethostbyname"]=_gethostbyname;_gethostbyname.sig="ii";function _getloadavg(loadavg,nelem){var limit=Math.min(nelem,3);var doubleSize=8;for(var i=0;i>3]=.1}return limit}Module["_getloadavg"]=_getloadavg;_getloadavg.sig="ipi";function _getnameinfo(sa,salen,node,nodelen,serv,servlen,flags){var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=""+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0}Module["_getnameinfo"]=_getnameinfo;var Protocols={list:[],map:{}};Module["Protocols"]=Protocols;function _setprotoent(stayopen){function allocprotoent(name,proto,aliases){var nameBuf=_malloc(name.length+1);writeAsciiToMemory(name,nameBuf);var j=0;var length=aliases.length;var aliasListBuf=_malloc((length+1)*4);for(var i=0;i>2]=aliasBuf}HEAPU32[aliasListBuf+j>>2]=0;var pe=_malloc(12);HEAPU32[pe>>2]=nameBuf;HEAPU32[pe+4>>2]=aliasListBuf;HEAP32[pe+8>>2]=proto;return pe}var list=Protocols.list;var map=Protocols.map;if(list.length===0){var entry=allocprotoent("tcp",6,["TCP"]);list.push(entry);map["tcp"]=map["6"]=entry;entry=allocprotoent("udp",17,["UDP"]);list.push(entry);map["udp"]=map["17"]=entry}_setprotoent.index=0}Module["_setprotoent"]=_setprotoent;function _getprotobyname(name){name=UTF8ToString(name);_setprotoent(true);var result=Protocols.map[name];return result}Module["_getprotobyname"]=_getprotobyname;function _proc_exit(code){procExit(code)}Module["_proc_exit"]=_proc_exit;_proc_exit.sig="vi";function _setTempRet0(val){setTempRet0(val)}Module["_setTempRet0"]=_setTempRet0;_setTempRet0.sig="vi";function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}Module["__isLeapYear"]=__isLeapYear;function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}Module["__arraySum"]=__arraySum;var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];Module["__MONTH_DAYS_LEAP"]=__MONTH_DAYS_LEAP;var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];Module["__MONTH_DAYS_REGULAR"]=__MONTH_DAYS_REGULAR;function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}Module["__addDays"]=__addDays;function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}else{return thisDate.getFullYear()}}else{return thisDate.getFullYear()-1}}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}else{return"PM"}},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}Module["_strftime"]=_strftime;_strftime.sig="ppppp";function _strftime_l(s,maxsize,format,tm){return _strftime(s,maxsize,format,tm)}Module["_strftime_l"]=_strftime_l;function _system(command){if(ENVIRONMENT_IS_NODE){if(!command)return 1;var cmdstr=UTF8ToString(command);if(!cmdstr.length)return 0;var cp=require("child_process");var ret=cp.spawnSync(cmdstr,[],{shell:true,stdio:"inherit"});var _W_EXITCODE=(ret,sig)=>ret<<8|sig;if(ret.status===null){var signalToNumber=sig=>{switch(sig){case"SIGHUP":return 1;case"SIGINT":return 2;case"SIGQUIT":return 3;case"SIGFPE":return 8;case"SIGKILL":return 9;case"SIGALRM":return 14;case"SIGTERM":return 15}return 2};return _W_EXITCODE(0,signalToNumber(ret.signal))}return _W_EXITCODE(ret.status,0)}if(!command)return 0;setErrNo(52);return-1}Module["_system"]=_system;function ptrToString(ptr){return"0x"+ptr.toString(16).padStart(8,"0")}Module["ptrToString"]=ptrToString;function _emscripten_notify_memory_growth(memoryIndex){updateGlobalBufferAndViews(wasmMemory.buffer)}Module["_emscripten_notify_memory_growth"]=_emscripten_notify_memory_growth;function ___asctime_r(tmPtr,buf){var date={tm_sec:HEAP32[tmPtr>>2],tm_min:HEAP32[tmPtr+4>>2],tm_hour:HEAP32[tmPtr+8>>2],tm_mday:HEAP32[tmPtr+12>>2],tm_mon:HEAP32[tmPtr+16>>2],tm_year:HEAP32[tmPtr+20>>2],tm_wday:HEAP32[tmPtr+24>>2]};var days=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];var months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];var s=days[date.tm_wday]+" "+months[date.tm_mon]+(date.tm_mday<10?" ":" ")+date.tm_mday+(date.tm_hour<10?" 0":" ")+date.tm_hour+(date.tm_min<10?":0":":")+date.tm_min+(date.tm_sec<10?":0":":")+date.tm_sec+" "+(1900+date.tm_year)+"\n";stringToUTF8(s,buf,26);return buf}Module["___asctime_r"]=___asctime_r;___asctime_r.sig="ppp";function _strptime(buf,format,tm){var pattern=UTF8ToString(format);var SPECIAL_CHARS="\\!@#$^&*()+=-[]/{}|:<>?,.";for(var i=0,ii=SPECIAL_CHARS.length;i=0;i=pattern.indexOf("%")){capture.push(pattern[i+1]);pattern=pattern.replace(new RegExp("\\%"+pattern[i+1],"g"),"")}var matches=new RegExp("^"+pattern,"i").exec(UTF8ToString(buf));function initDate(){function fixup(value,min,max){return typeof value!="number"||isNaN(value)?min:value>=min?value<=max?value:max:min}return{year:fixup(HEAP32[tm+20>>2]+1900,1970,9999),month:fixup(HEAP32[tm+16>>2],0,11),day:fixup(HEAP32[tm+12>>2],1,31),hour:fixup(HEAP32[tm+8>>2],0,23),min:fixup(HEAP32[tm+4>>2],0,59),sec:fixup(HEAP32[tm>>2],0,59)}}if(matches){var date=initDate();var value;var getMatch=symbol=>{var pos=capture.indexOf(symbol);if(pos>=0){return matches[pos+1]}return};if(value=getMatch("S")){date.sec=jstoi_q(value)}if(value=getMatch("M")){date.min=jstoi_q(value)}if(value=getMatch("H")){date.hour=jstoi_q(value)}else if(value=getMatch("I")){var hour=jstoi_q(value);if(value=getMatch("p")){hour+=value.toUpperCase()[0]==="P"?12:0}date.hour=hour}if(value=getMatch("Y")){date.year=jstoi_q(value)}else if(value=getMatch("y")){var year=jstoi_q(value);if(value=getMatch("C")){year+=jstoi_q(value)*100}else{year+=year<69?2e3:1900}date.year=year}if(value=getMatch("m")){date.month=jstoi_q(value)-1}else if(value=getMatch("b")){date.month=MONTH_NUMBERS[value.substring(0,3).toUpperCase()]||0}if(value=getMatch("d")){date.day=jstoi_q(value)}else if(value=getMatch("j")){var day=jstoi_q(value);var leapYear=__isLeapYear(date.year);for(var month=0;month<12;++month){var daysUntilMonth=__arraySum(leapYear?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,month-1);if(day<=daysUntilMonth+(leapYear?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[month]){date.day=day-daysUntilMonth}}}else if(value=getMatch("a")){var weekDay=value.substring(0,3).toUpperCase();if(value=getMatch("U")){var weekDayNumber=DAY_NUMBERS_SUN_FIRST[weekDay];var weekNumber=jstoi_q(value);var janFirst=new Date(date.year,0,1);var endDate;if(janFirst.getDay()===0){endDate=__addDays(janFirst,weekDayNumber+7*(weekNumber-1))}else{endDate=__addDays(janFirst,7-janFirst.getDay()+weekDayNumber+7*(weekNumber-1))}date.day=endDate.getDate();date.month=endDate.getMonth()}else if(value=getMatch("W")){var weekDayNumber=DAY_NUMBERS_MON_FIRST[weekDay];var weekNumber=jstoi_q(value);var janFirst=new Date(date.year,0,1);var endDate;if(janFirst.getDay()===1){endDate=__addDays(janFirst,weekDayNumber+7*(weekNumber-1))}else{endDate=__addDays(janFirst,7-janFirst.getDay()+1+weekDayNumber+7*(weekNumber-1))}date.day=endDate.getDate();date.month=endDate.getMonth()}}var fullDate=new Date(date.year,date.month,date.day,date.hour,date.min,date.sec,0);HEAP32[tm>>2]=fullDate.getSeconds();HEAP32[tm+4>>2]=fullDate.getMinutes();HEAP32[tm+8>>2]=fullDate.getHours();HEAP32[tm+12>>2]=fullDate.getDate();HEAP32[tm+16>>2]=fullDate.getMonth();HEAP32[tm+20>>2]=fullDate.getFullYear()-1900;HEAP32[tm+24>>2]=fullDate.getDay();HEAP32[tm+28>>2]=__arraySum(__isLeapYear(fullDate.getFullYear())?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,fullDate.getMonth()-1)+fullDate.getDate()-1;HEAP32[tm+32>>2]=0;return buf+intArrayFromString(matches[0]).length-1}return 0}Module["_strptime"]=_strptime;_strptime.sig="pppp";function _strptime_l(buf,format,tm){return _strptime(buf,format,tm)}Module["_strptime_l"]=_strptime_l;_strptime_l.sig="pppp";var ERRNO_MESSAGES={0:"Success",1:"Arg list too long",2:"Permission denied",3:"Address already in use",4:"Address not available",5:"Address family not supported by protocol family",6:"No more processes",7:"Socket already connected",8:"Bad file number",9:"Trying to read unreadable message",10:"Mount device busy",11:"Operation canceled",12:"No children",13:"Connection aborted",14:"Connection refused",15:"Connection reset by peer",16:"File locking deadlock error",17:"Destination address required",18:"Math arg out of domain of func",19:"Quota exceeded",20:"File exists",21:"Bad address",22:"File too large",23:"Host is unreachable",24:"Identifier removed",25:"Illegal byte sequence",26:"Connection already in progress",27:"Interrupted system call",28:"Invalid argument",29:"I/O error",30:"Socket is already connected",31:"Is a directory",32:"Too many symbolic links",33:"Too many open files",34:"Too many links",35:"Message too long",36:"Multihop attempted",37:"File or path name too long",38:"Network interface is not configured",39:"Connection reset by network",40:"Network is unreachable",41:"Too many open files in system",42:"No buffer space available",43:"No such device",44:"No such file or directory",45:"Exec format error",46:"No record locks available",47:"The link has been severed",48:"Not enough core",49:"No message of desired type",50:"Protocol not available",51:"No space left on device",52:"Function not implemented",53:"Socket is not connected",54:"Not a directory",55:"Directory not empty",56:"State not recoverable",57:"Socket operation on non-socket",59:"Not a typewriter",60:"No such device or address",61:"Value too large for defined data type",62:"Previous owner died",63:"Not super-user",64:"Broken pipe",65:"Protocol error",66:"Unknown protocol",67:"Protocol wrong type for socket",68:"Math result not representable",69:"Read only file system",70:"Illegal seek",71:"No such process",72:"Stale file handle",73:"Connection timed out",74:"Text file busy",75:"Cross-device link",100:"Device not a stream",101:"Bad font file fmt",102:"Invalid slot",103:"Invalid request code",104:"No anode",105:"Block device required",106:"Channel number out of range",107:"Level 3 halted",108:"Level 3 reset",109:"Link number out of range",110:"Protocol driver not attached",111:"No CSI structure available",112:"Level 2 halted",113:"Invalid exchange",114:"Invalid request descriptor",115:"Exchange full",116:"No data (for no delay io)",117:"Timer expired",118:"Out of streams resources",119:"Machine is not on the network",120:"Package not installed",121:"The object is remote",122:"Advertise error",123:"Srmount error",124:"Communication error on send",125:"Cross mount point (not really error)",126:"Given log. name not unique",127:"f.d. invalid for this operation",128:"Remote address changed",129:"Can access a needed shared lib",130:"Accessing a corrupted shared lib",131:".lib section in a.out corrupted",132:"Attempting to link in too many libs",133:"Attempting to exec a shared library",135:"Streams pipe error",136:"Too many users",137:"Socket type not supported",138:"Not supported",139:"Protocol family not supported",140:"Can't send after socket shutdown",141:"Too many references",142:"Host is down",148:"No medium (in tape drive)",156:"Level 2 not synchronized"};Module["ERRNO_MESSAGES"]=ERRNO_MESSAGES;function _gethostbyname_r(name,ret,buf,buflen,out,err){var data=_gethostbyname(name);_memcpy(ret,data,20);_free(data);HEAP32[err>>2]=0;HEAPU32[out>>2]=ret;return 0}Module["_gethostbyname_r"]=_gethostbyname_r;_gethostbyname_r.sig="iiiiiii";function _endprotoent(){}Module["_endprotoent"]=_endprotoent;function _getprotoent(number){if(_setprotoent.index===Protocols.list.length){return 0}else{var result=Protocols.list[_setprotoent.index++];return result}}Module["_getprotoent"]=_getprotoent;function _getprotobynumber(number){_setprotoent(true);var result=Protocols.map[number];return result}Module["_getprotobynumber"]=_getprotobynumber;function _emscripten_run_script(ptr){eval(UTF8ToString(ptr))}Module["_emscripten_run_script"]=_emscripten_run_script;_emscripten_run_script.sig="vp";function _emscripten_run_script_int(ptr){return eval(UTF8ToString(ptr))|0}Module["_emscripten_run_script_int"]=_emscripten_run_script_int;_emscripten_run_script_int.sig="ip";function _emscripten_run_script_string(ptr){var s=eval(UTF8ToString(ptr));if(s==null){return 0}s+="";var me=_emscripten_run_script_string;var len=lengthBytesUTF8(s);if(!me.bufferSize||me.bufferSize=4){symbolName=parts[1];file=parts[2];lineno=parts[3];column=parts[4]|0}else{callstack+=line+"\n";continue}}var haveSourceMap=false;if(flags&8){var orig=emscripten_source_map.originalPositionFor({line:lineno,column:column});haveSourceMap=orig&&orig.source;if(haveSourceMap){if(flags&64){orig.source=orig.source.substring(orig.source.replace(/\\/g,"/").lastIndexOf("/")+1)}callstack+=" at "+symbolName+" ("+orig.source+":"+orig.line+":"+orig.column+")\n"}}if(flags&16||!haveSourceMap){if(flags&64){file=file.substring(file.replace(/\\/g,"/").lastIndexOf("/")+1)}callstack+=(haveSourceMap?" = "+symbolName:" at "+symbolName)+" ("+file+":"+lineno+":"+column+")\n"}if(flags&128&&stack_args[0]){if(stack_args[1]==symbolName&&stack_args[2].length>0){callstack=callstack.replace(/\s+$/,"");callstack+=" with values: "+stack_args[1]+stack_args[2]+"\n"}stack_args=traverseStack(stack_args[0])}}callstack=callstack.replace(/\s+$/,"");return callstack}Module["_emscripten_get_callstack_js"]=_emscripten_get_callstack_js;function _emscripten_get_callstack(flags,str,maxbytes){var callstack=_emscripten_get_callstack_js(flags);if(!str||maxbytes<=0){return lengthBytesUTF8(callstack)+1}var bytesWrittenExcludingNull=stringToUTF8(callstack,str,maxbytes);return bytesWrittenExcludingNull+1}Module["_emscripten_get_callstack"]=_emscripten_get_callstack;function _emscripten_log_js(flags,str){if(flags&24){str=str.replace(/\s+$/,"");str+=(str.length>0?"\n":"")+_emscripten_get_callstack_js(flags)}if(flags&1){if(flags&4){console.error(str)}else if(flags&2){console.warn(str)}else if(flags&512){console.info(str)}else if(flags&256){console.debug(str)}else{console.log(str)}}else if(flags&6){err(str)}else{out(str)}}Module["_emscripten_log_js"]=_emscripten_log_js;function reallyNegative(x){return x<0||x===0&&1/x===-Infinity}Module["reallyNegative"]=reallyNegative;function convertI32PairToI53(lo,hi){return(lo>>>0)+hi*4294967296}Module["convertI32PairToI53"]=convertI32PairToI53;function convertU32PairToI53(lo,hi){return(lo>>>0)+(hi>>>0)*4294967296}Module["convertU32PairToI53"]=convertU32PairToI53;function reSign(value,bits){if(value<=0){return value}var half=bits<=32?Math.abs(1<=half&&(bits<=32||value>half)){value=-2*half+value}return value}Module["reSign"]=reSign;function unSign(value,bits){if(value>=0){return value}return bits<=32?2*Math.abs(1<>3]);argIndex+=8}else if(type=="i64"){ret=[HEAP32[argIndex>>2],HEAP32[argIndex+4>>2]];argIndex+=8}else{type="i32";ret=HEAP32[argIndex>>2];argIndex+=4}return ret}var ret=[];var curr,next,currArg;while(1){var startTextIndex=textIndex;curr=HEAP8[textIndex>>0];if(curr===0)break;next=HEAP8[textIndex+1>>0];if(curr==37){var flagAlwaysSigned=false;var flagLeftAlign=false;var flagAlternative=false;var flagZeroPad=false;var flagPadSign=false;flagsLoop:while(1){switch(next){case 43:flagAlwaysSigned=true;break;case 45:flagLeftAlign=true;break;case 35:flagAlternative=true;break;case 48:if(flagZeroPad){break flagsLoop}else{flagZeroPad=true;break}case 32:flagPadSign=true;break;default:break flagsLoop}textIndex++;next=HEAP8[textIndex+1>>0]}var width=0;if(next==42){width=getNextArg("i32");textIndex++;next=HEAP8[textIndex+1>>0]}else{while(next>=48&&next<=57){width=width*10+(next-48);textIndex++;next=HEAP8[textIndex+1>>0]}}var precisionSet=false,precision=-1;if(next==46){precision=0;precisionSet=true;textIndex++;next=HEAP8[textIndex+1>>0];if(next==42){precision=getNextArg("i32");textIndex++}else{while(1){var precisionChr=HEAP8[textIndex+1>>0];if(precisionChr<48||precisionChr>57)break;precision=precision*10+(precisionChr-48);textIndex++}}next=HEAP8[textIndex+1>>0]}if(precision<0){precision=6;precisionSet=false}var argSize;switch(String.fromCharCode(next)){case"h":var nextNext=HEAP8[textIndex+2>>0];if(nextNext==104){textIndex++;argSize=1}else{argSize=2}break;case"l":var nextNext=HEAP8[textIndex+2>>0];if(nextNext==108){textIndex++;argSize=8}else{argSize=4}break;case"L":case"q":case"j":argSize=8;break;case"z":case"t":case"I":argSize=4;break;default:argSize=null}if(argSize)textIndex++;next=HEAP8[textIndex+1>>0];switch(String.fromCharCode(next)){case"d":case"i":case"u":case"o":case"x":case"X":case"p":{var signed=next==100||next==105;argSize=argSize||4;currArg=getNextArg("i"+argSize*8);var argText;if(argSize==8){currArg=next==117?convertU32PairToI53(currArg[0],currArg[1]):convertI32PairToI53(currArg[0],currArg[1])}if(argSize<=4){var limit=Math.pow(256,argSize)-1;currArg=(signed?reSign:unSign)(currArg&limit,argSize*8)}var currAbsArg=Math.abs(currArg);var prefix="";if(next==100||next==105){argText=reSign(currArg,8*argSize).toString(10)}else if(next==117){argText=unSign(currArg,8*argSize).toString(10);currArg=Math.abs(currArg)}else if(next==111){argText=(flagAlternative?"0":"")+currAbsArg.toString(8)}else if(next==120||next==88){prefix=flagAlternative&&currArg!=0?"0x":"";if(currArg<0){currArg=-currArg;argText=(currAbsArg-1).toString(16);var buffer=[];for(var i=0;i=0){if(flagAlwaysSigned){prefix="+"+prefix}else if(flagPadSign){prefix=" "+prefix}}if(argText.charAt(0)=="-"){prefix="-"+prefix;argText=argText.substr(1)}while(prefix.length+argText.lengthexponent&&exponent>=-4){next=(next==103?"f":"F").charCodeAt(0);precision-=exponent+1}else{next=(next==103?"e":"E").charCodeAt(0);precision--}effectivePrecision=Math.min(precision,20)}if(next==101||next==69){argText=currArg.toExponential(effectivePrecision);if(/[eE][-+]\d$/.test(argText)){argText=argText.slice(0,-1)+"0"+argText.slice(-1)}}else if(next==102||next==70){argText=currArg.toFixed(effectivePrecision);if(currArg===0&&reallyNegative(currArg)){argText="-"+argText}}var parts=argText.split("e");if(isGeneral&&!flagAlternative){while(parts[0].length>1&&parts[0].includes(".")&&(parts[0].slice(-1)=="0"||parts[0].slice(-1)==".")){parts[0]=parts[0].slice(0,-1)}}else{if(flagAlternative&&argText.indexOf(".")==-1)parts[0]+=".";while(precision>effectivePrecision++)parts[0]+="0"}argText=parts[0]+(parts.length>1?"e"+parts[1]:"");if(next==69)argText=argText.toUpperCase();if(currArg>=0){if(flagAlwaysSigned){argText="+"+argText}else if(flagPadSign){argText=" "+argText}}}while(argText.length>0])}}else{ret=ret.concat(intArrayFromString("(null)".substr(0,argLength),true))}if(flagLeftAlign){while(argLength0){ret.push(32)}if(!flagLeftAlign)ret.push(getNextArg("i8"));break}case"n":{var ptr=getNextArg("i32*");HEAP32[ptr>>2]=ret.length;break}case"%":{ret.push(curr);break}default:{for(var i=startTextIndex;i>0])}}}textIndex+=2}else{ret.push(curr);textIndex+=1}}return ret}Module["formatString"]=formatString;function _emscripten_log(flags,format,varargs){var result=formatString(format,varargs);var str=UTF8ArrayToString(result,0);_emscripten_log_js(flags,str)}Module["_emscripten_log"]=_emscripten_log;_emscripten_log.sig="vipp";function _emscripten_get_compiler_setting(name){throw"You must build with -sRETAIN_COMPILER_SETTINGS for getCompilerSetting or emscripten_get_compiler_setting to work"}Module["_emscripten_get_compiler_setting"]=_emscripten_get_compiler_setting;_emscripten_get_compiler_setting.sig="pp";function _emscripten_has_asyncify(){return 0}Module["_emscripten_has_asyncify"]=_emscripten_has_asyncify;function _emscripten_debugger(){debugger}Module["_emscripten_debugger"]=_emscripten_debugger;function _emscripten_print_double(x,to,max){var str=x+"";if(to)return stringToUTF8(str,to,max);else return lengthBytesUTF8(str)}Module["_emscripten_print_double"]=_emscripten_print_double;_emscripten_print_double.sig="iipi";function convertFrameToPC(frame){abort("Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER");return 0}Module["convertFrameToPC"]=convertFrameToPC;function _emscripten_return_address(level){var callstack=jsStackTrace().split("\n");if(callstack[0]=="Error"){callstack.shift()}var caller=callstack[level+3];return convertFrameToPC(caller)}Module["_emscripten_return_address"]=_emscripten_return_address;_emscripten_return_address.sig="pi";var UNWIND_CACHE={};Module["UNWIND_CACHE"]=UNWIND_CACHE;function saveInUnwindCache(callstack){callstack.forEach(frame=>{var pc=convertFrameToPC(frame);if(pc){UNWIND_CACHE[pc]=frame}})}Module["saveInUnwindCache"]=saveInUnwindCache;function _emscripten_stack_snapshot(){var callstack=jsStackTrace().split("\n");if(callstack[0]=="Error"){callstack.shift()}saveInUnwindCache(callstack);UNWIND_CACHE.last_addr=convertFrameToPC(callstack[3]);UNWIND_CACHE.last_stack=callstack;return UNWIND_CACHE.last_addr}Module["_emscripten_stack_snapshot"]=_emscripten_stack_snapshot;_emscripten_stack_snapshot.sig="p";function _emscripten_stack_unwind_buffer(addr,buffer,count){var stack;if(UNWIND_CACHE.last_addr==addr){stack=UNWIND_CACHE.last_stack}else{stack=jsStackTrace().split("\n");if(stack[0]=="Error"){stack.shift()}saveInUnwindCache(stack)}var offset=3;while(stack[offset]&&convertFrameToPC(stack[offset])!=addr){++offset}for(var i=0;i>2]=convertFrameToPC(stack[i+offset])}return i}Module["_emscripten_stack_unwind_buffer"]=_emscripten_stack_unwind_buffer;_emscripten_stack_unwind_buffer.sig="ippi";function _emscripten_pc_get_function(pc){abort("Cannot use emscripten_pc_get_function without -sUSE_OFFSET_CONVERTER")}Module["_emscripten_pc_get_function"]=_emscripten_pc_get_function;function convertPCtoSourceLocation(pc){if(UNWIND_CACHE.last_get_source_pc==pc)return UNWIND_CACHE.last_source;var match;var source;if(!source){var frame=UNWIND_CACHE[pc];if(!frame)return null;if(match=/\((.*):(\d+):(\d+)\)$/.exec(frame)){source={file:match[1],line:match[2],column:match[3]}}else if(match=/@(.*):(\d+):(\d+)/.exec(frame)){source={file:match[1],line:match[2],column:match[3]}}}UNWIND_CACHE.last_get_source_pc=pc;UNWIND_CACHE.last_source=source;return source}Module["convertPCtoSourceLocation"]=convertPCtoSourceLocation;function _emscripten_pc_get_file(pc){var result=convertPCtoSourceLocation(pc);if(!result)return 0;if(_emscripten_pc_get_file.ret)_free(_emscripten_pc_get_file.ret);_emscripten_pc_get_file.ret=allocateUTF8(result.file);return _emscripten_pc_get_file.ret}Module["_emscripten_pc_get_file"]=_emscripten_pc_get_file;_emscripten_pc_get_file.sig="pp";function _emscripten_pc_get_line(pc){var result=convertPCtoSourceLocation(pc);return result?result.line:0}Module["_emscripten_pc_get_line"]=_emscripten_pc_get_line;_emscripten_pc_get_line.sig="pp";function _emscripten_pc_get_column(pc){var result=convertPCtoSourceLocation(pc);return result?result.column||0:0}Module["_emscripten_pc_get_column"]=_emscripten_pc_get_column;_emscripten_pc_get_column.sig="pp";function _emscripten_get_module_name(buf,length){return stringToUTF8(wasmBinaryFile,buf,length)}Module["_emscripten_get_module_name"]=_emscripten_get_module_name;function _emscripten_asm_const_double(a0,a1,a2){return _emscripten_asm_const_int(a0,a1,a2)}Module["_emscripten_asm_const_double"]=_emscripten_asm_const_double;_emscripten_asm_const_double.sig="ippp";function _emscripten_asm_const_ptr(a0,a1,a2){return _emscripten_asm_const_int(a0,a1,a2)}Module["_emscripten_asm_const_ptr"]=_emscripten_asm_const_ptr;_emscripten_asm_const_ptr.sig="ippp";function mainThreadEM_ASM(code,sigPtr,argbuf,sync){code-=1024;var args=readAsmConstArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}Module["mainThreadEM_ASM"]=mainThreadEM_ASM;mainThreadEM_ASM.sig="iippi";function _emscripten_asm_const_int_sync_on_main_thread(code,sigPtr,argbuf){return mainThreadEM_ASM(code,sigPtr,argbuf,1)}Module["_emscripten_asm_const_int_sync_on_main_thread"]=_emscripten_asm_const_int_sync_on_main_thread;_emscripten_asm_const_int_sync_on_main_thread.sig="iiii";function _emscripten_asm_const_double_sync_on_main_thread(a0,a1,a2){return _emscripten_asm_const_int_sync_on_main_thread(a0,a1,a2)}Module["_emscripten_asm_const_double_sync_on_main_thread"]=_emscripten_asm_const_double_sync_on_main_thread;_emscripten_asm_const_double_sync_on_main_thread.sig="iiii";function _emscripten_asm_const_async_on_main_thread(code,sigPtr,argbuf){return mainThreadEM_ASM(code,sigPtr,argbuf,0)}Module["_emscripten_asm_const_async_on_main_thread"]=_emscripten_asm_const_async_on_main_thread;function jstoi_s(str){return Number(str)}Module["jstoi_s"]=jstoi_s;function __Unwind_Backtrace(func,arg){var trace=_emscripten_get_callstack_js();var parts=trace.split("\n");for(var i=0;i>2];ptr+=4;var len=HEAPU32[ptr>>2];ptr+=4;var content=HEAPU32[ptr>>2];ptr+=4;var name=UTF8ToString(name_addr);FS.createPath("/",PATH.dirname(name),true,true);FS.createDataFile(name,null,HEAP8.subarray(content,content+len),true,true,true)}while(HEAPU32[ptr>>2])}Module["__emscripten_fs_load_embedded_files"]=__emscripten_fs_load_embedded_files;__emscripten_fs_load_embedded_files.sig="vp";function writeI53ToI64Clamped(ptr,num){if(num>0x8000000000000000){HEAPU32[ptr>>2]=4294967295;HEAPU32[ptr+4>>2]=2147483647}else if(num<-0x8000000000000000){HEAPU32[ptr>>2]=0;HEAPU32[ptr+4>>2]=2147483648}else{HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}}Module["writeI53ToI64Clamped"]=writeI53ToI64Clamped;function writeI53ToI64Signaling(ptr,num){if(num>0x8000000000000000||num<-0x8000000000000000){throw"RangeError:"+num}HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}Module["writeI53ToI64Signaling"]=writeI53ToI64Signaling;function writeI53ToU64Clamped(ptr,num){if(num>0x10000000000000000)HEAPU32[ptr>>2]=HEAPU32[ptr+4>>2]=4294967295;else if(num<0)HEAPU32[ptr>>2]=HEAPU32[ptr+4>>2]=0;else{HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}}Module["writeI53ToU64Clamped"]=writeI53ToU64Clamped;function writeI53ToU64Signaling(ptr,num){if(num<0||num>0x10000000000000000){throw"RangeError:"+num}HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}Module["writeI53ToU64Signaling"]=writeI53ToU64Signaling;function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}Module["readI53FromI64"]=readI53FromI64;function readI53FromU64(ptr){return HEAPU32[ptr>>2]+HEAPU32[ptr+4>>2]*4294967296}Module["readI53FromU64"]=readI53FromU64;function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}Module["convertI32PairToI53Checked"]=convertI32PairToI53Checked;function _emscripten_math_cbrt(x){return Math.cbrt(x)}Module["_emscripten_math_cbrt"]=_emscripten_math_cbrt;function _emscripten_math_pow(x,y){return Math.pow(x,y)}Module["_emscripten_math_pow"]=_emscripten_math_pow;function _emscripten_math_random(){return Math.random()}Module["_emscripten_math_random"]=_emscripten_math_random;function _emscripten_math_sign(x){return Math.sign(x)}Module["_emscripten_math_sign"]=_emscripten_math_sign;function _emscripten_math_sqrt(x){return Math.sqrt(x)}Module["_emscripten_math_sqrt"]=_emscripten_math_sqrt;function _emscripten_math_exp(x){return Math.exp(x)}Module["_emscripten_math_exp"]=_emscripten_math_exp;function _emscripten_math_expm1(x){return Math.expm1(x)}Module["_emscripten_math_expm1"]=_emscripten_math_expm1;function _emscripten_math_fmod(x,y){return x%y}Module["_emscripten_math_fmod"]=_emscripten_math_fmod;function _emscripten_math_log(x){return Math.log(x)}Module["_emscripten_math_log"]=_emscripten_math_log;function _emscripten_math_log1p(x){return Math.log1p(x)}Module["_emscripten_math_log1p"]=_emscripten_math_log1p;function _emscripten_math_log10(x){return Math.log10(x)}Module["_emscripten_math_log10"]=_emscripten_math_log10;function _emscripten_math_log2(x){return Math.log2(x)}Module["_emscripten_math_log2"]=_emscripten_math_log2;function _emscripten_math_round(x){return Math.round(x)}Module["_emscripten_math_round"]=_emscripten_math_round;function _emscripten_math_acos(x){return Math.acos(x)}Module["_emscripten_math_acos"]=_emscripten_math_acos;function _emscripten_math_acosh(x){return Math.acosh(x)}Module["_emscripten_math_acosh"]=_emscripten_math_acosh;function _emscripten_math_asin(x){return Math.asin(x)}Module["_emscripten_math_asin"]=_emscripten_math_asin;function _emscripten_math_asinh(x){return Math.asinh(x)}Module["_emscripten_math_asinh"]=_emscripten_math_asinh;function _emscripten_math_atan(x){return Math.atan(x)}Module["_emscripten_math_atan"]=_emscripten_math_atan;function _emscripten_math_atanh(x){return Math.atanh(x)}Module["_emscripten_math_atanh"]=_emscripten_math_atanh;function _emscripten_math_atan2(y,x){return Math.atan2(y,x)}Module["_emscripten_math_atan2"]=_emscripten_math_atan2;function _emscripten_math_cos(x){return Math.cos(x)}Module["_emscripten_math_cos"]=_emscripten_math_cos;function _emscripten_math_cosh(x){return Math.cosh(x)}Module["_emscripten_math_cosh"]=_emscripten_math_cosh;function _emscripten_math_hypot(count,varargs){var args=[];for(var i=0;i>3)+i]);return Math.hypot.apply(null,args)}Module["_emscripten_math_hypot"]=_emscripten_math_hypot;_emscripten_math_hypot.sig="iip";function _emscripten_math_sin(x){return Math.sin(x)}Module["_emscripten_math_sin"]=_emscripten_math_sin;function _emscripten_math_sinh(x){return Math.sinh(x)}Module["_emscripten_math_sinh"]=_emscripten_math_sinh;function _emscripten_math_tan(x){return Math.tan(x)}Module["_emscripten_math_tan"]=_emscripten_math_tan;function _emscripten_math_tanh(x){return Math.tanh(x)}Module["_emscripten_math_tanh"]=_emscripten_math_tanh;function ___syscall_rename(old_path,new_path){try{old_path=SYSCALLS.getStr(old_path);new_path=SYSCALLS.getStr(new_path);FS.rename(old_path,new_path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}Module["___syscall_rename"]=___syscall_rename;___syscall_rename.sig="ipp";var JSEvents={inEventHandler:0,removeAllEventListeners:function(){for(var i=JSEvents.eventHandlers.length-1;i>=0;--i){JSEvents._removeHandler(i)}JSEvents.eventHandlers=[];JSEvents.deferredCalls=[]},registerRemoveEventListeners:function(){if(!JSEvents.removeEventListenersRegistered){__ATEXIT__.push(JSEvents.removeAllEventListeners);JSEvents.removeEventListenersRegistered=true}},deferredCalls:[],deferCall:function(targetFunction,precedence,argsList){function arraysHaveEqualContent(arrA,arrB){if(arrA.length!=arrB.length)return false;for(var i in arrA){if(arrA[i]!=arrB[i])return false}return true}for(var i in JSEvents.deferredCalls){var call=JSEvents.deferredCalls[i];if(call.targetFunction==targetFunction&&arraysHaveEqualContent(call.argsList,argsList)){return}}JSEvents.deferredCalls.push({targetFunction:targetFunction,precedence:precedence,argsList:argsList});JSEvents.deferredCalls.sort(function(x,y){return x.precedence2?UTF8ToString(cString):cString}Module["maybeCStringToJsString"]=maybeCStringToJsString;var specialHTMLTargets=[0,typeof document!="undefined"?document:0,typeof window!="undefined"?window:0];Module["specialHTMLTargets"]=specialHTMLTargets;function findEventTarget(target){target=maybeCStringToJsString(target);var domElement=specialHTMLTargets[target]||(typeof document!="undefined"?document.querySelector(target):undefined);return domElement}Module["findEventTarget"]=findEventTarget;function registerKeyEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.keyEvent)JSEvents.keyEvent=_malloc(176);var keyEventHandlerFunc=function(e){var keyEventData=JSEvents.keyEvent;HEAPF64[keyEventData>>3]=e.timeStamp;var idx=keyEventData>>2;HEAP32[idx+2]=e.location;HEAP32[idx+3]=e.ctrlKey;HEAP32[idx+4]=e.shiftKey;HEAP32[idx+5]=e.altKey;HEAP32[idx+6]=e.metaKey;HEAP32[idx+7]=e.repeat;HEAP32[idx+8]=e.charCode;HEAP32[idx+9]=e.keyCode;HEAP32[idx+10]=e.which;stringToUTF8(e.key||"",keyEventData+44,32);stringToUTF8(e.code||"",keyEventData+76,32);stringToUTF8(e.char||"",keyEventData+108,32);stringToUTF8(e.locale||"",keyEventData+140,32);if(getWasmTableEntry(callbackfunc)(eventTypeId,keyEventData,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),allowsDeferredCalls:true,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:keyEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerKeyEventCallback"]=registerKeyEventCallback;function findCanvasEventTarget(target){return findEventTarget(target)}Module["findCanvasEventTarget"]=findCanvasEventTarget;function _emscripten_set_keypress_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerKeyEventCallback(target,userData,useCapture,callbackfunc,1,"keypress",targetThread);return 0}Module["_emscripten_set_keypress_callback_on_thread"]=_emscripten_set_keypress_callback_on_thread;_emscripten_set_keypress_callback_on_thread.sig="iiiiii";function _emscripten_set_keydown_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerKeyEventCallback(target,userData,useCapture,callbackfunc,2,"keydown",targetThread);return 0}Module["_emscripten_set_keydown_callback_on_thread"]=_emscripten_set_keydown_callback_on_thread;_emscripten_set_keydown_callback_on_thread.sig="iiiiii";function _emscripten_set_keyup_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerKeyEventCallback(target,userData,useCapture,callbackfunc,3,"keyup",targetThread);return 0}Module["_emscripten_set_keyup_callback_on_thread"]=_emscripten_set_keyup_callback_on_thread;_emscripten_set_keyup_callback_on_thread.sig="iiiiii";function getBoundingClientRect(e){return specialHTMLTargets.indexOf(e)<0?e.getBoundingClientRect():{"left":0,"top":0}}Module["getBoundingClientRect"]=getBoundingClientRect;function fillMouseEventData(eventStruct,e,target){HEAPF64[eventStruct>>3]=e.timeStamp;var idx=eventStruct>>2;HEAP32[idx+2]=e.screenX;HEAP32[idx+3]=e.screenY;HEAP32[idx+4]=e.clientX;HEAP32[idx+5]=e.clientY;HEAP32[idx+6]=e.ctrlKey;HEAP32[idx+7]=e.shiftKey;HEAP32[idx+8]=e.altKey;HEAP32[idx+9]=e.metaKey;HEAP16[idx*2+20]=e.button;HEAP16[idx*2+21]=e.buttons;HEAP32[idx+11]=e["movementX"];HEAP32[idx+12]=e["movementY"];var rect=getBoundingClientRect(target);HEAP32[idx+13]=e.clientX-rect.left;HEAP32[idx+14]=e.clientY-rect.top}Module["fillMouseEventData"]=fillMouseEventData;function registerMouseEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.mouseEvent)JSEvents.mouseEvent=_malloc(72);target=findEventTarget(target);var mouseEventHandlerFunc=function(ev){var e=ev||event;fillMouseEventData(JSEvents.mouseEvent,e,target);if(getWasmTableEntry(callbackfunc)(eventTypeId,JSEvents.mouseEvent,userData))e.preventDefault()};var eventHandler={target:target,allowsDeferredCalls:eventTypeString!="mousemove"&&eventTypeString!="mouseenter"&&eventTypeString!="mouseleave",eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:mouseEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerMouseEventCallback"]=registerMouseEventCallback;function _emscripten_set_click_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,4,"click",targetThread);return 0}Module["_emscripten_set_click_callback_on_thread"]=_emscripten_set_click_callback_on_thread;_emscripten_set_click_callback_on_thread.sig="iiiiii";function _emscripten_set_mousedown_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,5,"mousedown",targetThread);return 0}Module["_emscripten_set_mousedown_callback_on_thread"]=_emscripten_set_mousedown_callback_on_thread;_emscripten_set_mousedown_callback_on_thread.sig="iiiiii";function _emscripten_set_mouseup_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,6,"mouseup",targetThread);return 0}Module["_emscripten_set_mouseup_callback_on_thread"]=_emscripten_set_mouseup_callback_on_thread;_emscripten_set_mouseup_callback_on_thread.sig="iiiiii";function _emscripten_set_dblclick_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,7,"dblclick",targetThread);return 0}Module["_emscripten_set_dblclick_callback_on_thread"]=_emscripten_set_dblclick_callback_on_thread;_emscripten_set_dblclick_callback_on_thread.sig="iiiiii";function _emscripten_set_mousemove_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,8,"mousemove",targetThread);return 0}Module["_emscripten_set_mousemove_callback_on_thread"]=_emscripten_set_mousemove_callback_on_thread;_emscripten_set_mousemove_callback_on_thread.sig="iiiiii";function _emscripten_set_mouseenter_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,33,"mouseenter",targetThread);return 0}Module["_emscripten_set_mouseenter_callback_on_thread"]=_emscripten_set_mouseenter_callback_on_thread;_emscripten_set_mouseenter_callback_on_thread.sig="iiiiii";function _emscripten_set_mouseleave_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,34,"mouseleave",targetThread);return 0}Module["_emscripten_set_mouseleave_callback_on_thread"]=_emscripten_set_mouseleave_callback_on_thread;_emscripten_set_mouseleave_callback_on_thread.sig="iiiiii";function _emscripten_set_mouseover_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,35,"mouseover",targetThread);return 0}Module["_emscripten_set_mouseover_callback_on_thread"]=_emscripten_set_mouseover_callback_on_thread;_emscripten_set_mouseover_callback_on_thread.sig="iiiiii";function _emscripten_set_mouseout_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerMouseEventCallback(target,userData,useCapture,callbackfunc,36,"mouseout",targetThread);return 0}Module["_emscripten_set_mouseout_callback_on_thread"]=_emscripten_set_mouseout_callback_on_thread;_emscripten_set_mouseout_callback_on_thread.sig="iiiiii";function _emscripten_get_mouse_status(mouseState){if(!JSEvents.mouseEvent)return-7;HEAP8.set(HEAP8.subarray(JSEvents.mouseEvent,JSEvents.mouseEvent+72),mouseState);return 0}Module["_emscripten_get_mouse_status"]=_emscripten_get_mouse_status;_emscripten_get_mouse_status.sig="ii";function registerWheelEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.wheelEvent)JSEvents.wheelEvent=_malloc(104);var wheelHandlerFunc=function(ev){var e=ev||event;var wheelEvent=JSEvents.wheelEvent;fillMouseEventData(wheelEvent,e,target);HEAPF64[wheelEvent+72>>3]=e["deltaX"];HEAPF64[wheelEvent+80>>3]=e["deltaY"];HEAPF64[wheelEvent+88>>3]=e["deltaZ"];HEAP32[wheelEvent+96>>2]=e["deltaMode"];if(getWasmTableEntry(callbackfunc)(eventTypeId,wheelEvent,userData))e.preventDefault()};var eventHandler={target:target,allowsDeferredCalls:true,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:wheelHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerWheelEventCallback"]=registerWheelEventCallback;function _emscripten_set_wheel_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){target=findEventTarget(target);if(typeof target.onwheel!="undefined"){registerWheelEventCallback(target,userData,useCapture,callbackfunc,9,"wheel",targetThread);return 0}else{return-1}}Module["_emscripten_set_wheel_callback_on_thread"]=_emscripten_set_wheel_callback_on_thread;_emscripten_set_wheel_callback_on_thread.sig="iiiiii";function registerUiEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.uiEvent)JSEvents.uiEvent=_malloc(36);target=findEventTarget(target);var uiEventHandlerFunc=function(ev){var e=ev||event;if(e.target!=target){return}var b=document.body;if(!b){return}var uiEvent=JSEvents.uiEvent;HEAP32[uiEvent>>2]=e.detail;HEAP32[uiEvent+4>>2]=b.clientWidth;HEAP32[uiEvent+8>>2]=b.clientHeight;HEAP32[uiEvent+12>>2]=innerWidth;HEAP32[uiEvent+16>>2]=innerHeight;HEAP32[uiEvent+20>>2]=outerWidth;HEAP32[uiEvent+24>>2]=outerHeight;HEAP32[uiEvent+28>>2]=pageXOffset;HEAP32[uiEvent+32>>2]=pageYOffset;if(getWasmTableEntry(callbackfunc)(eventTypeId,uiEvent,userData))e.preventDefault()};var eventHandler={target:target,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:uiEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerUiEventCallback"]=registerUiEventCallback;function _emscripten_set_resize_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerUiEventCallback(target,userData,useCapture,callbackfunc,10,"resize",targetThread);return 0}Module["_emscripten_set_resize_callback_on_thread"]=_emscripten_set_resize_callback_on_thread;_emscripten_set_resize_callback_on_thread.sig="iiiiii";function _emscripten_set_scroll_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerUiEventCallback(target,userData,useCapture,callbackfunc,11,"scroll",targetThread);return 0}Module["_emscripten_set_scroll_callback_on_thread"]=_emscripten_set_scroll_callback_on_thread;_emscripten_set_scroll_callback_on_thread.sig="iiiiii";function registerFocusEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.focusEvent)JSEvents.focusEvent=_malloc(256);var focusEventHandlerFunc=function(ev){var e=ev||event;var nodeName=JSEvents.getNodeNameForTarget(e.target);var id=e.target.id?e.target.id:"";var focusEvent=JSEvents.focusEvent;stringToUTF8(nodeName,focusEvent+0,128);stringToUTF8(id,focusEvent+128,128);if(getWasmTableEntry(callbackfunc)(eventTypeId,focusEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:focusEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerFocusEventCallback"]=registerFocusEventCallback;function _emscripten_set_blur_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerFocusEventCallback(target,userData,useCapture,callbackfunc,12,"blur",targetThread);return 0}Module["_emscripten_set_blur_callback_on_thread"]=_emscripten_set_blur_callback_on_thread;_emscripten_set_blur_callback_on_thread.sig="iiiiii";function _emscripten_set_focus_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerFocusEventCallback(target,userData,useCapture,callbackfunc,13,"focus",targetThread);return 0}Module["_emscripten_set_focus_callback_on_thread"]=_emscripten_set_focus_callback_on_thread;_emscripten_set_focus_callback_on_thread.sig="iiiiii";function _emscripten_set_focusin_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerFocusEventCallback(target,userData,useCapture,callbackfunc,14,"focusin",targetThread);return 0}Module["_emscripten_set_focusin_callback_on_thread"]=_emscripten_set_focusin_callback_on_thread;_emscripten_set_focusin_callback_on_thread.sig="iiiiii";function _emscripten_set_focusout_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerFocusEventCallback(target,userData,useCapture,callbackfunc,15,"focusout",targetThread);return 0}Module["_emscripten_set_focusout_callback_on_thread"]=_emscripten_set_focusout_callback_on_thread;_emscripten_set_focusout_callback_on_thread.sig="iiiiii";function fillDeviceOrientationEventData(eventStruct,e,target){HEAPF64[eventStruct>>3]=e.alpha;HEAPF64[eventStruct+8>>3]=e.beta;HEAPF64[eventStruct+16>>3]=e.gamma;HEAP32[eventStruct+24>>2]=e.absolute}Module["fillDeviceOrientationEventData"]=fillDeviceOrientationEventData;function registerDeviceOrientationEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.deviceOrientationEvent)JSEvents.deviceOrientationEvent=_malloc(32);var deviceOrientationEventHandlerFunc=function(ev){var e=ev||event;fillDeviceOrientationEventData(JSEvents.deviceOrientationEvent,e,target);if(getWasmTableEntry(callbackfunc)(eventTypeId,JSEvents.deviceOrientationEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:deviceOrientationEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerDeviceOrientationEventCallback"]=registerDeviceOrientationEventCallback;function _emscripten_set_deviceorientation_callback_on_thread(userData,useCapture,callbackfunc,targetThread){registerDeviceOrientationEventCallback(2,userData,useCapture,callbackfunc,16,"deviceorientation",targetThread);return 0}Module["_emscripten_set_deviceorientation_callback_on_thread"]=_emscripten_set_deviceorientation_callback_on_thread;_emscripten_set_deviceorientation_callback_on_thread.sig="iiiii";function _emscripten_get_deviceorientation_status(orientationState){if(!JSEvents.deviceOrientationEvent)return-7;HEAP32.set(HEAP32.subarray(JSEvents.deviceOrientationEvent,32),orientationState);return 0}Module["_emscripten_get_deviceorientation_status"]=_emscripten_get_deviceorientation_status;_emscripten_get_deviceorientation_status.sig="ii";function fillDeviceMotionEventData(eventStruct,e,target){var supportedFields=0;var a=e["acceleration"];supportedFields|=a&&1;var ag=e["accelerationIncludingGravity"];supportedFields|=ag&&2;var rr=e["rotationRate"];supportedFields|=rr&&4;a=a||{};ag=ag||{};rr=rr||{};HEAPF64[eventStruct>>3]=a["x"];HEAPF64[eventStruct+8>>3]=a["y"];HEAPF64[eventStruct+16>>3]=a["z"];HEAPF64[eventStruct+24>>3]=ag["x"];HEAPF64[eventStruct+32>>3]=ag["y"];HEAPF64[eventStruct+40>>3]=ag["z"];HEAPF64[eventStruct+48>>3]=rr["alpha"];HEAPF64[eventStruct+56>>3]=rr["beta"];HEAPF64[eventStruct+64>>3]=rr["gamma"]}Module["fillDeviceMotionEventData"]=fillDeviceMotionEventData;function registerDeviceMotionEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.deviceMotionEvent)JSEvents.deviceMotionEvent=_malloc(80);var deviceMotionEventHandlerFunc=function(ev){var e=ev||event;fillDeviceMotionEventData(JSEvents.deviceMotionEvent,e,target);if(getWasmTableEntry(callbackfunc)(eventTypeId,JSEvents.deviceMotionEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:deviceMotionEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerDeviceMotionEventCallback"]=registerDeviceMotionEventCallback;function _emscripten_set_devicemotion_callback_on_thread(userData,useCapture,callbackfunc,targetThread){registerDeviceMotionEventCallback(2,userData,useCapture,callbackfunc,17,"devicemotion",targetThread);return 0}Module["_emscripten_set_devicemotion_callback_on_thread"]=_emscripten_set_devicemotion_callback_on_thread;_emscripten_set_devicemotion_callback_on_thread.sig="iiiii";function _emscripten_get_devicemotion_status(motionState){if(!JSEvents.deviceMotionEvent)return-7;HEAP32.set(HEAP32.subarray(JSEvents.deviceMotionEvent,80),motionState);return 0}Module["_emscripten_get_devicemotion_status"]=_emscripten_get_devicemotion_status;_emscripten_get_devicemotion_status.sig="ii";function screenOrientation(){if(!screen)return undefined;return screen.orientation||screen.mozOrientation||screen.webkitOrientation||screen.msOrientation}Module["screenOrientation"]=screenOrientation;function fillOrientationChangeEventData(eventStruct){var orientations=["portrait-primary","portrait-secondary","landscape-primary","landscape-secondary"];var orientations2=["portrait","portrait","landscape","landscape"];var orientationString=screenOrientation();var orientation=orientations.indexOf(orientationString);if(orientation==-1){orientation=orientations2.indexOf(orientationString)}HEAP32[eventStruct>>2]=1<>2]=orientation}Module["fillOrientationChangeEventData"]=fillOrientationChangeEventData;function registerOrientationChangeEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.orientationChangeEvent)JSEvents.orientationChangeEvent=_malloc(8);var orientationChangeEventHandlerFunc=function(ev){var e=ev||event;var orientationChangeEvent=JSEvents.orientationChangeEvent;fillOrientationChangeEventData(orientationChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,orientationChangeEvent,userData))e.preventDefault()};if(eventTypeString=="orientationchange"&&screen.mozOrientation!==undefined){eventTypeString="mozorientationchange"}var eventHandler={target:target,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:orientationChangeEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerOrientationChangeEventCallback"]=registerOrientationChangeEventCallback;function _emscripten_set_orientationchange_callback_on_thread(userData,useCapture,callbackfunc,targetThread){if(!screen||!screen["addEventListener"])return-1;registerOrientationChangeEventCallback(screen,userData,useCapture,callbackfunc,18,"orientationchange",targetThread);return 0}Module["_emscripten_set_orientationchange_callback_on_thread"]=_emscripten_set_orientationchange_callback_on_thread;_emscripten_set_orientationchange_callback_on_thread.sig="iiiii";function _emscripten_get_orientation_status(orientationChangeEvent){if(!screenOrientation()&&typeof orientation=="undefined")return-1;fillOrientationChangeEventData(orientationChangeEvent);return 0}Module["_emscripten_get_orientation_status"]=_emscripten_get_orientation_status;_emscripten_get_orientation_status.sig="ii";function _emscripten_lock_orientation(allowedOrientations){var orientations=[];if(allowedOrientations&1)orientations.push("portrait-primary");if(allowedOrientations&2)orientations.push("portrait-secondary");if(allowedOrientations&4)orientations.push("landscape-primary");if(allowedOrientations&8)orientations.push("landscape-secondary");var succeeded;if(screen.lockOrientation){succeeded=screen.lockOrientation(orientations)}else if(screen.mozLockOrientation){succeeded=screen.mozLockOrientation(orientations)}else if(screen.webkitLockOrientation){succeeded=screen.webkitLockOrientation(orientations)}else if(screen.msLockOrientation){succeeded=screen.msLockOrientation(orientations)}else{return-1}if(succeeded){return 0}else{return-6}}Module["_emscripten_lock_orientation"]=_emscripten_lock_orientation;_emscripten_lock_orientation.sig="ii";function _emscripten_unlock_orientation(){if(screen.unlockOrientation){screen.unlockOrientation()}else if(screen.mozUnlockOrientation){screen.mozUnlockOrientation()}else if(screen.webkitUnlockOrientation){screen.webkitUnlockOrientation()}else if(screen.msUnlockOrientation){screen.msUnlockOrientation()}else{return-1}return 0}Module["_emscripten_unlock_orientation"]=_emscripten_unlock_orientation;_emscripten_unlock_orientation.sig="i";function fillFullscreenChangeEventData(eventStruct){var fullscreenElement=document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement;var isFullscreen=!!fullscreenElement;HEAP32[eventStruct>>2]=isFullscreen;HEAP32[eventStruct+4>>2]=JSEvents.fullscreenEnabled();var reportedElement=isFullscreen?fullscreenElement:JSEvents.previousFullscreenElement;var nodeName=JSEvents.getNodeNameForTarget(reportedElement);var id=reportedElement&&reportedElement.id?reportedElement.id:"";stringToUTF8(nodeName,eventStruct+8,128);stringToUTF8(id,eventStruct+136,128);HEAP32[eventStruct+264>>2]=reportedElement?reportedElement.clientWidth:0;HEAP32[eventStruct+268>>2]=reportedElement?reportedElement.clientHeight:0;HEAP32[eventStruct+272>>2]=screen.width;HEAP32[eventStruct+276>>2]=screen.height;if(isFullscreen){JSEvents.previousFullscreenElement=fullscreenElement}}Module["fillFullscreenChangeEventData"]=fillFullscreenChangeEventData;function registerFullscreenChangeEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.fullscreenChangeEvent)JSEvents.fullscreenChangeEvent=_malloc(280);var fullscreenChangeEventhandlerFunc=function(ev){var e=ev||event;var fullscreenChangeEvent=JSEvents.fullscreenChangeEvent;fillFullscreenChangeEventData(fullscreenChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,fullscreenChangeEvent,userData))e.preventDefault()};var eventHandler={target:target,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:fullscreenChangeEventhandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerFullscreenChangeEventCallback"]=registerFullscreenChangeEventCallback;function _emscripten_set_fullscreenchange_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){if(!JSEvents.fullscreenEnabled())return-1;target=findEventTarget(target);if(!target)return-4;registerFullscreenChangeEventCallback(target,userData,useCapture,callbackfunc,19,"fullscreenchange",targetThread);registerFullscreenChangeEventCallback(target,userData,useCapture,callbackfunc,19,"webkitfullscreenchange",targetThread);return 0}Module["_emscripten_set_fullscreenchange_callback_on_thread"]=_emscripten_set_fullscreenchange_callback_on_thread;_emscripten_set_fullscreenchange_callback_on_thread.sig="iiiiii";function _emscripten_get_fullscreen_status(fullscreenStatus){if(!JSEvents.fullscreenEnabled())return-1;fillFullscreenChangeEventData(fullscreenStatus);return 0}Module["_emscripten_get_fullscreen_status"]=_emscripten_get_fullscreen_status;_emscripten_get_fullscreen_status.sig="ii";function _emscripten_get_canvas_element_size(target,width,height){var canvas=findCanvasEventTarget(target);if(!canvas)return-4;HEAP32[width>>2]=canvas.width;HEAP32[height>>2]=canvas.height}Module["_emscripten_get_canvas_element_size"]=_emscripten_get_canvas_element_size;_emscripten_get_canvas_element_size.sig="ippp";function getCanvasElementSize(target){return withStackSave(function(){var w=stackAlloc(8);var h=w+4;var targetInt=stackAlloc(target.id.length+1);stringToUTF8(target.id,targetInt,target.id.length+1);var ret=_emscripten_get_canvas_element_size(targetInt,w,h);var size=[HEAP32[w>>2],HEAP32[h>>2]];return size})}Module["getCanvasElementSize"]=getCanvasElementSize;function _emscripten_set_canvas_element_size(target,width,height){var canvas=findCanvasEventTarget(target);if(!canvas)return-4;canvas.width=width;canvas.height=height;return 0}Module["_emscripten_set_canvas_element_size"]=_emscripten_set_canvas_element_size;_emscripten_set_canvas_element_size.sig="iiii";function setCanvasElementSize(target,width,height){if(!target.controlTransferredOffscreen){target.width=width;target.height=height}else{withStackSave(function(){var targetInt=stackAlloc(target.id.length+1);stringToUTF8(target.id,targetInt,target.id.length+1);_emscripten_set_canvas_element_size(targetInt,width,height)})}}Module["setCanvasElementSize"]=setCanvasElementSize;function registerRestoreOldStyle(canvas){var canvasSize=getCanvasElementSize(canvas);var oldWidth=canvasSize[0];var oldHeight=canvasSize[1];var oldCssWidth=canvas.style.width;var oldCssHeight=canvas.style.height;var oldBackgroundColor=canvas.style.backgroundColor;var oldDocumentBackgroundColor=document.body.style.backgroundColor;var oldPaddingLeft=canvas.style.paddingLeft;var oldPaddingRight=canvas.style.paddingRight;var oldPaddingTop=canvas.style.paddingTop;var oldPaddingBottom=canvas.style.paddingBottom;var oldMarginLeft=canvas.style.marginLeft;var oldMarginRight=canvas.style.marginRight;var oldMarginTop=canvas.style.marginTop;var oldMarginBottom=canvas.style.marginBottom;var oldDocumentBodyMargin=document.body.style.margin;var oldDocumentOverflow=document.documentElement.style.overflow;var oldDocumentScroll=document.body.scroll;var oldImageRendering=canvas.style.imageRendering;function restoreOldStyle(){var fullscreenElement=document.fullscreenElement||document.webkitFullscreenElement;if(!fullscreenElement){document.removeEventListener("fullscreenchange",restoreOldStyle);document.removeEventListener("webkitfullscreenchange",restoreOldStyle);setCanvasElementSize(canvas,oldWidth,oldHeight);canvas.style.width=oldCssWidth;canvas.style.height=oldCssHeight;canvas.style.backgroundColor=oldBackgroundColor;if(!oldDocumentBackgroundColor)document.body.style.backgroundColor="white";document.body.style.backgroundColor=oldDocumentBackgroundColor;canvas.style.paddingLeft=oldPaddingLeft;canvas.style.paddingRight=oldPaddingRight;canvas.style.paddingTop=oldPaddingTop;canvas.style.paddingBottom=oldPaddingBottom;canvas.style.marginLeft=oldMarginLeft;canvas.style.marginRight=oldMarginRight;canvas.style.marginTop=oldMarginTop;canvas.style.marginBottom=oldMarginBottom;document.body.style.margin=oldDocumentBodyMargin;document.documentElement.style.overflow=oldDocumentOverflow;document.body.scroll=oldDocumentScroll;canvas.style.imageRendering=oldImageRendering;if(canvas.GLctxObject)canvas.GLctxObject.GLctx.viewport(0,0,oldWidth,oldHeight);if(currentFullscreenStrategy.canvasResizedCallback){getWasmTableEntry(currentFullscreenStrategy.canvasResizedCallback)(37,0,currentFullscreenStrategy.canvasResizedCallbackUserData)}}}document.addEventListener("fullscreenchange",restoreOldStyle);document.addEventListener("webkitfullscreenchange",restoreOldStyle);return restoreOldStyle}Module["registerRestoreOldStyle"]=registerRestoreOldStyle;function setLetterbox(element,topBottom,leftRight){element.style.paddingLeft=element.style.paddingRight=leftRight+"px";element.style.paddingTop=element.style.paddingBottom=topBottom+"px"}Module["setLetterbox"]=setLetterbox;function JSEvents_resizeCanvasForFullscreen(target,strategy){var restoreOldStyle=registerRestoreOldStyle(target);var cssWidth=strategy.softFullscreen?innerWidth:screen.width;var cssHeight=strategy.softFullscreen?innerHeight:screen.height;var rect=getBoundingClientRect(target);var windowedCssWidth=rect.width;var windowedCssHeight=rect.height;var canvasSize=getCanvasElementSize(target);var windowedRttWidth=canvasSize[0];var windowedRttHeight=canvasSize[1];if(strategy.scaleMode==3){setLetterbox(target,(cssHeight-windowedCssHeight)/2,(cssWidth-windowedCssWidth)/2);cssWidth=windowedCssWidth;cssHeight=windowedCssHeight}else if(strategy.scaleMode==2){if(cssWidth*windowedRttHeightx*h)w=h*x/y|0;topMargin=(screenHeight-h)/2|0}if(inPixelPerfectFullscreenMode){setCanvasElementSize(canvas,w,h);if(canvas.GLctxObject)canvas.GLctxObject.GLctx.viewport(0,0,w,h)}if(inHiDPIFullscreenMode){topMargin/=dpr;w/=dpr;h/=dpr;w=Math.round(w*1e4)/1e4;h=Math.round(h*1e4)/1e4;topMargin=Math.round(topMargin*1e4)/1e4}if(inCenteredWithoutScalingFullscreenMode){var t=(innerHeight-jstoi_q(canvas.style.height))/2;var b=(innerWidth-jstoi_q(canvas.style.width))/2;setLetterbox(canvas,t,b)}else{canvas.style.width=w+"px";canvas.style.height=h+"px";var b=(innerWidth-w)/2;setLetterbox(canvas,topMargin,b)}if(!inCenteredWithoutScalingFullscreenMode&¤tFullscreenStrategy.canvasResizedCallback){getWasmTableEntry(currentFullscreenStrategy.canvasResizedCallback)(37,0,currentFullscreenStrategy.canvasResizedCallbackUserData)}}Module["softFullscreenResizeWebGLRenderTarget"]=softFullscreenResizeWebGLRenderTarget;function doRequestFullscreen(target,strategy){if(!JSEvents.fullscreenEnabled())return-1;target=findEventTarget(target);if(!target)return-4;if(!target.requestFullscreen&&!target.webkitRequestFullscreen){return-3}var canPerformRequests=JSEvents.canPerformEventHandlerRequests();if(!canPerformRequests){if(strategy.deferUntilInEventHandler){JSEvents.deferCall(JSEvents_requestFullscreen,1,[target,strategy]);return 1}else{return-2}}return JSEvents_requestFullscreen(target,strategy)}Module["doRequestFullscreen"]=doRequestFullscreen;function _emscripten_request_fullscreen(target,deferUntilInEventHandler){var strategy={scaleMode:0,canvasResolutionScaleMode:0,filteringMode:0,deferUntilInEventHandler:deferUntilInEventHandler,canvasResizedCallbackTargetThread:2};return doRequestFullscreen(target,strategy)}Module["_emscripten_request_fullscreen"]=_emscripten_request_fullscreen;_emscripten_request_fullscreen.sig="iii";function _emscripten_request_fullscreen_strategy(target,deferUntilInEventHandler,fullscreenStrategy){var strategy={scaleMode:HEAP32[fullscreenStrategy>>2],canvasResolutionScaleMode:HEAP32[fullscreenStrategy+4>>2],filteringMode:HEAP32[fullscreenStrategy+8>>2],deferUntilInEventHandler:deferUntilInEventHandler,canvasResizedCallback:HEAP32[fullscreenStrategy+12>>2],canvasResizedCallbackUserData:HEAP32[fullscreenStrategy+16>>2]};return doRequestFullscreen(target,strategy)}Module["_emscripten_request_fullscreen_strategy"]=_emscripten_request_fullscreen_strategy;_emscripten_request_fullscreen_strategy.sig="iiii";function _emscripten_enter_soft_fullscreen(target,fullscreenStrategy){target=findEventTarget(target);if(!target)return-4;var strategy={scaleMode:HEAP32[fullscreenStrategy>>2],canvasResolutionScaleMode:HEAP32[fullscreenStrategy+4>>2],filteringMode:HEAP32[fullscreenStrategy+8>>2],canvasResizedCallback:HEAP32[fullscreenStrategy+12>>2],canvasResizedCallbackUserData:HEAP32[fullscreenStrategy+16>>2],target:target,softFullscreen:true};var restoreOldStyle=JSEvents_resizeCanvasForFullscreen(target,strategy);document.documentElement.style.overflow="hidden";document.body.scroll="no";document.body.style.margin="0px";var hiddenElements=hideEverythingExceptGivenElement(target);function restoreWindowedState(){restoreOldStyle();restoreHiddenElements(hiddenElements);removeEventListener("resize",softFullscreenResizeWebGLRenderTarget);if(strategy.canvasResizedCallback){getWasmTableEntry(strategy.canvasResizedCallback)(37,0,strategy.canvasResizedCallbackUserData)}currentFullscreenStrategy=0}restoreOldWindowedStyle=restoreWindowedState;currentFullscreenStrategy=strategy;addEventListener("resize",softFullscreenResizeWebGLRenderTarget);if(strategy.canvasResizedCallback){getWasmTableEntry(strategy.canvasResizedCallback)(37,0,strategy.canvasResizedCallbackUserData)}return 0}Module["_emscripten_enter_soft_fullscreen"]=_emscripten_enter_soft_fullscreen;_emscripten_enter_soft_fullscreen.sig="iii";function _emscripten_exit_soft_fullscreen(){if(restoreOldWindowedStyle)restoreOldWindowedStyle();restoreOldWindowedStyle=null;return 0}Module["_emscripten_exit_soft_fullscreen"]=_emscripten_exit_soft_fullscreen;_emscripten_exit_soft_fullscreen.sig="i";function _emscripten_exit_fullscreen(){if(!JSEvents.fullscreenEnabled())return-1;JSEvents.removeDeferredCalls(JSEvents_requestFullscreen);var d=specialHTMLTargets[1];if(d.exitFullscreen){d.fullscreenElement&&d.exitFullscreen()}else if(d.webkitExitFullscreen){d.webkitFullscreenElement&&d.webkitExitFullscreen()}else{return-1}return 0}Module["_emscripten_exit_fullscreen"]=_emscripten_exit_fullscreen;_emscripten_exit_fullscreen.sig="i";function fillPointerlockChangeEventData(eventStruct){var pointerLockElement=document.pointerLockElement||document.mozPointerLockElement||document.webkitPointerLockElement||document.msPointerLockElement;var isPointerlocked=!!pointerLockElement;HEAP32[eventStruct>>2]=isPointerlocked;var nodeName=JSEvents.getNodeNameForTarget(pointerLockElement);var id=pointerLockElement&&pointerLockElement.id?pointerLockElement.id:"";stringToUTF8(nodeName,eventStruct+4,128);stringToUTF8(id,eventStruct+132,128)}Module["fillPointerlockChangeEventData"]=fillPointerlockChangeEventData;function registerPointerlockChangeEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.pointerlockChangeEvent)JSEvents.pointerlockChangeEvent=_malloc(260);var pointerlockChangeEventHandlerFunc=function(ev){var e=ev||event;var pointerlockChangeEvent=JSEvents.pointerlockChangeEvent;fillPointerlockChangeEventData(pointerlockChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,pointerlockChangeEvent,userData))e.preventDefault()};var eventHandler={target:target,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:pointerlockChangeEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerPointerlockChangeEventCallback"]=registerPointerlockChangeEventCallback;function _emscripten_set_pointerlockchange_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){if(!document||!document.body||!document.body.requestPointerLock&&!document.body.mozRequestPointerLock&&!document.body.webkitRequestPointerLock&&!document.body.msRequestPointerLock){return-1}target=findEventTarget(target);if(!target)return-4;registerPointerlockChangeEventCallback(target,userData,useCapture,callbackfunc,20,"pointerlockchange",targetThread);registerPointerlockChangeEventCallback(target,userData,useCapture,callbackfunc,20,"mozpointerlockchange",targetThread);registerPointerlockChangeEventCallback(target,userData,useCapture,callbackfunc,20,"webkitpointerlockchange",targetThread);registerPointerlockChangeEventCallback(target,userData,useCapture,callbackfunc,20,"mspointerlockchange",targetThread);return 0}Module["_emscripten_set_pointerlockchange_callback_on_thread"]=_emscripten_set_pointerlockchange_callback_on_thread;_emscripten_set_pointerlockchange_callback_on_thread.sig="iiiiii";function registerPointerlockErrorEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){var pointerlockErrorEventHandlerFunc=function(ev){var e=ev||event;if(getWasmTableEntry(callbackfunc)(eventTypeId,0,userData))e.preventDefault()};var eventHandler={target:target,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:pointerlockErrorEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerPointerlockErrorEventCallback"]=registerPointerlockErrorEventCallback;function _emscripten_set_pointerlockerror_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){if(!document||!document.body.requestPointerLock&&!document.body.mozRequestPointerLock&&!document.body.webkitRequestPointerLock&&!document.body.msRequestPointerLock){return-1}target=findEventTarget(target);if(!target)return-4;registerPointerlockErrorEventCallback(target,userData,useCapture,callbackfunc,38,"pointerlockerror",targetThread);registerPointerlockErrorEventCallback(target,userData,useCapture,callbackfunc,38,"mozpointerlockerror",targetThread);registerPointerlockErrorEventCallback(target,userData,useCapture,callbackfunc,38,"webkitpointerlockerror",targetThread);registerPointerlockErrorEventCallback(target,userData,useCapture,callbackfunc,38,"mspointerlockerror",targetThread);return 0}Module["_emscripten_set_pointerlockerror_callback_on_thread"]=_emscripten_set_pointerlockerror_callback_on_thread;_emscripten_set_pointerlockerror_callback_on_thread.sig="iiiiii";function _emscripten_get_pointerlock_status(pointerlockStatus){if(pointerlockStatus)fillPointerlockChangeEventData(pointerlockStatus);if(!document.body||!document.body.requestPointerLock&&!document.body.mozRequestPointerLock&&!document.body.webkitRequestPointerLock&&!document.body.msRequestPointerLock){return-1}return 0}Module["_emscripten_get_pointerlock_status"]=_emscripten_get_pointerlock_status;_emscripten_get_pointerlock_status.sig="ii";function requestPointerLock(target){if(target.requestPointerLock){target.requestPointerLock()}else{if(document.body.requestPointerLock){return-3}else{return-1}}return 0}Module["requestPointerLock"]=requestPointerLock;function _emscripten_request_pointerlock(target,deferUntilInEventHandler){target=findEventTarget(target);if(!target)return-4;if(!target.requestPointerLock){return-1}var canPerformRequests=JSEvents.canPerformEventHandlerRequests();if(!canPerformRequests){if(deferUntilInEventHandler){JSEvents.deferCall(requestPointerLock,2,[target]);return 1}else{return-2}}return requestPointerLock(target)}Module["_emscripten_request_pointerlock"]=_emscripten_request_pointerlock;_emscripten_request_pointerlock.sig="iii";function _emscripten_exit_pointerlock(){JSEvents.removeDeferredCalls(requestPointerLock);if(document.exitPointerLock){document.exitPointerLock()}else{return-1}return 0}Module["_emscripten_exit_pointerlock"]=_emscripten_exit_pointerlock;_emscripten_exit_pointerlock.sig="i";function _emscripten_vibrate(msecs){if(!navigator.vibrate)return-1;navigator.vibrate(msecs);return 0}Module["_emscripten_vibrate"]=_emscripten_vibrate;_emscripten_vibrate.sig="ii";function _emscripten_vibrate_pattern(msecsArray,numEntries){if(!navigator.vibrate)return-1;var vibrateList=[];for(var i=0;i>2];vibrateList.push(msecs)}navigator.vibrate(vibrateList);return 0}Module["_emscripten_vibrate_pattern"]=_emscripten_vibrate_pattern;_emscripten_vibrate_pattern.sig="iii";function fillVisibilityChangeEventData(eventStruct){var visibilityStates=["hidden","visible","prerender","unloaded"];var visibilityState=visibilityStates.indexOf(document.visibilityState);HEAP32[eventStruct>>2]=document.hidden;HEAP32[eventStruct+4>>2]=visibilityState}Module["fillVisibilityChangeEventData"]=fillVisibilityChangeEventData;function registerVisibilityChangeEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.visibilityChangeEvent)JSEvents.visibilityChangeEvent=_malloc(8);var visibilityChangeEventHandlerFunc=function(ev){var e=ev||event;var visibilityChangeEvent=JSEvents.visibilityChangeEvent;fillVisibilityChangeEventData(visibilityChangeEvent);if(getWasmTableEntry(callbackfunc)(eventTypeId,visibilityChangeEvent,userData))e.preventDefault()};var eventHandler={target:target,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:visibilityChangeEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerVisibilityChangeEventCallback"]=registerVisibilityChangeEventCallback;function _emscripten_set_visibilitychange_callback_on_thread(userData,useCapture,callbackfunc,targetThread){if(!specialHTMLTargets[1]){return-4}registerVisibilityChangeEventCallback(specialHTMLTargets[1],userData,useCapture,callbackfunc,21,"visibilitychange",targetThread);return 0}Module["_emscripten_set_visibilitychange_callback_on_thread"]=_emscripten_set_visibilitychange_callback_on_thread;_emscripten_set_visibilitychange_callback_on_thread.sig="iiiii";function _emscripten_get_visibility_status(visibilityStatus){if(typeof document.visibilityState=="undefined"&&typeof document.hidden=="undefined"){return-1}fillVisibilityChangeEventData(visibilityStatus);return 0}Module["_emscripten_get_visibility_status"]=_emscripten_get_visibility_status;_emscripten_get_visibility_status.sig="ii";function registerTouchEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.touchEvent)JSEvents.touchEvent=_malloc(1696);target=findEventTarget(target);var touchEventHandlerFunc=function(e){var t,touches={},et=e.touches;for(var i=0;i>3]=e.timeStamp;var idx=touchEvent>>2;HEAP32[idx+3]=e.ctrlKey;HEAP32[idx+4]=e.shiftKey;HEAP32[idx+5]=e.altKey;HEAP32[idx+6]=e.metaKey;idx+=7;var targetRect=getBoundingClientRect(target);var numTouches=0;for(var i in touches){t=touches[i];HEAP32[idx+0]=t.identifier;HEAP32[idx+1]=t.screenX;HEAP32[idx+2]=t.screenY;HEAP32[idx+3]=t.clientX;HEAP32[idx+4]=t.clientY;HEAP32[idx+5]=t.pageX;HEAP32[idx+6]=t.pageY;HEAP32[idx+7]=t.isChanged;HEAP32[idx+8]=t.onTarget;HEAP32[idx+9]=t.clientX-targetRect.left;HEAP32[idx+10]=t.clientY-targetRect.top;idx+=13;if(++numTouches>31){break}}HEAP32[touchEvent+8>>2]=numTouches;if(getWasmTableEntry(callbackfunc)(eventTypeId,touchEvent,userData))e.preventDefault()};var eventHandler={target:target,allowsDeferredCalls:eventTypeString=="touchstart"||eventTypeString=="touchend",eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:touchEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerTouchEventCallback"]=registerTouchEventCallback;function _emscripten_set_touchstart_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerTouchEventCallback(target,userData,useCapture,callbackfunc,22,"touchstart",targetThread);return 0}Module["_emscripten_set_touchstart_callback_on_thread"]=_emscripten_set_touchstart_callback_on_thread;_emscripten_set_touchstart_callback_on_thread.sig="iiiiii";function _emscripten_set_touchend_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerTouchEventCallback(target,userData,useCapture,callbackfunc,23,"touchend",targetThread);return 0}Module["_emscripten_set_touchend_callback_on_thread"]=_emscripten_set_touchend_callback_on_thread;_emscripten_set_touchend_callback_on_thread.sig="iiiiii";function _emscripten_set_touchmove_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerTouchEventCallback(target,userData,useCapture,callbackfunc,24,"touchmove",targetThread);return 0}Module["_emscripten_set_touchmove_callback_on_thread"]=_emscripten_set_touchmove_callback_on_thread;_emscripten_set_touchmove_callback_on_thread.sig="iiiiii";function _emscripten_set_touchcancel_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){registerTouchEventCallback(target,userData,useCapture,callbackfunc,25,"touchcancel",targetThread);return 0}Module["_emscripten_set_touchcancel_callback_on_thread"]=_emscripten_set_touchcancel_callback_on_thread;_emscripten_set_touchcancel_callback_on_thread.sig="iiiiii";function fillGamepadEventData(eventStruct,e){HEAPF64[eventStruct>>3]=e.timestamp;for(var i=0;i>3]=e.axes[i]}for(var i=0;i>3]=e.buttons[i].value}else{HEAPF64[eventStruct+i*8+528>>3]=e.buttons[i]}}for(var i=0;i>2]=e.buttons[i].pressed}else{HEAP32[eventStruct+i*4+1040>>2]=e.buttons[i]==1}}HEAP32[eventStruct+1296>>2]=e.connected;HEAP32[eventStruct+1300>>2]=e.index;HEAP32[eventStruct+8>>2]=e.axes.length;HEAP32[eventStruct+12>>2]=e.buttons.length;stringToUTF8(e.id,eventStruct+1304,64);stringToUTF8(e.mapping,eventStruct+1368,64)}Module["fillGamepadEventData"]=fillGamepadEventData;function registerGamepadEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.gamepadEvent)JSEvents.gamepadEvent=_malloc(1432);var gamepadEventHandlerFunc=function(ev){var e=ev||event;var gamepadEvent=JSEvents.gamepadEvent;fillGamepadEventData(gamepadEvent,e["gamepad"]);if(getWasmTableEntry(callbackfunc)(eventTypeId,gamepadEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),allowsDeferredCalls:true,eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:gamepadEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerGamepadEventCallback"]=registerGamepadEventCallback;function _emscripten_set_gamepadconnected_callback_on_thread(userData,useCapture,callbackfunc,targetThread){if(!navigator.getGamepads&&!navigator.webkitGetGamepads)return-1;registerGamepadEventCallback(2,userData,useCapture,callbackfunc,26,"gamepadconnected",targetThread);return 0}Module["_emscripten_set_gamepadconnected_callback_on_thread"]=_emscripten_set_gamepadconnected_callback_on_thread;_emscripten_set_gamepadconnected_callback_on_thread.sig="iiiii";function _emscripten_set_gamepaddisconnected_callback_on_thread(userData,useCapture,callbackfunc,targetThread){if(!navigator.getGamepads&&!navigator.webkitGetGamepads)return-1;registerGamepadEventCallback(2,userData,useCapture,callbackfunc,27,"gamepaddisconnected",targetThread);return 0}Module["_emscripten_set_gamepaddisconnected_callback_on_thread"]=_emscripten_set_gamepaddisconnected_callback_on_thread;_emscripten_set_gamepaddisconnected_callback_on_thread.sig="iiiii";function _emscripten_sample_gamepad_data(){return(JSEvents.lastGamepadState=navigator.getGamepads?navigator.getGamepads():navigator.webkitGetGamepads?navigator.webkitGetGamepads():null)?0:-1}Module["_emscripten_sample_gamepad_data"]=_emscripten_sample_gamepad_data;_emscripten_sample_gamepad_data.sig="i";function _emscripten_get_num_gamepads(){return JSEvents.lastGamepadState.length}Module["_emscripten_get_num_gamepads"]=_emscripten_get_num_gamepads;_emscripten_get_num_gamepads.sig="i";function _emscripten_get_gamepad_status(index,gamepadState){if(index<0||index>=JSEvents.lastGamepadState.length)return-5;if(!JSEvents.lastGamepadState[index])return-7;fillGamepadEventData(gamepadState,JSEvents.lastGamepadState[index]);return 0}Module["_emscripten_get_gamepad_status"]=_emscripten_get_gamepad_status;_emscripten_get_gamepad_status.sig="iii";function registerBeforeUnloadEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString){var beforeUnloadEventHandlerFunc=function(ev){var e=ev||event;var confirmationMessage=getWasmTableEntry(callbackfunc)(eventTypeId,0,userData);if(confirmationMessage){confirmationMessage=UTF8ToString(confirmationMessage)}if(confirmationMessage){e.preventDefault();e.returnValue=confirmationMessage;return confirmationMessage}};var eventHandler={target:findEventTarget(target),eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:beforeUnloadEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerBeforeUnloadEventCallback"]=registerBeforeUnloadEventCallback;function _emscripten_set_beforeunload_callback_on_thread(userData,callbackfunc,targetThread){if(typeof onbeforeunload=="undefined")return-1;if(targetThread!==1)return-5;registerBeforeUnloadEventCallback(2,userData,true,callbackfunc,28,"beforeunload");return 0}Module["_emscripten_set_beforeunload_callback_on_thread"]=_emscripten_set_beforeunload_callback_on_thread;_emscripten_set_beforeunload_callback_on_thread.sig="iii";function fillBatteryEventData(eventStruct,e){HEAPF64[eventStruct>>3]=e.chargingTime;HEAPF64[eventStruct+8>>3]=e.dischargingTime;HEAPF64[eventStruct+16>>3]=e.level;HEAP32[eventStruct+24>>2]=e.charging}Module["fillBatteryEventData"]=fillBatteryEventData;function battery(){return navigator.battery||navigator.mozBattery||navigator.webkitBattery}Module["battery"]=battery;function registerBatteryEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){if(!JSEvents.batteryEvent)JSEvents.batteryEvent=_malloc(32);var batteryEventHandlerFunc=function(ev){var e=ev||event;var batteryEvent=JSEvents.batteryEvent;fillBatteryEventData(batteryEvent,battery());if(getWasmTableEntry(callbackfunc)(eventTypeId,batteryEvent,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:batteryEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["registerBatteryEventCallback"]=registerBatteryEventCallback;function _emscripten_set_batterychargingchange_callback_on_thread(userData,callbackfunc,targetThread){if(!battery())return-1;registerBatteryEventCallback(battery(),userData,true,callbackfunc,29,"chargingchange",targetThread);return 0}Module["_emscripten_set_batterychargingchange_callback_on_thread"]=_emscripten_set_batterychargingchange_callback_on_thread;_emscripten_set_batterychargingchange_callback_on_thread.sig="iii";function _emscripten_set_batterylevelchange_callback_on_thread(userData,callbackfunc,targetThread){if(!battery())return-1;registerBatteryEventCallback(battery(),userData,true,callbackfunc,30,"levelchange",targetThread);return 0}Module["_emscripten_set_batterylevelchange_callback_on_thread"]=_emscripten_set_batterylevelchange_callback_on_thread;_emscripten_set_batterylevelchange_callback_on_thread.sig="iii";function _emscripten_get_battery_status(batteryState){if(!battery())return-1;fillBatteryEventData(batteryState,battery());return 0}Module["_emscripten_get_battery_status"]=_emscripten_get_battery_status;_emscripten_get_battery_status.sig="ii";function _emscripten_set_element_css_size(target,width,height){target=findEventTarget(target);if(!target)return-4;target.style.width=width+"px";target.style.height=height+"px";return 0}Module["_emscripten_set_element_css_size"]=_emscripten_set_element_css_size;_emscripten_set_element_css_size.sig="iiii";function _emscripten_get_element_css_size(target,width,height){target=findEventTarget(target);if(!target)return-4;var rect=getBoundingClientRect(target);HEAPF64[width>>3]=rect.width;HEAPF64[height>>3]=rect.height;return 0}Module["_emscripten_get_element_css_size"]=_emscripten_get_element_css_size;_emscripten_get_element_css_size.sig="iiii";function _emscripten_html5_remove_all_event_listeners(){JSEvents.removeAllEventListeners()}Module["_emscripten_html5_remove_all_event_listeners"]=_emscripten_html5_remove_all_event_listeners;_emscripten_html5_remove_all_event_listeners.sig="v";function _emscripten_request_animation_frame(cb,userData){return requestAnimationFrame(function(timeStamp){getWasmTableEntry(cb)(timeStamp,userData)})}Module["_emscripten_request_animation_frame"]=_emscripten_request_animation_frame;function _emscripten_cancel_animation_frame(id){cancelAnimationFrame(id)}Module["_emscripten_cancel_animation_frame"]=_emscripten_cancel_animation_frame;function _emscripten_request_animation_frame_loop(cb,userData){function tick(timeStamp){if(getWasmTableEntry(cb)(timeStamp,userData)){requestAnimationFrame(tick)}}return requestAnimationFrame(tick)}Module["_emscripten_request_animation_frame_loop"]=_emscripten_request_animation_frame_loop;function _emscripten_date_now(){return Date.now()}Module["_emscripten_date_now"]=_emscripten_date_now;function _emscripten_performance_now(){return performance.now()}Module["_emscripten_performance_now"]=_emscripten_performance_now;function _emscripten_get_device_pixel_ratio(){return typeof devicePixelRatio=="number"&&devicePixelRatio||1}Module["_emscripten_get_device_pixel_ratio"]=_emscripten_get_device_pixel_ratio;_emscripten_get_device_pixel_ratio.sig="d";function checkWasiClock(clock_id){return clock_id==0||clock_id==1||clock_id==2||clock_id==3}Module["checkWasiClock"]=checkWasiClock;function _clock_time_get(clk_id,ignored_precision,ptime){if(!checkWasiClock(clk_id)){return 28}var now;if(clk_id===0){now=Date.now()}else if(nowIsMonotonic){now=_emscripten_get_now()}else{return 52}var nsec=Math.round(now*1e3*1e3);HEAP32[ptime>>2]=nsec>>>0;HEAP32[ptime+4>>2]=nsec/Math.pow(2,32)>>>0;return 0}Module["_clock_time_get"]=_clock_time_get;_clock_time_get.sig="iijp";function _clock_res_get(clk_id,pres){if(!checkWasiClock(clk_id)){return 28}var nsec;if(clk_id===0){nsec=1e3*1e3}else if(nowIsMonotonic){nsec=_emscripten_get_now_res()}else{return 52}HEAP32[pres>>2]=nsec>>>0;HEAP32[pres+4>>2]=nsec/Math.pow(2,32)>>>0;return 0}Module["_clock_res_get"]=_clock_res_get;_clock_res_get.sig="iip";function _emscripten_unwind_to_js_event_loop(){throw"unwind"}Module["_emscripten_unwind_to_js_event_loop"]=_emscripten_unwind_to_js_event_loop;function setImmediateWrapped(func){if(!setImmediateWrapped.mapping)setImmediateWrapped.mapping=[];var id=setImmediateWrapped.mapping.length;setImmediateWrapped.mapping[id]=setImmediate(function(){setImmediateWrapped.mapping[id]=undefined;func()});return id}Module["setImmediateWrapped"]=setImmediateWrapped;function clearImmediateWrapped(id){clearImmediate(setImmediateWrapped.mapping[id]);setImmediateWrapped.mapping[id]=undefined}Module["clearImmediateWrapped"]=clearImmediateWrapped;function polyfillSetImmediate(){}Module["polyfillSetImmediate"]=polyfillSetImmediate;function _emscripten_set_immediate(cb,userData){polyfillSetImmediate();return emSetImmediate(function(){callUserCallback(function(){getWasmTableEntry(cb)(userData)})})}Module["_emscripten_set_immediate"]=_emscripten_set_immediate;_emscripten_set_immediate.sig="ipp";function _emscripten_clear_immediate(id){emClearImmediate(id)}Module["_emscripten_clear_immediate"]=_emscripten_clear_immediate;_emscripten_clear_immediate.sig="vi";function _emscripten_set_immediate_loop(cb,userData){polyfillSetImmediate();function tick(){callUserCallback(function(){if(getWasmTableEntry(cb)(userData)){emSetImmediate(tick)}})}return emSetImmediate(tick)}Module["_emscripten_set_immediate_loop"]=_emscripten_set_immediate_loop;_emscripten_set_immediate_loop.sig="vpp";function _emscripten_set_timeout(cb,msecs,userData){return setTimeout(function(){callUserCallback(function(){getWasmTableEntry(cb)(userData)})},msecs)}Module["_emscripten_set_timeout"]=_emscripten_set_timeout;_emscripten_set_timeout.sig="ipdp";function _emscripten_clear_timeout(id){clearTimeout(id)}Module["_emscripten_clear_timeout"]=_emscripten_clear_timeout;_emscripten_clear_timeout.sig="vi";function _emscripten_set_timeout_loop(cb,msecs,userData){function tick(){var t=performance.now();var n=t+msecs;callUserCallback(function(){if(getWasmTableEntry(cb)(t,userData)){setTimeout(tick,n-performance.now())}})}return setTimeout(tick,0)}Module["_emscripten_set_timeout_loop"]=_emscripten_set_timeout_loop;_emscripten_set_timeout_loop.sig="vpdp";function _emscripten_set_interval(cb,msecs,userData){return setInterval(function(){callUserCallback(function(){getWasmTableEntry(cb)(userData)})},msecs)}Module["_emscripten_set_interval"]=_emscripten_set_interval;_emscripten_set_interval.sig="ipdp";function _emscripten_clear_interval(id){clearInterval(id)}Module["_emscripten_clear_interval"]=_emscripten_clear_interval;_emscripten_clear_interval.sig="vi";function _llvm_eh_typeid_for(type){return type}Module["_llvm_eh_typeid_for"]=_llvm_eh_typeid_for;_llvm_eh_typeid_for.sig="ip";function ___cxa_get_exception_ptr(ptr){return new ExceptionInfo(ptr).get_exception_ptr()}Module["___cxa_get_exception_ptr"]=___cxa_get_exception_ptr;___cxa_get_exception_ptr.sig="pp";function ___cxa_find_matching_catch(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}var typeArray=Array.prototype.slice.call(arguments);for(var i=0;i0){dependenciesFulfilled=onload}else{onload()}}};script.onerror=function(){if(onerror)onerror()};script.src=UTF8ToString(url);document.body.appendChild(script)}Module["_emscripten_async_load_script"]=_emscripten_async_load_script;function _emscripten_get_main_loop_timing(mode,value){if(mode)HEAP32[mode>>2]=Browser.mainLoop.timingMode;if(value)HEAP32[value>>2]=Browser.mainLoop.timingValue}Module["_emscripten_get_main_loop_timing"]=_emscripten_get_main_loop_timing;_emscripten_get_main_loop_timing.sig="vii";function _emscripten_set_main_loop(func,fps,simulateInfiniteLoop){var browserIterationFunc=getWasmTableEntry(func);setMainLoop(browserIterationFunc,fps,simulateInfiniteLoop)}Module["_emscripten_set_main_loop"]=_emscripten_set_main_loop;_emscripten_set_main_loop.sig="viii";function _emscripten_set_main_loop_arg(func,arg,fps,simulateInfiniteLoop){var browserIterationFunc=function(){getWasmTableEntry(func)(arg)};setMainLoop(browserIterationFunc,fps,simulateInfiniteLoop,arg)}Module["_emscripten_set_main_loop_arg"]=_emscripten_set_main_loop_arg;_emscripten_set_main_loop_arg.sig="viiii";function _emscripten_cancel_main_loop(){Browser.mainLoop.pause();Browser.mainLoop.func=null}Module["_emscripten_cancel_main_loop"]=_emscripten_cancel_main_loop;_emscripten_cancel_main_loop.sig="v";function _emscripten_pause_main_loop(){Browser.mainLoop.pause()}Module["_emscripten_pause_main_loop"]=_emscripten_pause_main_loop;_emscripten_pause_main_loop.sig="v";function _emscripten_resume_main_loop(){Browser.mainLoop.resume()}Module["_emscripten_resume_main_loop"]=_emscripten_resume_main_loop;_emscripten_resume_main_loop.sig="v";function __emscripten_push_main_loop_blocker(func,arg,name){Browser.mainLoop.queue.push({func:function(){getWasmTableEntry(func)(arg)},name:UTF8ToString(name),counted:true});Browser.mainLoop.updateStatus()}Module["__emscripten_push_main_loop_blocker"]=__emscripten_push_main_loop_blocker;function __emscripten_push_uncounted_main_loop_blocker(func,arg,name){Browser.mainLoop.queue.push({func:function(){getWasmTableEntry(func)(arg)},name:UTF8ToString(name),counted:false});Browser.mainLoop.updateStatus()}Module["__emscripten_push_uncounted_main_loop_blocker"]=__emscripten_push_uncounted_main_loop_blocker;function _emscripten_set_main_loop_expected_blockers(num){Browser.mainLoop.expectedBlockers=num;Browser.mainLoop.remainingBlockers=num;Browser.mainLoop.updateStatus()}Module["_emscripten_set_main_loop_expected_blockers"]=_emscripten_set_main_loop_expected_blockers;_emscripten_set_main_loop_expected_blockers.sig="vi";function _emscripten_async_call(func,arg,millis){function wrapper(){getWasmTableEntry(func)(arg)}if(millis>=0||ENVIRONMENT_IS_NODE){safeSetTimeout(wrapper,millis)}else{Browser.safeRequestAnimationFrame(wrapper)}}Module["_emscripten_async_call"]=_emscripten_async_call;_emscripten_async_call.sig="vppi";function _emscripten_get_window_title(){var buflen=256;if(!_emscripten_get_window_title.buffer){_emscripten_get_window_title.buffer=_malloc(buflen)}stringToUTF8(document.title,_emscripten_get_window_title.buffer,buflen);return _emscripten_get_window_title.buffer}Module["_emscripten_get_window_title"]=_emscripten_get_window_title;_emscripten_get_window_title.sig="iv";function _emscripten_set_window_title(title){setWindowTitle(UTF8ToString(title))}Module["_emscripten_set_window_title"]=_emscripten_set_window_title;_emscripten_set_window_title.sig="vi";function _emscripten_get_screen_size(width,height){HEAP32[width>>2]=screen.width;HEAP32[height>>2]=screen.height}Module["_emscripten_get_screen_size"]=_emscripten_get_screen_size;_emscripten_get_screen_size.sig="vii";function _emscripten_hide_mouse(){var styleSheet=document.styleSheets[0];var rules=styleSheet.cssRules;for(var i=0;i>2]=canvas.width;HEAP32[height>>2]=canvas.height;HEAP32[isFullscreen>>2]=Browser.isFullscreen?1:0}Module["_emscripten_get_canvas_size"]=_emscripten_get_canvas_size;_emscripten_get_canvas_size.sig="viii";function _emscripten_create_worker(url){url=UTF8ToString(url);var id=Browser.workers.length;var info={worker:new Worker(url),callbacks:[],awaited:0,buffer:0,bufferSize:0};info.worker.onmessage=function info_worker_onmessage(msg){if(ABORT)return;var info=Browser.workers[id];if(!info)return;var callbackId=msg.data["callbackId"];var callbackInfo=info.callbacks[callbackId];if(!callbackInfo)return;if(msg.data["finalResponse"]){info.awaited--;info.callbacks[callbackId]=null}var data=msg.data["data"];if(data){if(!data.byteLength)data=new Uint8Array(data);if(!info.buffer||info.bufferSize>2]=canvas.width;HEAP32[h>>2]=canvas.height;return buf}return 0}Module["_emscripten_get_preloaded_image_data"]=_emscripten_get_preloaded_image_data;_emscripten_get_preloaded_image_data.sig="iiii";function _emscripten_get_preloaded_image_data_from_FILE(file,w,h){var fd=Module["_fileno"](file);var stream=FS.getStream(fd);if(stream){return _emscripten_get_preloaded_image_data(stream.path,w,h)}return 0}Module["_emscripten_get_preloaded_image_data_from_FILE"]=_emscripten_get_preloaded_image_data_from_FILE;_emscripten_get_preloaded_image_data_from_FILE.sig="iiii";var wget={wgetRequests:{},nextWgetRequestHandle:0,getNextWgetRequestHandle:function(){var handle=wget.nextWgetRequestHandle;wget.nextWgetRequestHandle++;return handle}};Module["wget"]=wget;function _emscripten_async_wget(url,file,onload,onerror){var _url=UTF8ToString(url);var _file=UTF8ToString(file);_file=PATH_FS.resolve(_file);function doCallback(callback){if(callback){callUserCallback(function(){withStackSave(function(){getWasmTableEntry(callback)(allocateUTF8OnStack(_file))})})}}var destinationDirectory=PATH.dirname(_file);FS.createPreloadedFile(destinationDirectory,PATH.basename(_file),_url,true,true,function(){doCallback(onload)},function(){doCallback(onerror)},false,false,function(){try{FS.unlink(_file)}catch(e){}FS.mkdirTree(destinationDirectory)})}Module["_emscripten_async_wget"]=_emscripten_async_wget;_emscripten_async_wget.sig="viiii";function _emscripten_async_wget_data(url,arg,onload,onerror){asyncLoad(UTF8ToString(url),function(byteArray){callUserCallback(function(){var buffer=_malloc(byteArray.length);HEAPU8.set(byteArray,buffer);getWasmTableEntry(onload)(arg,buffer,byteArray.length);_free(buffer)})},function(){if(onerror){callUserCallback(function(){getWasmTableEntry(onerror)(arg)})}},true)}Module["_emscripten_async_wget_data"]=_emscripten_async_wget_data;_emscripten_async_wget_data.sig="viiii";function _emscripten_async_wget2(url,file,request,param,arg,onload,onerror,onprogress){var _url=UTF8ToString(url);var _file=UTF8ToString(file);_file=PATH_FS.resolve(_file);var _request=UTF8ToString(request);var _param=UTF8ToString(param);var index=_file.lastIndexOf("/");var http=new XMLHttpRequest;http.open(_request,_url,true);http.responseType="arraybuffer";var handle=wget.getNextWgetRequestHandle();var destinationDirectory=PATH.dirname(_file);http.onload=function http_onload(e){if(http.status>=200&&http.status<300){try{FS.unlink(_file)}catch(e){}FS.mkdirTree(destinationDirectory);FS.createDataFile(_file.substr(0,index),_file.substr(index+1),new Uint8Array(http.response),true,true,false);if(onload){withStackSave(function(){getWasmTableEntry(onload)(handle,arg,allocateUTF8OnStack(_file))})}}else{if(onerror)getWasmTableEntry(onerror)(handle,arg,http.status)}delete wget.wgetRequests[handle]};http.onerror=function http_onerror(e){if(onerror)getWasmTableEntry(onerror)(handle,arg,http.status);delete wget.wgetRequests[handle]};http.onprogress=function http_onprogress(e){if(e.lengthComputable||e.lengthComputable===undefined&&e.total!=0){var percentComplete=e.loaded/e.total*100;if(onprogress)getWasmTableEntry(onprogress)(handle,arg,percentComplete)}};http.onabort=function http_onabort(e){delete wget.wgetRequests[handle]};if(_request=="POST"){http.setRequestHeader("Content-type","application/x-www-form-urlencoded");http.send(_param)}else{http.send(null)}wget.wgetRequests[handle]=http;return handle}Module["_emscripten_async_wget2"]=_emscripten_async_wget2;_emscripten_async_wget2.sig="iiiiiiiii";function _emscripten_async_wget2_data(url,request,param,arg,free,onload,onerror,onprogress){var _url=UTF8ToString(url);var _request=UTF8ToString(request);var _param=UTF8ToString(param);var http=new XMLHttpRequest;http.open(_request,_url,true);http.responseType="arraybuffer";var handle=wget.getNextWgetRequestHandle();function onerrorjs(){if(onerror){var statusText=0;if(http.statusText){var len=lengthBytesUTF8(http.statusText)+1;statusText=stackAlloc(len);stringToUTF8(http.statusText,statusText,len)}getWasmTableEntry(onerror)(handle,arg,http.status,statusText)}}http.onload=function http_onload(e){if(http.status>=200&&http.status<300||http.status===0&&_url.substr(0,4).toLowerCase()!="http"){var byteArray=new Uint8Array(http.response);var buffer=_malloc(byteArray.length);HEAPU8.set(byteArray,buffer);if(onload)getWasmTableEntry(onload)(handle,arg,buffer,byteArray.length);if(free)_free(buffer)}else{onerrorjs()}delete wget.wgetRequests[handle]};http.onerror=function http_onerror(e){onerrorjs();delete wget.wgetRequests[handle]};http.onprogress=function http_onprogress(e){if(onprogress)getWasmTableEntry(onprogress)(handle,arg,e.loaded,e.lengthComputable||e.lengthComputable===undefined?e.total:0)};http.onabort=function http_onabort(e){delete wget.wgetRequests[handle]};if(_request=="POST"){http.setRequestHeader("Content-type","application/x-www-form-urlencoded");http.send(_param)}else{http.send(null)}wget.wgetRequests[handle]=http;return handle}Module["_emscripten_async_wget2_data"]=_emscripten_async_wget2_data;_emscripten_async_wget2_data.sig="iiiiiiiii";function _emscripten_async_wget2_abort(handle){var http=wget.wgetRequests[handle];if(http){http.abort()}}Module["_emscripten_async_wget2_abort"]=_emscripten_async_wget2_abort;_emscripten_async_wget2_abort.sig="vi";function _setNetworkCallback(event,userData,callback){function _callback(data){try{if(event==="error"){withStackSave(function(){var msg=allocateUTF8OnStack(data[2]);getWasmTableEntry(callback)(data[0],data[1],msg,userData)})}else{getWasmTableEntry(callback)(data,userData)}}catch(e){if(e instanceof ExitStatus){return}else{if(e&&typeof e=="object"&&e.stack)err("exception thrown: "+[e,e.stack]);throw e}}}Module["websocket"]["on"](event,callback?_callback:null)}Module["_setNetworkCallback"]=_setNetworkCallback;function _emscripten_set_socket_error_callback(userData,callback){_setNetworkCallback("error",userData,callback)}Module["_emscripten_set_socket_error_callback"]=_emscripten_set_socket_error_callback;function _emscripten_set_socket_open_callback(userData,callback){_setNetworkCallback("open",userData,callback)}Module["_emscripten_set_socket_open_callback"]=_emscripten_set_socket_open_callback;function _emscripten_set_socket_listen_callback(userData,callback){_setNetworkCallback("listen",userData,callback)}Module["_emscripten_set_socket_listen_callback"]=_emscripten_set_socket_listen_callback;function _emscripten_set_socket_connection_callback(userData,callback){_setNetworkCallback("connection",userData,callback)}Module["_emscripten_set_socket_connection_callback"]=_emscripten_set_socket_connection_callback;function _emscripten_set_socket_message_callback(userData,callback){_setNetworkCallback("message",userData,callback)}Module["_emscripten_set_socket_message_callback"]=_emscripten_set_socket_message_callback;function _emscripten_set_socket_close_callback(userData,callback){_setNetworkCallback("close",userData,callback)}Module["_emscripten_set_socket_close_callback"]=_emscripten_set_socket_close_callback;function _emscripten_webgl_enable_ANGLE_instanced_arrays(ctx){return __webgl_enable_ANGLE_instanced_arrays(GL.contexts[ctx].GLctx)}Module["_emscripten_webgl_enable_ANGLE_instanced_arrays"]=_emscripten_webgl_enable_ANGLE_instanced_arrays;function _emscripten_webgl_enable_OES_vertex_array_object(ctx){return __webgl_enable_OES_vertex_array_object(GL.contexts[ctx].GLctx)}Module["_emscripten_webgl_enable_OES_vertex_array_object"]=_emscripten_webgl_enable_OES_vertex_array_object;function _emscripten_webgl_enable_WEBGL_draw_buffers(ctx){return __webgl_enable_WEBGL_draw_buffers(GL.contexts[ctx].GLctx)}Module["_emscripten_webgl_enable_WEBGL_draw_buffers"]=_emscripten_webgl_enable_WEBGL_draw_buffers;function _emscripten_webgl_enable_WEBGL_multi_draw(ctx){return __webgl_enable_WEBGL_multi_draw(GL.contexts[ctx].GLctx)}Module["_emscripten_webgl_enable_WEBGL_multi_draw"]=_emscripten_webgl_enable_WEBGL_multi_draw;function _glPixelStorei(pname,param){if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)}Module["_glPixelStorei"]=_glPixelStorei;_glPixelStorei.sig="vii";function _glGetString(name_){var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret}Module["_glGetString"]=_glGetString;_glGetString.sig="ii";function _glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}Module["_glGetIntegerv"]=_glGetIntegerv;_glGetIntegerv.sig="vii";function _glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}Module["_glGetFloatv"]=_glGetFloatv;_glGetFloatv.sig="vii";function _glGetBooleanv(name_,p){emscriptenWebGLGet(name_,p,4)}Module["_glGetBooleanv"]=_glGetBooleanv;_glGetBooleanv.sig="vii";function _glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}Module["_glDeleteTextures"]=_glDeleteTextures;_glDeleteTextures.sig="vii";function _glCompressedTexImage2D(target,level,internalFormat,width,height,border,imageSize,data){GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,data?HEAPU8.subarray(data,data+imageSize):null)}Module["_glCompressedTexImage2D"]=_glCompressedTexImage2D;_glCompressedTexImage2D.sig="viiiiiiii";function _glCompressedTexSubImage2D(target,level,xoffset,yoffset,width,height,format,imageSize,data){GLctx["compressedTexSubImage2D"](target,level,xoffset,yoffset,width,height,format,data?HEAPU8.subarray(data,data+imageSize):null)}Module["_glCompressedTexSubImage2D"]=_glCompressedTexSubImage2D;_glCompressedTexSubImage2D.sig="viiiiiiiii";function _glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}Module["_glTexImage2D"]=_glTexImage2D;_glTexImage2D.sig="viiiiiiiii";function _glTexSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels){var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)}Module["_glTexSubImage2D"]=_glTexSubImage2D;_glTexSubImage2D.sig="viiiiiiiii";function _glReadPixels(x,y,width,height,format,type,pixels){var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}Module["_glReadPixels"]=_glReadPixels;_glReadPixels.sig="viiiiiii";function _glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}Module["_glBindTexture"]=_glBindTexture;_glBindTexture.sig="vii";function _glGetTexParameterfv(target,pname,params){if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getTexParameter(target,pname)}Module["_glGetTexParameterfv"]=_glGetTexParameterfv;_glGetTexParameterfv.sig="viii";function _glGetTexParameteriv(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getTexParameter(target,pname)}Module["_glGetTexParameteriv"]=_glGetTexParameteriv;_glGetTexParameteriv.sig="viii";function _glTexParameterfv(target,pname,params){var param=HEAPF32[params>>2];GLctx.texParameterf(target,pname,param)}Module["_glTexParameterfv"]=_glTexParameterfv;_glTexParameterfv.sig="viii";function _glTexParameteriv(target,pname,params){var param=HEAP32[params>>2];GLctx.texParameteri(target,pname,param)}Module["_glTexParameteriv"]=_glTexParameteriv;_glTexParameteriv.sig="viii";function _glIsTexture(id){var texture=GL.textures[id];if(!texture)return 0;return GLctx.isTexture(texture)}Module["_glIsTexture"]=_glIsTexture;_glIsTexture.sig="ii";function _glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}Module["_glGenBuffers"]=_glGenBuffers;_glGenBuffers.sig="vii";function _glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}Module["_glGenTextures"]=_glGenTextures;_glGenTextures.sig="vii";function _glDeleteBuffers(n,buffers){for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null}}Module["_glDeleteBuffers"]=_glDeleteBuffers;_glDeleteBuffers.sig="vii";function _glGetBufferParameteriv(target,value,data){if(!data){GL.recordError(1281);return}HEAP32[data>>2]=GLctx.getBufferParameter(target,value)}Module["_glGetBufferParameteriv"]=_glGetBufferParameteriv;_glGetBufferParameteriv.sig="viii";function _glBufferData(target,size,data,usage){GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}Module["_glBufferData"]=_glBufferData;_glBufferData.sig="viiii";function _glBufferSubData(target,offset,size,data){GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}Module["_glBufferSubData"]=_glBufferSubData;_glBufferSubData.sig="viiii";function _glGenQueriesEXT(n,ids){for(var i=0;i>2]=0;return}var id=GL.getNewId(GL.queries);query.name=id;GL.queries[id]=query;HEAP32[ids+i*4>>2]=id}}Module["_glGenQueriesEXT"]=_glGenQueriesEXT;_glGenQueriesEXT.sig="vii";function _glDeleteQueriesEXT(n,ids){for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx.disjointTimerQueryExt["deleteQueryEXT"](query);GL.queries[id]=null}}Module["_glDeleteQueriesEXT"]=_glDeleteQueriesEXT;_glDeleteQueriesEXT.sig="vii";function _glIsQueryEXT(id){var query=GL.queries[id];if(!query)return 0;return GLctx.disjointTimerQueryExt["isQueryEXT"](query)}Module["_glIsQueryEXT"]=_glIsQueryEXT;_glIsQueryEXT.sig="ii";function _glBeginQueryEXT(target,id){GLctx.disjointTimerQueryExt["beginQueryEXT"](target,GL.queries[id])}Module["_glBeginQueryEXT"]=_glBeginQueryEXT;_glBeginQueryEXT.sig="vii";function _glEndQueryEXT(target){GLctx.disjointTimerQueryExt["endQueryEXT"](target)}Module["_glEndQueryEXT"]=_glEndQueryEXT;_glEndQueryEXT.sig="vi";function _glQueryCounterEXT(id,target){GLctx.disjointTimerQueryExt["queryCounterEXT"](GL.queries[id],target)}Module["_glQueryCounterEXT"]=_glQueryCounterEXT;_glQueryCounterEXT.sig="vii";function _glGetQueryivEXT(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.disjointTimerQueryExt["getQueryEXT"](target,pname)}Module["_glGetQueryivEXT"]=_glGetQueryivEXT;_glGetQueryivEXT.sig="viii";function _glGetQueryObjectivEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}Module["_glGetQueryObjectivEXT"]=_glGetQueryObjectivEXT;_glGetQueryObjectivEXT.sig="viii";function _glGetQueryObjectuivEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}Module["_glGetQueryObjectuivEXT"]=_glGetQueryObjectuivEXT;_glGetQueryObjectuivEXT.sig="viii";function _glGetQueryObjecti64vEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param;{param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname)}var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)}Module["_glGetQueryObjecti64vEXT"]=_glGetQueryObjecti64vEXT;_glGetQueryObjecti64vEXT.sig="viii";function _glGetQueryObjectui64vEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.queries[id];var param;{param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname)}var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)}Module["_glGetQueryObjectui64vEXT"]=_glGetQueryObjectui64vEXT;_glGetQueryObjectui64vEXT.sig="viii";function _glIsBuffer(buffer){var b=GL.buffers[buffer];if(!b)return 0;return GLctx.isBuffer(b)}Module["_glIsBuffer"]=_glIsBuffer;_glIsBuffer.sig="ii";function _glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}Module["_glGenRenderbuffers"]=_glGenRenderbuffers;_glGenRenderbuffers.sig="vii";function _glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}Module["_glDeleteRenderbuffers"]=_glDeleteRenderbuffers;_glDeleteRenderbuffers.sig="vii";function _glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}Module["_glBindRenderbuffer"]=_glBindRenderbuffer;_glBindRenderbuffer.sig="vii";function _glGetRenderbufferParameteriv(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getRenderbufferParameter(target,pname)}Module["_glGetRenderbufferParameteriv"]=_glGetRenderbufferParameteriv;_glGetRenderbufferParameteriv.sig="viii";function _glIsRenderbuffer(renderbuffer){var rb=GL.renderbuffers[renderbuffer];if(!rb)return 0;return GLctx.isRenderbuffer(rb)}Module["_glIsRenderbuffer"]=_glIsRenderbuffer;_glIsRenderbuffer.sig="ii";function _glGetUniformfv(program,location,params){emscriptenWebGLGetUniform(program,location,params,2)}Module["_glGetUniformfv"]=_glGetUniformfv;_glGetUniformfv.sig="viii";function _glGetUniformiv(program,location,params){emscriptenWebGLGetUniform(program,location,params,0)}Module["_glGetUniformiv"]=_glGetUniformiv;_glGetUniformiv.sig="viii";function _glGetUniformLocation(program,name){name=UTF8ToString(name);if(program=GL.programs[program]){webglPrepareUniformLocationsBeforeFirstUse(program);var uniformLocsById=program.uniformLocsById;var arrayIndex=0;var uniformBaseName=name;var leftBrace=webglGetLeftBracePos(name);if(leftBrace>0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex>2]=GLctx.getVertexAttribOffset(index,pname)}Module["_glGetVertexAttribPointerv"]=_glGetVertexAttribPointerv;_glGetVertexAttribPointerv.sig="viii";function _glUniform1f(location,v0){GLctx.uniform1f(webglGetUniformLocation(location),v0)}Module["_glUniform1f"]=_glUniform1f;_glUniform1f.sig="vif";function _glUniform2f(location,v0,v1){GLctx.uniform2f(webglGetUniformLocation(location),v0,v1)}Module["_glUniform2f"]=_glUniform2f;_glUniform2f.sig="viff";function _glUniform3f(location,v0,v1,v2){GLctx.uniform3f(webglGetUniformLocation(location),v0,v1,v2)}Module["_glUniform3f"]=_glUniform3f;_glUniform3f.sig="vifff";function _glUniform4f(location,v0,v1,v2,v3){GLctx.uniform4f(webglGetUniformLocation(location),v0,v1,v2,v3)}Module["_glUniform4f"]=_glUniform4f;_glUniform4f.sig="viffff";function _glUniform1i(location,v0){GLctx.uniform1i(webglGetUniformLocation(location),v0)}Module["_glUniform1i"]=_glUniform1i;_glUniform1i.sig="vii";function _glUniform2i(location,v0,v1){GLctx.uniform2i(webglGetUniformLocation(location),v0,v1)}Module["_glUniform2i"]=_glUniform2i;_glUniform2i.sig="viii";function _glUniform3i(location,v0,v1,v2){GLctx.uniform3i(webglGetUniformLocation(location),v0,v1,v2)}Module["_glUniform3i"]=_glUniform3i;_glUniform3i.sig="viiii";function _glUniform4i(location,v0,v1,v2,v3){GLctx.uniform4i(webglGetUniformLocation(location),v0,v1,v2,v3)}Module["_glUniform4i"]=_glUniform4i;_glUniform4i.sig="viiiii";function _glUniform1iv(location,count,value){if(count<=288){var view=__miniTempWebGLIntBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(webglGetUniformLocation(location),view)}Module["_glUniform1iv"]=_glUniform1iv;_glUniform1iv.sig="viii";function _glUniform2iv(location,count,value){if(count<=144){var view=__miniTempWebGLIntBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(webglGetUniformLocation(location),view)}Module["_glUniform2iv"]=_glUniform2iv;_glUniform2iv.sig="viii";function _glUniform3iv(location,count,value){if(count<=96){var view=__miniTempWebGLIntBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3iv(webglGetUniformLocation(location),view)}Module["_glUniform3iv"]=_glUniform3iv;_glUniform3iv.sig="viii";function _glUniform4iv(location,count,value){if(count<=72){var view=__miniTempWebGLIntBuffers[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2];view[i+3]=HEAP32[value+(4*i+12)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4iv(webglGetUniformLocation(location),view)}Module["_glUniform4iv"]=_glUniform4iv;_glUniform4iv.sig="viii";function _glUniform1fv(location,count,value){if(count<=288){var view=miniTempWebGLFloatBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1fv(webglGetUniformLocation(location),view)}Module["_glUniform1fv"]=_glUniform1fv;_glUniform1fv.sig="viii";function _glUniform2fv(location,count,value){if(count<=144){var view=miniTempWebGLFloatBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(webglGetUniformLocation(location),view)}Module["_glUniform2fv"]=_glUniform2fv;_glUniform2fv.sig="viii";function _glUniform3fv(location,count,value){if(count<=96){var view=miniTempWebGLFloatBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(webglGetUniformLocation(location),view)}Module["_glUniform3fv"]=_glUniform3fv;_glUniform3fv.sig="viii";function _glUniform4fv(location,count,value){if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<4*count;i+=4){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4fv(webglGetUniformLocation(location),view)}Module["_glUniform4fv"]=_glUniform4fv;_glUniform4fv.sig="viii";function _glUniformMatrix2fv(location,count,transpose,value){if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniformMatrix2fv(webglGetUniformLocation(location),!!transpose,view)}Module["_glUniformMatrix2fv"]=_glUniformMatrix2fv;_glUniformMatrix2fv.sig="viiii";function _glUniformMatrix3fv(location,count,transpose,value){if(count<=32){var view=miniTempWebGLFloatBuffers[9*count-1];for(var i=0;i<9*count;i+=9){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,view)}Module["_glUniformMatrix3fv"]=_glUniformMatrix3fv;_glUniformMatrix3fv.sig="viiii";function _glUniformMatrix4fv(location,count,transpose,value){if(count<=18){var view=miniTempWebGLFloatBuffers[16*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<16*count;i+=16){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3];view[i+4]=heap[dst+4];view[i+5]=heap[dst+5];view[i+6]=heap[dst+6];view[i+7]=heap[dst+7];view[i+8]=heap[dst+8];view[i+9]=heap[dst+9];view[i+10]=heap[dst+10];view[i+11]=heap[dst+11];view[i+12]=heap[dst+12];view[i+13]=heap[dst+13];view[i+14]=heap[dst+14];view[i+15]=heap[dst+15]}}else{var view=HEAPF32.subarray(value>>2,value+count*64>>2)}GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,view)}Module["_glUniformMatrix4fv"]=_glUniformMatrix4fv;_glUniformMatrix4fv.sig="viiii";function _glBindBuffer(target,buffer){GLctx.bindBuffer(target,GL.buffers[buffer])}Module["_glBindBuffer"]=_glBindBuffer;_glBindBuffer.sig="vii";function _glVertexAttrib1fv(index,v){GLctx.vertexAttrib1f(index,HEAPF32[v>>2])}Module["_glVertexAttrib1fv"]=_glVertexAttrib1fv;_glVertexAttrib1fv.sig="vii";function _glVertexAttrib2fv(index,v){GLctx.vertexAttrib2f(index,HEAPF32[v>>2],HEAPF32[v+4>>2])}Module["_glVertexAttrib2fv"]=_glVertexAttrib2fv;_glVertexAttrib2fv.sig="vii";function _glVertexAttrib3fv(index,v){GLctx.vertexAttrib3f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2])}Module["_glVertexAttrib3fv"]=_glVertexAttrib3fv;_glVertexAttrib3fv.sig="vii";function _glVertexAttrib4fv(index,v){GLctx.vertexAttrib4f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2],HEAPF32[v+12>>2])}Module["_glVertexAttrib4fv"]=_glVertexAttrib4fv;_glVertexAttrib4fv.sig="vii";function _glGetAttribLocation(program,name){return GLctx.getAttribLocation(GL.programs[program],UTF8ToString(name))}Module["_glGetAttribLocation"]=_glGetAttribLocation;_glGetAttribLocation.sig="iii";function _glGetActiveAttrib(program,index,bufSize,length,size,type,name){__glGetActiveAttribOrUniform("getActiveAttrib",program,index,bufSize,length,size,type,name)}Module["_glGetActiveAttrib"]=_glGetActiveAttrib;_glGetActiveAttrib.sig="viiiiiii";function _glGetActiveUniform(program,index,bufSize,length,size,type,name){__glGetActiveAttribOrUniform("getActiveUniform",program,index,bufSize,length,size,type,name)}Module["_glGetActiveUniform"]=_glGetActiveUniform;_glGetActiveUniform.sig="viiiiiii";function _glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}Module["_glCreateShader"]=_glCreateShader;_glCreateShader.sig="ii";function _glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}Module["_glDeleteShader"]=_glDeleteShader;_glDeleteShader.sig="vi";function _glGetAttachedShaders(program,maxCount,count,shaders){var result=GLctx.getAttachedShaders(GL.programs[program]);var len=result.length;if(len>maxCount){len=maxCount}HEAP32[count>>2]=len;for(var i=0;i>2]=id}}Module["_glGetAttachedShaders"]=_glGetAttachedShaders;_glGetAttachedShaders.sig="viiii";function _glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}Module["_glShaderSource"]=_glShaderSource;_glShaderSource.sig="viiii";function _glGetShaderSource(shader,bufSize,length,source){var result=GLctx.getShaderSource(GL.shaders[shader]);if(!result)return;var numBytesWrittenExclNull=bufSize>0&&source?stringToUTF8(result,source,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}Module["_glGetShaderSource"]=_glGetShaderSource;_glGetShaderSource.sig="viiii";function _glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}Module["_glCompileShader"]=_glCompileShader;_glCompileShader.sig="vi";function _glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}Module["_glGetShaderInfoLog"]=_glGetShaderInfoLog;_glGetShaderInfoLog.sig="viiii";function _glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}Module["_glGetShaderiv"]=_glGetShaderiv;_glGetShaderiv.sig="viii";function _glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}}Module["_glGetProgramiv"]=_glGetProgramiv;_glGetProgramiv.sig="viii";function _glIsShader(shader){var s=GL.shaders[shader];if(!s)return 0;return GLctx.isShader(s)}Module["_glIsShader"]=_glIsShader;_glIsShader.sig="ii";function _glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id}Module["_glCreateProgram"]=_glCreateProgram;_glCreateProgram.sig="i";function _glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null}Module["_glDeleteProgram"]=_glDeleteProgram;_glDeleteProgram.sig="vi";function _glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}Module["_glAttachShader"]=_glAttachShader;_glAttachShader.sig="vii";function _glDetachShader(program,shader){GLctx.detachShader(GL.programs[program],GL.shaders[shader])}Module["_glDetachShader"]=_glDetachShader;_glDetachShader.sig="vii";function _glGetShaderPrecisionFormat(shaderType,precisionType,range,precision){var result=GLctx.getShaderPrecisionFormat(shaderType,precisionType);HEAP32[range>>2]=result.rangeMin;HEAP32[range+4>>2]=result.rangeMax;HEAP32[precision>>2]=result.precision}Module["_glGetShaderPrecisionFormat"]=_glGetShaderPrecisionFormat;_glGetShaderPrecisionFormat.sig="viiii";function _glLinkProgram(program){program=GL.programs[program];GLctx.linkProgram(program);program.uniformLocsById=0;program.uniformSizeAndIdsByName={}}Module["_glLinkProgram"]=_glLinkProgram;_glLinkProgram.sig="vi";function _glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}Module["_glGetProgramInfoLog"]=_glGetProgramInfoLog;_glGetProgramInfoLog.sig="viiii";function _glUseProgram(program){program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program}Module["_glUseProgram"]=_glUseProgram;_glUseProgram.sig="vi";function _glValidateProgram(program){GLctx.validateProgram(GL.programs[program])}Module["_glValidateProgram"]=_glValidateProgram;_glValidateProgram.sig="vi";function _glIsProgram(program){program=GL.programs[program];if(!program)return 0;return GLctx.isProgram(program)}Module["_glIsProgram"]=_glIsProgram;_glIsProgram.sig="ii";function _glBindAttribLocation(program,index,name){GLctx.bindAttribLocation(GL.programs[program],index,UTF8ToString(name))}Module["_glBindAttribLocation"]=_glBindAttribLocation;_glBindAttribLocation.sig="viii";function _glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,GL.framebuffers[framebuffer])}Module["_glBindFramebuffer"]=_glBindFramebuffer;_glBindFramebuffer.sig="vii";function _glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}Module["_glGenFramebuffers"]=_glGenFramebuffers;_glGenFramebuffers.sig="vii";function _glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}Module["_glDeleteFramebuffers"]=_glDeleteFramebuffers;_glDeleteFramebuffers.sig="vii";function _glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}Module["_glFramebufferRenderbuffer"]=_glFramebufferRenderbuffer;_glFramebufferRenderbuffer.sig="viiii";function _glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}Module["_glFramebufferTexture2D"]=_glFramebufferTexture2D;_glFramebufferTexture2D.sig="viiiii";function _glGetFramebufferAttachmentParameteriv(target,attachment,pname,params){var result=GLctx.getFramebufferAttachmentParameter(target,attachment,pname);if(result instanceof WebGLRenderbuffer||result instanceof WebGLTexture){result=result.name|0}HEAP32[params>>2]=result}Module["_glGetFramebufferAttachmentParameteriv"]=_glGetFramebufferAttachmentParameteriv;_glGetFramebufferAttachmentParameteriv.sig="viiii";function _glIsFramebuffer(framebuffer){var fb=GL.framebuffers[framebuffer];if(!fb)return 0;return GLctx.isFramebuffer(fb)}Module["_glIsFramebuffer"]=_glIsFramebuffer;_glIsFramebuffer.sig="ii";function _glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}Module["_glGenVertexArrays"]=_glGenVertexArrays;_glGenVertexArrays.sig="vii";function _glDeleteVertexArrays(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}Module["_glDeleteVertexArrays"]=_glDeleteVertexArrays;_glDeleteVertexArrays.sig="vii";function _glBindVertexArray(vao){GLctx["bindVertexArray"](GL.vaos[vao])}Module["_glBindVertexArray"]=_glBindVertexArray;_glBindVertexArray.sig="vi";function _glIsVertexArray(array){var vao=GL.vaos[array];if(!vao)return 0;return GLctx["isVertexArray"](vao)}Module["_glIsVertexArray"]=_glIsVertexArray;_glIsVertexArray.sig="ii";function _glVertexPointer(){throw"Legacy GL function (glVertexPointer) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_glVertexPointer"]=_glVertexPointer;function _glMatrixMode(){throw"Legacy GL function (glMatrixMode) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_glMatrixMode"]=_glMatrixMode;function _glBegin(){throw"Legacy GL function (glBegin) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_glBegin"]=_glBegin;function _glLoadIdentity(){throw"Legacy GL function (glLoadIdentity) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_glLoadIdentity"]=_glLoadIdentity;function _glGenVertexArraysOES(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}Module["_glGenVertexArraysOES"]=_glGenVertexArraysOES;_glGenVertexArraysOES.sig="vii";function _glDeleteVertexArraysOES(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}Module["_glDeleteVertexArraysOES"]=_glDeleteVertexArraysOES;_glDeleteVertexArraysOES.sig="vii";function _glBindVertexArrayOES(vao){GLctx["bindVertexArray"](GL.vaos[vao])}Module["_glBindVertexArrayOES"]=_glBindVertexArrayOES;_glBindVertexArrayOES.sig="vi";function _glIsVertexArrayOES(array){var vao=GL.vaos[array];if(!vao)return 0;return GLctx["isVertexArray"](vao)}Module["_glIsVertexArrayOES"]=_glIsVertexArrayOES;_glIsVertexArrayOES.sig="ii";function _glVertexAttribPointer(index,size,type,normalized,stride,ptr){GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}Module["_glVertexAttribPointer"]=_glVertexAttribPointer;_glVertexAttribPointer.sig="viiiiii";function _glEnableVertexAttribArray(index){GLctx.enableVertexAttribArray(index)}Module["_glEnableVertexAttribArray"]=_glEnableVertexAttribArray;_glEnableVertexAttribArray.sig="vi";function _glDisableVertexAttribArray(index){GLctx.disableVertexAttribArray(index)}Module["_glDisableVertexAttribArray"]=_glDisableVertexAttribArray;_glDisableVertexAttribArray.sig="vi";function _glDrawArrays(mode,first,count){GLctx.drawArrays(mode,first,count)}Module["_glDrawArrays"]=_glDrawArrays;_glDrawArrays.sig="viii";function _glDrawElements(mode,count,type,indices){GLctx.drawElements(mode,count,type,indices)}Module["_glDrawElements"]=_glDrawElements;_glDrawElements.sig="viiii";function _glShaderBinary(){GL.recordError(1280)}Module["_glShaderBinary"]=_glShaderBinary;_glShaderBinary.sig="v";function _glReleaseShaderCompiler(){}Module["_glReleaseShaderCompiler"]=_glReleaseShaderCompiler;_glReleaseShaderCompiler.sig="v";function _glGetError(){var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error}Module["_glGetError"]=_glGetError;_glGetError.sig="i";function _glVertexAttribDivisor(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_glVertexAttribDivisor"]=_glVertexAttribDivisor;_glVertexAttribDivisor.sig="vii";function _glDrawArraysInstanced(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_glDrawArraysInstanced"]=_glDrawArraysInstanced;_glDrawArraysInstanced.sig="viiii";function _glDrawElementsInstanced(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_glDrawElementsInstanced"]=_glDrawElementsInstanced;_glDrawElementsInstanced.sig="viiiii";function _glVertexAttribDivisorNV(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_glVertexAttribDivisorNV"]=_glVertexAttribDivisorNV;_glVertexAttribDivisorNV.sig="vii";function _glDrawArraysInstancedNV(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_glDrawArraysInstancedNV"]=_glDrawArraysInstancedNV;_glDrawArraysInstancedNV.sig="viiii";function _glDrawElementsInstancedNV(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_glDrawElementsInstancedNV"]=_glDrawElementsInstancedNV;_glDrawElementsInstancedNV.sig="viiiii";function _glVertexAttribDivisorEXT(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_glVertexAttribDivisorEXT"]=_glVertexAttribDivisorEXT;_glVertexAttribDivisorEXT.sig="vii";function _glDrawArraysInstancedEXT(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_glDrawArraysInstancedEXT"]=_glDrawArraysInstancedEXT;_glDrawArraysInstancedEXT.sig="viiii";function _glDrawElementsInstancedEXT(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_glDrawElementsInstancedEXT"]=_glDrawElementsInstancedEXT;_glDrawElementsInstancedEXT.sig="viiiii";function _glVertexAttribDivisorARB(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_glVertexAttribDivisorARB"]=_glVertexAttribDivisorARB;_glVertexAttribDivisorARB.sig="vii";function _glDrawArraysInstancedARB(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_glDrawArraysInstancedARB"]=_glDrawArraysInstancedARB;_glDrawArraysInstancedARB.sig="viiii";function _glDrawElementsInstancedARB(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_glDrawElementsInstancedARB"]=_glDrawElementsInstancedARB;_glDrawElementsInstancedARB.sig="viiiii";function _glVertexAttribDivisorANGLE(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_glVertexAttribDivisorANGLE"]=_glVertexAttribDivisorANGLE;_glVertexAttribDivisorANGLE.sig="vii";function _glDrawArraysInstancedANGLE(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_glDrawArraysInstancedANGLE"]=_glDrawArraysInstancedANGLE;_glDrawArraysInstancedANGLE.sig="viiii";function _glDrawElementsInstancedANGLE(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_glDrawElementsInstancedANGLE"]=_glDrawElementsInstancedANGLE;_glDrawElementsInstancedANGLE.sig="viiiii";function _glDrawBuffers(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}Module["_glDrawBuffers"]=_glDrawBuffers;_glDrawBuffers.sig="vii";function _glDrawBuffersEXT(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}Module["_glDrawBuffersEXT"]=_glDrawBuffersEXT;_glDrawBuffersEXT.sig="vii";function _glDrawBuffersWEBGL(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}Module["_glDrawBuffersWEBGL"]=_glDrawBuffersWEBGL;_glDrawBuffersWEBGL.sig="vii";function _glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}Module["_glColorMask"]=_glColorMask;_glColorMask.sig="viiii";function _glDepthMask(flag){GLctx.depthMask(!!flag)}Module["_glDepthMask"]=_glDepthMask;_glDepthMask.sig="vi";function _glSampleCoverage(value,invert){GLctx.sampleCoverage(value,!!invert)}Module["_glSampleCoverage"]=_glSampleCoverage;_glSampleCoverage.sig="vii";function _glMultiDrawArrays(mode,firsts,counts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,drawcount)}Module["_glMultiDrawArrays"]=_glMultiDrawArrays;_glMultiDrawArrays.sig="viiii";function _glMultiDrawArraysANGLE(mode,firsts,counts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,drawcount)}Module["_glMultiDrawArraysANGLE"]=_glMultiDrawArraysANGLE;_glMultiDrawArraysANGLE.sig="viiii";function _glMultiDrawArraysWEBGL(mode,firsts,counts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,drawcount)}Module["_glMultiDrawArraysWEBGL"]=_glMultiDrawArraysWEBGL;_glMultiDrawArraysWEBGL.sig="viiii";function _glMultiDrawArraysInstancedANGLE(mode,firsts,counts,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysInstancedWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_glMultiDrawArraysInstancedANGLE"]=_glMultiDrawArraysInstancedANGLE;_glMultiDrawArraysInstancedANGLE.sig="viiiii";function _glMultiDrawArraysInstancedWEBGL(mode,firsts,counts,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysInstancedWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_glMultiDrawArraysInstancedWEBGL"]=_glMultiDrawArraysInstancedWEBGL;_glMultiDrawArraysInstancedWEBGL.sig="viiiii";function _glMultiDrawElements(mode,counts,type,offsets,drawcount){GLctx.multiDrawWebgl["multiDrawElementsWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,drawcount)}Module["_glMultiDrawElements"]=_glMultiDrawElements;_glMultiDrawElements.sig="viiiii";function _glMultiDrawElementsANGLE(mode,counts,type,offsets,drawcount){GLctx.multiDrawWebgl["multiDrawElementsWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,drawcount)}Module["_glMultiDrawElementsANGLE"]=_glMultiDrawElementsANGLE;_glMultiDrawElementsANGLE.sig="viiiii";function _glMultiDrawElementsWEBGL(mode,counts,type,offsets,drawcount){GLctx.multiDrawWebgl["multiDrawElementsWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,drawcount)}Module["_glMultiDrawElementsWEBGL"]=_glMultiDrawElementsWEBGL;_glMultiDrawElementsWEBGL.sig="viiiii";function _glMultiDrawElementsInstancedANGLE(mode,counts,type,offsets,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawElementsInstancedWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_glMultiDrawElementsInstancedANGLE"]=_glMultiDrawElementsInstancedANGLE;_glMultiDrawElementsInstancedANGLE.sig="viiiiii";function _glMultiDrawElementsInstancedWEBGL(mode,counts,type,offsets,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawElementsInstancedWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_glMultiDrawElementsInstancedWEBGL"]=_glMultiDrawElementsInstancedWEBGL;_glMultiDrawElementsInstancedWEBGL.sig="viiiiii";function _glFinish(){GLctx["finish"]()}Module["_glFinish"]=_glFinish;_glFinish.sig="v";function _glFlush(){GLctx["flush"]()}Module["_glFlush"]=_glFlush;_glFlush.sig="v";function _glClearDepth(x0){GLctx["clearDepth"](x0)}Module["_glClearDepth"]=_glClearDepth;_glClearDepth.sig="vi";function _glClearDepthf(x0){GLctx["clearDepth"](x0)}Module["_glClearDepthf"]=_glClearDepthf;_glClearDepthf.sig="vf";function _glDepthFunc(x0){GLctx["depthFunc"](x0)}Module["_glDepthFunc"]=_glDepthFunc;_glDepthFunc.sig="vi";function _glEnable(x0){GLctx["enable"](x0)}Module["_glEnable"]=_glEnable;_glEnable.sig="vi";function _glDisable(x0){GLctx["disable"](x0)}Module["_glDisable"]=_glDisable;_glDisable.sig="vi";function _glFrontFace(x0){GLctx["frontFace"](x0)}Module["_glFrontFace"]=_glFrontFace;_glFrontFace.sig="vi";function _glCullFace(x0){GLctx["cullFace"](x0)}Module["_glCullFace"]=_glCullFace;_glCullFace.sig="vi";function _glClear(x0){GLctx["clear"](x0)}Module["_glClear"]=_glClear;_glClear.sig="vi";function _glLineWidth(x0){GLctx["lineWidth"](x0)}Module["_glLineWidth"]=_glLineWidth;_glLineWidth.sig="vf";function _glClearStencil(x0){GLctx["clearStencil"](x0)}Module["_glClearStencil"]=_glClearStencil;_glClearStencil.sig="vi";function _glStencilMask(x0){GLctx["stencilMask"](x0)}Module["_glStencilMask"]=_glStencilMask;_glStencilMask.sig="vi";function _glCheckFramebufferStatus(x0){return GLctx["checkFramebufferStatus"](x0)}Module["_glCheckFramebufferStatus"]=_glCheckFramebufferStatus;_glCheckFramebufferStatus.sig="ii";function _glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}Module["_glGenerateMipmap"]=_glGenerateMipmap;_glGenerateMipmap.sig="vi";function _glActiveTexture(x0){GLctx["activeTexture"](x0)}Module["_glActiveTexture"]=_glActiveTexture;_glActiveTexture.sig="vi";function _glBlendEquation(x0){GLctx["blendEquation"](x0)}Module["_glBlendEquation"]=_glBlendEquation;_glBlendEquation.sig="vi";function _glIsEnabled(x0){return GLctx["isEnabled"](x0)}Module["_glIsEnabled"]=_glIsEnabled;_glIsEnabled.sig="ii";function _glBlendFunc(x0,x1){GLctx["blendFunc"](x0,x1)}Module["_glBlendFunc"]=_glBlendFunc;_glBlendFunc.sig="vii";function _glBlendEquationSeparate(x0,x1){GLctx["blendEquationSeparate"](x0,x1)}Module["_glBlendEquationSeparate"]=_glBlendEquationSeparate;_glBlendEquationSeparate.sig="vii";function _glDepthRange(x0,x1){GLctx["depthRange"](x0,x1)}Module["_glDepthRange"]=_glDepthRange;_glDepthRange.sig="vii";function _glDepthRangef(x0,x1){GLctx["depthRange"](x0,x1)}Module["_glDepthRangef"]=_glDepthRangef;_glDepthRangef.sig="vii";function _glStencilMaskSeparate(x0,x1){GLctx["stencilMaskSeparate"](x0,x1)}Module["_glStencilMaskSeparate"]=_glStencilMaskSeparate;_glStencilMaskSeparate.sig="vii";function _glHint(x0,x1){GLctx["hint"](x0,x1)}Module["_glHint"]=_glHint;_glHint.sig="vii";function _glPolygonOffset(x0,x1){GLctx["polygonOffset"](x0,x1)}Module["_glPolygonOffset"]=_glPolygonOffset;_glPolygonOffset.sig="vff";function _glVertexAttrib1f(x0,x1){GLctx["vertexAttrib1f"](x0,x1)}Module["_glVertexAttrib1f"]=_glVertexAttrib1f;_glVertexAttrib1f.sig="vif";function _glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}Module["_glTexParameteri"]=_glTexParameteri;_glTexParameteri.sig="viii";function _glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}Module["_glTexParameterf"]=_glTexParameterf;_glTexParameterf.sig="viii";function _glVertexAttrib2f(x0,x1,x2){GLctx["vertexAttrib2f"](x0,x1,x2)}Module["_glVertexAttrib2f"]=_glVertexAttrib2f;_glVertexAttrib2f.sig="viff";function _glStencilFunc(x0,x1,x2){GLctx["stencilFunc"](x0,x1,x2)}Module["_glStencilFunc"]=_glStencilFunc;_glStencilFunc.sig="viii";function _glStencilOp(x0,x1,x2){GLctx["stencilOp"](x0,x1,x2)}Module["_glStencilOp"]=_glStencilOp;_glStencilOp.sig="viii";function _glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}Module["_glViewport"]=_glViewport;_glViewport.sig="viiii";function _glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}Module["_glClearColor"]=_glClearColor;_glClearColor.sig="vffff";function _glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}Module["_glScissor"]=_glScissor;_glScissor.sig="viiii";function _glVertexAttrib3f(x0,x1,x2,x3){GLctx["vertexAttrib3f"](x0,x1,x2,x3)}Module["_glVertexAttrib3f"]=_glVertexAttrib3f;_glVertexAttrib3f.sig="vifff";function _glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}Module["_glRenderbufferStorage"]=_glRenderbufferStorage;_glRenderbufferStorage.sig="viiii";function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}Module["_glBlendFuncSeparate"]=_glBlendFuncSeparate;_glBlendFuncSeparate.sig="viiii";function _glBlendColor(x0,x1,x2,x3){GLctx["blendColor"](x0,x1,x2,x3)}Module["_glBlendColor"]=_glBlendColor;_glBlendColor.sig="vffff";function _glStencilFuncSeparate(x0,x1,x2,x3){GLctx["stencilFuncSeparate"](x0,x1,x2,x3)}Module["_glStencilFuncSeparate"]=_glStencilFuncSeparate;_glStencilFuncSeparate.sig="viiii";function _glStencilOpSeparate(x0,x1,x2,x3){GLctx["stencilOpSeparate"](x0,x1,x2,x3)}Module["_glStencilOpSeparate"]=_glStencilOpSeparate;_glStencilOpSeparate.sig="viiii";function _glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}Module["_glVertexAttrib4f"]=_glVertexAttrib4f;_glVertexAttrib4f.sig="viffff";function _glCopyTexImage2D(x0,x1,x2,x3,x4,x5,x6,x7){GLctx["copyTexImage2D"](x0,x1,x2,x3,x4,x5,x6,x7)}Module["_glCopyTexImage2D"]=_glCopyTexImage2D;_glCopyTexImage2D.sig="viiiiiiii";function _glCopyTexSubImage2D(x0,x1,x2,x3,x4,x5,x6,x7){GLctx["copyTexSubImage2D"](x0,x1,x2,x3,x4,x5,x6,x7)}Module["_glCopyTexSubImage2D"]=_glCopyTexSubImage2D;_glCopyTexSubImage2D.sig="viiiiiiii";function _emscripten_glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}Module["_emscripten_glGenVertexArrays"]=_emscripten_glGenVertexArrays;_emscripten_glGenVertexArrays.sig="vii";function _emscripten_glDeleteVertexArrays(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}Module["_emscripten_glDeleteVertexArrays"]=_emscripten_glDeleteVertexArrays;_emscripten_glDeleteVertexArrays.sig="vii";function _emscripten_glBindVertexArray(vao){GLctx["bindVertexArray"](GL.vaos[vao])}Module["_emscripten_glBindVertexArray"]=_emscripten_glBindVertexArray;_emscripten_glBindVertexArray.sig="vi";function _emscripten_glIsVertexArray(array){var vao=GL.vaos[array];if(!vao)return 0;return GLctx["isVertexArray"](vao)}Module["_emscripten_glIsVertexArray"]=_emscripten_glIsVertexArray;_emscripten_glIsVertexArray.sig="ii";function _emscripten_glVertexPointer(){throw"Legacy GL function (glVertexPointer) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_emscripten_glVertexPointer"]=_emscripten_glVertexPointer;function _emscripten_glMatrixMode(){throw"Legacy GL function (glMatrixMode) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_emscripten_glMatrixMode"]=_emscripten_glMatrixMode;function _emscripten_glBegin(){throw"Legacy GL function (glBegin) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_emscripten_glBegin"]=_emscripten_glBegin;function _emscripten_glLoadIdentity(){throw"Legacy GL function (glLoadIdentity) called. If you want legacy GL emulation, you need to compile with -sLEGACY_GL_EMULATION to enable legacy GL emulation."}Module["_emscripten_glLoadIdentity"]=_emscripten_glLoadIdentity;function _emscripten_glVertexAttribDivisor(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_emscripten_glVertexAttribDivisor"]=_emscripten_glVertexAttribDivisor;_emscripten_glVertexAttribDivisor.sig="vii";function _emscripten_glDrawArraysInstanced(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_emscripten_glDrawArraysInstanced"]=_emscripten_glDrawArraysInstanced;_emscripten_glDrawArraysInstanced.sig="viiii";function _emscripten_glDrawElementsInstanced(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_emscripten_glDrawElementsInstanced"]=_emscripten_glDrawElementsInstanced;_emscripten_glDrawElementsInstanced.sig="viiiii";function _emscripten_glVertexAttribDivisorNV(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_emscripten_glVertexAttribDivisorNV"]=_emscripten_glVertexAttribDivisorNV;_emscripten_glVertexAttribDivisorNV.sig="vii";function _emscripten_glDrawArraysInstancedNV(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_emscripten_glDrawArraysInstancedNV"]=_emscripten_glDrawArraysInstancedNV;_emscripten_glDrawArraysInstancedNV.sig="viiii";function _emscripten_glDrawElementsInstancedNV(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_emscripten_glDrawElementsInstancedNV"]=_emscripten_glDrawElementsInstancedNV;_emscripten_glDrawElementsInstancedNV.sig="viiiii";function _emscripten_glVertexAttribDivisorEXT(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_emscripten_glVertexAttribDivisorEXT"]=_emscripten_glVertexAttribDivisorEXT;_emscripten_glVertexAttribDivisorEXT.sig="vii";function _emscripten_glDrawArraysInstancedEXT(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_emscripten_glDrawArraysInstancedEXT"]=_emscripten_glDrawArraysInstancedEXT;_emscripten_glDrawArraysInstancedEXT.sig="viiii";function _emscripten_glDrawElementsInstancedEXT(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_emscripten_glDrawElementsInstancedEXT"]=_emscripten_glDrawElementsInstancedEXT;_emscripten_glDrawElementsInstancedEXT.sig="viiiii";function _emscripten_glVertexAttribDivisorARB(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}Module["_emscripten_glVertexAttribDivisorARB"]=_emscripten_glVertexAttribDivisorARB;_emscripten_glVertexAttribDivisorARB.sig="vii";function _emscripten_glDrawArraysInstancedARB(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}Module["_emscripten_glDrawArraysInstancedARB"]=_emscripten_glDrawArraysInstancedARB;_emscripten_glDrawArraysInstancedARB.sig="viiii";function _emscripten_glDrawElementsInstancedARB(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}Module["_emscripten_glDrawElementsInstancedARB"]=_emscripten_glDrawElementsInstancedARB;_emscripten_glDrawElementsInstancedARB.sig="viiiii";function _emscripten_glDrawBuffers(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}Module["_emscripten_glDrawBuffers"]=_emscripten_glDrawBuffers;_emscripten_glDrawBuffers.sig="vii";function _emscripten_glDrawBuffersEXT(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}Module["_emscripten_glDrawBuffersEXT"]=_emscripten_glDrawBuffersEXT;_emscripten_glDrawBuffersEXT.sig="vii";function _emscripten_glMultiDrawArrays(mode,firsts,counts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,drawcount)}Module["_emscripten_glMultiDrawArrays"]=_emscripten_glMultiDrawArrays;_emscripten_glMultiDrawArrays.sig="viiii";function _emscripten_glMultiDrawArraysANGLE(mode,firsts,counts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,drawcount)}Module["_emscripten_glMultiDrawArraysANGLE"]=_emscripten_glMultiDrawArraysANGLE;_emscripten_glMultiDrawArraysANGLE.sig="viiii";function _emscripten_glMultiDrawArraysWEBGL(mode,firsts,counts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,drawcount)}Module["_emscripten_glMultiDrawArraysWEBGL"]=_emscripten_glMultiDrawArraysWEBGL;_emscripten_glMultiDrawArraysWEBGL.sig="viiii";function _emscripten_glMultiDrawArraysInstancedANGLE(mode,firsts,counts,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysInstancedWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_emscripten_glMultiDrawArraysInstancedANGLE"]=_emscripten_glMultiDrawArraysInstancedANGLE;_emscripten_glMultiDrawArraysInstancedANGLE.sig="viiiii";function _emscripten_glMultiDrawArraysInstancedWEBGL(mode,firsts,counts,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawArraysInstancedWEBGL"](mode,HEAP32,firsts>>2,HEAP32,counts>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_emscripten_glMultiDrawArraysInstancedWEBGL"]=_emscripten_glMultiDrawArraysInstancedWEBGL;_emscripten_glMultiDrawArraysInstancedWEBGL.sig="viiiii";function _emscripten_glMultiDrawElements(mode,counts,type,offsets,drawcount){GLctx.multiDrawWebgl["multiDrawElementsWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,drawcount)}Module["_emscripten_glMultiDrawElements"]=_emscripten_glMultiDrawElements;_emscripten_glMultiDrawElements.sig="viiiii";function _emscripten_glMultiDrawElementsANGLE(mode,counts,type,offsets,drawcount){GLctx.multiDrawWebgl["multiDrawElementsWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,drawcount)}Module["_emscripten_glMultiDrawElementsANGLE"]=_emscripten_glMultiDrawElementsANGLE;_emscripten_glMultiDrawElementsANGLE.sig="viiiii";function _emscripten_glMultiDrawElementsWEBGL(mode,counts,type,offsets,drawcount){GLctx.multiDrawWebgl["multiDrawElementsWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,drawcount)}Module["_emscripten_glMultiDrawElementsWEBGL"]=_emscripten_glMultiDrawElementsWEBGL;_emscripten_glMultiDrawElementsWEBGL.sig="viiiii";function _emscripten_glMultiDrawElementsInstancedANGLE(mode,counts,type,offsets,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawElementsInstancedWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_emscripten_glMultiDrawElementsInstancedANGLE"]=_emscripten_glMultiDrawElementsInstancedANGLE;_emscripten_glMultiDrawElementsInstancedANGLE.sig="viiiiii";function _emscripten_glMultiDrawElementsInstancedWEBGL(mode,counts,type,offsets,instanceCounts,drawcount){GLctx.multiDrawWebgl["multiDrawElementsInstancedWEBGL"](mode,HEAP32,counts>>2,type,HEAP32,offsets>>2,HEAP32,instanceCounts>>2,drawcount)}Module["_emscripten_glMultiDrawElementsInstancedWEBGL"]=_emscripten_glMultiDrawElementsInstancedWEBGL;_emscripten_glMultiDrawElementsInstancedWEBGL.sig="viiiiii";function _emscripten_glClearDepth(x0){GLctx["clearDepth"](x0)}Module["_emscripten_glClearDepth"]=_emscripten_glClearDepth;_emscripten_glClearDepth.sig="vi";function _emscripten_glDepthRange(x0,x1){GLctx["depthRange"](x0,x1)}Module["_emscripten_glDepthRange"]=_emscripten_glDepthRange;_emscripten_glDepthRange.sig="vii";function writeGLArray(arr,dst,dstLength,heapType){var len=arr.length;var writeLength=dstLength>2)+i]=arr[i]}return len}Module["writeGLArray"]=writeGLArray;function _emscripten_webgl_init_context_attributes(attributes){var a=attributes>>2;for(var i=0;i<56>>2;++i){HEAP32[a+i]=0}HEAP32[a+(0>>2)]=HEAP32[a+(4>>2)]=HEAP32[a+(12>>2)]=HEAP32[a+(16>>2)]=HEAP32[a+(32>>2)]=HEAP32[a+(40>>2)]=1}Module["_emscripten_webgl_init_context_attributes"]=_emscripten_webgl_init_context_attributes;var __emscripten_webgl_power_preferences=["default","low-power","high-performance"];Module["__emscripten_webgl_power_preferences"]=__emscripten_webgl_power_preferences;function _emscripten_webgl_do_create_context(target,attributes){var a=attributes>>2;var powerPreference=HEAP32[a+(24>>2)];var contextAttributes={"alpha":!!HEAP32[a+(0>>2)],"depth":!!HEAP32[a+(4>>2)],"stencil":!!HEAP32[a+(8>>2)],"antialias":!!HEAP32[a+(12>>2)],"premultipliedAlpha":!!HEAP32[a+(16>>2)],"preserveDrawingBuffer":!!HEAP32[a+(20>>2)],"powerPreference":__emscripten_webgl_power_preferences[powerPreference],"failIfMajorPerformanceCaveat":!!HEAP32[a+(28>>2)],majorVersion:HEAP32[a+(32>>2)],minorVersion:HEAP32[a+(36>>2)],enableExtensionsByDefault:HEAP32[a+(40>>2)],explicitSwapControl:HEAP32[a+(44>>2)],proxyContextToMainThread:HEAP32[a+(48>>2)],renderViaOffscreenBackBuffer:HEAP32[a+(52>>2)]};var canvas=findCanvasEventTarget(target);if(!canvas){return 0}if(contextAttributes.explicitSwapControl){return 0}var contextHandle=GL.createContext(canvas,contextAttributes);return contextHandle}Module["_emscripten_webgl_do_create_context"]=_emscripten_webgl_do_create_context;_emscripten_webgl_do_create_context.sig="iii";function _emscripten_webgl_create_context(a0,a1){return _emscripten_webgl_do_create_context(a0,a1)}Module["_emscripten_webgl_create_context"]=_emscripten_webgl_create_context;_emscripten_webgl_create_context.sig="iii";function _emscripten_webgl_do_get_current_context(){return GL.currentContext?GL.currentContext.handle:0}Module["_emscripten_webgl_do_get_current_context"]=_emscripten_webgl_do_get_current_context;_emscripten_webgl_do_get_current_context.sig="i";function _emscripten_webgl_get_current_context(){return _emscripten_webgl_do_get_current_context()}Module["_emscripten_webgl_get_current_context"]=_emscripten_webgl_get_current_context;_emscripten_webgl_get_current_context.sig="i";function _emscripten_webgl_do_commit_frame(){if(!GL.currentContext||!GL.currentContext.GLctx){return-3}if(!GL.currentContext.attributes.explicitSwapControl){return-3}return 0}Module["_emscripten_webgl_do_commit_frame"]=_emscripten_webgl_do_commit_frame;_emscripten_webgl_do_commit_frame.sig="i";function _emscripten_webgl_commit_frame(){return _emscripten_webgl_do_commit_frame()}Module["_emscripten_webgl_commit_frame"]=_emscripten_webgl_commit_frame;_emscripten_webgl_commit_frame.sig="i";function _emscripten_webgl_make_context_current(contextHandle){var success=GL.makeContextCurrent(contextHandle);return success?0:-5}Module["_emscripten_webgl_make_context_current"]=_emscripten_webgl_make_context_current;_emscripten_webgl_make_context_current.sig="ii";function _emscripten_webgl_get_drawing_buffer_size(contextHandle,width,height){var GLContext=GL.getContext(contextHandle);if(!GLContext||!GLContext.GLctx||!width||!height){return-5}HEAP32[width>>2]=GLContext.GLctx.drawingBufferWidth;HEAP32[height>>2]=GLContext.GLctx.drawingBufferHeight;return 0}Module["_emscripten_webgl_get_drawing_buffer_size"]=_emscripten_webgl_get_drawing_buffer_size;_emscripten_webgl_get_drawing_buffer_size.sig="iiii";function _emscripten_webgl_get_context_attributes(c,a){if(!a)return-5;c=GL.contexts[c];if(!c)return-3;var t=c.GLctx;if(!t)return-3;t=t.getContextAttributes();HEAP32[a>>2]=t.alpha;HEAP32[a+4>>2]=t.depth;HEAP32[a+8>>2]=t.stencil;HEAP32[a+12>>2]=t.antialias;HEAP32[a+16>>2]=t.premultipliedAlpha;HEAP32[a+20>>2]=t.preserveDrawingBuffer;var power=t["powerPreference"]&&__emscripten_webgl_power_preferences.indexOf(t["powerPreference"]);HEAP32[a+24>>2]=power;HEAP32[a+28>>2]=t.failIfMajorPerformanceCaveat;HEAP32[a+32>>2]=c.version;HEAP32[a+36>>2]=0;HEAP32[a+40>>2]=c.attributes.enableExtensionsByDefault;return 0}Module["_emscripten_webgl_get_context_attributes"]=_emscripten_webgl_get_context_attributes;_emscripten_webgl_get_context_attributes.sig="iii";function _emscripten_webgl_destroy_context(contextHandle){if(GL.currentContext==contextHandle)GL.currentContext=0;GL.deleteContext(contextHandle)}Module["_emscripten_webgl_destroy_context"]=_emscripten_webgl_destroy_context;_emscripten_webgl_destroy_context.sig="vi";function _emscripten_webgl_enable_extension(contextHandle,extension){var context=GL.getContext(contextHandle);var extString=UTF8ToString(extension);if(extString.startsWith("GL_"))extString=extString.substr(3);if(extString=="ANGLE_instanced_arrays")__webgl_enable_ANGLE_instanced_arrays(GLctx);if(extString=="OES_vertex_array_object")__webgl_enable_OES_vertex_array_object(GLctx);if(extString=="WEBGL_draw_buffers")__webgl_enable_WEBGL_draw_buffers(GLctx);if(extString=="WEBGL_multi_draw")__webgl_enable_WEBGL_multi_draw(GLctx);var ext=context.GLctx.getExtension(extString);return!!ext}Module["_emscripten_webgl_enable_extension"]=_emscripten_webgl_enable_extension;_emscripten_webgl_enable_extension.sig="iii";function _emscripten_supports_offscreencanvas(){return 0}Module["_emscripten_supports_offscreencanvas"]=_emscripten_supports_offscreencanvas;function __registerWebGlEventCallback(target,userData,useCapture,callbackfunc,eventTypeId,eventTypeString,targetThread){var webGlEventHandlerFunc=function(ev){var e=ev||event;if(getWasmTableEntry(callbackfunc)(eventTypeId,0,userData))e.preventDefault()};var eventHandler={target:findEventTarget(target),eventTypeString:eventTypeString,callbackfunc:callbackfunc,handlerFunc:webGlEventHandlerFunc,useCapture:useCapture};JSEvents.registerOrRemoveHandler(eventHandler)}Module["__registerWebGlEventCallback"]=__registerWebGlEventCallback;function _emscripten_set_webglcontextlost_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){__registerWebGlEventCallback(target,userData,useCapture,callbackfunc,31,"webglcontextlost",targetThread);return 0}Module["_emscripten_set_webglcontextlost_callback_on_thread"]=_emscripten_set_webglcontextlost_callback_on_thread;_emscripten_set_webglcontextlost_callback_on_thread.sig="iiiiii";function _emscripten_set_webglcontextrestored_callback_on_thread(target,userData,useCapture,callbackfunc,targetThread){__registerWebGlEventCallback(target,userData,useCapture,callbackfunc,32,"webglcontextrestored",targetThread);return 0}Module["_emscripten_set_webglcontextrestored_callback_on_thread"]=_emscripten_set_webglcontextrestored_callback_on_thread;_emscripten_set_webglcontextrestored_callback_on_thread.sig="iiiiii";function _emscripten_is_webgl_context_lost(contextHandle){return!GL.contexts[contextHandle]||GL.contexts[contextHandle].GLctx.isContextLost()}Module["_emscripten_is_webgl_context_lost"]=_emscripten_is_webgl_context_lost;_emscripten_is_webgl_context_lost.sig="ii";function _emscripten_webgl_get_supported_extensions(){return stringToNewUTF8(GLctx.getSupportedExtensions().join(" "))}Module["_emscripten_webgl_get_supported_extensions"]=_emscripten_webgl_get_supported_extensions;_emscripten_webgl_get_supported_extensions.sig="i";function _emscripten_webgl_get_program_parameter_d(program,param){return GLctx.getProgramParameter(GL.programs[program],param)}Module["_emscripten_webgl_get_program_parameter_d"]=_emscripten_webgl_get_program_parameter_d;_emscripten_webgl_get_program_parameter_d.sig="fii";function _emscripten_webgl_get_program_info_log_utf8(program){return stringToNewUTF8(GLctx.getProgramInfoLog(GL.programs[program]))}Module["_emscripten_webgl_get_program_info_log_utf8"]=_emscripten_webgl_get_program_info_log_utf8;_emscripten_webgl_get_program_info_log_utf8.sig="ii";function _emscripten_webgl_get_shader_parameter_d(shader,param){return GLctx.getShaderParameter(GL.shaders[shader],param)}Module["_emscripten_webgl_get_shader_parameter_d"]=_emscripten_webgl_get_shader_parameter_d;_emscripten_webgl_get_shader_parameter_d.sig="fii";function _emscripten_webgl_get_shader_info_log_utf8(shader){return stringToNewUTF8(GLctx.getShaderInfoLog(GL.shaders[shader]))}Module["_emscripten_webgl_get_shader_info_log_utf8"]=_emscripten_webgl_get_shader_info_log_utf8;_emscripten_webgl_get_shader_info_log_utf8.sig="ii";function _emscripten_webgl_get_shader_source_utf8(shader){return stringToNewUTF8(GLctx.getShaderSource(GL.shaders[shader]))}Module["_emscripten_webgl_get_shader_source_utf8"]=_emscripten_webgl_get_shader_source_utf8;_emscripten_webgl_get_shader_source_utf8.sig="ii";function _emscripten_webgl_get_vertex_attrib_d(index,param){return GLctx.getVertexAttrib(index,param)}Module["_emscripten_webgl_get_vertex_attrib_d"]=_emscripten_webgl_get_vertex_attrib_d;_emscripten_webgl_get_vertex_attrib_d.sig="iii";function _emscripten_webgl_get_vertex_attrib_o(index,param){var obj=GLctx.getVertexAttrib(index,param);return obj&&obj.name}Module["_emscripten_webgl_get_vertex_attrib_o"]=_emscripten_webgl_get_vertex_attrib_o;_emscripten_webgl_get_vertex_attrib_o.sig="iii";function _emscripten_webgl_get_vertex_attrib_v(index,param,dst,dstLength,dstType){return writeGLArray(GLctx.getVertexAttrib(index,param),dst,dstLength,dstType)}Module["_emscripten_webgl_get_vertex_attrib_v"]=_emscripten_webgl_get_vertex_attrib_v;_emscripten_webgl_get_vertex_attrib_v.sig="iiiiii";function _emscripten_webgl_get_uniform_d(program,location){return GLctx.getUniform(GL.programs[program],webglGetUniformLocation(location))}Module["_emscripten_webgl_get_uniform_d"]=_emscripten_webgl_get_uniform_d;_emscripten_webgl_get_uniform_d.sig="fii";function _emscripten_webgl_get_uniform_v(program,location,dst,dstLength,dstType){return writeGLArray(GLctx.getUniform(GL.programs[program],webglGetUniformLocation(location)),dst,dstLength,dstType)}Module["_emscripten_webgl_get_uniform_v"]=_emscripten_webgl_get_uniform_v;_emscripten_webgl_get_uniform_v.sig="iiiiii";function _emscripten_webgl_get_parameter_v(param,dst,dstLength,dstType){return writeGLArray(GLctx.getParameter(param),dst,dstLength,dstType)}Module["_emscripten_webgl_get_parameter_v"]=_emscripten_webgl_get_parameter_v;_emscripten_webgl_get_parameter_v.sig="iiiii";function _emscripten_webgl_get_parameter_d(param){return GLctx.getParameter(param)}Module["_emscripten_webgl_get_parameter_d"]=_emscripten_webgl_get_parameter_d;_emscripten_webgl_get_parameter_d.sig="fi";function _emscripten_webgl_get_parameter_o(param){var obj=GLctx.getParameter(param);return obj&&obj.name}Module["_emscripten_webgl_get_parameter_o"]=_emscripten_webgl_get_parameter_o;_emscripten_webgl_get_parameter_o.sig="ii";function _emscripten_webgl_get_parameter_utf8(param){return stringToNewUTF8(GLctx.getParameter(param))}Module["_emscripten_webgl_get_parameter_utf8"]=_emscripten_webgl_get_parameter_utf8;_emscripten_webgl_get_parameter_utf8.sig="ii";function _emscripten_webgl_get_parameter_i64v(param,dst){writeI53ToI64(dst,GLctx.getParameter(param))}Module["_emscripten_webgl_get_parameter_i64v"]=_emscripten_webgl_get_parameter_i64v;_emscripten_webgl_get_parameter_i64v.sig="vii";function _SDL_GetTicks(){return Date.now()-SDL.startTime|0}Module["_SDL_GetTicks"]=_SDL_GetTicks;_SDL_GetTicks.sig="i";function _SDL_LockSurface(surf){var surfData=SDL.surfaces[surf];surfData.locked++;if(surfData.locked>1)return 0;if(!surfData.buffer){surfData.buffer=_malloc(surfData.width*surfData.height*4);HEAPU32[surf+20>>2]=surfData.buffer}HEAPU32[surf+20>>2]=surfData.buffer;if(surf==SDL.screen&&Module.screenIsReadOnly&&surfData.image)return 0;if(SDL.defaults.discardOnLock){if(!surfData.image){surfData.image=surfData.ctx.createImageData(surfData.width,surfData.height)}if(!SDL.defaults.opaqueFrontBuffer)return}else{surfData.image=surfData.ctx.getImageData(0,0,surfData.width,surfData.height)}if(surf==SDL.screen&&SDL.defaults.opaqueFrontBuffer){var data=surfData.image.data;var num=data.length;for(var i=0;i>2],y:HEAP32[rect+4>>2],w:HEAP32[rect+8>>2],h:HEAP32[rect+12>>2]}},updateRect:function(rect,r){HEAP32[rect>>2]=r.x;HEAP32[rect+4>>2]=r.y;HEAP32[rect+8>>2]=r.w;HEAP32[rect+12>>2]=r.h},intersectionOfRects:function(first,second){var leftX=Math.max(first.x,second.x);var leftY=Math.max(first.y,second.y);var rightX=Math.min(first.x+first.w,second.x+second.w);var rightY=Math.min(first.y+first.h,second.y+second.h);return{x:leftX,y:leftY,w:Math.max(leftX,rightX)-leftX,h:Math.max(leftY,rightY)-leftY}},checkPixelFormat:function(fmt){},loadColorToCSSRGB:function(color){var rgba=HEAP32[color>>2];return"rgb("+(rgba&255)+","+(rgba>>8&255)+","+(rgba>>16&255)+")"},loadColorToCSSRGBA:function(color){var rgba=HEAP32[color>>2];return"rgba("+(rgba&255)+","+(rgba>>8&255)+","+(rgba>>16&255)+","+(rgba>>24&255)/255+")"},translateColorToCSSRGBA:function(rgba){return"rgba("+(rgba&255)+","+(rgba>>8&255)+","+(rgba>>16&255)+","+(rgba>>>24)/255+")"},translateRGBAToCSSRGBA:function(r,g,b,a){return"rgba("+(r&255)+","+(g&255)+","+(b&255)+","+(a&255)/255+")"},translateRGBAToColor:function(r,g,b,a){return r|g<<8|b<<16|a<<24},makeSurface:function(width,height,flags,usePageCanvas,source,rmask,gmask,bmask,amask){flags=flags||0;var is_SDL_HWSURFACE=flags&1;var is_SDL_HWPALETTE=flags&2097152;var is_SDL_OPENGL=flags&67108864;var surf=_malloc(60);var pixelFormat=_malloc(44);var bpp=is_SDL_HWPALETTE?1:4;var buffer=0;if(!is_SDL_HWSURFACE&&!is_SDL_OPENGL){buffer=_malloc(width*height*4)}HEAP32[surf>>2]=flags;HEAPU32[surf+4>>2]=pixelFormat;HEAP32[surf+8>>2]=width;HEAP32[surf+12>>2]=height;HEAP32[surf+16>>2]=width*bpp;HEAPU32[surf+20>>2]=buffer;HEAP32[surf+36>>2]=0;HEAP32[surf+40>>2]=0;HEAP32[surf+44>>2]=Module["canvas"].width;HEAP32[surf+48>>2]=Module["canvas"].height;HEAP32[surf+56>>2]=1;HEAP32[pixelFormat>>2]=-2042224636;HEAP32[pixelFormat+4>>2]=0;HEAP8[pixelFormat+8>>0]=bpp*8;HEAP8[pixelFormat+9>>0]=bpp;HEAP32[pixelFormat+12>>2]=rmask||255;HEAP32[pixelFormat+16>>2]=gmask||65280;HEAP32[pixelFormat+20>>2]=bmask||16711680;HEAP32[pixelFormat+24>>2]=amask||4278190080;SDL.GL=SDL.GL||is_SDL_OPENGL;var canvas;if(!usePageCanvas){if(SDL.canvasPool.length>0){canvas=SDL.canvasPool.pop()}else{canvas=document.createElement("canvas")}canvas.width=width;canvas.height=height}else{canvas=Module["canvas"]}var webGLContextAttributes={antialias:SDL.glAttributes[13]!=0&&SDL.glAttributes[14]>1,depth:SDL.glAttributes[6]>0,stencil:SDL.glAttributes[7]>0,alpha:SDL.glAttributes[3]>0};var ctx=Browser.createContext(canvas,is_SDL_OPENGL,usePageCanvas,webGLContextAttributes);SDL.surfaces[surf]={width:width,height:height,canvas:canvas,ctx:ctx,surf:surf,buffer:buffer,pixelFormat:pixelFormat,alpha:255,flags:flags,locked:0,usePageCanvas:usePageCanvas,source:source,isFlagSet:function(flag){return flags&flag}};return surf},copyIndexedColorData:function(surfData,rX,rY,rW,rH){if(!surfData.colors){return}var fullWidth=Module["canvas"].width;var fullHeight=Module["canvas"].height;var startX=rX||0;var startY=rY||0;var endX=(rW||fullWidth-startX)+startX;var endY=(rH||fullHeight-startY)+startY;var buffer=surfData.buffer;if(!surfData.image.data32){surfData.image.data32=new Uint32Array(surfData.image.data.buffer)}var data32=surfData.image.data32;var colors32=surfData.colors32;for(var y=startY;y>0]]}}},freeSurface:function(surf){var refcountPointer=surf+56;var refcount=HEAP32[refcountPointer>>2];if(refcount>1){HEAP32[refcountPointer>>2]=refcount-1;return}var info=SDL.surfaces[surf];if(!info.usePageCanvas&&info.canvas)SDL.canvasPool.push(info.canvas);if(info.buffer)_free(info.buffer);_free(info.pixelFormat);_free(surf);SDL.surfaces[surf]=null;if(surf===SDL.screen){SDL.screen=null}},blitSurface:function(src,srcrect,dst,dstrect,scale){var srcData=SDL.surfaces[src];var dstData=SDL.surfaces[dst];var sr,dr;if(srcrect){sr=SDL.loadRect(srcrect)}else{sr={x:0,y:0,w:srcData.width,h:srcData.height}}if(dstrect){dr=SDL.loadRect(dstrect)}else{dr={x:0,y:0,w:srcData.width,h:srcData.height}}if(dstData.clipRect){var widthScale=!scale||sr.w===0?1:sr.w/dr.w;var heightScale=!scale||sr.h===0?1:sr.h/dr.h;dr=SDL.intersectionOfRects(dstData.clipRect,dr);sr.w=dr.w*widthScale;sr.h=dr.h*heightScale;if(dstrect){SDL.updateRect(dstrect,dr)}}var blitw,blith;if(scale){blitw=dr.w;blith=dr.h}else{blitw=sr.w;blith=sr.h}if(sr.w===0||sr.h===0||blitw===0||blith===0){return 0}var oldAlpha=dstData.ctx.globalAlpha;dstData.ctx.globalAlpha=srcData.alpha/255;dstData.ctx.drawImage(srcData.canvas,sr.x,sr.y,sr.w,sr.h,dr.x,dr.y,blitw,blith);dstData.ctx.globalAlpha=oldAlpha;if(dst!=SDL.screen){warnOnce("WARNING: copying canvas data to memory for compatibility");_SDL_LockSurface(dst);dstData.locked--}return 0},downFingers:{},savedKeydown:null,receiveEvent:function(event){function unpressAllPressedKeys(){for(var code in SDL.keyboardMap){SDL.events.push({type:"keyup",keyCode:SDL.keyboardMap[code]})}}switch(event.type){case"touchstart":case"touchmove":{event.preventDefault();var touches=[];if(event.type==="touchstart"){for(var i=0;i0?Math.max(delta,1):Math.min(delta,-1);var button=delta>0?3:4;SDL.events.push({type:"mousedown",button:button,pageX:event.pageX,pageY:event.pageY});SDL.events.push({type:"mouseup",button:button,pageX:event.pageX,pageY:event.pageY});SDL.events.push({type:"wheel",deltaX:0,deltaY:delta});event.preventDefault();break;case"mousemove":if(SDL.DOMButtons[0]===1){SDL.events.push({type:"touchmove",touch:{identifier:0,deviceID:-1,pageX:event.pageX,pageY:event.pageY}})}if(Browser.pointerLock){if("mozMovementX"in event){event["movementX"]=event["mozMovementX"];event["movementY"]=event["mozMovementY"]}if(event["movementX"]==0&&event["movementY"]==0){event.preventDefault();return}}case"keydown":case"keyup":case"keypress":case"mousedown":case"mouseup":if(event.type!=="keydown"||!SDL_unicode()&&!SDL.textInput||(event.keyCode===8||event.keyCode===9)){event.preventDefault()}if(event.type=="mousedown"){SDL.DOMButtons[event.button]=1;SDL.events.push({type:"touchstart",touch:{identifier:0,deviceID:-1,pageX:event.pageX,pageY:event.pageY}})}else if(event.type=="mouseup"){if(!SDL.DOMButtons[event.button]){return}SDL.events.push({type:"touchend",touch:{identifier:0,deviceID:-1,pageX:event.pageX,pageY:event.pageY}});SDL.DOMButtons[event.button]=0}if(event.type==="keydown"||event.type==="mousedown"){SDL.canRequestFullscreen=true}else if(event.type==="keyup"||event.type==="mouseup"){if(SDL.isRequestingFullscreen){Module["requestFullscreen"](true,true);SDL.isRequestingFullscreen=false}SDL.canRequestFullscreen=false}if(event.type==="keypress"&&SDL.savedKeydown){SDL.savedKeydown.keypressCharCode=event.charCode;SDL.savedKeydown=null}else if(event.type==="keydown"){SDL.savedKeydown=event}if(event.type!=="keypress"||SDL.textInput){SDL.events.push(event)}break;case"mouseout":for(var i=0;i<3;i++){if(SDL.DOMButtons[i]){SDL.events.push({type:"mouseup",button:i,pageX:event.pageX,pageY:event.pageY});SDL.DOMButtons[i]=0}}event.preventDefault();break;case"focus":SDL.events.push(event);event.preventDefault();break;case"blur":SDL.events.push(event);unpressAllPressedKeys();event.preventDefault();break;case"visibilitychange":SDL.events.push({type:"visibilitychange",visible:!document.hidden});unpressAllPressedKeys();event.preventDefault();break;case"unload":if(Browser.mainLoop.runner){SDL.events.push(event);Browser.mainLoop.runner()}return;case"resize":SDL.events.push(event);if(event.preventDefault){event.preventDefault()}break}if(SDL.events.length>=1e4){err("SDL event queue full, dropping events");SDL.events=SDL.events.slice(0,1e4)}SDL.flushEventsToHandler();return},lookupKeyCodeForEvent:function(event){var code=event.keyCode;if(code>=65&&code<=90){code+=32}else{code=SDL.keyCodes[event.keyCode]||event.keyCode;if(event.location===2&&code>=(224|1<<10)&&code<=(227|1<<10)){code+=4}}return code},handleEvent:function(event){if(event.handled)return;event.handled=true;switch(event.type){case"touchstart":case"touchend":case"touchmove":{Browser.calculateMouseEvent(event);break}case"keydown":case"keyup":{var down=event.type==="keydown";var code=SDL.lookupKeyCodeForEvent(event);HEAP8[SDL.keyboardState+code>>0]=down;SDL.modState=(HEAP8[SDL.keyboardState+1248>>0]?64:0)|(HEAP8[SDL.keyboardState+1249>>0]?1:0)|(HEAP8[SDL.keyboardState+1250>>0]?256:0)|(HEAP8[SDL.keyboardState+1252>>0]?128:0)|(HEAP8[SDL.keyboardState+1253>>0]?2:0)|(HEAP8[SDL.keyboardState+1254>>0]?512:0);if(down){SDL.keyboardMap[code]=event.keyCode}else{delete SDL.keyboardMap[code]}break}case"mousedown":case"mouseup":if(event.type=="mousedown"){SDL.buttonState|=1<0){if(SDL.makeCEvent(SDL.events.shift(),ptr)!==false)return 1}return 0}else{return SDL.events.length>0}},makeCEvent:function(event,ptr){if(typeof event=="number"){_memcpy(ptr,event,28);_free(event);return}SDL.handleEvent(event);switch(event.type){case"keydown":case"keyup":{var down=event.type==="keydown";var key=SDL.lookupKeyCodeForEvent(event);var scan;if(key>=1024){scan=key-1024}else{scan=SDL.scanCodes[key]||key}HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP8[ptr+8>>0]=down?1:0;HEAP8[ptr+9>>0]=0;HEAP32[ptr+12>>2]=scan;HEAP32[ptr+16>>2]=key;HEAP16[ptr+20>>1]=SDL.modState;HEAP32[ptr+24>>2]=event.keypressCharCode||key;break}case"keypress":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];var cStr=intArrayFromString(String.fromCharCode(event.charCode));for(var i=0;i>0]=cStr[i]}break}case"mousedown":case"mouseup":case"mousemove":{if(event.type!="mousemove"){var down=event.type==="mousedown";HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP32[ptr+8>>2]=0;HEAP32[ptr+12>>2]=0;HEAP8[ptr+16>>0]=event.button+1;HEAP8[ptr+17>>0]=down?1:0;HEAP32[ptr+20>>2]=Browser.mouseX;HEAP32[ptr+24>>2]=Browser.mouseY}else{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP32[ptr+8>>2]=0;HEAP32[ptr+12>>2]=0;HEAP32[ptr+16>>2]=SDL.buttonState;HEAP32[ptr+20>>2]=Browser.mouseX;HEAP32[ptr+24>>2]=Browser.mouseY;HEAP32[ptr+28>>2]=Browser.mouseMovementX;HEAP32[ptr+32>>2]=Browser.mouseMovementY}break}case"wheel":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+16>>2]=event.deltaX;HEAP32[ptr+20>>2]=event.deltaY;break}case"touchstart":case"touchend":case"touchmove":{var touch=event.touch;if(!Browser.touches[touch.identifier])break;var w=Module["canvas"].width;var h=Module["canvas"].height;var x=Browser.touches[touch.identifier].x/w;var y=Browser.touches[touch.identifier].y/h;var lx=Browser.lastTouches[touch.identifier].x/w;var ly=Browser.lastTouches[touch.identifier].y/h;var dx=x-lx;var dy=y-ly;if(touch["deviceID"]===undefined)touch.deviceID=SDL.TOUCH_DEFAULT_ID;if(dx===0&&dy===0&&event.type==="touchmove")return false;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=_SDL_GetTicks();HEAP64[ptr+8>>3]=BigInt(touch.deviceID);HEAP64[ptr+16>>3]=BigInt(touch.identifier);HEAPF32[ptr+24>>2]=x;HEAPF32[ptr+28>>2]=y;HEAPF32[ptr+32>>2]=dx;HEAPF32[ptr+36>>2]=dy;if(touch.force!==undefined){HEAPF32[ptr+40>>2]=touch.force}else{HEAPF32[ptr+40>>2]=event.type=="touchend"?0:1}break}case"unload":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];break}case"resize":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=event.w;HEAP32[ptr+8>>2]=event.h;break}case"joystick_button_up":case"joystick_button_down":{var state=event.type==="joystick_button_up"?0:1;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP8[ptr+4>>0]=event.index;HEAP8[ptr+5>>0]=event.button;HEAP8[ptr+6>>0]=state;break}case"joystick_axis_motion":{HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP8[ptr+4>>0]=event.index;HEAP8[ptr+5>>0]=event.axis;HEAP32[ptr+8>>2]=SDL.joystickAxisValueConversion(event.value);break}case"focus":{var SDL_WINDOWEVENT_FOCUS_GAINED=12;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP8[ptr+8>>0]=SDL_WINDOWEVENT_FOCUS_GAINED;break}case"blur":{var SDL_WINDOWEVENT_FOCUS_LOST=13;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP8[ptr+8>>0]=SDL_WINDOWEVENT_FOCUS_LOST;break}case"visibilitychange":{var SDL_WINDOWEVENT_SHOWN=1;var SDL_WINDOWEVENT_HIDDEN=2;var visibilityEventID=event.visible?SDL_WINDOWEVENT_SHOWN:SDL_WINDOWEVENT_HIDDEN;HEAP32[ptr>>2]=SDL.DOMEventToSDLEvent[event.type];HEAP32[ptr+4>>2]=0;HEAP8[ptr+8>>0]=visibilityEventID;break}default:throw"Unhandled SDL event: "+event.type}},makeFontString:function(height,fontName){if(fontName.charAt(0)!="'"&&fontName.charAt(0)!='"'){fontName='"'+fontName+'"'}return height+"px "+fontName+", serif"},estimateTextWidth:function(fontData,text){var h=fontData.size;var fontString=SDL.makeFontString(h,fontData.name);var tempCtx=SDL_ttfContext();tempCtx.font=fontString;var ret=tempCtx.measureText(text).width|0;return ret},allocateChannels:function(num){if(SDL.numChannels&&SDL.numChannels>=num&&num!=0)return;SDL.numChannels=num;SDL.channels=[];for(var i=0;i>1]/32768}}else if(audio.format==8){for(var j=0;j>0];channelData[j]=(v>=0?v-128:v+128)/128}}else if(audio.format==33056){for(var j=0;j>2]}}else{throw"Invalid SDL audio format "+audio.format+"!"}}},debugSurface:function(surfData){out("dumping surface "+[surfData.surf,surfData.source,surfData.width,surfData.height]);var image=surfData.ctx.getImageData(0,0,surfData.width,surfData.height);var data=image.data;var num=Math.min(surfData.width,surfData.height);for(var i=0;i0}},queryJoysticks:function(){for(var joystick in SDL.lastJoystickState){var state=SDL.getGamepad(joystick-1);var prevState=SDL.lastJoystickState[joystick];if(typeof state=="undefined")return;if(state===null)return;if(typeof state.timestamp!="number"||state.timestamp!=prevState.timestamp||!state.timestamp){var i;for(i=0;ideviceIndex&&deviceIndex>=0){return gamepads[deviceIndex]}return null}};Module["SDL"]=SDL;function SDL_unicode(){return SDL.unicode}Module["SDL_unicode"]=SDL_unicode;function _SDL_Linked_Version(){if(SDL.version===null){SDL.version=_malloc(3);HEAP8[SDL.version+0>>0]=1;HEAP8[SDL.version+1>>0]=3;HEAP8[SDL.version+2>>0]=0}return SDL.version}Module["_SDL_Linked_Version"]=_SDL_Linked_Version;_SDL_Linked_Version.sig="i";function _SDL_Init(initFlags){SDL.startTime=Date.now();SDL.initFlags=initFlags;if(!Module["doNotCaptureKeyboard"]){var keyboardListeningElement=Module["keyboardListeningElement"]||document;keyboardListeningElement.addEventListener("keydown",SDL.receiveEvent);keyboardListeningElement.addEventListener("keyup",SDL.receiveEvent);keyboardListeningElement.addEventListener("keypress",SDL.receiveEvent);window.addEventListener("focus",SDL.receiveEvent);window.addEventListener("blur",SDL.receiveEvent);document.addEventListener("visibilitychange",SDL.receiveEvent)}window.addEventListener("unload",SDL.receiveEvent);SDL.keyboardState=_malloc(65536);zeroMemory(SDL.keyboardState,65536);SDL.DOMEventToSDLEvent["keydown"]=768;SDL.DOMEventToSDLEvent["keyup"]=769;SDL.DOMEventToSDLEvent["keypress"]=771;SDL.DOMEventToSDLEvent["mousedown"]=1025;SDL.DOMEventToSDLEvent["mouseup"]=1026;SDL.DOMEventToSDLEvent["mousemove"]=1024;SDL.DOMEventToSDLEvent["wheel"]=1027;SDL.DOMEventToSDLEvent["touchstart"]=1792;SDL.DOMEventToSDLEvent["touchend"]=1793;SDL.DOMEventToSDLEvent["touchmove"]=1794;SDL.DOMEventToSDLEvent["unload"]=256;SDL.DOMEventToSDLEvent["resize"]=28673;SDL.DOMEventToSDLEvent["visibilitychange"]=512;SDL.DOMEventToSDLEvent["focus"]=512;SDL.DOMEventToSDLEvent["blur"]=512;SDL.DOMEventToSDLEvent["joystick_axis_motion"]=1536;SDL.DOMEventToSDLEvent["joystick_button_down"]=1539;SDL.DOMEventToSDLEvent["joystick_button_up"]=1540;return 0}Module["_SDL_Init"]=_SDL_Init;_SDL_Init.sig="ii";function _SDL_WasInit(){if(SDL.startTime===null){_SDL_Init()}return 1}Module["_SDL_WasInit"]=_SDL_WasInit;_SDL_WasInit.sig="i";function _SDL_GetVideoInfo(){var ret=_malloc(20);zeroMemory(ret,3);HEAP32[ret+12>>2]=Module["canvas"].width;HEAP32[ret+16>>2]=Module["canvas"].height;return ret}Module["_SDL_GetVideoInfo"]=_SDL_GetVideoInfo;_SDL_GetVideoInfo.sig="i";function _SDL_ListModes(format,flags){return-1}Module["_SDL_ListModes"]=_SDL_ListModes;function _SDL_VideoModeOK(width,height,depth,flags){return depth}Module["_SDL_VideoModeOK"]=_SDL_VideoModeOK;function _SDL_VideoDriverName(buf,max_size){if(SDL.startTime===null){return 0}var driverName=[101,109,115,99,114,105,112,116,101,110,95,115,100,108,95,100,114,105,118,101,114];var index=0;var size=driverName.length;if(max_size<=size){size=max_size-1}while(index>0]=value;index++}HEAP8[buf+index>>0]=0;return buf}Module["_SDL_VideoDriverName"]=_SDL_VideoDriverName;_SDL_VideoDriverName.sig="iii";function _SDL_AudioDriverName(buf,max_size){return _SDL_VideoDriverName(buf,max_size)}Module["_SDL_AudioDriverName"]=_SDL_AudioDriverName;function _SDL_SetVideoMode(width,height,depth,flags){["touchstart","touchend","touchmove","mousedown","mouseup","mousemove","DOMMouseScroll","mousewheel","wheel","mouseout"].forEach(function(event){Module["canvas"].addEventListener(event,SDL.receiveEvent,true)});var canvas=Module["canvas"];if(width==0&&height==0){width=canvas.width;height=canvas.height}if(!SDL.addedResizeListener){SDL.addedResizeListener=true;Browser.resizeListeners.push(function(w,h){if(!SDL.settingVideoMode){SDL.receiveEvent({type:"resize",w:w,h:h})}})}SDL.settingVideoMode=true;Browser.setCanvasSize(width,height);SDL.settingVideoMode=false;if(SDL.screen){SDL.freeSurface(SDL.screen);assert(!SDL.screen)}if(SDL.GL)flags=flags|67108864;SDL.screen=SDL.makeSurface(width,height,flags,true,"screen");return SDL.screen}Module["_SDL_SetVideoMode"]=_SDL_SetVideoMode;_SDL_SetVideoMode.sig="iiiii";function _SDL_GetVideoSurface(){return SDL.screen}Module["_SDL_GetVideoSurface"]=_SDL_GetVideoSurface;_SDL_GetVideoSurface.sig="i";function _SDL_AudioQuit(){for(var i=0;i0){return}if(surfData.isFlagSet(2097152)){SDL.copyIndexedColorData(surfData)}else if(!surfData.colors){var data=surfData.image.data;var buffer=surfData.buffer;assert(buffer%4==0,"Invalid buffer offset: "+buffer);var src=buffer>>2;var dst=0;var isScreen=surf==SDL.screen;var num;if(typeof CanvasPixelArray!="undefined"&&data instanceof CanvasPixelArray){num=data.length;while(dst>8&255;data[dst+2]=val>>16&255;data[dst+3]=isScreen?255:val>>24&255;src++;dst+=4}}else{var data32=new Uint32Array(data.buffer);if(isScreen&&SDL.defaults.opaqueFrontBuffer){num=data32.length;data32.set(HEAP32.subarray(src,src+num));var data8=new Uint8Array(data.buffer);var i=3;var j=i+4*num;if(num%8==0){while(i>0]*4;var start=base+x*4;data[start]=colors[val];data[start+1]=colors[val+1];data[start+2]=colors[val+2]}s+=width*3}}surfData.ctx.putImageData(surfData.image,0,0)}Module["_SDL_UnlockSurface"]=_SDL_UnlockSurface;_SDL_UnlockSurface.sig="vi";function _SDL_Flip(surf){}Module["_SDL_Flip"]=_SDL_Flip;function _SDL_UpdateRect(surf,x,y,w,h){}Module["_SDL_UpdateRect"]=_SDL_UpdateRect;function _SDL_UpdateRects(surf,numrects,rects){}Module["_SDL_UpdateRects"]=_SDL_UpdateRects;function _SDL_Delay(delay){if(!ENVIRONMENT_IS_WORKER)abort("SDL_Delay called on the main thread! Potential infinite loop, quitting. (consider building with async support like ASYNCIFY)");var now=Date.now();while(Date.now()-now>2]=65536}return SDL.keyboardState}Module["_SDL_GetKeyboardState"]=_SDL_GetKeyboardState;_SDL_GetKeyboardState.sig="ii";function _SDL_GetKeyState(){return _SDL_GetKeyboardState()}Module["_SDL_GetKeyState"]=_SDL_GetKeyState;function _SDL_GetKeyName(key){if(!SDL.keyName){SDL.keyName=allocateUTF8("unknown key")}return SDL.keyName}Module["_SDL_GetKeyName"]=_SDL_GetKeyName;_SDL_GetKeyName.sig="ii";function _SDL_GetModState(){return SDL.modState}Module["_SDL_GetModState"]=_SDL_GetModState;_SDL_GetModState.sig="i";function _SDL_GetMouseState(x,y){if(x)HEAP32[x>>2]=Browser.mouseX;if(y)HEAP32[y>>2]=Browser.mouseY;return SDL.buttonState}Module["_SDL_GetMouseState"]=_SDL_GetMouseState;_SDL_GetMouseState.sig="iii";function _SDL_WarpMouse(x,y){return}Module["_SDL_WarpMouse"]=_SDL_WarpMouse;_SDL_WarpMouse.sig="vii";function _SDL_ShowCursor(toggle){switch(toggle){case 0:if(Browser.isFullscreen){Module["canvas"].requestPointerLock();return 0}else{return 1}break;case 1:Module["canvas"].exitPointerLock();return 1;break;case-1:return!Browser.pointerLock;break;default:out("SDL_ShowCursor called with unknown toggle parameter value: "+toggle+".");break}}Module["_SDL_ShowCursor"]=_SDL_ShowCursor;_SDL_ShowCursor.sig="ii";function _SDL_GetError(){if(!SDL.errorMessage){SDL.errorMessage=allocateUTF8("unknown SDL-emscripten error")}return SDL.errorMessage}Module["_SDL_GetError"]=_SDL_GetError;_SDL_GetError.sig="i";function _SDL_SetError(){}Module["_SDL_SetError"]=_SDL_SetError;function _SDL_malloc(size){return _malloc(size)}Module["_SDL_malloc"]=_SDL_malloc;_SDL_malloc.sig="ii";function _SDL_free(ptr){_free(ptr)}Module["_SDL_free"]=_SDL_free;_SDL_free.sig="vi";function _SDL_CreateRGBSurface(flags,width,height,depth,rmask,gmask,bmask,amask){return SDL.makeSurface(width,height,flags,false,"CreateRGBSurface",rmask,gmask,bmask,amask)}Module["_SDL_CreateRGBSurface"]=_SDL_CreateRGBSurface;_SDL_CreateRGBSurface.sig="iiiiiiiii";function _SDL_CreateRGBSurfaceFrom(pixels,width,height,depth,pitch,rmask,gmask,bmask,amask){var surf=SDL.makeSurface(width,height,0,false,"CreateRGBSurfaceFrom",rmask,gmask,bmask,amask);if(depth!==32){out("TODO: Partially unimplemented SDL_CreateRGBSurfaceFrom called!");return surf}var data=SDL.surfaces[surf];var image=data.ctx.createImageData(width,height);var pitchOfDst=width*4;for(var row=0;row>0]}}data.ctx.putImageData(image,0,0);return surf}Module["_SDL_CreateRGBSurfaceFrom"]=_SDL_CreateRGBSurfaceFrom;_SDL_CreateRGBSurfaceFrom.sig="iiiiiiiiii";function _SDL_ConvertSurface(surf,format,flags){if(format){SDL.checkPixelFormat(format)}var oldData=SDL.surfaces[surf];var ret=SDL.makeSurface(oldData.width,oldData.height,oldData.flags,false,"copy:"+oldData.source);var newData=SDL.surfaces[ret];newData.ctx.globalCompositeOperation="copy";newData.ctx.drawImage(oldData.canvas,0,0);newData.ctx.globalCompositeOperation=oldData.ctx.globalCompositeOperation;return ret}Module["_SDL_ConvertSurface"]=_SDL_ConvertSurface;_SDL_ConvertSurface.sig="iiii";function _SDL_DisplayFormatAlpha(surf){return _SDL_ConvertSurface(surf)}Module["_SDL_DisplayFormatAlpha"]=_SDL_DisplayFormatAlpha;function _SDL_FreeSurface(surf){if(surf)SDL.freeSurface(surf)}Module["_SDL_FreeSurface"]=_SDL_FreeSurface;_SDL_FreeSurface.sig="vi";function _SDL_UpperBlit(src,srcrect,dst,dstrect){return SDL.blitSurface(src,srcrect,dst,dstrect,false)}Module["_SDL_UpperBlit"]=_SDL_UpperBlit;_SDL_UpperBlit.sig="iiiii";function _SDL_UpperBlitScaled(src,srcrect,dst,dstrect){return SDL.blitSurface(src,srcrect,dst,dstrect,true)}Module["_SDL_UpperBlitScaled"]=_SDL_UpperBlitScaled;_SDL_UpperBlitScaled.sig="iiiii";function _SDL_LowerBlit(a0,a1,a2,a3){return _SDL_UpperBlit(a0,a1,a2,a3)}Module["_SDL_LowerBlit"]=_SDL_LowerBlit;_SDL_LowerBlit.sig="iiiii";function _SDL_LowerBlitScaled(a0,a1,a2,a3){return _SDL_UpperBlitScaled(a0,a1,a2,a3)}Module["_SDL_LowerBlitScaled"]=_SDL_LowerBlitScaled;_SDL_LowerBlitScaled.sig="iiiii";function _SDL_GetClipRect(surf,rect){assert(rect);var surfData=SDL.surfaces[surf];var r=surfData.clipRect||{x:0,y:0,w:surfData.width,h:surfData.height};SDL.updateRect(rect,r)}Module["_SDL_GetClipRect"]=_SDL_GetClipRect;_SDL_GetClipRect.sig="vii";function _SDL_SetClipRect(surf,rect){var surfData=SDL.surfaces[surf];if(rect){surfData.clipRect=SDL.intersectionOfRects({x:0,y:0,w:surfData.width,h:surfData.height},SDL.loadRect(rect))}else{delete surfData.clipRect}}Module["_SDL_SetClipRect"]=_SDL_SetClipRect;_SDL_SetClipRect.sig="vii";function _SDL_FillRect(surf,rect,color){var surfData=SDL.surfaces[surf];assert(!surfData.locked);if(surfData.isFlagSet(2097152)){color=surfData.colors32[color]}var r=rect?SDL.loadRect(rect):{x:0,y:0,w:surfData.width,h:surfData.height};if(surfData.clipRect){r=SDL.intersectionOfRects(surfData.clipRect,r);if(rect){SDL.updateRect(rect,r)}}surfData.ctx.save();surfData.ctx.fillStyle=SDL.translateColorToCSSRGBA(color);surfData.ctx.fillRect(r.x,r.y,r.w,r.h);surfData.ctx.restore();return 0}Module["_SDL_FillRect"]=_SDL_FillRect;_SDL_FillRect.sig="iiii";function _SDL_BlitSurface(src,srcrect,dst,dstrect){return SDL.blitSurface(src,srcrect,dst,dstrect,false)}Module["_SDL_BlitSurface"]=_SDL_BlitSurface;_SDL_BlitSurface.sig="iiiii";function _SDL_BlitScaled(src,srcrect,dst,dstrect){return SDL.blitSurface(src,srcrect,dst,dstrect,true)}Module["_SDL_BlitScaled"]=_SDL_BlitScaled;_SDL_BlitScaled.sig="iiiii";function _zoomSurface(src,x,y,smooth){var srcData=SDL.surfaces[src];var w=srcData.width*x;var h=srcData.height*y;var ret=SDL.makeSurface(Math.abs(w),Math.abs(h),srcData.flags,false,"zoomSurface");var dstData=SDL.surfaces[ret];if(x>=0&&y>=0)dstData.ctx.drawImage(srcData.canvas,0,0,w,h);else{dstData.ctx.save();dstData.ctx.scale(x<0?-1:1,y<0?-1:1);dstData.ctx.drawImage(srcData.canvas,w<0?w:0,h<0?h:0,Math.abs(w),Math.abs(h));dstData.ctx.restore()}return ret}Module["_zoomSurface"]=_zoomSurface;function _rotozoomSurface(src,angle,zoom,smooth){if(angle%360===0){return _zoomSurface(src,zoom,zoom,smooth)}var srcData=SDL.surfaces[src];var w=srcData.width*zoom;var h=srcData.height*zoom;var diagonal=Math.ceil(Math.sqrt(Math.pow(w,2)+Math.pow(h,2)));var ret=SDL.makeSurface(diagonal,diagonal,srcData.flags,false,"rotozoomSurface");var dstData=SDL.surfaces[ret];dstData.ctx.translate(diagonal/2,diagonal/2);dstData.ctx.rotate(-angle*Math.PI/180);dstData.ctx.drawImage(srcData.canvas,-w/2,-h/2,w,h);return ret}Module["_rotozoomSurface"]=_rotozoomSurface;function _SDL_SetAlpha(surf,flag,alpha){var surfData=SDL.surfaces[surf];surfData.alpha=alpha;if(!(flag&65536)){surfData.alpha=255}}Module["_SDL_SetAlpha"]=_SDL_SetAlpha;_SDL_SetAlpha.sig="iiii";function _SDL_SetColorKey(surf,flag,key){warnOnce("SDL_SetColorKey is a no-op for performance reasons");return 0}Module["_SDL_SetColorKey"]=_SDL_SetColorKey;function _SDL_PollEvent(ptr){return SDL.pollEvent(ptr)}Module["_SDL_PollEvent"]=_SDL_PollEvent;_SDL_PollEvent.sig="ii";function _SDL_PushEvent(ptr){var copy=_malloc(28);_memcpy(copy,ptr,28);SDL.events.push(copy);return 0}Module["_SDL_PushEvent"]=_SDL_PushEvent;_SDL_PushEvent.sig="ii";function _SDL_PeepEvents(events,requestedEventCount,action,from,to){switch(action){case 2:{assert(requestedEventCount==1);var index=0;var retrievedEventCount=0;while(index>0];surfData.colors[index+1]=HEAPU8[colors+(i*4+1)>>0];surfData.colors[index+2]=HEAPU8[colors+(i*4+2)>>0];surfData.colors[index+3]=255}return 1}Module["_SDL_SetColors"]=_SDL_SetColors;_SDL_SetColors.sig="iiiii";function _SDL_SetPalette(surf,flags,colors,firstColor,nColors){return _SDL_SetColors(surf,colors,firstColor,nColors)}Module["_SDL_SetPalette"]=_SDL_SetPalette;function _SDL_MapRGB(fmt,r,g,b){SDL.checkPixelFormat(fmt);return r&255|(g&255)<<8|(b&255)<<16|4278190080}Module["_SDL_MapRGB"]=_SDL_MapRGB;_SDL_MapRGB.sig="iiiii";function _SDL_MapRGBA(fmt,r,g,b,a){SDL.checkPixelFormat(fmt);return r&255|(g&255)<<8|(b&255)<<16|(a&255)<<24}Module["_SDL_MapRGBA"]=_SDL_MapRGBA;_SDL_MapRGBA.sig="iiiiii";function _SDL_GetRGB(pixel,fmt,r,g,b){SDL.checkPixelFormat(fmt);if(r){HEAP8[r>>0]=pixel&255}if(g){HEAP8[g>>0]=pixel>>8&255}if(b){HEAP8[b>>0]=pixel>>16&255}}Module["_SDL_GetRGB"]=_SDL_GetRGB;_SDL_GetRGB.sig="viiiii";function _SDL_GetRGBA(pixel,fmt,r,g,b,a){SDL.checkPixelFormat(fmt);if(r){HEAP8[r>>0]=pixel&255}if(g){HEAP8[g>>0]=pixel>>8&255}if(b){HEAP8[b>>0]=pixel>>16&255}if(a){HEAP8[a>>0]=pixel>>24&255}}Module["_SDL_GetRGBA"]=_SDL_GetRGBA;_SDL_GetRGBA.sig="viiiiii";function _SDL_GetAppState(){var state=0;if(Browser.pointerLock){state|=1}if(document.hasFocus()){state|=2}state|=4;return state}Module["_SDL_GetAppState"]=_SDL_GetAppState;_SDL_GetAppState.sig="i";function _SDL_WM_GrabInput(){}Module["_SDL_WM_GrabInput"]=_SDL_WM_GrabInput;function _SDL_WM_ToggleFullScreen(surf){if(Browser.exitFullscreen()){return 1}else{if(!SDL.canRequestFullscreen){return 0}SDL.isRequestingFullscreen=true;return 1}}Module["_SDL_WM_ToggleFullScreen"]=_SDL_WM_ToggleFullScreen;_SDL_WM_ToggleFullScreen.sig="ii";function _IMG_Init(flags){return flags}Module["_IMG_Init"]=_IMG_Init;function _SDL_FreeRW(rwopsID){SDL.rwops[rwopsID]=null;while(SDL.rwops.length>0&&SDL.rwops[SDL.rwops.length-1]===null){SDL.rwops.pop()}}Module["_SDL_FreeRW"]=_SDL_FreeRW;_SDL_FreeRW.sig="vi";function _IMG_Load_RW(rwopsID,freeSrc){try{var cleanup=function(){if(rwops&&freeSrc)_SDL_FreeRW(rwopsID)};var addCleanup=function(func){var old=cleanup;cleanup=function added_cleanup(){old();func()}};var callStbImage=function(func,params){var x=Module["_malloc"](4);var y=Module["_malloc"](4);var comp=Module["_malloc"](4);addCleanup(function(){Module["_free"](x);Module["_free"](y);Module["_free"](comp);if(data)Module["_stbi_image_free"](data)});var data=Module["_"+func].apply(null,params.concat([x,y,comp,0]));if(!data)return null;return{rawData:true,data:data,width:HEAP32[x>>2],height:HEAP32[y>>2],size:HEAP32[x>>2]*HEAP32[y>>2]*HEAP32[comp>>2],bpp:HEAP32[comp>>2]}};var rwops=SDL.rwops[rwopsID];if(rwops===undefined){return 0}var raw;var filename=rwops.filename;if(filename===undefined){warnOnce("Only file names that have been preloaded are supported for IMG_Load_RW. Consider using STB_IMAGE=1 if you want synchronous image decoding (see settings.js), or package files with --use-preload-plugins");return 0}if(!raw){filename=PATH_FS.resolve(filename);raw=preloadedImages[filename];if(!raw){if(raw===null)err("Trying to reuse preloaded image, but freePreloadedMediaOnUse is set!");warnOnce("Cannot find preloaded image "+filename);warnOnce("Cannot find preloaded image "+filename+". Consider using STB_IMAGE=1 if you want synchronous image decoding (see settings.js), or package files with --use-preload-plugins");return 0}else if(Module["freePreloadedMediaOnUse"]){preloadedImages[filename]=null}}var surf=SDL.makeSurface(raw.width,raw.height,0,false,"load:"+filename);var surfData=SDL.surfaces[surf];surfData.ctx.globalCompositeOperation="copy";if(!raw.rawData){surfData.ctx.drawImage(raw,0,0,raw.width,raw.height,0,0,raw.width,raw.height)}else{var imageData=surfData.ctx.getImageData(0,0,surfData.width,surfData.height);if(raw.bpp==4){imageData.data.set(HEAPU8.subarray(raw.data,raw.data+raw.size))}else if(raw.bpp==3){var pixels=raw.size/3;var data=imageData.data;var sourcePtr=raw.data;var destPtr=0;for(var i=0;i>0];data[destPtr++]=HEAPU8[sourcePtr++>>0];data[destPtr++]=HEAPU8[sourcePtr++>>0];data[destPtr++]=255}}else if(raw.bpp==2){var pixels=raw.size;var data=imageData.data;var sourcePtr=raw.data;var destPtr=0;for(var i=0;i>0];var alpha=HEAPU8[sourcePtr++>>0];data[destPtr++]=gray;data[destPtr++]=gray;data[destPtr++]=gray;data[destPtr++]=alpha}}else if(raw.bpp==1){var pixels=raw.size;var data=imageData.data;var sourcePtr=raw.data;var destPtr=0;for(var i=0;i>0];data[destPtr++]=value;data[destPtr++]=value;data[destPtr++]=value;data[destPtr++]=255}}else{err("cannot handle bpp "+raw.bpp);return 0}surfData.ctx.putImageData(imageData,0,0)}surfData.ctx.globalCompositeOperation="source-over";_SDL_LockSurface(surf);surfData.locked--;if(SDL.GL){surfData.canvas=surfData.ctx=null}return surf}finally{cleanup()}}Module["_IMG_Load_RW"]=_IMG_Load_RW;_IMG_Load_RW.sig="iii";function _SDL_RWFromFile(_name,mode){var id=SDL.rwops.length;var name=UTF8ToString(_name);SDL.rwops.push({filename:name,mimetype:Browser.getMimetype(name)});return id}Module["_SDL_RWFromFile"]=_SDL_RWFromFile;_SDL_RWFromFile.sig="iii";function _IMG_Load(filename){var rwops=_SDL_RWFromFile(filename);var result=_IMG_Load_RW(rwops,1);return result}Module["_IMG_Load"]=_IMG_Load;_IMG_Load.sig="ii";function _SDL_LoadBMP(a0){return _IMG_Load(a0)}Module["_SDL_LoadBMP"]=_SDL_LoadBMP;_SDL_LoadBMP.sig="ii";function _SDL_LoadBMP_RW(a0,a1){return _IMG_Load_RW(a0,a1)}Module["_SDL_LoadBMP_RW"]=_SDL_LoadBMP_RW;_SDL_LoadBMP_RW.sig="iii";function _IMG_Quit(){out("IMG_Quit called (and ignored)")}Module["_IMG_Quit"]=_IMG_Quit;function _SDL_OpenAudio(desired,obtained){try{SDL.audio={freq:HEAPU32[desired>>2],format:HEAPU16[desired+4>>1],channels:HEAPU8[desired+6>>0],samples:HEAPU16[desired+8>>1],callback:HEAPU32[desired+16>>2],userdata:HEAPU32[desired+20>>2],paused:true,timer:null};if(SDL.audio.format==8){SDL.audio.silence=128}else if(SDL.audio.format==32784){SDL.audio.silence=0}else if(SDL.audio.format==33056){SDL.audio.silence=0}else{throw"Invalid SDL audio format "+SDL.audio.format+"!"}if(SDL.audio.freq<=0){throw"Unsupported sound frequency "+SDL.audio.freq+"!"}else if(SDL.audio.freq<=22050){SDL.audio.freq=22050}else if(SDL.audio.freq<=32e3){SDL.audio.freq=32e3}else if(SDL.audio.freq<=44100){SDL.audio.freq=44100}else if(SDL.audio.freq<=48e3){SDL.audio.freq=48e3}else if(SDL.audio.freq<=96e3){SDL.audio.freq=96e3}else{throw"Unsupported sound frequency "+SDL.audio.freq+"!"}if(SDL.audio.channels==0){SDL.audio.channels=1}else if(SDL.audio.channels<0||SDL.audio.channels>32){throw"Unsupported number of audio channels for SDL audio: "+SDL.audio.channels+"!"}else if(SDL.audio.channels!=1&&SDL.audio.channels!=2){out("Warning: Using untested number of audio channels "+SDL.audio.channels)}if(SDL.audio.samples<128||SDL.audio.samples>524288){throw"Unsupported audio callback buffer size "+SDL.audio.samples+"!"}else if((SDL.audio.samples&SDL.audio.samples-1)!=0){throw"Audio callback buffer size "+SDL.audio.samples+" must be a power-of-two!"}var totalSamples=SDL.audio.samples*SDL.audio.channels;if(SDL.audio.format==8){SDL.audio.bytesPerSample=1}else if(SDL.audio.format==32784){SDL.audio.bytesPerSample=2}else if(SDL.audio.format==33056){SDL.audio.bytesPerSample=4}else{throw"Invalid SDL audio format "+SDL.audio.format+"!"}SDL.audio.bufferSize=totalSamples*SDL.audio.bytesPerSample;SDL.audio.bufferDurationSecs=SDL.audio.bufferSize/SDL.audio.bytesPerSample/SDL.audio.channels/SDL.audio.freq;SDL.audio.bufferingDelay=50/1e3;SDL.audio.buffer=_malloc(SDL.audio.bufferSize);SDL.audio.numSimultaneouslyQueuedBuffers=Module["SDL_numSimultaneouslyQueuedBuffers"]||5;SDL.audio.queueNewAudioData=function SDL_queueNewAudioData(){if(!SDL.audio)return;for(var i=0;i=SDL.audio.bufferingDelay+SDL.audio.bufferDurationSecs*SDL.audio.numSimultaneouslyQueuedBuffers)return;getWasmTableEntry(SDL.audio.callback)(SDL.audio.userdata,SDL.audio.buffer,SDL.audio.bufferSize);SDL.audio.pushAudio(SDL.audio.buffer,SDL.audio.bufferSize)}};SDL.audio.caller=function SDL_audioCaller(){if(!SDL.audio)return;--SDL.audio.numAudioTimersPending;SDL.audio.queueNewAudioData();var secsUntilNextPlayStart=SDL.audio.nextPlayTime-SDL.audioContext["currentTime"];var preemptBufferFeedSecs=SDL.audio.bufferDurationSecs/2;if(SDL.audio.numAudioTimersPending>2]=SDL.audio.freq;HEAP16[obtained+4>>1]=SDL.audio.format;HEAP8[obtained+6>>0]=SDL.audio.channels;HEAP8[obtained+7>>0]=SDL.audio.silence;HEAP16[obtained+8>>1]=SDL.audio.samples;HEAPU32[obtained+16>>2]=SDL.audio.callback;HEAPU32[obtained+20>>2]=SDL.audio.userdata}SDL.allocateChannels(32)}catch(e){out('Initializing SDL audio threw an exception: "'+e.toString()+'"! Continuing without audio.');SDL.audio=null;SDL.allocateChannels(0);if(obtained){HEAP32[obtained>>2]=0;HEAP16[obtained+4>>1]=0;HEAP8[obtained+6>>0]=0;HEAP8[obtained+7>>0]=0;HEAP16[obtained+8>>1]=0;HEAPU32[obtained+16>>2]=0;HEAPU32[obtained+20>>2]=0}}if(!SDL.audio){return-1}return 0}Module["_SDL_OpenAudio"]=_SDL_OpenAudio;_SDL_OpenAudio.sig="iii";function _SDL_PauseAudio(pauseOn){if(!SDL.audio){return}if(pauseOn){if(SDL.audio.timer!==undefined){clearTimeout(SDL.audio.timer);SDL.audio.numAudioTimersPending=0;SDL.audio.timer=undefined}}else if(!SDL.audio.timer){SDL.audio.numAudioTimersPending=1;SDL.audio.timer=safeSetTimeout(SDL.audio.caller,1)}SDL.audio.paused=pauseOn}Module["_SDL_PauseAudio"]=_SDL_PauseAudio;_SDL_PauseAudio.sig="vi";function _SDL_CloseAudio(){if(SDL.audio){if(SDL.audio.callbackRemover){SDL.audio.callbackRemover();SDL.audio.callbackRemover=null}_SDL_PauseAudio(1);_free(SDL.audio.buffer);SDL.audio=null;SDL.allocateChannels(0)}}Module["_SDL_CloseAudio"]=_SDL_CloseAudio;_SDL_CloseAudio.sig="v";function _SDL_LockAudio(){}Module["_SDL_LockAudio"]=_SDL_LockAudio;function _SDL_UnlockAudio(){}Module["_SDL_UnlockAudio"]=_SDL_UnlockAudio;function _SDL_CreateMutex(){return 0}Module["_SDL_CreateMutex"]=_SDL_CreateMutex;function _SDL_LockMutex(){}Module["_SDL_LockMutex"]=_SDL_LockMutex;function _SDL_UnlockMutex(){}Module["_SDL_UnlockMutex"]=_SDL_UnlockMutex;function _SDL_mutexP(){return 0}Module["_SDL_mutexP"]=_SDL_mutexP;function _SDL_mutexV(){return 0}Module["_SDL_mutexV"]=_SDL_mutexV;function _SDL_DestroyMutex(){}Module["_SDL_DestroyMutex"]=_SDL_DestroyMutex;function _SDL_CreateCond(){return 0}Module["_SDL_CreateCond"]=_SDL_CreateCond;function _SDL_CondSignal(){}Module["_SDL_CondSignal"]=_SDL_CondSignal;function _SDL_CondWait(){}Module["_SDL_CondWait"]=_SDL_CondWait;function _SDL_DestroyCond(){}Module["_SDL_DestroyCond"]=_SDL_DestroyCond;function _SDL_StartTextInput(){SDL.textInput=true}Module["_SDL_StartTextInput"]=_SDL_StartTextInput;_SDL_StartTextInput.sig="v";function _SDL_StopTextInput(){SDL.textInput=false}Module["_SDL_StopTextInput"]=_SDL_StopTextInput;_SDL_StopTextInput.sig="v";function _Mix_Init(flags){if(!flags)return 0;return 8}Module["_Mix_Init"]=_Mix_Init;function _Mix_Quit(){}Module["_Mix_Quit"]=_Mix_Quit;function _Mix_OpenAudio(frequency,format,channels,chunksize){SDL.openAudioContext();autoResumeAudioContext(SDL.audioContext);SDL.allocateChannels(32);SDL.mixerFrequency=frequency;SDL.mixerFormat=format;SDL.mixerNumChannels=channels;SDL.mixerChunkSize=chunksize;return 0}Module["_Mix_OpenAudio"]=_Mix_OpenAudio;_Mix_OpenAudio.sig="iiiii";function _Mix_CloseAudio(){_SDL_CloseAudio()}Module["_Mix_CloseAudio"]=_Mix_CloseAudio;_Mix_CloseAudio.sig="v";function _Mix_AllocateChannels(num){SDL.allocateChannels(num);return num}Module["_Mix_AllocateChannels"]=_Mix_AllocateChannels;_Mix_AllocateChannels.sig="ii";function _Mix_ChannelFinished(func){SDL.channelFinished=func}Module["_Mix_ChannelFinished"]=_Mix_ChannelFinished;_Mix_ChannelFinished.sig="vi";function _Mix_Volume(channel,volume){if(channel==-1){for(var i=0;i>1;var buffer=new Float32Array(numSamples);for(var i=0;i>1]/32768}if(SDL.webAudioAvailable()){webAudio={};webAudio.decodedBuffer=buffer}else{audio=new Audio;audio.mozAudioChannelType="content";audio.numChannels=SDL.mixerNumChannels;audio.frequency=SDL.mixerFrequency}var id=SDL.audios.length;SDL.audios.push({source:"",audio:audio,webAudio:webAudio,buffer:buffer});return id}Module["_Mix_QuickLoad_RAW"]=_Mix_QuickLoad_RAW;_Mix_QuickLoad_RAW.sig="iii";function _Mix_FreeChunk(id){SDL.audios[id]=null}Module["_Mix_FreeChunk"]=_Mix_FreeChunk;_Mix_FreeChunk.sig="vi";function _Mix_ReserveChannels(num){SDL.channelMinimumNumber=num}Module["_Mix_ReserveChannels"]=_Mix_ReserveChannels;_Mix_ReserveChannels.sig="ii";function _Mix_PlayChannel(channel,id,loops){var info=SDL.audios[id];if(!info)return-1;if(!info.audio&&!info.webAudio)return-1;if(channel==-1){for(var i=SDL.channelMinimumNumber;i>2]=SDL.estimateTextWidth(fontData,UTF8ToString(text))}if(h){HEAP32[h>>2]=fontData.size}return 0}Module["_TTF_SizeText"]=_TTF_SizeText;_TTF_SizeText.sig="iiiii";function _TTF_SizeUTF8(a0,a1,a2,a3){return _TTF_SizeText(a0,a1,a2,a3)}Module["_TTF_SizeUTF8"]=_TTF_SizeUTF8;_TTF_SizeUTF8.sig="iiiii";function _TTF_GlyphMetrics(font,ch,minx,maxx,miny,maxy,advance){var fontData=SDL.fonts[font];var width=SDL.estimateTextWidth(fontData,String.fromCharCode(ch));if(advance){HEAP32[advance>>2]=width}if(minx){HEAP32[minx>>2]=0}if(maxx){HEAP32[maxx>>2]=width}if(miny){HEAP32[miny>>2]=0}if(maxy){HEAP32[maxy>>2]=fontData.size}}Module["_TTF_GlyphMetrics"]=_TTF_GlyphMetrics;_TTF_GlyphMetrics.sig="iiiiiiii";function _TTF_FontAscent(font){var fontData=SDL.fonts[font];return fontData.size*.98|0}Module["_TTF_FontAscent"]=_TTF_FontAscent;_TTF_FontAscent.sig="ii";function _TTF_FontDescent(font){var fontData=SDL.fonts[font];return fontData.size*.02|0}Module["_TTF_FontDescent"]=_TTF_FontDescent;_TTF_FontDescent.sig="ii";function _TTF_FontHeight(font){var fontData=SDL.fonts[font];return fontData.size}Module["_TTF_FontHeight"]=_TTF_FontHeight;_TTF_FontHeight.sig="ii";function _TTF_FontLineSkip(a0){return _TTF_FontHeight(a0)}Module["_TTF_FontLineSkip"]=_TTF_FontLineSkip;_TTF_FontLineSkip.sig="ii";function _TTF_Quit(){out("TTF_Quit called (and ignored)")}Module["_TTF_Quit"]=_TTF_Quit;var SDL_gfx={drawRectangle:function(surf,x1,y1,x2,y2,action,cssColor){x1=x1<<16>>16;y1=y1<<16>>16;x2=x2<<16>>16;y2=y2<<16>>16;var surfData=SDL.surfaces[surf];assert(!surfData.locked);var x=x1>16;y1=y1<<16>>16;x2=x2<<16>>16;y2=y2<<16>>16;var surfData=SDL.surfaces[surf];assert(!surfData.locked);surfData.ctx.save();surfData.ctx.strokeStyle=cssColor;surfData.ctx.beginPath();surfData.ctx.moveTo(x1,y1);surfData.ctx.lineTo(x2,y2);surfData.ctx.stroke();surfData.ctx.restore()},drawEllipse:function(surf,x,y,rx,ry,action,cssColor){x=x<<16>>16;y=y<<16>>16;rx=rx<<16>>16;ry=ry<<16>>16;var surfData=SDL.surfaces[surf];assert(!surfData.locked);surfData.ctx.save();surfData.ctx.beginPath();surfData.ctx.translate(x,y);surfData.ctx.scale(rx,ry);surfData.ctx.arc(0,0,1,0,2*Math.PI);surfData.ctx.restore();surfData.ctx.save();surfData.ctx[action+"Style"]=cssColor;surfData.ctx[action]();surfData.ctx.restore()},translateColorToCSSRGBA:function(rgba){return"rgba("+(rgba>>>24)+","+(rgba>>16&255)+","+(rgba>>8&255)+","+(rgba&255)+")"}};Module["SDL_gfx"]=SDL_gfx;function _boxColor(surf,x1,y1,x2,y2,color){return SDL_gfx.drawRectangle(surf,x1,y1,x2,y2,"fill",SDL_gfx.translateColorToCSSRGBA(color))}Module["_boxColor"]=_boxColor;function _boxRGBA(surf,x1,y1,x2,y2,r,g,b,a){return SDL_gfx.drawRectangle(surf,x1,y1,x2,y2,"fill",SDL.translateRGBAToCSSRGBA(r,g,b,a))}Module["_boxRGBA"]=_boxRGBA;function _rectangleColor(surf,x1,y1,x2,y2,color){return SDL_gfx.drawRectangle(surf,x1,y1,x2,y2,"stroke",SDL_gfx.translateColorToCSSRGBA(color))}Module["_rectangleColor"]=_rectangleColor;function _rectangleRGBA(surf,x1,y1,x2,y2,r,g,b,a){return SDL_gfx.drawRectangle(surf,x1,y1,x2,y2,"stroke",SDL.translateRGBAToCSSRGBA(r,g,b,a))}Module["_rectangleRGBA"]=_rectangleRGBA;function _ellipseColor(surf,x,y,rx,ry,color){return SDL_gfx.drawEllipse(surf,x,y,rx,ry,"stroke",SDL_gfx.translateColorToCSSRGBA(color))}Module["_ellipseColor"]=_ellipseColor;function _ellipseRGBA(surf,x,y,rx,ry,r,g,b,a){return SDL_gfx.drawEllipse(surf,x,y,rx,ry,"stroke",SDL.translateRGBAToCSSRGBA(r,g,b,a))}Module["_ellipseRGBA"]=_ellipseRGBA;function _filledEllipseColor(surf,x,y,rx,ry,color){return SDL_gfx.drawEllipse(surf,x,y,rx,ry,"fill",SDL_gfx.translateColorToCSSRGBA(color))}Module["_filledEllipseColor"]=_filledEllipseColor;function _filledEllipseRGBA(surf,x,y,rx,ry,r,g,b,a){return SDL_gfx.drawEllipse(surf,x,y,rx,ry,"fill",SDL.translateRGBAToCSSRGBA(r,g,b,a))}Module["_filledEllipseRGBA"]=_filledEllipseRGBA;function _lineColor(surf,x1,y1,x2,y2,color){return SDL_gfx.drawLine(surf,x1,y1,x2,y2,SDL_gfx.translateColorToCSSRGBA(color))}Module["_lineColor"]=_lineColor;function _lineRGBA(surf,x1,y1,x2,y2,r,g,b,a){return SDL_gfx.drawLine(surf,x1,y1,x2,y2,SDL.translateRGBAToCSSRGBA(r,g,b,a))}Module["_lineRGBA"]=_lineRGBA;function _pixelRGBA(surf,x1,y1,r,g,b,a){_boxRGBA(surf,x1,y1,x1,y1,r,g,b,a)}Module["_pixelRGBA"]=_pixelRGBA;function _SDL_GL_SetAttribute(attr,value){if(!(attr in SDL.glAttributes)){abort("Unknown SDL GL attribute ("+attr+"). Please check if your SDL version is supported.")}SDL.glAttributes[attr]=value}Module["_SDL_GL_SetAttribute"]=_SDL_GL_SetAttribute;_SDL_GL_SetAttribute.sig="iii";function _SDL_GL_GetAttribute(attr,value){if(!(attr in SDL.glAttributes)){abort("Unknown SDL GL attribute ("+attr+"). Please check if your SDL version is supported.")}if(value)HEAP32[value>>2]=SDL.glAttributes[attr];return 0}Module["_SDL_GL_GetAttribute"]=_SDL_GL_GetAttribute;_SDL_GL_GetAttribute.sig="iii";function _SDL_GL_SwapBuffers(){if(Browser.doSwapBuffers)Browser.doSwapBuffers()}Module["_SDL_GL_SwapBuffers"]=_SDL_GL_SwapBuffers;_SDL_GL_SwapBuffers.sig="v";function _SDL_GL_ExtensionSupported(extension){return Module.ctx.getExtension(extension)|0}Module["_SDL_GL_ExtensionSupported"]=_SDL_GL_ExtensionSupported;_SDL_GL_ExtensionSupported.sig="ii";function _SDL_DestroyWindow(window){}Module["_SDL_DestroyWindow"]=_SDL_DestroyWindow;function _SDL_DestroyRenderer(renderer){}Module["_SDL_DestroyRenderer"]=_SDL_DestroyRenderer;function _SDL_GetWindowFlags(){}Module["_SDL_GetWindowFlags"]=_SDL_GetWindowFlags;_SDL_GetWindowFlags.sig="iii";function _SDL_GL_SwapWindow(window){}Module["_SDL_GL_SwapWindow"]=_SDL_GL_SwapWindow;function _SDL_GL_MakeCurrent(window,context){}Module["_SDL_GL_MakeCurrent"]=_SDL_GL_MakeCurrent;function _SDL_GL_DeleteContext(context){}Module["_SDL_GL_DeleteContext"]=_SDL_GL_DeleteContext;function _SDL_GL_GetSwapInterval(state){if(Browser.mainLoop.timingMode==1)return Browser.mainLoop.timingValue;else return 0}Module["_SDL_GL_GetSwapInterval"]=_SDL_GL_GetSwapInterval;_SDL_GL_GetSwapInterval.sig="ii";function _SDL_GL_SetSwapInterval(state){_emscripten_set_main_loop_timing(1,state)}Module["_SDL_GL_SetSwapInterval"]=_SDL_GL_SetSwapInterval;function _SDL_SetWindowTitle(window,title){if(title)document.title=UTF8ToString(title)}Module["_SDL_SetWindowTitle"]=_SDL_SetWindowTitle;_SDL_SetWindowTitle.sig="vii";function _SDL_GetWindowSize(window,width,height){var w=Module["canvas"].width;var h=Module["canvas"].height;if(width)HEAP32[width>>2]=w;if(height)HEAP32[height>>2]=h}Module["_SDL_GetWindowSize"]=_SDL_GetWindowSize;_SDL_GetWindowSize.sig="viii";function _SDL_LogSetOutputFunction(callback,userdata){}Module["_SDL_LogSetOutputFunction"]=_SDL_LogSetOutputFunction;function _SDL_SetWindowFullscreen(window,fullscreen){if(Browser.isFullscreen){Module["canvas"].exitFullscreen();return 1}else{return 0}}Module["_SDL_SetWindowFullscreen"]=_SDL_SetWindowFullscreen;_SDL_SetWindowFullscreen.sig="iii";function _SDL_ClearError(){}Module["_SDL_ClearError"]=_SDL_ClearError;function _SDL_SetGamma(r,g,b){return-1}Module["_SDL_SetGamma"]=_SDL_SetGamma;function _SDL_SetGammaRamp(redTable,greenTable,blueTable){return-1}Module["_SDL_SetGammaRamp"]=_SDL_SetGammaRamp;function _SDL_NumJoysticks(){var count=0;var gamepads=SDL.getGamepads();for(var i=0;iaxis){return SDL.joystickAxisValueConversion(gamepad.axes[axis])}return 0}Module["_SDL_JoystickGetAxis"]=_SDL_JoystickGetAxis;_SDL_JoystickGetAxis.sig="iii";function _SDL_JoystickGetHat(joystick,hat){return 0}Module["_SDL_JoystickGetHat"]=_SDL_JoystickGetHat;function _SDL_JoystickGetBall(joystick,ball,dxptr,dyptr){return-1}Module["_SDL_JoystickGetBall"]=_SDL_JoystickGetBall;function _SDL_JoystickGetButton(joystick,button){var gamepad=SDL.getGamepad(joystick-1);if(gamepad&&gamepad.buttons.length>button){return SDL.getJoystickButtonState(gamepad.buttons[button])?1:0}return 0}Module["_SDL_JoystickGetButton"]=_SDL_JoystickGetButton;_SDL_JoystickGetButton.sig="iii";function _SDL_JoystickClose(joystick){delete SDL.lastJoystickState[joystick]}Module["_SDL_JoystickClose"]=_SDL_JoystickClose;_SDL_JoystickClose.sig="vi";function _SDL_InitSubSystem(flags){return 0}Module["_SDL_InitSubSystem"]=_SDL_InitSubSystem;function _SDL_RWFromConstMem(mem,size){var id=SDL.rwops.length;SDL.rwops.push({bytes:mem,count:size});return id}Module["_SDL_RWFromConstMem"]=_SDL_RWFromConstMem;_SDL_RWFromConstMem.sig="iii";function _SDL_RWFromMem(a0,a1){return _SDL_RWFromConstMem(a0,a1)}Module["_SDL_RWFromMem"]=_SDL_RWFromMem;_SDL_RWFromMem.sig="iii";function _SDL_GetNumAudioDrivers(){return 1}Module["_SDL_GetNumAudioDrivers"]=_SDL_GetNumAudioDrivers;function _SDL_GetCurrentAudioDriver(){return allocateUTF8("Emscripten Audio")}Module["_SDL_GetCurrentAudioDriver"]=_SDL_GetCurrentAudioDriver;function _SDL_GetAudioDriver(index){return _SDL_GetCurrentAudioDriver()}Module["_SDL_GetAudioDriver"]=_SDL_GetAudioDriver;function _SDL_EnableUNICODE(on){var ret=SDL.unicode||0;SDL.unicode=on;return ret}Module["_SDL_EnableUNICODE"]=_SDL_EnableUNICODE;_SDL_EnableUNICODE.sig="ii";function _SDL_AddTimer(interval,callback,param){return window.setTimeout(function(){getWasmTableEntry(callback)(interval,param)},interval)}Module["_SDL_AddTimer"]=_SDL_AddTimer;_SDL_AddTimer.sig="iiii";function _SDL_RemoveTimer(id){window.clearTimeout(id);return true}Module["_SDL_RemoveTimer"]=_SDL_RemoveTimer;_SDL_RemoveTimer.sig="ii";function _SDL_CreateThread(){throw"SDL threads cannot be supported in the web platform because they assume shared state. See emscripten_create_worker etc. for a message-passing concurrency model that does let you run code in another thread."}Module["_SDL_CreateThread"]=_SDL_CreateThread;function _SDL_WaitThread(){throw"SDL_WaitThread"}Module["_SDL_WaitThread"]=_SDL_WaitThread;function _SDL_GetThreadID(){throw"SDL_GetThreadID"}Module["_SDL_GetThreadID"]=_SDL_GetThreadID;function _SDL_ThreadID(){return 0}Module["_SDL_ThreadID"]=_SDL_ThreadID;function _SDL_AllocRW(){throw"SDL_AllocRW: TODO"}Module["_SDL_AllocRW"]=_SDL_AllocRW;function _SDL_CondBroadcast(){throw"SDL_CondBroadcast: TODO"}Module["_SDL_CondBroadcast"]=_SDL_CondBroadcast;function _SDL_CondWaitTimeout(){throw"SDL_CondWaitTimeout: TODO"}Module["_SDL_CondWaitTimeout"]=_SDL_CondWaitTimeout;function _SDL_WM_IconifyWindow(){throw"SDL_WM_IconifyWindow TODO"}Module["_SDL_WM_IconifyWindow"]=_SDL_WM_IconifyWindow;function _Mix_SetPostMix(){warnOnce("Mix_SetPostMix: TODO")}Module["_Mix_SetPostMix"]=_Mix_SetPostMix;function _Mix_VolumeChunk(chunk,volume){throw"Mix_VolumeChunk: TODO"}Module["_Mix_VolumeChunk"]=_Mix_VolumeChunk;function _Mix_SetPosition(channel,angle,distance){throw"Mix_SetPosition: TODO"}Module["_Mix_SetPosition"]=_Mix_SetPosition;function _Mix_QuerySpec(){throw"Mix_QuerySpec: TODO"}Module["_Mix_QuerySpec"]=_Mix_QuerySpec;function _Mix_FadeInChannelTimed(){throw"Mix_FadeInChannelTimed"}Module["_Mix_FadeInChannelTimed"]=_Mix_FadeInChannelTimed;function _Mix_FadeOutChannel(){throw"Mix_FadeOutChannel"}Module["_Mix_FadeOutChannel"]=_Mix_FadeOutChannel;function _Mix_Linked_Version(){throw"Mix_Linked_Version: TODO"}Module["_Mix_Linked_Version"]=_Mix_Linked_Version;function _SDL_SaveBMP_RW(){throw"SDL_SaveBMP_RW: TODO"}Module["_SDL_SaveBMP_RW"]=_SDL_SaveBMP_RW;function _SDL_WM_SetIcon(){}Module["_SDL_WM_SetIcon"]=_SDL_WM_SetIcon;function _SDL_HasRDTSC(){return 0}Module["_SDL_HasRDTSC"]=_SDL_HasRDTSC;function _SDL_HasMMX(){return 0}Module["_SDL_HasMMX"]=_SDL_HasMMX;function _SDL_HasMMXExt(){return 0}Module["_SDL_HasMMXExt"]=_SDL_HasMMXExt;function _SDL_Has3DNow(){return 0}Module["_SDL_Has3DNow"]=_SDL_Has3DNow;function _SDL_Has3DNowExt(){return 0}Module["_SDL_Has3DNowExt"]=_SDL_Has3DNowExt;function _SDL_HasSSE(){return 0}Module["_SDL_HasSSE"]=_SDL_HasSSE;function _SDL_HasSSE2(){return 0}Module["_SDL_HasSSE2"]=_SDL_HasSSE2;function _SDL_HasAltiVec(){return 0}Module["_SDL_HasAltiVec"]=_SDL_HasAltiVec;function _glutPostRedisplay(){if(GLUT.displayFunc&&!GLUT.requestedAnimationFrame){GLUT.requestedAnimationFrame=true;Browser.requestAnimationFrame(function(){GLUT.requestedAnimationFrame=false;Browser.mainLoop.runIter(function(){getWasmTableEntry(GLUT.displayFunc)()})})}}Module["_glutPostRedisplay"]=_glutPostRedisplay;_glutPostRedisplay.sig="v";var GLUT={initTime:null,idleFunc:null,displayFunc:null,keyboardFunc:null,keyboardUpFunc:null,specialFunc:null,specialUpFunc:null,reshapeFunc:null,motionFunc:null,passiveMotionFunc:null,mouseFunc:null,buttons:0,modifiers:0,initWindowWidth:256,initWindowHeight:256,initDisplayMode:18,windowX:0,windowY:0,windowWidth:0,windowHeight:0,requestedAnimationFrame:false,saveModifiers:function(event){GLUT.modifiers=0;if(event["shiftKey"])GLUT.modifiers+=1;if(event["ctrlKey"])GLUT.modifiers+=2;if(event["altKey"])GLUT.modifiers+=4},onMousemove:function(event){var lastX=Browser.mouseX;var lastY=Browser.mouseY;Browser.calculateMouseEvent(event);var newX=Browser.mouseX;var newY=Browser.mouseY;if(newX==lastX&&newY==lastY)return;if(GLUT.buttons==0&&event.target==Module["canvas"]&&GLUT.passiveMotionFunc){event.preventDefault();GLUT.saveModifiers(event);getWasmTableEntry(GLUT.passiveMotionFunc)(lastX,lastY)}else if(GLUT.buttons!=0&&GLUT.motionFunc){event.preventDefault();GLUT.saveModifiers(event);getWasmTableEntry(GLUT.motionFunc)(lastX,lastY)}},getSpecialKey:function(keycode){var key=null;switch(keycode){case 8:key=120;break;case 46:key=111;break;case 112:key=1;break;case 113:key=2;break;case 114:key=3;break;case 115:key=4;break;case 116:key=5;break;case 117:key=6;break;case 118:key=7;break;case 119:key=8;break;case 120:key=9;break;case 121:key=10;break;case 122:key=11;break;case 123:key=12;break;case 37:key=100;break;case 38:key=101;break;case 39:key=102;break;case 40:key=103;break;case 33:key=104;break;case 34:key=105;break;case 36:key=106;break;case 35:key=107;break;case 45:key=108;break;case 16:case 5:key=112;break;case 6:key=113;break;case 17:case 3:key=114;break;case 4:key=115;break;case 18:case 2:key=116;break;case 1:key=117;break}return key},getASCIIKey:function(event){if(event["ctrlKey"]||event["altKey"]||event["metaKey"])return null;var keycode=event["keyCode"];if(48<=keycode&&keycode<=57)return keycode;if(65<=keycode&&keycode<=90)return event["shiftKey"]?keycode:keycode+32;if(96<=keycode&&keycode<=105)return keycode-48;if(106<=keycode&&keycode<=111)return keycode-106+42;switch(keycode){case 9:case 13:case 27:case 32:case 61:return keycode}var s=event["shiftKey"];switch(keycode){case 186:return s?58:59;case 187:return s?43:61;case 188:return s?60:44;case 189:return s?95:45;case 190:return s?62:46;case 191:return s?63:47;case 219:return s?123:91;case 220:return s?124:47;case 221:return s?125:93;case 222:return s?34:39}return null},onKeydown:function(event){if(GLUT.specialFunc||GLUT.keyboardFunc){var key=GLUT.getSpecialKey(event["keyCode"]);if(key!==null){if(GLUT.specialFunc){event.preventDefault();GLUT.saveModifiers(event);getWasmTableEntry(GLUT.specialFunc)(key,Browser.mouseX,Browser.mouseY)}}else{key=GLUT.getASCIIKey(event);if(key!==null&&GLUT.keyboardFunc){event.preventDefault();GLUT.saveModifiers(event);getWasmTableEntry(GLUT.keyboardFunc)(key,Browser.mouseX,Browser.mouseY)}}}},onKeyup:function(event){if(GLUT.specialUpFunc||GLUT.keyboardUpFunc){var key=GLUT.getSpecialKey(event["keyCode"]);if(key!==null){if(GLUT.specialUpFunc){event.preventDefault();GLUT.saveModifiers(event);getWasmTableEntry(GLUT.specialUpFunc)(key,Browser.mouseX,Browser.mouseY)}}else{key=GLUT.getASCIIKey(event);if(key!==null&&GLUT.keyboardUpFunc){event.preventDefault();GLUT.saveModifiers(event);getWasmTableEntry(GLUT.keyboardUpFunc)(key,Browser.mouseX,Browser.mouseY)}}}},touchHandler:function(event){if(event.target!=Module["canvas"]){return}var touches=event.changedTouches,main=touches[0],type="";switch(event.type){case"touchstart":type="mousedown";break;case"touchmove":type="mousemove";break;case"touchend":type="mouseup";break;default:return}var simulatedEvent=document.createEvent("MouseEvent");simulatedEvent.initMouseEvent(type,true,true,window,1,main.screenX,main.screenY,main.clientX,main.clientY,false,false,false,false,0,null);main.target.dispatchEvent(simulatedEvent);event.preventDefault()},onMouseButtonDown:function(event){Browser.calculateMouseEvent(event);GLUT.buttons|=1<0?Math.max(delta,1):Math.min(delta,-1);var button=3;if(delta<0){button=4}if(GLUT.mouseFunc){event.preventDefault();GLUT.saveModifiers(event);getWasmTableEntry(GLUT.mouseFunc)(button,0,Browser.mouseX,Browser.mouseY)}},onFullscreenEventChange:function(event){var width;var height;if(document["fullscreen"]||document["fullScreen"]||document["mozFullScreen"]||document["webkitIsFullScreen"]){width=screen["width"];height=screen["height"]}else{width=GLUT.windowWidth;height=GLUT.windowHeight;document.removeEventListener("fullscreenchange",GLUT.onFullscreenEventChange,true);document.removeEventListener("mozfullscreenchange",GLUT.onFullscreenEventChange,true);document.removeEventListener("webkitfullscreenchange",GLUT.onFullscreenEventChange,true)}Browser.setCanvasSize(width,height,true);if(GLUT.reshapeFunc){getWasmTableEntry(GLUT.reshapeFunc)(width,height)}_glutPostRedisplay()}};Module["GLUT"]=GLUT;function _glutGetModifiers(){return GLUT.modifiers}Module["_glutGetModifiers"]=_glutGetModifiers;_glutGetModifiers.sig="i";function _glutInit(argcp,argv){GLUT.initTime=Date.now();var isTouchDevice="ontouchstart"in document.documentElement;if(isTouchDevice){window.addEventListener("touchmove",GLUT.touchHandler,true);window.addEventListener("touchstart",GLUT.touchHandler,true);window.addEventListener("touchend",GLUT.touchHandler,true)}window.addEventListener("keydown",GLUT.onKeydown,true);window.addEventListener("keyup",GLUT.onKeyup,true);window.addEventListener("mousemove",GLUT.onMousemove,true);window.addEventListener("mousedown",GLUT.onMouseButtonDown,true);window.addEventListener("mouseup",GLUT.onMouseButtonUp,true);window.addEventListener("mousewheel",GLUT.onMouseWheel,true);window.addEventListener("DOMMouseScroll",GLUT.onMouseWheel,true);Browser.resizeListeners.push(function(width,height){if(GLUT.reshapeFunc){getWasmTableEntry(GLUT.reshapeFunc)(width,height)}});__ATEXIT__.push(function(){if(isTouchDevice){window.removeEventListener("touchmove",GLUT.touchHandler,true);window.removeEventListener("touchstart",GLUT.touchHandler,true);window.removeEventListener("touchend",GLUT.touchHandler,true)}window.removeEventListener("keydown",GLUT.onKeydown,true);window.removeEventListener("keyup",GLUT.onKeyup,true);window.removeEventListener("mousemove",GLUT.onMousemove,true);window.removeEventListener("mousedown",GLUT.onMouseButtonDown,true);window.removeEventListener("mouseup",GLUT.onMouseButtonUp,true);window.removeEventListener("mousewheel",GLUT.onMouseWheel,true);window.removeEventListener("DOMMouseScroll",GLUT.onMouseWheel,true);Module["canvas"].width=Module["canvas"].height=1})}Module["_glutInit"]=_glutInit;_glutInit.sig="vii";function _glutInitWindowSize(width,height){Browser.setCanvasSize(GLUT.initWindowWidth=width,GLUT.initWindowHeight=height)}Module["_glutInitWindowSize"]=_glutInitWindowSize;_glutInitWindowSize.sig="vii";function _glutInitWindowPosition(x,y){}Module["_glutInitWindowPosition"]=_glutInitWindowPosition;_glutInitWindowPosition.sig="vii";function _glutGet(type){switch(type){case 100:return 0;case 101:return 0;case 102:return Module["canvas"].width;case 103:return Module["canvas"].height;case 200:return Module["canvas"].width;case 201:return Module["canvas"].height;case 500:return 0;case 501:return 0;case 502:return GLUT.initWindowWidth;case 503:return GLUT.initWindowHeight;case 700:var now=Date.now();return now-GLUT.initTime;case 105:return Module.ctx.getContextAttributes().stencil?8:0;case 106:return Module.ctx.getContextAttributes().depth?8:0;case 110:return Module.ctx.getContextAttributes().alpha?8:0;case 120:return Module.ctx.getContextAttributes().antialias?1:0;default:throw"glutGet("+type+") not implemented yet"}}Module["_glutGet"]=_glutGet;function _glutIdleFunc(func){function callback(){if(GLUT.idleFunc){getWasmTableEntry(GLUT.idleFunc)();safeSetTimeout(callback,4)}}if(!GLUT.idleFunc){safeSetTimeout(callback,0)}GLUT.idleFunc=func}Module["_glutIdleFunc"]=_glutIdleFunc;_glutIdleFunc.sig="vi";function _glutTimerFunc(msec,func,value){safeSetTimeout(function(){getWasmTableEntry(func)(value)},msec)}Module["_glutTimerFunc"]=_glutTimerFunc;_glutTimerFunc.sig="viii";function _glutDisplayFunc(func){GLUT.displayFunc=func}Module["_glutDisplayFunc"]=_glutDisplayFunc;_glutDisplayFunc.sig="vi";function _glutKeyboardFunc(func){GLUT.keyboardFunc=func}Module["_glutKeyboardFunc"]=_glutKeyboardFunc;_glutKeyboardFunc.sig="vi";function _glutKeyboardUpFunc(func){GLUT.keyboardUpFunc=func}Module["_glutKeyboardUpFunc"]=_glutKeyboardUpFunc;_glutKeyboardUpFunc.sig="vi";function _glutSpecialFunc(func){GLUT.specialFunc=func}Module["_glutSpecialFunc"]=_glutSpecialFunc;_glutSpecialFunc.sig="vi";function _glutSpecialUpFunc(func){GLUT.specialUpFunc=func}Module["_glutSpecialUpFunc"]=_glutSpecialUpFunc;_glutSpecialUpFunc.sig="vi";function _glutReshapeFunc(func){GLUT.reshapeFunc=func}Module["_glutReshapeFunc"]=_glutReshapeFunc;_glutReshapeFunc.sig="vi";function _glutMotionFunc(func){GLUT.motionFunc=func}Module["_glutMotionFunc"]=_glutMotionFunc;_glutMotionFunc.sig="vi";function _glutPassiveMotionFunc(func){GLUT.passiveMotionFunc=func}Module["_glutPassiveMotionFunc"]=_glutPassiveMotionFunc;_glutPassiveMotionFunc.sig="vi";function _glutMouseFunc(func){GLUT.mouseFunc=func}Module["_glutMouseFunc"]=_glutMouseFunc;_glutMouseFunc.sig="vi";function _glutSetCursor(cursor){var cursorStyle="auto";switch(cursor){case 0:break;case 1:break;case 2:cursorStyle="pointer";break;case 3:break;case 4:cursorStyle="help";break;case 5:break;case 6:break;case 7:cursorStyle="wait";break;case 8:cursorStyle="text";break;case 9:case 102:cursorStyle="crosshair";break;case 10:cursorStyle="ns-resize";break;case 11:cursorStyle="ew-resize";break;case 12:cursorStyle="n-resize";break;case 13:cursorStyle="s-resize";break;case 14:cursorStyle="w-resize";break;case 15:cursorStyle="e-resize";break;case 16:cursorStyle="nw-resize";break;case 17:cursorStyle="ne-resize";break;case 18:cursorStyle="se-resize";break;case 19:cursorStyle="sw-resize";break;case 100:break;case 101:cursorStyle="none";break;default:throw"glutSetCursor: Unknown cursor type: "+cursor}Module["canvas"].style.cursor=cursorStyle}Module["_glutSetCursor"]=_glutSetCursor;_glutSetCursor.sig="vi";function _glutCreateWindow(name){var contextAttributes={antialias:(GLUT.initDisplayMode&128)!=0,depth:(GLUT.initDisplayMode&16)!=0,stencil:(GLUT.initDisplayMode&32)!=0,alpha:(GLUT.initDisplayMode&8)!=0};Module.ctx=Browser.createContext(Module["canvas"],true,true,contextAttributes);return Module.ctx?1:0}Module["_glutCreateWindow"]=_glutCreateWindow;_glutCreateWindow.sig="ii";function _glutDestroyWindow(name){Module.ctx=Browser.destroyContext(Module["canvas"],true,true);return 1}Module["_glutDestroyWindow"]=_glutDestroyWindow;_glutDestroyWindow.sig="ii";function _glutReshapeWindow(width,height){Browser.exitFullscreen();Browser.setCanvasSize(width,height,true);if(GLUT.reshapeFunc){getWasmTableEntry(GLUT.reshapeFunc)(width,height)}_glutPostRedisplay()}Module["_glutReshapeWindow"]=_glutReshapeWindow;_glutReshapeWindow.sig="vi";function _glutPositionWindow(x,y){Browser.exitFullscreen();_glutPostRedisplay()}Module["_glutPositionWindow"]=_glutPositionWindow;_glutPositionWindow.sig="vii";function _glutFullScreen(){GLUT.windowX=0;GLUT.windowY=0;GLUT.windowWidth=Module["canvas"].width;GLUT.windowHeight=Module["canvas"].height;document.addEventListener("fullscreenchange",GLUT.onFullscreenEventChange,true);document.addEventListener("mozfullscreenchange",GLUT.onFullscreenEventChange,true);document.addEventListener("webkitfullscreenchange",GLUT.onFullscreenEventChange,true);Browser.requestFullscreen(false,false)}Module["_glutFullScreen"]=_glutFullScreen;_glutFullScreen.sig="v";function _glutInitDisplayMode(mode){GLUT.initDisplayMode=mode}Module["_glutInitDisplayMode"]=_glutInitDisplayMode;_glutInitDisplayMode.sig="vi";function _glutSwapBuffers(){}Module["_glutSwapBuffers"]=_glutSwapBuffers;_glutSwapBuffers.sig="v";function _glutMainLoop(){_glutReshapeWindow(Module["canvas"].width,Module["canvas"].height);_glutPostRedisplay();throw"unwind"}Module["_glutMainLoop"]=_glutMainLoop;_glutMainLoop.sig="v";function _XOpenDisplay(){return 1}Module["_XOpenDisplay"]=_XOpenDisplay;function _XCreateWindow(display,parent,x,y,width,height,border_width,depth,class_,visual,valuemask,attributes){Browser.setCanvasSize(width,height);return 2}Module["_XCreateWindow"]=_XCreateWindow;function _XChangeWindowAttributes(){}Module["_XChangeWindowAttributes"]=_XChangeWindowAttributes;function _XSetWMHints(){}Module["_XSetWMHints"]=_XSetWMHints;function _XMapWindow(){}Module["_XMapWindow"]=_XMapWindow;function _XStoreName(){}Module["_XStoreName"]=_XStoreName;function _XInternAtom(display,name_,hmm){return 0}Module["_XInternAtom"]=_XInternAtom;function _XSendEvent(){}Module["_XSendEvent"]=_XSendEvent;function _XPending(display){return 0}Module["_XPending"]=_XPending;var EGL={errorCode:12288,defaultDisplayInitialized:false,currentContext:0,currentReadSurface:0,currentDrawSurface:0,contextAttributes:{alpha:false,depth:false,stencil:false,antialias:false},stringCache:{},setErrorCode:function(code){EGL.errorCode=code},chooseConfig:function(display,attribList,config,config_size,numConfigs){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(attribList){for(;;){var param=HEAP32[attribList>>2];if(param==12321){var alphaSize=HEAP32[attribList+4>>2];EGL.contextAttributes.alpha=alphaSize>0}else if(param==12325){var depthSize=HEAP32[attribList+4>>2];EGL.contextAttributes.depth=depthSize>0}else if(param==12326){var stencilSize=HEAP32[attribList+4>>2];EGL.contextAttributes.stencil=stencilSize>0}else if(param==12337){var samples=HEAP32[attribList+4>>2];EGL.contextAttributes.antialias=samples>0}else if(param==12338){var samples=HEAP32[attribList+4>>2];EGL.contextAttributes.antialias=samples==1}else if(param==12544){var requestedPriority=HEAP32[attribList+4>>2];EGL.contextAttributes.lowLatency=requestedPriority!=12547}else if(param==12344){break}attribList+=8}}if((!config||!config_size)&&!numConfigs){EGL.setErrorCode(12300);return 0}if(numConfigs){HEAP32[numConfigs>>2]=1}if(config&&config_size>0){HEAP32[config>>2]=62002}EGL.setErrorCode(12288);return 1}};Module["EGL"]=EGL;function _eglGetDisplay(nativeDisplayType){EGL.setErrorCode(12288);return 62e3}Module["_eglGetDisplay"]=_eglGetDisplay;_eglGetDisplay.sig="ii";function _eglInitialize(display,majorVersion,minorVersion){if(display==62e3){if(majorVersion){HEAP32[majorVersion>>2]=1}if(minorVersion){HEAP32[minorVersion>>2]=4}EGL.defaultDisplayInitialized=true;EGL.setErrorCode(12288);return 1}else{EGL.setErrorCode(12296);return 0}}Module["_eglInitialize"]=_eglInitialize;_eglInitialize.sig="iiii";function _eglTerminate(display){if(display!=62e3){EGL.setErrorCode(12296);return 0}EGL.currentContext=0;EGL.currentReadSurface=0;EGL.currentDrawSurface=0;EGL.defaultDisplayInitialized=false;EGL.setErrorCode(12288);return 1}Module["_eglTerminate"]=_eglTerminate;_eglTerminate.sig="ii";function _eglGetConfigs(display,configs,config_size,numConfigs){return EGL.chooseConfig(display,0,configs,config_size,numConfigs)}Module["_eglGetConfigs"]=_eglGetConfigs;_eglGetConfigs.sig="iiiii";function _eglChooseConfig(display,attrib_list,configs,config_size,numConfigs){return EGL.chooseConfig(display,attrib_list,configs,config_size,numConfigs)}Module["_eglChooseConfig"]=_eglChooseConfig;_eglChooseConfig.sig="iiiiii";function _eglGetConfigAttrib(display,config,attribute,value){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(config!=62002){EGL.setErrorCode(12293);return 0}if(!value){EGL.setErrorCode(12300);return 0}EGL.setErrorCode(12288);switch(attribute){case 12320:HEAP32[value>>2]=EGL.contextAttributes.alpha?32:24;return 1;case 12321:HEAP32[value>>2]=EGL.contextAttributes.alpha?8:0;return 1;case 12322:HEAP32[value>>2]=8;return 1;case 12323:HEAP32[value>>2]=8;return 1;case 12324:HEAP32[value>>2]=8;return 1;case 12325:HEAP32[value>>2]=EGL.contextAttributes.depth?24:0;return 1;case 12326:HEAP32[value>>2]=EGL.contextAttributes.stencil?8:0;return 1;case 12327:HEAP32[value>>2]=12344;return 1;case 12328:HEAP32[value>>2]=62002;return 1;case 12329:HEAP32[value>>2]=0;return 1;case 12330:HEAP32[value>>2]=4096;return 1;case 12331:HEAP32[value>>2]=16777216;return 1;case 12332:HEAP32[value>>2]=4096;return 1;case 12333:HEAP32[value>>2]=0;return 1;case 12334:HEAP32[value>>2]=0;return 1;case 12335:HEAP32[value>>2]=12344;return 1;case 12337:HEAP32[value>>2]=EGL.contextAttributes.antialias?4:0;return 1;case 12338:HEAP32[value>>2]=EGL.contextAttributes.antialias?1:0;return 1;case 12339:HEAP32[value>>2]=4;return 1;case 12340:HEAP32[value>>2]=12344;return 1;case 12341:case 12342:case 12343:HEAP32[value>>2]=-1;return 1;case 12345:case 12346:HEAP32[value>>2]=0;return 1;case 12347:HEAP32[value>>2]=0;return 1;case 12348:HEAP32[value>>2]=1;return 1;case 12349:case 12350:HEAP32[value>>2]=0;return 1;case 12351:HEAP32[value>>2]=12430;return 1;case 12352:HEAP32[value>>2]=4;return 1;case 12354:HEAP32[value>>2]=0;return 1;default:EGL.setErrorCode(12292);return 0}}Module["_eglGetConfigAttrib"]=_eglGetConfigAttrib;_eglGetConfigAttrib.sig="iiiii";function _eglCreateWindowSurface(display,config,win,attrib_list){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(config!=62002){EGL.setErrorCode(12293);return 0}EGL.setErrorCode(12288);return 62006}Module["_eglCreateWindowSurface"]=_eglCreateWindowSurface;_eglCreateWindowSurface.sig="iiiii";function _eglDestroySurface(display,surface){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(surface!=62006){EGL.setErrorCode(12301);return 1}if(EGL.currentReadSurface==surface){EGL.currentReadSurface=0}if(EGL.currentDrawSurface==surface){EGL.currentDrawSurface=0}EGL.setErrorCode(12288);return 1}Module["_eglDestroySurface"]=_eglDestroySurface;_eglDestroySurface.sig="iii";function _eglCreateContext(display,config,hmm,contextAttribs){if(display!=62e3){EGL.setErrorCode(12296);return 0}var glesContextVersion=1;for(;;){var param=HEAP32[contextAttribs>>2];if(param==12440){glesContextVersion=HEAP32[contextAttribs+4>>2]}else if(param==12344){break}else{EGL.setErrorCode(12292);return 0}contextAttribs+=8}if(glesContextVersion!=2){EGL.setErrorCode(12293);return 0}EGL.contextAttributes.majorVersion=glesContextVersion-1;EGL.contextAttributes.minorVersion=0;EGL.context=GL.createContext(Module["canvas"],EGL.contextAttributes);if(EGL.context!=0){EGL.setErrorCode(12288);GL.makeContextCurrent(EGL.context);Module.useWebGL=true;Browser.moduleContextCreatedCallbacks.forEach(function(callback){callback()});GL.makeContextCurrent(null);return 62004}else{EGL.setErrorCode(12297);return 0}}Module["_eglCreateContext"]=_eglCreateContext;_eglCreateContext.sig="iiiii";function _eglDestroyContext(display,context){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(context!=62004){EGL.setErrorCode(12294);return 0}GL.deleteContext(EGL.context);EGL.setErrorCode(12288);if(EGL.currentContext==context){EGL.currentContext=0}return 1}Module["_eglDestroyContext"]=_eglDestroyContext;_eglDestroyContext.sig="iii";function _eglQuerySurface(display,surface,attribute,value){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(surface!=62006){EGL.setErrorCode(12301);return 0}if(!value){EGL.setErrorCode(12300);return 0}EGL.setErrorCode(12288);switch(attribute){case 12328:HEAP32[value>>2]=62002;return 1;case 12376:return 1;case 12375:HEAP32[value>>2]=Module["canvas"].width;return 1;case 12374:HEAP32[value>>2]=Module["canvas"].height;return 1;case 12432:HEAP32[value>>2]=-1;return 1;case 12433:HEAP32[value>>2]=-1;return 1;case 12434:HEAP32[value>>2]=-1;return 1;case 12422:HEAP32[value>>2]=12420;return 1;case 12441:HEAP32[value>>2]=12442;return 1;case 12435:HEAP32[value>>2]=12437;return 1;case 12416:case 12417:case 12418:case 12419:return 1;default:EGL.setErrorCode(12292);return 0}}Module["_eglQuerySurface"]=_eglQuerySurface;_eglQuerySurface.sig="iiiii";function _eglQueryContext(display,context,attribute,value){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(context!=62004){EGL.setErrorCode(12294);return 0}if(!value){EGL.setErrorCode(12300);return 0}EGL.setErrorCode(12288);switch(attribute){case 12328:HEAP32[value>>2]=62002;return 1;case 12439:HEAP32[value>>2]=12448;return 1;case 12440:HEAP32[value>>2]=EGL.contextAttributes.majorVersion+1;return 1;case 12422:HEAP32[value>>2]=12420;return 1;default:EGL.setErrorCode(12292);return 0}}Module["_eglQueryContext"]=_eglQueryContext;_eglQueryContext.sig="iiiii";function _eglGetError(){return EGL.errorCode}Module["_eglGetError"]=_eglGetError;_eglGetError.sig="i";function _eglQueryString(display,name){if(display!=62e3){EGL.setErrorCode(12296);return 0}EGL.setErrorCode(12288);if(EGL.stringCache[name])return EGL.stringCache[name];var ret;switch(name){case 12371:ret=allocateUTF8("Emscripten");break;case 12372:ret=allocateUTF8("1.4 Emscripten EGL");break;case 12373:ret=allocateUTF8("");break;case 12429:ret=allocateUTF8("OpenGL_ES");break;default:EGL.setErrorCode(12300);return 0}EGL.stringCache[name]=ret;return ret}Module["_eglQueryString"]=_eglQueryString;_eglQueryString.sig="iii";function _eglBindAPI(api){if(api==12448){EGL.setErrorCode(12288);return 1}else{EGL.setErrorCode(12300);return 0}}Module["_eglBindAPI"]=_eglBindAPI;_eglBindAPI.sig="ii";function _eglQueryAPI(){EGL.setErrorCode(12288);return 12448}Module["_eglQueryAPI"]=_eglQueryAPI;_eglQueryAPI.sig="i";function _eglWaitClient(){EGL.setErrorCode(12288);return 1}Module["_eglWaitClient"]=_eglWaitClient;_eglWaitClient.sig="i";function _eglWaitNative(nativeEngineId){EGL.setErrorCode(12288);return 1}Module["_eglWaitNative"]=_eglWaitNative;_eglWaitNative.sig="ii";function _eglWaitGL(){return _eglWaitClient()}Module["_eglWaitGL"]=_eglWaitGL;_eglWaitGL.sig="i";function _eglSwapInterval(display,interval){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(interval==0)_emscripten_set_main_loop_timing(0,0);else _emscripten_set_main_loop_timing(1,interval);EGL.setErrorCode(12288);return 1}Module["_eglSwapInterval"]=_eglSwapInterval;_eglSwapInterval.sig="iii";function _eglMakeCurrent(display,draw,read,context){if(display!=62e3){EGL.setErrorCode(12296);return 0}if(context!=0&&context!=62004){EGL.setErrorCode(12294);return 0}if(read!=0&&read!=62006||draw!=0&&draw!=62006){EGL.setErrorCode(12301);return 0}GL.makeContextCurrent(context?EGL.context:null);EGL.currentContext=context;EGL.currentDrawSurface=draw;EGL.currentReadSurface=read;EGL.setErrorCode(12288);return 1}Module["_eglMakeCurrent"]=_eglMakeCurrent;_eglMakeCurrent.sig="iiiii";function _eglGetCurrentContext(){return EGL.currentContext}Module["_eglGetCurrentContext"]=_eglGetCurrentContext;_eglGetCurrentContext.sig="i";function _eglGetCurrentSurface(readdraw){if(readdraw==12378){return EGL.currentReadSurface}else if(readdraw==12377){return EGL.currentDrawSurface}else{EGL.setErrorCode(12300);return 0}}Module["_eglGetCurrentSurface"]=_eglGetCurrentSurface;_eglGetCurrentSurface.sig="ii";function _eglGetCurrentDisplay(){return EGL.currentContext?62e3:0}Module["_eglGetCurrentDisplay"]=_eglGetCurrentDisplay;_eglGetCurrentDisplay.sig="i";function _eglSwapBuffers(){if(!EGL.defaultDisplayInitialized){EGL.setErrorCode(12289)}else if(!Module.ctx){EGL.setErrorCode(12290)}else if(Module.ctx.isContextLost()){EGL.setErrorCode(12302)}else{EGL.setErrorCode(12288);return 1}return 0}Module["_eglSwapBuffers"]=_eglSwapBuffers;_eglSwapBuffers.sig="iii";function _eglReleaseThread(){EGL.currentContext=0;EGL.currentReadSurface=0;EGL.currentDrawSurface=0;EGL.setErrorCode(12288);return 1}Module["_eglReleaseThread"]=_eglReleaseThread;_eglReleaseThread.sig="i";var GLFW={WindowFromId:function(id){if(id<=0||!GLFW.windows)return null;return GLFW.windows[id-1]},joystickFunc:null,errorFunc:null,monitorFunc:null,active:null,windows:null,monitors:null,monitorString:null,versionString:null,initialTime:null,extensions:null,hints:null,defaultHints:{131073:0,131074:0,131075:1,131076:1,131077:1,135169:8,135170:8,135171:8,135172:8,135173:24,135174:8,135175:0,135176:0,135177:0,135178:0,135179:0,135180:0,135181:0,135182:0,135183:0,139265:196609,139266:1,139267:0,139268:0,139269:0,139270:0,139271:0,139272:0},DOMToGLFWKeyCode:function(keycode){switch(keycode){case 32:return 32;case 222:return 39;case 188:return 44;case 173:return 45;case 189:return 45;case 190:return 46;case 191:return 47;case 48:return 48;case 49:return 49;case 50:return 50;case 51:return 51;case 52:return 52;case 53:return 53;case 54:return 54;case 55:return 55;case 56:return 56;case 57:return 57;case 59:return 59;case 61:return 61;case 187:return 61;case 65:return 65;case 66:return 66;case 67:return 67;case 68:return 68;case 69:return 69;case 70:return 70;case 71:return 71;case 72:return 72;case 73:return 73;case 74:return 74;case 75:return 75;case 76:return 76;case 77:return 77;case 78:return 78;case 79:return 79;case 80:return 80;case 81:return 81;case 82:return 82;case 83:return 83;case 84:return 84;case 85:return 85;case 86:return 86;case 87:return 87;case 88:return 88;case 89:return 89;case 90:return 90;case 219:return 91;case 220:return 92;case 221:return 93;case 192:return 96;case 27:return 256+1;case 112:return 256+2;case 113:return 256+3;case 114:return 256+4;case 115:return 256+5;case 116:return 256+6;case 117:return 256+7;case 118:return 256+8;case 119:return 256+9;case 120:return 256+10;case 121:return 256+11;case 122:return 256+12;case 123:return 256+13;case 124:return 256+14;case 125:return 256+15;case 126:return 256+16;case 127:return 256+17;case 128:return 256+18;case 129:return 256+19;case 130:return 256+20;case 131:return 256+21;case 132:return 256+22;case 133:return 256+23;case 134:return 256+24;case 135:return 256+25;case 136:return 256+26;case 39:return 256+30;case 37:return 256+29;case 40:return 256+28;case 38:return 256+27;case 16:return 256+31;case 17:return 256+33;case 18:return 256+35;case 9:return 256+37;case 13:return 256+38;case 8:return 256+39;case 45:return 256+40;case 46:return 256+41;case 33:return 256+42;case 34:return 256+43;case 36:return 256+44;case 35:return 256+45;case 96:return 256+46;case 97:return 256+47;case 98:return 256+48;case 99:return 256+49;case 100:return 256+50;case 101:return 256+51;case 102:return 256+52;case 103:return 256+53;case 104:return 256+54;case 105:return 256+55;case 111:return 256+56;case 106:return 256+57;case 109:return 256+58;case 107:return 256+59;case 110:return 256+60;case 144:return 256+63;case 20:return 256+64;case 145:return 256+65;case 19:return 256+66;case 91:return 256+67;case 93:return 256+69;default:return-1}},getModBits:function(win){var mod=0;if(win.keys[340])mod|=1;if(win.keys[341])mod|=2;if(win.keys[342])mod|=4;if(win.keys[343])mod|=8;return mod},onKeyPress:function(event){if(!GLFW.active||!GLFW.active.charFunc)return;if(event.ctrlKey||event.metaKey)return;var charCode=event.charCode;if(charCode==0||charCode>=0&&charCode<=31)return;getWasmTableEntry(GLFW.active.charFunc)(charCode,1)},onKeyChanged:function(keyCode,status){if(!GLFW.active)return;var key=GLFW.DOMToGLFWKeyCode(keyCode);if(key==-1)return;GLFW.active.keys[key]=status;GLFW.active.domKeys[keyCode]=status;if(!GLFW.active.keyFunc)return;getWasmTableEntry(GLFW.active.keyFunc)(key,status)},onGamepadConnected:function(event){GLFW.refreshJoysticks()},onGamepadDisconnected:function(event){GLFW.refreshJoysticks()},onKeydown:function(event){GLFW.onKeyChanged(event.keyCode,1);if(event.keyCode===8||event.keyCode===9){event.preventDefault()}},onKeyup:function(event){GLFW.onKeyChanged(event.keyCode,0)},onBlur:function(event){if(!GLFW.active)return;for(var i=0;i0){if(eventButton==1){eventButton=2}else{eventButton=1}}return eventButton},onMouseenter:function(event){if(!GLFW.active)return;if(event.target!=Module["canvas"]||!GLFW.active.cursorEnterFunc)return},onMouseleave:function(event){if(!GLFW.active)return;if(event.target!=Module["canvas"]||!GLFW.active.cursorEnterFunc)return},onMouseButtonChanged:function(event,status){if(!GLFW.active)return;Browser.calculateMouseEvent(event);if(event.target!=Module["canvas"])return;var eventButton=GLFW.DOMToGLFWMouseButton(event);if(status==1){GLFW.active.buttons|=1<0?Math.max(delta,1):Math.min(delta,-1);GLFW.wheelPos+=delta;if(!GLFW.active||!GLFW.active.scrollFunc||event.target!=Module["canvas"])return;getWasmTableEntry(GLFW.active.scrollFunc)(GLFW.wheelPos);event.preventDefault()},onCanvasResize:function(width,height){if(!GLFW.active)return;var resizeNeeded=true;if(document["fullscreen"]||document["fullScreen"]||document["mozFullScreen"]||document["webkitIsFullScreen"]){GLFW.active.storedX=GLFW.active.x;GLFW.active.storedY=GLFW.active.y;GLFW.active.storedWidth=GLFW.active.width;GLFW.active.storedHeight=GLFW.active.height;GLFW.active.x=GLFW.active.y=0;GLFW.active.width=screen.width;GLFW.active.height=screen.height;GLFW.active.fullscreen=true}else if(GLFW.active.fullscreen==true){GLFW.active.x=GLFW.active.storedX;GLFW.active.y=GLFW.active.storedY;GLFW.active.width=GLFW.active.storedWidth;GLFW.active.height=GLFW.active.storedHeight;GLFW.active.fullscreen=false}else if(GLFW.active.width!=width||GLFW.active.height!=height){GLFW.active.width=width;GLFW.active.height=height}else{resizeNeeded=false}if(resizeNeeded){Browser.setCanvasSize(GLFW.active.width,GLFW.active.height,true);GLFW.onWindowSizeChanged();GLFW.onFramebufferSizeChanged()}},onWindowSizeChanged:function(){if(!GLFW.active)return;if(!GLFW.active.windowSizeFunc)return;callUserCallback(function(){getWasmTableEntry(GLFW.active.windowSizeFunc)(GLFW.active.width,GLFW.active.height)})},onFramebufferSizeChanged:function(){if(!GLFW.active)return;if(!GLFW.active.framebufferSizeFunc)return;callUserCallback(function(){})},getTime:function(){return _emscripten_get_now()/1e3},setWindowTitle:function(winid,title){var win=GLFW.WindowFromId(winid);if(!win)return;win.title=UTF8ToString(title);if(GLFW.active.id==win.id){document.title=win.title}},setJoystickCallback:function(cbfun){GLFW.joystickFunc=cbfun;GLFW.refreshJoysticks()},joys:{},lastGamepadState:[],lastGamepadStateFrame:null,refreshJoysticks:function(){if(Browser.mainLoop.currentFrameNumber!==GLFW.lastGamepadStateFrame||!Browser.mainLoop.currentFrameNumber){GLFW.lastGamepadState=navigator.getGamepads?navigator.getGamepads():navigator.webkitGetGamepads?navigator.webkitGetGamepads:[];GLFW.lastGamepadStateFrame=Browser.mainLoop.currentFrameNumber;for(var joy=0;joy>0]=gamepad.buttons[i].pressed}for(var i=0;i>2]=gamepad.axes[i]}}else{if(GLFW.joys[joy]){out("glfw joystick disconnected",joy);if(GLFW.joystickFunc){getWasmTableEntry(GLFW.joystickFunc)(joy,262146)}_free(GLFW.joys[joy].id);_free(GLFW.joys[joy].buttons);_free(GLFW.joys[joy].axes);delete GLFW.joys[joy]}}}}},setKeyCallback:function(winid,cbfun){var win=GLFW.WindowFromId(winid);if(!win)return null;var prevcbfun=win.keyFunc;win.keyFunc=cbfun;return prevcbfun},setCharCallback:function(winid,cbfun){var win=GLFW.WindowFromId(winid);if(!win)return null;var prevcbfun=win.charFunc;win.charFunc=cbfun;return prevcbfun},setMouseButtonCallback:function(winid,cbfun){var win=GLFW.WindowFromId(winid);if(!win)return null;var prevcbfun=win.mouseButtonFunc;win.mouseButtonFunc=cbfun;return prevcbfun},setCursorPosCallback:function(winid,cbfun){var win=GLFW.WindowFromId(winid);if(!win)return null;var prevcbfun=win.cursorPosFunc;win.cursorPosFunc=cbfun;return prevcbfun},setScrollCallback:function(winid,cbfun){var win=GLFW.WindowFromId(winid);if(!win)return null;var prevcbfun=win.scrollFunc;win.scrollFunc=cbfun;return prevcbfun},setDropCallback:function(winid,cbfun){var win=GLFW.WindowFromId(winid);if(!win)return null;var prevcbfun=win.dropFunc;win.dropFunc=cbfun;return prevcbfun},onDrop:function(event){if(!GLFW.active||!GLFW.active.dropFunc)return;if(!event.dataTransfer||!event.dataTransfer.files||event.dataTransfer.files.length==0)return;event.preventDefault();var filenames=_malloc(event.dataTransfer.files.length*4);var filenamesArray=[];var count=event.dataTransfer.files.length;var written=0;var drop_dir=".glfw_dropped_files";FS.createPath("/",drop_dir);function save(file){var path="/"+drop_dir+"/"+file.name.replace(/\//g,"_");var reader=new FileReader;reader.onloadend=e=>{if(reader.readyState!=2){++written;out("failed to read dropped file: "+file.name+": "+reader.error);return}var data=e.target.result;FS.writeFile(path,new Uint8Array(data));if(++written===count){getWasmTableEntry(GLFW.active.dropFunc)(GLFW.active.id,count,filenames);for(var i=0;i>2]=filename}for(var i=0;i0},getCursorPos:function(winid,x,y){HEAPF64[x>>3]=Browser.mouseX;HEAPF64[y>>3]=Browser.mouseY},getMousePos:function(winid,x,y){HEAP32[x>>2]=Browser.mouseX;HEAP32[y>>2]=Browser.mouseY},setCursorPos:function(winid,x,y){},getWindowPos:function(winid,x,y){var wx=0;var wy=0;var win=GLFW.WindowFromId(winid);if(win){wx=win.x;wy=win.y}if(x){HEAP32[x>>2]=wx}if(y){HEAP32[y>>2]=wy}},setWindowPos:function(winid,x,y){var win=GLFW.WindowFromId(winid);if(!win)return;win.x=x;win.y=y},getWindowSize:function(winid,width,height){var ww=0;var wh=0;var win=GLFW.WindowFromId(winid);if(win){ww=win.width;wh=win.height}if(width){HEAP32[width>>2]=ww}if(height){HEAP32[height>>2]=wh}},setWindowSize:function(winid,width,height){var win=GLFW.WindowFromId(winid);if(!win)return;if(GLFW.active.id==win.id){if(width==screen.width&&height==screen.height){Browser.requestFullscreen()}else{Browser.exitFullscreen();Browser.setCanvasSize(width,height);win.width=width;win.height=height}}if(!win.windowSizeFunc)return;getWasmTableEntry(win.windowSizeFunc)(width,height)},createWindow:function(width,height,title,monitor,share){var i,id;for(i=0;i0)throw"glfwCreateWindow only supports one window at time currently";id=i+1;if(width<=0||height<=0)return 0;if(monitor){Browser.requestFullscreen()}else{Browser.setCanvasSize(width,height)}for(i=0;i0;if(i==GLFW.windows.length){if(useWebGL){var contextAttributes={antialias:GLFW.hints[135181]>1,depth:GLFW.hints[135173]>0,stencil:GLFW.hints[135174]>0,alpha:GLFW.hints[135172]>0};Module.ctx=Browser.createContext(Module["canvas"],true,true,contextAttributes)}else{Browser.init()}}if(!Module.ctx&&useWebGL)return 0;var win=new GLFW_Window(id,width,height,title,monitor,share);if(id-1==GLFW.windows.length){GLFW.windows.push(win)}else{GLFW.windows[id-1]=win}GLFW.active=win;return win.id},destroyWindow:function(winid){var win=GLFW.WindowFromId(winid);if(!win)return;GLFW.windows[win.id-1]=null;if(GLFW.active.id==win.id)GLFW.active=null;for(var i=0;i>2]=2;HEAP32[minor>>2]=7;HEAP32[rev>>2]=7}Module["_glfwGetVersion"]=_glfwGetVersion;_glfwGetVersion.sig="viii";function _glfwPollEvents(){}Module["_glfwPollEvents"]=_glfwPollEvents;_glfwPollEvents.sig="v";function _glfwWaitEvents(){}Module["_glfwWaitEvents"]=_glfwWaitEvents;_glfwWaitEvents.sig="v";function _glfwGetTime(){return GLFW.getTime()-GLFW.initialTime}Module["_glfwGetTime"]=_glfwGetTime;_glfwGetTime.sig="d";function _glfwSetTime(time){GLFW.initialTime=GLFW.getTime()-time}Module["_glfwSetTime"]=_glfwSetTime;_glfwSetTime.sig="vd";function _glfwExtensionSupported(extension){if(!GLFW.extensions){GLFW.extensions=UTF8ToString(_glGetString(7939)).split(" ")}if(GLFW.extensions.includes(extension))return 1;return GLFW.extensions.includes("GL_"+extension)}Module["_glfwExtensionSupported"]=_glfwExtensionSupported;_glfwExtensionSupported.sig="ii";function _glfwSwapInterval(interval){interval=Math.abs(interval);if(interval==0)_emscripten_set_main_loop_timing(0,0);else _emscripten_set_main_loop_timing(1,interval)}Module["_glfwSwapInterval"]=_glfwSwapInterval;_glfwSwapInterval.sig="vi";function _glfwOpenWindow(width,height,redbits,greenbits,bluebits,alphabits,depthbits,stencilbits,mode){GLFW.hints[135169]=redbits;GLFW.hints[135170]=greenbits;GLFW.hints[135171]=bluebits;GLFW.hints[135172]=alphabits;GLFW.hints[135173]=depthbits;GLFW.hints[135174]=stencilbits;GLFW.createWindow(width,height,"GLFW2 Window",0,0);return 1}Module["_glfwOpenWindow"]=_glfwOpenWindow;function _glfwCloseWindow(){GLFW.destroyWindow(GLFW.active.id)}Module["_glfwCloseWindow"]=_glfwCloseWindow;function _glfwOpenWindowHint(target,hint){target=GLFW.GLFW2ParamToGLFW3Param(target);GLFW.hints[target]=hint}Module["_glfwOpenWindowHint"]=_glfwOpenWindowHint;function _glfwGetWindowSize(width,height){GLFW.getWindowSize(GLFW.active.id,width,height)}Module["_glfwGetWindowSize"]=_glfwGetWindowSize;function _glfwSetWindowSize(width,height){GLFW.setWindowSize(GLFW.active.id,width,height)}Module["_glfwSetWindowSize"]=_glfwSetWindowSize;function _glfwGetWindowPos(x,y){GLFW.getWindowPos(GLFW.active.id,x,y)}Module["_glfwGetWindowPos"]=_glfwGetWindowPos;function _glfwSetWindowPos(x,y){GLFW.setWindowPos(GLFW.active.id,x,y)}Module["_glfwSetWindowPos"]=_glfwSetWindowPos;function _glfwSetWindowTitle(title){GLFW.setWindowTitle(GLFW.active.id,title)}Module["_glfwSetWindowTitle"]=_glfwSetWindowTitle;function _glfwIconifyWindow(){}Module["_glfwIconifyWindow"]=_glfwIconifyWindow;function _glfwRestoreWindow(){}Module["_glfwRestoreWindow"]=_glfwRestoreWindow;function _glfwSwapBuffers(){GLFW.swapBuffers(GLFW.active.id)}Module["_glfwSwapBuffers"]=_glfwSwapBuffers;function _glfwGetWindowParam(param){param=GLFW.GLFW2ParamToGLFW3Param(param);return GLFW.hints[param]}Module["_glfwGetWindowParam"]=_glfwGetWindowParam;function _glfwSetWindowSizeCallback(cbfun){GLFW.setWindowSizeCallback(GLFW.active.id,cbfun)}Module["_glfwSetWindowSizeCallback"]=_glfwSetWindowSizeCallback;function _glfwSetWindowCloseCallback(cbfun){GLFW.setWindowCloseCallback(GLFW.active.id,cbfun)}Module["_glfwSetWindowCloseCallback"]=_glfwSetWindowCloseCallback;function _glfwSetWindowRefreshCallback(cbfun){GLFW.setWindowRefreshCallback(GLFW.active.id,cbfun)}Module["_glfwSetWindowRefreshCallback"]=_glfwSetWindowRefreshCallback;function _glfwGetKey(key){return GLFW.getKey(GLFW.active.id,key)}Module["_glfwGetKey"]=_glfwGetKey;function _glfwGetMouseButton(button){return GLFW.getMouseButton(GLFW.active.id,button)}Module["_glfwGetMouseButton"]=_glfwGetMouseButton;function _glfwGetMousePos(x,y){GLFW.getMousePos(GLFW.active.id,x,y)}Module["_glfwGetMousePos"]=_glfwGetMousePos;function _glfwSetMousePos(x,y){GLFW.setCursorPos(GLFW.active.id,x,y)}Module["_glfwSetMousePos"]=_glfwSetMousePos;function _glfwGetMouseWheel(){return 0}Module["_glfwGetMouseWheel"]=_glfwGetMouseWheel;function _glfwSetMouseWheel(pos){}Module["_glfwSetMouseWheel"]=_glfwSetMouseWheel;function _glfwSetKeyCallback(cbfun){GLFW.setKeyCallback(GLFW.active.id,cbfun)}Module["_glfwSetKeyCallback"]=_glfwSetKeyCallback;function _glfwSetCharCallback(cbfun){GLFW.setCharCallback(GLFW.active.id,cbfun)}Module["_glfwSetCharCallback"]=_glfwSetCharCallback;function _glfwSetMouseButtonCallback(cbfun){GLFW.setMouseButtonCallback(GLFW.active.id,cbfun)}Module["_glfwSetMouseButtonCallback"]=_glfwSetMouseButtonCallback;function _glfwSetMousePosCallback(cbfun){GLFW.setCursorPosCallback(GLFW.active.id,cbfun)}Module["_glfwSetMousePosCallback"]=_glfwSetMousePosCallback;function _glfwSetMouseWheelCallback(cbfun){GLFW.setScrollCallback(GLFW.active.id,cbfun)}Module["_glfwSetMouseWheelCallback"]=_glfwSetMouseWheelCallback;function _glfwGetDesktopMode(mode){throw"glfwGetDesktopMode is not implemented."}Module["_glfwGetDesktopMode"]=_glfwGetDesktopMode;function _glfwSleep(time){_sleep(time)}Module["_glfwSleep"]=_glfwSleep;function _glfwEnable(target){target=GLFW.GLFW2ParamToGLFW3Param(target);GLFW.hints[target]=false}Module["_glfwEnable"]=_glfwEnable;function _glfwDisable(target){target=GLFW.GLFW2ParamToGLFW3Param(target);GLFW.hints[target]=true}Module["_glfwDisable"]=_glfwDisable;function _glfwGetGLVersion(major,minor,rev){HEAP32[major>>2]=0;HEAP32[minor>>2]=0;HEAP32[rev>>2]=1}Module["_glfwGetGLVersion"]=_glfwGetGLVersion;function _glfwCreateThread(fun,arg){getWasmTableEntry(fun)(arg);return 0}Module["_glfwCreateThread"]=_glfwCreateThread;function _glfwDestroyThread(ID){}Module["_glfwDestroyThread"]=_glfwDestroyThread;function _glfwWaitThread(ID,waitmode){}Module["_glfwWaitThread"]=_glfwWaitThread;function _glfwGetThreadID(){return 0}Module["_glfwGetThreadID"]=_glfwGetThreadID;function _glfwCreateMutex(){throw"glfwCreateMutex is not implemented."}Module["_glfwCreateMutex"]=_glfwCreateMutex;function _glfwDestroyMutex(mutex){throw"glfwDestroyMutex is not implemented."}Module["_glfwDestroyMutex"]=_glfwDestroyMutex;function _glfwLockMutex(mutex){throw"glfwLockMutex is not implemented."}Module["_glfwLockMutex"]=_glfwLockMutex;function _glfwUnlockMutex(mutex){throw"glfwUnlockMutex is not implemented."}Module["_glfwUnlockMutex"]=_glfwUnlockMutex;function _glfwCreateCond(){throw"glfwCreateCond is not implemented."}Module["_glfwCreateCond"]=_glfwCreateCond;function _glfwDestroyCond(cond){throw"glfwDestroyCond is not implemented."}Module["_glfwDestroyCond"]=_glfwDestroyCond;function _glfwWaitCond(cond,mutex,timeout){throw"glfwWaitCond is not implemented."}Module["_glfwWaitCond"]=_glfwWaitCond;function _glfwSignalCond(cond){throw"glfwSignalCond is not implemented."}Module["_glfwSignalCond"]=_glfwSignalCond;function _glfwBroadcastCond(cond){throw"glfwBroadcastCond is not implemented."}Module["_glfwBroadcastCond"]=_glfwBroadcastCond;function _glfwGetNumberOfProcessors(){return 1}Module["_glfwGetNumberOfProcessors"]=_glfwGetNumberOfProcessors;function _glfwReadImage(name,img,flags){throw"glfwReadImage is not implemented."}Module["_glfwReadImage"]=_glfwReadImage;function _glfwReadMemoryImage(data,size,img,flags){throw"glfwReadMemoryImage is not implemented."}Module["_glfwReadMemoryImage"]=_glfwReadMemoryImage;function _glfwFreeImage(img){throw"glfwFreeImage is not implemented."}Module["_glfwFreeImage"]=_glfwFreeImage;function _glfwLoadTexture2D(name,flags){throw"glfwLoadTexture2D is not implemented."}Module["_glfwLoadTexture2D"]=_glfwLoadTexture2D;function _glfwLoadMemoryTexture2D(data,size,flags){throw"glfwLoadMemoryTexture2D is not implemented."}Module["_glfwLoadMemoryTexture2D"]=_glfwLoadMemoryTexture2D;function _glfwLoadTextureImage2D(img,flags){throw"glfwLoadTextureImage2D is not implemented."}Module["_glfwLoadTextureImage2D"]=_glfwLoadTextureImage2D;function _uuid_clear(uu){zeroMemory(uu,16)}Module["_uuid_clear"]=_uuid_clear;function _uuid_compare(uu1,uu2){return _memcmp(uu1,uu2,16)}Module["_uuid_compare"]=_uuid_compare;function _uuid_copy(dst,src){_memcpy(dst,src,16)}Module["_uuid_copy"]=_uuid_copy;function _uuid_generate(out){var uuid=null;if(ENVIRONMENT_IS_NODE){try{var rb=require("crypto")["randomBytes"];uuid=rb(16)}catch(e){}}else if(ENVIRONMENT_IS_WEB&&typeof window.crypto!="undefined"&&typeof window.crypto.getRandomValues!="undefined"){uuid=new Uint8Array(16);window.crypto.getRandomValues(uuid)}if(!uuid){uuid=new Array(16);var d=(new Date).getTime();for(var i=0;i<16;i++){var r=(d+Math.random()*256)%256|0;d=d/256|0;uuid[i]=r}}uuid[6]=uuid[6]&15|64;uuid[8]=uuid[8]&63|128;writeArrayToMemory(uuid,out)}Module["_uuid_generate"]=_uuid_generate;function _uuid_is_null(uu){for(var i=0;i<4;i++,uu=uu+4|0){var val=HEAP32[uu>>2];if(val){return 0}}return 1}Module["_uuid_is_null"]=_uuid_is_null;function _uuid_parse(inp,uu){inp=UTF8ToString(inp);if(inp.length===36){var i=0;var uuid=new Array(16);inp.toLowerCase().replace(/[0-9a-f]{2}/g,function(byte){if(i<16){uuid[i++]=parseInt(byte,16)}});if(i<16){return-1}else{writeArrayToMemory(uuid,uu);return 0}}else{return-1}}Module["_uuid_parse"]=_uuid_parse;function _uuid_unparse(uu,out,upper){var i=0;var uuid="xxxx-xx-xx-xx-xxxxxx".replace(/[x]/g,function(c){var r=upper?HEAPU8[uu+i>>0].toString(16).toUpperCase():HEAPU8[uu+i>>0].toString(16);r=r.length===1?"0"+r:r;i++;return r});stringToUTF8(uuid,out,37)}Module["_uuid_unparse"]=_uuid_unparse;function _uuid_unparse_lower(uu,out){_uuid_unparse(uu,out)}Module["_uuid_unparse_lower"]=_uuid_unparse_lower;function _uuid_unparse_upper(uu,out){_uuid_unparse(uu,out,true)}Module["_uuid_unparse_upper"]=_uuid_unparse_upper;function _uuid_type(uu){return 4}Module["_uuid_type"]=_uuid_type;function _uuid_variant(uu){return 1}Module["_uuid_variant"]=_uuid_variant;var GLEW={isLinaroFork:1,extensions:null,error:{0:null,1:null,2:null,3:null,4:null,5:null,6:null,7:null,8:null},version:{1:null,2:null,3:null,4:null},errorStringConstantFromCode:function(error){if(GLEW.isLinaroFork){switch(error){case 4:return"OpenGL ES lib expected, found OpenGL lib";case 5:return"OpenGL lib expected, found OpenGL ES lib";case 6:return"Missing EGL version";case 7:return"EGL 1.1 and up are supported";default:break}}switch(error){case 0:return"No error";case 1:return"Missing GL version";case 2:return"GL 1.1 and up are supported";case 3:return"GLX 1.2 and up are supported";default:return null}},errorString:function(error){if(!GLEW.error[error]){var string=GLEW.errorStringConstantFromCode(error);if(!string){string="Unknown error";error=8}GLEW.error[error]=allocateUTF8(string)}return GLEW.error[error]},versionStringConstantFromCode:function(name){switch(name){case 1:return"1.10.0";case 2:return"1";case 3:return"10";case 4:return"0";default:return null}},versionString:function(name){if(!GLEW.version[name]){var string=GLEW.versionStringConstantFromCode(name);if(!string)return 0;GLEW.version[name]=allocateUTF8(string)}return GLEW.version[name]},extensionIsSupported:function(name){if(!GLEW.extensions){GLEW.extensions=UTF8ToString(_glGetString(7939)).split(" ")}if(GLEW.extensions.includes(name))return 1;return GLEW.extensions.includes("GL_"+name)}};Module["GLEW"]=GLEW;function _glewInit(){return 0}Module["_glewInit"]=_glewInit;function _glewIsSupported(name){var exts=UTF8ToString(name).split(" ");for(var i=0;i0)};req.onerror=function(error){callback(error)}})}};Module["IDBStore"]=IDBStore;function _emscripten_idb_async_load(db,id,arg,onload,onerror){IDBStore.getFile(UTF8ToString(db),UTF8ToString(id),function(error,byteArray){if(error){if(onerror)getWasmTableEntry(onerror)(arg);return}var buffer=_malloc(byteArray.length);HEAPU8.set(byteArray,buffer);getWasmTableEntry(onload)(arg,buffer,byteArray.length);_free(buffer)})}Module["_emscripten_idb_async_load"]=_emscripten_idb_async_load;_emscripten_idb_async_load.sig="vppppp";function _emscripten_idb_async_store(db,id,ptr,num,arg,onstore,onerror){IDBStore.setFile(UTF8ToString(db),UTF8ToString(id),new Uint8Array(HEAPU8.subarray(ptr,ptr+num)),function(error){if(error){if(onerror)getWasmTableEntry(onerror)(arg);return}if(onstore)getWasmTableEntry(onstore)(arg)})}Module["_emscripten_idb_async_store"]=_emscripten_idb_async_store;_emscripten_idb_async_store.sig="vpppippp";function _emscripten_idb_async_delete(db,id,arg,ondelete,onerror){IDBStore.deleteFile(UTF8ToString(db),UTF8ToString(id),function(error){if(error){if(onerror)getWasmTableEntry(onerror)(arg);return}if(ondelete)getWasmTableEntry(ondelete)(arg)})}Module["_emscripten_idb_async_delete"]=_emscripten_idb_async_delete;_emscripten_idb_async_delete.sig="vppppp";function _emscripten_idb_async_exists(db,id,arg,oncheck,onerror){IDBStore.existsFile(UTF8ToString(db),UTF8ToString(id),function(error,exists){if(error){if(onerror)getWasmTableEntry(onerror)(arg);return}if(oncheck)getWasmTableEntry(oncheck)(arg,exists)})}Module["_emscripten_idb_async_exists"]=_emscripten_idb_async_exists;_emscripten_idb_async_exists.sig="vppppp";function _emscripten_idb_load(){throw"Please compile your program with async support in order to use synchronous operations like emscripten_idb_load, etc."}Module["_emscripten_idb_load"]=_emscripten_idb_load;function _emscripten_idb_store(){throw"Please compile your program with async support in order to use synchronous operations like emscripten_idb_store, etc."}Module["_emscripten_idb_store"]=_emscripten_idb_store;function _emscripten_idb_delete(){throw"Please compile your program with async support in order to use synchronous operations like emscripten_idb_delete, etc."}Module["_emscripten_idb_delete"]=_emscripten_idb_delete;function _emscripten_idb_exists(){throw"Please compile your program with async support in order to use synchronous operations like emscripten_idb_exists, etc."}Module["_emscripten_idb_exists"]=_emscripten_idb_exists;function runAndAbortIfError(func){try{return func()}catch(e){abort(e)}}Module["runAndAbortIfError"]=runAndAbortIfError;function _emscripten_sleep(){throw"Please compile your program with async support in order to use asynchronous operations like emscripten_sleep"}Module["_emscripten_sleep"]=_emscripten_sleep;function _emscripten_wget(){throw"Please compile your program with async support in order to use asynchronous operations like emscripten_wget"}Module["_emscripten_wget"]=_emscripten_wget;function _emscripten_wget_data(){throw"Please compile your program with async support in order to use asynchronous operations like emscripten_wget_data"}Module["_emscripten_wget_data"]=_emscripten_wget_data;function _emscripten_scan_registers(){throw"Please compile your program with async support in order to use asynchronous operations like emscripten_scan_registers"}Module["_emscripten_scan_registers"]=_emscripten_scan_registers;function _emscripten_fiber_init(){throw"Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_init"}Module["_emscripten_fiber_init"]=_emscripten_fiber_init;function _emscripten_fiber_init_from_current_context(){throw"Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_init_from_current_context"}Module["_emscripten_fiber_init_from_current_context"]=_emscripten_fiber_init_from_current_context;function _emscripten_fiber_swap(){throw"Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_swap"}Module["_emscripten_fiber_swap"]=_emscripten_fiber_swap;function _emscripten_is_main_browser_thread(){return!ENVIRONMENT_IS_WORKER}Module["_emscripten_is_main_browser_thread"]=_emscripten_is_main_browser_thread;var WS={sockets:[null],socketEvent:null};Module["WS"]=WS;function _emscripten_websocket_get_ready_state(socketId,readyState){var socket=WS.sockets[socketId];if(!socket){return-3}HEAP16[readyState>>1]=socket.readyState;return 0}Module["_emscripten_websocket_get_ready_state"]=_emscripten_websocket_get_ready_state;_emscripten_websocket_get_ready_state.sig="iii";function _emscripten_websocket_get_buffered_amount(socketId,bufferedAmount){var socket=WS.sockets[socketId];if(!socket){return-3}HEAP64[bufferedAmount>>3]=BigInt(socket.bufferedAmount);return 0}Module["_emscripten_websocket_get_buffered_amount"]=_emscripten_websocket_get_buffered_amount;_emscripten_websocket_get_buffered_amount.sig="iii";function _emscripten_websocket_get_extensions(socketId,extensions,extensionsLength){var socket=WS.sockets[socketId];if(!socket){return-3}if(!extensions)return-5;stringToUTF8(socket.extensions,extensions,extensionsLength);return 0}Module["_emscripten_websocket_get_extensions"]=_emscripten_websocket_get_extensions;_emscripten_websocket_get_extensions.sig="iiii";function _emscripten_websocket_get_extensions_length(socketId,extensionsLength){var socket=WS.sockets[socketId];if(!socket){return-3}if(!extensionsLength)return-5;HEAP32[extensionsLength>>2]=lengthBytesUTF8(socket.extensions)+1;return 0}Module["_emscripten_websocket_get_extensions_length"]=_emscripten_websocket_get_extensions_length;_emscripten_websocket_get_extensions_length.sig="iii";function _emscripten_websocket_get_protocol(socketId,protocol,protocolLength){var socket=WS.sockets[socketId];if(!socket){return-3}if(!protocol)return-5;stringToUTF8(socket.protocol,protocol,protocolLength);return 0}Module["_emscripten_websocket_get_protocol"]=_emscripten_websocket_get_protocol;_emscripten_websocket_get_protocol.sig="iiii";function _emscripten_websocket_get_protocol_length(socketId,protocolLength){var socket=WS.sockets[socketId];if(!socket){return-3}if(!protocolLength)return-5;HEAP32[protocolLength>>2]=lengthBytesUTF8(socket.protocol)+1;return 0}Module["_emscripten_websocket_get_protocol_length"]=_emscripten_websocket_get_protocol_length;_emscripten_websocket_get_protocol_length.sig="iii";function _emscripten_websocket_get_url(socketId,url,urlLength){var socket=WS.sockets[socketId];if(!socket){return-3}if(!url)return-5;stringToUTF8(socket.url,url,urlLength);return 0}Module["_emscripten_websocket_get_url"]=_emscripten_websocket_get_url;_emscripten_websocket_get_url.sig="iiii";function _emscripten_websocket_get_url_length(socketId,urlLength){var socket=WS.sockets[socketId];if(!socket){return-3}if(!urlLength)return-5;HEAP32[urlLength>>2]=lengthBytesUTF8(socket.url)+1;return 0}Module["_emscripten_websocket_get_url_length"]=_emscripten_websocket_get_url_length;_emscripten_websocket_get_url_length.sig="iii";function _emscripten_websocket_set_onopen_callback_on_thread(socketId,userData,callbackFunc,thread){if(!WS.socketEvent)WS.socketEvent=_malloc(1024);var socket=WS.sockets[socketId];if(!socket){return-3}socket.onopen=function(e){HEAPU32[WS.socketEvent>>2]=socketId;getWasmTableEntry(callbackFunc)(0,WS.socketEvent,userData)};return 0}Module["_emscripten_websocket_set_onopen_callback_on_thread"]=_emscripten_websocket_set_onopen_callback_on_thread;_emscripten_websocket_set_onopen_callback_on_thread.sig="iiiii";function _emscripten_websocket_set_onerror_callback_on_thread(socketId,userData,callbackFunc,thread){if(!WS.socketEvent)WS.socketEvent=_malloc(1024);var socket=WS.sockets[socketId];if(!socket){return-3}socket.onerror=function(e){HEAPU32[WS.socketEvent>>2]=socketId;getWasmTableEntry(callbackFunc)(0,WS.socketEvent,userData)};return 0}Module["_emscripten_websocket_set_onerror_callback_on_thread"]=_emscripten_websocket_set_onerror_callback_on_thread;_emscripten_websocket_set_onerror_callback_on_thread.sig="iiiii";function _emscripten_websocket_set_onclose_callback_on_thread(socketId,userData,callbackFunc,thread){if(!WS.socketEvent)WS.socketEvent=_malloc(1024);var socket=WS.sockets[socketId];if(!socket){return-3}socket.onclose=function(e){HEAPU32[WS.socketEvent>>2]=socketId;HEAPU32[WS.socketEvent+4>>2]=e.wasClean;HEAPU32[WS.socketEvent+8>>2]=e.code;stringToUTF8(e.reason,WS.socketEvent+10,512);getWasmTableEntry(callbackFunc)(0,WS.socketEvent,userData)};return 0}Module["_emscripten_websocket_set_onclose_callback_on_thread"]=_emscripten_websocket_set_onclose_callback_on_thread;_emscripten_websocket_set_onclose_callback_on_thread.sig="iiiii";function _emscripten_websocket_set_onmessage_callback_on_thread(socketId,userData,callbackFunc,thread){if(!WS.socketEvent)WS.socketEvent=_malloc(1024);var socket=WS.sockets[socketId];if(!socket){return-3}socket.onmessage=function(e){HEAPU32[WS.socketEvent>>2]=socketId;if(typeof e.data=="string"){var len=lengthBytesUTF8(e.data)+1;var buf=_malloc(len);stringToUTF8(e.data,buf,len);HEAPU32[WS.socketEvent+12>>2]=1}else{var len=e.data.byteLength;var buf=_malloc(len);HEAP8.set(new Uint8Array(e.data),buf);HEAPU32[WS.socketEvent+12>>2]=0}HEAPU32[WS.socketEvent+4>>2]=buf;HEAPU32[WS.socketEvent+8>>2]=len;getWasmTableEntry(callbackFunc)(0,WS.socketEvent,userData);_free(buf)};return 0}Module["_emscripten_websocket_set_onmessage_callback_on_thread"]=_emscripten_websocket_set_onmessage_callback_on_thread;_emscripten_websocket_set_onmessage_callback_on_thread.sig="iiiii";function _emscripten_websocket_new(createAttributes){if(typeof WebSocket=="undefined"){return-1}if(!createAttributes){return-5}var createAttrs=createAttributes>>2;var url=UTF8ToString(HEAP32[createAttrs]);var protocols=HEAP32[createAttrs+1];var socket=protocols?new WebSocket(url,UTF8ToString(protocols).split(",")):new WebSocket(url);socket.binaryType="arraybuffer";var socketId=WS.sockets.length;WS.sockets[socketId]=socket;return socketId}Module["_emscripten_websocket_new"]=_emscripten_websocket_new;_emscripten_websocket_new.sig="ii";function _emscripten_websocket_send_utf8_text(socketId,textData){var socket=WS.sockets[socketId];if(!socket){return-3}var str=UTF8ToString(textData);socket.send(str);return 0}Module["_emscripten_websocket_send_utf8_text"]=_emscripten_websocket_send_utf8_text;_emscripten_websocket_send_utf8_text.sig="iii";function _emscripten_websocket_send_binary(socketId,binaryData,dataLength){var socket=WS.sockets[socketId];if(!socket){return-3}socket.send(HEAPU8.subarray(binaryData,binaryData+dataLength));return 0}Module["_emscripten_websocket_send_binary"]=_emscripten_websocket_send_binary;_emscripten_websocket_send_binary.sig="iiii";function _emscripten_websocket_close(socketId,code,reason){var socket=WS.sockets[socketId];if(!socket){return-3}var reasonStr=reason?UTF8ToString(reason):undefined;if(reason)socket.close(code||undefined,UTF8ToString(reason));else if(code)socket.close(code);else socket.close();return 0}Module["_emscripten_websocket_close"]=_emscripten_websocket_close;_emscripten_websocket_close.sig="iiii";function _emscripten_websocket_delete(socketId){var socket=WS.sockets[socketId];if(!socket){return-3}socket.onopen=socket.onerror=socket.onclose=socket.onmessage=null;delete WS.sockets[socketId];return 0}Module["_emscripten_websocket_delete"]=_emscripten_websocket_delete;_emscripten_websocket_delete.sig="ii";function _emscripten_websocket_is_supported(){return typeof WebSocket!="undefined"}Module["_emscripten_websocket_is_supported"]=_emscripten_websocket_is_supported;_emscripten_websocket_is_supported.sig="i";function _emscripten_websocket_deinitialize(){for(var i in WS.sockets){var socket=WS.sockets[i];if(socket){socket.close();_emscripten_websocket_delete(i)}}WS.sockets=[]}Module["_emscripten_websocket_deinitialize"]=_emscripten_websocket_deinitialize;_emscripten_websocket_deinitialize.sig="v";Module["requestFullscreen"]=function Module_requestFullscreen(lockPointer,resizeCanvas){Browser.requestFullscreen(lockPointer,resizeCanvas)};Module["requestAnimationFrame"]=function Module_requestAnimationFrame(func){Browser.requestAnimationFrame(func)};Module["setCanvasSize"]=function Module_setCanvasSize(width,height,noUpdates){Browser.setCanvasSize(width,height,noUpdates)};Module["pauseMainLoop"]=function Module_pauseMainLoop(){Browser.mainLoop.pause()};Module["resumeMainLoop"]=function Module_resumeMainLoop(){Browser.mainLoop.resume()};Module["getUserMedia"]=function Module_getUserMedia(){Browser.getUserMedia()};Module["createContext"]=function Module_createContext(canvas,useWebGL,setInModule,webGLContextAttributes){return Browser.createContext(canvas,useWebGL,setInModule,webGLContextAttributes)};var preloadedWasm={};var preloadedImages={};var preloadedAudios={};var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();Module["FS_createPath"]=FS.createPath;Module["FS_createDataFile"]=FS.createDataFile;Module["FS_createPreloadedFile"]=FS.createPreloadedFile;Module["FS_unlink"]=FS.unlink;Module["FS_createLazyFile"]=FS.createLazyFile;Module["FS_createDevice"]=FS.createDevice;if(ENVIRONMENT_IS_NODE){requireNodeFS();NODEFS.staticInit()}ERRNO_CODES={"EPERM":63,"ENOENT":44,"ESRCH":71,"EINTR":27,"EIO":29,"ENXIO":60,"E2BIG":1,"ENOEXEC":45,"EBADF":8,"ECHILD":12,"EAGAIN":6,"EWOULDBLOCK":6,"ENOMEM":48,"EACCES":2,"EFAULT":21,"ENOTBLK":105,"EBUSY":10,"EEXIST":20,"EXDEV":75,"ENODEV":43,"ENOTDIR":54,"EISDIR":31,"EINVAL":28,"ENFILE":41,"EMFILE":33,"ENOTTY":59,"ETXTBSY":74,"EFBIG":22,"ENOSPC":51,"ESPIPE":70,"EROFS":69,"EMLINK":34,"EPIPE":64,"EDOM":18,"ERANGE":68,"ENOMSG":49,"EIDRM":24,"ECHRNG":106,"EL2NSYNC":156,"EL3HLT":107,"EL3RST":108,"ELNRNG":109,"EUNATCH":110,"ENOCSI":111,"EL2HLT":112,"EDEADLK":16,"ENOLCK":46,"EBADE":113,"EBADR":114,"EXFULL":115,"ENOANO":104,"EBADRQC":103,"EBADSLT":102,"EDEADLOCK":16,"EBFONT":101,"ENOSTR":100,"ENODATA":116,"ETIME":117,"ENOSR":118,"ENONET":119,"ENOPKG":120,"EREMOTE":121,"ENOLINK":47,"EADV":122,"ESRMNT":123,"ECOMM":124,"EPROTO":65,"EMULTIHOP":36,"EDOTDOT":125,"EBADMSG":9,"ENOTUNIQ":126,"EBADFD":127,"EREMCHG":128,"ELIBACC":129,"ELIBBAD":130,"ELIBSCN":131,"ELIBMAX":132,"ELIBEXEC":133,"ENOSYS":52,"ENOTEMPTY":55,"ENAMETOOLONG":37,"ELOOP":32,"EOPNOTSUPP":138,"EPFNOSUPPORT":139,"ECONNRESET":15,"ENOBUFS":42,"EAFNOSUPPORT":5,"EPROTOTYPE":67,"ENOTSOCK":57,"ENOPROTOOPT":50,"ESHUTDOWN":140,"ECONNREFUSED":14,"EADDRINUSE":3,"ECONNABORTED":13,"ENETUNREACH":40,"ENETDOWN":38,"ETIMEDOUT":73,"EHOSTDOWN":142,"EHOSTUNREACH":23,"EINPROGRESS":26,"EALREADY":7,"EDESTADDRREQ":17,"EMSGSIZE":35,"EPROTONOSUPPORT":66,"ESOCKTNOSUPPORT":137,"EADDRNOTAVAIL":4,"ENETRESET":39,"EISCONN":30,"ENOTCONN":53,"ETOOMANYREFS":141,"EUSERS":136,"EDQUOT":19,"ESTALE":72,"ENOTSUP":138,"ENOMEDIUM":148,"EILSEQ":25,"EOVERFLOW":61,"ECANCELED":11,"ENOTRECOVERABLE":56,"EOWNERDEAD":62,"ESTRPIPE":135};var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var miniTempWebGLFloatBuffersStorage=new Float32Array(288);for(var i=0;i<288;++i){miniTempWebGLFloatBuffers[i]=miniTempWebGLFloatBuffersStorage.subarray(0,i+1)}var __miniTempWebGLIntBuffersStorage=new Int32Array(288);for(var i=0;i<288;++i){__miniTempWebGLIntBuffers[i]=__miniTempWebGLIntBuffersStorage.subarray(0,i+1)}var emSetImmediate;var emClearImmediate;if(typeof setImmediate!="undefined"){emSetImmediate=setImmediateWrapped;emClearImmediate=clearImmediateWrapped}else if(typeof addEventListener=="function"){var __setImmediate_id_counter=0;var __setImmediate_queue=[];var __setImmediate_message_id="_si";function __setImmediate_cb(e){if(e.data===__setImmediate_message_id){e.stopPropagation();__setImmediate_queue.shift()();++__setImmediate_id_counter}}addEventListener("message",__setImmediate_cb,true);emSetImmediate=function(func){postMessage(__setImmediate_message_id,"*");return __setImmediate_id_counter+__setImmediate_queue.push(func)-1};emClearImmediate=function(id){var index=id-__setImmediate_id_counter;if(index>=0&&index<__setImmediate_queue.length)__setImmediate_queue[index]=function(){}}}var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}function intArrayToString(array){var ret=[];for(var i=0;i255){if(ASSERTIONS){assert(false,"Character code "+chr+" ("+String.fromCharCode(chr)+") at offset "+i+" not in 0x00-0xFF.")}chr&=255}ret.push(String.fromCharCode(chr))}return ret.join("")}var decodeBase64=typeof atob=="function"?atob:function(input){var keyStr="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var output="";var chr1,chr2,chr3;var enc1,enc2,enc3,enc4;var i=0;input=input.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{enc1=keyStr.indexOf(input.charAt(i++));enc2=keyStr.indexOf(input.charAt(i++));enc3=keyStr.indexOf(input.charAt(i++));enc4=keyStr.indexOf(input.charAt(i++));chr1=enc1<<2|enc2>>4;chr2=(enc2&15)<<4|enc3>>2;chr3=(enc3&3)<<6|enc4;output=output+String.fromCharCode(chr1);if(enc3!==64){output=output+String.fromCharCode(chr2)}if(enc4!==64){output=output+String.fromCharCode(chr3)}}while(i>2;args.forEach(arg=>{HEAP32[argv_ptr++]=allocateUTF8OnStack(arg)});HEAP32[argv_ptr]=0;try{var ret=entryFunction(argc,argv);exit(ret,true);return ret}catch(e){return handleException(e)}finally{calledMain=true}}var dylibsLoaded=false;function run(args){args=args||arguments_;if(runDependencies>0){return}if(!dylibsLoaded){preloadDylibs();dylibsLoaded=true;if(runDependencies>0){return}}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;function exit(status,implicit){EXITSTATUS=status;procExit(status)}function procExit(code){EXITSTATUS=code;if(!keepRuntimeAlive()){if(Module["onExit"])Module["onExit"](code);ABORT=true}quit_(code,new ExitStatus(code))}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"])shouldRunNow=false;run();Module.UTF8ToString=UTF8ToString;Module.wasmTable=wasmTable;Module.ERRNO_CODES=ERRNO_CODES;Module.preloadedWasm=preloadedWasm; + + + return _createPyodideModule.ready +} +); +})(); +globalThis._createPyodideModule = _createPyodideModule; diff --git a/daring-daffodils/browser_extension/runtime/pyodide.asm.wasm b/daring-daffodils/browser_extension/runtime/pyodide.asm.wasm new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/pyodide.js b/daring-daffodils/browser_extension/runtime/pyodide.js new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/pyodide_py.tar b/daring-daffodils/browser_extension/runtime/pyodide_py.tar new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/pyparsing-3.0.9-py3-none-any.whl b/daring-daffodils/browser_extension/runtime/pyparsing-3.0.9-py3-none-any.whl new file mode 100644 index 00000000..e69de29b diff --git a/daring-daffodils/browser_extension/runtime/pyscript.css b/daring-daffodils/browser_extension/runtime/pyscript.css new file mode 100644 index 00000000..be942997 --- /dev/null +++ b/daring-daffodils/browser_extension/runtime/pyscript.css @@ -0,0 +1,244 @@ +:not(:defined) { + display: none +} + +html{ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + line-height: 1.5; +} + +.spinner::after { + content: ''; + box-sizing: border-box; + width: 40px; + height: 40px; + position: absolute; + top: calc(40% - 20px); + left: calc(50% - 20px); + border-radius: 50%; +} + +.spinner.smooth::after { + border-top: 4px solid rgba(255, 255, 255, 1); + border-left: 4px solid rgba(255, 255, 255, 1); + border-right: 4px solid rgba(255, 255, 255, 0); + animation: spinner 0.6s linear infinite; +} +@keyframes spinner { + to { + transform: rotate(360deg); + } +} + +.label { + text-align: center; + width: 100%; + display: block; + color: rgba(255, 255, 255, 0.8); + font-size: 0.8rem; + margin-top: 6rem; +} + +/* Pop-up second layer begin */ + +.py-overlay { + position: fixed; + display: flex; + justify-content: center; + align-items: center; + color: white; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.5); + transition: opacity 500ms; + visibility: hidden; + color: visible; + opacity: 1; +} + +.py-overlay { + visibility: visible; + opacity: 1; +} + +.py-pop-up { + text-align: center; + width: 600px; +} + +.py-pop-up p { + margin: 5px; +} + +.py-pop-up a { + position: absolute; + color: white; + text-decoration: none; + font-size: 200%; + top: 3.5%; + right: 5%; +} + +.py-box{ + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.py-box div.py-box-child * +{ + max-width: 100%; +} + +.py-repl-box{ + flex-direction: column; +} + +.editor-box{ + --tw-border-opacity: 1; + border-color: rgba(209, 213, 219, var(--tw-border-opacity)); + border-width: 1px; + position: relative; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgba(59, 130, 246, 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + position: relative; + + box-sizing: border-box; + border-width: 1px; + border-style: solid; + border-color: rgb(209, 213, 219) +} + +.editor-box:hover button{ + opacity: 1; +} + +.repl-play-button{ + opacity: 0; + bottom: 0.25rem; + right: 0.25rem; + position: absolute; + padding: 0; + line-height: inherit; + color: inherit; + cursor: pointer; + background-color: transparent; + background-image: none; + -webkit-appearance: button; + text-transform: none; + font-family: inherit; + font-size: 100%; + margin: 0; + text-rendering: auto; + letter-spacing: normal; + word-spacing: normal; + line-height: normal; + text-transform: none; + text-indent: 0px; + text-shadow: none; + display: inline-block; + text-align: center; + align-items: flex-start; + cursor: default; + box-sizing: border-box; + background-color: -internal-light-dark(rgb(239, 239, 239), rgb(59, 59, 59)); + margin: 0em; + padding: 1px 6px; + border: 0; +} + +.repl-play-button:hover{ + opacity: 1; +} + +.py-title{ + text-transform: uppercase; + text-align: center; +} + +.py-title h1{ + font-weight: 700; + font-size: 1.875rem; +} + +.py-input{ + padding: 0.5rem; + --tw-border-opacity: 1; + border-color: rgba(209, 213, 219, var(--tw-border-opacity)); + border-width: 1px; + border-radius: 0.25rem; + margin-right: 0.75rem; + border-style: solid; + width: auto; +} + +.py-box input.py-input{ + width: -webkit-fill-available; +} + +.central-content{ + max-width: 20rem; + margin-left: auto; + margin-right: auto; +} + +input { + text-rendering: auto; + color: -internal-light-dark(black, white); + letter-spacing: normal; + word-spacing: normal; + line-height: normal; + text-transform: none; + text-indent: 0px; + text-shadow: none; + display: inline-block; + text-align: start; + appearance: auto; + -webkit-rtl-ordering: logical; + cursor: text; + background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59)); + margin: 0em; + padding: 1px 2px; + border-width: 2px; + border-style: inset; + border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); + border-image: initial; +} + +.py-button{ + --tw-text-opacity: 1; + color: rgba(255, 255, 255, var(--tw-text-opacity)); + padding: 0.5rem; + --tw-bg-opacity: 1; + background-color: rgba(37, 99, 235, var(--tw-bg-opacity)); + --tw-border-opacity: 1; + border-color: rgba(37, 99, 235, var(--tw-border-opacity)); + border-width: 1px; + border-radius: 0.25rem; +} + +.py-li-element p{ + margin: 5px; +} + +.py-li-element p{ + display: inline; +} + +button, input, optgroup, select, textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; +} + +.line-through { + text-decoration: line-through; +} diff --git a/daring-daffodils/browser_extension/runtime/pyscript.js b/daring-daffodils/browser_extension/runtime/pyscript.js new file mode 100644 index 00000000..670280a5 --- /dev/null +++ b/daring-daffodils/browser_extension/runtime/pyscript.js @@ -0,0 +1,27856 @@ +(function () { + 'use strict'; + + function noop() { } + function safe_not_equal(a, b) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); + } + Promise.resolve(); + + const subscriber_queue = []; + /** + * Create a `Writable` store that allows both updating and reading by subscription. + * @param {*=}value initial value + * @param {StartStopNotifier=}start start and stop notifications for subscriptions + */ + function writable(value, start = noop) { + let stop; + const subscribers = new Set(); + function set(new_value) { + if (safe_not_equal(value, new_value)) { + value = new_value; + if (stop) { // store is ready + const run_queue = !subscriber_queue.length; + for (const subscriber of subscribers) { + subscriber[1](); + subscriber_queue.push(subscriber, value); + } + if (run_queue) { + for (let i = 0; i < subscriber_queue.length; i += 2) { + subscriber_queue[i][0](subscriber_queue[i + 1]); + } + subscriber_queue.length = 0; + } + } + } + } + function update(fn) { + set(fn(value)); + } + function subscribe(run, invalidate = noop) { + const subscriber = [run, invalidate]; + subscribers.add(subscriber); + if (subscribers.size === 1) { + stop = start(set) || noop; + } + run(value); + return () => { + subscribers.delete(subscriber); + if (subscribers.size === 0) { + stop(); + stop = null; + } + }; + } + return { set, update, subscribe }; + } + + /* Very simple logger interface. + + Each module is expected to create its own logger by doing e.g.: + + const logger = getLogger('my-prefix'); + + and then use it instead of console: + + logger.info('hello', 'world'); + logger.warn('...'); + logger.error('...'); + + The logger automatically adds the prefix "[my-prefix]" to all logs; so e.g., the + above call would print: + + [my-prefix] hello world + + logger.log is intentionally omitted. The idea is that PyScript should not + write anything to console.log, to leave it free for the user. + + Currently, the logger does not to anything more than that. In the future, + we might want to add additional features such as the ability to + enable/disable logs on a global or per-module basis. + */ + const _cache = new Map(); + function getLogger(prefix) { + let logger = _cache.get(prefix); + if (logger === undefined) { + logger = _makeLogger(prefix); + _cache.set(prefix, logger); + } + return logger; + } + function _makeLogger(prefix) { + prefix = "[" + prefix + "] "; + function make(level) { + const out_fn = console[level].bind(console); + function fn(fmt, ...args) { + out_fn(prefix + fmt, ...args); + } + return fn; + } + // 'log' is intentionally omitted + const debug = make('debug'); + const info = make('info'); + const warn = make('warn'); + const error = make('error'); + return { debug, info, warn, error }; + } + + /* + A store for Runtime which can encompass any + runtime, but currently only has Pyodide as its offering. + */ + const runtimeLoaded = writable(); + const loadedEnvironments = writable({}); + const scriptsQueue = writable([]); + const initializers = writable([]); + const postInitializers = writable([]); + const globalLoader = writable(); + const appConfig = writable(); + const addToScriptsQueue = (script) => { + scriptsQueue.update(scriptsQueue => [...scriptsQueue, script]); + }; + const addInitializer = (initializer) => { + initializers.update(initializers => [...initializers, initializer]); + }; + const addPostInitializer = (initializer) => { + postInitializers.update(postInitializers => [...postInitializers, initializer]); + }; + + // taken from https://github.com/Gin-Quin/fast-toml + let e = "", t$1 = 0; + function i$2(e, t = 0) { + let i; + for (; (i = e[t++]) && (" " == i || "\t" == i || "\r" == i);) + ; + return t - 1; + } + function n(e) { + switch (e[0]) { + case void 0: + return ""; + case '"': + return (function (e) { + let t, i = 0, n = ""; + for (; (t = e.indexOf("\\", i) + 1);) { + switch (((n += e.slice(i, t - 1)), e[t])) { + case "\\": + n += "\\"; + break; + case '"': + n += '"'; + break; + case "\r": + "\n" == e[t + 1] && t++; + case "\n": + break; + case "b": + n += "\b"; + break; + case "t": + n += "\t"; + break; + case "n": + n += "\n"; + break; + case "f": + n += "\f"; + break; + case "r": + n += "\r"; + break; + case "u": + (n += String.fromCharCode(parseInt(e.substr(t + 1, 4), 16))), (t += 4); + break; + case "U": + (n += String.fromCharCode(parseInt(e.substr(t + 1, 8), 16))), (t += 8); + break; + default: + throw r(e[t]); + } + i = t + 1; + } + return n + e.slice(i); + })(e.slice(1, -1)); + case "'": + return e.slice(1, -1); + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + case "+": + case "-": + case ".": + let t = e; + if ((-1 != t.indexOf("_") && (t = t.replace(/_/g, "")), !isNaN(t))) + return +t; + if ("-" == e[4] && "-" == e[7]) { + let t = new Date(e); + if ("Invalid Date" != t.toString()) + return t; + } + else if (":" == e[2] && ":" == e[5] && e.length >= 7) { + let t = new Date("0000-01-01T" + e + "Z"); + if ("Invalid Date" != t.toString()) + return t; + } + return e; + } + switch (e) { + case "true": + return !0; + case "false": + return !1; + case "nan": + case "NaN": + return !1; + case "null": + return null; + case "inf": + case "+inf": + case "Infinity": + case "+Infinity": + return 1 / 0; + case "-inf": + case "-Infinity": + return -1 / 0; + } + return e; + } + function r(i) { + let n = (function () { + let i = e[t$1], n = t$1; + "\n" == i && n--; + let r = 1, s = e.lastIndexOf("\n", n), a = e.indexOf("\n", n); + -1 == a && (a = 1 / 0); + ("," != i && "\n" != i) || (n = s + 1); + if (-1 == s) + return { line: r, column: n + 1, position: n, lineContent: e.slice(0, a).trim() }; + const c = n - s + 1, o = e.slice(s + 1, a).trim(); + r++; + for (; -1 != (s = e.lastIndexOf("\n", s - 1));) + r++; + return { line: r, column: c, position: n, lineContent: o }; + })(), r = String(n.line); + return (i += "\n" + r + " | " + n.lineContent + "\n"), (i += " ".repeat(r.length + n.column + 2) + "^"), SyntaxError(i); + } + function s(e, i = 0, n = !1) { + let a, c = e[i], o = c, f = c, l = !0, u = !1; + switch (c) { + case '"': + case "'": + if (((a = i + 1), n && e[i + 1] == c && e[i + 2] == c ? ((f = c + c + c), (a += 2)) : (u = !0), "'" == c)) + a = e.indexOf(f, a) + 1; + else + for (; (a = e.indexOf(f, a) + 1);) { + let t = !0, i = a - 1; + for (; "\\" == e[--i];) + t = !t; + if (t) + break; + } + if (!a) + throw r("Missing " + f + " closer"); + if (c != f) + a += 2; + else if (u) { + let n = e.indexOf("\n", i + 1) + 1; + if (n && n < a) + throw ((t$1 = n - 2), r("Forbidden end-of-line character in single-line string")); + } + return a; + case "(": + f = ")"; + break; + case "{": + f = "}"; + break; + case "[": + f = "]"; + break; + case "<": + f = ">"; + break; + default: + l = !1; + } + let h = 0; + for (; (c = e[++i]);) + if (c == f) { + if (0 == h) + return i + 1; + h--; + } + else if ('"' == c || "'" == c) { + i = s(e, i, n) - 1; + } + else + l && c == o && h++; + throw r("Missing " + f); + } + function a(e) { + "string" != typeof e && (e = String(e)); + let t, i, n = -1, a = "", c = []; + for (; (i = e[++n]);) + switch (i) { + case ".": + if (!a) + throw r('Unexpected "."'); + c.push(a), (a = ""); + continue; + case '"': + case "'": + if (((t = s(e, n)), t == n + 2)) + throw r("Empty string key"); + (a += e.slice(n + 1, t - 1)), (n = t - 1); + continue; + default: + a += i; + } + return a && c.push(a), c; + } + function c(e, t = []) { + const i = t.pop(); + for (let i of t) { + if ("object" != typeof e) { + throw r('["' + t.slice(0, t.indexOf(i) + 1).join('"].["') + '"]' + " must be an object"); + } + void 0 === e[i] && (e[i] = {}), (e = e[i]) instanceof Array && (e = e[e.length - 1]); + } + return [e, i]; + } + class o { + root; + data; + inlineScopeList; + constructor() { + this.root = {}; + this.data = this.root; + this.inlineScopeList = []; + } + get isRoot() { + return this.data == this.root; + } + set(e, t) { + let [i, n] = c(this.data, a(e)); + if ("string" == typeof i) + throw "Wtf the scope is a string. Please report the bug"; + if (n in i) + throw r(`Re-writing the key '${e}'`); + return (i[n] = t), t; + } + push(e) { + if (!(this.data instanceof Array)) { + if (!this.isRoot) + throw r("Missing key"); + (this.data = Object.assign([], this.data)), (this.root = this.data); + } + return this.data.push(e), this; + } + use(e) { + return ((this.data = (function (e, t = []) { + for (let i of t) { + //if (void 0 === e) e = lastData[lastElt] = {}; + //else + if ("object" != typeof e) { + throw r('["' + t.slice(0, t.indexOf(i) + 1).join('"].["') + '"]' + " must be an object"); + } + void 0 === e[i] && (e[i] = {}), (e = e[i]) instanceof Array && (e = e[e.length - 1]); + } + return e; + })(this.root, a(e))), + this); + } + useArray(e) { + let [t, i] = c(this.root, a(e)); + return (this.data = {}), void 0 === t[i] && (t[i] = []), t[i].push(this.data), this; + } + enter(e, t) { + return this.inlineScopeList.push(this.data), this.set(e, t), (this.data = t), this; + } + enterArray(e) { + return this.inlineScopeList.push(this.data), this.push(e), (this.data = e), this; + } + exit() { + return (this.data = this.inlineScopeList.pop()), this; + } + } + function f(a) { + "string" != typeof a && (a = String(a)); + const c = new o(), f = []; + (e = a), (t$1 = 0); + let l, u, h = "", d = "", p = e[0], w = !0; + const g = () => { + if (((h = h.trimEnd()), w)) + h && c.push(n(h)); + else { + if (!h) + throw r("Expected key before ="); + if (!d) + throw r("Expected value after ="); + c.set(h, n(d.trimEnd())); + } + (h = ""), (d = ""), (w = !0); + }; + do { + switch (p) { + case " ": + w ? h && (h += p) : d && (d += p); + case "\t": + case "\r": + continue; + case "#": + (t$1 = e.indexOf("\n", t$1 + 1) - 1), -2 == t$1 && (t$1 = 1 / 0); + continue; + case '"': + case "'": + if (!w && d) { + d += p; + continue; + } + let n = e[t$1 + 1] == p && e[t$1 + 2] == p; + if (((l = s(e, t$1, !0)), w)) { + if (h) + throw r("Unexpected " + p); + (h += n ? e.slice(t$1 + 2, l - 2) : e.slice(t$1, l)), (t$1 = l); + } + else + (d = e.slice(t$1, l)), (t$1 = l), n && ((d = d.slice(2, -2)), "\n" == d[1] ? (d = d[0] + d.slice(2)) : "\r" == d[1] && "\n" == d[2] && (d = d[0] + d.slice(3))); + if (((t$1 = i$2(e, t$1)), (p = e[t$1]), p && "," != p && "\n" != p && "#" != p && "}" != p && "]" != p && "=" != p)) + throw r("Unexpected character after end of string"); + t$1--; + continue; + case "\n": + case ",": + case void 0: + g(); + continue; + case "[": + case "{": + if (((u = "[" == p ? "]" : "}"), w && !f.length)) { + if (h) + throw r("Unexpected " + p); + if (((l = s(e, t$1)), "[" == p && "[" == e[t$1 + 1])) { + if ("]" != e[l - 2]) + throw r("Missing ]]"); + c.useArray(e.slice(t$1 + 2, l - 2)); + } + else + c.use(e.slice(t$1 + 1, l - 1)); + t$1 = l; + } + else if (w) { + if (h) + throw r("Unexpected " + p); + c.enterArray("[" == p ? [] : {}), f.push(u); + } + else { + if (d) + throw r("Unexpected " + p); + c.enter(h.trimEnd(), "[" == p ? [] : {}), f.push(u), (h = ""), (w = !0); + } + continue; + case "]": + case "}": + if ((h && g(), f.pop() != p)) + throw r("Unexpected " + p); + if ((c.exit(), (t$1 = i$2(e, t$1 + 1)), (p = e[t$1]), p && "," != p && "\n" != p && "#" != p && "}" != p && "]" != p)) + throw r("Unexpected character after end of scope"); + t$1--; + continue; + case "=": + if (!w) + throw r("Unexpected " + p); + if (!h) + throw r("Missing key before " + p); + w = !1; + continue; + default: + w ? (h += p) : (d += p); + } + } while ((p = e[++t$1]) || h); + if (f.length) + throw r("Missing " + f.pop()); + return c.root; + } + function h() { + let e = ""; + for (let t of arguments) + e += "string" == typeof t ? t : t[0]; + return f(e); + } + const toml = ((h.parse = f), + h); + + const allKeys = { + "string": ["name", "description", "version", "type", "author_name", "author_email", "license"], + "number": ["schema_version"], + "boolean": ["autoclose_loader"], + "array": ["runtimes", "packages", "paths", "plugins"] + }; + const defaultConfig$1 = { + "schema_version": 1, + "type": "app", + "autoclose_loader": true, + "runtimes": [{ + "src": "https://cdn.jsdelivr.net/pyodide/v0.21.2/full/pyodide.js", + "name": "pyodide-0.21.2", + "lang": "python" + }], + "packages": [], + "paths": [], + "plugins": [] + }; + function addClasses(element, classes) { + for (const entry of classes) { + element.classList.add(entry); + } + } + function removeClasses(element, classes) { + for (const entry of classes) { + element.classList.remove(entry); + } + } + function getLastPath(str) { + return str.split('\\').pop().split('/').pop(); + } + function escape(str) { + return str.replace(//g, ">"); + } + function htmlDecode(input) { + const doc = new DOMParser().parseFromString(ltrim(escape(input)), 'text/html'); + return doc.documentElement.textContent; + } + function ltrim(code) { + const lines = code.split('\n'); + if (lines.length == 0) + return code; + const lengths = lines + .filter(line => line.trim().length != 0) + .map(line => { + const [prefix] = line.match(/^\s*/); + return prefix.length; + }); + const k = Math.min(...lengths); + return k != 0 ? lines.map(line => line.substring(k)).join('\n') + : code; + } + function guidGenerator() { + const S4 = function () { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + }; + return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4(); + } + /* + * Display a page-wide error message to show that something has gone wrong with + * PyScript or Pyodide during loading. Probably not be used for issues that occur within + * Python scripts, since stderr can be routed to somewhere in the DOM + */ + function showError(msg) { + const warning = document.createElement('div'); + warning.style.backgroundColor = 'LightCoral'; + warning.style.alignContent = 'center'; + warning.style.margin = '4px'; + warning.style.padding = '4px'; + warning.innerHTML = msg; + document.body.prepend(warning); + } + function handleFetchError(e, singleFile) { + //Should we still export full error contents to console? + console.warn(`Caught an error in loadPaths:\r\n ${e.toString()}`); + let errorContent; + if (e.message.includes('TypeError: Failed to fetch')) { + errorContent = `

PyScript: Access to local files + (using "Paths:" in <py-env>) + is not available when directly opening a HTML file; + you must use a webserver to serve the additional files. + See this reference + on starting a simple webserver with Python.

`; + } + else if (e.message.includes('404')) { + errorContent = + `

PyScript: Loading from file ` + + singleFile + + ` failed with error 404 (File not Found). Are your filename and path are correct?

`; + } + else { + errorContent = '

PyScript encountered an error while loading from file: ' + e.message + '

'; + } + showError(errorContent); + } + function readTextFromPath(path) { + const request = new XMLHttpRequest(); + request.open("GET", path, false); + request.send(); + const returnValue = request.responseText; + return returnValue; + } + function fillUserData(inputConfig, resultConfig) { + for (const key in inputConfig) { + // fill in all extra keys ignored by the validator + if (!(key in defaultConfig$1)) { + resultConfig[key] = inputConfig[key]; + } + } + return resultConfig; + } + function mergeConfig(inlineConfig, externalConfig) { + if (Object.keys(inlineConfig).length === 0 && Object.keys(externalConfig).length === 0) { + return defaultConfig$1; + } + else if (Object.keys(inlineConfig).length === 0) { + return externalConfig; + } + else if (Object.keys(externalConfig).length === 0) { + return inlineConfig; + } + else { + let merged = {}; + for (const keyType in allKeys) { + const keys = allKeys[keyType]; + keys.forEach(function (item) { + if (keyType === "boolean") { + merged[item] = (typeof inlineConfig[item] !== "undefined") ? inlineConfig[item] : externalConfig[item]; + } + else { + merged[item] = inlineConfig[item] || externalConfig[item]; + } + }); + } + // fill extra keys from external first + // they will be overridden by inline if extra keys also clash + merged = fillUserData(externalConfig, merged); + merged = fillUserData(inlineConfig, merged); + return merged; + } + } + function parseConfig(configText, configType = "toml") { + let config; + if (configType === "toml") { + try { + // TOML parser is soft and can parse even JSON strings, this additional check prevents it. + if (configText.trim()[0] === "{") { + const errMessage = `config supplied: ${configText} is an invalid TOML and cannot be parsed`; + showError(`

${errMessage}

`); + throw Error(errMessage); + } + config = toml.parse(configText); + } + catch (err) { + const errMessage = err.toString(); + showError(`

config supplied: ${configText} is an invalid TOML and cannot be parsed: ${errMessage}

`); + throw err; + } + } + else if (configType === "json") { + try { + config = JSON.parse(configText); + } + catch (err) { + const errMessage = err.toString(); + showError(`

config supplied: ${configText} is an invalid JSON and cannot be parsed: ${errMessage}

`); + throw err; + } + } + else { + showError(`

type of config supplied is: ${configType}, supported values are ["toml", "json"].

`); + } + return config; + } + function validateConfig(configText, configType = "toml") { + const config = parseConfig(configText, configType); + const finalConfig = {}; + for (const keyType in allKeys) { + const keys = allKeys[keyType]; + keys.forEach(function (item) { + if (validateParamInConfig(item, keyType, config)) { + if (item === "runtimes") { + finalConfig[item] = []; + const runtimes = config[item]; + runtimes.forEach(function (eachRuntime) { + const runtimeConfig = {}; + for (const eachRuntimeParam in eachRuntime) { + if (validateParamInConfig(eachRuntimeParam, "string", eachRuntime)) { + runtimeConfig[eachRuntimeParam] = eachRuntime[eachRuntimeParam]; + } + } + finalConfig[item].push(runtimeConfig); + }); + } + else { + finalConfig[item] = config[item]; + } + } + }); + } + return fillUserData(config, finalConfig); + } + function validateParamInConfig(paramName, paramType, config) { + if (paramName in config) { + return paramType === "array" ? Array.isArray(config[paramName]) : typeof config[paramName] === paramType; + } + return false; + } + + const logger$b = getLogger('pyscript/base'); + // Global `Runtime` that implements the generic runtimes API + let runtime$2; + let Element; + runtimeLoaded.subscribe(value => { + runtime$2 = value; + }); + class BaseEvalElement extends HTMLElement { + shadow; + wrapper; + code; + source; + btnConfig; + btnRun; + outputElement; + errorElement; + theme; + appendOutput; + constructor() { + super(); + // attach shadow so we can preserve the element original innerHtml content + this.shadow = this.attachShadow({ mode: 'open' }); + this.wrapper = document.createElement('slot'); + this.shadow.appendChild(this.wrapper); + this.setOutputMode("append"); + } + addToOutput(s) { + this.outputElement.innerHTML += '
' + s + '
'; + this.outputElement.hidden = false; + } + setOutputMode(defaultMode = "append") { + const mode = this.hasAttribute('output-mode') ? this.getAttribute('output-mode') : defaultMode; + switch (mode) { + case "append": + this.appendOutput = true; + break; + case "replace": + this.appendOutput = false; + break; + default: + logger$b.warn(`${this.id}: custom output-modes are currently not implemented`); + } + } + // subclasses should overwrite this method to define custom logic + // before code gets evaluated + preEvaluate() { + return null; + } + // subclasses should overwrite this method to define custom logic + // after code has been evaluated + postEvaluate() { + return null; + } + checkId() { + if (!this.id) + this.id = 'py-' + guidGenerator(); + } + getSourceFromElement() { + return ''; + } + async getSourceFromFile(s) { + const response = await fetch(s); + this.code = await response.text(); + return this.code; + } + async _register_esm(runtime) { + const imports = {}; + const nodes = document.querySelectorAll("script[type='importmap']"); + const importmaps = []; + nodes.forEach(node => { + let importmap; + try { + importmap = JSON.parse(node.textContent); + if (importmap?.imports == null) + return; + importmaps.push(importmap); + } + catch { + return; + } + }); + for (const importmap of importmaps) { + for (const [name, url] of Object.entries(importmap.imports)) { + if (typeof name != 'string' || typeof url != 'string') + continue; + try { + // XXX: pyodide doesn't like Module(), failing with + // "can't read 'name' of undefined" at import time + imports[name] = { ...(await import(url)) }; + } + catch { + logger$b.error(`failed to fetch '${url}' for '${name}'`); + } + } + } + runtime.registerJsModule('esm', imports); + } + async evaluate() { + this.preEvaluate(); + let source; + let output; + try { + source = this.source ? await this.getSourceFromFile(this.source) + : this.getSourceFromElement(); + this._register_esm(runtime$2); + await runtime$2.run(`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`); + output = await runtime$2.run(source); + if (output !== undefined) { + if (Element === undefined) { + Element = runtime$2.globals.get('Element'); + } + const out = Element(this.outputElement.id); + out.write.callKwargs(output, { append: this.appendOutput }); + this.outputElement.hidden = false; + this.outputElement.style.display = 'block'; + } + await runtime$2.run(`output_manager.revert()`); + // check if this REPL contains errors, delete them and remove error classes + const errorElements = document.querySelectorAll(`div[id^='${this.errorElement.id}'][error]`); + if (errorElements.length > 0) { + errorElements.forEach(errorElement => { + errorElement.classList.add('hidden'); + if (this.hasAttribute('std-err')) { + this.errorElement.hidden = true; + this.errorElement.style.removeProperty('display'); + } + }); + } + removeClasses(this.errorElement, ['bg-red-200', 'p-2']); + this.postEvaluate(); + } + catch (err) { + logger$b.error(err); + try { + if (Element === undefined) { + Element = runtime$2.globals.get('Element'); + } + const out = Element(this.errorElement.id); + addClasses(this.errorElement, ['bg-red-200', 'p-2']); + out.write.callKwargs(err.toString(), { append: this.appendOutput }); + if (this.errorElement.children.length === 0) { + this.errorElement.setAttribute('error', ''); + } + else { + this.errorElement.children[this.errorElement.children.length - 1].setAttribute('error', ''); + } + this.errorElement.hidden = false; + this.errorElement.style.display = 'block'; + this.errorElement.style.visibility = 'visible'; + } + catch (internalErr) { + logger$b.error("Unnable to write error to error element in page."); + } + } + } // end evaluate + async eval(source) { + try { + const output = await runtime$2.run(source); + if (output !== undefined) { + logger$b.info(output); + } + } + catch (err) { + logger$b.error(err); + } + } // end eval + runAfterRuntimeInitialized(callback) { + runtimeLoaded.subscribe(value => { + if ('run' in value) { + setTimeout(() => { + void callback(); + }, 100); + } + }); + } + } + function createWidget(name, code, klass) { + class CustomWidget extends HTMLElement { + shadow; + wrapper; + name = name; + klass = klass; + code = code; + proxy; + proxyClass; + constructor() { + super(); + // attach shadow so we can preserve the element original innerHtml content + this.shadow = this.attachShadow({ mode: 'open' }); + this.wrapper = document.createElement('slot'); + this.shadow.appendChild(this.wrapper); + } + connectedCallback() { + // TODO: we are calling with a 2secs delay to allow pyodide to load + // ideally we can just wait for it to load and then run. To do + // so we need to replace using the promise and actually using + // the interpreter after it loads completely + // setTimeout(() => { + // void (async () => { + // await this.eval(this.code); + // this.proxy = this.proxyClass(this); + // console.log('proxy', this.proxy); + // this.proxy.connect(); + // this.registerWidget(); + // })(); + // }, 2000); + runtimeLoaded.subscribe(value => { + if ('run' in value) { + runtime$2 = value; + setTimeout(() => { + void (async () => { + await this.eval(this.code); + this.proxy = this.proxyClass(this); + this.proxy.connect(); + this.registerWidget(); + })(); + }, 1000); + } + }); + } + registerWidget() { + logger$b.info('new widget registered:', this.name); + runtime$2.globals.set(this.id, this.proxy); + } + async eval(source) { + try { + const output = await runtime$2.run(source); + this.proxyClass = runtime$2.globals.get(this.klass); + if (output !== undefined) { + logger$b.info('CustomWidget.eval: ', output); + } + } + catch (err) { + logger$b.error('CustomWidget.eval: ', err); + } + } + } + customElements.define(name, CustomWidget); + } + class PyWidget extends HTMLElement { + shadow; + name; + klass; + outputElement; + errorElement; + wrapper; + theme; + source; + code; + constructor() { + super(); + // attach shadow so we can preserve the element original innerHtml content + this.shadow = this.attachShadow({ mode: 'open' }); + this.wrapper = document.createElement('slot'); + this.shadow.appendChild(this.wrapper); + this.addAttributes('src', 'name', 'klass'); + } + addAttributes(...attrs) { + for (const each of attrs) { + const property = each === "src" ? "source" : each; + if (this.hasAttribute(each)) { + this[property] = this.getAttribute(each); + } + } + } + async connectedCallback() { + if (this.id === undefined) { + throw new ReferenceError(`No id specified for component. Components must have an explicit id. Please use id="" to specify your component id.`); + } + const mainDiv = document.createElement('div'); + mainDiv.id = this.id + '-main'; + this.appendChild(mainDiv); + logger$b.debug('PyWidget: reading source', this.source); + this.code = await this.getSourceFromFile(this.source); + createWidget(this.name, this.code, this.klass); + } + initOutErr() { + if (this.hasAttribute('output')) { + this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output')); + // in this case, the default output-mode is append, if hasn't been specified + if (!this.hasAttribute('output-mode')) { + this.setAttribute('output-mode', 'append'); + } + } + else { + if (this.hasAttribute('std-out')) { + this.outputElement = document.getElementById(this.getAttribute('std-out')); + } + else { + // In this case neither output or std-out have been provided so we need + // to create a new output div to output to + this.outputElement = document.createElement('div'); + this.outputElement.classList.add('output'); + this.outputElement.hidden = true; + this.outputElement.id = this.id + '-' + this.getAttribute('exec-id'); + } + if (this.hasAttribute('std-err')) { + this.errorElement = document.getElementById(this.getAttribute('std-err')); + } + else { + this.errorElement = this.outputElement; + } + } + } + async getSourceFromFile(s) { + const response = await fetch(s); + return await response.text(); + } + async eval(source) { + try { + const output = await runtime$2.run(source); + if (output !== undefined) { + logger$b.info('PyWidget.eval: ', output); + } + } + catch (err) { + logger$b.error('PyWidget.eval: ', err); + } + } + } + + const logger$a = getLogger('py-script'); + // Premise used to connect to the first available runtime (can be pyodide or others) + let runtime$1; + runtimeLoaded.subscribe(value => { + runtime$1 = value; + }); + loadedEnvironments.subscribe(value => { + }); + class PyScript extends BaseEvalElement { + constructor() { + super(); + // add an extra div where we can attach the codemirror editor + this.shadow.appendChild(this.wrapper); + } + connectedCallback() { + this.checkId(); + this.code = htmlDecode(this.innerHTML); + this.innerHTML = ''; + const mainDiv = document.createElement('div'); + addClasses(mainDiv, ['output']); + // add Editor to main PyScript div + if (this.hasAttribute('output')) { + this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output')); + // in this case, the default output-mode is append, if hasn't been specified + if (!this.hasAttribute('output-mode')) { + this.setAttribute('output-mode', 'append'); + } + } + else { + if (this.hasAttribute('std-out')) { + this.outputElement = document.getElementById(this.getAttribute('std-out')); + } + else { + // In this case neither output or std-out have been provided so we need + // to create a new output div to output to + // Let's check if we have an id first and create one if not + this.outputElement = document.createElement('div'); + const exec_id = this.getAttribute('exec-id'); + this.outputElement.id = this.id + (exec_id ? '-' + exec_id : ''); + // add the output div id if there's not output pre-defined + mainDiv.appendChild(this.outputElement); + } + if (this.hasAttribute('std-err')) { + this.errorElement = document.getElementById(this.getAttribute('std-err')); + } + else { + this.errorElement = this.outputElement; + } + } + this.appendChild(mainDiv); + addToScriptsQueue(this); + if (this.hasAttribute('src')) { + this.source = this.getAttribute('src'); + } + } + async _register_esm(runtime) { + for (const node of document.querySelectorAll("script[type='importmap']")) { + const importmap = (() => { + try { + return JSON.parse(node.textContent); + } + catch { + return null; + } + })(); + if (importmap?.imports == null) + continue; + for (const [name, url] of Object.entries(importmap.imports)) { + if (typeof name != 'string' || typeof url != 'string') + continue; + let exports; + try { + // XXX: pyodide doesn't like Module(), failing with + // "can't read 'name' of undefined" at import time + exports = { ...(await import(url)) }; + } + catch { + logger$a.warn(`failed to fetch '${url}' for '${name}'`); + continue; + } + runtime.registerJsModule(name, exports); + } + } + } + getSourceFromElement() { + return htmlDecode(this.code); + } + } + /** Defines all possible py-on* and their corresponding event types */ + const pyAttributeToEvent = new Map([ + // Leaving pys-onClick and pys-onKeyDown for backward compatibility + ["pys-onClick", "click"], + ["pys-onKeyDown", "keydown"], + ["py-onClick", "click"], + ["py-onKeyDown", "keydown"], + // Window Events + ["py-afterprint", "afterprint"], + ["py-beforeprint", "beforeprint"], + ["py-beforeunload", "beforeunload"], + ["py-error", "error"], + ["py-hashchange", "hashchange"], + ["py-load", "load"], + ["py-message", "message"], + ["py-offline", "offline"], + ["py-online", "online"], + ["py-pagehide", "pagehide"], + ["py-pageshow", "pageshow"], + ["py-popstate", "popstate"], + ["py-resize", "resize"], + ["py-storage", "storage"], + ["py-unload", "unload"], + // Form Events + ["py-blur", "blur"], + ["py-change", "change"], + ["py-contextmenu", "contextmenu"], + ["py-focus", "focus"], + ["py-input", "input"], + ["py-invalid", "invalid"], + ["py-reset", "reset"], + ["py-search", "search"], + ["py-select", "select"], + ["py-submit", "submit"], + // Keyboard Events + ["py-keydown", "keydown"], + ["py-keypress", "keypress"], + ["py-keyup", "keyup"], + // Mouse Events + ["py-click", "click"], + ["py-dblclick", "dblclick"], + ["py-mousedown", "mousedown"], + ["py-mousemove", "mousemove"], + ["py-mouseout", "mouseout"], + ["py-mouseover", "mouseover"], + ["py-mouseup", "mouseup"], + ["py-mousewheel", "mousewheel"], + ["py-wheel", "wheel"], + // Drag Events + ["py-drag", "drag"], + ["py-dragend", "dragend"], + ["py-dragenter", "dragenter"], + ["py-dragleave", "dragleave"], + ["py-dragover", "dragover"], + ["py-dragstart", "dragstart"], + ["py-drop", "drop"], + ["py-scroll", "scroll"], + // Clipboard Events + ["py-copy", "copy"], + ["py-cut", "cut"], + ["py-paste", "paste"], + // Media Events + ["py-abort", "abort"], + ["py-canplay", "canplay"], + ["py-canplaythrough", "canplaythrough"], + ["py-cuechange", "cuechange"], + ["py-durationchange", "durationchange"], + ["py-emptied", "emptied"], + ["py-ended", "ended"], + ["py-loadeddata", "loadeddata"], + ["py-loadedmetadata", "loadedmetadata"], + ["py-loadstart", "loadstart"], + ["py-pause", "pause"], + ["py-play", "play"], + ["py-playing", "playing"], + ["py-progress", "progress"], + ["py-ratechange", "ratechange"], + ["py-seeked", "seeked"], + ["py-seeking", "seeking"], + ["py-stalled", "stalled"], + ["py-suspend", "suspend"], + ["py-timeupdate", "timeupdate"], + ["py-volumechange", "volumechange"], + ["py-waiting", "waiting"], + // Misc Events + ["py-toggle", "toggle"], + ]); + /** Initialize all elements with py-* handlers attributes */ + async function initHandlers() { + logger$a.debug('Initializing py-* event handlers...'); + for (const pyAttribute of pyAttributeToEvent.keys()) { + await createElementsWithEventListeners(runtime$1, pyAttribute); + } + } + /** Initializes an element with the given py-on* attribute and its handler */ + async function createElementsWithEventListeners(runtime, pyAttribute) { + const matches = document.querySelectorAll(`[${pyAttribute}]`); + for (const el of matches) { + if (el.id.length === 0) { + throw new TypeError(`<${el.tagName.toLowerCase()}> must have an id attribute, when using the ${pyAttribute} attribute`); + } + const handlerCode = el.getAttribute(pyAttribute); + const event = pyAttributeToEvent.get(pyAttribute); + if (pyAttribute === 'pys-onClick' || pyAttribute === 'pys-onKeyDown') { + console.warn("Use of pys-onClick and pys-onKeyDown attributes is deprecated in favor of py-onClick() and py-onKeyDown(). pys-on* attributes will be deprecated in a future version of PyScript."); + const source = ` + from pyodide.ffi import create_proxy + Element("${el.id}").element.addEventListener("${event}", create_proxy(${handlerCode})) + `; + await runtime.run(source); + } + else { + el.addEventListener(event, () => { + (async () => { await runtime.run(handlerCode); })(); + }); + } + // TODO: Should we actually map handlers in JS instead of Python? + // el.onclick = (evt: any) => { + // console.log("click"); + // new Promise((resolve, reject) => { + // setTimeout(() => { + // console.log('Inside') + // }, 300); + // }).then(() => { + // console.log("resolved") + // }); + // // let handlerCode = el.getAttribute('py-onClick'); + // // pyodide.runPython(handlerCode); + // } + } + } + /** Mount all elements with attribute py-mount into the Python namespace */ + async function mountElements() { + const matches = document.querySelectorAll('[py-mount]'); + logger$a.info(`py-mount: found ${matches.length} elements`); + let source = ''; + for (const el of matches) { + const mountName = el.getAttribute('py-mount') || el.id.split('-').join('_'); + source += `\n${mountName} = Element("${el.id}")`; + } + await runtime$1.run(source); + } + addInitializer(mountElements); + addPostInitializer(initHandlers); + + /*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ + function isNothing(subject) { + return (typeof subject === 'undefined') || (subject === null); + } + + + function isObject(subject) { + return (typeof subject === 'object') && (subject !== null); + } + + + function toArray(sequence) { + if (Array.isArray(sequence)) return sequence; + else if (isNothing(sequence)) return []; + + return [ sequence ]; + } + + + function extend$1(target, source) { + var index, length, key, sourceKeys; + + if (source) { + sourceKeys = Object.keys(source); + + for (index = 0, length = sourceKeys.length; index < length; index += 1) { + key = sourceKeys[index]; + target[key] = source[key]; + } + } + + return target; + } + + + function repeat(string, count) { + var result = '', cycle; + + for (cycle = 0; cycle < count; cycle += 1) { + result += string; + } + + return result; + } + + + function isNegativeZero(number) { + return (number === 0) && (Number.NEGATIVE_INFINITY === 1 / number); + } + + + var isNothing_1 = isNothing; + var isObject_1 = isObject; + var toArray_1 = toArray; + var repeat_1 = repeat; + var isNegativeZero_1 = isNegativeZero; + var extend_1 = extend$1; + + var common = { + isNothing: isNothing_1, + isObject: isObject_1, + toArray: toArray_1, + repeat: repeat_1, + isNegativeZero: isNegativeZero_1, + extend: extend_1 + }; + + // YAML error class. http://stackoverflow.com/questions/8458984 + + + function formatError(exception, compact) { + var where = '', message = exception.reason || '(unknown reason)'; + + if (!exception.mark) return message; + + if (exception.mark.name) { + where += 'in "' + exception.mark.name + '" '; + } + + where += '(' + (exception.mark.line + 1) + ':' + (exception.mark.column + 1) + ')'; + + if (!compact && exception.mark.snippet) { + where += '\n\n' + exception.mark.snippet; + } + + return message + ' ' + where; + } + + + function YAMLException$1(reason, mark) { + // Super constructor + Error.call(this); + + this.name = 'YAMLException'; + this.reason = reason; + this.mark = mark; + this.message = formatError(this, false); + + // Include stack trace in error object + if (Error.captureStackTrace) { + // Chrome and NodeJS + Error.captureStackTrace(this, this.constructor); + } else { + // FF, IE 10+ and Safari 6+. Fallback for others + this.stack = (new Error()).stack || ''; + } + } + + + // Inherit from Error + YAMLException$1.prototype = Object.create(Error.prototype); + YAMLException$1.prototype.constructor = YAMLException$1; + + + YAMLException$1.prototype.toString = function toString(compact) { + return this.name + ': ' + formatError(this, compact); + }; + + + var exception = YAMLException$1; + + // get snippet for a single line, respecting maxLength + function getLine(buffer, lineStart, lineEnd, position, maxLineLength) { + var head = ''; + var tail = ''; + var maxHalfLength = Math.floor(maxLineLength / 2) - 1; + + if (position - lineStart > maxHalfLength) { + head = ' ... '; + lineStart = position - maxHalfLength + head.length; + } + + if (lineEnd - position > maxHalfLength) { + tail = ' ...'; + lineEnd = position + maxHalfLength - tail.length; + } + + return { + str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, '→') + tail, + pos: position - lineStart + head.length // relative position + }; + } + + + function padStart(string, max) { + return common.repeat(' ', max - string.length) + string; + } + + + function makeSnippet(mark, options) { + options = Object.create(options || null); + + if (!mark.buffer) return null; + + if (!options.maxLength) options.maxLength = 79; + if (typeof options.indent !== 'number') options.indent = 1; + if (typeof options.linesBefore !== 'number') options.linesBefore = 3; + if (typeof options.linesAfter !== 'number') options.linesAfter = 2; + + var re = /\r?\n|\r|\0/g; + var lineStarts = [ 0 ]; + var lineEnds = []; + var match; + var foundLineNo = -1; + + while ((match = re.exec(mark.buffer))) { + lineEnds.push(match.index); + lineStarts.push(match.index + match[0].length); + + if (mark.position <= match.index && foundLineNo < 0) { + foundLineNo = lineStarts.length - 2; + } + } + + if (foundLineNo < 0) foundLineNo = lineStarts.length - 1; + + var result = '', i, line; + var lineNoLength = Math.min(mark.line + options.linesAfter, lineEnds.length).toString().length; + var maxLineLength = options.maxLength - (options.indent + lineNoLength + 3); + + for (i = 1; i <= options.linesBefore; i++) { + if (foundLineNo - i < 0) break; + line = getLine( + mark.buffer, + lineStarts[foundLineNo - i], + lineEnds[foundLineNo - i], + mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo - i]), + maxLineLength + ); + result = common.repeat(' ', options.indent) + padStart((mark.line - i + 1).toString(), lineNoLength) + + ' | ' + line.str + '\n' + result; + } + + line = getLine(mark.buffer, lineStarts[foundLineNo], lineEnds[foundLineNo], mark.position, maxLineLength); + result += common.repeat(' ', options.indent) + padStart((mark.line + 1).toString(), lineNoLength) + + ' | ' + line.str + '\n'; + result += common.repeat('-', options.indent + lineNoLength + 3 + line.pos) + '^' + '\n'; + + for (i = 1; i <= options.linesAfter; i++) { + if (foundLineNo + i >= lineEnds.length) break; + line = getLine( + mark.buffer, + lineStarts[foundLineNo + i], + lineEnds[foundLineNo + i], + mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo + i]), + maxLineLength + ); + result += common.repeat(' ', options.indent) + padStart((mark.line + i + 1).toString(), lineNoLength) + + ' | ' + line.str + '\n'; + } + + return result.replace(/\n$/, ''); + } + + + var snippet = makeSnippet; + + var TYPE_CONSTRUCTOR_OPTIONS = [ + 'kind', + 'multi', + 'resolve', + 'construct', + 'instanceOf', + 'predicate', + 'represent', + 'representName', + 'defaultStyle', + 'styleAliases' + ]; + + var YAML_NODE_KINDS = [ + 'scalar', + 'sequence', + 'mapping' + ]; + + function compileStyleAliases(map) { + var result = {}; + + if (map !== null) { + Object.keys(map).forEach(function (style) { + map[style].forEach(function (alias) { + result[String(alias)] = style; + }); + }); + } + + return result; + } + + function Type$1(tag, options) { + options = options || {}; + + Object.keys(options).forEach(function (name) { + if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { + throw new exception('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); + } + }); + + // TODO: Add tag format check. + this.options = options; // keep original options in case user wants to extend this type later + this.tag = tag; + this.kind = options['kind'] || null; + this.resolve = options['resolve'] || function () { return true; }; + this.construct = options['construct'] || function (data) { return data; }; + this.instanceOf = options['instanceOf'] || null; + this.predicate = options['predicate'] || null; + this.represent = options['represent'] || null; + this.representName = options['representName'] || null; + this.defaultStyle = options['defaultStyle'] || null; + this.multi = options['multi'] || false; + this.styleAliases = compileStyleAliases(options['styleAliases'] || null); + + if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { + throw new exception('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); + } + } + + var type = Type$1; + + /*eslint-disable max-len*/ + + + + + + function compileList(schema, name) { + var result = []; + + schema[name].forEach(function (currentType) { + var newIndex = result.length; + + result.forEach(function (previousType, previousIndex) { + if (previousType.tag === currentType.tag && + previousType.kind === currentType.kind && + previousType.multi === currentType.multi) { + + newIndex = previousIndex; + } + }); + + result[newIndex] = currentType; + }); + + return result; + } + + + function compileMap(/* lists... */) { + var result = { + scalar: {}, + sequence: {}, + mapping: {}, + fallback: {}, + multi: { + scalar: [], + sequence: [], + mapping: [], + fallback: [] + } + }, index, length; + + function collectType(type) { + if (type.multi) { + result.multi[type.kind].push(type); + result.multi['fallback'].push(type); + } else { + result[type.kind][type.tag] = result['fallback'][type.tag] = type; + } + } + + for (index = 0, length = arguments.length; index < length; index += 1) { + arguments[index].forEach(collectType); + } + return result; + } + + + function Schema$1(definition) { + return this.extend(definition); + } + + + Schema$1.prototype.extend = function extend(definition) { + var implicit = []; + var explicit = []; + + if (definition instanceof type) { + // Schema.extend(type) + explicit.push(definition); + + } else if (Array.isArray(definition)) { + // Schema.extend([ type1, type2, ... ]) + explicit = explicit.concat(definition); + + } else if (definition && (Array.isArray(definition.implicit) || Array.isArray(definition.explicit))) { + // Schema.extend({ explicit: [ type1, type2, ... ], implicit: [ type1, type2, ... ] }) + if (definition.implicit) implicit = implicit.concat(definition.implicit); + if (definition.explicit) explicit = explicit.concat(definition.explicit); + + } else { + throw new exception('Schema.extend argument should be a Type, [ Type ], ' + + 'or a schema definition ({ implicit: [...], explicit: [...] })'); + } + + implicit.forEach(function (type$1) { + if (!(type$1 instanceof type)) { + throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); + } + + if (type$1.loadKind && type$1.loadKind !== 'scalar') { + throw new exception('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.'); + } + + if (type$1.multi) { + throw new exception('There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.'); + } + }); + + explicit.forEach(function (type$1) { + if (!(type$1 instanceof type)) { + throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); + } + }); + + var result = Object.create(Schema$1.prototype); + + result.implicit = (this.implicit || []).concat(implicit); + result.explicit = (this.explicit || []).concat(explicit); + + result.compiledImplicit = compileList(result, 'implicit'); + result.compiledExplicit = compileList(result, 'explicit'); + result.compiledTypeMap = compileMap(result.compiledImplicit, result.compiledExplicit); + + return result; + }; + + + var schema = Schema$1; + + var str = new type('tag:yaml.org,2002:str', { + kind: 'scalar', + construct: function (data) { return data !== null ? data : ''; } + }); + + var seq = new type('tag:yaml.org,2002:seq', { + kind: 'sequence', + construct: function (data) { return data !== null ? data : []; } + }); + + var map = new type('tag:yaml.org,2002:map', { + kind: 'mapping', + construct: function (data) { return data !== null ? data : {}; } + }); + + var failsafe = new schema({ + explicit: [ + str, + seq, + map + ] + }); + + function resolveYamlNull(data) { + if (data === null) return true; + + var max = data.length; + + return (max === 1 && data === '~') || + (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL')); + } + + function constructYamlNull() { + return null; + } + + function isNull(object) { + return object === null; + } + + var _null = new type('tag:yaml.org,2002:null', { + kind: 'scalar', + resolve: resolveYamlNull, + construct: constructYamlNull, + predicate: isNull, + represent: { + canonical: function () { return '~'; }, + lowercase: function () { return 'null'; }, + uppercase: function () { return 'NULL'; }, + camelcase: function () { return 'Null'; }, + empty: function () { return ''; } + }, + defaultStyle: 'lowercase' + }); + + function resolveYamlBoolean(data) { + if (data === null) return false; + + var max = data.length; + + return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) || + (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE')); + } + + function constructYamlBoolean(data) { + return data === 'true' || + data === 'True' || + data === 'TRUE'; + } + + function isBoolean(object) { + return Object.prototype.toString.call(object) === '[object Boolean]'; + } + + var bool = new type('tag:yaml.org,2002:bool', { + kind: 'scalar', + resolve: resolveYamlBoolean, + construct: constructYamlBoolean, + predicate: isBoolean, + represent: { + lowercase: function (object) { return object ? 'true' : 'false'; }, + uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; }, + camelcase: function (object) { return object ? 'True' : 'False'; } + }, + defaultStyle: 'lowercase' + }); + + function isHexCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) || + ((0x41/* A */ <= c) && (c <= 0x46/* F */)) || + ((0x61/* a */ <= c) && (c <= 0x66/* f */)); + } + + function isOctCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */)); + } + + function isDecCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)); + } + + function resolveYamlInteger(data) { + if (data === null) return false; + + var max = data.length, + index = 0, + hasDigits = false, + ch; + + if (!max) return false; + + ch = data[index]; + + // sign + if (ch === '-' || ch === '+') { + ch = data[++index]; + } + + if (ch === '0') { + // 0 + if (index + 1 === max) return true; + ch = data[++index]; + + // base 2, base 8, base 16 + + if (ch === 'b') { + // base 2 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (ch !== '0' && ch !== '1') return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + + + if (ch === 'x') { + // base 16 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isHexCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + + + if (ch === 'o') { + // base 8 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isOctCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + } + + // base 10 (except 0) + + // value should not start with `_`; + if (ch === '_') return false; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isDecCode(data.charCodeAt(index))) { + return false; + } + hasDigits = true; + } + + // Should have digits and should not end with `_` + if (!hasDigits || ch === '_') return false; + + return true; + } + + function constructYamlInteger(data) { + var value = data, sign = 1, ch; + + if (value.indexOf('_') !== -1) { + value = value.replace(/_/g, ''); + } + + ch = value[0]; + + if (ch === '-' || ch === '+') { + if (ch === '-') sign = -1; + value = value.slice(1); + ch = value[0]; + } + + if (value === '0') return 0; + + if (ch === '0') { + if (value[1] === 'b') return sign * parseInt(value.slice(2), 2); + if (value[1] === 'x') return sign * parseInt(value.slice(2), 16); + if (value[1] === 'o') return sign * parseInt(value.slice(2), 8); + } + + return sign * parseInt(value, 10); + } + + function isInteger(object) { + return (Object.prototype.toString.call(object)) === '[object Number]' && + (object % 1 === 0 && !common.isNegativeZero(object)); + } + + var int = new type('tag:yaml.org,2002:int', { + kind: 'scalar', + resolve: resolveYamlInteger, + construct: constructYamlInteger, + predicate: isInteger, + represent: { + binary: function (obj) { return obj >= 0 ? '0b' + obj.toString(2) : '-0b' + obj.toString(2).slice(1); }, + octal: function (obj) { return obj >= 0 ? '0o' + obj.toString(8) : '-0o' + obj.toString(8).slice(1); }, + decimal: function (obj) { return obj.toString(10); }, + /* eslint-disable max-len */ + hexadecimal: function (obj) { return obj >= 0 ? '0x' + obj.toString(16).toUpperCase() : '-0x' + obj.toString(16).toUpperCase().slice(1); } + }, + defaultStyle: 'decimal', + styleAliases: { + binary: [ 2, 'bin' ], + octal: [ 8, 'oct' ], + decimal: [ 10, 'dec' ], + hexadecimal: [ 16, 'hex' ] + } + }); + + var YAML_FLOAT_PATTERN = new RegExp( + // 2.5e4, 2.5 and integers + '^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?' + + // .2e4, .2 + // special case, seems not from spec + '|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?' + + // .inf + '|[-+]?\\.(?:inf|Inf|INF)' + + // .nan + '|\\.(?:nan|NaN|NAN))$'); + + function resolveYamlFloat(data) { + if (data === null) return false; + + if (!YAML_FLOAT_PATTERN.test(data) || + // Quick hack to not allow integers end with `_` + // Probably should update regexp & check speed + data[data.length - 1] === '_') { + return false; + } + + return true; + } + + function constructYamlFloat(data) { + var value, sign; + + value = data.replace(/_/g, '').toLowerCase(); + sign = value[0] === '-' ? -1 : 1; + + if ('+-'.indexOf(value[0]) >= 0) { + value = value.slice(1); + } + + if (value === '.inf') { + return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; + + } else if (value === '.nan') { + return NaN; + } + return sign * parseFloat(value, 10); + } + + + var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; + + function representYamlFloat(object, style) { + var res; + + if (isNaN(object)) { + switch (style) { + case 'lowercase': return '.nan'; + case 'uppercase': return '.NAN'; + case 'camelcase': return '.NaN'; + } + } else if (Number.POSITIVE_INFINITY === object) { + switch (style) { + case 'lowercase': return '.inf'; + case 'uppercase': return '.INF'; + case 'camelcase': return '.Inf'; + } + } else if (Number.NEGATIVE_INFINITY === object) { + switch (style) { + case 'lowercase': return '-.inf'; + case 'uppercase': return '-.INF'; + case 'camelcase': return '-.Inf'; + } + } else if (common.isNegativeZero(object)) { + return '-0.0'; + } + + res = object.toString(10); + + // JS stringifier can build scientific format without dots: 5e-100, + // while YAML requres dot: 5.e-100. Fix it with simple hack + + return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res; + } + + function isFloat(object) { + return (Object.prototype.toString.call(object) === '[object Number]') && + (object % 1 !== 0 || common.isNegativeZero(object)); + } + + var float = new type('tag:yaml.org,2002:float', { + kind: 'scalar', + resolve: resolveYamlFloat, + construct: constructYamlFloat, + predicate: isFloat, + represent: representYamlFloat, + defaultStyle: 'lowercase' + }); + + var json = failsafe.extend({ + implicit: [ + _null, + bool, + int, + float + ] + }); + + var core = json; + + var YAML_DATE_REGEXP = new RegExp( + '^([0-9][0-9][0-9][0-9])' + // [1] year + '-([0-9][0-9])' + // [2] month + '-([0-9][0-9])$'); // [3] day + + var YAML_TIMESTAMP_REGEXP = new RegExp( + '^([0-9][0-9][0-9][0-9])' + // [1] year + '-([0-9][0-9]?)' + // [2] month + '-([0-9][0-9]?)' + // [3] day + '(?:[Tt]|[ \\t]+)' + // ... + '([0-9][0-9]?)' + // [4] hour + ':([0-9][0-9])' + // [5] minute + ':([0-9][0-9])' + // [6] second + '(?:\\.([0-9]*))?' + // [7] fraction + '(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour + '(?::([0-9][0-9]))?))?$'); // [11] tz_minute + + function resolveYamlTimestamp(data) { + if (data === null) return false; + if (YAML_DATE_REGEXP.exec(data) !== null) return true; + if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; + return false; + } + + function constructYamlTimestamp(data) { + var match, year, month, day, hour, minute, second, fraction = 0, + delta = null, tz_hour, tz_minute, date; + + match = YAML_DATE_REGEXP.exec(data); + if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); + + if (match === null) throw new Error('Date resolve error'); + + // match: [1] year [2] month [3] day + + year = +(match[1]); + month = +(match[2]) - 1; // JS month starts with 0 + day = +(match[3]); + + if (!match[4]) { // no hour + return new Date(Date.UTC(year, month, day)); + } + + // match: [4] hour [5] minute [6] second [7] fraction + + hour = +(match[4]); + minute = +(match[5]); + second = +(match[6]); + + if (match[7]) { + fraction = match[7].slice(0, 3); + while (fraction.length < 3) { // milli-seconds + fraction += '0'; + } + fraction = +fraction; + } + + // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute + + if (match[9]) { + tz_hour = +(match[10]); + tz_minute = +(match[11] || 0); + delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds + if (match[9] === '-') delta = -delta; + } + + date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); + + if (delta) date.setTime(date.getTime() - delta); + + return date; + } + + function representYamlTimestamp(object /*, style*/) { + return object.toISOString(); + } + + var timestamp = new type('tag:yaml.org,2002:timestamp', { + kind: 'scalar', + resolve: resolveYamlTimestamp, + construct: constructYamlTimestamp, + instanceOf: Date, + represent: representYamlTimestamp + }); + + function resolveYamlMerge(data) { + return data === '<<' || data === null; + } + + var merge = new type('tag:yaml.org,2002:merge', { + kind: 'scalar', + resolve: resolveYamlMerge + }); + + /*eslint-disable no-bitwise*/ + + + + + + // [ 64, 65, 66 ] -> [ padding, CR, LF ] + var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r'; + + + function resolveYamlBinary(data) { + if (data === null) return false; + + var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP; + + // Convert one by one. + for (idx = 0; idx < max; idx++) { + code = map.indexOf(data.charAt(idx)); + + // Skip CR/LF + if (code > 64) continue; + + // Fail on illegal characters + if (code < 0) return false; + + bitlen += 6; + } + + // If there are any bits left, source was corrupted + return (bitlen % 8) === 0; + } + + function constructYamlBinary(data) { + var idx, tailbits, + input = data.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan + max = input.length, + map = BASE64_MAP, + bits = 0, + result = []; + + // Collect by 6*4 bits (3 bytes) + + for (idx = 0; idx < max; idx++) { + if ((idx % 4 === 0) && idx) { + result.push((bits >> 16) & 0xFF); + result.push((bits >> 8) & 0xFF); + result.push(bits & 0xFF); + } + + bits = (bits << 6) | map.indexOf(input.charAt(idx)); + } + + // Dump tail + + tailbits = (max % 4) * 6; + + if (tailbits === 0) { + result.push((bits >> 16) & 0xFF); + result.push((bits >> 8) & 0xFF); + result.push(bits & 0xFF); + } else if (tailbits === 18) { + result.push((bits >> 10) & 0xFF); + result.push((bits >> 2) & 0xFF); + } else if (tailbits === 12) { + result.push((bits >> 4) & 0xFF); + } + + return new Uint8Array(result); + } + + function representYamlBinary(object /*, style*/) { + var result = '', bits = 0, idx, tail, + max = object.length, + map = BASE64_MAP; + + // Convert every three bytes to 4 ASCII characters. + + for (idx = 0; idx < max; idx++) { + if ((idx % 3 === 0) && idx) { + result += map[(bits >> 18) & 0x3F]; + result += map[(bits >> 12) & 0x3F]; + result += map[(bits >> 6) & 0x3F]; + result += map[bits & 0x3F]; + } + + bits = (bits << 8) + object[idx]; + } + + // Dump tail + + tail = max % 3; + + if (tail === 0) { + result += map[(bits >> 18) & 0x3F]; + result += map[(bits >> 12) & 0x3F]; + result += map[(bits >> 6) & 0x3F]; + result += map[bits & 0x3F]; + } else if (tail === 2) { + result += map[(bits >> 10) & 0x3F]; + result += map[(bits >> 4) & 0x3F]; + result += map[(bits << 2) & 0x3F]; + result += map[64]; + } else if (tail === 1) { + result += map[(bits >> 2) & 0x3F]; + result += map[(bits << 4) & 0x3F]; + result += map[64]; + result += map[64]; + } + + return result; + } + + function isBinary(obj) { + return Object.prototype.toString.call(obj) === '[object Uint8Array]'; + } + + var binary = new type('tag:yaml.org,2002:binary', { + kind: 'scalar', + resolve: resolveYamlBinary, + construct: constructYamlBinary, + predicate: isBinary, + represent: representYamlBinary + }); + + var _hasOwnProperty$3 = Object.prototype.hasOwnProperty; + var _toString$2 = Object.prototype.toString; + + function resolveYamlOmap(data) { + if (data === null) return true; + + var objectKeys = [], index, length, pair, pairKey, pairHasKey, + object = data; + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + pairHasKey = false; + + if (_toString$2.call(pair) !== '[object Object]') return false; + + for (pairKey in pair) { + if (_hasOwnProperty$3.call(pair, pairKey)) { + if (!pairHasKey) pairHasKey = true; + else return false; + } + } + + if (!pairHasKey) return false; + + if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); + else return false; + } + + return true; + } + + function constructYamlOmap(data) { + return data !== null ? data : []; + } + + var omap = new type('tag:yaml.org,2002:omap', { + kind: 'sequence', + resolve: resolveYamlOmap, + construct: constructYamlOmap + }); + + var _toString$1 = Object.prototype.toString; + + function resolveYamlPairs(data) { + if (data === null) return true; + + var index, length, pair, keys, result, + object = data; + + result = new Array(object.length); + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + + if (_toString$1.call(pair) !== '[object Object]') return false; + + keys = Object.keys(pair); + + if (keys.length !== 1) return false; + + result[index] = [ keys[0], pair[keys[0]] ]; + } + + return true; + } + + function constructYamlPairs(data) { + if (data === null) return []; + + var index, length, pair, keys, result, + object = data; + + result = new Array(object.length); + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + + keys = Object.keys(pair); + + result[index] = [ keys[0], pair[keys[0]] ]; + } + + return result; + } + + var pairs = new type('tag:yaml.org,2002:pairs', { + kind: 'sequence', + resolve: resolveYamlPairs, + construct: constructYamlPairs + }); + + var _hasOwnProperty$2 = Object.prototype.hasOwnProperty; + + function resolveYamlSet(data) { + if (data === null) return true; + + var key, object = data; + + for (key in object) { + if (_hasOwnProperty$2.call(object, key)) { + if (object[key] !== null) return false; + } + } + + return true; + } + + function constructYamlSet(data) { + return data !== null ? data : {}; + } + + var set = new type('tag:yaml.org,2002:set', { + kind: 'mapping', + resolve: resolveYamlSet, + construct: constructYamlSet + }); + + var _default = core.extend({ + implicit: [ + timestamp, + merge + ], + explicit: [ + binary, + omap, + pairs, + set + ] + }); + + /*eslint-disable max-len,no-use-before-define*/ + + + + + + + + var _hasOwnProperty$1 = Object.prototype.hasOwnProperty; + + + var CONTEXT_FLOW_IN = 1; + var CONTEXT_FLOW_OUT = 2; + var CONTEXT_BLOCK_IN = 3; + var CONTEXT_BLOCK_OUT = 4; + + + var CHOMPING_CLIP = 1; + var CHOMPING_STRIP = 2; + var CHOMPING_KEEP = 3; + + + var PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; + var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; + var PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; + var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; + var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; + + + function _class(obj) { return Object.prototype.toString.call(obj); } + + function is_EOL(c) { + return (c === 0x0A/* LF */) || (c === 0x0D/* CR */); + } + + function is_WHITE_SPACE(c) { + return (c === 0x09/* Tab */) || (c === 0x20/* Space */); + } + + function is_WS_OR_EOL(c) { + return (c === 0x09/* Tab */) || + (c === 0x20/* Space */) || + (c === 0x0A/* LF */) || + (c === 0x0D/* CR */); + } + + function is_FLOW_INDICATOR(c) { + return c === 0x2C/* , */ || + c === 0x5B/* [ */ || + c === 0x5D/* ] */ || + c === 0x7B/* { */ || + c === 0x7D/* } */; + } + + function fromHexCode(c) { + var lc; + + if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { + return c - 0x30; + } + + /*eslint-disable no-bitwise*/ + lc = c | 0x20; + + if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) { + return lc - 0x61 + 10; + } + + return -1; + } + + function escapedHexLen(c) { + if (c === 0x78/* x */) { return 2; } + if (c === 0x75/* u */) { return 4; } + if (c === 0x55/* U */) { return 8; } + return 0; + } + + function fromDecimalCode(c) { + if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { + return c - 0x30; + } + + return -1; + } + + function simpleEscapeSequence(c) { + /* eslint-disable indent */ + return (c === 0x30/* 0 */) ? '\x00' : + (c === 0x61/* a */) ? '\x07' : + (c === 0x62/* b */) ? '\x08' : + (c === 0x74/* t */) ? '\x09' : + (c === 0x09/* Tab */) ? '\x09' : + (c === 0x6E/* n */) ? '\x0A' : + (c === 0x76/* v */) ? '\x0B' : + (c === 0x66/* f */) ? '\x0C' : + (c === 0x72/* r */) ? '\x0D' : + (c === 0x65/* e */) ? '\x1B' : + (c === 0x20/* Space */) ? ' ' : + (c === 0x22/* " */) ? '\x22' : + (c === 0x2F/* / */) ? '/' : + (c === 0x5C/* \ */) ? '\x5C' : + (c === 0x4E/* N */) ? '\x85' : + (c === 0x5F/* _ */) ? '\xA0' : + (c === 0x4C/* L */) ? '\u2028' : + (c === 0x50/* P */) ? '\u2029' : ''; + } + + function charFromCodepoint(c) { + if (c <= 0xFFFF) { + return String.fromCharCode(c); + } + // Encode UTF-16 surrogate pair + // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF + return String.fromCharCode( + ((c - 0x010000) >> 10) + 0xD800, + ((c - 0x010000) & 0x03FF) + 0xDC00 + ); + } + + var simpleEscapeCheck = new Array(256); // integer, for fast access + var simpleEscapeMap = new Array(256); + for (var i$1 = 0; i$1 < 256; i$1++) { + simpleEscapeCheck[i$1] = simpleEscapeSequence(i$1) ? 1 : 0; + simpleEscapeMap[i$1] = simpleEscapeSequence(i$1); + } + + + function State$1(input, options) { + this.input = input; + + this.filename = options['filename'] || null; + this.schema = options['schema'] || _default; + this.onWarning = options['onWarning'] || null; + // (Hidden) Remove? makes the loader to expect YAML 1.1 documents + // if such documents have no explicit %YAML directive + this.legacy = options['legacy'] || false; + + this.json = options['json'] || false; + this.listener = options['listener'] || null; + + this.implicitTypes = this.schema.compiledImplicit; + this.typeMap = this.schema.compiledTypeMap; + + this.length = input.length; + this.position = 0; + this.line = 0; + this.lineStart = 0; + this.lineIndent = 0; + + // position of first leading tab in the current line, + // used to make sure there are no tabs in the indentation + this.firstTabInLine = -1; + + this.documents = []; + + /* + this.version; + this.checkLineBreaks; + this.tagMap; + this.anchorMap; + this.tag; + this.anchor; + this.kind; + this.result;*/ + + } + + + function generateError(state, message) { + var mark = { + name: state.filename, + buffer: state.input.slice(0, -1), // omit trailing \0 + position: state.position, + line: state.line, + column: state.position - state.lineStart + }; + + mark.snippet = snippet(mark); + + return new exception(message, mark); + } + + function throwError(state, message) { + throw generateError(state, message); + } + + function throwWarning(state, message) { + if (state.onWarning) { + state.onWarning.call(null, generateError(state, message)); + } + } + + + var directiveHandlers = { + + YAML: function handleYamlDirective(state, name, args) { + + var match, major, minor; + + if (state.version !== null) { + throwError(state, 'duplication of %YAML directive'); + } + + if (args.length !== 1) { + throwError(state, 'YAML directive accepts exactly one argument'); + } + + match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); + + if (match === null) { + throwError(state, 'ill-formed argument of the YAML directive'); + } + + major = parseInt(match[1], 10); + minor = parseInt(match[2], 10); + + if (major !== 1) { + throwError(state, 'unacceptable YAML version of the document'); + } + + state.version = args[0]; + state.checkLineBreaks = (minor < 2); + + if (minor !== 1 && minor !== 2) { + throwWarning(state, 'unsupported YAML version of the document'); + } + }, + + TAG: function handleTagDirective(state, name, args) { + + var handle, prefix; + + if (args.length !== 2) { + throwError(state, 'TAG directive accepts exactly two arguments'); + } + + handle = args[0]; + prefix = args[1]; + + if (!PATTERN_TAG_HANDLE.test(handle)) { + throwError(state, 'ill-formed tag handle (first argument) of the TAG directive'); + } + + if (_hasOwnProperty$1.call(state.tagMap, handle)) { + throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle'); + } + + if (!PATTERN_TAG_URI.test(prefix)) { + throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive'); + } + + try { + prefix = decodeURIComponent(prefix); + } catch (err) { + throwError(state, 'tag prefix is malformed: ' + prefix); + } + + state.tagMap[handle] = prefix; + } + }; + + + function captureSegment(state, start, end, checkJson) { + var _position, _length, _character, _result; + + if (start < end) { + _result = state.input.slice(start, end); + + if (checkJson) { + for (_position = 0, _length = _result.length; _position < _length; _position += 1) { + _character = _result.charCodeAt(_position); + if (!(_character === 0x09 || + (0x20 <= _character && _character <= 0x10FFFF))) { + throwError(state, 'expected valid JSON character'); + } + } + } else if (PATTERN_NON_PRINTABLE.test(_result)) { + throwError(state, 'the stream contains non-printable characters'); + } + + state.result += _result; + } + } + + function mergeMappings(state, destination, source, overridableKeys) { + var sourceKeys, key, index, quantity; + + if (!common.isObject(source)) { + throwError(state, 'cannot merge mappings; the provided source object is unacceptable'); + } + + sourceKeys = Object.keys(source); + + for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) { + key = sourceKeys[index]; + + if (!_hasOwnProperty$1.call(destination, key)) { + destination[key] = source[key]; + overridableKeys[key] = true; + } + } + } + + function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, + startLine, startLineStart, startPos) { + + var index, quantity; + + // The output is a plain object here, so keys can only be strings. + // We need to convert keyNode to a string, but doing so can hang the process + // (deeply nested arrays that explode exponentially using aliases). + if (Array.isArray(keyNode)) { + keyNode = Array.prototype.slice.call(keyNode); + + for (index = 0, quantity = keyNode.length; index < quantity; index += 1) { + if (Array.isArray(keyNode[index])) { + throwError(state, 'nested arrays are not supported inside keys'); + } + + if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') { + keyNode[index] = '[object Object]'; + } + } + } + + // Avoid code execution in load() via toString property + // (still use its own toString for arrays, timestamps, + // and whatever user schema extensions happen to have @@toStringTag) + if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') { + keyNode = '[object Object]'; + } + + + keyNode = String(keyNode); + + if (_result === null) { + _result = {}; + } + + if (keyTag === 'tag:yaml.org,2002:merge') { + if (Array.isArray(valueNode)) { + for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { + mergeMappings(state, _result, valueNode[index], overridableKeys); + } + } else { + mergeMappings(state, _result, valueNode, overridableKeys); + } + } else { + if (!state.json && + !_hasOwnProperty$1.call(overridableKeys, keyNode) && + _hasOwnProperty$1.call(_result, keyNode)) { + state.line = startLine || state.line; + state.lineStart = startLineStart || state.lineStart; + state.position = startPos || state.position; + throwError(state, 'duplicated mapping key'); + } + + // used for this specific key only because Object.defineProperty is slow + if (keyNode === '__proto__') { + Object.defineProperty(_result, keyNode, { + configurable: true, + enumerable: true, + writable: true, + value: valueNode + }); + } else { + _result[keyNode] = valueNode; + } + delete overridableKeys[keyNode]; + } + + return _result; + } + + function readLineBreak(state) { + var ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x0A/* LF */) { + state.position++; + } else if (ch === 0x0D/* CR */) { + state.position++; + if (state.input.charCodeAt(state.position) === 0x0A/* LF */) { + state.position++; + } + } else { + throwError(state, 'a line break is expected'); + } + + state.line += 1; + state.lineStart = state.position; + state.firstTabInLine = -1; + } + + function skipSeparationSpace(state, allowComments, checkIndent) { + var lineBreaks = 0, + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + while (is_WHITE_SPACE(ch)) { + if (ch === 0x09/* Tab */ && state.firstTabInLine === -1) { + state.firstTabInLine = state.position; + } + ch = state.input.charCodeAt(++state.position); + } + + if (allowComments && ch === 0x23/* # */) { + do { + ch = state.input.charCodeAt(++state.position); + } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0); + } + + if (is_EOL(ch)) { + readLineBreak(state); + + ch = state.input.charCodeAt(state.position); + lineBreaks++; + state.lineIndent = 0; + + while (ch === 0x20/* Space */) { + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + } else { + break; + } + } + + if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { + throwWarning(state, 'deficient indentation'); + } + + return lineBreaks; + } + + function testDocumentSeparator(state) { + var _position = state.position, + ch; + + ch = state.input.charCodeAt(_position); + + // Condition state.position === state.lineStart is tested + // in parent on each call, for efficiency. No needs to test here again. + if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) && + ch === state.input.charCodeAt(_position + 1) && + ch === state.input.charCodeAt(_position + 2)) { + + _position += 3; + + ch = state.input.charCodeAt(_position); + + if (ch === 0 || is_WS_OR_EOL(ch)) { + return true; + } + } + + return false; + } + + function writeFoldedLines(state, count) { + if (count === 1) { + state.result += ' '; + } else if (count > 1) { + state.result += common.repeat('\n', count - 1); + } + } + + + function readPlainScalar(state, nodeIndent, withinFlowCollection) { + var preceding, + following, + captureStart, + captureEnd, + hasPendingContent, + _line, + _lineStart, + _lineIndent, + _kind = state.kind, + _result = state.result, + ch; + + ch = state.input.charCodeAt(state.position); + + if (is_WS_OR_EOL(ch) || + is_FLOW_INDICATOR(ch) || + ch === 0x23/* # */ || + ch === 0x26/* & */ || + ch === 0x2A/* * */ || + ch === 0x21/* ! */ || + ch === 0x7C/* | */ || + ch === 0x3E/* > */ || + ch === 0x27/* ' */ || + ch === 0x22/* " */ || + ch === 0x25/* % */ || + ch === 0x40/* @ */ || + ch === 0x60/* ` */) { + return false; + } + + if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + return false; + } + } + + state.kind = 'scalar'; + state.result = ''; + captureStart = captureEnd = state.position; + hasPendingContent = false; + + while (ch !== 0) { + if (ch === 0x3A/* : */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + break; + } + + } else if (ch === 0x23/* # */) { + preceding = state.input.charCodeAt(state.position - 1); + + if (is_WS_OR_EOL(preceding)) { + break; + } + + } else if ((state.position === state.lineStart && testDocumentSeparator(state)) || + withinFlowCollection && is_FLOW_INDICATOR(ch)) { + break; + + } else if (is_EOL(ch)) { + _line = state.line; + _lineStart = state.lineStart; + _lineIndent = state.lineIndent; + skipSeparationSpace(state, false, -1); + + if (state.lineIndent >= nodeIndent) { + hasPendingContent = true; + ch = state.input.charCodeAt(state.position); + continue; + } else { + state.position = captureEnd; + state.line = _line; + state.lineStart = _lineStart; + state.lineIndent = _lineIndent; + break; + } + } + + if (hasPendingContent) { + captureSegment(state, captureStart, captureEnd, false); + writeFoldedLines(state, state.line - _line); + captureStart = captureEnd = state.position; + hasPendingContent = false; + } + + if (!is_WHITE_SPACE(ch)) { + captureEnd = state.position + 1; + } + + ch = state.input.charCodeAt(++state.position); + } + + captureSegment(state, captureStart, captureEnd, false); + + if (state.result) { + return true; + } + + state.kind = _kind; + state.result = _result; + return false; + } + + function readSingleQuotedScalar(state, nodeIndent) { + var ch, + captureStart, captureEnd; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x27/* ' */) { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x27/* ' */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x27/* ' */) { + captureStart = state.position; + state.position++; + captureEnd = state.position; + } else { + return true; + } + + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a single quoted scalar'); + + } else { + state.position++; + captureEnd = state.position; + } + } + + throwError(state, 'unexpected end of the stream within a single quoted scalar'); + } + + function readDoubleQuotedScalar(state, nodeIndent) { + var captureStart, + captureEnd, + hexLength, + hexResult, + tmp, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x22/* " */) { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x22/* " */) { + captureSegment(state, captureStart, state.position, true); + state.position++; + return true; + + } else if (ch === 0x5C/* \ */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + + if (is_EOL(ch)) { + skipSeparationSpace(state, false, nodeIndent); + + // TODO: rework to inline fn with no type cast? + } else if (ch < 256 && simpleEscapeCheck[ch]) { + state.result += simpleEscapeMap[ch]; + state.position++; + + } else if ((tmp = escapedHexLen(ch)) > 0) { + hexLength = tmp; + hexResult = 0; + + for (; hexLength > 0; hexLength--) { + ch = state.input.charCodeAt(++state.position); + + if ((tmp = fromHexCode(ch)) >= 0) { + hexResult = (hexResult << 4) + tmp; + + } else { + throwError(state, 'expected hexadecimal character'); + } + } + + state.result += charFromCodepoint(hexResult); + + state.position++; + + } else { + throwError(state, 'unknown escape sequence'); + } + + captureStart = captureEnd = state.position; + + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a double quoted scalar'); + + } else { + state.position++; + captureEnd = state.position; + } + } + + throwError(state, 'unexpected end of the stream within a double quoted scalar'); + } + + function readFlowCollection(state, nodeIndent) { + var readNext = true, + _line, + _lineStart, + _pos, + _tag = state.tag, + _result, + _anchor = state.anchor, + following, + terminator, + isPair, + isExplicitPair, + isMapping, + overridableKeys = Object.create(null), + keyNode, + keyTag, + valueNode, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x5B/* [ */) { + terminator = 0x5D;/* ] */ + isMapping = false; + _result = []; + } else if (ch === 0x7B/* { */) { + terminator = 0x7D;/* } */ + isMapping = true; + _result = {}; + } else { + return false; + } + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(++state.position); + + while (ch !== 0) { + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if (ch === terminator) { + state.position++; + state.tag = _tag; + state.anchor = _anchor; + state.kind = isMapping ? 'mapping' : 'sequence'; + state.result = _result; + return true; + } else if (!readNext) { + throwError(state, 'missed comma between flow collection entries'); + } else if (ch === 0x2C/* , */) { + // "flow collection entries can never be completely empty", as per YAML 1.2, section 7.4 + throwError(state, "expected the node content, but found ','"); + } + + keyTag = keyNode = valueNode = null; + isPair = isExplicitPair = false; + + if (ch === 0x3F/* ? */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following)) { + isPair = isExplicitPair = true; + state.position++; + skipSeparationSpace(state, true, nodeIndent); + } + } + + _line = state.line; // Save the current line. + _lineStart = state.lineStart; + _pos = state.position; + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + keyTag = state.tag; + keyNode = state.result; + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) { + isPair = true; + ch = state.input.charCodeAt(++state.position); + skipSeparationSpace(state, true, nodeIndent); + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + valueNode = state.result; + } + + if (isMapping) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos); + } else if (isPair) { + _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos)); + } else { + _result.push(keyNode); + } + + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x2C/* , */) { + readNext = true; + ch = state.input.charCodeAt(++state.position); + } else { + readNext = false; + } + } + + throwError(state, 'unexpected end of the stream within a flow collection'); + } + + function readBlockScalar(state, nodeIndent) { + var captureStart, + folding, + chomping = CHOMPING_CLIP, + didReadContent = false, + detectedIndent = false, + textIndent = nodeIndent, + emptyLines = 0, + atMoreIndented = false, + tmp, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x7C/* | */) { + folding = false; + } else if (ch === 0x3E/* > */) { + folding = true; + } else { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + + while (ch !== 0) { + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + if (CHOMPING_CLIP === chomping) { + chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP; + } else { + throwError(state, 'repeat of a chomping mode identifier'); + } + + } else if ((tmp = fromDecimalCode(ch)) >= 0) { + if (tmp === 0) { + throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one'); + } else if (!detectedIndent) { + textIndent = nodeIndent + tmp - 1; + detectedIndent = true; + } else { + throwError(state, 'repeat of an indentation width identifier'); + } + + } else { + break; + } + } + + if (is_WHITE_SPACE(ch)) { + do { ch = state.input.charCodeAt(++state.position); } + while (is_WHITE_SPACE(ch)); + + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (!is_EOL(ch) && (ch !== 0)); + } + } + + while (ch !== 0) { + readLineBreak(state); + state.lineIndent = 0; + + ch = state.input.charCodeAt(state.position); + + while ((!detectedIndent || state.lineIndent < textIndent) && + (ch === 0x20/* Space */)) { + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + + if (!detectedIndent && state.lineIndent > textIndent) { + textIndent = state.lineIndent; + } + + if (is_EOL(ch)) { + emptyLines++; + continue; + } + + // End of the scalar. + if (state.lineIndent < textIndent) { + + // Perform the chomping. + if (chomping === CHOMPING_KEEP) { + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + } else if (chomping === CHOMPING_CLIP) { + if (didReadContent) { // i.e. only if the scalar is not empty. + state.result += '\n'; + } + } + + // Break this `while` cycle and go to the funciton's epilogue. + break; + } + + // Folded style: use fancy rules to handle line breaks. + if (folding) { + + // Lines starting with white space characters (more-indented lines) are not folded. + if (is_WHITE_SPACE(ch)) { + atMoreIndented = true; + // except for the first content line (cf. Example 8.1) + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + + // End of more-indented block. + } else if (atMoreIndented) { + atMoreIndented = false; + state.result += common.repeat('\n', emptyLines + 1); + + // Just one line break - perceive as the same line. + } else if (emptyLines === 0) { + if (didReadContent) { // i.e. only if we have already read some scalar content. + state.result += ' '; + } + + // Several line breaks - perceive as different lines. + } else { + state.result += common.repeat('\n', emptyLines); + } + + // Literal style: just add exact number of line breaks between content lines. + } else { + // Keep all line breaks except the header line break. + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + } + + didReadContent = true; + detectedIndent = true; + emptyLines = 0; + captureStart = state.position; + + while (!is_EOL(ch) && (ch !== 0)) { + ch = state.input.charCodeAt(++state.position); + } + + captureSegment(state, captureStart, state.position, false); + } + + return true; + } + + function readBlockSequence(state, nodeIndent) { + var _line, + _tag = state.tag, + _anchor = state.anchor, + _result = [], + following, + detected = false, + ch; + + // there is a leading tab before this token, so it can't be a block sequence/mapping; + // it can still be flow sequence/mapping or a scalar + if (state.firstTabInLine !== -1) return false; + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + if (state.firstTabInLine !== -1) { + state.position = state.firstTabInLine; + throwError(state, 'tab characters must not be used in indentation'); + } + + if (ch !== 0x2D/* - */) { + break; + } + + following = state.input.charCodeAt(state.position + 1); + + if (!is_WS_OR_EOL(following)) { + break; + } + + detected = true; + state.position++; + + if (skipSeparationSpace(state, true, -1)) { + if (state.lineIndent <= nodeIndent) { + _result.push(null); + ch = state.input.charCodeAt(state.position); + continue; + } + } + + _line = state.line; + composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true); + _result.push(state.result); + skipSeparationSpace(state, true, -1); + + ch = state.input.charCodeAt(state.position); + + if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { + throwError(state, 'bad indentation of a sequence entry'); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'sequence'; + state.result = _result; + return true; + } + return false; + } + + function readBlockMapping(state, nodeIndent, flowIndent) { + var following, + allowCompact, + _line, + _keyLine, + _keyLineStart, + _keyPos, + _tag = state.tag, + _anchor = state.anchor, + _result = {}, + overridableKeys = Object.create(null), + keyTag = null, + keyNode = null, + valueNode = null, + atExplicitKey = false, + detected = false, + ch; + + // there is a leading tab before this token, so it can't be a block sequence/mapping; + // it can still be flow sequence/mapping or a scalar + if (state.firstTabInLine !== -1) return false; + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + if (!atExplicitKey && state.firstTabInLine !== -1) { + state.position = state.firstTabInLine; + throwError(state, 'tab characters must not be used in indentation'); + } + + following = state.input.charCodeAt(state.position + 1); + _line = state.line; // Save the current line. + + // + // Explicit notation case. There are two separate blocks: + // first for the key (denoted by "?") and second for the value (denoted by ":") + // + if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) { + + if (ch === 0x3F/* ? */) { + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } + + detected = true; + atExplicitKey = true; + allowCompact = true; + + } else if (atExplicitKey) { + // i.e. 0x3A/* : */ === character after the explicit key. + atExplicitKey = false; + allowCompact = true; + + } else { + throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line'); + } + + state.position += 1; + ch = following; + + // + // Implicit notation case. Flow-style node as the key first, then ":", and the value. + // + } else { + _keyLine = state.line; + _keyLineStart = state.lineStart; + _keyPos = state.position; + + if (!composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) { + // Neither implicit nor explicit notation. + // Reading is done. Go to the epilogue. + break; + } + + if (state.line === _line) { + ch = state.input.charCodeAt(state.position); + + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (ch === 0x3A/* : */) { + ch = state.input.charCodeAt(++state.position); + + if (!is_WS_OR_EOL(ch)) { + throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping'); + } + + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } + + detected = true; + atExplicitKey = false; + allowCompact = false; + keyTag = state.tag; + keyNode = state.result; + + } else if (detected) { + throwError(state, 'can not read an implicit mapping pair; a colon is missed'); + + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } + + } else if (detected) { + throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key'); + + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } + } + + // + // Common reading code for both explicit and implicit notations. + // + if (state.line === _line || state.lineIndent > nodeIndent) { + if (atExplicitKey) { + _keyLine = state.line; + _keyLineStart = state.lineStart; + _keyPos = state.position; + } + + if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) { + if (atExplicitKey) { + keyNode = state.result; + } else { + valueNode = state.result; + } + } + + if (!atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } + + skipSeparationSpace(state, true, -1); + ch = state.input.charCodeAt(state.position); + } + + if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { + throwError(state, 'bad indentation of a mapping entry'); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + + // + // Epilogue. + // + + // Special case: last mapping's node contains only the key in explicit notation. + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + } + + // Expose the resulting mapping. + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'mapping'; + state.result = _result; + } + + return detected; + } + + function readTagProperty(state) { + var _position, + isVerbatim = false, + isNamed = false, + tagHandle, + tagName, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x21/* ! */) return false; + + if (state.tag !== null) { + throwError(state, 'duplication of a tag property'); + } + + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x3C/* < */) { + isVerbatim = true; + ch = state.input.charCodeAt(++state.position); + + } else if (ch === 0x21/* ! */) { + isNamed = true; + tagHandle = '!!'; + ch = state.input.charCodeAt(++state.position); + + } else { + tagHandle = '!'; + } + + _position = state.position; + + if (isVerbatim) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && ch !== 0x3E/* > */); + + if (state.position < state.length) { + tagName = state.input.slice(_position, state.position); + ch = state.input.charCodeAt(++state.position); + } else { + throwError(state, 'unexpected end of the stream within a verbatim tag'); + } + } else { + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + + if (ch === 0x21/* ! */) { + if (!isNamed) { + tagHandle = state.input.slice(_position - 1, state.position + 1); + + if (!PATTERN_TAG_HANDLE.test(tagHandle)) { + throwError(state, 'named tag handle cannot contain such characters'); + } + + isNamed = true; + _position = state.position + 1; + } else { + throwError(state, 'tag suffix cannot contain exclamation marks'); + } + } + + ch = state.input.charCodeAt(++state.position); + } + + tagName = state.input.slice(_position, state.position); + + if (PATTERN_FLOW_INDICATORS.test(tagName)) { + throwError(state, 'tag suffix cannot contain flow indicator characters'); + } + } + + if (tagName && !PATTERN_TAG_URI.test(tagName)) { + throwError(state, 'tag name cannot contain such characters: ' + tagName); + } + + try { + tagName = decodeURIComponent(tagName); + } catch (err) { + throwError(state, 'tag name is malformed: ' + tagName); + } + + if (isVerbatim) { + state.tag = tagName; + + } else if (_hasOwnProperty$1.call(state.tagMap, tagHandle)) { + state.tag = state.tagMap[tagHandle] + tagName; + + } else if (tagHandle === '!') { + state.tag = '!' + tagName; + + } else if (tagHandle === '!!') { + state.tag = 'tag:yaml.org,2002:' + tagName; + + } else { + throwError(state, 'undeclared tag handle "' + tagHandle + '"'); + } + + return true; + } + + function readAnchorProperty(state) { + var _position, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x26/* & */) return false; + + if (state.anchor !== null) { + throwError(state, 'duplication of an anchor property'); + } + + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (state.position === _position) { + throwError(state, 'name of an anchor node must contain at least one character'); + } + + state.anchor = state.input.slice(_position, state.position); + return true; + } + + function readAlias(state) { + var _position, alias, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x2A/* * */) return false; + + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (state.position === _position) { + throwError(state, 'name of an alias node must contain at least one character'); + } + + alias = state.input.slice(_position, state.position); + + if (!_hasOwnProperty$1.call(state.anchorMap, alias)) { + throwError(state, 'unidentified alias "' + alias + '"'); + } + + state.result = state.anchorMap[alias]; + skipSeparationSpace(state, true, -1); + return true; + } + + function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { + var allowBlockStyles, + allowBlockScalars, + allowBlockCollections, + indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } + } + + if (indentStatus === 1) { + while (readTagProperty(state) || readAnchorProperty(state)) { + if (skipSeparationSpace(state, true, -1)) { + atNewLine = true; + allowBlockCollections = allowBlockStyles; + + if (state.lineIndent > parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } else { + allowBlockCollections = false; + } + } + } + + if (allowBlockCollections) { + allowBlockCollections = atNewLine || allowCompact; + } + + if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) { + if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) { + flowIndent = parentIndent; + } else { + flowIndent = parentIndent + 1; + } + + blockIndent = state.position - state.lineStart; + + if (indentStatus === 1) { + if (allowBlockCollections && + (readBlockSequence(state, blockIndent) || + readBlockMapping(state, blockIndent, flowIndent)) || + readFlowCollection(state, flowIndent)) { + hasContent = true; + } else { + if ((allowBlockScalars && readBlockScalar(state, flowIndent)) || + readSingleQuotedScalar(state, flowIndent) || + readDoubleQuotedScalar(state, flowIndent)) { + hasContent = true; + + } else if (readAlias(state)) { + hasContent = true; + + if (state.tag !== null || state.anchor !== null) { + throwError(state, 'alias node should not have any properties'); + } + + } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) { + hasContent = true; + + if (state.tag === null) { + state.tag = '?'; + } + } + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } + } else if (indentStatus === 0) { + // Special case: block sequences are allowed to have same indentation level as the parent. + // http://www.yaml.org/spec/1.2/spec.html#id2799784 + hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); + } + } + + if (state.tag === null) { + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + + } else if (state.tag === '?') { + // Implicit resolving is not allowed for non-scalar types, and '?' + // non-specific tag is only automatically assigned to plain scalars. + // + // We only need to check kind conformity in case user explicitly assigns '?' + // tag, for example like this: "! [0]" + // + if (state.result !== null && state.kind !== 'scalar') { + throwError(state, 'unacceptable node kind for ! tag; it should be "scalar", not "' + state.kind + '"'); + } + + for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) { + type = state.implicitTypes[typeIndex]; + + if (type.resolve(state.result)) { // `state.result` updated in resolver if matched + state.result = type.construct(state.result); + state.tag = type.tag; + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + break; + } + } + } else if (state.tag !== '!') { + if (_hasOwnProperty$1.call(state.typeMap[state.kind || 'fallback'], state.tag)) { + type = state.typeMap[state.kind || 'fallback'][state.tag]; + } else { + // looking for multi type + type = null; + typeList = state.typeMap.multi[state.kind || 'fallback']; + + for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) { + if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) { + type = typeList[typeIndex]; + break; + } + } + } + + if (!type) { + throwError(state, 'unknown tag !<' + state.tag + '>'); + } + + if (state.result !== null && type.kind !== state.kind) { + throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"'); + } + + if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched + throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag'); + } else { + state.result = type.construct(state.result, state.tag); + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } + } + + if (state.listener !== null) { + state.listener('close', state); + } + return state.tag !== null || state.anchor !== null || hasContent; + } + + function readDocument(state) { + var documentStart = state.position, + _position, + directiveName, + directiveArgs, + hasDirectives = false, + ch; + + state.version = null; + state.checkLineBreaks = state.legacy; + state.tagMap = Object.create(null); + state.anchorMap = Object.create(null); + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + skipSeparationSpace(state, true, -1); + + ch = state.input.charCodeAt(state.position); + + if (state.lineIndent > 0 || ch !== 0x25/* % */) { + break; + } + + hasDirectives = true; + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + directiveName = state.input.slice(_position, state.position); + directiveArgs = []; + + if (directiveName.length < 1) { + throwError(state, 'directive name must not be less than one character in length'); + } + + while (ch !== 0) { + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && !is_EOL(ch)); + break; + } + + if (is_EOL(ch)) break; + + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + directiveArgs.push(state.input.slice(_position, state.position)); + } + + if (ch !== 0) readLineBreak(state); + + if (_hasOwnProperty$1.call(directiveHandlers, directiveName)) { + directiveHandlers[directiveName](state, directiveName, directiveArgs); + } else { + throwWarning(state, 'unknown document directive "' + directiveName + '"'); + } + } + + skipSeparationSpace(state, true, -1); + + if (state.lineIndent === 0 && + state.input.charCodeAt(state.position) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 1) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 2) === 0x2D/* - */) { + state.position += 3; + skipSeparationSpace(state, true, -1); + + } else if (hasDirectives) { + throwError(state, 'directives end mark is expected'); + } + + composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true); + skipSeparationSpace(state, true, -1); + + if (state.checkLineBreaks && + PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { + throwWarning(state, 'non-ASCII line breaks are interpreted as content'); + } + + state.documents.push(state.result); + + if (state.position === state.lineStart && testDocumentSeparator(state)) { + + if (state.input.charCodeAt(state.position) === 0x2E/* . */) { + state.position += 3; + skipSeparationSpace(state, true, -1); + } + return; + } + + if (state.position < (state.length - 1)) { + throwError(state, 'end of the stream or a document separator is expected'); + } else { + return; + } + } + + + function loadDocuments(input, options) { + input = String(input); + options = options || {}; + + if (input.length !== 0) { + + // Add tailing `\n` if not exists + if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ && + input.charCodeAt(input.length - 1) !== 0x0D/* CR */) { + input += '\n'; + } + + // Strip BOM + if (input.charCodeAt(0) === 0xFEFF) { + input = input.slice(1); + } + } + + var state = new State$1(input, options); + + var nullpos = input.indexOf('\0'); + + if (nullpos !== -1) { + state.position = nullpos; + throwError(state, 'null byte is not allowed in input'); + } + + // Use 0 as string terminator. That significantly simplifies bounds check. + state.input += '\0'; + + while (state.input.charCodeAt(state.position) === 0x20/* Space */) { + state.lineIndent += 1; + state.position += 1; + } + + while (state.position < (state.length - 1)) { + readDocument(state); + } + + return state.documents; + } + + + function loadAll$1(input, iterator, options) { + if (iterator !== null && typeof iterator === 'object' && typeof options === 'undefined') { + options = iterator; + iterator = null; + } + + var documents = loadDocuments(input, options); + + if (typeof iterator !== 'function') { + return documents; + } + + for (var index = 0, length = documents.length; index < length; index += 1) { + iterator(documents[index]); + } + } + + + function load$1(input, options) { + var documents = loadDocuments(input, options); + + if (documents.length === 0) { + /*eslint-disable no-undefined*/ + return undefined; + } else if (documents.length === 1) { + return documents[0]; + } + throw new exception('expected a single document in the stream, but found more'); + } + + + var loadAll_1 = loadAll$1; + var load_1 = load$1; + + var loader$2 = { + loadAll: loadAll_1, + load: load_1 + }; + var load = loader$2.load; + + const logger$9 = getLogger('py-env'); + // Premise used to connect to the first available runtime (can be pyodide or others) + let runtime; + runtimeLoaded.subscribe(value => { + runtime = value; + }); + class PyEnv extends HTMLElement { + shadow; + wrapper; + code; + environment; + runtime; + env; + paths; + constructor() { + super(); + this.shadow = this.attachShadow({ mode: 'open' }); + this.wrapper = document.createElement('slot'); + } + connectedCallback() { + logger$9.info("The tag is deprecated, please use instead."); + this.code = this.innerHTML; + this.innerHTML = ''; + const env = []; + const paths = []; + this.environment = load(this.code); + if (this.environment === undefined) + return; + for (const entry of Array.isArray(this.environment) ? this.environment : []) { + if (typeof entry == 'string') { + env.push(entry); + } + else if (entry && typeof entry === 'object') { + const obj = entry; + for (const path of Array.isArray(obj.paths) ? obj.paths : []) { + if (typeof path === 'string') { + paths.push(path); + } + } + } + } + this.env = env; + this.paths = paths; + async function loadEnv() { + logger$9.info("Loading env: ", env); + await runtime.installPackage(env); + } + async function loadPaths() { + logger$9.info("Paths to load: ", paths); + for (const singleFile of paths) { + logger$9.info(` loading path: ${singleFile}`); + try { + await runtime.loadFromFile(singleFile); + } + catch (e) { + //Should we still export full error contents to console? + handleFetchError(e, singleFile); + } + } + logger$9.info("All paths loaded"); + } + addInitializer(loadEnv); + addInitializer(loadPaths); + } + } + + const logger$8 = getLogger('py-loader'); + class PyLoader extends BaseEvalElement { + widths; + label; + mount_name; + details; + operation; + constructor() { + super(); + } + connectedCallback() { + this.innerHTML = `
+
+
+
+
+
+
+
+
`; + this.mount_name = this.id.split('-').join('_'); + this.operation = document.getElementById('pyscript-operation'); + this.details = document.getElementById('pyscript-operation-details'); + } + log(msg) { + // loader messages are showed both in the HTML and in the console + logger$8.info(msg); + const newLog = document.createElement('p'); + newLog.innerText = msg; + this.details.appendChild(newLog); + } + close() { + logger$8.info('Closing'); + this.remove(); + } + } + + // Compressed representation of the Grapheme_Cluster_Break=Extend + // information from + // http://www.unicode.org/Public/13.0.0/ucd/auxiliary/GraphemeBreakProperty.txt. + // Each pair of elements represents a range, as an offet from the + // previous range and a length. Numbers are in base-36, with the empty + // string being a shorthand for 1. + let extend = /*@__PURE__*/"lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(s => s ? parseInt(s, 36) : 1); + // Convert offsets into absolute values + for (let i = 1; i < extend.length; i++) + extend[i] += extend[i - 1]; + function isExtendingChar(code) { + for (let i = 1; i < extend.length; i += 2) + if (extend[i] > code) + return extend[i - 1] <= code; + return false; + } + function isRegionalIndicator(code) { + return code >= 0x1F1E6 && code <= 0x1F1FF; + } + const ZWJ = 0x200d; + /** + Returns a next grapheme cluster break _after_ (not equal to) + `pos`, if `forward` is true, or before otherwise. Returns `pos` + itself if no further cluster break is available in the string. + Moves across surrogate pairs, extending characters (when + `includeExtending` is true), characters joined with zero-width + joiners, and flag emoji. + */ + function findClusterBreak(str, pos, forward = true, includeExtending = true) { + return (forward ? nextClusterBreak : prevClusterBreak)(str, pos, includeExtending); + } + function nextClusterBreak(str, pos, includeExtending) { + if (pos == str.length) + return pos; + // If pos is in the middle of a surrogate pair, move to its start + if (pos && surrogateLow(str.charCodeAt(pos)) && surrogateHigh(str.charCodeAt(pos - 1))) + pos--; + let prev = codePointAt(str, pos); + pos += codePointSize(prev); + while (pos < str.length) { + let next = codePointAt(str, pos); + if (prev == ZWJ || next == ZWJ || includeExtending && isExtendingChar(next)) { + pos += codePointSize(next); + prev = next; + } + else if (isRegionalIndicator(next)) { + let countBefore = 0, i = pos - 2; + while (i >= 0 && isRegionalIndicator(codePointAt(str, i))) { + countBefore++; + i -= 2; + } + if (countBefore % 2 == 0) + break; + else + pos += 2; + } + else { + break; + } + } + return pos; + } + function prevClusterBreak(str, pos, includeExtending) { + while (pos > 0) { + let found = nextClusterBreak(str, pos - 2, includeExtending); + if (found < pos) + return found; + pos--; + } + return 0; + } + function surrogateLow(ch) { return ch >= 0xDC00 && ch < 0xE000; } + function surrogateHigh(ch) { return ch >= 0xD800 && ch < 0xDC00; } + /** + Find the code point at the given position in a string (like the + [`codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) + string method). + */ + function codePointAt(str, pos) { + let code0 = str.charCodeAt(pos); + if (!surrogateHigh(code0) || pos + 1 == str.length) + return code0; + let code1 = str.charCodeAt(pos + 1); + if (!surrogateLow(code1)) + return code0; + return ((code0 - 0xd800) << 10) + (code1 - 0xdc00) + 0x10000; + } + /** + Given a Unicode codepoint, return the JavaScript string that + respresents it (like + [`String.fromCodePoint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint)). + */ + function fromCodePoint(code) { + if (code <= 0xffff) + return String.fromCharCode(code); + code -= 0x10000; + return String.fromCharCode((code >> 10) + 0xd800, (code & 1023) + 0xdc00); + } + /** + The first character that takes up two positions in a JavaScript + string. It is often useful to compare with this after calling + `codePointAt`, to figure out whether your character takes up 1 or + 2 index positions. + */ + function codePointSize(code) { return code < 0x10000 ? 1 : 2; } + + /** + Count the column position at the given offset into the string, + taking extending characters and tab size into account. + */ + function countColumn(string, tabSize, to = string.length) { + let n = 0; + for (let i = 0; i < to;) { + if (string.charCodeAt(i) == 9) { + n += tabSize - (n % tabSize); + i++; + } + else { + n++; + i = findClusterBreak(string, i); + } + } + return n; + } + /** + Find the offset that corresponds to the given column position in a + string, taking extending characters and tab size into account. By + default, the string length is returned when it is too short to + reach the column. Pass `strict` true to make it return -1 in that + situation. + */ + function findColumn(string, col, tabSize, strict) { + for (let i = 0, n = 0;;) { + if (n >= col) + return i; + if (i == string.length) + break; + n += string.charCodeAt(i) == 9 ? tabSize - (n % tabSize) : 1; + i = findClusterBreak(string, i); + } + return strict === true ? -1 : string.length; + } + + /** + The data structure for documents. + */ + class Text { + /** + @internal + */ + constructor() { } + /** + Get the line description around the given position. + */ + lineAt(pos) { + if (pos < 0 || pos > this.length) + throw new RangeError(`Invalid position ${pos} in document of length ${this.length}`); + return this.lineInner(pos, false, 1, 0); + } + /** + Get the description for the given (1-based) line number. + */ + line(n) { + if (n < 1 || n > this.lines) + throw new RangeError(`Invalid line number ${n} in ${this.lines}-line document`); + return this.lineInner(n, true, 1, 0); + } + /** + Replace a range of the text with the given content. + */ + replace(from, to, text) { + let parts = []; + this.decompose(0, from, parts, 2 /* To */); + if (text.length) + text.decompose(0, text.length, parts, 1 /* From */ | 2 /* To */); + this.decompose(to, this.length, parts, 1 /* From */); + return TextNode.from(parts, this.length - (to - from) + text.length); + } + /** + Append another document to this one. + */ + append(other) { + return this.replace(this.length, this.length, other); + } + /** + Retrieve the text between the given points. + */ + slice(from, to = this.length) { + let parts = []; + this.decompose(from, to, parts, 0); + return TextNode.from(parts, to - from); + } + /** + Test whether this text is equal to another instance. + */ + eq(other) { + if (other == this) + return true; + if (other.length != this.length || other.lines != this.lines) + return false; + let start = this.scanIdentical(other, 1), end = this.length - this.scanIdentical(other, -1); + let a = new RawTextCursor(this), b = new RawTextCursor(other); + for (let skip = start, pos = start;;) { + a.next(skip); + b.next(skip); + skip = 0; + if (a.lineBreak != b.lineBreak || a.done != b.done || a.value != b.value) + return false; + pos += a.value.length; + if (a.done || pos >= end) + return true; + } + } + /** + Iterate over the text. When `dir` is `-1`, iteration happens + from end to start. This will return lines and the breaks between + them as separate strings, and for long lines, might split lines + themselves into multiple chunks as well. + */ + iter(dir = 1) { return new RawTextCursor(this, dir); } + /** + Iterate over a range of the text. When `from` > `to`, the + iterator will run in reverse. + */ + iterRange(from, to = this.length) { return new PartialTextCursor(this, from, to); } + /** + Return a cursor that iterates over the given range of lines, + _without_ returning the line breaks between, and yielding empty + strings for empty lines. + + When `from` and `to` are given, they should be 1-based line numbers. + */ + iterLines(from, to) { + let inner; + if (from == null) { + inner = this.iter(); + } + else { + if (to == null) + to = this.lines + 1; + let start = this.line(from).from; + inner = this.iterRange(start, Math.max(start, to == this.lines + 1 ? this.length : to <= 1 ? 0 : this.line(to - 1).to)); + } + return new LineCursor(inner); + } + /** + @internal + */ + toString() { return this.sliceString(0); } + /** + Convert the document to an array of lines (which can be + deserialized again via [`Text.of`](https://codemirror.net/6/docs/ref/#text.Text^of)). + */ + toJSON() { + let lines = []; + this.flatten(lines); + return lines; + } + /** + Create a `Text` instance for the given array of lines. + */ + static of(text) { + if (text.length == 0) + throw new RangeError("A document must have at least one line"); + if (text.length == 1 && !text[0]) + return Text.empty; + return text.length <= 32 /* Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, [])); + } + } + // Leaves store an array of line strings. There are always line breaks + // between these strings. Leaves are limited in size and have to be + // contained in TextNode instances for bigger documents. + class TextLeaf extends Text { + constructor(text, length = textLength(text)) { + super(); + this.text = text; + this.length = length; + } + get lines() { return this.text.length; } + get children() { return null; } + lineInner(target, isLine, line, offset) { + for (let i = 0;; i++) { + let string = this.text[i], end = offset + string.length; + if ((isLine ? line : end) >= target) + return new Line(offset, end, line, string); + offset = end + 1; + line++; + } + } + decompose(from, to, target, open) { + let text = from <= 0 && to >= this.length ? this + : new TextLeaf(sliceText(this.text, from, to), Math.min(to, this.length) - Math.max(0, from)); + if (open & 1 /* From */) { + let prev = target.pop(); + let joined = appendText(text.text, prev.text.slice(), 0, text.length); + if (joined.length <= 32 /* Branch */) { + target.push(new TextLeaf(joined, prev.length + text.length)); + } + else { + let mid = joined.length >> 1; + target.push(new TextLeaf(joined.slice(0, mid)), new TextLeaf(joined.slice(mid))); + } + } + else { + target.push(text); + } + } + replace(from, to, text) { + if (!(text instanceof TextLeaf)) + return super.replace(from, to, text); + let lines = appendText(this.text, appendText(text.text, sliceText(this.text, 0, from)), to); + let newLen = this.length + text.length - (to - from); + if (lines.length <= 32 /* Branch */) + return new TextLeaf(lines, newLen); + return TextNode.from(TextLeaf.split(lines, []), newLen); + } + sliceString(from, to = this.length, lineSep = "\n") { + let result = ""; + for (let pos = 0, i = 0; pos <= to && i < this.text.length; i++) { + let line = this.text[i], end = pos + line.length; + if (pos > from && i) + result += lineSep; + if (from < end && to > pos) + result += line.slice(Math.max(0, from - pos), to - pos); + pos = end + 1; + } + return result; + } + flatten(target) { + for (let line of this.text) + target.push(line); + } + scanIdentical() { return 0; } + static split(text, target) { + let part = [], len = -1; + for (let line of text) { + part.push(line); + len += line.length + 1; + if (part.length == 32 /* Branch */) { + target.push(new TextLeaf(part, len)); + part = []; + len = -1; + } + } + if (len > -1) + target.push(new TextLeaf(part, len)); + return target; + } + } + // Nodes provide the tree structure of the `Text` type. They store a + // number of other nodes or leaves, taking care to balance themselves + // on changes. There are implied line breaks _between_ the children of + // a node (but not before the first or after the last child). + class TextNode extends Text { + constructor(children, length) { + super(); + this.children = children; + this.length = length; + this.lines = 0; + for (let child of children) + this.lines += child.lines; + } + lineInner(target, isLine, line, offset) { + for (let i = 0;; i++) { + let child = this.children[i], end = offset + child.length, endLine = line + child.lines - 1; + if ((isLine ? endLine : end) >= target) + return child.lineInner(target, isLine, line, offset); + offset = end + 1; + line = endLine + 1; + } + } + decompose(from, to, target, open) { + for (let i = 0, pos = 0; pos <= to && i < this.children.length; i++) { + let child = this.children[i], end = pos + child.length; + if (from <= end && to >= pos) { + let childOpen = open & ((pos <= from ? 1 /* From */ : 0) | (end >= to ? 2 /* To */ : 0)); + if (pos >= from && end <= to && !childOpen) + target.push(child); + else + child.decompose(from - pos, to - pos, target, childOpen); + } + pos = end + 1; + } + } + replace(from, to, text) { + if (text.lines < this.lines) + for (let i = 0, pos = 0; i < this.children.length; i++) { + let child = this.children[i], end = pos + child.length; + // Fast path: if the change only affects one child and the + // child's size remains in the acceptable range, only update + // that child + if (from >= pos && to <= end) { + let updated = child.replace(from - pos, to - pos, text); + let totalLines = this.lines - child.lines + updated.lines; + if (updated.lines < (totalLines >> (5 /* BranchShift */ - 1)) && + updated.lines > (totalLines >> (5 /* BranchShift */ + 1))) { + let copy = this.children.slice(); + copy[i] = updated; + return new TextNode(copy, this.length - (to - from) + text.length); + } + return super.replace(pos, end, updated); + } + pos = end + 1; + } + return super.replace(from, to, text); + } + sliceString(from, to = this.length, lineSep = "\n") { + let result = ""; + for (let i = 0, pos = 0; i < this.children.length && pos <= to; i++) { + let child = this.children[i], end = pos + child.length; + if (pos > from && i) + result += lineSep; + if (from < end && to > pos) + result += child.sliceString(from - pos, to - pos, lineSep); + pos = end + 1; + } + return result; + } + flatten(target) { + for (let child of this.children) + child.flatten(target); + } + scanIdentical(other, dir) { + if (!(other instanceof TextNode)) + return 0; + let length = 0; + let [iA, iB, eA, eB] = dir > 0 ? [0, 0, this.children.length, other.children.length] + : [this.children.length - 1, other.children.length - 1, -1, -1]; + for (;; iA += dir, iB += dir) { + if (iA == eA || iB == eB) + return length; + let chA = this.children[iA], chB = other.children[iB]; + if (chA != chB) + return length + chA.scanIdentical(chB, dir); + length += chA.length + 1; + } + } + static from(children, length = children.reduce((l, ch) => l + ch.length + 1, -1)) { + let lines = 0; + for (let ch of children) + lines += ch.lines; + if (lines < 32 /* Branch */) { + let flat = []; + for (let ch of children) + ch.flatten(flat); + return new TextLeaf(flat, length); + } + let chunk = Math.max(32 /* Branch */, lines >> 5 /* BranchShift */), maxChunk = chunk << 1, minChunk = chunk >> 1; + let chunked = [], currentLines = 0, currentLen = -1, currentChunk = []; + function add(child) { + let last; + if (child.lines > maxChunk && child instanceof TextNode) { + for (let node of child.children) + add(node); + } + else if (child.lines > minChunk && (currentLines > minChunk || !currentLines)) { + flush(); + chunked.push(child); + } + else if (child instanceof TextLeaf && currentLines && + (last = currentChunk[currentChunk.length - 1]) instanceof TextLeaf && + child.lines + last.lines <= 32 /* Branch */) { + currentLines += child.lines; + currentLen += child.length + 1; + currentChunk[currentChunk.length - 1] = new TextLeaf(last.text.concat(child.text), last.length + 1 + child.length); + } + else { + if (currentLines + child.lines > chunk) + flush(); + currentLines += child.lines; + currentLen += child.length + 1; + currentChunk.push(child); + } + } + function flush() { + if (currentLines == 0) + return; + chunked.push(currentChunk.length == 1 ? currentChunk[0] : TextNode.from(currentChunk, currentLen)); + currentLen = -1; + currentLines = currentChunk.length = 0; + } + for (let child of children) + add(child); + flush(); + return chunked.length == 1 ? chunked[0] : new TextNode(chunked, length); + } + } + Text.empty = /*@__PURE__*/new TextLeaf([""], 0); + function textLength(text) { + let length = -1; + for (let line of text) + length += line.length + 1; + return length; + } + function appendText(text, target, from = 0, to = 1e9) { + for (let pos = 0, i = 0, first = true; i < text.length && pos <= to; i++) { + let line = text[i], end = pos + line.length; + if (end >= from) { + if (end > to) + line = line.slice(0, to - pos); + if (pos < from) + line = line.slice(from - pos); + if (first) { + target[target.length - 1] += line; + first = false; + } + else + target.push(line); + } + pos = end + 1; + } + return target; + } + function sliceText(text, from, to) { + return appendText(text, [""], from, to); + } + class RawTextCursor { + constructor(text, dir = 1) { + this.dir = dir; + this.done = false; + this.lineBreak = false; + this.value = ""; + this.nodes = [text]; + this.offsets = [dir > 0 ? 1 : (text instanceof TextLeaf ? text.text.length : text.children.length) << 1]; + } + nextInner(skip, dir) { + this.done = this.lineBreak = false; + for (;;) { + let last = this.nodes.length - 1; + let top = this.nodes[last], offsetValue = this.offsets[last], offset = offsetValue >> 1; + let size = top instanceof TextLeaf ? top.text.length : top.children.length; + if (offset == (dir > 0 ? size : 0)) { + if (last == 0) { + this.done = true; + this.value = ""; + return this; + } + if (dir > 0) + this.offsets[last - 1]++; + this.nodes.pop(); + this.offsets.pop(); + } + else if ((offsetValue & 1) == (dir > 0 ? 0 : 1)) { + this.offsets[last] += dir; + if (skip == 0) { + this.lineBreak = true; + this.value = "\n"; + return this; + } + skip--; + } + else if (top instanceof TextLeaf) { + // Move to the next string + let next = top.text[offset + (dir < 0 ? -1 : 0)]; + this.offsets[last] += dir; + if (next.length > Math.max(0, skip)) { + this.value = skip == 0 ? next : dir > 0 ? next.slice(skip) : next.slice(0, next.length - skip); + return this; + } + skip -= next.length; + } + else { + let next = top.children[offset + (dir < 0 ? -1 : 0)]; + if (skip > next.length) { + skip -= next.length; + this.offsets[last] += dir; + } + else { + if (dir < 0) + this.offsets[last]--; + this.nodes.push(next); + this.offsets.push(dir > 0 ? 1 : (next instanceof TextLeaf ? next.text.length : next.children.length) << 1); + } + } + } + } + next(skip = 0) { + if (skip < 0) { + this.nextInner(-skip, (-this.dir)); + skip = this.value.length; + } + return this.nextInner(skip, this.dir); + } + } + class PartialTextCursor { + constructor(text, start, end) { + this.value = ""; + this.done = false; + this.cursor = new RawTextCursor(text, start > end ? -1 : 1); + this.pos = start > end ? text.length : 0; + this.from = Math.min(start, end); + this.to = Math.max(start, end); + } + nextInner(skip, dir) { + if (dir < 0 ? this.pos <= this.from : this.pos >= this.to) { + this.value = ""; + this.done = true; + return this; + } + skip += Math.max(0, dir < 0 ? this.pos - this.to : this.from - this.pos); + let limit = dir < 0 ? this.pos - this.from : this.to - this.pos; + if (skip > limit) + skip = limit; + limit -= skip; + let { value } = this.cursor.next(skip); + this.pos += (value.length + skip) * dir; + this.value = value.length <= limit ? value : dir < 0 ? value.slice(value.length - limit) : value.slice(0, limit); + this.done = !this.value; + return this; + } + next(skip = 0) { + if (skip < 0) + skip = Math.max(skip, this.from - this.pos); + else if (skip > 0) + skip = Math.min(skip, this.to - this.pos); + return this.nextInner(skip, this.cursor.dir); + } + get lineBreak() { return this.cursor.lineBreak && this.value != ""; } + } + class LineCursor { + constructor(inner) { + this.inner = inner; + this.afterBreak = true; + this.value = ""; + this.done = false; + } + next(skip = 0) { + let { done, lineBreak, value } = this.inner.next(skip); + if (done) { + this.done = true; + this.value = ""; + } + else if (lineBreak) { + if (this.afterBreak) { + this.value = ""; + } + else { + this.afterBreak = true; + this.next(); + } + } + else { + this.value = value; + this.afterBreak = false; + } + return this; + } + get lineBreak() { return false; } + } + if (typeof Symbol != "undefined") { + Text.prototype[Symbol.iterator] = function () { return this.iter(); }; + RawTextCursor.prototype[Symbol.iterator] = PartialTextCursor.prototype[Symbol.iterator] = + LineCursor.prototype[Symbol.iterator] = function () { return this; }; + } + /** + This type describes a line in the document. It is created + on-demand when lines are [queried](https://codemirror.net/6/docs/ref/#text.Text.lineAt). + */ + class Line { + /** + @internal + */ + constructor( + /** + The position of the start of the line. + */ + from, + /** + The position at the end of the line (_before_ the line break, + or at the end of document for the last line). + */ + to, + /** + This line's line number (1-based). + */ + number, + /** + The line's content. + */ + text) { + this.from = from; + this.to = to; + this.number = number; + this.text = text; + } + /** + The length of the line (not including any line break after it). + */ + get length() { return this.to - this.from; } + } + + const DefaultSplit = /\r\n?|\n/; + /** + Distinguishes different ways in which positions can be mapped. + */ + var MapMode = /*@__PURE__*/(function (MapMode) { + /** + Map a position to a valid new position, even when its context + was deleted. + */ + MapMode[MapMode["Simple"] = 0] = "Simple"; + /** + Return null if deletion happens across the position. + */ + MapMode[MapMode["TrackDel"] = 1] = "TrackDel"; + /** + Return null if the character _before_ the position is deleted. + */ + MapMode[MapMode["TrackBefore"] = 2] = "TrackBefore"; + /** + Return null if the character _after_ the position is deleted. + */ + MapMode[MapMode["TrackAfter"] = 3] = "TrackAfter"; + return MapMode})(MapMode || (MapMode = {})); + /** + A change description is a variant of [change set](https://codemirror.net/6/docs/ref/#state.ChangeSet) + that doesn't store the inserted text. As such, it can't be + applied, but is cheaper to store and manipulate. + */ + class ChangeDesc { + // Sections are encoded as pairs of integers. The first is the + // length in the current document, and the second is -1 for + // unaffected sections, and the length of the replacement content + // otherwise. So an insertion would be (0, n>0), a deletion (n>0, + // 0), and a replacement two positive numbers. + /** + @internal + */ + constructor( + /** + @internal + */ + sections) { + this.sections = sections; + } + /** + The length of the document before the change. + */ + get length() { + let result = 0; + for (let i = 0; i < this.sections.length; i += 2) + result += this.sections[i]; + return result; + } + /** + The length of the document after the change. + */ + get newLength() { + let result = 0; + for (let i = 0; i < this.sections.length; i += 2) { + let ins = this.sections[i + 1]; + result += ins < 0 ? this.sections[i] : ins; + } + return result; + } + /** + False when there are actual changes in this set. + */ + get empty() { return this.sections.length == 0 || this.sections.length == 2 && this.sections[1] < 0; } + /** + Iterate over the unchanged parts left by these changes. + */ + iterGaps(f) { + for (let i = 0, posA = 0, posB = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++]; + if (ins < 0) { + f(posA, posB, len); + posB += len; + } + else { + posB += ins; + } + posA += len; + } + } + /** + Iterate over the ranges changed by these changes. (See + [`ChangeSet.iterChanges`](https://codemirror.net/6/docs/ref/#state.ChangeSet.iterChanges) for a + variant that also provides you with the inserted text.) + + When `individual` is true, adjacent changes (which are kept + separate for [position mapping](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) are + reported separately. + */ + iterChangedRanges(f, individual = false) { + iterChanges(this, f, individual); + } + /** + Get a description of the inverted form of these changes. + */ + get invertedDesc() { + let sections = []; + for (let i = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++]; + if (ins < 0) + sections.push(len, ins); + else + sections.push(ins, len); + } + return new ChangeDesc(sections); + } + /** + Compute the combined effect of applying another set of changes + after this one. The length of the document after this set should + match the length before `other`. + */ + composeDesc(other) { return this.empty ? other : other.empty ? this : composeSets(this, other); } + /** + Map this description, which should start with the same document + as `other`, over another set of changes, so that it can be + applied after it. When `before` is true, map as if the changes + in `other` happened before the ones in `this`. + */ + mapDesc(other, before = false) { return other.empty ? this : mapSet(this, other, before); } + mapPos(pos, assoc = -1, mode = MapMode.Simple) { + let posA = 0, posB = 0; + for (let i = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++], endA = posA + len; + if (ins < 0) { + if (endA > pos) + return posB + (pos - posA); + posB += len; + } + else { + if (mode != MapMode.Simple && endA >= pos && + (mode == MapMode.TrackDel && posA < pos && endA > pos || + mode == MapMode.TrackBefore && posA < pos || + mode == MapMode.TrackAfter && endA > pos)) + return null; + if (endA > pos || endA == pos && assoc < 0 && !len) + return pos == posA || assoc < 0 ? posB : posB + ins; + posB += ins; + } + posA = endA; + } + if (pos > posA) + throw new RangeError(`Position ${pos} is out of range for changeset of length ${posA}`); + return posB; + } + /** + Check whether these changes touch a given range. When one of the + changes entirely covers the range, the string `"cover"` is + returned. + */ + touchesRange(from, to = from) { + for (let i = 0, pos = 0; i < this.sections.length && pos <= to;) { + let len = this.sections[i++], ins = this.sections[i++], end = pos + len; + if (ins >= 0 && pos <= to && end >= from) + return pos < from && end > to ? "cover" : true; + pos = end; + } + return false; + } + /** + @internal + */ + toString() { + let result = ""; + for (let i = 0; i < this.sections.length;) { + let len = this.sections[i++], ins = this.sections[i++]; + result += (result ? " " : "") + len + (ins >= 0 ? ":" + ins : ""); + } + return result; + } + /** + Serialize this change desc to a JSON-representable value. + */ + toJSON() { return this.sections; } + /** + Create a change desc from its JSON representation (as produced + by [`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeDesc.toJSON). + */ + static fromJSON(json) { + if (!Array.isArray(json) || json.length % 2 || json.some(a => typeof a != "number")) + throw new RangeError("Invalid JSON representation of ChangeDesc"); + return new ChangeDesc(json); + } + } + /** + A change set represents a group of modifications to a document. It + stores the document length, and can only be applied to documents + with exactly that length. + */ + class ChangeSet extends ChangeDesc { + /** + @internal + */ + constructor(sections, + /** + @internal + */ + inserted) { + super(sections); + this.inserted = inserted; + } + /** + Apply the changes to a document, returning the modified + document. + */ + apply(doc) { + if (this.length != doc.length) + throw new RangeError("Applying change set to a document with the wrong length"); + iterChanges(this, (fromA, toA, fromB, _toB, text) => doc = doc.replace(fromB, fromB + (toA - fromA), text), false); + return doc; + } + mapDesc(other, before = false) { return mapSet(this, other, before, true); } + /** + Given the document as it existed _before_ the changes, return a + change set that represents the inverse of this set, which could + be used to go from the document created by the changes back to + the document as it existed before the changes. + */ + invert(doc) { + let sections = this.sections.slice(), inserted = []; + for (let i = 0, pos = 0; i < sections.length; i += 2) { + let len = sections[i], ins = sections[i + 1]; + if (ins >= 0) { + sections[i] = ins; + sections[i + 1] = len; + let index = i >> 1; + while (inserted.length < index) + inserted.push(Text.empty); + inserted.push(len ? doc.slice(pos, pos + len) : Text.empty); + } + pos += len; + } + return new ChangeSet(sections, inserted); + } + /** + Combine two subsequent change sets into a single set. `other` + must start in the document produced by `this`. If `this` goes + `docA` → `docB` and `other` represents `docB` → `docC`, the + returned value will represent the change `docA` → `docC`. + */ + compose(other) { return this.empty ? other : other.empty ? this : composeSets(this, other, true); } + /** + Given another change set starting in the same document, maps this + change set over the other, producing a new change set that can be + applied to the document produced by applying `other`. When + `before` is `true`, order changes as if `this` comes before + `other`, otherwise (the default) treat `other` as coming first. + + Given two changes `A` and `B`, `A.compose(B.map(A))` and + `B.compose(A.map(B, true))` will produce the same document. This + provides a basic form of [operational + transformation](https://en.wikipedia.org/wiki/Operational_transformation), + and can be used for collaborative editing. + */ + map(other, before = false) { return other.empty ? this : mapSet(this, other, before, true); } + /** + Iterate over the changed ranges in the document, calling `f` for + each, with the range in the original document (`fromA`-`toA`) + and the range that replaces it in the new document + (`fromB`-`toB`). + + When `individual` is true, adjacent changes are reported + separately. + */ + iterChanges(f, individual = false) { + iterChanges(this, f, individual); + } + /** + Get a [change description](https://codemirror.net/6/docs/ref/#state.ChangeDesc) for this change + set. + */ + get desc() { return new ChangeDesc(this.sections); } + /** + @internal + */ + filter(ranges) { + let resultSections = [], resultInserted = [], filteredSections = []; + let iter = new SectionIter(this); + done: for (let i = 0, pos = 0;;) { + let next = i == ranges.length ? 1e9 : ranges[i++]; + while (pos < next || pos == next && iter.len == 0) { + if (iter.done) + break done; + let len = Math.min(iter.len, next - pos); + addSection(filteredSections, len, -1); + let ins = iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0; + addSection(resultSections, len, ins); + if (ins > 0) + addInsert(resultInserted, resultSections, iter.text); + iter.forward(len); + pos += len; + } + let end = ranges[i++]; + while (pos < end) { + if (iter.done) + break done; + let len = Math.min(iter.len, end - pos); + addSection(resultSections, len, -1); + addSection(filteredSections, len, iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0); + iter.forward(len); + pos += len; + } + } + return { changes: new ChangeSet(resultSections, resultInserted), + filtered: new ChangeDesc(filteredSections) }; + } + /** + Serialize this change set to a JSON-representable value. + */ + toJSON() { + let parts = []; + for (let i = 0; i < this.sections.length; i += 2) { + let len = this.sections[i], ins = this.sections[i + 1]; + if (ins < 0) + parts.push(len); + else if (ins == 0) + parts.push([len]); + else + parts.push([len].concat(this.inserted[i >> 1].toJSON())); + } + return parts; + } + /** + Create a change set for the given changes, for a document of the + given length, using `lineSep` as line separator. + */ + static of(changes, length, lineSep) { + let sections = [], inserted = [], pos = 0; + let total = null; + function flush(force = false) { + if (!force && !sections.length) + return; + if (pos < length) + addSection(sections, length - pos, -1); + let set = new ChangeSet(sections, inserted); + total = total ? total.compose(set.map(total)) : set; + sections = []; + inserted = []; + pos = 0; + } + function process(spec) { + if (Array.isArray(spec)) { + for (let sub of spec) + process(sub); + } + else if (spec instanceof ChangeSet) { + if (spec.length != length) + throw new RangeError(`Mismatched change set length (got ${spec.length}, expected ${length})`); + flush(); + total = total ? total.compose(spec.map(total)) : spec; + } + else { + let { from, to = from, insert } = spec; + if (from > to || from < 0 || to > length) + throw new RangeError(`Invalid change range ${from} to ${to} (in doc of length ${length})`); + let insText = !insert ? Text.empty : typeof insert == "string" ? Text.of(insert.split(lineSep || DefaultSplit)) : insert; + let insLen = insText.length; + if (from == to && insLen == 0) + return; + if (from < pos) + flush(); + if (from > pos) + addSection(sections, from - pos, -1); + addSection(sections, to - from, insLen); + addInsert(inserted, sections, insText); + pos = to; + } + } + process(changes); + flush(!total); + return total; + } + /** + Create an empty changeset of the given length. + */ + static empty(length) { + return new ChangeSet(length ? [length, -1] : [], []); + } + /** + Create a changeset from its JSON representation (as produced by + [`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeSet.toJSON). + */ + static fromJSON(json) { + if (!Array.isArray(json)) + throw new RangeError("Invalid JSON representation of ChangeSet"); + let sections = [], inserted = []; + for (let i = 0; i < json.length; i++) { + let part = json[i]; + if (typeof part == "number") { + sections.push(part, -1); + } + else if (!Array.isArray(part) || typeof part[0] != "number" || part.some((e, i) => i && typeof e != "string")) { + throw new RangeError("Invalid JSON representation of ChangeSet"); + } + else if (part.length == 1) { + sections.push(part[0], 0); + } + else { + while (inserted.length < i) + inserted.push(Text.empty); + inserted[i] = Text.of(part.slice(1)); + sections.push(part[0], inserted[i].length); + } + } + return new ChangeSet(sections, inserted); + } + } + function addSection(sections, len, ins, forceJoin = false) { + if (len == 0 && ins <= 0) + return; + let last = sections.length - 2; + if (last >= 0 && ins <= 0 && ins == sections[last + 1]) + sections[last] += len; + else if (len == 0 && sections[last] == 0) + sections[last + 1] += ins; + else if (forceJoin) { + sections[last] += len; + sections[last + 1] += ins; + } + else + sections.push(len, ins); + } + function addInsert(values, sections, value) { + if (value.length == 0) + return; + let index = (sections.length - 2) >> 1; + if (index < values.length) { + values[values.length - 1] = values[values.length - 1].append(value); + } + else { + while (values.length < index) + values.push(Text.empty); + values.push(value); + } + } + function iterChanges(desc, f, individual) { + let inserted = desc.inserted; + for (let posA = 0, posB = 0, i = 0; i < desc.sections.length;) { + let len = desc.sections[i++], ins = desc.sections[i++]; + if (ins < 0) { + posA += len; + posB += len; + } + else { + let endA = posA, endB = posB, text = Text.empty; + for (;;) { + endA += len; + endB += ins; + if (ins && inserted) + text = text.append(inserted[(i - 2) >> 1]); + if (individual || i == desc.sections.length || desc.sections[i + 1] < 0) + break; + len = desc.sections[i++]; + ins = desc.sections[i++]; + } + f(posA, endA, posB, endB, text); + posA = endA; + posB = endB; + } + } + } + function mapSet(setA, setB, before, mkSet = false) { + let sections = [], insert = mkSet ? [] : null; + let a = new SectionIter(setA), b = new SectionIter(setB); + for (let posA = 0, posB = 0;;) { + if (a.ins == -1) { + posA += a.len; + a.next(); + } + else if (b.ins == -1 && posB < posA) { + let skip = Math.min(b.len, posA - posB); + b.forward(skip); + addSection(sections, skip, -1); + posB += skip; + } + else if (b.ins >= 0 && (a.done || posB < posA || posB == posA && (b.len < a.len || b.len == a.len && !before))) { + addSection(sections, b.ins, -1); + while (posA > posB && !a.done && posA + a.len < posB + b.len) { + posA += a.len; + a.next(); + } + posB += b.len; + b.next(); + } + else if (a.ins >= 0) { + let len = 0, end = posA + a.len; + for (;;) { + if (b.ins >= 0 && posB > posA && posB + b.len < end) { + len += b.ins; + posB += b.len; + b.next(); + } + else if (b.ins == -1 && posB < end) { + let skip = Math.min(b.len, end - posB); + len += skip; + b.forward(skip); + posB += skip; + } + else { + break; + } + } + addSection(sections, len, a.ins); + if (insert) + addInsert(insert, sections, a.text); + posA = end; + a.next(); + } + else if (a.done && b.done) { + return insert ? new ChangeSet(sections, insert) : new ChangeDesc(sections); + } + else { + throw new Error("Mismatched change set lengths"); + } + } + } + function composeSets(setA, setB, mkSet = false) { + let sections = []; + let insert = mkSet ? [] : null; + let a = new SectionIter(setA), b = new SectionIter(setB); + for (let open = false;;) { + if (a.done && b.done) { + return insert ? new ChangeSet(sections, insert) : new ChangeDesc(sections); + } + else if (a.ins == 0) { // Deletion in A + addSection(sections, a.len, 0, open); + a.next(); + } + else if (b.len == 0 && !b.done) { // Insertion in B + addSection(sections, 0, b.ins, open); + if (insert) + addInsert(insert, sections, b.text); + b.next(); + } + else if (a.done || b.done) { + throw new Error("Mismatched change set lengths"); + } + else { + let len = Math.min(a.len2, b.len), sectionLen = sections.length; + if (a.ins == -1) { + let insB = b.ins == -1 ? -1 : b.off ? 0 : b.ins; + addSection(sections, len, insB, open); + if (insert && insB) + addInsert(insert, sections, b.text); + } + else if (b.ins == -1) { + addSection(sections, a.off ? 0 : a.len, len, open); + if (insert) + addInsert(insert, sections, a.textBit(len)); + } + else { + addSection(sections, a.off ? 0 : a.len, b.off ? 0 : b.ins, open); + if (insert && !b.off) + addInsert(insert, sections, b.text); + } + open = (a.ins > len || b.ins >= 0 && b.len > len) && (open || sections.length > sectionLen); + a.forward2(len); + b.forward(len); + } + } + } + class SectionIter { + constructor(set) { + this.set = set; + this.i = 0; + this.next(); + } + next() { + let { sections } = this.set; + if (this.i < sections.length) { + this.len = sections[this.i++]; + this.ins = sections[this.i++]; + } + else { + this.len = 0; + this.ins = -2; + } + this.off = 0; + } + get done() { return this.ins == -2; } + get len2() { return this.ins < 0 ? this.len : this.ins; } + get text() { + let { inserted } = this.set, index = (this.i - 2) >> 1; + return index >= inserted.length ? Text.empty : inserted[index]; + } + textBit(len) { + let { inserted } = this.set, index = (this.i - 2) >> 1; + return index >= inserted.length && !len ? Text.empty + : inserted[index].slice(this.off, len == null ? undefined : this.off + len); + } + forward(len) { + if (len == this.len) + this.next(); + else { + this.len -= len; + this.off += len; + } + } + forward2(len) { + if (this.ins == -1) + this.forward(len); + else if (len == this.ins) + this.next(); + else { + this.ins -= len; + this.off += len; + } + } + } + + /** + A single selection range. When + [`allowMultipleSelections`](https://codemirror.net/6/docs/ref/#state.EditorState^allowMultipleSelections) + is enabled, a [selection](https://codemirror.net/6/docs/ref/#state.EditorSelection) may hold + multiple ranges. By default, selections hold exactly one range. + */ + class SelectionRange { + /** + @internal + */ + constructor( + /** + The lower boundary of the range. + */ + from, + /** + The upper boundary of the range. + */ + to, flags) { + this.from = from; + this.to = to; + this.flags = flags; + } + /** + The anchor of the range—the side that doesn't move when you + extend it. + */ + get anchor() { return this.flags & 16 /* Inverted */ ? this.to : this.from; } + /** + The head of the range, which is moved when the range is + [extended](https://codemirror.net/6/docs/ref/#state.SelectionRange.extend). + */ + get head() { return this.flags & 16 /* Inverted */ ? this.from : this.to; } + /** + True when `anchor` and `head` are at the same position. + */ + get empty() { return this.from == this.to; } + /** + If this is a cursor that is explicitly associated with the + character on one of its sides, this returns the side. -1 means + the character before its position, 1 the character after, and 0 + means no association. + */ + get assoc() { return this.flags & 4 /* AssocBefore */ ? -1 : this.flags & 8 /* AssocAfter */ ? 1 : 0; } + /** + The bidirectional text level associated with this cursor, if + any. + */ + get bidiLevel() { + let level = this.flags & 3 /* BidiLevelMask */; + return level == 3 ? null : level; + } + /** + The goal column (stored vertical offset) associated with a + cursor. This is used to preserve the vertical position when + [moving](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) across + lines of different length. + */ + get goalColumn() { + let value = this.flags >> 5 /* GoalColumnOffset */; + return value == 33554431 /* NoGoalColumn */ ? undefined : value; + } + /** + Map this range through a change, producing a valid range in the + updated document. + */ + map(change, assoc = -1) { + let from, to; + if (this.empty) { + from = to = change.mapPos(this.from, assoc); + } + else { + from = change.mapPos(this.from, 1); + to = change.mapPos(this.to, -1); + } + return from == this.from && to == this.to ? this : new SelectionRange(from, to, this.flags); + } + /** + Extend this range to cover at least `from` to `to`. + */ + extend(from, to = from) { + if (from <= this.anchor && to >= this.anchor) + return EditorSelection.range(from, to); + let head = Math.abs(from - this.anchor) > Math.abs(to - this.anchor) ? from : to; + return EditorSelection.range(this.anchor, head); + } + /** + Compare this range to another range. + */ + eq(other) { + return this.anchor == other.anchor && this.head == other.head; + } + /** + Return a JSON-serializable object representing the range. + */ + toJSON() { return { anchor: this.anchor, head: this.head }; } + /** + Convert a JSON representation of a range to a `SelectionRange` + instance. + */ + static fromJSON(json) { + if (!json || typeof json.anchor != "number" || typeof json.head != "number") + throw new RangeError("Invalid JSON representation for SelectionRange"); + return EditorSelection.range(json.anchor, json.head); + } + } + /** + An editor selection holds one or more selection ranges. + */ + class EditorSelection { + /** + @internal + */ + constructor( + /** + The ranges in the selection, sorted by position. Ranges cannot + overlap (but they may touch, if they aren't empty). + */ + ranges, + /** + The index of the _main_ range in the selection (which is + usually the range that was added last). + */ + mainIndex = 0) { + this.ranges = ranges; + this.mainIndex = mainIndex; + } + /** + Map a selection through a change. Used to adjust the selection + position for changes. + */ + map(change, assoc = -1) { + if (change.empty) + return this; + return EditorSelection.create(this.ranges.map(r => r.map(change, assoc)), this.mainIndex); + } + /** + Compare this selection to another selection. + */ + eq(other) { + if (this.ranges.length != other.ranges.length || + this.mainIndex != other.mainIndex) + return false; + for (let i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].eq(other.ranges[i])) + return false; + return true; + } + /** + Get the primary selection range. Usually, you should make sure + your code applies to _all_ ranges, by using methods like + [`changeByRange`](https://codemirror.net/6/docs/ref/#state.EditorState.changeByRange). + */ + get main() { return this.ranges[this.mainIndex]; } + /** + Make sure the selection only has one range. Returns a selection + holding only the main range from this selection. + */ + asSingle() { + return this.ranges.length == 1 ? this : new EditorSelection([this.main]); + } + /** + Extend this selection with an extra range. + */ + addRange(range, main = true) { + return EditorSelection.create([range].concat(this.ranges), main ? 0 : this.mainIndex + 1); + } + /** + Replace a given range with another range, and then normalize the + selection to merge and sort ranges if necessary. + */ + replaceRange(range, which = this.mainIndex) { + let ranges = this.ranges.slice(); + ranges[which] = range; + return EditorSelection.create(ranges, this.mainIndex); + } + /** + Convert this selection to an object that can be serialized to + JSON. + */ + toJSON() { + return { ranges: this.ranges.map(r => r.toJSON()), main: this.mainIndex }; + } + /** + Create a selection from a JSON representation. + */ + static fromJSON(json) { + if (!json || !Array.isArray(json.ranges) || typeof json.main != "number" || json.main >= json.ranges.length) + throw new RangeError("Invalid JSON representation for EditorSelection"); + return new EditorSelection(json.ranges.map((r) => SelectionRange.fromJSON(r)), json.main); + } + /** + Create a selection holding a single range. + */ + static single(anchor, head = anchor) { + return new EditorSelection([EditorSelection.range(anchor, head)], 0); + } + /** + Sort and merge the given set of ranges, creating a valid + selection. + */ + static create(ranges, mainIndex = 0) { + if (ranges.length == 0) + throw new RangeError("A selection needs at least one range"); + for (let pos = 0, i = 0; i < ranges.length; i++) { + let range = ranges[i]; + if (range.empty ? range.from <= pos : range.from < pos) + return normalized(ranges.slice(), mainIndex); + pos = range.to; + } + return new EditorSelection(ranges, mainIndex); + } + /** + Create a cursor selection range at the given position. You can + safely ignore the optional arguments in most situations. + */ + static cursor(pos, assoc = 0, bidiLevel, goalColumn) { + return new SelectionRange(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 4 /* AssocBefore */ : 8 /* AssocAfter */) | + (bidiLevel == null ? 3 : Math.min(2, bidiLevel)) | + ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */)); + } + /** + Create a selection range. + */ + static range(anchor, head, goalColumn) { + let goal = (goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */; + return head < anchor ? new SelectionRange(head, anchor, 16 /* Inverted */ | goal | 8 /* AssocAfter */) + : new SelectionRange(anchor, head, goal | (head > anchor ? 4 /* AssocBefore */ : 0)); + } + } + function normalized(ranges, mainIndex = 0) { + let main = ranges[mainIndex]; + ranges.sort((a, b) => a.from - b.from); + mainIndex = ranges.indexOf(main); + for (let i = 1; i < ranges.length; i++) { + let range = ranges[i], prev = ranges[i - 1]; + if (range.empty ? range.from <= prev.to : range.from < prev.to) { + let from = prev.from, to = Math.max(range.to, prev.to); + if (i <= mainIndex) + mainIndex--; + ranges.splice(--i, 2, range.anchor > range.head ? EditorSelection.range(to, from) : EditorSelection.range(from, to)); + } + } + return new EditorSelection(ranges, mainIndex); + } + function checkSelection(selection, docLength) { + for (let range of selection.ranges) + if (range.to > docLength) + throw new RangeError("Selection points outside of document"); + } + + let nextID = 0; + /** + A facet is a labeled value that is associated with an editor + state. It takes inputs from any number of extensions, and combines + those into a single output value. + + Examples of facets are the [theme](https://codemirror.net/6/docs/ref/#view.EditorView^theme) styles + associated with an editor or the [tab + size](https://codemirror.net/6/docs/ref/#state.EditorState^tabSize) (which is reduced to a single + value, using the input with the hightest precedence). + */ + class Facet { + constructor( + /** + @internal + */ + combine, + /** + @internal + */ + compareInput, + /** + @internal + */ + compare, isStatic, + /** + @internal + */ + extensions) { + this.combine = combine; + this.compareInput = compareInput; + this.compare = compare; + this.isStatic = isStatic; + this.extensions = extensions; + /** + @internal + */ + this.id = nextID++; + this.default = combine([]); + } + /** + Define a new facet. + */ + static define(config = {}) { + return new Facet(config.combine || ((a) => a), config.compareInput || ((a, b) => a === b), config.compare || (!config.combine ? sameArray$1 : (a, b) => a === b), !!config.static, config.enables); + } + /** + Returns an extension that adds the given value for this facet. + */ + of(value) { + return new FacetProvider([], this, 0 /* Static */, value); + } + /** + Create an extension that computes a value for the facet from a + state. You must take care to declare the parts of the state that + this value depends on, since your function is only called again + for a new state when one of those parts changed. + + In most cases, you'll want to use the + [`provide`](https://codemirror.net/6/docs/ref/#state.StateField^define^config.provide) option when + defining a field instead. + */ + compute(deps, get) { + if (this.isStatic) + throw new Error("Can't compute a static facet"); + return new FacetProvider(deps, this, 1 /* Single */, get); + } + /** + Create an extension that computes zero or more values for this + facet from a state. + */ + computeN(deps, get) { + if (this.isStatic) + throw new Error("Can't compute a static facet"); + return new FacetProvider(deps, this, 2 /* Multi */, get); + } + from(field, get) { + if (!get) + get = x => x; + return this.compute([field], state => get(state.field(field))); + } + } + function sameArray$1(a, b) { + return a == b || a.length == b.length && a.every((e, i) => e === b[i]); + } + class FacetProvider { + constructor(dependencies, facet, type, value) { + this.dependencies = dependencies; + this.facet = facet; + this.type = type; + this.value = value; + this.id = nextID++; + } + dynamicSlot(addresses) { + var _a; + let getter = this.value; + let compare = this.facet.compareInput; + let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2 /* Multi */; + let depDoc = false, depSel = false, depAddrs = []; + for (let dep of this.dependencies) { + if (dep == "doc") + depDoc = true; + else if (dep == "selection") + depSel = true; + else if ((((_a = addresses[dep.id]) !== null && _a !== void 0 ? _a : 1) & 1) == 0) + depAddrs.push(addresses[dep.id]); + } + return { + create(state) { + state.values[idx] = getter(state); + return 1 /* Changed */; + }, + update(state, tr) { + if ((depDoc && tr.docChanged) || (depSel && (tr.docChanged || tr.selection)) || + depAddrs.some(addr => (ensureAddr(state, addr) & 1 /* Changed */) > 0)) { + let newVal = getter(state); + if (multi ? !compareArray(newVal, state.values[idx], compare) : !compare(newVal, state.values[idx])) { + state.values[idx] = newVal; + return 1 /* Changed */; + } + } + return 0; + }, + reconfigure(state, oldState) { + let newVal = getter(state); + let oldAddr = oldState.config.address[id]; + if (oldAddr != null) { + let oldVal = getAddr(oldState, oldAddr); + if (multi ? compareArray(newVal, oldVal, compare) : compare(newVal, oldVal)) { + state.values[idx] = oldVal; + return 0; + } + } + state.values[idx] = newVal; + return 1 /* Changed */; + } + }; + } + } + function compareArray(a, b, compare) { + if (a.length != b.length) + return false; + for (let i = 0; i < a.length; i++) + if (!compare(a[i], b[i])) + return false; + return true; + } + function dynamicFacetSlot(addresses, facet, providers) { + let providerAddrs = providers.map(p => addresses[p.id]); + let providerTypes = providers.map(p => p.type); + let dynamic = providerAddrs.filter(p => !(p & 1)); + let idx = addresses[facet.id] >> 1; + function get(state) { + let values = []; + for (let i = 0; i < providerAddrs.length; i++) { + let value = getAddr(state, providerAddrs[i]); + if (providerTypes[i] == 2 /* Multi */) + for (let val of value) + values.push(val); + else + values.push(value); + } + return facet.combine(values); + } + return { + create(state) { + for (let addr of providerAddrs) + ensureAddr(state, addr); + state.values[idx] = get(state); + return 1 /* Changed */; + }, + update(state, tr) { + if (!dynamic.some(dynAddr => ensureAddr(state, dynAddr) & 1 /* Changed */)) + return 0; + let value = get(state); + if (facet.compare(value, state.values[idx])) + return 0; + state.values[idx] = value; + return 1 /* Changed */; + }, + reconfigure(state, oldState) { + let depChanged = providerAddrs.some(addr => ensureAddr(state, addr) & 1 /* Changed */); + let oldProviders = oldState.config.facets[facet.id], oldValue = oldState.facet(facet); + if (oldProviders && !depChanged && sameArray$1(providers, oldProviders)) { + state.values[idx] = oldValue; + return 0; + } + let value = get(state); + if (facet.compare(value, oldValue)) { + state.values[idx] = oldValue; + return 0; + } + state.values[idx] = value; + return 1 /* Changed */; + } + }; + } + const initField = /*@__PURE__*/Facet.define({ static: true }); + /** + Fields can store additional information in an editor state, and + keep it in sync with the rest of the state. + */ + class StateField { + constructor( + /** + @internal + */ + id, createF, updateF, compareF, + /** + @internal + */ + spec) { + this.id = id; + this.createF = createF; + this.updateF = updateF; + this.compareF = compareF; + this.spec = spec; + /** + @internal + */ + this.provides = undefined; + } + /** + Define a state field. + */ + static define(config) { + let field = new StateField(nextID++, config.create, config.update, config.compare || ((a, b) => a === b), config); + if (config.provide) + field.provides = config.provide(field); + return field; + } + create(state) { + let init = state.facet(initField).find(i => i.field == this); + return ((init === null || init === void 0 ? void 0 : init.create) || this.createF)(state); + } + /** + @internal + */ + slot(addresses) { + let idx = addresses[this.id] >> 1; + return { + create: (state) => { + state.values[idx] = this.create(state); + return 1 /* Changed */; + }, + update: (state, tr) => { + let oldVal = state.values[idx]; + let value = this.updateF(oldVal, tr); + if (this.compareF(oldVal, value)) + return 0; + state.values[idx] = value; + return 1 /* Changed */; + }, + reconfigure: (state, oldState) => { + if (oldState.config.address[this.id] != null) { + state.values[idx] = oldState.field(this); + return 0; + } + state.values[idx] = this.create(state); + return 1 /* Changed */; + } + }; + } + /** + Returns an extension that enables this field and overrides the + way it is initialized. Can be useful when you need to provide a + non-default starting value for the field. + */ + init(create) { + return [this, initField.of({ field: this, create })]; + } + /** + State field instances can be used as + [`Extension`](https://codemirror.net/6/docs/ref/#state.Extension) values to enable the field in a + given state. + */ + get extension() { return this; } + } + const Prec_ = { lowest: 4, low: 3, default: 2, high: 1, highest: 0 }; + function prec(value) { + return (ext) => new PrecExtension(ext, value); + } + /** + By default extensions are registered in the order they are found + in the flattened form of nested array that was provided. + Individual extension values can be assigned a precedence to + override this. Extensions that do not have a precedence set get + the precedence of the nearest parent with a precedence, or + [`default`](https://codemirror.net/6/docs/ref/#state.Prec.default) if there is no such parent. The + final ordering of extensions is determined by first sorting by + precedence and then by order within each precedence. + */ + const Prec = { + /** + The lowest precedence level. Meant for things that should end up + near the end of the extension order. + */ + lowest: /*@__PURE__*/prec(Prec_.lowest), + /** + A lower-than-default precedence, for extensions. + */ + low: /*@__PURE__*/prec(Prec_.low), + /** + The default precedence, which is also used for extensions + without an explicit precedence. + */ + default: /*@__PURE__*/prec(Prec_.default), + /** + A higher-than-default precedence, for extensions that should + come before those with default precedence. + */ + high: /*@__PURE__*/prec(Prec_.high), + /** + The highest precedence level, for extensions that should end up + near the start of the precedence ordering. + */ + highest: /*@__PURE__*/prec(Prec_.highest), + // FIXME Drop these in some future breaking version + /** + Backwards-compatible synonym for `Prec.lowest`. + */ + fallback: /*@__PURE__*/prec(Prec_.lowest), + /** + Backwards-compatible synonym for `Prec.high`. + */ + extend: /*@__PURE__*/prec(Prec_.high), + /** + Backwards-compatible synonym for `Prec.highest`. + */ + override: /*@__PURE__*/prec(Prec_.highest) + }; + class PrecExtension { + constructor(inner, prec) { + this.inner = inner; + this.prec = prec; + } + } + /** + Extension compartments can be used to make a configuration + dynamic. By [wrapping](https://codemirror.net/6/docs/ref/#state.Compartment.of) part of your + configuration in a compartment, you can later + [replace](https://codemirror.net/6/docs/ref/#state.Compartment.reconfigure) that part through a + transaction. + */ + class Compartment { + /** + Create an instance of this compartment to add to your [state + configuration](https://codemirror.net/6/docs/ref/#state.EditorStateConfig.extensions). + */ + of(ext) { return new CompartmentInstance(this, ext); } + /** + Create an [effect](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) that + reconfigures this compartment. + */ + reconfigure(content) { + return Compartment.reconfigure.of({ compartment: this, extension: content }); + } + /** + Get the current content of the compartment in the state, or + `undefined` if it isn't present. + */ + get(state) { + return state.config.compartments.get(this); + } + } + class CompartmentInstance { + constructor(compartment, inner) { + this.compartment = compartment; + this.inner = inner; + } + } + class Configuration { + constructor(base, compartments, dynamicSlots, address, staticValues, facets) { + this.base = base; + this.compartments = compartments; + this.dynamicSlots = dynamicSlots; + this.address = address; + this.staticValues = staticValues; + this.facets = facets; + this.statusTemplate = []; + while (this.statusTemplate.length < dynamicSlots.length) + this.statusTemplate.push(0 /* Unresolved */); + } + staticFacet(facet) { + let addr = this.address[facet.id]; + return addr == null ? facet.default : this.staticValues[addr >> 1]; + } + static resolve(base, compartments, oldState) { + let fields = []; + let facets = Object.create(null); + let newCompartments = new Map(); + for (let ext of flatten(base, compartments, newCompartments)) { + if (ext instanceof StateField) + fields.push(ext); + else + (facets[ext.facet.id] || (facets[ext.facet.id] = [])).push(ext); + } + let address = Object.create(null); + let staticValues = []; + let dynamicSlots = []; + for (let field of fields) { + address[field.id] = dynamicSlots.length << 1; + dynamicSlots.push(a => field.slot(a)); + } + let oldFacets = oldState === null || oldState === void 0 ? void 0 : oldState.config.facets; + for (let id in facets) { + let providers = facets[id], facet = providers[0].facet; + let oldProviders = oldFacets && oldFacets[id] || []; + if (providers.every(p => p.type == 0 /* Static */)) { + address[facet.id] = (staticValues.length << 1) | 1; + if (sameArray$1(oldProviders, providers)) { + staticValues.push(oldState.facet(facet)); + } + else { + let value = facet.combine(providers.map(p => p.value)); + staticValues.push(oldState && facet.compare(value, oldState.facet(facet)) ? oldState.facet(facet) : value); + } + } + else { + for (let p of providers) { + if (p.type == 0 /* Static */) { + address[p.id] = (staticValues.length << 1) | 1; + staticValues.push(p.value); + } + else { + address[p.id] = dynamicSlots.length << 1; + dynamicSlots.push(a => p.dynamicSlot(a)); + } + } + address[facet.id] = dynamicSlots.length << 1; + dynamicSlots.push(a => dynamicFacetSlot(a, facet, providers)); + } + } + let dynamic = dynamicSlots.map(f => f(address)); + return new Configuration(base, newCompartments, dynamic, address, staticValues, facets); + } + } + function flatten(extension, compartments, newCompartments) { + let result = [[], [], [], [], []]; + let seen = new Map(); + function inner(ext, prec) { + let known = seen.get(ext); + if (known != null) { + if (known >= prec) + return; + let found = result[known].indexOf(ext); + if (found > -1) + result[known].splice(found, 1); + if (ext instanceof CompartmentInstance) + newCompartments.delete(ext.compartment); + } + seen.set(ext, prec); + if (Array.isArray(ext)) { + for (let e of ext) + inner(e, prec); + } + else if (ext instanceof CompartmentInstance) { + if (newCompartments.has(ext.compartment)) + throw new RangeError(`Duplicate use of compartment in extensions`); + let content = compartments.get(ext.compartment) || ext.inner; + newCompartments.set(ext.compartment, content); + inner(content, prec); + } + else if (ext instanceof PrecExtension) { + inner(ext.inner, ext.prec); + } + else if (ext instanceof StateField) { + result[prec].push(ext); + if (ext.provides) + inner(ext.provides, prec); + } + else if (ext instanceof FacetProvider) { + result[prec].push(ext); + if (ext.facet.extensions) + inner(ext.facet.extensions, prec); + } + else { + let content = ext.extension; + if (!content) + throw new Error(`Unrecognized extension value in extension set (${ext}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`); + inner(content, prec); + } + } + inner(extension, Prec_.default); + return result.reduce((a, b) => a.concat(b)); + } + function ensureAddr(state, addr) { + if (addr & 1) + return 2 /* Computed */; + let idx = addr >> 1; + let status = state.status[idx]; + if (status == 4 /* Computing */) + throw new Error("Cyclic dependency between fields and/or facets"); + if (status & 2 /* Computed */) + return status; + state.status[idx] = 4 /* Computing */; + let changed = state.computeSlot(state, state.config.dynamicSlots[idx]); + return state.status[idx] = 2 /* Computed */ | changed; + } + function getAddr(state, addr) { + return addr & 1 ? state.config.staticValues[addr >> 1] : state.values[addr >> 1]; + } + + const languageData = /*@__PURE__*/Facet.define(); + const allowMultipleSelections = /*@__PURE__*/Facet.define({ + combine: values => values.some(v => v), + static: true + }); + const lineSeparator = /*@__PURE__*/Facet.define({ + combine: values => values.length ? values[0] : undefined, + static: true + }); + const changeFilter = /*@__PURE__*/Facet.define(); + const transactionFilter = /*@__PURE__*/Facet.define(); + const transactionExtender = /*@__PURE__*/Facet.define(); + const readOnly = /*@__PURE__*/Facet.define({ + combine: values => values.length ? values[0] : false + }); + + /** + Annotations are tagged values that are used to add metadata to + transactions in an extensible way. They should be used to model + things that effect the entire transaction (such as its [time + stamp](https://codemirror.net/6/docs/ref/#state.Transaction^time) or information about its + [origin](https://codemirror.net/6/docs/ref/#state.Transaction^userEvent)). For effects that happen + _alongside_ the other changes made by the transaction, [state + effects](https://codemirror.net/6/docs/ref/#state.StateEffect) are more appropriate. + */ + class Annotation { + /** + @internal + */ + constructor( + /** + The annotation type. + */ + type, + /** + The value of this annotation. + */ + value) { + this.type = type; + this.value = value; + } + /** + Define a new type of annotation. + */ + static define() { return new AnnotationType(); } + } + /** + Marker that identifies a type of [annotation](https://codemirror.net/6/docs/ref/#state.Annotation). + */ + class AnnotationType { + /** + Create an instance of this annotation. + */ + of(value) { return new Annotation(this, value); } + } + /** + Representation of a type of state effect. Defined with + [`StateEffect.define`](https://codemirror.net/6/docs/ref/#state.StateEffect^define). + */ + class StateEffectType { + /** + @internal + */ + constructor( + // The `any` types in these function types are there to work + // around TypeScript issue #37631, where the type guard on + // `StateEffect.is` mysteriously stops working when these properly + // have type `Value`. + /** + @internal + */ + map) { + this.map = map; + } + /** + Create a [state effect](https://codemirror.net/6/docs/ref/#state.StateEffect) instance of this + type. + */ + of(value) { return new StateEffect(this, value); } + } + /** + State effects can be used to represent additional effects + associated with a [transaction](https://codemirror.net/6/docs/ref/#state.Transaction.effects). They + are often useful to model changes to custom [state + fields](https://codemirror.net/6/docs/ref/#state.StateField), when those changes aren't implicit in + document or selection changes. + */ + class StateEffect { + /** + @internal + */ + constructor( + /** + @internal + */ + type, + /** + The value of this effect. + */ + value) { + this.type = type; + this.value = value; + } + /** + Map this effect through a position mapping. Will return + `undefined` when that ends up deleting the effect. + */ + map(mapping) { + let mapped = this.type.map(this.value, mapping); + return mapped === undefined ? undefined : mapped == this.value ? this : new StateEffect(this.type, mapped); + } + /** + Tells you whether this effect object is of a given + [type](https://codemirror.net/6/docs/ref/#state.StateEffectType). + */ + is(type) { return this.type == type; } + /** + Define a new effect type. The type parameter indicates the type + of values that his effect holds. + */ + static define(spec = {}) { + return new StateEffectType(spec.map || (v => v)); + } + /** + Map an array of effects through a change set. + */ + static mapEffects(effects, mapping) { + if (!effects.length) + return effects; + let result = []; + for (let effect of effects) { + let mapped = effect.map(mapping); + if (mapped) + result.push(mapped); + } + return result; + } + } + /** + This effect can be used to reconfigure the root extensions of + the editor. Doing this will discard any extensions + [appended](https://codemirror.net/6/docs/ref/#state.StateEffect^appendConfig), but does not reset + the content of [reconfigured](https://codemirror.net/6/docs/ref/#state.Compartment.reconfigure) + compartments. + */ + StateEffect.reconfigure = /*@__PURE__*/StateEffect.define(); + /** + Append extensions to the top-level configuration of the editor. + */ + StateEffect.appendConfig = /*@__PURE__*/StateEffect.define(); + /** + Changes to the editor state are grouped into transactions. + Typically, a user action creates a single transaction, which may + contain any number of document changes, may change the selection, + or have other effects. Create a transaction by calling + [`EditorState.update`](https://codemirror.net/6/docs/ref/#state.EditorState.update). + */ + class Transaction { + /** + @internal + */ + constructor( + /** + The state from which the transaction starts. + */ + startState, + /** + The document changes made by this transaction. + */ + changes, + /** + The selection set by this transaction, or undefined if it + doesn't explicitly set a selection. + */ + selection, + /** + The effects added to the transaction. + */ + effects, + /** + @internal + */ + annotations, + /** + Whether the selection should be scrolled into view after this + transaction is dispatched. + */ + scrollIntoView) { + this.startState = startState; + this.changes = changes; + this.selection = selection; + this.effects = effects; + this.annotations = annotations; + this.scrollIntoView = scrollIntoView; + /** + @internal + */ + this._doc = null; + /** + @internal + */ + this._state = null; + if (selection) + checkSelection(selection, changes.newLength); + if (!annotations.some((a) => a.type == Transaction.time)) + this.annotations = annotations.concat(Transaction.time.of(Date.now())); + } + /** + The new document produced by the transaction. Contrary to + [`.state`](https://codemirror.net/6/docs/ref/#state.Transaction.state)`.doc`, accessing this won't + force the entire new state to be computed right away, so it is + recommended that [transaction + filters](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter) use this getter + when they need to look at the new document. + */ + get newDoc() { + return this._doc || (this._doc = this.changes.apply(this.startState.doc)); + } + /** + The new selection produced by the transaction. If + [`this.selection`](https://codemirror.net/6/docs/ref/#state.Transaction.selection) is undefined, + this will [map](https://codemirror.net/6/docs/ref/#state.EditorSelection.map) the start state's + current selection through the changes made by the transaction. + */ + get newSelection() { + return this.selection || this.startState.selection.map(this.changes); + } + /** + The new state created by the transaction. Computed on demand + (but retained for subsequent access), so itis recommended not to + access it in [transaction + filters](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter) when possible. + */ + get state() { + if (!this._state) + this.startState.applyTransaction(this); + return this._state; + } + /** + Get the value of the given annotation type, if any. + */ + annotation(type) { + for (let ann of this.annotations) + if (ann.type == type) + return ann.value; + return undefined; + } + /** + Indicates whether the transaction changed the document. + */ + get docChanged() { return !this.changes.empty; } + /** + Indicates whether this transaction reconfigures the state + (through a [configuration compartment](https://codemirror.net/6/docs/ref/#state.Compartment) or + with a top-level configuration + [effect](https://codemirror.net/6/docs/ref/#state.StateEffect^reconfigure). + */ + get reconfigured() { return this.startState.config != this.state.config; } + /** + Returns true if the transaction has a [user + event](https://codemirror.net/6/docs/ref/#state.Transaction^userEvent) annotation that is equal to + or more specific than `event`. For example, if the transaction + has `"select.pointer"` as user event, `"select"` and + `"select.pointer"` will match it. + */ + isUserEvent(event) { + let e = this.annotation(Transaction.userEvent); + return !!(e && (e == event || e.length > event.length && e.slice(0, event.length) == event && e[event.length] == ".")); + } + } + /** + Annotation used to store transaction timestamps. + */ + Transaction.time = /*@__PURE__*/Annotation.define(); + /** + Annotation used to associate a transaction with a user interface + event. Holds a string identifying the event, using a + dot-separated format to support attaching more specific + information. The events used by the core libraries are: + + - `"input"` when content is entered + - `"input.type"` for typed input + - `"input.type.compose"` for composition + - `"input.paste"` for pasted input + - `"input.drop"` when adding content with drag-and-drop + - `"input.complete"` when autocompleting + - `"delete"` when the user deletes content + - `"delete.selection"` when deleting the selection + - `"delete.forward"` when deleting forward from the selection + - `"delete.backward"` when deleting backward from the selection + - `"delete.cut"` when cutting to the clipboard + - `"move"` when content is moved + - `"move.drop"` when content is moved within the editor through drag-and-drop + - `"select"` when explicitly changing the selection + - `"select.pointer"` when selecting with a mouse or other pointing device + - `"undo"` and `"redo"` for history actions + + Use [`isUserEvent`](https://codemirror.net/6/docs/ref/#state.Transaction.isUserEvent) to check + whether the annotation matches a given event. + */ + Transaction.userEvent = /*@__PURE__*/Annotation.define(); + /** + Annotation indicating whether a transaction should be added to + the undo history or not. + */ + Transaction.addToHistory = /*@__PURE__*/Annotation.define(); + /** + Annotation indicating (when present and true) that a transaction + represents a change made by some other actor, not the user. This + is used, for example, to tag other people's changes in + collaborative editing. + */ + Transaction.remote = /*@__PURE__*/Annotation.define(); + function joinRanges(a, b) { + let result = []; + for (let iA = 0, iB = 0;;) { + let from, to; + if (iA < a.length && (iB == b.length || b[iB] >= a[iA])) { + from = a[iA++]; + to = a[iA++]; + } + else if (iB < b.length) { + from = b[iB++]; + to = b[iB++]; + } + else + return result; + if (!result.length || result[result.length - 1] < from) + result.push(from, to); + else if (result[result.length - 1] < to) + result[result.length - 1] = to; + } + } + function mergeTransaction(a, b, sequential) { + var _a; + let mapForA, mapForB, changes; + if (sequential) { + mapForA = b.changes; + mapForB = ChangeSet.empty(b.changes.length); + changes = a.changes.compose(b.changes); + } + else { + mapForA = b.changes.map(a.changes); + mapForB = a.changes.mapDesc(b.changes, true); + changes = a.changes.compose(mapForA); + } + return { + changes, + selection: b.selection ? b.selection.map(mapForB) : (_a = a.selection) === null || _a === void 0 ? void 0 : _a.map(mapForA), + effects: StateEffect.mapEffects(a.effects, mapForA).concat(StateEffect.mapEffects(b.effects, mapForB)), + annotations: a.annotations.length ? a.annotations.concat(b.annotations) : b.annotations, + scrollIntoView: a.scrollIntoView || b.scrollIntoView + }; + } + function resolveTransactionInner(state, spec, docSize) { + let sel = spec.selection, annotations = asArray$1(spec.annotations); + if (spec.userEvent) + annotations = annotations.concat(Transaction.userEvent.of(spec.userEvent)); + return { + changes: spec.changes instanceof ChangeSet ? spec.changes + : ChangeSet.of(spec.changes || [], docSize, state.facet(lineSeparator)), + selection: sel && (sel instanceof EditorSelection ? sel : EditorSelection.single(sel.anchor, sel.head)), + effects: asArray$1(spec.effects), + annotations, + scrollIntoView: !!spec.scrollIntoView + }; + } + function resolveTransaction(state, specs, filter) { + let s = resolveTransactionInner(state, specs.length ? specs[0] : {}, state.doc.length); + if (specs.length && specs[0].filter === false) + filter = false; + for (let i = 1; i < specs.length; i++) { + if (specs[i].filter === false) + filter = false; + let seq = !!specs[i].sequential; + s = mergeTransaction(s, resolveTransactionInner(state, specs[i], seq ? s.changes.newLength : state.doc.length), seq); + } + let tr = new Transaction(state, s.changes, s.selection, s.effects, s.annotations, s.scrollIntoView); + return extendTransaction(filter ? filterTransaction(tr) : tr); + } + // Finish a transaction by applying filters if necessary. + function filterTransaction(tr) { + let state = tr.startState; + // Change filters + let result = true; + for (let filter of state.facet(changeFilter)) { + let value = filter(tr); + if (value === false) { + result = false; + break; + } + if (Array.isArray(value)) + result = result === true ? value : joinRanges(result, value); + } + if (result !== true) { + let changes, back; + if (result === false) { + back = tr.changes.invertedDesc; + changes = ChangeSet.empty(state.doc.length); + } + else { + let filtered = tr.changes.filter(result); + changes = filtered.changes; + back = filtered.filtered.invertedDesc; + } + tr = new Transaction(state, changes, tr.selection && tr.selection.map(back), StateEffect.mapEffects(tr.effects, back), tr.annotations, tr.scrollIntoView); + } + // Transaction filters + let filters = state.facet(transactionFilter); + for (let i = filters.length - 1; i >= 0; i--) { + let filtered = filters[i](tr); + if (filtered instanceof Transaction) + tr = filtered; + else if (Array.isArray(filtered) && filtered.length == 1 && filtered[0] instanceof Transaction) + tr = filtered[0]; + else + tr = resolveTransaction(state, asArray$1(filtered), false); + } + return tr; + } + function extendTransaction(tr) { + let state = tr.startState, extenders = state.facet(transactionExtender), spec = tr; + for (let i = extenders.length - 1; i >= 0; i--) { + let extension = extenders[i](tr); + if (extension && Object.keys(extension).length) + spec = mergeTransaction(tr, resolveTransactionInner(state, extension, tr.changes.newLength), true); + } + return spec == tr ? tr : new Transaction(state, tr.changes, tr.selection, spec.effects, spec.annotations, spec.scrollIntoView); + } + const none$3 = []; + function asArray$1(value) { + return value == null ? none$3 : Array.isArray(value) ? value : [value]; + } + + /** + The categories produced by a [character + categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer). These are used + do things like selecting by word. + */ + var CharCategory = /*@__PURE__*/(function (CharCategory) { + /** + Word characters. + */ + CharCategory[CharCategory["Word"] = 0] = "Word"; + /** + Whitespace. + */ + CharCategory[CharCategory["Space"] = 1] = "Space"; + /** + Anything else. + */ + CharCategory[CharCategory["Other"] = 2] = "Other"; + return CharCategory})(CharCategory || (CharCategory = {})); + const nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + let wordChar; + try { + wordChar = /*@__PURE__*/new RegExp("[\\p{Alphabetic}\\p{Number}_]", "u"); + } + catch (_) { } + function hasWordChar(str) { + if (wordChar) + return wordChar.test(str); + for (let i = 0; i < str.length; i++) { + let ch = str[i]; + if (/\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))) + return true; + } + return false; + } + function makeCategorizer(wordChars) { + return (char) => { + if (!/\S/.test(char)) + return CharCategory.Space; + if (hasWordChar(char)) + return CharCategory.Word; + for (let i = 0; i < wordChars.length; i++) + if (char.indexOf(wordChars[i]) > -1) + return CharCategory.Word; + return CharCategory.Other; + }; + } + + /** + The editor state class is a persistent (immutable) data structure. + To update a state, you [create](https://codemirror.net/6/docs/ref/#state.EditorState.update) a + [transaction](https://codemirror.net/6/docs/ref/#state.Transaction), which produces a _new_ state + instance, without modifying the original object. + + As such, _never_ mutate properties of a state directly. That'll + just break things. + */ + class EditorState { + /** + @internal + */ + constructor( + /** + @internal + */ + config, + /** + The current document. + */ + doc, + /** + The current selection. + */ + selection, + /** + @internal + */ + values, computeSlot, tr) { + this.config = config; + this.doc = doc; + this.selection = selection; + this.values = values; + this.status = config.statusTemplate.slice(); + this.computeSlot = computeSlot; + // Fill in the computed state immediately, so that further queries + // for it made during the update return this state + if (tr) + tr._state = this; + for (let i = 0; i < this.config.dynamicSlots.length; i++) + ensureAddr(this, i << 1); + this.computeSlot = null; + } + field(field, require = true) { + let addr = this.config.address[field.id]; + if (addr == null) { + if (require) + throw new RangeError("Field is not present in this state"); + return undefined; + } + ensureAddr(this, addr); + return getAddr(this, addr); + } + /** + Create a [transaction](https://codemirror.net/6/docs/ref/#state.Transaction) that updates this + state. Any number of [transaction specs](https://codemirror.net/6/docs/ref/#state.TransactionSpec) + can be passed. Unless + [`sequential`](https://codemirror.net/6/docs/ref/#state.TransactionSpec.sequential) is set, the + [changes](https://codemirror.net/6/docs/ref/#state.TransactionSpec.changes) (if any) of each spec + are assumed to start in the _current_ document (not the document + produced by previous specs), and its + [selection](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection) and + [effects](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) are assumed to refer + to the document created by its _own_ changes. The resulting + transaction contains the combined effect of all the different + specs. For [selection](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection), later + specs take precedence over earlier ones. + */ + update(...specs) { + return resolveTransaction(this, specs, true); + } + /** + @internal + */ + applyTransaction(tr) { + let conf = this.config, { base, compartments } = conf; + for (let effect of tr.effects) { + if (effect.is(Compartment.reconfigure)) { + if (conf) { + compartments = new Map; + conf.compartments.forEach((val, key) => compartments.set(key, val)); + conf = null; + } + compartments.set(effect.value.compartment, effect.value.extension); + } + else if (effect.is(StateEffect.reconfigure)) { + conf = null; + base = effect.value; + } + else if (effect.is(StateEffect.appendConfig)) { + conf = null; + base = asArray$1(base).concat(effect.value); + } + } + let startValues; + if (!conf) { + conf = Configuration.resolve(base, compartments, this); + let intermediateState = new EditorState(conf, this.doc, this.selection, conf.dynamicSlots.map(() => null), (state, slot) => slot.reconfigure(state, this), null); + startValues = intermediateState.values; + } + else { + startValues = tr.startState.values.slice(); + } + new EditorState(conf, tr.newDoc, tr.newSelection, startValues, (state, slot) => slot.update(state, tr), tr); + } + /** + Create a [transaction spec](https://codemirror.net/6/docs/ref/#state.TransactionSpec) that + replaces every selection range with the given content. + */ + replaceSelection(text) { + if (typeof text == "string") + text = this.toText(text); + return this.changeByRange(range => ({ changes: { from: range.from, to: range.to, insert: text }, + range: EditorSelection.cursor(range.from + text.length) })); + } + /** + Create a set of changes and a new selection by running the given + function for each range in the active selection. The function + can return an optional set of changes (in the coordinate space + of the start document), plus an updated range (in the coordinate + space of the document produced by the call's own changes). This + method will merge all the changes and ranges into a single + changeset and selection, and return it as a [transaction + spec](https://codemirror.net/6/docs/ref/#state.TransactionSpec), which can be passed to + [`update`](https://codemirror.net/6/docs/ref/#state.EditorState.update). + */ + changeByRange(f) { + let sel = this.selection; + let result1 = f(sel.ranges[0]); + let changes = this.changes(result1.changes), ranges = [result1.range]; + let effects = asArray$1(result1.effects); + for (let i = 1; i < sel.ranges.length; i++) { + let result = f(sel.ranges[i]); + let newChanges = this.changes(result.changes), newMapped = newChanges.map(changes); + for (let j = 0; j < i; j++) + ranges[j] = ranges[j].map(newMapped); + let mapBy = changes.mapDesc(newChanges, true); + ranges.push(result.range.map(mapBy)); + changes = changes.compose(newMapped); + effects = StateEffect.mapEffects(effects, newMapped).concat(StateEffect.mapEffects(asArray$1(result.effects), mapBy)); + } + return { + changes, + selection: EditorSelection.create(ranges, sel.mainIndex), + effects + }; + } + /** + Create a [change set](https://codemirror.net/6/docs/ref/#state.ChangeSet) from the given change + description, taking the state's document length and line + separator into account. + */ + changes(spec = []) { + if (spec instanceof ChangeSet) + return spec; + return ChangeSet.of(spec, this.doc.length, this.facet(EditorState.lineSeparator)); + } + /** + Using the state's [line + separator](https://codemirror.net/6/docs/ref/#state.EditorState^lineSeparator), create a + [`Text`](https://codemirror.net/6/docs/ref/#text.Text) instance from the given string. + */ + toText(string) { + return Text.of(string.split(this.facet(EditorState.lineSeparator) || DefaultSplit)); + } + /** + Return the given range of the document as a string. + */ + sliceDoc(from = 0, to = this.doc.length) { + return this.doc.sliceString(from, to, this.lineBreak); + } + /** + Get the value of a state [facet](https://codemirror.net/6/docs/ref/#state.Facet). + */ + facet(facet) { + let addr = this.config.address[facet.id]; + if (addr == null) + return facet.default; + ensureAddr(this, addr); + return getAddr(this, addr); + } + /** + Convert this state to a JSON-serializable object. When custom + fields should be serialized, you can pass them in as an object + mapping property names (in the resulting object, which should + not use `doc` or `selection`) to fields. + */ + toJSON(fields) { + let result = { + doc: this.sliceDoc(), + selection: this.selection.toJSON() + }; + if (fields) + for (let prop in fields) { + let value = fields[prop]; + if (value instanceof StateField) + result[prop] = value.spec.toJSON(this.field(fields[prop]), this); + } + return result; + } + /** + Deserialize a state from its JSON representation. When custom + fields should be deserialized, pass the same object you passed + to [`toJSON`](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) when serializing as + third argument. + */ + static fromJSON(json, config = {}, fields) { + if (!json || typeof json.doc != "string") + throw new RangeError("Invalid JSON representation for EditorState"); + let fieldInit = []; + if (fields) + for (let prop in fields) { + let field = fields[prop], value = json[prop]; + fieldInit.push(field.init(state => field.spec.fromJSON(value, state))); + } + return EditorState.create({ + doc: json.doc, + selection: EditorSelection.fromJSON(json.selection), + extensions: config.extensions ? fieldInit.concat([config.extensions]) : fieldInit + }); + } + /** + Create a new state. You'll usually only need this when + initializing an editor—updated states are created by applying + transactions. + */ + static create(config = {}) { + let configuration = Configuration.resolve(config.extensions || [], new Map); + let doc = config.doc instanceof Text ? config.doc + : Text.of((config.doc || "").split(configuration.staticFacet(EditorState.lineSeparator) || DefaultSplit)); + let selection = !config.selection ? EditorSelection.single(0) + : config.selection instanceof EditorSelection ? config.selection + : EditorSelection.single(config.selection.anchor, config.selection.head); + checkSelection(selection, doc.length); + if (!configuration.staticFacet(allowMultipleSelections)) + selection = selection.asSingle(); + return new EditorState(configuration, doc, selection, configuration.dynamicSlots.map(() => null), (state, slot) => slot.create(state), null); + } + /** + The size (in columns) of a tab in the document, determined by + the [`tabSize`](https://codemirror.net/6/docs/ref/#state.EditorState^tabSize) facet. + */ + get tabSize() { return this.facet(EditorState.tabSize); } + /** + Get the proper [line-break](https://codemirror.net/6/docs/ref/#state.EditorState^lineSeparator) + string for this state. + */ + get lineBreak() { return this.facet(EditorState.lineSeparator) || "\n"; } + /** + Returns true when the editor is + [configured](https://codemirror.net/6/docs/ref/#state.EditorState^readOnly) to be read-only. + */ + get readOnly() { return this.facet(readOnly); } + /** + Look up a translation for the given phrase (via the + [`phrases`](https://codemirror.net/6/docs/ref/#state.EditorState^phrases) facet), or return the + original string if no translation is found. + */ + phrase(phrase) { + for (let map of this.facet(EditorState.phrases)) + if (Object.prototype.hasOwnProperty.call(map, phrase)) + return map[phrase]; + return phrase; + } + /** + Find the values for a given language data field, provided by the + the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet. + */ + languageDataAt(name, pos, side = -1) { + let values = []; + for (let provider of this.facet(languageData)) { + for (let result of provider(this, pos, side)) { + if (Object.prototype.hasOwnProperty.call(result, name)) + values.push(result[name]); + } + } + return values; + } + /** + Return a function that can categorize strings (expected to + represent a single [grapheme cluster](https://codemirror.net/6/docs/ref/#text.findClusterBreak)) + into one of: + + - Word (contains an alphanumeric character or a character + explicitly listed in the local language's `"wordChars"` + language data, which should be a string) + - Space (contains only whitespace) + - Other (anything else) + */ + charCategorizer(at) { + return makeCategorizer(this.languageDataAt("wordChars", at).join("")); + } + /** + Find the word at the given position, meaning the range + containing all [word](https://codemirror.net/6/docs/ref/#state.CharCategory.Word) characters + around it. If no word characters are adjacent to the position, + this returns null. + */ + wordAt(pos) { + let { text, from, length } = this.doc.lineAt(pos); + let cat = this.charCategorizer(pos); + let start = pos - from, end = pos - from; + while (start > 0) { + let prev = findClusterBreak(text, start, false); + if (cat(text.slice(prev, start)) != CharCategory.Word) + break; + start = prev; + } + while (end < length) { + let next = findClusterBreak(text, end); + if (cat(text.slice(end, next)) != CharCategory.Word) + break; + end = next; + } + return start == end ? null : EditorSelection.range(start + from, end + from); + } + } + /** + A facet that, when enabled, causes the editor to allow multiple + ranges to be selected. Be careful though, because by default the + editor relies on the native DOM selection, which cannot handle + multiple selections. An extension like + [`drawSelection`](https://codemirror.net/6/docs/ref/#view.drawSelection) can be used to make + secondary selections visible to the user. + */ + EditorState.allowMultipleSelections = allowMultipleSelections; + /** + Configures the tab size to use in this state. The first + (highest-precedence) value of the facet is used. If no value is + given, this defaults to 4. + */ + EditorState.tabSize = /*@__PURE__*/Facet.define({ + combine: values => values.length ? values[0] : 4 + }); + /** + The line separator to use. By default, any of `"\n"`, `"\r\n"` + and `"\r"` is treated as a separator when splitting lines, and + lines are joined with `"\n"`. + + When you configure a value here, only that precise separator + will be used, allowing you to round-trip documents through the + editor without normalizing line separators. + */ + EditorState.lineSeparator = lineSeparator; + /** + This facet controls the value of the + [`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) getter, which is + consulted by commands and extensions that implement editing + functionality to determine whether they should apply. It + defaults to false, but when its highest-precedence value is + `true`, such functionality disables itself. + + Not to be confused with + [`EditorView.editable`](https://codemirror.net/6/docs/ref/#view.EditorView^editable), which + controls whether the editor's DOM is set to be editable (and + thus focusable). + */ + EditorState.readOnly = readOnly; + /** + Registers translation phrases. The + [`phrase`](https://codemirror.net/6/docs/ref/#state.EditorState.phrase) method will look through + all objects registered with this facet to find translations for + its argument. + */ + EditorState.phrases = /*@__PURE__*/Facet.define(); + /** + A facet used to register [language + data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) providers. + */ + EditorState.languageData = languageData; + /** + Facet used to register change filters, which are called for each + transaction (unless explicitly + [disabled](https://codemirror.net/6/docs/ref/#state.TransactionSpec.filter)), and can suppress + part of the transaction's changes. + + Such a function can return `true` to indicate that it doesn't + want to do anything, `false` to completely stop the changes in + the transaction, or a set of ranges in which changes should be + suppressed. Such ranges are represented as an array of numbers, + with each pair of two number indicating the start and end of a + range. So for example `[10, 20, 100, 110]` suppresses changes + between 10 and 20, and between 100 and 110. + */ + EditorState.changeFilter = changeFilter; + /** + Facet used to register a hook that gets a chance to update or + replace transaction specs before they are applied. This will + only be applied for transactions that don't have + [`filter`](https://codemirror.net/6/docs/ref/#state.TransactionSpec.filter) set to `false`. You + can either return a single transaction spec (possibly the input + transaction), or an array of specs (which will be combined in + the same way as the arguments to + [`EditorState.update`](https://codemirror.net/6/docs/ref/#state.EditorState.update)). + + When possible, it is recommended to avoid accessing + [`Transaction.state`](https://codemirror.net/6/docs/ref/#state.Transaction.state) in a filter, + since it will force creation of a state that will then be + discarded again, if the transaction is actually filtered. + + (This functionality should be used with care. Indiscriminately + modifying transaction is likely to break something or degrade + the user experience.) + */ + EditorState.transactionFilter = transactionFilter; + /** + This is a more limited form of + [`transactionFilter`](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter), + which can only add + [annotations](https://codemirror.net/6/docs/ref/#state.TransactionSpec.annotations) and + [effects](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects). _But_, this type + of filter runs even the transaction has disabled regular + [filtering](https://codemirror.net/6/docs/ref/#state.TransactionSpec.filter), making it suitable + for effects that don't need to touch the changes or selection, + but do want to process every transaction. + + Extenders run _after_ filters, when both are applied. + */ + EditorState.transactionExtender = transactionExtender; + Compartment.reconfigure = /*@__PURE__*/StateEffect.define(); + + /** + Utility function for combining behaviors to fill in a config + object from an array of provided configs. Will, by default, error + when a field gets two values that aren't `===`-equal, but you can + provide combine functions per field to do something else. + */ + function combineConfig(configs, defaults, // Should hold only the optional properties of Config, but I haven't managed to express that + combine = {}) { + let result = {}; + for (let config of configs) + for (let key of Object.keys(config)) { + let value = config[key], current = result[key]; + if (current === undefined) + result[key] = value; + else if (current === value || value === undefined) ; // No conflict + else if (Object.hasOwnProperty.call(combine, key)) + result[key] = combine[key](current, value); + else + throw new Error("Config merge conflict for field " + key); + } + for (let key in defaults) + if (result[key] === undefined) + result[key] = defaults[key]; + return result; + } + + const C = "\u037c"; + const COUNT = typeof Symbol == "undefined" ? "__" + C : Symbol.for(C); + const SET = typeof Symbol == "undefined" ? "__styleSet" + Math.floor(Math.random() * 1e8) : Symbol("styleSet"); + const top = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : {}; + + // :: - Style modules encapsulate a set of CSS rules defined from + // JavaScript. Their definitions are only available in a given DOM + // root after it has been _mounted_ there with `StyleModule.mount`. + // + // Style modules should be created once and stored somewhere, as + // opposed to re-creating them every time you need them. The amount of + // CSS rules generated for a given DOM root is bounded by the amount + // of style modules that were used. So to avoid leaking rules, don't + // create these dynamically, but treat them as one-time allocations. + class StyleModule { + // :: (Object + + + +
+ Misclick + + +
+
+
+ + +
+ + +
+
+ + + + + + + + + diff --git a/daring-daffodils/mobile_page/mobile_page.py b/daring-daffodils/mobile_page/mobile_page.py new file mode 100644 index 00000000..53ea41b1 --- /dev/null +++ b/daring-daffodils/mobile_page/mobile_page.py @@ -0,0 +1,262 @@ +import json +from js import WebSocket +from js import clearTimeout +from js import console +from js import document +from js import setTimeout +from js import window +from pyodide.ffi import create_proxy + +ws_url = f"ws://{window.location.hostname}:{window.location.port}/ws" +console.log(f"Starting off with {ws_url}") +ws = WebSocket.new(ws_url) + +# Initial touch coordinates (updated on touch start) +START_X = 0 +START_Y = 0 + +# Browser viewport dimensions +BROWSER_HEIGHT = window.innerHeight +BROWSER_WIDTH = window.innerWidth + +# Number of fingers currently touching the screen +NO_OF_FINGERS = 1 + +# Timer handle for detecting long-press gestures +PRESS_TIMER = None + +# Flag indicating whether a drag gesture is currently active +IS_DRAGGING = False + +# Flag indicating if a drag gesture was cancelled due to movement +DRAG_CANCELLED = False + +# Duration (in milliseconds) to recognize a long-press before enabling drag +LONG_PRESS_TIME = 300 # ms + +# Minimum movement (in pixels) to consider the gesture as a drag/scroll rather than a tap +MOVE_THRESHOLD = 5 # px + + +def create_toast(message="Hello from PyScript 🎉"): + """ + Create an in-page toast notification element. + + Parameters: + message (str): The text to display in the toast. + + Returns: + JS DOM element: The created toast
element, appended to document.body. + + Notes: + - Positioned fixed at the bottom center of the viewport. + - Initially hidden (opacity 0); can be animated in/out using CSS transitions. + """ + toast = document.createElement("div") + toast.innerText = message + toast.id = "toast" + style = toast.style + style.position = "fixed" + style.bottom = "30px" + style.left = "50%" + style.transform = "translateX(-50%)" + style.background = "#333" + style.color = "white" + style.padding = "14px 20px" + style.borderRadius = "10px" + style.fontSize = "16px" + style.zIndex = 999999 + style.opacity = "0" + style.transition = "opacity 0.5s ease, bottom 0.5s ease" + + document.body.appendChild(toast) # ← this is missing + return toast + + +async def sendCoords(x, y, click, fingers, type_): + """ + Send touch coordinates and event information to the WebSocket server. + + Parameters: + x (float): Normalized X-coordinate (relative to viewport width). + y (float): Normalized Y-coordinate (relative to viewport height). + click (bool): Whether the gesture is a click/tap. + fingers (int): Number of fingers involved in the touch event. + type_ (str): Type of gesture ('touch', 'drag', 'scroll'). + + Returns: + None + + Notes: + - Converts click to 1/0 before sending. + - Includes browser width and height in the payload. + """ + payload = { + "x": x, + "y": y, + "click": 1 if click else 0, + "fingers": fingers, + "browser_width": BROWSER_WIDTH, + "browser_height": BROWSER_HEIGHT, + "type": type_, + } + console.log(x, y, click, fingers, type_) + console.log("Sending coordinates", payload) + ws.send(json.dumps(payload)) + + +async def touch_start(event): + """ + Handle the start of a touch gesture on the touch area. + + Parameters: + event (JS TouchEvent): The touchstart event from the browser. + + Effects: + - Records initial touch coordinates (startX, startY). + - Records number of fingers in contact. + - Sets up a long-press timer to enable drag mode if the press lasts LONG_PRESS_TIME. + """ + global START_X, START_Y, NO_OF_FINGERS, IS_DRAGGING, DRAG_CANCELLED, PRESS_TIMER + touch = event.touches.item(0) # ✅ JS method to access first touch + START_X = touch.clientX + START_Y = touch.clientY + NO_OF_FINGERS = event.touches.length + IS_DRAGGING = False + DRAG_CANCELLED = False + + def enable_drag(): + global IS_DRAGGING + if not DRAG_CANCELLED: + IS_DRAGGING = True + console.log("Long press → drag mode enabled") + + PRESS_TIMER = setTimeout(create_proxy(enable_drag), LONG_PRESS_TIME) + + +async def touch_move(event): + """ + Handle movement during a touch gesture. + + Parameters: + event (JS TouchEvent): The touchmove event from the browser. + + Effects: + - Cancels drag initiation if movement exceeds MOVE_THRESHOLD before long-press. + """ + global DRAG_CANCELLED + touch = event.touches.item(0) + dx = abs(touch.clientX - START_X) + dy = abs(touch.clientY - START_Y) + + if not IS_DRAGGING and (dx > MOVE_THRESHOLD or dy > MOVE_THRESHOLD): + DRAG_CANCELLED = True + clearTimeout(PRESS_TIMER) + + +async def touch_end(event): + """ + Handle the end of a touch gesture. + + Parameters: + event (JS TouchEvent): The touchend event from the browser. + + Effects: + - Clears the long-press timer. + - Computes gesture type: 'touch', 'drag', or 'scroll'. + - Normalizes end coordinates relative to browser dimensions. + - Sends gesture data via sendCoords to the WebSocket server. + """ + global PRESS_TIMER + clearTimeout(PRESS_TIMER) + + # ✅ Access first changed touch via .item(0) + touch = event.changedTouches.item(0) + endX = touch.clientX + endY = touch.clientY + + deltaX = (endX - START_X) / BROWSER_WIDTH + deltaY = (endY - START_Y) / BROWSER_HEIGHT + + click = False + if IS_DRAGGING: + type_ = "drag" + elif DRAG_CANCELLED: + type_ = "scroll" + else: + type_ = "touch" + click = True + + console.log(f"Type: {type_}, End coords: ({endX}, {endY})") + await sendCoords(deltaX, deltaY, click, NO_OF_FINGERS, type_) + + +def update_textarea(text): + """ + Update the content of the 'copiedText' textarea element. + + Parameters: + text (str | None): Text to display in the textarea. Empty string if None. + + Effects: + - Logs the update action. + - Replaces the textarea content with the provided text. + """ + console.log("Update textarea") + doc = document.getElementById("copiedText") + doc.value = text if text else "" + + +touch_area = document.getElementById("touchArea") +touch_area.addEventListener("touchstart", create_proxy(touch_start), {"passive": True}) +touch_area.addEventListener("touchmove", create_proxy(touch_move), {"passive": True}) +touch_area.addEventListener("touchend", create_proxy(touch_end), {"passive": True}) + + +def onopen(event): # <-- accept event arg + """ + Handle WebSocket connection opening. + + Parameters: + event (JS Event): The WebSocket 'open' event. + + Effects: + - Logs connection success to the console. + """ + console.log("Connection opened from page") + + +def onmessage(event): + """ + Handle incoming WebSocket messages. + + Parameters: + event (JS MessageEvent): The WebSocket 'message' event. + + Effects: + - Parses JSON data from the server. + - If 'copied_text' is present, updates the textarea using update_textarea(). + """ + data = json.loads(event.data) + console.log(data) + if data and data.get("copied_text"): + update_textarea(data["copied_text"]) + + +def onclose(event): + """ + Handle WebSocket connection closure. + + Parameters: + event (JS Event): The WebSocket 'close' event. + + Effects: + - Logs connection closure to the console. + """ + console.log("Connection closed") + + +# Add event listeners +ws.addEventListener("open", create_proxy(onopen)) +ws.addEventListener("message", create_proxy(onmessage)) +ws.addEventListener("close", create_proxy(onclose)) diff --git a/daring-daffodils/mobile_page/static/brick_wall.png b/daring-daffodils/mobile_page/static/brick_wall.png new file mode 100644 index 00000000..2c65651f Binary files /dev/null and b/daring-daffodils/mobile_page/static/brick_wall.png differ diff --git a/daring-daffodils/mobile_page/static/misclick_icon.png b/daring-daffodils/mobile_page/static/misclick_icon.png new file mode 100644 index 00000000..7bdc59e2 Binary files /dev/null and b/daring-daffodils/mobile_page/static/misclick_icon.png differ diff --git a/daring-daffodils/mobile_page/static/mouse_pointer.png b/daring-daffodils/mobile_page/static/mouse_pointer.png new file mode 100644 index 00000000..003a215e Binary files /dev/null and b/daring-daffodils/mobile_page/static/mouse_pointer.png differ diff --git a/daring-daffodils/mobile_page/static/toucharea_background.png b/daring-daffodils/mobile_page/static/toucharea_background.png new file mode 100644 index 00000000..dae4ed47 Binary files /dev/null and b/daring-daffodils/mobile_page/static/toucharea_background.png differ diff --git a/daring-daffodils/poetry.lock b/daring-daffodils/poetry.lock new file mode 100644 index 00000000..7b137e36 --- /dev/null +++ b/daring-daffodils/poetry.lock @@ -0,0 +1,1313 @@ +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.10.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "fastapi" +version = "0.116.1" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"}, + {file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"}, +] + +[package.dependencies] +email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"standard\""} +fastapi-cli = {version = ">=0.0.8", extras = ["standard"], optional = true, markers = "extra == \"standard\""} +httpx = {version = ">=0.23.0", optional = true, markers = "extra == \"standard\""} +jinja2 = {version = ">=3.1.5", optional = true, markers = "extra == \"standard\""} +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +python-multipart = {version = ">=0.0.18", optional = true, markers = "extra == \"standard\""} +starlette = ">=0.40.0,<0.48.0" +typing-extensions = ">=4.8.0" +uvicorn = {version = ">=0.12.0", extras = ["standard"], optional = true, markers = "extra == \"standard\""} + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fastapi-cli" +version = "0.0.8" +description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi_cli-0.0.8-py3-none-any.whl", hash = "sha256:0ea95d882c85b9219a75a65ab27e8da17dac02873e456850fa0a726e96e985eb"}, + {file = "fastapi_cli-0.0.8.tar.gz", hash = "sha256:2360f2989b1ab4a3d7fc8b3a0b20e8288680d8af2e31de7c38309934d7f8a0ee"}, +] + +[package.dependencies] +fastapi-cloud-cli = {version = ">=0.1.1", optional = true, markers = "extra == \"standard\""} +rich-toolkit = ">=0.14.8" +typer = ">=0.15.1" +uvicorn = {version = ">=0.15.0", extras = ["standard"]} + +[package.extras] +standard = ["fastapi-cloud-cli (>=0.1.1)", "uvicorn[standard] (>=0.15.0)"] +standard-no-fastapi-cloud-cli = ["uvicorn[standard] (>=0.15.0)"] + +[[package]] +name = "fastapi-cloud-cli" +version = "0.1.5" +description = "Deploy and manage FastAPI Cloud apps from the command line 🚀" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi_cloud_cli-0.1.5-py3-none-any.whl", hash = "sha256:d80525fb9c0e8af122370891f9fa83cf5d496e4ad47a8dd26c0496a6c85a012a"}, + {file = "fastapi_cloud_cli-0.1.5.tar.gz", hash = "sha256:341ee585eb731a6d3c3656cb91ad38e5f39809bf1a16d41de1333e38635a7937"}, +] + +[package.dependencies] +httpx = ">=0.27.0" +pydantic = {version = ">=1.6.1", extras = ["email"]} +rich-toolkit = ">=0.14.5" +rignore = ">=0.5.1" +sentry-sdk = ">=2.20.0" +typer = ">=0.12.3" +uvicorn = {version = ">=0.15.0", extras = ["standard"]} + +[package.extras] +standard = ["uvicorn[standard] (>=0.15.0)"] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httptools" +version = "0.6.4" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, + {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"}, + {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"}, + {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"}, + {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"}, + {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"}, + {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"}, + {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"}, + {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"}, +] + +[package.extras] +test = ["Cython (>=0.29.24)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, + {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins (>=0.5.0)"] +profiling = ["gprof2dot"] +rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-multipart" +version = "0.0.20" +description = "A streaming multipart parser for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, + {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "qrcode" +version = "8.2" +description = "QR Code image generator" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f"}, + {file = "qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +all = ["pillow (>=9.1.0)", "pypng"] +pil = ["pillow (>=9.1.0)"] +png = ["pypng"] + +[[package]] +name = "rich" +version = "14.1.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rich-toolkit" +version = "0.15.0" +description = "Rich toolkit for building command-line applications" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "rich_toolkit-0.15.0-py3-none-any.whl", hash = "sha256:ddb91008283d4a7989fd8ff0324a48773a7a2276229c6a3070755645538ef1bb"}, + {file = "rich_toolkit-0.15.0.tar.gz", hash = "sha256:3f5730e9f2d36d0bfe01cf723948b7ecf4cc355d2b71e2c00e094f7963128c09"}, +] + +[package.dependencies] +click = ">=8.1.7" +rich = ">=13.7.1" +typing-extensions = ">=4.12.2" + +[[package]] +name = "rignore" +version = "0.6.4" +description = "Python Bindings for the ignore crate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "rignore-0.6.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c201375cfe76e56e61fcdfe50d0882aafb49544b424bfc828e0508dc9fbc431b"}, + {file = "rignore-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4962d537e377394292c4828e1e9c620618dd8daa49ba746abe533733a89f8644"}, + {file = "rignore-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a6dd2f213cff6ca3c4d257fa3f5b0c7d4f6c23fe83bf292425fbe8d0c9c908a"}, + {file = "rignore-0.6.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64d379193f86a21fc93762783f36651927f54d5eea54c4922fdccb5e37076ed2"}, + {file = "rignore-0.6.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53c4f8682cf645b7a9160e0f1786af3201ed54a020bb4abd515c970043387127"}, + {file = "rignore-0.6.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af1246e672bd835a17d3ae91579b3c235ec55b10924ef22608d3e9ec90fa2699"}, + {file = "rignore-0.6.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82eed48fbc3097af418862e3c5c26fa81aa993e0d8b5f3a0a9a29cc6975eedff"}, + {file = "rignore-0.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df1215a071d42fd857fb6363c13803fbd915d48eaeaa9b103fb2266ba89c8995"}, + {file = "rignore-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:82f2d318e66756066ed664015d8ca720078ab1d319377f1f61e3f4d01325faea"}, + {file = "rignore-0.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e7d4258fc81051097c4d4c6ad17f0100c40088dbd2c6c31fc3c888a1d5a16190"}, + {file = "rignore-0.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a0d0b9ec7929df8fd35ae89cb56619850dc140869139d61a2f4fa2941d2d1878"}, + {file = "rignore-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8883d079b948ffcd56b67572831c9b8949eca7fe2e8f7bdbf7691c7a9388f054"}, + {file = "rignore-0.6.4-cp310-cp310-win32.whl", hash = "sha256:5aeac5b354e15eb9f7857b02ad2af12ae2c2ed25a61921b0bd7e272774530f77"}, + {file = "rignore-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:90419f881d05a1febb0578a175aa3e51d149ded1875421ed75a8af4392b7fe56"}, + {file = "rignore-0.6.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:85f684dfc2c497e35ad34ffd6744a3bcdcac273ec1dbe7d0464bfa20f3331434"}, + {file = "rignore-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23954acc6debc852dbccbffbb70f0e26b12d230239e1ad0638eb5540694d0308"}, + {file = "rignore-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2bf793bd58dbf3dee063a758b23ea446b5f037370405ecefc78e1e8923fc658"}, + {file = "rignore-0.6.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1eaeaa5a904e098604ea2012383a721de06211c8b4013abf0d41c3cfeb982f4f"}, + {file = "rignore-0.6.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a48bdbeb03093e3fac2b40d62a718c59b5bb4f29cfdc8e7cbb360e1ea7bf0056"}, + {file = "rignore-0.6.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c5f9452d116be405f0967160b449c46ac929b50eaf527f33ee4680e3716e39"}, + {file = "rignore-0.6.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cf1039bfbdaa0f9710a6fb75436c25ca26d364881ec4d1e66d466bb36a7fb98"}, + {file = "rignore-0.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:136629eb0ec2b6ac6ab34e71ce8065a07106fe615a53eceefc30200d528a4612"}, + {file = "rignore-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:35e3d0ebaf01086e6454c3fecae141e2db74a5ddf4a97c72c69428baeff0b7d4"}, + {file = "rignore-0.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ed1f9010fa1ef5ea0b69803d1dfb4b7355921779e03a30396034c52691658bc"}, + {file = "rignore-0.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c16e9e898ed0afe2e20fa8d6412e02bd13f039f7e0d964a289368efd4d9ad320"}, + {file = "rignore-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e6bc0bdcd404a7a8268629e8e99967127bb41e02d9eb09a471364c4bc25e215"}, + {file = "rignore-0.6.4-cp311-cp311-win32.whl", hash = "sha256:fdd59bd63d2a49cc6d4f3598f285552ccb1a41e001df1012e0e0345cf2cabf79"}, + {file = "rignore-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:7bf5be0e8a01845e57b5faa47ef9c623bb2070aa2f743c2fc73321ffaae45701"}, + {file = "rignore-0.6.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:74720d074b79f32449d5d212ce732e0144a294a184246d1f1e7bcc1fc5c83b69"}, + {file = "rignore-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a8184fcf567bd6b6d7b85a0c138d98dd40f63054141c96b175844414c5530d7"}, + {file = "rignore-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcb0d7d7ecc3fbccf6477bb187c04a091579ea139f15f139abe0b3b48bdfef69"}, + {file = "rignore-0.6.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feac73377a156fb77b3df626c76f7e5893d9b4e9e886ac8c0f9d44f1206a2a91"}, + {file = "rignore-0.6.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:465179bc30beb1f7a3439e428739a2b5777ed26660712b8c4e351b15a7c04483"}, + {file = "rignore-0.6.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a4877b4dca9cf31a4d09845b300c677c86267657540d0b4d3e6d0ce3110e6e9"}, + {file = "rignore-0.6.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:456456802b1e77d1e2d149320ee32505b8183e309e228129950b807d204ddd17"}, + {file = "rignore-0.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c1ff2fc223f1d9473d36923160af37bf765548578eb9d47a2f52e90da8ae408"}, + {file = "rignore-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e445fbc214ae18e0e644a78086ea5d0f579e210229a4fbe86367d11a4cd03c11"}, + {file = "rignore-0.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e07d9c5270fc869bc431aadcfb6ed0447f89b8aafaa666914c077435dc76a123"}, + {file = "rignore-0.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7a6ccc0ea83d2c0c6df6b166f2acacedcc220a516436490f41e99a5ae73b6019"}, + {file = "rignore-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:536392c5ec91755db48389546c833c4ab1426fe03e5a8522992b54ef8a244e7e"}, + {file = "rignore-0.6.4-cp312-cp312-win32.whl", hash = "sha256:f5f9dca46fc41c0a1e236767f68be9d63bdd2726db13a0ae3a30f68414472969"}, + {file = "rignore-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:e02eecb9e1b9f9bf7c9030ae73308a777bed3b2486204cc74dfcfbe699ab1497"}, + {file = "rignore-0.6.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2b3b1e266ce45189240d14dfa1057f8013ea34b9bc8b3b44125ec8d25fdb3985"}, + {file = "rignore-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45fe803628cc14714df10e8d6cdc23950a47eb9eb37dfea9a4779f4c672d2aa0"}, + {file = "rignore-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e439f034277a947a4126e2da79dbb43e33d73d7c09d3d72a927e02f8a16f59aa"}, + {file = "rignore-0.6.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84b5121650ae24621154c7bdba8b8970b0739d8146505c9f38e0cda9385d1004"}, + {file = "rignore-0.6.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b0957b585ab48a445cf8ac1dbc33a272ab060835e583b4f95aa8c67c23fb2b"}, + {file = "rignore-0.6.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50359e0d5287b5e2743bd2f2fbf05df619c8282fd3af12f6628ff97b9675551d"}, + {file = "rignore-0.6.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe18096dcb1596757dfe0b412aab6d32564473ae7ee58dea0a8b4be5b1a2e3b"}, + {file = "rignore-0.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b79c212d9990a273ad91e8d9765e1766ef6ecedd3be65375d786a252762ba385"}, + {file = "rignore-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6ffa7f2a8894c65aa5dc4e8ac8bbdf39a326c0c6589efd27686cfbb48f0197d"}, + {file = "rignore-0.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a63f5720dffc8d8fb0a4d02fafb8370a4031ebf3f99a4e79f334a91e905b7349"}, + {file = "rignore-0.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ce33982da47ac5dc09d19b04fa8d7c9aa6292fc0bd1ecf33076989faa8886094"}, + {file = "rignore-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d899621867aa266824fbd9150e298f19d25b93903ef0133c09f70c65a3416eca"}, + {file = "rignore-0.6.4-cp313-cp313-win32.whl", hash = "sha256:d0615a6bf4890ec5a90b5fb83666822088fbd4e8fcd740c386fcce51e2f6feea"}, + {file = "rignore-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:145177f0e32716dc2f220b07b3cde2385b994b7ea28d5c96fbec32639e9eac6f"}, + {file = "rignore-0.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e55bf8f9bbd186f58ab646b4a08718c77131d28a9004e477612b0cbbd5202db2"}, + {file = "rignore-0.6.4-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2521f7bf3ee1f2ab22a100a3a4eed39a97b025804e5afe4323528e9ce8f084a5"}, + {file = "rignore-0.6.4-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cc35773a8a9c119359ef974d0856988d4601d4daa6f532c05f66b4587cf35bc"}, + {file = "rignore-0.6.4-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b665b1ea14457d7b49e834baabc635a3b8c10cfb5cca5c21161fabdbfc2b850e"}, + {file = "rignore-0.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c7fd339f344a8548724f289495b835bed7b81174a0bc1c28c6497854bd8855db"}, + {file = "rignore-0.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:91dc94b1cc5af8d6d25ce6edd29e7351830f19b0a03b75cb3adf1f76d00f3007"}, + {file = "rignore-0.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4d1918221a249e5342b60fd5fa513bf3d6bf272a8738e66023799f0c82ecd788"}, + {file = "rignore-0.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:240777332b859dc89dcba59ab6e3f1e062bc8e862ffa3e5f456e93f7fd5cb415"}, + {file = "rignore-0.6.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b0e548753e55cc648f1e7b02d9f74285fe48bb49cec93643d31e563773ab3f"}, + {file = "rignore-0.6.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6971ac9fdd5a0bd299a181096f091c4f3fd286643adceba98eccc03c688a6637"}, + {file = "rignore-0.6.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:7fa603aeaf9c1c536fb55527e24816d797a607434e2a8d45acf1683efc566fb6"}, + {file = "rignore-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1721763b59437a7800be6641b83bbf6d5d3dd029363d7518a78b0486e3a988a1"}, + {file = "rignore-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74d66d3172f7e67f3c0bb3cfa4d7a7c234cc71480bd8878bdfbf55a9972e9956"}, + {file = "rignore-0.6.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:867460ecf9d09bd2a43f4ba5ce97df1920d460527e623e8a2e4d27f64ca5909a"}, + {file = "rignore-0.6.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c0b8cb660cb991a307bac05436d5c91829098c73bf0f4ffb3a3e035abf9fb34"}, + {file = "rignore-0.6.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857e37f42cb1c70eaac56d79d46d987cc4cb6926c5a577d34d1b948a9bb9d63e"}, + {file = "rignore-0.6.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc23741267fea2df981e995c49ecca9f888d05e1436ef9b4fc4681f09dadc27"}, + {file = "rignore-0.6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8efac1560a12e214771afcb70c7a8bf03966dfa8b3cda216a150b009b02da2dc"}, + {file = "rignore-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1f05d1a8660f668b3b8b8b02edbdccd2a94b36680e7d7b28effb2374459c730a"}, + {file = "rignore-0.6.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:714aa11043ee6b2cf307b9be7319f05587a3a31bf9d3f24203e38fa43d6fd8e4"}, + {file = "rignore-0.6.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1921d1fca65f1ae4b1e376239eba6c0c029361fea7f4aeadf3b372506fe2153f"}, + {file = "rignore-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:1b8493990fb15811525d81e7969e521a3071ac7ee998860b46e6ccbcce6eca4b"}, + {file = "rignore-0.6.4-cp38-cp38-win32.whl", hash = "sha256:abadfd4dac2564f47ed26ae1b56b199dc5679d987e6c720868e26c53dae8dafa"}, + {file = "rignore-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:b149dbba5c786f29cba3ba3b43af8771a1969fa7ccde9895487260fb87e43a4e"}, + {file = "rignore-0.6.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:6a336ec8b8d4da5063b09a95042740491ca822afb4fcce2530bb6e5e3baf0845"}, + {file = "rignore-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ed8a704960fa3764b510a88ececef0c8d0447d4dfc64ab0e2e28321952cfed8"}, + {file = "rignore-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e758c42ec19b3545e66f73dc7cf584f3ed7ea5029dd6716a3257f68db6c0c8"}, + {file = "rignore-0.6.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8aca66ca2d08e0ae684dde745daecdfe8997a5c873c51b22771ecf3afb17d33"}, + {file = "rignore-0.6.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b14a8bc6929914c13d346607a076d2762e3ac26eb179866277c5ee06c9669f"}, + {file = "rignore-0.6.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e903431cb6ac2a0416c5ffab16b103a1d85f2c337371f107a6838ece5f2052e"}, + {file = "rignore-0.6.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa969d5d9148fcde194d9ddb547df2e7b6d321ad68d1dc906934e2c0fc3b939"}, + {file = "rignore-0.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9f3602d3c162c3061a004648e5f1bf81a3ed54d2c3052318008a7f7cee6fb46"}, + {file = "rignore-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8544f45f583a5ab7cd1ad38564c473b2aac136a3a770abbcb92fca6d37eaa84e"}, + {file = "rignore-0.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0e1c95f7bd8a32fd483a86c301e88853f433423d7be58b173ee55d8612354ff0"}, + {file = "rignore-0.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e1af717d4f01a84ecb6976badb20f37595bc31158fc60dae35f7f4cda16eef41"}, + {file = "rignore-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7b73b1626938a17b6766b092b71abc05fcdb1bf9ed9e4ee0b86362e8f81105f1"}, + {file = "rignore-0.6.4-cp39-cp39-win32.whl", hash = "sha256:f9a7219da435de314a822988503f78fa4917e668e42985c6e427f2c66580a652"}, + {file = "rignore-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b56e2ed34103afb39f767aa3787138738fb4b0a82d748269f27cc81d59940bc7"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40f493eef4b191777ba6d16879e3f73836142e04480d2e2f483675d652e6b559"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6790635e4df35333e27cd9e8b31d1d559826cf8b52f2c374b81ab698ac0140cf"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e326dab28787f07c6987c04686d4ad9d4b1e1caca1a15b85d443f91af2e133d2"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd24cb0f58c6036b0f64ac6fc3f759b7f0de5506fa9f5a65e9d57f8cf44a026d"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36cb95b0acae3c88b99a39f4246b395fd983848f3ec85ff26531d638b6584a45"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dfc954973429ce545d06163d87a6bae0ccea5703adbc957ee3d332c9592a58eb"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:cbed37d7c128b58ab9ade80e131efc4a48b6d045cd0bd1d3254cbb6b4a0ad67e"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a0db910ef867d6ca2d52fefd22d8b6b63b20ec61661e2ad57e5c425a4e39431a"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d664443a0a71d0a7d669adf32be59c4249bbff8b2810960f1b91d413ee4cf6b8"}, + {file = "rignore-0.6.4-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:b9f6f1d91429b4a6772152848815cf1459663796b7b899a0e15d9198e32c9371"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b3da26d5a35ab15525b68d30b7352ad2247321f5201fc7e50ba6d547f78d5ea"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43028f3587558231d9fa68accff58c901dc50fd7bbc5764d3ee3df95290f6ebf"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc56f1fcab7740751b98fead67b98ba64896424d8c834ea22089568db4e36dfa"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6033f2280898535a5f69935e08830a4e49ff1e29ef2c3f9a2b9ced59de06fdbf"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f5ac0c4e6a24be88f3821e101ef4665e9e1dc015f9e45109f32fed71dbcdafa"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8906ac8dd585ece83b1346e0470260a1951058cc0ef5a17542069bde4aa3f42f"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:14d095622969504a2e56f666286202dad583f08d3347b7be2d647ddfd7a9bf47"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:30f3d688df7eb4850318f1b5864d14f2c5fe5dbf3803ed0fc8329d2a7ad560dc"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:028f62a7b0a6235bb3f03c9e7f342352e7fa4b3f08c761c72f9de8faee40ed9c"}, + {file = "rignore-0.6.4-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:7e6c425603db2c147eace4f752ca3cd4551e7568c9d332175d586c68bcbe3d8d"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:518662bee0d4c6e2b220fef28e4851a149921ff0eac1a060f739c5490b72754e"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:285b60526360aab1bf3cacdf6efe22b90f3b80392dcb4f8ae1e3257043c4c5ad"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72ec7c6fb883775a70bfc1ae6b8d2d709d76da16e8a23eeda93ca86a1a6b81e4"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54662abe43fa4dfc905bcb5af29163fe40dfc5ccae83fccbe6abd5a73b23d776"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:5219f65b5df40d86e11fc91a0d25ced2b324a7569ac730c75775198e9ef3f5b5"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:4be42621233bf03dd6acfe17cf7ad2a29bc84ac008564feea3ec412cabac5b5a"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:dd3db7ce8350a30507239a7d3239dc697c1a958c1f09e230529612de2882f2ff"}, + {file = "rignore-0.6.4-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3ac29f2366b3b0135de7c3a0ed6a9a62f793d8bb518e53479d23f1870fdc0fe9"}, + {file = "rignore-0.6.4.tar.gz", hash = "sha256:e893fdd2d7fdcfa9407d0b7600ef2c2e2df97f55e1c45d4a8f54364829ddb0ab"}, +] + +[[package]] +name = "sentry-sdk" +version = "2.35.0" +description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "sentry_sdk-2.35.0-py2.py3-none-any.whl", hash = "sha256:6e0c29b9a5d34de8575ffb04d289a987ff3053cf2c98ede445bea995e3830263"}, + {file = "sentry_sdk-2.35.0.tar.gz", hash = "sha256:5ea58d352779ce45d17bc2fa71ec7185205295b83a9dbb5707273deb64720092"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.11" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] +http2 = ["httpcore[http2] (==1.*)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +huggingface-hub = ["huggingface_hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"] +litestar = ["litestar (>=2.0.0)"] +loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] +openfeature = ["openfeature-sdk (>=0.7.1)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro"] +pure-eval = ["asttokens", "executing", "pure_eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +statsig = ["statsig (>=0.55.3)"] +tornado = ["tornado (>=6)"] +unleash = ["UnleashClient (>=6.0.1)"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "starlette" +version = "0.47.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b"}, + {file = "starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8"}, +] + +[package.dependencies] +anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} + +[package.extras] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] + +[[package]] +name = "typer" +version = "0.16.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"}, + {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.35.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a"}, + {file = "uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.21.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"}, + {file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"}, +] + +[package.extras] +dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "watchfiles" +version = "1.1.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9"}, + {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72"}, + {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587"}, + {file = "watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82"}, + {file = "watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2"}, + {file = "watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f"}, + {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4"}, + {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d"}, + {file = "watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2"}, + {file = "watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12"}, + {file = "watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a"}, + {file = "watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179"}, + {file = "watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f"}, + {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4"}, + {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f"}, + {file = "watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd"}, + {file = "watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47"}, + {file = "watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6"}, + {file = "watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30"}, + {file = "watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c"}, + {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b"}, + {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb"}, + {file = "watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9"}, + {file = "watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7"}, + {file = "watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5"}, + {file = "watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1"}, + {file = "watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20"}, + {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef"}, + {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb"}, + {file = "watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297"}, + {file = "watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e"}, + {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b"}, + {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259"}, + {file = "watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f"}, + {file = "watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147"}, + {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8"}, + {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db"}, + {file = "watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa"}, + {file = "watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e"}, + {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86"}, + {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f"}, + {file = "watchfiles-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267"}, + {file = "watchfiles-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277"}, + {file = "watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "15.0.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, + {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, + {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, + {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, + {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, + {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, + {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, + {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, + {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, + {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, + {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, + {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, + {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, +] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.12,<4.0" +content-hash = "1c96146e0db69fae6a537b986a393e7169bf78c10248be4aa36dbc771a19b333" diff --git a/daring-daffodils/pyproject.toml b/daring-daffodils/pyproject.toml new file mode 100644 index 00000000..c45e1de9 --- /dev/null +++ b/daring-daffodils/pyproject.toml @@ -0,0 +1,70 @@ +[project] +# This section contains metadata about your project. +# Don't forget to change the name, description, and authors to match your project! +name = "Misclick" +description = '''Why settle for a boring, predictable mouse when you can have pure chaos? +Misclick is a “wireless mouse” that isn’t really wireless… or a mouse… or particularly useful. Instead of moving your actual cursor, it randomly wanders across the screen, misbehaves on touch, and occasionally copies text just to remind you who’s in charge. +In short: It’s not the right tool for the job — and that’s the whole point.''' +authors = [ + { name = "Sooraj T S"}, + { name = "Daksh Gakhar"}, + { name = "SaketKunjathur"}, + { name = "Vibhakar Solanki"}, + { name = "Oni Musha"}, + { name = "Astar-777"} + +] +version = "0.1.0" +readme = "README.md" + +[tool.poetry.dependencies] +python = ">=3.12,<4.0" +fastapi = { extras = ["standard"], version = "^0.116.1" } +qrcode = "^8.2" + +[dependency-groups] +# This `dev` group contains all the development requirements for our linting toolchain. +# Don't forget to pin your dependencies! +# This list will have to be migrated if you wish to use another dependency manager. +dev = [ + "pre-commit~=4.2.0", + "ruff~=0.12.2", +] + +[tool.ruff] +exclude = ["browser_extension/runtime"] +# Increase the line length. This breaks PEP8 but it is way easier to work with. +# The original reason for this limit was a standard vim terminal is only 79 characters, +# but this doesn't really apply anymore. +line-length = 119 +# Target Python 3.12. If you decide to use a different version of Python +# you will need to update this value. +target-version = "py312" +# Automatically fix auto-fixable issues. +fix = true + +[tool.ruff.lint] +select = [ + "E", # all syntax and formatting errors + "F", # Pyflakes checks (undefined names, unused imports) + "W", # Warnings like unused variables, redefined names + +] +# Ignore some of the most obnoxious linting errors. +ignore = [ + # Docstring whitespace. + "D203", + "D213", + # Docstring punctuation. + "D415", + # Docstring quotes. + "D301", + # Builtins. + "A", + # Print statements. + "T20", + # TODOs. + "TD002", + "TD003", + "FIX", +] diff --git a/daring-daffodils/setup.ps1 b/daring-daffodils/setup.ps1 new file mode 100644 index 00000000..8b5dcdf6 --- /dev/null +++ b/daring-daffodils/setup.ps1 @@ -0,0 +1,54 @@ +# setup_and_run.ps1 + +# --- Step 0: Set paths --- +$venvPath = ".\.venv" +$activateScript = "$venvPath\Scripts\Activate.ps1" + +# --- Step 1: Check for Python --- +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue + if (-not $python) { + Write-Host "❌ Python not found. Please install Python 3.10+ from https://www.python.org/downloads/windows/" + exit 1 + } +} + +Write-Host "✅ Python found at $($python.Path)" + +# --- Step 2: Create virtual environment --- +if (-not (Test-Path $venvPath)) { + Write-Host "👉 Creating virtual environment..." + & $python.Path -m venv $venvPath +} else { + Write-Host "✅ Virtual environment already exists" +} + +# --- Step 3: Activate virtual environment --- +if (-not (Test-Path $activateScript)) { + Write-Host "❌ Activation script not found!" + exit 1 +} + +# Function to run commands inside venv +function Run-InVenv($cmd) { + & $activateScript + Invoke-Expression $cmd +} + +# --- Step 4: Ensure Poetry is installed --- +$poetry = Get-Command poetry -ErrorAction SilentlyContinue +if (-not $poetry) { + Write-Host "👉 Installing Poetry..." + Invoke-Expression "& $python.Path -c `"$(Invoke-WebRequest -UseBasicParsing https://install.python-poetry.org).Content`"" +} else { + Write-Host "✅ Poetry already installed" +} + +# --- Step 5: Install dependencies --- +Write-Host "👉 Installing dependencies..." +Run-InVenv "poetry install" + +# --- Step 6: Run main.py --- +Write-Host "👉 Running main.py..." +Run-InVenv "python app.py" diff --git a/grand-gardenias/.github/workflows/lint.yaml b/grand-gardenias/.github/workflows/lint.yaml new file mode 100644 index 00000000..7f67e803 --- /dev/null +++ b/grand-gardenias/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/grand-gardenias/.gitignore b/grand-gardenias/.gitignore new file mode 100644 index 00000000..61f27918 --- /dev/null +++ b/grand-gardenias/.gitignore @@ -0,0 +1,38 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store + +# npm and tailwindcss +# Dependency directory +node_modules/ + +# Generated files +dist/ diff --git a/grand-gardenias/.pre-commit-config.yaml b/grand-gardenias/.pre-commit-config.yaml new file mode 100644 index 00000000..c0a8de23 --- /dev/null +++ b/grand-gardenias/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/grand-gardenias/LICENSE.txt b/grand-gardenias/LICENSE.txt new file mode 100644 index 00000000..5a04926b --- /dev/null +++ b/grand-gardenias/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/grand-gardenias/README.md b/grand-gardenias/README.md new file mode 100644 index 00000000..6126baa7 --- /dev/null +++ b/grand-gardenias/README.md @@ -0,0 +1,142 @@ +

+ Project Logo +

+ +# 👾 Tetris Bugs: The Code Editor You Never Asked For + +Built by **Grand Gardenias** for Python Discord's Summer CodeJam 2025. Challenge theme: **Wrong Tool for the Job** using **Python in the Browser** via [Pyscript](https://pyscript.net/). + +--- + +## 🎯 The Concept + +Instead of typing code like a normal human, you now have to **catch falling code blocks** and arrange them into working programs. + +## 🎮 Game Modes (Choose Your Suffering) + +### 1. 🏗️ Tetris Code Editor + +- New File +- Export/Save +- Undo/Redo +- Run Code +- Terminal Output +- Code Falls from Sky + +### 2. ⚡ CodeRush Mode + +- 5-minute timer counting down +- Questions show up on the left +- Solve as many as you can before time’s up +- Your score = how many problems you solved before the end + +### 3. 🗡️ Roguelike Mode + +- We provide the CORRECT solution +- Arrange the blocks properly to "clear" lines like Tetris +- Wrong arrangements stack up +- Reach the top = GAME OVER + +### 4. We could not finish the multiplayer mode, but it is documented in [docs/goals.md](docs/goals.md) + +## 🛠️ Tech Stack + +- **PyScript** +- **TailwindCSS** + +## 📦 Requirements + +- Python 3.13+ +- Node.js & npm + +## ⚙️ Setup Instructions + +1. Clone the repository: + ```shell + git clone https://github.com/zishankadri/tetris-bugs.git + cd tetris-bugs + ``` +3. Start a local Server: + ```shell + cd frontend + npm install + npm run build + python -m http.server + ``` + +3. **Open Your Browser** to `http://localhost:8000` +4. 💡 For Contributors: Run `npm run dev` to auto-update Tailwind while you code. + +## 🎉 Achievement Unlocked +**Congratulations! You now have the most unique answer to "What IDE do you use?"** + +*"Oh, I use Tetris."* + +## Video Presentation +[code-editor.webm](https://github.com/user-attachments/assets/cf059496-4f29-4d98-a95a-8969f91b333e) + +
+ ⚡ CodeRush Video Presentation + https://drive.google.com/file/d/1t6_92z2R_ntOok2yyzjklHpKyxoKZl_g/view +
+ +
+ 🗡️ Roguelike (BETA) Video Presentation + +[roguelike.webm](https://github.com/user-attachments/assets/ffc35054-e3de-4e12-ac63-f1e3520f2ee8) +
+ +## 📸 Screenshots +
+ Preview Images 📸 + code-editor + code-editor + code-rush + roguelike + menu + +
+ +## 🎯 Wrong Tool for the Job + +✅ Using Tetris as an IDE +✅ Spatial reasoning for coding problems + +With some tweaks and backend, this could be a useful tool as a coding qualifier, as we doubt any LLM alone could pass this test. + +## 🤝 Credits + +(in order of contributed LOC): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameContributions
LuffyLuffyLead developer, Project architecture, Ideation, Frontend
rxdiationxrxdiationxBase of CodeRush, sound effects/music, loading screen, Sourcing of problems
Sapient44Sapient44Terminal and code execution, Modifications in roguelike mode, Sourcing of audio
Shivk123Shivk123Meeting facilitation, Initial file structure, Set up Flask backend
mhasanali2010mhasanali2010timer logic, pause screen
diff --git a/grand-gardenias/backend/__init__.py b/grand-gardenias/backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/backend/app.py b/grand-gardenias/backend/app.py new file mode 100644 index 00000000..ab00dd81 --- /dev/null +++ b/grand-gardenias/backend/app.py @@ -0,0 +1,21 @@ +# Define paths +from pathlib import Path + +from flask import Flask, render_template + +template_dir = Path(__file__).resolve().parent.parent / "frontend" +static_dir = template_dir # if your CSS/JS/config are in frontend/ + +app = Flask( + __name__, + template_folder=template_dir, + static_folder=static_dir, # serve static files from frontend/ + static_url_path="", # serve at root so /config.json works +) + + +@app.route("/") +def home() -> None: + """Render the main page.""" + # Render the index.html file from the frontend directory + return render_template("index.html") diff --git a/grand-gardenias/backend/run.py b/grand-gardenias/backend/run.py new file mode 100644 index 00000000..c9997e69 --- /dev/null +++ b/grand-gardenias/backend/run.py @@ -0,0 +1,40 @@ +import shutil +import subprocess +import sys +from pathlib import Path + +from flask.cli import main as flask_main + +DEFAULT_ARG_COUNT = 2 # Avoid magic number + + +def main() -> None: + """Run the Flask application and start the frontend development server.""" + frontend_dir = Path(__file__).parent.parent / "frontend" + + # Locate npm safely + npm_path = shutil.which("npm") + if npm_path is None: + print("npm not found in PATH.") + sys.exit(1) + + # Start npm dev server + try: + subprocess.Popen( # noqa: S603 + [npm_path, "run", "dev"], + cwd=frontend_dir, + ) + print("Frontend dev server started.") + except OSError as err: + print(f"Failed to start frontend: {err}") + + sys.argv.insert(1, "--app=backend.app") + + if len(sys.argv) == DEFAULT_ARG_COUNT: + sys.argv.append("run") + + flask_main() + + +if __name__ == "__main__": + main() diff --git a/grand-gardenias/docs/architecture.md b/grand-gardenias/docs/architecture.md new file mode 100644 index 00000000..56cfcf05 --- /dev/null +++ b/grand-gardenias/docs/architecture.md @@ -0,0 +1,23 @@ +# Project Architecture + +*(See ![Architecture Diagram](img/architecture-diagram.svg) for visual reference.)* + +**Goal:** Avoid circular imports, enable reuse across modes, separate concerns. + +## Layers + +1. **Objects Layer** — self-contained game entities (no external knowledge). +2. **Core Game Management** — orchestrates state, no DOM access. +3. **Support Modules** — UI Manager, controls, block generators. + +**Entry Point:** `main` wires components together and selects the mode. + +## Dependency Rules + +- Objects → no outward dependencies. +- Game Manager → can talk to objects & support modules, but no cycles. +- UI Manager → DOM Manipulation. +- Controls → interact with Game Manager and objects only. +- `main` → composition root, no logic. + +Modes can add their own files, but they must follow the same rules. diff --git a/grand-gardenias/docs/goals.md b/grand-gardenias/docs/goals.md new file mode 100644 index 00000000..b601fd07 --- /dev/null +++ b/grand-gardenias/docs/goals.md @@ -0,0 +1,13 @@ +# MULTIPLAYER + +![multiplayer screen](img/multiplayer.svg) + +## THE CONCEPT OF 👾 Bugs + +Occasionally, instead of the actual code blocks, the player will receive bugs from a number of pre-defined weapons in a class named 'Bugs' e.g. Bugs.beetle(). I originally thought of Bugs.bomb (), which on placing will immediately disappear from the player's own screen, but a bomb will appear at the same spot on the enemy's screen touching which will require the enemy to do over that one block, giving us a 'time advantage'. This will make so much sense if we create a 'time-based winner' game. (Open to suggestions this is just an example I have in mind for now.) + +![bugs](img/special-block.svg) + +## Bugs.beetle() + +![bugs](img/bugs.svg) diff --git a/grand-gardenias/docs/img/architecture-diagram.svg b/grand-gardenias/docs/img/architecture-diagram.svg new file mode 100644 index 00000000..9be508f0 --- /dev/null +++ b/grand-gardenias/docs/img/architecture-diagram.svg @@ -0,0 +1,3 @@ +UIManagerother mode-specific modulesMainDOMmanipulationControlsGame MangaerBlockAll Objects must beIsolatedObjects«constructor injection»«constructor injection»«setter injection»«constructor injection»«import»«import»«import»«import» diff --git a/grand-gardenias/docs/img/bugs.svg b/grand-gardenias/docs/img/bugs.svg new file mode 100644 index 00000000..849b6264 --- /dev/null +++ b/grand-gardenias/docs/img/bugs.svg @@ -0,0 +1,3 @@ +for i in ran_____ 6): if i % 2 == 0: print(f"{i} is even") else: print(f"{i} is odd")rripfoWorks the sameas Bugs.bomb(),just fancier. diff --git a/grand-gardenias/docs/img/logo.svg b/grand-gardenias/docs/img/logo.svg new file mode 100644 index 00000000..175d3c9d --- /dev/null +++ b/grand-gardenias/docs/img/logo.svg @@ -0,0 +1,2 @@ +T{TR!SGUBS diff --git a/grand-gardenias/docs/img/multiplayer.svg b/grand-gardenias/docs/img/multiplayer.svg new file mode 100644 index 00000000..66006f16 --- /dev/null +++ b/grand-gardenias/docs/img/multiplayer.svg @@ -0,0 +1,2 @@ +Enemy ScreeenYour screen diff --git a/grand-gardenias/docs/img/special-block.svg b/grand-gardenias/docs/img/special-block.svg new file mode 100644 index 00000000..bc0a5b85 --- /dev/null +++ b/grand-gardenias/docs/img/special-block.svg @@ -0,0 +1,2 @@ +BUGS.BOMBB() diff --git a/grand-gardenias/docs/refactoring/2025-08-15-singleplayer-architecture-update.md b/grand-gardenias/docs/refactoring/2025-08-15-singleplayer-architecture-update.md new file mode 100644 index 00000000..41743e4b --- /dev/null +++ b/grand-gardenias/docs/refactoring/2025-08-15-singleplayer-architecture-update.md @@ -0,0 +1,24 @@ +# Refactor: Singleplayer mode architecture update + +**Date:** 2025-08-15 + +**Reason:** +Modules in singleplayer mode were tightly coupled, making reuse in other modes impossible. +New features were causing circular imports, blocking further development. + +**Changes:** + +- Redesigned singleplayer architecture to decouple modules. +- Introduced clear separation of shared utilities and mode-specific logic. +- Updated imports across affected files to match new structure. + +**Impact:** + +- No more circular import issues. +- Modules can now be reused across different modes without modification. +- Easier to extend features without breaking unrelated components. + +**Follow-up:** + +- Apply the same architecture to all modes. +- Document the new module dependency flow in `docs/architecture.md`. diff --git a/grand-gardenias/frontend/__init__.py b/grand-gardenias/frontend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/__init__.py b/grand-gardenias/frontend/engine/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/constants.py b/grand-gardenias/frontend/engine/constants.py new file mode 100644 index 00000000..ad3eca62 --- /dev/null +++ b/grand-gardenias/frontend/engine/constants.py @@ -0,0 +1,2 @@ +MAX_BLOCK_LENGTH = 5 +TIMER_MINUTES = 5 diff --git a/grand-gardenias/frontend/engine/controls.py b/grand-gardenias/frontend/engine/controls.py new file mode 100644 index 00000000..83392a9b --- /dev/null +++ b/grand-gardenias/frontend/engine/controls.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.game import GameManager + from engine.ui_manager import UIManager + +from js import KeyboardEvent, document + + +class BaseController: + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def __init__(self, game_manager: GameManager, ui_manager: UIManager) -> None: + self.game_manager = game_manager + self.ui_manager = ui_manager + + def handle_key(self, evt: KeyboardEvent) -> None: + """Handle arrow keys and spacebar.""" + active = document.activeElement + if active and active.id == "text-input": + return + + if not self.game_manager.current_block: + return + + moved = False + if evt.key == "ArrowLeft": + moved = self.game_manager.current_block.move(-1, 0, self.game_manager.grid) + elif evt.key == "ArrowRight": + moved = self.game_manager.current_block.move(1, 0, self.game_manager.grid) + elif evt.key == "ArrowDown": + moved = self.game_manager.current_block.move(0, 1, self.game_manager.grid) + elif evt.key == " ": + evt.preventDefault() + self.game_manager.lock_current_block() + moved = True + + if moved: + self.ui_manager.render() diff --git a/grand-gardenias/frontend/engine/game.py b/grand-gardenias/frontend/engine/game.py new file mode 100644 index 00000000..bd388d7c --- /dev/null +++ b/grand-gardenias/frontend/engine/game.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.ui_manager import BaseUIManager + +from engine.objects.block import Block +from engine.patterns import SingletonMeta + + +class BaseGameManager(metaclass=SingletonMeta): + """Game logic manager.""" + + def __init__(self, cols: int, rows: int) -> None: + self.cols, self.rows = cols, rows + self.grid: list[list[str | None]] = [[None for _ in range(self.cols)] for _ in range(self.rows)] + self.current_block: Block | None = None + self.ui_manager: BaseUIManager = None + + def tick(self) -> None: + """Advance game state by one step.""" + if not self.current_block or not self.current_block.falling: + return + + if not self.current_block.move(0, 1, self.grid): + self.lock_current_block() + + def spawn_block(self, text: str) -> None: + """Create and spawn a new block.""" + new_block = Block(text, self.cols, self.rows) + self.current_block = new_block + + self.ui_manager.render() + + def lock_current_block(self) -> None: + """Lock current block into grid.""" + self.current_block.lock(self.grid) + self.ui_manager.lock_visual_cells() + + self.current_block = None + + def format_grid_as_text(self) -> str: + """Return grid contents as plain text.""" + lines = [ + "".join(cell if cell else " " for cell in row) + for row in self.grid + if any(cell is not None for cell in row) + ] + return "\n".join(lines) if lines else "" + + def format_grid_line_as_text(self, i: int) -> str: + """Return the i-th line of the grid as text.""" + row = self.grid[i] + return "".join(cell if cell else " " for cell in row).rstrip() + + def clear_grid(self) -> None: + """Clear the grid by resetting all cells to None. + + Note: + This only updates the game state; visual updates are be handled + separately by the ui_manager. + + """ + self.current_block = None + self.grid = [[None for _ in range(self.cols)] for _ in range(self.rows)] + + def clear_row(self, row_index: int) -> None: + """Clear all cells in the specified row in the internal grid. + + Note: + This only updates the game state; visual updates are be handled + separately by the ui_manager. + + """ + self.grid[row_index] = [None for _ in range(self.cols)] diff --git a/grand-gardenias/frontend/engine/objects/__init__.py b/grand-gardenias/frontend/engine/objects/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/objects/block.py b/grand-gardenias/frontend/engine/objects/block.py new file mode 100644 index 00000000..00c9c3c9 --- /dev/null +++ b/grand-gardenias/frontend/engine/objects/block.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from random import randint + +from engine.constants import MAX_BLOCK_LENGTH + + +class Block: + """Represents a horizontal text block moving on a grid.""" + + def __init__(self, text: str, grid_cols: int, grid_rows: int) -> None: + self.text = text[: min(MAX_BLOCK_LENGTH, grid_cols)] + self.cols = grid_cols + self.rows = grid_rows + # Spawns blocks randomly along the x-axis. + self.x = randint(0, self.cols - len(self.text)) # noqa: S311 + self.y = 0 + self.falling = True + + def can_move(self, dx: int, dy: int, grid: list[list[str | None]]) -> bool: + """Check if move is valid.""" + new_x = self.x + dx + new_y = self.y + dy + for i in range(len(self.text)): + tx, ty = new_x + i, new_y + if tx < 0 or tx >= self.cols or ty >= self.rows: + return False + if ty >= 0 and grid[ty][tx] is not None: + return False + return True + + def move(self, dx: int, dy: int, grid: list[list[str | None]]) -> bool: + """Try moving the block, return True if moved.""" + if self.can_move(dx, dy, grid): + # Update position if move is valid + self.x += dx + self.y += dy + return True + return False + + def lock(self, grid: list[list[str | None]]) -> None: + """Place the block into the grid and stop it from falling.""" + for i, ch in enumerate(self.text): + tx = self.x + i + ty = self.y + if 0 <= tx < self.cols and 0 <= ty < self.rows: + grid[ty][tx] = ch + self.falling = False + + def get_cells_coords(self) -> list[tuple[int, int]]: + """Return the coordinates of the current block on the grid.""" + return [(self.x + i, self.y) for i in range(len(self.text))] diff --git a/grand-gardenias/frontend/engine/objects/bugs.py b/grand-gardenias/frontend/engine/objects/bugs.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/patterns.py b/grand-gardenias/frontend/engine/patterns.py new file mode 100644 index 00000000..b76a5e3d --- /dev/null +++ b/grand-gardenias/frontend/engine/patterns.py @@ -0,0 +1,8 @@ +class SingletonMeta(type): # noqa: D101 + _instance = None # type: object | None + + def __call__(cls: type["SingletonMeta"], *args: object, **kwargs: object) -> object: + """Return the singleton instance, creating it if necessary.""" + if cls._instance is None: + cls._instance = super().__call__(*args, **kwargs) + return cls._instance diff --git a/grand-gardenias/frontend/engine/ui_manager.py b/grand-gardenias/frontend/engine/ui_manager.py new file mode 100644 index 00000000..093a12d8 --- /dev/null +++ b/grand-gardenias/frontend/engine/ui_manager.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from datetime import UTC, datetime +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.game import GameManager +from js import URL, Blob, document + + +class BaseUIManager: + """Handles rendering and UI interactions for the game.""" + + def __init__(self, game: GameManager) -> None: + self.game = game + self.cells: list[list] = [] + self._last_rendered_grid: list[list[str | None]] = [[None for _ in range(game.cols)] for _ in range(game.rows)] + + def create_visual_grid(self) -> None: + """Create the visual grid in DOM.""" + game_div = document.getElementById("game") + fragment = document.createDocumentFragment() + + for _y in range(self.game.rows): + row = [] + for _x in range(self.game.cols): + cell = document.createElement("div") + cell.classList.add("cell") + fragment.appendChild(cell) + row.append(cell) + self.cells.append(row) + + game_div.appendChild(fragment) + + def render(self) -> None: + """Update only cells that have changed to match the current game state.""" + combined_grid = [row.copy() for row in self.game.grid] + + if self.game.current_block and self.game.current_block.falling: + for i, ch in enumerate(self.game.current_block.text): + tx = self.game.current_block.x + i + ty = self.game.current_block.y + if 0 <= tx < self.game.cols and 0 <= ty < self.game.rows: + combined_grid[ty][tx] = ch + + for y in range(self.game.rows): + for x in range(self.game.cols): + cell = self.cells[y][x] + current_char = combined_grid[y][x] + last_char = self._last_rendered_grid[y][x] + + if current_char != last_char: + if current_char is None: + cell.className = "cell" + cell.style.background = "" + cell.style.color = "" + cell.textContent = "" + else: + cell.className = "block" + cell.textContent = current_char + + self._last_rendered_grid[y][x] = current_char + + def save_grid_code_to_file(self) -> None: + """Prompt user to download current grid code.""" + saved_code = self.game.format_grid_as_text() + blob = Blob.new([saved_code], {"type": "text/x-python"}) + url = URL.createObjectURL(blob) + + download_link = document.createElement("a") + download_link.href = url + timestamp = datetime.now(UTC).strftime("%Y-%m-%d_%H-%M-%S") + download_link.download = f"tetris_code_{timestamp}.py" + download_link.style.display = "none" + + document.body.appendChild(download_link) + download_link.click() + document.body.removeChild(download_link) + URL.revokeObjectURL(url) + + def lock_visual_cells(self) -> None: + """Lock the cells occupied by the current block visually.""" + block_cords = self.game.current_block.get_cells_coords() + + block_cells = [self.cells[cord[1]][cord[0]] for cord in block_cords] + + for cell in block_cells: + cell.className = "locked-cell" + + def clear_grid(self) -> None: + """Clear the grid by resetting all cells to None.""" + self.game.clear_grid() + self._last_rendered_grid = [[None for _ in range(self.game.cols)] for _ in range(self.game.rows)] + + # Force clear all visual cells to ensure locked cells are reset + for y in range(self.game.rows): + for x in range(self.game.cols): + cell = self.cells[y][x] + cell.className = "cell" + cell.style.background = "" + cell.style.color = "" + cell.textContent = "" + self.render() + + def clear_row(self, row_index: int) -> None: + """Clear all cells in a specific row of the game grid. + + This will set all elements in the row to None in the game's internal + grid representation and update the visual representation on the DOM. + + Args: + row_index (int): The index of the row to clear (0-based). + + """ + # Clear the internal grid data + self.game.clear_row(row_index) + + # Clear the visual cells + for x in range(self.game.cols): + cell = self.cells[row_index][x] + + cell.className = "cell" + cell.style.background = "" + cell.style.color = "" + cell.textContent = "" + + # Update last rendered grid to reflect cleared state + self._last_rendered_grid[row_index] = [None for _ in range(self.game.cols)] + self.render() diff --git a/grand-gardenias/frontend/index.html b/grand-gardenias/frontend/index.html new file mode 100644 index 00000000..21a4e75b --- /dev/null +++ b/grand-gardenias/frontend/index.html @@ -0,0 +1,65 @@ + + + + + + + Game Menu + + + + + + + + + + + + + + + +

Tetris Bugs

+ + + + + + + + + + + diff --git a/grand-gardenias/frontend/modes/code_editor/__init__.py b/grand-gardenias/frontend/modes/code_editor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/code_editor/config.json b/grand-gardenias/frontend/modes/code_editor/config.json new file mode 100644 index 00000000..356560a2 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/config.json @@ -0,0 +1,18 @@ +{ + "files": { + "../../engine/game.py": "engine/game.py", + "../../engine/patterns.py": "engine/patterns.py", + "../../engine/controls.py": "engine/controls.py", + "../../engine/ui_manager.py": "engine/ui_manager.py", + "../../engine/constants.py": "engine/constants.py", + "../../engine/objects/block.py": "engine/objects/block.py", + "../../shared/audio_utils.py": "shared/audio_utils.py", + + "./src/controls.py": "", + "./src/game.py": "", + "./src/modal.py": "", + "./src/ui_manager.py": "", + "./src/timer.py": "", + "./src/execute_code.py": "" + } +} diff --git a/grand-gardenias/frontend/modes/code_editor/index.html b/grand-gardenias/frontend/modes/code_editor/index.html new file mode 100644 index 00000000..75e556bd --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/index.html @@ -0,0 +1,154 @@ + + + + + + + Code Editor - Tetris + + + + + + + + + + + + + + + +
+
+

Tetris Bugs

+
+ +

Loading...

+
+
+ + + + + +
+ +
+ + +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ + +
+ + +
+
+ + +
+ +
+ +
+ + +
+

Terminal

+
+
+
+ +
+ + + + + + + + + diff --git a/grand-gardenias/frontend/modes/code_editor/main.py b/grand-gardenias/frontend/modes/code_editor/main.py new file mode 100644 index 00000000..8020aa61 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/main.py @@ -0,0 +1,84 @@ +from typing import Tuple # noqa: UP035 + +from controls import Controller +from execute_code import run_python_code +from game import GameManager +from js import document, setInterval, window +from modal import continue_modal + +# pyright: reportMissingImports=false +from pyodide.ffi import create_proxy +from ui_manager import UIManager + +game_manager = GameManager(40, 20) + + +def main() -> None: + """Initialize the game.""" + ui_manager = UIManager(game_manager) + game_manager.ui_manager = ui_manager # Inject ui_manager instance (dependency injection) + controller = Controller(game_manager, ui_manager) # Inject game_manager and ui_manager instance + + ui_manager.create_visual_grid() # Create display grid + + # Bind events + input_box = document.getElementById("text-input") + input_proxy = create_proxy(lambda evt: controller.handle_input(evt, input_box)) + input_box.addEventListener("keydown", input_proxy) + + # Bind save button + run_btn = document.getElementById("runCodeButton") + run_proxy = create_proxy(lambda *_: run_python_code(game_manager)) + run_btn.addEventListener("click", run_proxy) + + # Bind save button + save_btn = document.getElementById("save-btn") + save_btn2 = document.getElementById("save-btn2") + save_proxy = create_proxy(lambda *_: ui_manager.save_grid_code_to_file()) + save_btn.addEventListener("click", save_proxy) + save_btn2.addEventListener("click", save_proxy) + + # MENU BUTTONS + # Bind new-file button + new_file = document.getElementById("new-file") + new_file_proxy = create_proxy(lambda *_: ui_manager.clear_grid()) + new_file.addEventListener("click", new_file_proxy) + + # Bind undo button + undo = document.getElementById("undo") + undo_proxy = create_proxy(lambda *_: game_manager.undo()) + undo.addEventListener("click", undo_proxy) + + # Bind redo button + redo = document.getElementById("redo") + redo_proxy = create_proxy(lambda *_: game_manager.redo()) + redo.addEventListener("click", redo_proxy) + + # Bind continue modal button + continue_btn = document.getElementById("continue-btn") + if continue_btn: + + def on_continue(*_args: Tuple) -> None: # noqa: UP006 + continue_modal("modal-bg") # hide modal + + continue_proxy = create_proxy(on_continue) + continue_btn.addEventListener("click", continue_proxy) + + # Bind keyboard event inside the game manager + handle_key_proxy = create_proxy(lambda evt: controller.handle_key(evt)) + window.addEventListener("keydown", handle_key_proxy) + + tick_proxy = create_proxy(lambda *_: (game_manager.tick(), ui_manager.render())) + setInterval(tick_proxy, 500) + + ui_manager.render() + + # Hide loading screen once game is ready + loading_screen = document.getElementById("loading-screen") + if loading_screen: + loading_screen.classList.add("hidden") + + return ui_manager + + +main() diff --git a/grand-gardenias/frontend/modes/code_editor/src/__init__.py b/grand-gardenias/frontend/modes/code_editor/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/code_editor/src/controls.py b/grand-gardenias/frontend/modes/code_editor/src/controls.py new file mode 100644 index 00000000..428c0a39 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/controls.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from js import HTMLInputElement, KeyboardEvent + +from engine.constants import MAX_BLOCK_LENGTH +from engine.controls import BaseController + + +class Controller(BaseController): + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def handle_input(self, evt: KeyboardEvent, input_box: HTMLInputElement) -> None: + """Spawn a new block when Enter is pressed.""" + # Only allow new block if none is falling + if self.game_manager.current_block and self.game_manager.current_block.falling: + return + try: + if evt.key == "Enter": + text = input_box.value.strip() + if 1 <= len(text) <= MAX_BLOCK_LENGTH: + self.game_manager.spawn_block(text) + input_box.value = "" + input_box.blur() + self.ui_manager.render() + except AttributeError: + pass diff --git a/grand-gardenias/frontend/modes/code_editor/src/execute_code.py b/grand-gardenias/frontend/modes/code_editor/src/execute_code.py new file mode 100644 index 00000000..06baa260 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/execute_code.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.game import BaseGameManager + +import io +import sys + +from js import document + + +def run_python_code(game_manager: BaseGameManager) -> str: + """Run the python code in the grid.""" + code = game_manager.format_grid_as_text() + buffer = io.StringIO() + sys_stdout = sys.stdout # Save the current stdout + try: + sys.stdout = buffer # Redirect stdout to buffer + exec(code) # noqa: S102 + output = buffer.getvalue() + except Exception as e: # noqa: BLE001 + # We don't know what exception may arise so this is a catch all solution + output = f"Error: {e}" + finally: + sys.stdout = sys_stdout # Restore original stdout + document.getElementById("CodeOutput").textContent = output diff --git a/grand-gardenias/frontend/modes/code_editor/src/game.py b/grand-gardenias/frontend/modes/code_editor/src/game.py new file mode 100644 index 00000000..9a0e3993 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/game.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import copy + +from engine.game import BaseGameManager + +Grid = list[list[str | None]] + + +class GameManager(BaseGameManager): + """Game logic manager.""" + + def __init__(self, *args: int, **kwargs: int) -> None: + super().__init__(*args, **kwargs) + self.undo_stack: list[Grid] = [] + self.redo_stack: list[Grid] = [] + + def lock_current_block(self) -> None: + """Lock current block into grid and add last state to undo history.""" + self.undo_stack.append(copy.deepcopy(self.grid)) + + super().lock_current_block() + + def undo(self) -> None: + """Undo block placement in the grid.""" + if not self.undo_stack: + return + self.redo_stack.append(copy.deepcopy(self.grid)) + self.grid = self.undo_stack.pop() + self.ui_manager.render() + + def redo(self) -> None: + """Redo block placement in the grid.""" + if not self.redo_stack: + return + self.undo_stack.append(copy.deepcopy(self.grid)) + self.grid = self.redo_stack.pop() + self.ui_manager.render() + + +game_manager = GameManager(40, 20) diff --git a/grand-gardenias/frontend/modes/code_editor/src/modal.py b/grand-gardenias/frontend/modes/code_editor/src/modal.py new file mode 100644 index 00000000..441f891d --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/modal.py @@ -0,0 +1,16 @@ +from js import document + + +def close_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + modal_bg = document.getElementById(id) + if modal_bg: + modal_bg.remove() + + +def continue_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + close_modal(id) + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() diff --git a/grand-gardenias/frontend/modes/code_editor/src/timer.py b/grand-gardenias/frontend/modes/code_editor/src/timer.py new file mode 100644 index 00000000..10036e32 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/timer.py @@ -0,0 +1,32 @@ +from constants import TIMER_MINUTES +from js import clearInterval, document, setInterval +from pyodide.ffi import create_proxy + +time_left = TIMER_MINUTES * 60 +interval_id = None + + +def run_timer() -> None: + """Run the timer.""" + global time_left, interval_id # noqa: PLW0603 + timer_element = document.getElementById("timer") + if not timer_element: + return + + minutes = time_left // 60 + seconds = time_left % 60 + timer_element.textContent = f"{minutes:02d}:{seconds:02d}" + + time_left -= 1 + if time_left < 0: + clearInterval(interval_id) + interval_id = None + + +def start_timer() -> None: + """Start the timer.""" + global interval_id # noqa: PLW0603 + if interval_id is None: + run_timer() + timer_proxy = create_proxy(run_timer) + interval_id = setInterval(timer_proxy, 1000) diff --git a/grand-gardenias/frontend/modes/code_editor/src/ui_manager.py b/grand-gardenias/frontend/modes/code_editor/src/ui_manager.py new file mode 100644 index 00000000..8e5ab187 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/ui_manager.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from engine.ui_manager import BaseUIManager +from js import document +from shared.audio_utils import play_place_sound + + +class UIManager(BaseUIManager): + """Handles rendering and UI interactions for the game.""" + + def lock_visual_cells(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Lock the cells occupied by the current block visually.""" + super().lock_visual_cells(*args, **kwargs) + + # Play place sound + play_place_sound() + + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() diff --git a/grand-gardenias/frontend/modes/coderush/__init__.py b/grand-gardenias/frontend/modes/coderush/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/coderush/config.json b/grand-gardenias/frontend/modes/coderush/config.json new file mode 100644 index 00000000..dfc0d42e --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/config.json @@ -0,0 +1,24 @@ +{ + "files": { + "../../engine/game.py": "engine/game.py", + "../../engine/patterns.py": "engine/patterns.py", + "../../engine/controls.py": "engine/controls.py", + "../../engine/ui_manager.py": "engine/ui_manager.py", + "../../engine/constants.py": "engine/constants.py", + "../../engine/objects/block.py": "engine/objects/block.py", + "../../shared/audio_utils.py": "shared/audio_utils.py", + + + + "./src/controls.py": "", + "./src/ui_manager.py": "", + "./src/modal.py": "", + + "./src/problem.py": "", + "./src/problem_helper.py": "", + "./src/timer.py":"", + + "./src/problems.json":"" + + } +} diff --git a/grand-gardenias/frontend/modes/coderush/index.html b/grand-gardenias/frontend/modes/coderush/index.html new file mode 100644 index 00000000..a98c734e --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/index.html @@ -0,0 +1,292 @@ + + + + + + + Code Rush + + + + + + + + + + + + + + + + + +
+
+

Tetris Bugs

+
+ +

Loading...

+
+
+ + + + + + + + + +
+ +
+ +
+

+

+
+ +
+ +

+ Current Score: 0
+ High Score: 0 +

+
+ +
+
+ + +
+
+
+
+ + +
+ +
+ + + + +

+

+
+ + + + +
+
+ + +
+ + + +
+ +
+
+ + + + + + + + + + + + + + + + diff --git a/grand-gardenias/frontend/modes/coderush/main.py b/grand-gardenias/frontend/modes/coderush/main.py new file mode 100644 index 00000000..c82cdaaf --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/main.py @@ -0,0 +1,72 @@ +import timer +from controls import Controller +from engine.game import BaseGameManager +from js import document, setInterval, window +from modal import continue_modal +from pyodide.ffi import create_proxy +from ui_manager import UIManager + +game_manager = BaseGameManager(40, 25) + + +def main() -> None: + """Initialize the game.""" + ui_manager = UIManager(game_manager) + game_manager.ui_manager = ui_manager # Inject ui_manager instance (dependency injection) + controller = Controller(game_manager, ui_manager) # Inject game_manager and ui_manager instance + + ui_manager.create_visual_grid() # Create display grid + + # Set timer callback for game over + timer.on_time_up = ui_manager.show_game_over + + # Bind text-input + input_box = document.getElementById("text-input") + input_proxy = create_proxy(lambda evt: controller.handle_input(evt, input_box)) + input_box.addEventListener("keydown", input_proxy) + + # Bind save button + save_btn = document.getElementById("save-btn") + save_proxy = create_proxy(lambda *_: ui_manager.save_grid_code_to_file()) + save_btn.addEventListener("click", save_proxy) + + # Bind run button + run_btn = document.getElementById("run-btn") + run_proxy = create_proxy(lambda *_: ui_manager.problem_switch()) + run_btn.addEventListener("click", run_proxy) + + # Bind retry button + retry_btn = document.getElementById("retry-btn") + retry_proxy = create_proxy(lambda *_: ui_manager.clear_grid()) + retry_btn.addEventListener("click", retry_proxy) + + # Bind restart button + restart_btn = document.getElementById("restart-btn") + restart_proxy = create_proxy(lambda *_: ui_manager.restart_game()) + restart_btn.addEventListener("click", restart_proxy) + + # Bind continue modal button and start timer + + continue_btn = document.getElementById("continue-btn") + continue_proxy = create_proxy(lambda _evt: continue_modal("modal-bg")) + continue_btn.addEventListener("click", continue_proxy) + + # Bind keyboard event inside the game manager + handle_key_proxy = create_proxy(lambda evt: controller.handle_key(evt)) + window.addEventListener("keydown", handle_key_proxy) + + tick_proxy = create_proxy(lambda *_: (game_manager.tick(), ui_manager.render())) + setInterval(tick_proxy, 500) + + # Kick start + ui_manager.render() + ui_manager.show_problem() + ui_manager.update_score_display() + + # Hide loading screen once game is ready + loading_screen = document.getElementById("loading-screen") + if loading_screen: + loading_screen.classList.add("hidden") + + +main() diff --git a/grand-gardenias/frontend/modes/coderush/src/__init__.py b/grand-gardenias/frontend/modes/coderush/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/coderush/src/controls.py b/grand-gardenias/frontend/modes/coderush/src/controls.py new file mode 100644 index 00000000..428c0a39 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/controls.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from js import HTMLInputElement, KeyboardEvent + +from engine.constants import MAX_BLOCK_LENGTH +from engine.controls import BaseController + + +class Controller(BaseController): + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def handle_input(self, evt: KeyboardEvent, input_box: HTMLInputElement) -> None: + """Spawn a new block when Enter is pressed.""" + # Only allow new block if none is falling + if self.game_manager.current_block and self.game_manager.current_block.falling: + return + try: + if evt.key == "Enter": + text = input_box.value.strip() + if 1 <= len(text) <= MAX_BLOCK_LENGTH: + self.game_manager.spawn_block(text) + input_box.value = "" + input_box.blur() + self.ui_manager.render() + except AttributeError: + pass diff --git a/grand-gardenias/frontend/modes/coderush/src/modal.py b/grand-gardenias/frontend/modes/coderush/src/modal.py new file mode 100644 index 00000000..92a621f0 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/modal.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from js import document +from timer import start_timer + + +def close_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + modal_bg = document.getElementById(id) + if modal_bg: + modal_bg.remove() + + +def continue_modal(id: str) -> None: + """Close a modal and spawn the first block.""" + close_modal(id) + + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() + start_timer() # start the timer diff --git a/grand-gardenias/frontend/modes/coderush/src/problem.py b/grand-gardenias/frontend/modes/coderush/src/problem.py new file mode 100644 index 00000000..d881f53c --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/problem.py @@ -0,0 +1,33 @@ +from engine.patterns import SingletonMeta +from js import document +from problem_helper import get_ques + + +class Problem(metaclass=SingletonMeta): # noqa: D101 + def __init__(self) -> None: + self.problem_id = 1 + self.problems_solved = 0 + self.problem_title = get_ques(self.problem_id)["title"] + self.problem_desc = get_ques(self.problem_id)["description"] + self.title_elem = document.getElementById("problem-title") + self.desc_elem = document.getElementById("problem-desc") + + def render(self) -> None: + """Render question.""" + self.title_elem.innerText = self.problem_title + self.desc_elem.innerText = self.problem_desc + + def switch_problem(self) -> None: + """Switch_problem.""" + self.problems_solved += 1 + if get_ques(self.problem_id) != {}: + self.problem_title = get_ques(self.problem_id)["title"] + self.problem_desc = get_ques(self.problem_id)["description"] + self.render() + else: + self.problem_title = "Winner!" + self.problem_desc = "You beat the game!" + self.render() + + +problem_manager = Problem() diff --git a/grand-gardenias/frontend/modes/coderush/src/problem_helper.py b/grand-gardenias/frontend/modes/coderush/src/problem_helper.py new file mode 100644 index 00000000..13a8e3c1 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/problem_helper.py @@ -0,0 +1,48 @@ +import json + + +def check_code(code: str, id: int) -> str: # noqa: C901 + """Check code and test case.""" + if not code: + return "Write some code, dumbo." + ques = get_ques(id) + namespace = {} + try: + exec(code, namespace) # noqa: S102 + try: + func = namespace[ques["func_name"]] + validity_list = [] + + for i in ques["test_cases"]: + if isinstance(i["input"], list) and len(i["input"]) == 2: # noqa: PLR2004 + if func(*tuple(i["input"])) == i["expected_output"]: + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + elif str(func(i["input"])) == str(i["expected_output"]): + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + failed_case = [] + for i, test_case in validity_list: + if not i: + failed_case.append((i, test_case)) + if failed_case: + return f"Incorrect: {failed_case} {len(failed_case)} test case(s) failed" + return "correct" # noqa: TRY300 + + except (KeyError, TypeError): + return "incorrect function" + + except (NameError, IndentationError, SyntaxError): + return "wrong code" + + +# Get questions from json file +def get_ques(id: int) -> dict: + """Get ques from json file.""" + try: + with open("problems.json") as f: # noqa: PTH123 + return json.loads(f.read())[id - 1] + except IndexError: + return {} diff --git a/grand-gardenias/frontend/modes/coderush/src/problems.json b/grand-gardenias/frontend/modes/coderush/src/problems.json new file mode 100644 index 00000000..24f30538 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/problems.json @@ -0,0 +1,606 @@ +[ + { + "problem_id": 1, + "title": "Add Two Numbers", + "func_name":"add_numbers", + "description": "Write a function named 'add_numbers' that takes two numbers as input and returns their sum.", + "solution_code": "def add_numbers(a, b):\n return a + b", + "test_cases": [ + { + "input": [5, 3], + "expected_output": 8 + }, + { + "input": [0, 0], + "expected_output": 0 + }, + { + "input": [-10, 5], + "expected_output": -5 + } + ] + }, + { + "problem_id": 2, + "title": "Reverse a String", + "func_name":"reverse_string", + "description": "Write a function named 'reverse_string' that takes a string as input and returns the string reversed.", + "solution_code": "def reverse_string(s):\n return s[::-1]", + "test_cases": [ + { + "input": "hello", + "expected_output": "olleh" + }, + { + "input": "Python", + "expected_output": "nohtyP" + }, + { + "input": "a", + "expected_output": "a" + } + ] + }, + { + "problem_id": 3, + "title": "Check if a Number is Even", + "func_name":"is_even", + "description": "Write a function named 'is_even' that takes an integer and returns True if it's even, otherwise False.", + "solution_code": "def is_even(n):\n return n % 2 == 0", + "test_cases": [ + { + "input": 4, + "expected_output": "True" + }, + { + "input": 7, + "expected_output": "False" + }, + { + "input": 0, + "expected_output": "True" + } + ] + }, + { + "problem_id": 4, + "title": "Find the Maximum Number in a List", + "func_name":"find_max", + "description": "Write a function named 'find_max' that takes a list of numbers and returns the largest number in the list.", + "solution_code": "def find_max(numbers):\n return max(numbers)", + "test_cases": [ + { + "input": [1, 5, 2, 8, 3], + "expected_output": 8 + }, + { + "input": [-10, -2, -5], + "expected_output": -2 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 5, + "title": "Calculate Factorial", + "func_name":"factorial", + "description": "Write a function named 'factorial' to calculate the factorial of a non-negative integer.", + "solution_code": "def factorial(n):\n if n == 0:\n return 1\n else:\n return n * factorial(n - 1)", + "test_cases": [ + { + "input": 5, + "expected_output": 120 + }, + { + "input": 0, + "expected_output": 1 + }, + { + "input": 3, + "expected_output": 6 + } + ] + }, + { + "problem_id": 6, + "title": "Count Vowels in a String", + "func_name":"count_vowels", + "description": "Write a function named 'count_vowels' that takes a string and returns the number of vowels (a, e, i, o, u) in it.", + "solution_code": "def count_vowels(s):\n vowels = \"aeiou\"\n count = 0\n for char in s.lower():\n if char in vowels:\n count += 1\n return count", + "test_cases": [ + { + "input": "hello", + "expected_output": 2 + }, + { + "input": "AEIOU", + "expected_output": 5 + }, + { + "input": "rhythm", + "expected_output": 0 + } + ] + }, + { + "problem_id": 7, + "title": "Check for Palindrome", + "func_name":"is_palindrome", + "description": "Write a function named 'is_palindrome' that checks if a string is a palindrome (reads the same forwards and backwards).", + "solution_code": "def is_palindrome(s):\n return s.lower() == s.lower()[::-1]", + "test_cases": [ + { + "input": "racecar", + "expected_output": "True" + }, + { + "input": "hello", + "expected_output": "False" + }, + { + "input": "Madam", + "expected_output": "True" + } + ] + }, + { + "problem_id": 8, + "title": "Find the Smallest Number in a List", + "func_name":"find_min", + "description": "Write a function named 'find_min' that takes a list of numbers and returns the smallest number.", + "solution_code": "def find_min(numbers):\n return min(numbers)", + "test_cases": [ + { + "input": [10, 5, 2, 8, 3], + "expected_output": 2 + }, + { + "input": [-10, -2, -5], + "expected_output": -10 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 9, + "title": "Sum all Numbers in a List", + "func_name":"sum_list", + "description": "Write a function named 'sum_list' that takes a list of numbers and returns their sum.", + "solution_code": "def sum_list(numbers):\n return sum(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": 10 + }, + { + "input": [0, 0, 0], + "expected_output": 0 + }, + { + "input": [10, -5, 2], + "expected_output": 7 + } + ] + }, + { + "problem_id": 10, + "title": "Remove Duplicates from a List", + "func_name":"remove_duplicates", + "description": "Write a function named 'remove_duplicates' that takes a list and returns a new list with all duplicates removed.", + "solution_code": "def remove_duplicates(l):\n return list(set(l))", + "test_cases": [ + { + "input": [1, 2, 2, 3, 4, 4, 5], + "expected_output": [1, 2, 3, 4, 5] + }, + { + "input": ["a", "b", "c", "a"], + "expected_output": ["a", "b", "c"] + }, + { + "input": [1, 1, 1], + "expected_output": [1] + } + ] + }, + { + "problem_id": 11, + "title": "Calculate Average", + "func_name":"calculate_average", + "description": "Write a function named 'calculate_average' that takes a list of numbers and returns their average.", + "solution_code": "def calculate_average(numbers):\n if not numbers:\n return 0\n return sum(numbers) / len(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4, 5], + "expected_output": 3.0 + }, + { + "input": [10, 20, 30], + "expected_output": 20.0 + }, + { + "input": [], + "expected_output": 0 + } + ] + }, + { + "problem_id": 12, + "title": "Find the Longest Word", + "func_name":"find_longest_word", + "description": "Write a function named 'find_longest_word' that takes a list of words and returns the longest word.", + "solution_code": "def find_longest_word(words):\n return max(words, key=len)", + "test_cases": [ + { + "input": ["apple", "banana", "kiwi"], + "expected_output": "banana" + }, + { + "input": ["a", "bb", "ccc"], + "expected_output": "ccc" + }, + { + "input": ["one"], + "expected_output": "one" + } + ] + }, + { + "problem_id": 13, + "title": "Is String a Substring?", + "func_name":"is_substring", + "description": "Write a function named 'is_substring' that checks if a string is a substring of another string.", + "solution_code": "def is_substring(sub, main):\n return sub in main", + "test_cases": [ + { + "input": ["world", "hello world"], + "expected_output": "True" + }, + { + "input": ["moon", "sun"], + "expected_output": "False" + }, + { + "input": ["", "any string"], + "expected_output": "True" + } + ] + }, + { + "problem_id": 14, + "title": "Calculate Circle Area", + "func_name":"circle_area", + + "description": "Write a function named 'circle_area' that takes the radius of a circle and returns its area.", + "solution_code": "import math\ndef circle_area(r):\n return math.pi * r**2", + "test_cases": [ + { + "input": 1, + "expected_output": 3.141592653589793 + }, + { + "input": 0, + "expected_output": 0.0 + }, + { + "input": 2.5, + "expected_output": 19.634954084936208 + } + ] + }, + { + "problem_id": 15, + "title": "Convert Celsius to Fahrenheit", + "func_name":"celsius_to_farenheit", + "description": "Write a function named 'celsius_to_fahrenheit' that converts a temperature from Celsius to Fahrenheit.", + "solution_code": "def celsius_to_fahrenheit(c):\n return (c * 9/5) + 32", + "test_cases": [ + { + "input": 0, + "expected_output": 32.0 + }, + { + "input": 100, + "expected_output": 212.0 + }, + { + "input": -40, + "expected_output": -40.0 + } + ] + }, + { + "problem_id": 16, + "title": "Count Words in a String", + "func_name":"count_words", + "description": "Write a function named 'count_words' that counts the number of words in a given string.", + "solution_code": "def count_words(s):\n return len(s.split())", + "test_cases": [ + { + "input": "Hello world", + "expected_output": 2 + }, + { + "input": "This is a test sentence.", + "expected_output": 5 + }, + { + "input": "oneword", + "expected_output": 1 + } + ] + }, + { + "problem_id": 17, + "title": "Find First Uppercase Letter", + "func_name":"find_first_uppercase", + "description": "Write a function named 'find_first_uppercase' that finds the first uppercase letter in a string.", + "solution_code": "def find_first_uppercase(s):\n for char in s:\n if 'A' <= char <= 'Z':\n return char\n return None", + "test_cases": [ + { + "input": "helloWorld", + "expected_output": "W" + }, + { + "input": "alllowercase", + "expected_output": null + }, + { + "input": "UPPERCASE", + "expected_output": "U" + } + ] + }, + { + "problem_id": 18, + "title": "Generate a Fibonacci Sequence", + "func_name":"fibonacci_sequence", + + "description": "Write a function named 'fibonacci_sequence' that generates a Fibonacci sequence up to a given number of terms.", + "solution_code": "def fibonacci_sequence(n):\n sequence = []\n a, b = 0, 1\n while len(sequence) < n:\n sequence.append(a)\n a, b = b, a + b\n return sequence", + "test_cases": [ + { + "input": 5, + "expected_output": [0, 1, 1, 2, 3] + }, + { + "input": 1, + "expected_output": [0] + }, + { + "input": 0, + "expected_output": [] + } + ] + }, + { + "problem_id": 19, + "title": "Check if a Number is Prime", + "func_name":"is_prime", + "description": "Write a function named 'is_prime' that checks if a number is a prime number.", + "solution_code": "def is_prime(n):\n if n <= 1:\n return False\n for i in range(2, int(n**0.5) + 1):\n if n % i == 0:\n return False\n return True", + "test_cases": [ + { + "input": 7, + "expected_output": "True" + }, + { + "input": 10, + "expected_output": "False" + }, + { + "input": 1, + "expected_output": "False" + } + ] + }, + { + "problem_id": 20, + "title": "FizzBuzz", + "func_name":"fizzbuzz", + "description": "Write a function named 'fizzbuzz' that prints numbers from 1 to 15. For multiples of 3 print 'Fizz', for multiples of 5 print 'Buzz', and for multiples of both, print 'FizzBuzz'.", + "solution_code": "def fizzbuzz():\n results = []\n for i in range(1, 16):\n if i % 3 == 0 and i % 5 == 0:\n results.append('FizzBuzz')\n elif i % 3 == 0:\n results.append('Fizz')\n elif i % 5 == 0:\n results.append('Buzz')\n else:\n results.append(str(i))\n return results", + "test_cases": [ + { + "input": null, + "expected_output": ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"] + } + ] + }, + { + "problem_id": 21, + "title": "Merge Two Lists", + "func_name":"merge_lists", + "description": "Write a function named 'merge_lists' that takes two lists and returns a new list containing all elements from both.", + "solution_code": "def merge_lists(list1, list2):\n return list1 + list2", + "test_cases": [ + { + "input": [[1, 2], [3, 4]], + "expected_output": [1, 2, 3, 4] + }, + { + "input": [["a"], ["b", "c"]], + "expected_output": ["a", "b", "c"] + }, + { + "input": [[], [1, 2, 3]], + "expected_output": [1, 2, 3] + } + ] + }, + { + "problem_id": 22, + "title": "Check if List is Sorted", + "func_name":"is_sorted", + "description": "Write a function named 'is_sorted' that returns True if a list is sorted in ascending order, otherwise False.", + "solution_code": "def is_sorted(l):\n return l == sorted(l)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [4, 3, 2, 1], + "expected_output": "False" + }, + { + "input": [1, 3, 2], + "expected_output": "False" + } + ] + }, + { + "problem_id": 23, + "title": "Reverse a List", + "func_name":"reverse_list", + "description": "Write a function named 'reverse_list' that reverses the order of elements in a list.", + "solution_code": "def reverse_list(l):\n return l[::-1]", + "test_cases": [ + { + "input": [1, 2, 3], + "expected_output": [3, 2, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 24, + "title": "Sum of Digits", + "func_name":"sum_digits", + "description": "Write a function named 'sum_digits' that calculates the sum of the digits of an integer.", + "solution_code": "def sum_digits(n):\n return sum(int(digit) for digit in str(n))", + "test_cases": [ + { + "input": 123, + "expected_output": 6 + }, + { + "input": 999, + "expected_output": 27 + }, + { + "input": 0, + "expected_output": 0 + } + ] + }, + + { + "problem_id": 25, + "func_name":"is_digit_string", + "title": "Check if a String Contains Only Digits", + "description": "Write a function named 'is_digit_string' that returns True if a string contains only digits, otherwise False.", + "solution_code": "def is_digit_string(s):\n return s.isdigit()", + "test_cases": [ + { + "input": "12345", + "expected_output": "True" + }, + { + "input": "123a45", + "expected_output": "False" + }, + { + "input": "", + "expected_output": "False" + } + ] + }, + { + "problem_id": 26, + "func_name":"swap_first_last", + "title": "Swap First and Last Elements of a List", + "description": "Write a function named 'swap_first_last' that swaps the first and last elements of a list and returns the modified list.", + "solution_code": "def swap_first_last(l):\n if len(l) > 1:\n l[0], l[-1] = l[-1], l[0]\n return l", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": [4, 2, 3, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 27, + "title": "Convert a String to a List of Characters", + "func_name":"string_to_list", + "description": "Write a function named 'string_to_list' that takes a string and converts it into a list of its characters.", + "solution_code": "def string_to_list(s):\n return list(s)", + "test_cases": [ + { + "input": "hello", + "expected_output": ["h", "e", "l", "l", "o"] + }, + { + "input": "Python", + "expected_output": ["P", "y", "t", "h", "o", "n"] + }, + { + "input": "", + "expected_output": [] + } + ] + }, + { + "problem_id": 28, + "func_name":"all_are_unique", + "title": "Check if all Elements in a List are Unique", + "description": "Write a function named 'are_all_unique' that returns True if all elements in a list are unique, otherwise False.", + "solution_code": "def are_all_unique(l):\n return len(l) == len(set(l))", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [1, 2, 2, 3], + "expected_output": "False" + }, + { + "input": [], + "expected_output": "True" + } + ] + }, + { + "problem_id": 29, + "title": "Find the Intersection of Two Lists", + "func_name":"list_intersection", + "description": "Write a function named 'list_intersection' that takes two lists and returns a new list containing only the elements that are common to both.", + "solution_code": "def list_intersection(list1, list2):\n return list(set(list1) & set(list2))", + "test_cases": [ + { + "input": [[1, 2, 3], [3, 4, 5]], + "expected_output": [3] + }, + { + "input": [[1, 2], [3, 4]], + "expected_output": [] + }, + { + "input": [["a", "b"], ["b", "c"]], + "expected_output": ["b"] + } + ] + } + ] diff --git a/grand-gardenias/frontend/modes/coderush/src/timer.py b/grand-gardenias/frontend/modes/coderush/src/timer.py new file mode 100644 index 00000000..f7303002 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/timer.py @@ -0,0 +1,46 @@ +from engine.constants import TIMER_MINUTES +from js import clearInterval, document, setInterval +from pyodide.ffi import create_proxy + +time_left = TIMER_MINUTES * 60 +interval_id = None + +# Callback to be called when timer runs out +on_time_up = None + + +def run_timer() -> None: + """Run the timer.""" + global time_left, interval_id # noqa: PLW0603 + timer_element = document.getElementById("timer") + if not timer_element: + return + + minutes = time_left // 60 + seconds = time_left % 60 + timer_element.textContent = f"{minutes:02d}:{seconds:02d}" + + time_left -= 1 + if time_left < 0: + clearInterval(interval_id) + interval_id = None + if on_time_up is not None: + on_time_up() + + +def start_timer() -> None: + """Start the timer.""" + global interval_id # noqa: PLW0603 + if interval_id is None: + run_timer() + timer_proxy = create_proxy(run_timer) + interval_id = setInterval(timer_proxy, 1000) + + +def reset_timer() -> None: + """Reset the timer to full duration.""" + global time_left, interval_id # noqa: PLW0603 + time_left = TIMER_MINUTES * 60 + if interval_id is not None: + clearInterval(interval_id) + interval_id = None diff --git a/grand-gardenias/frontend/modes/coderush/src/ui_manager.py b/grand-gardenias/frontend/modes/coderush/src/ui_manager.py new file mode 100644 index 00000000..21eeb2c9 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/ui_manager.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +from engine.ui_manager import BaseUIManager +from js import document, localStorage, window +from problem import problem_manager +from problem_helper import check_code, get_ques +from shared.audio_utils import play_place_sound, win_sound + + +class UIManager(BaseUIManager): + """Handles rendering and UI interactions for the game.""" + + def clear_grid(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Clear the grid by resetting all cells to None.""" + super().clear_grid(*args, **kwargs) + + # Clear the code output + output = document.getElementById("code-output") + if output: + output.innerText = "" + # Focus the input field + input_box = document.getElementById("text-input") + if input_box: + input_box.focus() + self.update_score_display() + + def lock_visual_cells(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Lock the cells occupied by the current block visually.""" + super().lock_visual_cells(*args, **kwargs) + + # Play place sound + play_place_sound() + + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() + + # Mode-specific methods + + def show_problem(self) -> None: + """Show and update problem.""" + problem_manager.render() + + def update_score_display(self) -> None: + """Update both current score and high score displays above the grid.""" + # Update current score + current_score = problem_manager.problems_solved + current_score_elem = document.getElementById("current-score") + if current_score_elem: + current_score_elem.innerText = str(current_score) + + # Update high score + current_high_score = self.get_current_high_score() + high_score_elem = document.getElementById("current-high-score") + if high_score_elem: + high_score_elem.innerText = str(current_high_score) + + def update_high_score_display(self) -> None: + """Update the high score display above the grid.""" + current_high_score = self.get_current_high_score() + high_score_elem = document.getElementById("current-high-score") + if high_score_elem: + high_score_elem.innerText = str(current_high_score) + + def get_current_high_score(self) -> int: + """Get the current high score from localStorage.""" + current_high_score = localStorage.getItem("tetris_high_score") + if current_high_score is None or str(current_high_score).lower() in ["null", "jsnull", "undefined"]: + return 0 + try: + return int(str(current_high_score)) + except ValueError: + return 0 + + def problem_switch(self) -> None: + """Move to next problem.""" + output = document.getElementById("code-output") + code = self.game.format_grid_as_text() + if check_code(code, problem_manager.problem_id) == "correct": + problem_manager.problem_id += 1 + problem_manager.switch_problem() + current_high_score = self.get_current_high_score() + current_score = problem_manager.problems_solved + if current_score > current_high_score: + localStorage.setItem("tetris_high_score", str(current_score)) + self.update_score_display() + self.clear_grid() + win_sound() + output.innerText = "Correct!" + elif check_code(code, problem_manager.problem_id) == "wrong code": + # Play wrong answer sound + if hasattr(window, "playWrongAnswerSound"): + window.playWrongAnswerSound() + output.innerText = "Incorrect Code, try again" + + elif check_code(code, problem_manager.problem_id) == "incorrect function": + # Play wrong answer sound + if hasattr(window, "playWrongAnswerSound"): + window.playWrongAnswerSound() + output.innerText = "Check your function name" + + else: + # Play wrong answer sound for any other incorrect result + if hasattr(window, "playWrongAnswerSound"): + window.playWrongAnswerSound() + output.innerText = check_code(code, problem_manager.problem_id) + + def show_game_over(self) -> None: + """Show game over screen with high score.""" + # Get current high score from localStorage + current_high_score = self.get_current_high_score() + # Check if current score is higher + current_score = problem_manager.problems_solved + if current_score > current_high_score: + localStorage.setItem("tetris_high_score", str(current_score)) + current_high_score = current_score + # Update the game over modal + problems_solved_elem = document.getElementById("problems-solved") + high_score_elem = document.getElementById("high-score") + if problems_solved_elem: + problems_solved_elem.innerText = str(current_score) + if high_score_elem: + high_score_elem.innerText = str(current_high_score) + # Show the game over modal + game_over_modal = document.getElementById("game-over-modal") + if game_over_modal: + game_over_modal.classList.remove("hidden") + # Update the score display + self.update_score_display() + # Pause the game + self.game.paused = True + + def restart_game(self) -> None: + """Restart the game.""" + problem_manager.problem_id = 1 + problem_manager.problems_solved = 0 + problem_manager.problem_title = get_ques(problem_manager.problem_id)["title"] + problem_manager.problem_desc = get_ques(problem_manager.problem_id)["description"] + problem_manager.render() + self.update_score_display() + self.clear_grid() + game_over_modal = document.getElementById("game-over-modal") + if game_over_modal: + game_over_modal.classList.add("hidden") + self.game.paused = False + from timer import reset_timer, start_timer # noqa: PLC0415 + + reset_timer() + start_timer() + self.update_score_display() diff --git a/grand-gardenias/frontend/modes/roguelike/__init__.py b/grand-gardenias/frontend/modes/roguelike/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/roguelike/config.json b/grand-gardenias/frontend/modes/roguelike/config.json new file mode 100644 index 00000000..bf29072f --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/config.json @@ -0,0 +1,20 @@ +{ + "files": { + "../../engine/game.py": "engine/game.py", + "../../engine/patterns.py": "engine/patterns.py", + "../../engine/controls.py": "engine/controls.py", + "../../engine/ui_manager.py": "engine/ui_manager.py", + "../../engine/constants.py": "engine/constants.py", + "../../engine/objects/block.py": "engine/objects/block.py", + + "./src/game.py": "", + "./src/modal.py": "", + + "./src/block_generator.py": "", + "./src/question_manager.py": "", + "./src/game_state.py": "", + "./src/controls.py": "", + "./src/problem_helper.py": "", + "./src/problems.json": "" + } +} diff --git a/grand-gardenias/frontend/modes/roguelike/index.html b/grand-gardenias/frontend/modes/roguelike/index.html new file mode 100644 index 00000000..3dff3e16 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/index.html @@ -0,0 +1,98 @@ + + + + + + + Roguelike + + + + + + + + + + + + + + + + + +
+
+

Tetris Bugs

+
+ +

Loading...

+
+
+ + + + + +
+
+ + +
+ + +
+ +

+ +
+ + +
+
+ +
+ +
+ + +
+ + + import sys + sys.modules.pop("main_v2", None) + + + + + + + + + + diff --git a/grand-gardenias/frontend/modes/roguelike/main.py b/grand-gardenias/frontend/modes/roguelike/main.py new file mode 100644 index 00000000..9e10662d --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/main.py @@ -0,0 +1,45 @@ +from block_generator import block_generator +from controls import Controller +from engine.ui_manager import BaseUIManager +from game import game_manager +from js import document, setInterval, window +from modal import continue_modal +from pyodide.ffi import create_proxy + + +def main() -> None: + """Initialize the game.""" + ui_manager = BaseUIManager(game_manager) + game_manager.ui_manager = ui_manager # Inject ui_manager instance (dependency injection) + controller = Controller(game_manager, ui_manager) # Inject game_manager and ui_manager instance + game_manager.block_gen = block_generator(ui_manager) + + ui_manager.create_visual_grid() # Create display grid + + # Bind continue modal button and start timer + continue_btn = document.getElementById("continue-btn") + + continue_proxy = create_proxy(lambda _evt: continue_modal("modal-bg", ui_manager)) + continue_btn.addEventListener("click", continue_proxy) + + # Bind the retry button to refresh the page + retry_button = document.getElementById("retry-btn") + retry_proxy = create_proxy(lambda _evt: window.location.reload()) + retry_button.addEventListener("click", retry_proxy) + + # Bind keyboard event inside the game manager + handle_key_proxy = create_proxy(lambda evt: controller.handle_key(evt)) + window.addEventListener("keydown", handle_key_proxy) + + tick_proxy = create_proxy(lambda *_: (game_manager.tick(), ui_manager.render())) + setInterval(tick_proxy, 500) + + ui_manager.render() + + # Hide loading screen once game is ready + loading_screen = document.getElementById("loading-screen") + if loading_screen: + loading_screen.classList.add("hidden") + + +main() diff --git a/grand-gardenias/frontend/modes/roguelike/src/__init__.py b/grand-gardenias/frontend/modes/roguelike/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/roguelike/src/block_generator.py b/grand-gardenias/frontend/modes/roguelike/src/block_generator.py new file mode 100644 index 00000000..398b0119 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/block_generator.py @@ -0,0 +1,60 @@ +import random +from collections.abc import Generator + +from game import game_manager +from question_manager import question_manager + + +def split_into_blocks(s: str, block_size: int = 5) -> list[str]: + """Split a string into blocks of given size, last block may be shorter.""" + return [s[i : i + block_size] for i in range(0, len(s), block_size)] + + +def block_generator(renderer: str) -> Generator[str]: + """Yield blocks of a program string from bottom to top for gameplay. + + Each line of the program is split into mostly 5-character blocks, + and blocks are yielded in random order. After yielding a line's + blocks, the generator checks the corresponding player's line: + if it matches, the row is cleared from both the game state and renderer. + + Args: + renderer (str): The GridRenderer instance used to update the visual grid. + + Yields: + str: Individual blocks of code from the program string, in random order. + + """ + + def first_empty_row_from_bottom() -> int: + """Return the 1-based index of the first completely empty row from the bottom.""" + n = len(game_manager.grid) + for i in range(n - 1, -1, -1): + if all(not cell for cell in game_manager.grid[i]): + # Distance from bottom + return n - i + return 1 # fallback if no empty row + + question_details = question_manager.get_question() + lines = question_details["solution_code"].splitlines() + bottom_pointer = 1 + + for line in reversed(lines): + blocks = split_into_blocks(line.strip()) + + while len(blocks): + rand_j = random.randint(0, len(blocks) - 1) # noqa: S311 + yield blocks[rand_j] + blocks.pop(rand_j) + + player_line = game_manager.format_grid_line_as_text(-(bottom_pointer)) + + if player_line.strip() == line.strip(): + # Clear the row if correctly answered + game_manager.clear_row(-(bottom_pointer)) + renderer.clear_row(-(bottom_pointer)) + else: + # Incorrect answer + # Increment the pointer, as the current row will stay stuck + bottom_pointer = first_empty_row_from_bottom() + game_manager.bottom_pointer = bottom_pointer diff --git a/grand-gardenias/frontend/modes/roguelike/src/controls.py b/grand-gardenias/frontend/modes/roguelike/src/controls.py new file mode 100644 index 00000000..8f519b88 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/controls.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from engine.controls import BaseController +from js import KeyboardEvent, document + + +class Controller(BaseController): + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def handle_key(self, evt: KeyboardEvent) -> None: + """Handle arrow keys and spacebar.""" + active = document.activeElement + if active and active.id == "text-input": + return + + if not self.game_manager.current_block: + return + + moved = False + if evt.key == "ArrowLeft": + moved = self.game_manager.current_block.move(-1, 0, self.game_manager.grid) + elif evt.key == "ArrowRight": + moved = self.game_manager.current_block.move(1, 0, self.game_manager.grid) + elif evt.key == "ArrowDown": + moved = self.game_manager.current_block.move(0, 1, self.game_manager.grid) + + if moved: + self.ui_manager.render() diff --git a/grand-gardenias/frontend/modes/roguelike/src/game.py b/grand-gardenias/frontend/modes/roguelike/src/game.py new file mode 100644 index 00000000..fb8406c7 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/game.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator + + from engine.objects.block import Block + from engine.ui_manager import BaseUIManager + +from engine.game import BaseGameManager +from game_state import update_result + + +class GameManager(BaseGameManager): + # class GameManager(metaclass=SingletonMeta): + """Game logic manager.""" + + def __init__(self, *args: int, **kwargs: int) -> None: + super().__init__(*args, **kwargs) + self.block_gen: Iterator[Block] | None = None + self.ui_manager: BaseUIManager = None + self.bottom_pointer = 1 + + def tick(self) -> None: + """Advance game state by one step.""" + if not self.current_block or not self.current_block.falling: + return + + if self.current_block.y > self.rows - self.bottom_pointer: + self.lock_current_block() + + if not self.current_block.move(0, 1, self.grid): + self.lock_current_block() + + def spawn_next_block(self) -> None: + """Generate and spawn the next block.""" + try: + self.spawn_block(next(self.block_gen)) + except StopIteration: + update_result(self.format_grid_as_text()) + + def lock_current_block(self) -> None: + """Lock current block into grid.""" + super().lock_current_block() + self.spawn_next_block() + + +game_manager = GameManager(40, 20) diff --git a/grand-gardenias/frontend/modes/roguelike/src/game_state.py b/grand-gardenias/frontend/modes/roguelike/src/game_state.py new file mode 100644 index 00000000..aa905548 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/game_state.py @@ -0,0 +1,24 @@ +from js import document +from problem_helper import check_code +from question_manager import question_manager + + +def check_win(code: str) -> bool: # noqa: RET503 + """Check Whether the player has won or lost.""" + result = check_code(code, question_manager.ques_id) + if result in {"wrong code", "incorrect function"}: + return False + if result == "correct": + return True + + +def update_result(code: str) -> None: + """Update the UI after checking of the game status.""" + winning_status = check_win(code) + game_state = document.getElementById("game-state") + game_end_box = document.getElementById("game-end") + game_end_box.classList.remove("hidden") + if winning_status: + game_state.textContent = "Wow you got the code right" + else: + game_state.textContent = "No.. That is not right" diff --git a/grand-gardenias/frontend/modes/roguelike/src/modal.py b/grand-gardenias/frontend/modes/roguelike/src/modal.py new file mode 100644 index 00000000..4f078c0b --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/modal.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.ui_manager import BaseUIManager + +from game import game_manager +from js import document + + +def close_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + modal_bg = document.getElementById(id) + if modal_bg: + modal_bg.remove() + + +def continue_modal(id: str, ui_manager: BaseUIManager) -> None: + """Close a modal and spawn the first block.""" + close_modal(id) + + # Spawn the first block + game_manager.spawn_next_block() + ui_manager.render() diff --git a/grand-gardenias/frontend/modes/roguelike/src/problem_helper.py b/grand-gardenias/frontend/modes/roguelike/src/problem_helper.py new file mode 100644 index 00000000..446a1f39 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/problem_helper.py @@ -0,0 +1,48 @@ +import json + + +def check_code(code: str, id: int) -> str: # noqa: C901 + """Check code and test case.""" + if not code: + return "Write some code, dumbo." + ques = get_ques(id) + namespace = {} + try: + exec(code, namespace) # noqa: S102 + try: + func = namespace[ques["func_name"]] + validity_list = [] + + for i in ques["test_cases"]: + if isinstance(i["input"], list) and len(i["input"]) == 2: # noqa: PLR2004 + if func(*tuple(i["input"])) == i["expected_output"]: + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + elif str(func(i["input"])) == str(i["expected_output"]): + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + failed_case = [] + for i, test_case in validity_list: + if not i: + failed_case.append((i, test_case)) + if failed_case: + return f"Incorrect: {failed_case} {len(failed_case)} test case(s) failed" + return "correct" # noqa: TRY300 + + except (KeyError, TypeError): + return "incorrect function" + + except (NameError, IndentationError, SyntaxError): + return "wrong code" + + +# Get questions from json file +def get_ques(id: int) -> dict: + """Get ques from json file.""" + try: + with open(r"problems.json") as f: # noqa: PTH123 + return json.loads(f.read())[id - 1] + except IndexError: + return {} diff --git a/grand-gardenias/frontend/modes/roguelike/src/problems.json b/grand-gardenias/frontend/modes/roguelike/src/problems.json new file mode 100644 index 00000000..24f30538 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/problems.json @@ -0,0 +1,606 @@ +[ + { + "problem_id": 1, + "title": "Add Two Numbers", + "func_name":"add_numbers", + "description": "Write a function named 'add_numbers' that takes two numbers as input and returns their sum.", + "solution_code": "def add_numbers(a, b):\n return a + b", + "test_cases": [ + { + "input": [5, 3], + "expected_output": 8 + }, + { + "input": [0, 0], + "expected_output": 0 + }, + { + "input": [-10, 5], + "expected_output": -5 + } + ] + }, + { + "problem_id": 2, + "title": "Reverse a String", + "func_name":"reverse_string", + "description": "Write a function named 'reverse_string' that takes a string as input and returns the string reversed.", + "solution_code": "def reverse_string(s):\n return s[::-1]", + "test_cases": [ + { + "input": "hello", + "expected_output": "olleh" + }, + { + "input": "Python", + "expected_output": "nohtyP" + }, + { + "input": "a", + "expected_output": "a" + } + ] + }, + { + "problem_id": 3, + "title": "Check if a Number is Even", + "func_name":"is_even", + "description": "Write a function named 'is_even' that takes an integer and returns True if it's even, otherwise False.", + "solution_code": "def is_even(n):\n return n % 2 == 0", + "test_cases": [ + { + "input": 4, + "expected_output": "True" + }, + { + "input": 7, + "expected_output": "False" + }, + { + "input": 0, + "expected_output": "True" + } + ] + }, + { + "problem_id": 4, + "title": "Find the Maximum Number in a List", + "func_name":"find_max", + "description": "Write a function named 'find_max' that takes a list of numbers and returns the largest number in the list.", + "solution_code": "def find_max(numbers):\n return max(numbers)", + "test_cases": [ + { + "input": [1, 5, 2, 8, 3], + "expected_output": 8 + }, + { + "input": [-10, -2, -5], + "expected_output": -2 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 5, + "title": "Calculate Factorial", + "func_name":"factorial", + "description": "Write a function named 'factorial' to calculate the factorial of a non-negative integer.", + "solution_code": "def factorial(n):\n if n == 0:\n return 1\n else:\n return n * factorial(n - 1)", + "test_cases": [ + { + "input": 5, + "expected_output": 120 + }, + { + "input": 0, + "expected_output": 1 + }, + { + "input": 3, + "expected_output": 6 + } + ] + }, + { + "problem_id": 6, + "title": "Count Vowels in a String", + "func_name":"count_vowels", + "description": "Write a function named 'count_vowels' that takes a string and returns the number of vowels (a, e, i, o, u) in it.", + "solution_code": "def count_vowels(s):\n vowels = \"aeiou\"\n count = 0\n for char in s.lower():\n if char in vowels:\n count += 1\n return count", + "test_cases": [ + { + "input": "hello", + "expected_output": 2 + }, + { + "input": "AEIOU", + "expected_output": 5 + }, + { + "input": "rhythm", + "expected_output": 0 + } + ] + }, + { + "problem_id": 7, + "title": "Check for Palindrome", + "func_name":"is_palindrome", + "description": "Write a function named 'is_palindrome' that checks if a string is a palindrome (reads the same forwards and backwards).", + "solution_code": "def is_palindrome(s):\n return s.lower() == s.lower()[::-1]", + "test_cases": [ + { + "input": "racecar", + "expected_output": "True" + }, + { + "input": "hello", + "expected_output": "False" + }, + { + "input": "Madam", + "expected_output": "True" + } + ] + }, + { + "problem_id": 8, + "title": "Find the Smallest Number in a List", + "func_name":"find_min", + "description": "Write a function named 'find_min' that takes a list of numbers and returns the smallest number.", + "solution_code": "def find_min(numbers):\n return min(numbers)", + "test_cases": [ + { + "input": [10, 5, 2, 8, 3], + "expected_output": 2 + }, + { + "input": [-10, -2, -5], + "expected_output": -10 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 9, + "title": "Sum all Numbers in a List", + "func_name":"sum_list", + "description": "Write a function named 'sum_list' that takes a list of numbers and returns their sum.", + "solution_code": "def sum_list(numbers):\n return sum(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": 10 + }, + { + "input": [0, 0, 0], + "expected_output": 0 + }, + { + "input": [10, -5, 2], + "expected_output": 7 + } + ] + }, + { + "problem_id": 10, + "title": "Remove Duplicates from a List", + "func_name":"remove_duplicates", + "description": "Write a function named 'remove_duplicates' that takes a list and returns a new list with all duplicates removed.", + "solution_code": "def remove_duplicates(l):\n return list(set(l))", + "test_cases": [ + { + "input": [1, 2, 2, 3, 4, 4, 5], + "expected_output": [1, 2, 3, 4, 5] + }, + { + "input": ["a", "b", "c", "a"], + "expected_output": ["a", "b", "c"] + }, + { + "input": [1, 1, 1], + "expected_output": [1] + } + ] + }, + { + "problem_id": 11, + "title": "Calculate Average", + "func_name":"calculate_average", + "description": "Write a function named 'calculate_average' that takes a list of numbers and returns their average.", + "solution_code": "def calculate_average(numbers):\n if not numbers:\n return 0\n return sum(numbers) / len(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4, 5], + "expected_output": 3.0 + }, + { + "input": [10, 20, 30], + "expected_output": 20.0 + }, + { + "input": [], + "expected_output": 0 + } + ] + }, + { + "problem_id": 12, + "title": "Find the Longest Word", + "func_name":"find_longest_word", + "description": "Write a function named 'find_longest_word' that takes a list of words and returns the longest word.", + "solution_code": "def find_longest_word(words):\n return max(words, key=len)", + "test_cases": [ + { + "input": ["apple", "banana", "kiwi"], + "expected_output": "banana" + }, + { + "input": ["a", "bb", "ccc"], + "expected_output": "ccc" + }, + { + "input": ["one"], + "expected_output": "one" + } + ] + }, + { + "problem_id": 13, + "title": "Is String a Substring?", + "func_name":"is_substring", + "description": "Write a function named 'is_substring' that checks if a string is a substring of another string.", + "solution_code": "def is_substring(sub, main):\n return sub in main", + "test_cases": [ + { + "input": ["world", "hello world"], + "expected_output": "True" + }, + { + "input": ["moon", "sun"], + "expected_output": "False" + }, + { + "input": ["", "any string"], + "expected_output": "True" + } + ] + }, + { + "problem_id": 14, + "title": "Calculate Circle Area", + "func_name":"circle_area", + + "description": "Write a function named 'circle_area' that takes the radius of a circle and returns its area.", + "solution_code": "import math\ndef circle_area(r):\n return math.pi * r**2", + "test_cases": [ + { + "input": 1, + "expected_output": 3.141592653589793 + }, + { + "input": 0, + "expected_output": 0.0 + }, + { + "input": 2.5, + "expected_output": 19.634954084936208 + } + ] + }, + { + "problem_id": 15, + "title": "Convert Celsius to Fahrenheit", + "func_name":"celsius_to_farenheit", + "description": "Write a function named 'celsius_to_fahrenheit' that converts a temperature from Celsius to Fahrenheit.", + "solution_code": "def celsius_to_fahrenheit(c):\n return (c * 9/5) + 32", + "test_cases": [ + { + "input": 0, + "expected_output": 32.0 + }, + { + "input": 100, + "expected_output": 212.0 + }, + { + "input": -40, + "expected_output": -40.0 + } + ] + }, + { + "problem_id": 16, + "title": "Count Words in a String", + "func_name":"count_words", + "description": "Write a function named 'count_words' that counts the number of words in a given string.", + "solution_code": "def count_words(s):\n return len(s.split())", + "test_cases": [ + { + "input": "Hello world", + "expected_output": 2 + }, + { + "input": "This is a test sentence.", + "expected_output": 5 + }, + { + "input": "oneword", + "expected_output": 1 + } + ] + }, + { + "problem_id": 17, + "title": "Find First Uppercase Letter", + "func_name":"find_first_uppercase", + "description": "Write a function named 'find_first_uppercase' that finds the first uppercase letter in a string.", + "solution_code": "def find_first_uppercase(s):\n for char in s:\n if 'A' <= char <= 'Z':\n return char\n return None", + "test_cases": [ + { + "input": "helloWorld", + "expected_output": "W" + }, + { + "input": "alllowercase", + "expected_output": null + }, + { + "input": "UPPERCASE", + "expected_output": "U" + } + ] + }, + { + "problem_id": 18, + "title": "Generate a Fibonacci Sequence", + "func_name":"fibonacci_sequence", + + "description": "Write a function named 'fibonacci_sequence' that generates a Fibonacci sequence up to a given number of terms.", + "solution_code": "def fibonacci_sequence(n):\n sequence = []\n a, b = 0, 1\n while len(sequence) < n:\n sequence.append(a)\n a, b = b, a + b\n return sequence", + "test_cases": [ + { + "input": 5, + "expected_output": [0, 1, 1, 2, 3] + }, + { + "input": 1, + "expected_output": [0] + }, + { + "input": 0, + "expected_output": [] + } + ] + }, + { + "problem_id": 19, + "title": "Check if a Number is Prime", + "func_name":"is_prime", + "description": "Write a function named 'is_prime' that checks if a number is a prime number.", + "solution_code": "def is_prime(n):\n if n <= 1:\n return False\n for i in range(2, int(n**0.5) + 1):\n if n % i == 0:\n return False\n return True", + "test_cases": [ + { + "input": 7, + "expected_output": "True" + }, + { + "input": 10, + "expected_output": "False" + }, + { + "input": 1, + "expected_output": "False" + } + ] + }, + { + "problem_id": 20, + "title": "FizzBuzz", + "func_name":"fizzbuzz", + "description": "Write a function named 'fizzbuzz' that prints numbers from 1 to 15. For multiples of 3 print 'Fizz', for multiples of 5 print 'Buzz', and for multiples of both, print 'FizzBuzz'.", + "solution_code": "def fizzbuzz():\n results = []\n for i in range(1, 16):\n if i % 3 == 0 and i % 5 == 0:\n results.append('FizzBuzz')\n elif i % 3 == 0:\n results.append('Fizz')\n elif i % 5 == 0:\n results.append('Buzz')\n else:\n results.append(str(i))\n return results", + "test_cases": [ + { + "input": null, + "expected_output": ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"] + } + ] + }, + { + "problem_id": 21, + "title": "Merge Two Lists", + "func_name":"merge_lists", + "description": "Write a function named 'merge_lists' that takes two lists and returns a new list containing all elements from both.", + "solution_code": "def merge_lists(list1, list2):\n return list1 + list2", + "test_cases": [ + { + "input": [[1, 2], [3, 4]], + "expected_output": [1, 2, 3, 4] + }, + { + "input": [["a"], ["b", "c"]], + "expected_output": ["a", "b", "c"] + }, + { + "input": [[], [1, 2, 3]], + "expected_output": [1, 2, 3] + } + ] + }, + { + "problem_id": 22, + "title": "Check if List is Sorted", + "func_name":"is_sorted", + "description": "Write a function named 'is_sorted' that returns True if a list is sorted in ascending order, otherwise False.", + "solution_code": "def is_sorted(l):\n return l == sorted(l)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [4, 3, 2, 1], + "expected_output": "False" + }, + { + "input": [1, 3, 2], + "expected_output": "False" + } + ] + }, + { + "problem_id": 23, + "title": "Reverse a List", + "func_name":"reverse_list", + "description": "Write a function named 'reverse_list' that reverses the order of elements in a list.", + "solution_code": "def reverse_list(l):\n return l[::-1]", + "test_cases": [ + { + "input": [1, 2, 3], + "expected_output": [3, 2, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 24, + "title": "Sum of Digits", + "func_name":"sum_digits", + "description": "Write a function named 'sum_digits' that calculates the sum of the digits of an integer.", + "solution_code": "def sum_digits(n):\n return sum(int(digit) for digit in str(n))", + "test_cases": [ + { + "input": 123, + "expected_output": 6 + }, + { + "input": 999, + "expected_output": 27 + }, + { + "input": 0, + "expected_output": 0 + } + ] + }, + + { + "problem_id": 25, + "func_name":"is_digit_string", + "title": "Check if a String Contains Only Digits", + "description": "Write a function named 'is_digit_string' that returns True if a string contains only digits, otherwise False.", + "solution_code": "def is_digit_string(s):\n return s.isdigit()", + "test_cases": [ + { + "input": "12345", + "expected_output": "True" + }, + { + "input": "123a45", + "expected_output": "False" + }, + { + "input": "", + "expected_output": "False" + } + ] + }, + { + "problem_id": 26, + "func_name":"swap_first_last", + "title": "Swap First and Last Elements of a List", + "description": "Write a function named 'swap_first_last' that swaps the first and last elements of a list and returns the modified list.", + "solution_code": "def swap_first_last(l):\n if len(l) > 1:\n l[0], l[-1] = l[-1], l[0]\n return l", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": [4, 2, 3, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 27, + "title": "Convert a String to a List of Characters", + "func_name":"string_to_list", + "description": "Write a function named 'string_to_list' that takes a string and converts it into a list of its characters.", + "solution_code": "def string_to_list(s):\n return list(s)", + "test_cases": [ + { + "input": "hello", + "expected_output": ["h", "e", "l", "l", "o"] + }, + { + "input": "Python", + "expected_output": ["P", "y", "t", "h", "o", "n"] + }, + { + "input": "", + "expected_output": [] + } + ] + }, + { + "problem_id": 28, + "func_name":"all_are_unique", + "title": "Check if all Elements in a List are Unique", + "description": "Write a function named 'are_all_unique' that returns True if all elements in a list are unique, otherwise False.", + "solution_code": "def are_all_unique(l):\n return len(l) == len(set(l))", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [1, 2, 2, 3], + "expected_output": "False" + }, + { + "input": [], + "expected_output": "True" + } + ] + }, + { + "problem_id": 29, + "title": "Find the Intersection of Two Lists", + "func_name":"list_intersection", + "description": "Write a function named 'list_intersection' that takes two lists and returns a new list containing only the elements that are common to both.", + "solution_code": "def list_intersection(list1, list2):\n return list(set(list1) & set(list2))", + "test_cases": [ + { + "input": [[1, 2, 3], [3, 4, 5]], + "expected_output": [3] + }, + { + "input": [[1, 2], [3, 4]], + "expected_output": [] + }, + { + "input": [["a", "b"], ["b", "c"]], + "expected_output": ["b"] + } + ] + } + ] diff --git a/grand-gardenias/frontend/modes/roguelike/src/question_manager.py b/grand-gardenias/frontend/modes/roguelike/src/question_manager.py new file mode 100644 index 00000000..7593ece3 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/question_manager.py @@ -0,0 +1,37 @@ +import random +from typing import ClassVar + +from engine.patterns import SingletonMeta +from js import document +from problem_helper import get_ques + + +class QuestionManager(metaclass=SingletonMeta): + """Manages question selection and updates the displayed question.""" + + TOTAL_QUESTIONS: ClassVar[int] = 29 + # Questions excluded because they cause issues in this game mode + DEFAULT_EXCLUDED: ClassVar[set[int]] = {0, 6, 17, 18, 19, 20, 26, 29} + + def __init__(self) -> None: + self.ques_id: int | None = None + # Keep track of excluded questions and avoid duplicates + self.excluded_questions = set(self.DEFAULT_EXCLUDED) + + def get_question(self) -> dict: + """Select a new question while avoiding excluded questions.""" + remaining_questions = set(range(1, self.TOTAL_QUESTIONS + 1)) - self.excluded_questions + + if not remaining_questions: + # TODO: Add end game logic + pass + + self.ques_id = random.choice(list(remaining_questions)) # noqa: S311 + self.excluded_questions.add(self.ques_id) + + ques = get_ques(self.ques_id) + document.getElementById("question").textContent = ques["description"] + return ques + + +question_manager = QuestionManager() diff --git a/grand-gardenias/frontend/package-lock.json b/grand-gardenias/frontend/package-lock.json new file mode 100644 index 00000000..ac50c023 --- /dev/null +++ b/grand-gardenias/frontend/package-lock.json @@ -0,0 +1,1140 @@ +{ + "name": "frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@tailwindcss/cli": "^4.1.11", + "tailwindcss": "^4.1.11" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.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/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.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/cli": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz", + "integrity": "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ==", + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.5.1", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "enhanced-resolve": "^5.18.1", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "tailwindcss": "4.1.11" + }, + "bin": { + "tailwindcss": "dist/index.mjs" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "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.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.11", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "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-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "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.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "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/lightningcss/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "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": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/grand-gardenias/frontend/package.json b/grand-gardenias/frontend/package.json new file mode 100644 index 00000000..4a6f2524 --- /dev/null +++ b/grand-gardenias/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "npx @tailwindcss/cli -i ./styles/input.css -o ./dist/output.css --watch", + "build": "npx @tailwindcss/cli -i ./styles/input.css -o ./dist/output.css --minify" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@tailwindcss/cli": "^4.1.11", + "tailwindcss": "^4.1.11" + } +} diff --git a/grand-gardenias/frontend/shared/__init__.py b/grand-gardenias/frontend/shared/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/shared/audio/button.wav b/grand-gardenias/frontend/shared/audio/button.wav new file mode 100644 index 00000000..f4519317 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/button.wav differ diff --git a/grand-gardenias/frontend/shared/audio/game-over-wolarado.wav b/grand-gardenias/frontend/shared/audio/game-over-wolarado.wav new file mode 100644 index 00000000..4b83faac Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/game-over-wolarado.wav differ diff --git a/grand-gardenias/frontend/shared/audio/place.wav b/grand-gardenias/frontend/shared/audio/place.wav new file mode 100644 index 00000000..1e06fb03 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/place.wav differ diff --git a/grand-gardenias/frontend/shared/audio/retro.mp3 b/grand-gardenias/frontend/shared/audio/retro.mp3 new file mode 100644 index 00000000..0f227df5 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/retro.mp3 differ diff --git a/grand-gardenias/frontend/shared/audio/win.wav b/grand-gardenias/frontend/shared/audio/win.wav new file mode 100644 index 00000000..80d96ed0 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/win.wav differ diff --git a/grand-gardenias/frontend/shared/audio/wrong-ans.wav b/grand-gardenias/frontend/shared/audio/wrong-ans.wav new file mode 100644 index 00000000..16d679e2 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/wrong-ans.wav differ diff --git a/grand-gardenias/frontend/shared/audio_utils.py b/grand-gardenias/frontend/shared/audio_utils.py new file mode 100644 index 00000000..9b11dd1b --- /dev/null +++ b/grand-gardenias/frontend/shared/audio_utils.py @@ -0,0 +1,13 @@ +from js import Audio + + +def play_place_sound() -> None: + """Play the block place sound effect.""" + audio = Audio.new("/shared/audio/place.wav") + audio.play() + + +def win_sound() -> None: + """Play win sound.""" + audio = Audio.new("/shared/audio/win.wav") + audio.play() diff --git a/grand-gardenias/frontend/styles/input.css b/grand-gardenias/frontend/styles/input.css new file mode 100644 index 00000000..5aa05b28 --- /dev/null +++ b/grand-gardenias/frontend/styles/input.css @@ -0,0 +1,82 @@ +@import "tailwindcss"; + +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Press+Start+2P&display=swap'); + +/* Import custom CSS files */ +/* @import "./base.css"; */ +/* @import "./components.css"; */ + + +/* When input is focused, remove border & shadow from grid */ +#wrapper:focus-within #game { + @apply border-transparent shadow-none; +} + +/* When input is focused, give it brand border */ +#wrapper:focus-within #text-input { + @apply border border-brand outline-none; + border-style: solid; +} + + +@theme { + --animate-spin: spin 1s linear infinite; + --color-brand: #11ce60; /* lime-500 by default */ + --font-mono: "JetBrains Mono", monospace; + --font-retro: "Press Start 2P", system-ui; + --shadow-retro: 4px 4px 0 #ff00ff, 8px 8px 0 #00ffff; + --shadow-retro-lg: 6px 6px 0 #ff00ff, 12px 12px 0 #00ffff; + + /* shadow-retro Animations */ + --shadow-retro-small: 1px 1px 0 #ff00ff, 2px 2px 0 #00ffff; + --shadow-retro-large: 4px 4px 0 #ff00ff, 8px 8px 0 #00ffff; + --animate-retro-shadow: retro-shadow-grow 0.3s forwards; +} + +@keyframes retro-shadow-grow { + from { + box-shadow: var(--shadow-retro-small); + } + to { + box-shadow: var(--shadow-retro-large); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.block { + @apply font-mono bg-brand text-black flex items-center justify-center; +} + +.cell { + @apply w-[20px] h-[20px] bg-[#111] border border-black; + @apply text-white text-[12px] font-mono flex items-center justify-center; +} + +.locked-cell { + @apply font-mono bg-[#222] flex items-center justify-center; +} + +.retro-shadow { + box-shadow: 4px 4px 0 #ff00ff, 8px 8px 0 #00ffff; +} + + +.retro-btn { + @apply py-4 px-8 bg-brand text-black uppercase font-retro; + @apply cursor-pointer transition-all; + @apply hover:animate-retro-shadow +} + +.retro-btn-secondary { + @apply py-4 px-8 border-brand text-brand uppercase font-retro; + @apply cursor-pointer transition-all hover:bg-brand border hover:text-black; + @apply hover:animate-retro-shadow +} diff --git a/grand-gardenias/poetry.lock b/grand-gardenias/poetry.lock new file mode 100644 index 00000000..20d79058 --- /dev/null +++ b/grand-gardenias/poetry.lock @@ -0,0 +1,719 @@ +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. + +[[package]] +name = "blinker" +version = "1.9.0" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, + {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distlib" +version = "0.4.0" +description = "Distribution utilities" +optional = false +python-versions = "*" +groups = ["main", "dev"] +files = [ + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, +] + +[[package]] +name = "filelock" +version = "3.18.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] + +[[package]] +name = "flask" +version = "3.1.1" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c"}, + {file = "flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e"}, +] + +[package.dependencies] +blinker = ">=1.9.0" +click = ">=8.1.3" +itsdangerous = ">=2.2.0" +jinja2 = ">=3.1.2" +markupsafe = ">=2.1.1" +werkzeug = ">=3.1.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "identify" +version = "2.6.13" +description = "File identification library for Python" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "4.3.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, + {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyscript" +version = "0.3.3" +description = "Command Line Interface for PyScript" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyscript-0.3.3-py3-none-any.whl", hash = "sha256:320383f38e9eec6515dbe0c184d4ad9d9c58e2c98fb82ec09e8d8b2e93c9e62f"}, + {file = "pyscript-0.3.3.tar.gz", hash = "sha256:11fc64a3f187d8645c601ae6a80e3f0142e0dd9e0c5d3244b0ec508ca0d373f9"}, +] + +[package.dependencies] +Jinja2 = "<3.2" +platformdirs = "<4.3" +pluggy = "1.5.0" +requests = "<=2.31.0" +rich = "<=13.7.1" +toml = "<0.11" +typer = "<=0.9.0" + +[package.extras] +dev = ["coverage (<7.3)", "mypy (<=1.4.1)", "pytest (<7.5)", "types-requests", "types-toml (<0.11)"] +docs = ["Sphinx (<5.2)", "myst-parser (<0.19.3)", "pydata-sphinx-theme (<0.13.4)", "sphinx-autobuild (<2021.4.0)", "sphinx-autodoc-typehints (<1.20)"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruff" +version = "0.12.8" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513"}, + {file = "ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc"}, + {file = "ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46"}, + {file = "ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3"}, + {file = "ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e"}, + {file = "ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749"}, + {file = "ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "typer" +version = "0.9.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, + {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.33.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, + {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "werkzeug" +version = "3.1.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.13" +content-hash = "73ff5e7216ad449dbd9b9ddb15d661cbe6b0f52bd9ded2003b2bd98c1fc75894" diff --git a/grand-gardenias/pyproject.toml b/grand-gardenias/pyproject.toml new file mode 100644 index 00000000..e166ff52 --- /dev/null +++ b/grand-gardenias/pyproject.toml @@ -0,0 +1,44 @@ +[tool.poetry] +name = "code-jam-template" +version = "0.1.0" +description = "Add your description here" +authors = [ + "Zishan Kadri", + "Shivam Khetan" +] +readme = "README.md" +packages = [ + { include = "backend" }, + { include = "frontend" } +] + +[tool.poetry.scripts] +start = "backend.run:main" + +[tool.poetry.dependencies] +python = "^3.13" +pyscript = "^0.3.3" +flask = "^3.1.1" +pre-commit = "^4.3.0" + +[tool.poetry.group.dev.dependencies] +pre-commit = "^4.3.0" +ruff = "~0.12.2" + +[tool.ruff] +line-length = 119 +target-version = "py313" +fix = true +src = ["src"] + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "D100", "D104", "D105", "D106", "D107", + "D203", "D213", + "D415", + "D301", + "A", + "T20", + "TD002", "TD003", "FIX" +] diff --git a/heavenly-hostas/.gitattributes b/heavenly-hostas/.gitattributes new file mode 100644 index 00000000..541aa8a6 --- /dev/null +++ b/heavenly-hostas/.gitattributes @@ -0,0 +1,5 @@ +# Blender 3D models +*.blend binary +# Model binaries +*.glb binary +*.hdr binary diff --git a/heavenly-hostas/.github/workflows/data.yaml b/heavenly-hostas/.github/workflows/data.yaml new file mode 100644 index 00000000..7d79f454 --- /dev/null +++ b/heavenly-hostas/.github/workflows/data.yaml @@ -0,0 +1,98 @@ +name: Validate and auto-handle PR + +on: + pull_request_target: + types: [opened] + branches: + - data + +permissions: + pull-requests: write + contents: write + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Set up GitHub CLI + run: | + sudo apt-get update + sudo apt-get install -y gh jq + gh --version + + - name: Check out PR + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate PR + id: validate + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + PR_NUMBER=${{ github.event.pull_request.number }} + + # ensure a single commit + commit_count=$(gh pr view $PR_NUMBER --json commits -q '.commits | length') + if [ "$commit_count" -ne 1 ]; then + echo "::error::PR must have exactly one commit" + echo "valid=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # ensure a single file + files=$(gh pr diff $PR_NUMBER --name-only) + file_count=$(echo "$files" | wc -l) + if [ "$file_count" -ne 1 ]; then + echo "::error::PR must modify exactly one file" + echo "valid=false" >> $GITHUB_OUTPUT + exit 0 + fi + + + commit_hash=${{ github.event.pull_request.head.sha }} + filename="$files" + + echo "Commit: $commit_hash" + echo "File: $filename" + + # ensure PR came from the backend + response=$(curl -s \ + "https://cj12.matiiss.com/api/verify_pr?filename=$filename&commit_hash=$commit_hash") + + echo "Endpoint response: $response" + + is_valid=$(echo "$response" | jq -r '.is_valid') + + if [ "$is_valid" != "true" ]; then + echo "::error::Validation endpoint returned is_valid=$is_valid" + echo "valid=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "valid=true" >> $GITHUB_OUTPUT + echo "commit_hash=$commit_hash" >> $GITHUB_OUTPUT + echo "filename=$filename" >> $GITHUB_OUTPUT + + - name: Close PR if invalid + if: steps.validate.outputs.valid == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr close ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --comment "Validation failed, closing the PR" + + - name: Merge PR if valid + if: steps.validate.outputs.valid == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr merge ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --merge \ + --auto \ + --delete-branch \ + --body "Validation successful, merging the PR" diff --git a/heavenly-hostas/.github/workflows/lint.yaml b/heavenly-hostas/.github/workflows/lint.yaml new file mode 100644 index 00000000..7f67e803 --- /dev/null +++ b/heavenly-hostas/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/heavenly-hostas/.github/workflows/static-deploy.yaml b/heavenly-hostas/.github/workflows/static-deploy.yaml new file mode 100644 index 00000000..80c4f267 --- /dev/null +++ b/heavenly-hostas/.github/workflows/static-deploy.yaml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main", "test-static-deploy"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # upload only the gallery + path: "./packages/gallery" + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/heavenly-hostas/.gitignore b/heavenly-hostas/.gitignore new file mode 100644 index 00000000..8789461f --- /dev/null +++ b/heavenly-hostas/.gitignore @@ -0,0 +1,40 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store + +# We're keeping .blend backup files out of the repo +*.blend1 + +# supabase volumes +volumes/ + +# secrets +*.pem diff --git a/heavenly-hostas/.pre-commit-config.yaml b/heavenly-hostas/.pre-commit-config.yaml new file mode 100644 index 00000000..c0a8de23 --- /dev/null +++ b/heavenly-hostas/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/heavenly-hostas/Dockerfile.backend b/heavenly-hostas/Dockerfile.backend new file mode 100644 index 00000000..801cfdbd --- /dev/null +++ b/heavenly-hostas/Dockerfile.backend @@ -0,0 +1,21 @@ +FROM python:3.12-slim + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +WORKDIR /app + +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-workspace --package=backend + +# we'll rely on mounts for now +# ... +# err, nvm +ADD ./packages/backend /app + +# RUN --mount=type=cache,target=/root/.cache/uv \ +# uv sync --frozen + +# Run with uvicorn +CMD ["uv", "run", "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "9000"] diff --git a/heavenly-hostas/LICENSE.txt b/heavenly-hostas/LICENSE.txt new file mode 100644 index 00000000..5a04926b --- /dev/null +++ b/heavenly-hostas/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/heavenly-hostas/README.md b/heavenly-hostas/README.md new file mode 100644 index 00000000..e6f82560 --- /dev/null +++ b/heavenly-hostas/README.md @@ -0,0 +1,10 @@ +# Heavenly Hostas' Hosting + +This is our submission for the official Python Discord's code jam! + +The project is divided into three main components, please check their respective READMEs for details: +- [Image Editor](https://github.com/heavenly-hostas-hosting/HHH/tree/main/packages/editor) +- [Image Gallery](https://github.com/heavenly-hostas-hosting/HHH/tree/main/packages/gallery) +- [Global Image DB](https://github.com/heavenly-hostas-hosting/HHH/tree/main/packages/backend) + +You can also access a slides presentation on our project [here](https://docs.google.com/presentation/d/1ngL511CRSySNVy05QM7rpqqBGVEvdEtr4sJroFdb_BI/edit?usp=sharing). diff --git a/heavenly-hostas/docker-compose.yaml b/heavenly-hostas/docker-compose.yaml new file mode 100644 index 00000000..46489d13 --- /dev/null +++ b/heavenly-hostas/docker-compose.yaml @@ -0,0 +1,248 @@ +name: cj12 + +services: + cj12-backend: + build: + context: . + dockerfile: Dockerfile.backend + image: cj12-backend + container_name: cj12-backend + ports: + - "9000:9000" + # volumes: + # - ./packages/backend:/app + # - ./packages/backend/.env:/app/.env:ro + env_file: ./packages/backend/.env + networks: + - default + - shared-net + + cj12-editor: + image: zauberzeug/nicegui:latest + restart: always + ports: + - 9010:9010 + environment: + - PUID=1000 # change this to your user id + - PGID=1000 # change this to your group id + volumes: + - ./packages/editor:/app + networks: + - shared-net + + studio: + container_name: supabase-studio + image: supabase/studio:2025.06.30-sha-6f5982d + restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://studio:3000/api/platform/profile').then((r) => {if (r.status !== 200) throw new Error(r.status)})", + ] + timeout: 10s + interval: 5s + retries: 3 + environment: + STUDIO_PG_META_URL: http://meta:8080 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + + DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION} + DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT} + + SUPABASE_URL: http://kong:8000 + SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL} + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} + AUTH_JWT_SECRET: ${JWT_SECRET} + + kong: + container_name: supabase-kong + image: kong:2.8.1 + restart: unless-stopped + # ports: + # - ${KONG_HTTP_PORT}:8000/tcp + # - ${KONG_HTTPS_PORT}:8443/tcp + volumes: + # https://github.com/supabase/supabase/issues/12661 + - ./volumes/api/kong.yml:/home/kong/temp.yml:ro,z + networks: + - default + - shared-net + environment: + KONG_DATABASE: "off" + KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml + # https://github.com/supabase/cli/issues/14 + KONG_DNS_ORDER: LAST,A,CNAME + KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth + KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k + KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} + DASHBOARD_USERNAME: ${DASHBOARD_USERNAME} + DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD} + # https://unix.stackexchange.com/a/294837 + entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start' + + auth: + container_name: supabase-auth + image: supabase/gotrue:v2.177.0 + restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://localhost:9999/health", + ] + timeout: 5s + interval: 5s + retries: 3 + depends_on: + db: + condition: service_healthy + environment: + GOTRUE_API_HOST: 0.0.0.0 + GOTRUE_API_PORT: 9999 + API_EXTERNAL_URL: ${API_EXTERNAL_URL} + + GOTRUE_DB_DRIVER: postgres + GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + + GOTRUE_SITE_URL: ${SITE_URL} + GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS} + GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP} + + GOTRUE_JWT_ADMIN_ROLES: service_role + GOTRUE_JWT_AUD: authenticated + GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated + GOTRUE_JWT_EXP: ${JWT_EXPIRY} + GOTRUE_JWT_SECRET: ${JWT_SECRET} + + GOTRUE_EXTERNAL_GITHUB_ENABLED: true + GOTRUE_EXTERNAL_GITHUB_CLIENT_ID: ${CLIENT_ID} + GOTRUE_EXTERNAL_GITHUB_SECRET: ${CLIENT_SECRET} + GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI: ${GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI} + + meta: + container_name: supabase-meta + image: supabase/postgres-meta:v0.91.0 + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + PG_META_PORT: 8080 + PG_META_DB_HOST: ${POSTGRES_HOST} + PG_META_DB_PORT: ${POSTGRES_PORT} + PG_META_DB_NAME: ${POSTGRES_DB} + PG_META_DB_USER: supabase_admin + PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD} + + db: + container_name: supabase-db + image: supabase/postgres:15.8.1.060 + restart: unless-stopped + volumes: + - ./volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z + # Must be superuser to create event trigger + - ./volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z + # Must be superuser to alter reserved role + - ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z + # Initialize the database settings with JWT_SECRET and JWT_EXP + - ./volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z + # PGDATA directory is persisted between restarts + - ./volumes/db/data:/var/lib/postgresql/data:Z + # Changes required for internal supabase data such as _analytics + - ./volumes/db/_supabase.sql:/docker-entrypoint-initdb.d/migrations/97-_supabase.sql:Z + # Changes required for Analytics support + - ./volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z + # Changes required for Pooler support + - ./volumes/db/pooler.sql:/docker-entrypoint-initdb.d/migrations/99-pooler.sql:Z + # Use named volume to persist pgsodium decryption key between restarts + - db-config:/etc/postgresql-custom + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres", "-h", "localhost"] + interval: 5s + timeout: 5s + retries: 10 + environment: + POSTGRES_HOST: /var/run/postgresql + PGPORT: ${POSTGRES_PORT} + POSTGRES_PORT: ${POSTGRES_PORT} + PGPASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATABASE: ${POSTGRES_DB} + POSTGRES_DB: ${POSTGRES_DB} + JWT_SECRET: ${JWT_SECRET} + JWT_EXP: ${JWT_EXPIRY} + command: [ + "postgres", + "-c", + "config_file=/etc/postgresql/postgresql.conf", + "-c", + "log_min_messages=fatal", # prevents Realtime polling queries from appearing in logs + ] + + supavisor: + container_name: supabase-pooler + image: supabase/supavisor:2.5.7 + restart: unless-stopped + ports: + - ${POSTGRES_PORT}:5432 + - ${POOLER_PROXY_PORT_TRANSACTION}:6543 + volumes: + - ./volumes/pooler/pooler.exs:/etc/pooler/pooler.exs:ro,z + healthcheck: + test: + [ + "CMD", + "curl", + "-sSfL", + "--head", + "-o", + "/dev/null", + "http://127.0.0.1:4000/api/health", + ] + interval: 10s + timeout: 5s + retries: 5 + depends_on: + db: + condition: service_healthy + environment: + PORT: 4000 + POSTGRES_PORT: ${POSTGRES_PORT} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + DATABASE_URL: ecto://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/_supabase + CLUSTER_POSTGRES: true + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + VAULT_ENC_KEY: ${VAULT_ENC_KEY} + API_JWT_SECRET: ${JWT_SECRET} + METRICS_JWT_SECRET: ${JWT_SECRET} + REGION: local + ERL_AFLAGS: -proto_dist inet_tcp + POOLER_TENANT_ID: ${POOLER_TENANT_ID} + POOLER_DEFAULT_POOL_SIZE: ${POOLER_DEFAULT_POOL_SIZE} + POOLER_MAX_CLIENT_CONN: ${POOLER_MAX_CLIENT_CONN} + POOLER_POOL_MODE: transaction + DB_POOL_SIZE: ${POOLER_DB_POOL_SIZE} + command: + [ + "/bin/sh", + "-c", + '/app/bin/migrate && /app/bin/supavisor eval "$$(cat /etc/pooler/pooler.exs)" && /app/bin/server', + ] + +volumes: + db-config: + +networks: + shared-net: + external: true diff --git a/heavenly-hostas/docs/backend/assets/actions_variables.png b/heavenly-hostas/docs/backend/assets/actions_variables.png new file mode 100644 index 00000000..062d4b9b Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/actions_variables.png differ diff --git a/heavenly-hostas/docs/backend/assets/actions_variables_mgmt.png b/heavenly-hostas/docs/backend/assets/actions_variables_mgmt.png new file mode 100644 index 00000000..d267bcdb Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/actions_variables_mgmt.png differ diff --git a/heavenly-hostas/docs/backend/assets/app_gen_key.png b/heavenly-hostas/docs/backend/assets/app_gen_key.png new file mode 100644 index 00000000..ce2c46ff Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/app_gen_key.png differ diff --git a/heavenly-hostas/docs/backend/assets/app_gen_secrets.png b/heavenly-hostas/docs/backend/assets/app_gen_secrets.png new file mode 100644 index 00000000..c805809a Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/app_gen_secrets.png differ diff --git a/heavenly-hostas/docs/backend/assets/app_name_site_callback.png b/heavenly-hostas/docs/backend/assets/app_name_site_callback.png new file mode 100644 index 00000000..d9aea733 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/app_name_site_callback.png differ diff --git a/heavenly-hostas/docs/backend/assets/app_no_active_hooks.png b/heavenly-hostas/docs/backend/assets/app_no_active_hooks.png new file mode 100644 index 00000000..ee050e35 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/app_no_active_hooks.png differ diff --git a/heavenly-hostas/docs/backend/assets/app_save_changes.png b/heavenly-hostas/docs/backend/assets/app_save_changes.png new file mode 100644 index 00000000..4b66d673 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/app_save_changes.png differ diff --git a/heavenly-hostas/docs/backend/assets/app_select_any_acc_and_create.png b/heavenly-hostas/docs/backend/assets/app_select_any_acc_and_create.png new file mode 100644 index 00000000..3cf976ee Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/app_select_any_acc_and_create.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_create_new.png b/heavenly-hostas/docs/backend/assets/arrow_to_create_new.png new file mode 100644 index 00000000..53d2d87d Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_create_new.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_developer_settings.png b/heavenly-hostas/docs/backend/assets/arrow_to_developer_settings.png new file mode 100644 index 00000000..5f5ac34d Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_developer_settings.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_edit_file.png b/heavenly-hostas/docs/backend/assets/arrow_to_edit_file.png new file mode 100644 index 00000000..cb7bad1e Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_edit_file.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_new_app.png b/heavenly-hostas/docs/backend/assets/arrow_to_new_app.png new file mode 100644 index 00000000..32687382 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_new_app.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_new_repo.png b/heavenly-hostas/docs/backend/assets/arrow_to_new_repo.png new file mode 100644 index 00000000..79ce23ec Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_new_repo.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_pages_settings.png b/heavenly-hostas/docs/backend/assets/arrow_to_pages_settings.png new file mode 100644 index 00000000..bb3d1a81 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_pages_settings.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_path_packages.png b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages.png new file mode 100644 index 00000000..9f6b756b Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery.png b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery.png new file mode 100644 index 00000000..57b6c990 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_assets.png b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_assets.png new file mode 100644 index 00000000..504e0e14 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_assets.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_assets_editor-html.png b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_assets_editor-html.png new file mode 100644 index 00000000..abce2213 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_assets_editor-html.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_main-py.png b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_main-py.png new file mode 100644 index 00000000..128af4a3 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_path_packages_gallery_main-py.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_pfp.png b/heavenly-hostas/docs/backend/assets/arrow_to_pfp.png new file mode 100644 index 00000000..bf0bf0d9 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_pfp.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_repo_settings.png b/heavenly-hostas/docs/backend/assets/arrow_to_repo_settings.png new file mode 100644 index 00000000..a4d97130 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_repo_settings.png differ diff --git a/heavenly-hostas/docs/backend/assets/arrow_to_settings.png b/heavenly-hostas/docs/backend/assets/arrow_to_settings.png new file mode 100644 index 00000000..cb3c7b28 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/arrow_to_settings.png differ diff --git a/heavenly-hostas/docs/backend/assets/code_button.png b/heavenly-hostas/docs/backend/assets/code_button.png new file mode 100644 index 00000000..39e3bedc Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/code_button.png differ diff --git a/heavenly-hostas/docs/backend/assets/code_button_dropdown.png b/heavenly-hostas/docs/backend/assets/code_button_dropdown.png new file mode 100644 index 00000000..13e9ba8b Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/code_button_dropdown.png differ diff --git a/heavenly-hostas/docs/backend/assets/commit_changes_1.png b/heavenly-hostas/docs/backend/assets/commit_changes_1.png new file mode 100644 index 00000000..6c4cb1bc Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/commit_changes_1.png differ diff --git a/heavenly-hostas/docs/backend/assets/commit_changes_2.png b/heavenly-hostas/docs/backend/assets/commit_changes_2.png new file mode 100644 index 00000000..9b86a58c Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/commit_changes_2.png differ diff --git a/heavenly-hostas/docs/backend/assets/forking-1.png b/heavenly-hostas/docs/backend/assets/forking-1.png new file mode 100644 index 00000000..204fa71e Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/forking-1.png differ diff --git a/heavenly-hostas/docs/backend/assets/forking-2.png b/heavenly-hostas/docs/backend/assets/forking-2.png new file mode 100644 index 00000000..3dd53fcb Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/forking-2.png differ diff --git a/heavenly-hostas/docs/backend/assets/gallery_editor_url_original.png b/heavenly-hostas/docs/backend/assets/gallery_editor_url_original.png new file mode 100644 index 00000000..b38babba Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/gallery_editor_url_original.png differ diff --git a/heavenly-hostas/docs/backend/assets/gallery_editor_url_updated.png b/heavenly-hostas/docs/backend/assets/gallery_editor_url_updated.png new file mode 100644 index 00000000..fdb24d76 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/gallery_editor_url_updated.png differ diff --git a/heavenly-hostas/docs/backend/assets/gallery_repo_url_original.png b/heavenly-hostas/docs/backend/assets/gallery_repo_url_original.png new file mode 100644 index 00000000..88b8b867 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/gallery_repo_url_original.png differ diff --git a/heavenly-hostas/docs/backend/assets/gallery_repo_url_updated.png b/heavenly-hostas/docs/backend/assets/gallery_repo_url_updated.png new file mode 100644 index 00000000..5062e557 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/gallery_repo_url_updated.png differ diff --git a/heavenly-hostas/docs/backend/assets/gh_pages_settings.png b/heavenly-hostas/docs/backend/assets/gh_pages_settings.png new file mode 100644 index 00000000..eef0986c Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/gh_pages_settings.png differ diff --git a/heavenly-hostas/docs/backend/assets/goto_existing_fork.png b/heavenly-hostas/docs/backend/assets/goto_existing_fork.png new file mode 100644 index 00000000..021c2afd Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/goto_existing_fork.png differ diff --git a/heavenly-hostas/docs/backend/assets/new_repo_creation.png b/heavenly-hostas/docs/backend/assets/new_repo_creation.png new file mode 100644 index 00000000..233637c1 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/new_repo_creation.png differ diff --git a/heavenly-hostas/docs/backend/assets/new_repo_var.png b/heavenly-hostas/docs/backend/assets/new_repo_var.png new file mode 100644 index 00000000..d3c661fa Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/new_repo_var.png differ diff --git a/heavenly-hostas/docs/backend/assets/perms_account.png b/heavenly-hostas/docs/backend/assets/perms_account.png new file mode 100644 index 00000000..1759d6cf Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/perms_account.png differ diff --git a/heavenly-hostas/docs/backend/assets/perms_contents_rw.png b/heavenly-hostas/docs/backend/assets/perms_contents_rw.png new file mode 100644 index 00000000..ec8c2655 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/perms_contents_rw.png differ diff --git a/heavenly-hostas/docs/backend/assets/perms_email_ro.png b/heavenly-hostas/docs/backend/assets/perms_email_ro.png new file mode 100644 index 00000000..aaa9aaeb Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/perms_email_ro.png differ diff --git a/heavenly-hostas/docs/backend/assets/perms_prs_rw.png b/heavenly-hostas/docs/backend/assets/perms_prs_rw.png new file mode 100644 index 00000000..df5ad563 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/perms_prs_rw.png differ diff --git a/heavenly-hostas/docs/backend/assets/perms_repo.png b/heavenly-hostas/docs/backend/assets/perms_repo.png new file mode 100644 index 00000000..c9c91198 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/perms_repo.png differ diff --git a/heavenly-hostas/docs/backend/assets/push_existing_local_repo_to_empty_repo.png b/heavenly-hostas/docs/backend/assets/push_existing_local_repo_to_empty_repo.png new file mode 100644 index 00000000..e5b2982f Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/push_existing_local_repo_to_empty_repo.png differ diff --git a/heavenly-hostas/docs/backend/assets/pyfetch_localhost_api.png b/heavenly-hostas/docs/backend/assets/pyfetch_localhost_api.png new file mode 100644 index 00000000..2b5b7d19 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/pyfetch_localhost_api.png differ diff --git a/heavenly-hostas/docs/backend/assets/pyfetch_matiiss_api.png b/heavenly-hostas/docs/backend/assets/pyfetch_matiiss_api.png new file mode 100644 index 00000000..64905201 Binary files /dev/null and b/heavenly-hostas/docs/backend/assets/pyfetch_matiiss_api.png differ diff --git a/heavenly-hostas/packages/backend/.env.template b/heavenly-hostas/packages/backend/.env.template new file mode 100644 index 00000000..4960f636 --- /dev/null +++ b/heavenly-hostas/packages/backend/.env.template @@ -0,0 +1,64 @@ +# --- GitHub App Configuration --- + +CLIENT_ID="YOUR_CLIENT_ID" +CLIENT_SECRET="YOUR_CLIENT_SECRET" + +GIT_UPSTREAM_OWNER="heavenly-hostas-hosting" +GIT_UPSTREAM_REPO="HHH" +GIT_UPSTREAM_DATA_BRANCH="data" +GIT_UPSTREAM_DATA_BRANCH_FIRST_COMMIT_HASH="6fe3ed2dd48fbaa0bebaee5a1eb377a603feedca" +GIT_UPSTREAM_APP_INSTALLATION_ID="81340179" + + +# --- Supabase Configuration --- +# For more information visit https://supabase.com/docs/guides/self-hosting/docker +# These variables are "inspired" by the official Supabase configuration example: +# https://github.com/supabase/supabase/blob/master/docker/.env.example + +# Secrets +# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION +POSTGRES_PASSWORD="your-super-secret-and-long-postgres-password" +JWT_SECRET="your-super-secret-jwt-token-with-at-least-32-characters-long" +ANON_KEY="your-anon-jwt-token" +SERVICE_ROLE_KEY="your-service-jwt-token" +DASHBOARD_USERNAME="supabase" +DASHBOARD_PASSWORD="this_password_is_insecure_and_should_be_updated" +SECRET_KEY_BASE="UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq" +VAULT_ENC_KEY="your-encryption-key-32-chars-min" + +# Database - You can change these to any PostgreSQL database that has logical replication enabled. +POSTGRES_HOST="db" +POSTGRES_DB="postgres" +POSTGRES_PORT="5432" +# default user is postgres + +# Supavisor -- Database pooler +POOLER_PROXY_PORT_TRANSACTION="6543" +POOLER_DEFAULT_POOL_SIZE="20" +POOLER_MAX_CLIENT_CONN="100" +POOLER_TENANT_ID="your-tenant-id" +POOLER_DB_POOL_SIZE="5" + +# API Proxy - Configuration for the Kong Reverse proxy. +KONG_HTTP_PORT="8000" +KONG_HTTPS_PORT="8443" + +# Auth +# This is the same as the callback URL you set in your GitHub app. +GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI="http://localhost:8000/auth/v1/callback" +GITHUB_CALLBACK_REDIRECT_URI="http://localhost:9000/auth" +POST_AUTH_REDIRECT_URI="https://cj12.matiiss.com/editor" + +# General +SITE_URL=${POST_AUTH_REDIRECT_URI} +ADDITIONAL_REDIRECT_URLS=${GITHUB_CALLBACK_REDIRECT_URI} +JWT_EXPIRY="3600" +DISABLE_SIGNUP="false" +API_EXTERNAL_URL="http://localhost:8000" + +# Studio - Configuration for the Dashboard +STUDIO_DEFAULT_ORGANIZATION="Default Organization" +STUDIO_DEFAULT_PROJECT="Default Project" +STUDIO_PORT="3000" +SUPABASE_PUBLIC_URL="http://localhost:8000" +SUPABASE_INTERNAL_URL="http://kong:8000" diff --git a/heavenly-hostas/packages/backend/README.md b/heavenly-hostas/packages/backend/README.md new file mode 100644 index 00000000..68fe7325 --- /dev/null +++ b/heavenly-hostas/packages/backend/README.md @@ -0,0 +1,422 @@ +## Deployment +**So you want to deploy the entire stack yourself? You've come to the right place.** + +> [!NOTE] +> This guide is primarily written to facilitate deployment from Linux machines, but the vast majority of the things done here should translate 1-to-1 to Windows as well + +Now, there are some prerequisites you would want to get sorted out before we begin: +- `git` is installed and accessible from your terminal + - To download it, head over to https://git-scm.com/downloads +- Docker Engine is installed and you have access to `docker` and `docker compose` commands from your terminal + - You can see a detailed guide on how to install the Docker Engine here: https://docs.docker.com/engine/install/. + - Likewise, for `docker compose` you can see the guide here: https://docs.docker.com/compose/install/. +- Two [GitHub](https://github.com/) accounts + - This is important because part of the stack is entirely based on GitHub and the setup requires that you create a GitHub app and host a repository on GitHub. This might be one of the first of your clues on how this project fits the "wrong tool for the job" theme. + - The other account is necessary if you want to test the deployment with the publishing system because you can't fork your own repositories + - You must also set up either HTTPS or SSH authentication (I personally would suggest SSH) for GitHub: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#authenticating-with-the-command-line + +Alright, got that sorted out? Great, let's move on to the next steps. + +### Creating a GitHub repository + +There are two approaches you can use to achieve this, the simplest and fastest way would be to simply fork this repository, however, if you choose this approach, you will need another account to test the "database" aspect of this project. The other approach is to clone it locally, create an empty repository on GitHub and push the local clone to this newly created GitHub repository. + +> [!IMPORTANT] +> If you choose to **fork**, you **must** have another (either personal or organizational) account if you want to test the deployment out, because you will need to create a fork of your fork to establish the "database" + +--- + +
+Creating a new, unassociated repository + +
+ 1. Click on the + button in the top right corner of the page anywhere on the GitHub site + Red arrow pointing to '+' icon +
+ +
+ 2. Click the New repository button + Red arrow pointing to 'New repository' button +
+ +
+ 3. Pick a name for the repository, leave all the settings to their defaults, click Create repository + GitHub new repository creation screen +
+ +
+ 4. Open your terminal, clone https://github.com/heavenly-hostas-hosting/HHH locally, and cd into the created directory +

+git clone https://github.com/heavenly-hostas-hosting/HHH
+cd ./HHH
+    
+
+ +
+ 5. While in the same terminal, remove the origin remote +

+git remote remove origin
+    
+ If you get an error along the lines of the remote not existing, first list all your remotes, then remove the one (should be only one) that you see +

+git remote  # for example, this outputs 'upstream', then you'd do the following
+git remote remove upstream
+    
+
+ +
+ 6. Go to your newly created repository from step 3, select HTTPS or SSH depending on which one you have set up for your account (personally I would suggest using SSH), copy the …or push an existing repository from the command line command and paste and run it in your terminal + GitHub empty repository instructions +
+ +
+ +--- + +
+Forking this repository + +There can exist two states of being for this step, you have either already forked this repository or you have not... Apparently (finding out as I'm writing this guide), GitHub does not let you create multiple forks of the same repository on the same account. So really your options in this case are either to pick another account to create the fork on if you want to separate this deployment from your potential artwork storage or you can use your already existing fork. + +--- + +
+Already have a fork of this repository and want to use it for deployment? Expand this section +Feels pretty empty, doesn't it? +
+Just head over to your forked repository, that's all :) +

+If you do still need more detailed instructions: + +
+ 1. Go to our GitHub repository: https://github.com/heavenly-hostas-hosting/HHH + You're probably already here :), but do head over to the main page (for example, by following the link given above) to be able to follow the next steps exactly. +
+ +
+ 2. Click the downwards facing triangle (it looks about like this: \/) next to the Fork button and then click on your forked repository + Red arrow pointing to Fork button +
+ +
+ +--- + +
+Haven't forked this repository yet or want to fork it with a different account? This is your dropdown +
+ 1. Go to our GitHub repository: https://github.com/heavenly-hostas-hosting/HHH + You're probably already here :), but do head over to the main page (for example, by following the link given above) to be able to follow the next steps exactly. +
+ +
+ 2. Click the Fork button at the top, it's right next to the Star button, and while you're at it, click that one as well :P + Red arrow pointing to Fork button +
+ +
+ 3. Pick a name for your fork (and an account if necessary) or leave it as the default and click on Create fork + Red arrow pointing to Fork button +
+
+ +--- + +Finally you're going to want to clone the forked repository locally: + +
+ 1. Go to your fork and click on <> Code + Red arrow pointing to Code button +
+ +
+ 2. Select either HTTPS or SSH tab depending on which method you have set up for authentication for your GitHub account (personally I would suggest using SSH), then copy the URL + Code button dropdown +
+ +
+ 3. In your terminal, clone the repository whose URL you just copied and cd into its directory +

+git clone <paste your url here> ./HHH  # <- this picks the destination directory of the repository, you can pick a different one if you want, but take that into account for the next command
+cd ./HHH
+    
+
+ +
+ +--- + +### Creating a GitHub App +For a more detailed overview take a loot at GitHub's own guide for Apps: https://docs.github.com/en/apps/overview + +
+ 1. Click on your profile picture in the top right corner of the page anywhere on the GitHub site + Red arrow pointing to GitHub profile icon +
+ +
+ 2. Go to Settings + Red arrow pointing to settings +
+ +
+ 3. Go to Developer Settings + Red arrow pointing to developer settings in user settings +
+ +
+ 4. Click on New GitHub App + Red arrow pointing to 'New GitHub App' button +
+ +
+ 5. Set GitHub App name to something like pydis-cj12-HHH-deploy-<your-github-username>, Homepage URL can just be your repository URL, set Callback URL to http://localhost/api/kong/auth/v1/callback and make sure to tick the OAuth box + +
+ +
+ 6. Disable webhooks + +
+ +
+ 7. Set Repository permissions to Contents: Read and write, Pull requests: Read and write, and Account permissions to Email addresses: Read-only +
+
+
+
+
+
+ +
+ 8. Allow app to be installed on any account and click Create GitHub App + +
+ +
+ 9. Generate a new client secret, store it somewhere safe, will be needed later on, then scroll down and Generate a private key, make sure to save it with the name pydis-cj12-heavenly-hostas-app.private-key.pem and store it inside the packages/backend directory +
+
+
+ +
+ 10. Make sure to Save changes + +
+ +
+ +Lastly go to your GitHub App's public URL and install it on your main deployment repository (it might seem like it fails because it can't find `localhost`, but it will install itself on the repository, do make sure to only install it on the deployment repo and not all of your repositories) + + +### Creating a `data` branch +In your terminal, go to your local repository and create a new branch and add the data workflow to it +``` +git switch --orphan data +git checkout main -- .github/workflows/data.yaml +``` +Now, if you're on Linux, you can use the following command to replace the string `cj12.matiiss.com` with the string `${{ vars.DATA_API_HOST }}`, otherwise you can just use any text editor you'd like (preferrably one with Find & Replace functionality) to do the same for the file `.github/workflows/data.yaml` +``` +sed 's/cj12.matiiss.com/\$\{\{ vars.DATA_API_HOST \}\}/g' .github/workflows/data.yaml > tmp.yaml && mv tmp.yaml .github/workflows/data.yaml +``` + +Next, just add, commit, and push the changes to GitHub and then switch back to the main branch +``` +git add .github/workflows/data.yaml +git commit -m "Change hard-coded URL to a workflow variable" +git push -u origin HEAD +git switch main +``` + +Now you need to learn how to set GitHub Actions variables + +
+ 1. Go to your repository's Settings + Red arrow pointing to 'Settings' +
+ +
+ 2. Go to Secrets and variables > Actions + Red arrow pointing to 'Settings' +
+ +
+ 3. Create a New repository variable + Red arrow pointing to 'Settings' +
+ +
+ 4. Name it DATA_API_HOST and add some filler value as its value for now + Red arrow pointing to 'Settings' +
+ + +### Set up and deploy GitHub Pages +For a more detailed overview of what GitHub Pages are see https://pages.github.com/ + +
+ 1. Go to your repository's Settings + Red arrow pointing to 'Settings' +
+ +
+ 2. Go to Pages + Red arrow pointing to 'Pages' settings +
+ +
+ 3. Change Source to deploy from GitHub Actions + GitHub Pages settings +
+ +
+ 4. Go back to your repository's main page and navigate to packages/gallery/main.py +
    +
  1. Arrow to directory
  2. +
  3. Arrow to directory
  4. +
  5. Arrow to file
  6. +
+
+ +
+ 5. Enter file edit mode for packages/gallery/main.py + Arrow to edit file button +
+ +
+ 6. Find where REPO_URL is defined and change heavenly-hostas-hosting/HHH to <your-username>/<your-repo-name> + For example, from: +
+ Text... +
+ to: +
+ Text... +
+ +
+ 7. Find cj12.matiiss.com and replace it with localhost + From: +
+ Text... +
+ to: +
+ Text... +
+ +
+ 8. Commit your changes +
    +
  1. Arrow to Commit changes button
  2. +
  3. Commit dialog close-up
  4. +
+
+ +
+ 9. Go back to your repository's main page and navigate to packages/gallery/assets/editor.html +
    +
  1. Arrow to directory
  2. +
  3. Arrow to directory
  4. +
  5. Arrow to directory
  6. +
  7. Arrow to file
  8. +
+
+ +
+ 10. Enter file edit mode for packages/gallery/assets/editor.html + Arrow to edit file button +
+ +
+ 11. Replace all instances of cj12.matiiss.com with localhost + From: +
+ Text... +
+ to: +
+ Text... +
+ +
+ 12. Commit your changes +
    +
  1. Arrow to Commit changes button
  2. +
  3. Commit dialog close-up
  4. +
+
+ + +### Local setup +You need to set up the initial volumes for supabase and to do that there's a convenience script, so just run that +``` +bash ./set_up_supabase_volumes.sh +``` + +Then afterwards you want to copy the `.env.template` from the backend and link it to project root +``` +cp packages/backend/.env.template packages/backend/.env +ln packages/backend/.env .env +``` + +Open up the `.env` file in a text editor and adjust the environment variables as necessary: +- `CLIENT_ID` is your GitHub App's client ID +- `CLIENT_SECRET` is the secret your generated for your GitHub App +- `GIT_UPSTREAM_*` refers to your deployment repository, the first commit hash you can get from GitHub or git for the `data` branch by looking at the history/logs. The app installation ID you can see in the URL in your browser when you visit your installation. +- Regarding supabase variables, they have a generator for those: https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys +- `GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI="https://localhost/api/kong/auth/v1/callback"` +- `GITHUB_CALLBACK_REDIRECT_URI="https://localhost/api/auth"` +- `POST_AUTH_REDIRECT_URI="https://localhost/editor"` +- `SUPABASE_PUBLIC_URL="https://localhost/api/kong"` + + + +### 3-2-1 Lift-off! +First, let's connect to the internet so that the GitHub workflow can access our API for PR verification, we'll do this using https://localhost.run/ which will provide us with a free, temporary, public domain name. +``` +ssh -R 80:localhost:80 localhost.run +``` +Navigate over to your GitHub repository and set that domain name (just the `.lhr.life` part without the protocol) as the value for the `DATA_API_HOST` repository variable as well. + +> [!IMPORTANT] +> Since this domain is temporary, it will get refreshed every once in a while, so keep the variable updated whenever that happens + +Before we run any `docker` container, we must first create a shared network for some of the `docker compose` services to communicate with `nginx` and vice versa +``` +docker network create shared-net +``` + +It's now time to spin up the entire stack +``` +docker compose up -d +``` + +Finally let's spin up `nginx` for traffic routing, just copy and paste the following in your terminal, then run it +```bash +NETWORK=shared-net TMP_DIR=$(mktemp -d) && \ +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout "$TMP_DIR/key.pem" -out "$TMP_DIR/cert.pem" -subj "/CN=localhost" && \ +cat > "$TMP_DIR/nginx.conf" <<'EOF' +events {} +http { + server { listen 80; server_name localhost; return 301 https://$host$request_uri; } + server { + listen 443 ssl; server_name localhost; + ssl_certificate /etc/nginx/certs/cert.pem; ssl_certificate_key /etc/nginx/certs/key.pem; + location ~ ^/_next|monaco-editor/\$ { proxy_pass http://kong:8000; } + location /api/platform/ { proxy_pass http://kong:8000; } + location /api/kong/ { proxy_pass http://kong:8000/; } + location /api/ { proxy_pass http://cj12-backend:9000/; } + location /editor/ { proxy_pass http://cj12-editor:9010/; proxy_redirect / /editor/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } + location /scripts/ { proxy_pass http://cj12-editor:9010; } + location /static/ { proxy_pass http://cj12-editor:9010; } + } + server { + listen 80; server_name *.lhr.life; + location = /api/verify_pr { proxy_pass http://cj12-backend:9000/verify_pr$is_args$args; } + location / { return 403; } + } +} +EOF +docker run --rm --name nginx -p 80:80 -p 443:443 --network "$NETWORK" -v "$TMP_DIR/nginx.conf":/etc/nginx/nginx.conf:ro -v "$TMP_DIR":/etc/nginx/certs:ro nginx +``` diff --git a/heavenly-hostas/packages/backend/pyproject.toml b/heavenly-hostas/packages/backend/pyproject.toml new file mode 100644 index 00000000..2b42d79e --- /dev/null +++ b/heavenly-hostas/packages/backend/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "backend" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "cryptography>=45.0.6", + "fastapi>=0.116.1", + "httpx>=0.28.1", + "psycopg[binary,pool]>=3.2.9", + "pyjwt>=2.10.1", + "python-multipart>=0.0.20", + "supabase>=2.18.0", + "uvicorn>=0.35.0", +] diff --git a/heavenly-hostas/packages/backend/server/__init__.py b/heavenly-hostas/packages/backend/server/__init__.py new file mode 100644 index 00000000..ca95ba2c --- /dev/null +++ b/heavenly-hostas/packages/backend/server/__init__.py @@ -0,0 +1,254 @@ +import secrets +from datetime import datetime +from typing import Annotated + +from fastapi import FastAPI, HTTPException, Query, Request, Response, UploadFile +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse, RedirectResponse +from gotrue import CodeExchangeParams, SignInWithOAuthCredentials, SignInWithOAuthCredentialsOptions +from pydantic import BaseModel + +from . import env, gh, pg, sb + +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["https://heavenly-hostas-hosting.github.io"], + allow_credentials=False, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/login") +async def login() -> Response: + sb_client = await sb.create_public_client() + + gh_response = await sb_client.auth.sign_in_with_oauth( + SignInWithOAuthCredentials( + provider="github", + options=SignInWithOAuthCredentialsOptions(redirect_to=env.GITHUB_CALLBACK_REDIRECT_URI), + ), + ) + + response = RedirectResponse(gh_response.url) + response.set_cookie( + key=sb.CODE_VERIFIER_COOKIE_KEY, + value=await sb.get_code_verifier_from_client(sb_client), + httponly=True, + secure=True, + samesite="lax", + ) + + return response + + +@app.get("/logout") +async def logout(request: Request) -> Response: + sb_client = await sb.get_session(request) + await sb_client.auth.sign_out() + + response = RedirectResponse(env.POST_AUTH_REDIRECT_URI) + response.delete_cookie( + key=sb.ACCESS_TOKEN_COOKIE_KEY, + httponly=True, + secure=True, + samesite="lax", + ) + response.delete_cookie( + key=sb.REFRESH_TOKEN_COOKIE_KEY, + httponly=True, + secure=True, + samesite="lax", + ) + + return response + + +@app.get("/auth") +async def auth( + code: Annotated[str, Query()], + request: Request, +) -> RedirectResponse: + client = await sb.create_internal_client() + code_verifier = request.cookies.get(sb.CODE_VERIFIER_COOKIE_KEY) + if code_verifier is None: + raise HTTPException(status_code=401, detail="Code verifier not found in cookies") + + gh_response = await client.auth.exchange_code_for_session( + CodeExchangeParams( + code_verifier=code_verifier, + auth_code=code, + redirect_to="", + ), + ) + + if gh_response.session is None: + raise HTTPException(status_code=401, detail="Failed to exchange code for session") + + response = RedirectResponse(env.POST_AUTH_REDIRECT_URI) + response.delete_cookie( + key=sb.CODE_VERIFIER_COOKIE_KEY, + httponly=True, + secure=True, + samesite="lax", + ) + sb.set_response_token_cookies_( + response, + access_token=gh_response.session.access_token, + refresh_token=gh_response.session.refresh_token, + ) + + return response + + +@app.post("/publish") +async def publish( # noqa: C901 + image: UploadFile, + http_request: Request, +) -> Response: + client = await sb.get_session(http_request) + + client_session = await client.auth.get_session() + if client_session is None: + raise HTTPException(status_code=401, detail="User not authenticated") + + gh_identity = await sb.get_github_identity(client) + user_name = gh_identity.identity_data["user_name"] + app_token = gh.get_app_token() + + installation_id: int | None = None + for installation in await gh.get_app_installations(app_token): + if installation["account"]["login"] == user_name: + if installation_id is not None: + raise HTTPException(status_code=400, detail="Multiple GitHub App installations found for user") + + installation_id = installation["id"] + + if installation_id is None: + raise HTTPException(status_code=404, detail="No GitHub App installation found") + + app_installation_token = await gh.get_app_installation_token(installation_id, app_token) + installation_repositories = await gh.get_app_installation_repositories(app_installation_token) + + total_repo_count = installation_repositories["total_count"] + if total_repo_count == 0: + raise HTTPException(status_code=409, detail="GitHub App not installed on any repository") + elif total_repo_count > 1: + raise HTTPException(status_code=409, detail="GitHub App must be installed on a single repository") + + root_app_installation_token = await gh.get_app_installation_token(env.GIT_UPSTREAM_APP_INSTALLATION_ID, app_token) + all_fork_full_names = set( + repo["full_name"] for repo in await gh.get_app_installation_repository_forks(root_app_installation_token) + ) + repository = installation_repositories["repositories"][0] + + if not repository["fork"] or repository["full_name"] not in all_fork_full_names: + raise HTTPException( + status_code=409, detail="The installation repository must be a fork of the main repository" + ) + + now = datetime.now().strftime("%Y-%m-%dT%H-%M-%S") + random_sequence = secrets.token_hex(8) + file_stem = f"{now}_{random_sequence}" + file_name = f"{file_stem}.webp" + + commit_hash = await gh.commit_and_create_pull_request( + root_app_installation_token=root_app_installation_token, + app_installation_token=app_installation_token, + fork_owner=user_name, + fork_name=repository["name"], + new_branch=file_stem, + file_path=file_name, + file_content=await image.read(), + pr_title=f"Publish {file_name}", + ) + await pg.github_files_insert_row( + username=user_name, + filename=file_name, + commit_hash=commit_hash, + ) + + response = Response(content="Publish endpoint hit", status_code=200) + sb.set_response_token_cookies_( + response, + access_token=client_session.access_token, + refresh_token=client_session.refresh_token, + ) + + return response + + +class LoginStatusResponse(BaseModel): + username: str | None + logged_in: bool + + +@app.get("/status", response_model=LoginStatusResponse) +async def status(http_request: Request) -> JSONResponse: + try: + client = await sb.get_session(http_request) + client_session = await client.auth.get_session() + if client_session is None: + raise HTTPException(status_code=401, detail="User not authenticated") + + gh_identity = await sb.get_github_identity(client) + except HTTPException as e: + if e.status_code != 401: + raise + + return JSONResponse( + content=LoginStatusResponse( + username=None, + logged_in=False, + ).model_dump() + ) + + user_name = gh_identity.identity_data["user_name"] + response = JSONResponse( + content=LoginStatusResponse( + username=user_name, + logged_in=True, + ).model_dump() + ) + sb.set_response_token_cookies_( + response, + access_token=client_session.access_token, + refresh_token=client_session.refresh_token, + ) + + return response + + +class VerifyPRResponse(BaseModel): + is_valid: bool + + +@app.get("/verify_pr") +async def verify_pr( + filename: Annotated[str, Query()], + commit_hash: Annotated[str, Query()], +) -> VerifyPRResponse: + is_valid = await pg.github_files_check_exists( + filename=filename, + commit_hash=commit_hash, + ) + + return VerifyPRResponse(is_valid=is_valid) + + +class ArtworksResponse(BaseModel): + artworks: list[tuple[str, str]] + + +@app.get("/artworks") +async def artworks() -> ArtworksResponse: + works = await pg.github_files_get_all() + + return ArtworksResponse(artworks=works) + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run("main:app", host="localhost", port=8000, workers=1) diff --git a/heavenly-hostas/packages/backend/server/env.py b/heavenly-hostas/packages/backend/server/env.py new file mode 100644 index 00000000..cca106e5 --- /dev/null +++ b/heavenly-hostas/packages/backend/server/env.py @@ -0,0 +1,22 @@ +from pathlib import Path + +from . import utils + +CLIENT_ID = utils.assure_get_env("CLIENT_ID") +CLIENT_SECRET = utils.assure_get_env("CLIENT_SECRET") +PRIVATE_KEY = Path("pydis-cj12-heavenly-hostas-app.private-key.pem").read_text().strip() + +SUPABASE_PUBLIC_URL = utils.assure_get_env("SUPABASE_PUBLIC_URL") +SUPABASE_INTERNAL_URL = utils.assure_get_env("SUPABASE_INTERNAL_URL") +SUPABASE_KEY = utils.assure_get_env("ANON_KEY") + +GITHUB_CALLBACK_REDIRECT_URI = utils.assure_get_env("GITHUB_CALLBACK_REDIRECT_URI") +POST_AUTH_REDIRECT_URI = utils.assure_get_env("POST_AUTH_REDIRECT_URI") + +JWT_SECRET = utils.assure_get_env("JWT_SECRET") + +GIT_UPSTREAM_OWNER = utils.assure_get_env("GIT_UPSTREAM_OWNER") +GIT_UPSTREAM_REPO = utils.assure_get_env("GIT_UPSTREAM_REPO") +GIT_UPSTREAM_DATA_BRANCH = utils.assure_get_env("GIT_UPSTREAM_DATA_BRANCH") +GIT_UPSTREAM_DATA_BRANCH_FIRST_COMMIT_HASH = utils.assure_get_env("GIT_UPSTREAM_DATA_BRANCH_FIRST_COMMIT_HASH") +GIT_UPSTREAM_APP_INSTALLATION_ID = int(utils.assure_get_env("GIT_UPSTREAM_APP_INSTALLATION_ID")) diff --git a/heavenly-hostas/packages/backend/server/gh.py b/heavenly-hostas/packages/backend/server/gh.py new file mode 100644 index 00000000..00c803ed --- /dev/null +++ b/heavenly-hostas/packages/backend/server/gh.py @@ -0,0 +1,140 @@ +import base64 # noqa: F401 +import time +from typing import Any + +import httpx +import jwt + +from . import env + + +def get_app_token() -> str: + """Generate a JWT token for the GitHub App.""" + now = int(time.time()) + payload = { + "iat": now, + "exp": now + (3 * 60), # JWT valid for 3 minutes + "iss": env.CLIENT_ID, # GitHub App ID + } + + return jwt.encode(payload, env.PRIVATE_KEY, algorithm="RS256") + + +async def get_app_installations(app_token: str) -> list[dict[str, Any]]: + """Get all installations of the GitHub App.""" + headers = { + "Authorization": f"Bearer {app_token}", + "Accept": "application/vnd.github+json", + } + async with httpx.AsyncClient() as client: + r = await client.get("https://api.github.com/app/installations", headers=headers) + r.raise_for_status() + return r.json() + + +async def get_app_installation_repositories(app_installation_token: str) -> dict[str, Any]: + """Get all repositories a GitHub App installation has access to.""" + headers = { + "Authorization": f"Bearer {app_installation_token}", + "Accept": "application/vnd.github+json", + } + async with httpx.AsyncClient() as client: + r = await client.get("https://api.github.com/installation/repositories", headers=headers) + r.raise_for_status() + return r.json() + + +async def get_app_installation_token(installation_id: int, app_token: str) -> str: + """Get an installation token for the GitHub App. + + This token is used to perform actions on behalf of the installation. + """ + headers = { + "Authorization": f"Bearer {app_token}", + "Accept": "application/vnd.github+json", + } + async with httpx.AsyncClient() as client: + r = await client.post( + f"https://api.github.com/app/installations/{installation_id}/access_tokens", + headers=headers, + ) + r.raise_for_status() + return r.json()["token"] + + +async def get_app_installation_repository_forks(app_installation_token: str) -> list[dict[str, Any]]: + headers = { + "Authorization": f"Bearer {app_installation_token}", + "Accept": "application/vnd.github+json", + } + async with httpx.AsyncClient() as client: + r = await client.get( + f"https://api.github.com/repos/{env.GIT_UPSTREAM_OWNER}/{env.GIT_UPSTREAM_REPO}/forks", + headers=headers, + ) + r.raise_for_status() + return r.json() + + +async def commit_and_create_pull_request( + root_app_installation_token: str, + app_installation_token: str, + fork_owner: str, + fork_name: str, + new_branch: str, + file_path: str, + file_content: bytes, + pr_title: str, +) -> str: + root_auth_headers = {"Authorization": f"token {root_app_installation_token}"} + auth_headers = {"Authorization": f"token {app_installation_token}"} + meta_headers = {"Accept": "application/vnd.github+json"} + + root_headers = root_auth_headers | meta_headers + headers = auth_headers | meta_headers + + async with httpx.AsyncClient() as client: + # Get SHA of the data branch to create a new branch off of in the fork + # r = await client.get( + # f"https://api.github.com/repos/{env.GIT_UPSTREAM_OWNER}/{env.GIT_UPSTREAM_REPO}/git/refs/heads/{env.GIT_UPSTREAM_DATA_BRANCH}", + # headers=headers, + # ) + # r.raise_for_status() + # base_sha = r.json()["object"]["sha"] + + # Create a new branch in the fork + r = await client.post( + f"https://api.github.com/repos/{fork_owner}/{fork_name}/git/refs", + headers=headers, + json={"ref": f"refs/heads/{new_branch}", "sha": env.GIT_UPSTREAM_DATA_BRANCH_FIRST_COMMIT_HASH}, + ) + r.raise_for_status() + + # Commit file contents to the new branch + r = await client.put( + f"https://api.github.com/repos/{fork_owner}/{fork_name}/contents/{file_path}", + headers=headers, + json={ + "message": f"Add {file_path}", + "content": base64.b64encode(file_content).decode("utf-8"), + "branch": new_branch, + }, + ) + r.raise_for_status() + commit_hash: str = r.json()["commit"]["sha"] + + # Open PR against upstream + r = await client.post( + f"https://api.github.com/repos/{env.GIT_UPSTREAM_OWNER}/{env.GIT_UPSTREAM_REPO}/pulls", + headers=root_headers, + json={ + "title": pr_title, + "head": f"{fork_owner}:{new_branch}", + "head_repo": fork_name, + "base": env.GIT_UPSTREAM_DATA_BRANCH, + "maintainer_can_modify": False, + }, + ) + r.raise_for_status() + + return commit_hash diff --git a/heavenly-hostas/packages/backend/server/pg.py b/heavenly-hostas/packages/backend/server/pg.py new file mode 100644 index 00000000..05a641f6 --- /dev/null +++ b/heavenly-hostas/packages/backend/server/pg.py @@ -0,0 +1,84 @@ +import os + +import psycopg +from psycopg.rows import tuple_row + + +async def get_connection() -> psycopg.AsyncConnection: + return await psycopg.AsyncConnection.connect( + dbname=os.getenv("POSTGRES_DB"), + user=os.getenv("POSTGRES_USER", "postgres"), + password=os.getenv("POSTGRES_PASSWORD"), + host=os.getenv("POSTGRES_HOST", "localhost"), + port=os.getenv("POSTGRES_PORT", "5432"), + row_factory=tuple_row, + ) + + +async def github_files_create_table() -> None: + async with await get_connection() as conn: + async with conn.cursor() as cur: + await cur.execute( + """ + CREATE TABLE IF NOT EXISTS github_files ( + id SERIAL PRIMARY KEY, + github_username VARCHAR(39) NOT NULL, + filename CHAR(42) NOT NULL, + commit_hash CHAR(40) NOT NULL + ); + """ + ) + await conn.commit() + + +async def github_files_insert_row(username: str, filename: str, commit_hash: str) -> None: + await github_files_create_table() + + async with await get_connection() as conn: + async with conn.cursor() as cur: + await cur.execute( + """ + INSERT INTO github_files (github_username, filename, commit_hash) + VALUES (%s, %s, %s); + """, + (username, filename, commit_hash), + ) + await conn.commit() + + +async def github_files_check_exists(filename: str, commit_hash: str) -> bool: + async with await get_connection() as conn: + async with conn.cursor() as cur: + await cur.execute( + """ + SELECT + * + FROM + github_files + WHERE + filename=%s + AND commit_hash=%s + """, + (filename, commit_hash), + ) + + rows = await cur.fetchall() + return len(rows) == 1 + + +async def github_files_get_all() -> list[tuple[str, str]]: + async with await get_connection() as conn: + async with conn.cursor() as cur: + await cur.execute( + """ + SELECT + github_username, + filename + FROM + github_files + ORDER BY + id ASC + """ + ) + rows = await cur.fetchall() + return rows diff --git a/heavenly-hostas/packages/backend/server/sb.py b/heavenly-hostas/packages/backend/server/sb.py new file mode 100644 index 00000000..2ed8eb79 --- /dev/null +++ b/heavenly-hostas/packages/backend/server/sb.py @@ -0,0 +1,86 @@ +from fastapi import HTTPException, Request, Response +from gotrue.constants import STORAGE_KEY +from gotrue.errors import AuthSessionMissingError +from gotrue.types import UserIdentity +from supabase import AsyncClient, AsyncClientOptions, create_async_client + +from . import env + +ACCESS_TOKEN_COOKIE_KEY = "sb_access_token" # noqa: S105 +REFRESH_TOKEN_COOKIE_KEY = "sb_refresh_token" # noqa: S105 +CODE_VERIFIER_COOKIE_KEY = "sb_code_verifier" + + +def set_response_token_cookies_(response: Response, access_token: str, refresh_token: str) -> None: + response.set_cookie( + key=ACCESS_TOKEN_COOKIE_KEY, + value=access_token, + httponly=True, + secure=True, + samesite="lax", + ) + response.set_cookie( + key=REFRESH_TOKEN_COOKIE_KEY, + value=refresh_token, + httponly=True, + secure=True, + samesite="lax", + ) + + +async def create_internal_client() -> AsyncClient: + """Create a Supabase client.""" + return await create_async_client( + supabase_url=env.SUPABASE_INTERNAL_URL, + supabase_key=env.SUPABASE_KEY, + options=AsyncClientOptions(flow_type="pkce"), + ) + + +async def create_public_client() -> AsyncClient: + """Create a Supabase client.""" + return await create_async_client( + supabase_url=env.SUPABASE_PUBLIC_URL, + supabase_key=env.SUPABASE_KEY, + options=AsyncClientOptions(flow_type="pkce"), + ) + + +async def get_code_verifier_from_client(client: AsyncClient) -> str: + """Get the code verifier from the client.""" + storage = client.auth._storage # noqa: SLF001 + code_verifier = await storage.get_item(f"{STORAGE_KEY}-code-verifier") + + if code_verifier is None: + raise HTTPException(status_code=401, detail="Code verifier not found in storage") + + return code_verifier + + +async def get_session(request: Request) -> AsyncClient: + """Get a Supabase client session.""" + access_token = request.cookies.get(ACCESS_TOKEN_COOKIE_KEY) + refresh_token = request.cookies.get(REFRESH_TOKEN_COOKIE_KEY) + + if access_token is None or refresh_token is None: + raise HTTPException(status_code=401, detail="No session tokens found") + + client = await create_internal_client() + await client.auth.set_session(access_token=access_token, refresh_token=refresh_token) + + return client + + +async def get_github_identity(client: AsyncClient) -> UserIdentity: + user_identities = await client.auth.get_user_identities() + if isinstance(user_identities, AuthSessionMissingError): + raise HTTPException(status_code=401, detail="User not authenticated") + + for identity in user_identities.identities: + if identity.provider == "github": + gh_identity = identity + break + else: + raise HTTPException(status_code=401, detail="GitHub identity not found... how did you get here?") + + return gh_identity diff --git a/heavenly-hostas/packages/backend/server/utils.py b/heavenly-hostas/packages/backend/server/utils.py new file mode 100644 index 00000000..f7d8379e --- /dev/null +++ b/heavenly-hostas/packages/backend/server/utils.py @@ -0,0 +1,10 @@ +import os + + +def assure_get_env(var: str) -> str: + """Get an environment variable or raise an error if it is not set.""" + value = os.getenv(var) + if value is None: + msg = f"Environment variable '{var}' is not set." + raise OSError(msg) + return value diff --git a/heavenly-hostas/packages/editor/README.md b/heavenly-hostas/packages/editor/README.md new file mode 100644 index 00000000..2360fb3e --- /dev/null +++ b/heavenly-hostas/packages/editor/README.md @@ -0,0 +1,52 @@ +## Dev Guide +### Intended Use +To run this package as intended, please see the [backend package README](https://github.com/heavenly-hostas-hosting/HHH/blob/main/packages/backend/README.md). +### Standalone Execution +If you wish to run this package as a standalone webapp, first install [`NiceGUI`](https://pypi.org/project/nicegui/). +Then, in `main.py` remove the `root_path` argument in `ui.run`, as shown below. + +From: +```py +if __name__ in {"__main__", "__mp_main__"}: + ui.run(port=9010, title="HHH Editor", root_path="/editor") +``` +To: +```py +if __name__ in {"__main__", "__mp_main__"}: + ui.run(port=9010, title="HHH Editor") +``` +Then run the file such as below. +``` +python3 packages/editor/main.py +``` + +## Concept + +This part of the project is an image editor, meant to be similar to apps such as Microsoft Paint. + +You can: +- Draw lines or pixels, depending on the mode you select. Changing modes will clear the canvas. Pixel mode is limited in features. [`p`] for pen mode. +- Erase lines/pixels. [`e`] for eraser mode. +- Smudge. [`s`] for smudge mode. +- Clip regions. [`c`] for clip mode. +- Draw circles, squares, triangles, stars, and the Python logo. +- Use a different colour via a slot machine-esque spinner. [`a`] to spin. +- Change line width. +- Add text. You can choose for the text to be bolded and/or italicised. You can also choose the font. +- Upload images. +- Undo/redo. [`ctrl` + `z`] and [`ctrl` + `shift` + `z`] respectively. +- Download your creations. +- Clear the canvas. +- Clipped regions, uploaded images, and text can all be resized or rotated. Using a scrollwheel or a similar device changes the size, and using [`Alt` + `Left Arrow`] or [`Alt` + `Right Arrow`] rotates the region +counterclockwise and clockwise respectively. Pressing [`backspace`] will remove the region. +- Open the help menu [`?`]. + +Keybinds are in the square brackets. Additional details are in a help menu. + +To publish your art to the gallery, click the `Register` button and follow the instructions. After logging in clicking the `Publish` button should add your piece +to the gallery. + +## Development + +We used [`Pyscript`](https://pyscript.com/) and [`NiceGUI`](https://nicegui.io/) for this part of the project. NiceGUI was used to create the UI, and Pyscript +was used to control the drawing features, via the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API). diff --git a/heavenly-hostas/packages/editor/main.py b/heavenly-hostas/packages/editor/main.py new file mode 100644 index 00000000..a1a6fa6d --- /dev/null +++ b/heavenly-hostas/packages/editor/main.py @@ -0,0 +1,623 @@ +import asyncio +import base64 +import pathlib +import random + +from nicegui import app, ui +from nicegui.client import Client +from nicegui.events import UploadEventArguments, ValueChangeEventArguments + +SPIN_COUNT = 10 + +HEX = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"] + + +action_options = { + "pen": "🖊️", + "eraser": "🧽", + "smudge": "💨", + "clip": "📎", + "circle": "🟢", + "rectangle": "🟪", + "triangle": "🔺", + "star": "⭐", + "python": "🐍", +} +# I really don't want to do this but I don't know how else to achieve it +global_vars = { + "type_programatically_changed": False, +} + +app.add_static_files("/scripts", pathlib.Path(__file__).parent / "scripts") +app.add_static_files("/static", pathlib.Path(__file__).parent / "static") + + +@ui.page("/", response_timeout=10) +async def index(client: Client) -> None: # noqa: C901, PLR0915 All of the below lines need to be in this function for private viewing of the page + """Index page for the editor.""" + + def do_reset(*, mode_value: bool) -> None: + """Reset the canvas.""" + if mode_value: + ui.run_javascript(f""" + const event = new Event('change'); + const typeSelect = document.querySelector("#type-select"); + typeSelect.setAttribute("value", "{mode_value}"); + typeSelect.dispatchEvent(event); + """) + reset() + + def reset_confirmation(*, mode_value: bool = False) -> None: + """Prompt user to reset canvas.""" + with ui.dialog() as dialog, ui.card(): + ui.label("Are you sure you want to clear the canvas?") + with ui.row().style("display: flex; justify-content: space-between; width: 100%;"): + ui.button("Cancel", on_click=lambda: dialog.close()) + ui.button("Clear", on_click=lambda: (do_reset(mode_value=mode_value), dialog.close())).props( + "color='red'", + ) + dialog.open() + + def revert_type() -> None: + """Revert the type change when cancel is clicked.""" + global_vars["type_programatically_changed"] = True + type_toggle.set_visibility(False) + type_toggle.value = "smooth" if type_toggle.value == "pixel" else "pixel" + type_toggle.update() + type_toggle.set_visibility(True) + global_vars["type_programatically_changed"] = False + + def handle_type_change(dialog: ui.dialog, *, mode_value: bool) -> None: + """Handle type change.""" + dialog.close() + do_reset(mode_value=mode_value) + action_toggle.set_value("pen") + if type_toggle.value == "smooth": + width_input.enable() + width_slider.enable() + file_uploader.enable() + text_input.enable() + add_text_button.enable() + bold_checkbox.enable() + italics_checkbox.enable() + font_family.enable() + elif type_toggle.value == "pixel": + width_input.disable() + width_slider.disable() + file_uploader.disable() + text_input.disable() + add_text_button.disable() + bold_checkbox.disable() + italics_checkbox.disable() + font_family.disable() + + def change_type(*, mode_value: bool = False) -> None: + """Prompt user to reset canvas.""" + if global_vars["type_programatically_changed"]: + return + with ui.dialog() as dialog, ui.card(): + ui.label( + """ + Are you sure you want to change the drawing mode? This will clear the canvas. + You will not be able to undo this. + """, + ).style("text-align: center;") + with ui.row().style("display: flex; justify-content: space-between; width: 100%;"): + ui.button( + "Cancel", + on_click=lambda: ( + dialog.close(), + revert_type(), + ), + ) + ui.button( + "Change", + on_click=lambda: handle_type_change(dialog, mode_value=mode_value), + ).props( + "color='red'", + ) + dialog.open() + + def reset() -> None: + """Reset canvas.""" + ui.run_javascript(""" + const event = new Event('reset'); + document.body.dispatchEvent(event); + """) + + async def spin() -> None: + """Change RGB values.""" + hex_value = "" + for x in range(SPIN_COUNT): + hex_value = "" + for y in range(3): + text = random.choice(HEX) + random.choice(HEX) # noqa: S311 This isn't for cryptography + colour_values[y].text = text + hex_value += text + await asyncio.sleep(0.1) + ui.run_javascript(f""" + window.pen = window.pen || {{}}; + window.pen.colour = "#{hex_value}"; + const event = new Event('colourChange'); + document.body.dispatchEvent(event); + """) + + def upload_image(e: UploadEventArguments) -> None: + """Fire upload event.""" + ui.notify(f"Uploaded {e.name}") + content = base64.b64encode(e.content.read()).decode("utf-8") + ui.run_javascript(f""" + let event = new Event("change"); + const fileUpload = document.querySelector("#file-upload"); + fileUpload.src = "data:{e.type};base64,{content}"; + fileUpload.dispatchEvent(event); + """) + # e.sender is the file upload element which has a .reset() method + e.sender.reset() # type: ignore # noqa: PGH003 + + def switch_action(e: ValueChangeEventArguments) -> None: + """Fire switch action event.""" + if type_toggle.value == "pixel" and e.value not in ("pen", "eraser"): + action_toggle.value = "pen" + ui.notify("You can only select the pen or erase action while in pixel mode.", type="negative") + return + ui.run_javascript(f""" + const event = new Event('change'); + const actionSelect = document.querySelector("#action-select"); + actionSelect.setAttribute("value", "{e.value}"); + actionSelect.dispatchEvent(event); + """) + + def show_help_menu() -> None: + """Show help modal.""" + with ui.dialog() as dialog, ui.card(): + with ui.card_section(): + ui.markdown( + """ + There are keybinds for the editor actions. + """, + ) + with ui.list().props("dense separator"): + ui.item("p: Select pen (🖊️) mode.") + ui.item("e: Select eraser (🧽) mode.") + ui.item("s: Select smudge (💨) mode.") + ui.item("c: Select clip (📎) mode.") + ui.item("a: Spin a new colour.") + ui.item("ctrl+z: Undo.") + ui.item("ctrl+shift+z: Redo.") + ui.item("?: Show this help menu.") + ui.markdown( + """ + To add images to the canvas, upload one via the file upload, and then click where you want to add + it on the canvas. + """, + ) + ui.markdown( + """ + To add text to the canvas, type in the text input and click the `Add to canvas` button. You can + set the text to be bolded or italicised. You can also select the font from the dropdown. + """, + ) + ui.markdown( + """ + Clipped regions, images, and text can all be resized and rotated. They can be resized using the + scroll wheel or similar. They can be rotated by holding `Alt` and then pressing the + left or right arrow key. + """, + ) + ui.markdown( + """ + You can switch between the smooth (✍️) or pixel (👾) modes using the toggle below. + """, + ) + ui.button( + "Close", + on_click=dialog.close, + ) + dialog.open() + + def show_registration_menu() -> None: + with ui.dialog() as dialog, ui.card(): + with ui.card_section(): + with ui.column(): + ui.html( + """ +

To register you must have a GitHub account.

+

+ If you don't have a GitHub account yet, you can create one here. +

+

+ Already registered? Log In instead. +

+
+

Follow these steps to complete registration:

+ +
    +
  1. + Go to + + https://github.com/heavenly-hostas-hosting/HHH + and create a fork of the repository. +
  2. +
  3. + Head over to + + the app installation link + to + authorize and install our GitHub app, make sure to only select the repository you + forked. +
  4. +
  5. You can now Sign in with GitHub.
  6. +
+ """ + ).classes("registration-menu") + ui.space() + ui.separator().classes("w-full") + ui.space() + ui.label("Step By Step Reference Images") + with ui.expansion("Forking The Repository").classes("w-full"): + ui.image("/static/forking-1.png") + ui.image("/static/forking-2.png") + with ui.expansion("Installing The Application").classes("w-full"): + ui.image("/static/installing-app.png") + + ui.button( + "Close", + on_click=dialog.close, + ) + dialog.open() + + async def publish() -> None: + """Fetch the API and publish the canvas.""" + ui.notify("Publishing...") + try: + response_ok = await ui.run_javascript( + """ + const format = "image/webp"; + const quality = 0.7; // 70% + + const canvas = document.querySelector("#image-canvas"); + const blob = await new Promise((r) => canvas.toBlob(r, format, quality)); + + if (blob === null) { + return false; + } + + // Use FormData so FastAPI can read it as UploadFile + const form = new FormData(); + form.append("image", blob, "canvas.webp"); + + response = await fetch( + "/api/publish", + { + method: "POST", + body: form, + }, + ).catch((e) => console.error(e)); + + return response.ok; + """, + timeout=300, + ) + + if not response_ok: + ui.notify("Failed to publish!", type="negative") + return + + ui.notify("Artwork published successfully!", type="positive") + + except Exception as e: # noqa: BLE001 + ui.notify(f"An error occurred: {e}", type="negative") + + def show_publish_confirmation() -> None: + with ui.dialog() as dialog, ui.card(): + with ui.card_section(), ui.column(): + ui.label("Are you sure you want to publish your creation?").style("text-align: center;") + ui.label("You can only upload 5 images an hour.").style("text-align: center; margin: auto;") + ui.space() + with ui.row().style("display: flex; justify-content: space-between; width: 100%;"): + ui.button("Cancel", on_click=dialog.close) + + async def confirm_publish(): + dialog.close() + await publish() + + ui.button("Publish", on_click=confirm_publish) + + dialog.open() + + async def login() -> None: + """Fetch the API and login.""" + ui.notify("Logging in...") + try: + await ui.run_javascript( + """ + const redirectUrl = "/api/login"; + window.location.href = redirectUrl; + + sessionStorage.setItem("cj12-hhh-logged-in", "true"); + """, + timeout=60, + ) + + ui.notify("Logged in successfully!", type="positive") + + register_button.move(hidden_buttons) + login_button.move(hidden_buttons) + + publish_button.move(shown_buttons) + logout_button.move(shown_buttons) + + except Exception as e: + ui.notify(f"An error occurred: {e}", type="negative") + + async def logout() -> None: + """Fetch the API and logout.""" + ui.notify("Logging out...") + try: + await ui.run_javascript( + """ + const redirectUrl = "/api/logout"; + window.location.href = redirectUrl; + + sessionStorage.setItem("cj12-hhh-logged-in", "false"); + """, + timeout=60, + ) + + ui.notify("Logged out successfully!", type="positive") + + register_button.move(shown_buttons) + login_button.move(shown_buttons) + + publish_button.move(hidden_buttons) + logout_button.move(hidden_buttons) + + except Exception as e: + ui.notify(f"An error occurred: {e}", type="negative") + + async def check_login_status() -> None: + try: + response = await ui.run_javascript( + """ + response = await fetch( + "/api/status", + { method: "GET" }, + ).catch((e) => console.error(e)); + + response_json = response.json(); + + sessionStorage.setItem("cj12-hhh-logged-in", response_json['logged_in']); + + return response_json; + """, + timeout=60, + ) + + if response["logged_in"]: + username.set_text(response["username"]) + register_button.move(hidden_buttons) + login_button.move(hidden_buttons) + + publish_button.move(shown_buttons) + logout_button.move(shown_buttons) + else: + username.set_text("") + register_button.move(shown_buttons) + login_button.move(shown_buttons) + + publish_button.move(hidden_buttons) + logout_button.move(hidden_buttons) + + except Exception as e: + ui.notify(f"An error occurred: {e}", type="negative") + + ui.add_head_html(""" + + + + + """) + + ui.add_body_html(""" + +

Loading...

+
+ """) + + ui.element("img").props("id='file-upload'").style("display: none;") + + with ui.row().style("display: flex; width: 100%;"): + # Page controls + with ui.column().style("flex-grow: 1; flex-basis: 0;"): + username = ui.label("") + + ui.separator().classes("w-full") + + with ui.row(): + dark = ui.dark_mode() + ui.switch("Dark mode").bind_value(dark) + + ui.button(icon="help", on_click=lambda: show_help_menu()).props( + "class='keyboard-shortcuts' shortcut_data='btn,?'", + ) + + ui.button("Clear Canvas", on_click=reset_confirmation).props("color='red'") + + ui.button("Download").props("id='download-button'") + + file_uploader = ( + ui.upload( + label="Upload file", + auto_upload=True, + on_upload=upload_image, + on_rejected=lambda _: ui.notify("There was an issue with the upload."), + ) + .props("accept='image/*' id='file-input'") + .style("width: 100%;") + ) + + type_toggle = ui.toggle( + {"smooth": "✍️", "pixel": "👾"}, + value="smooth", + on_change=lambda e: change_type(mode_value=e.value), + ).props("id='type-select'") + + with ui.row().props("id='shown-buttons'") as shown_buttons: + register_button = ui.button("Register", on_click=show_registration_menu) + login_button = ui.button("Login", on_click=login) + + with ui.row().props("id='hidden-buttons'").style("display: none") as hidden_buttons: + publish_button = ui.button("Publish", on_click=show_publish_confirmation) + logout_button = ui.button("Logout", on_click=logout) + + ui.link("Visit the gallery", "https://heavenly-hostas-hosting.github.io/HHH/") + + with ui.element("div").style("position: relative;"): + ui.element("canvas").props("id='image-canvas'").style( + "border: 1px solid black; background-color: white;", + ) + ui.element("canvas").props("id='buffer-canvas'").style( + "pointer-events: none; position: absolute; top: 0; left: 0;", + ) + + # Canvas controls + with ui.column().style("flex-grow: 1; flex-basis: 0;"): + with ui.row(): + ui.button("Undo").props("id='undo-button' class='keyboard-shortcuts'") + ui.button("Redo").props("id='redo-button' class='keyboard-shortcuts'") + + action_toggle = ( + ui.toggle( + action_options, + value="pen", + on_change=switch_action, + ) + .props( + "id='action-select' class='keyboard-shortcuts' shortcut_data='toggle,p:🖊️,e:🧽,s:💨,c:📎'", + ) + .style("flex-wrap: wrap;") + ) + + ui.separator().classes("w-full") + + with ui.row(): + colour_values = [] + for colour in ["R", "G", "B"]: + with ui.column().style("align-items: center;"): + ui.label(colour) + colour_label = ui.label("00") + colour_values.append(colour_label) + + ui.button("Spin", on_click=spin).props("class='keyboard-shortcuts' shortcut_data='btn,a'") + + ui.separator().classes("w-full") + + width_input = ui.number(label="Line Width", min=1, max=100, step=1) + width_slider = ui.slider( + min=1, + max=100, + value=5, + on_change=lambda _: ui.run_javascript(""" + const event = new Event('change'); + document.querySelector(".width-input").dispatchEvent(event); + """), + ).classes("width-input") + width_input.bind_value(width_slider) + + ui.separator().classes("w-full") + + text_input = ui.input( + label="Text", + placeholder="Start typing", + ).props("id='text-input'") + + with ui.row(): + bold_checkbox = ui.checkbox("Bold").props("id='bold-text'") + italics_checkbox = ui.checkbox("Italics").props("id='italics-text'") + + with ui.row(): + font_family = ui.select( + [ + "Arial", + "Verdana", + "Tahoma", + "Trebuchet MS", + "Times New Roman", + "Georgia", + "Garamond", + "Courier New", + "Brush Script MT", + ], + value="Arial", + ).props("id='text-font-family'") + + add_text_button = ui.button( + "Add to canvas", + on_click=lambda: ( + ui.run_javascript(""" + const event = new Event("addText"); + document.querySelector("#text-input").dispatchEvent(event); + """), + text_input.set_value(""), + ), + ) + + ui.add_body_html(""" + + [[fetch]] + from = "/scripts/" + files = ["canvas_ctx.py", "editor.py", "shortcuts.py"] + + + + """) + + await client.connected() + drawing_mode = await ui.run_javascript("return localStorage.getItem('cj12-hhh-drawing-mode');") + if drawing_mode == "pixel": + revert_type() + width_input.disable() + width_slider.disable() + file_uploader.disable() + text_input.disable() + add_text_button.disable() + bold_checkbox.disable() + italics_checkbox.disable() + font_family.disable() + + logged_in = await ui.run_javascript("return sessionStorage.getItem('cj12-hhh-logged-in');") + if logged_in == "true": + register_button.move(hidden_buttons) + login_button.move(hidden_buttons) + + publish_button.move(shown_buttons) + logout_button.move(shown_buttons) + + ui.on("content_loaded", check_login_status) + ui.timer(5.0, check_login_status) + + +if __name__ in {"__main__", "__mp_main__"}: + ui.run(port=9010, title="HHH Editor", root_path="/editor") diff --git a/heavenly-hostas/packages/editor/pyproject.toml b/heavenly-hostas/packages/editor/pyproject.toml new file mode 100644 index 00000000..233e1071 --- /dev/null +++ b/heavenly-hostas/packages/editor/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "editor" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "nicegui>=2.22.2", +] diff --git a/heavenly-hostas/packages/editor/scripts/__init__.py b/heavenly-hostas/packages/editor/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/heavenly-hostas/packages/editor/scripts/canvas_ctx.py b/heavenly-hostas/packages/editor/scripts/canvas_ctx.py new file mode 100644 index 00000000..e1b4f3e9 --- /dev/null +++ b/heavenly-hostas/packages/editor/scripts/canvas_ctx.py @@ -0,0 +1,486 @@ +# There are a lot of NOQAs in this file as these are typehints based on JS classes/methods. + +from typing import Any, Literal + +from js import ( # pyright: ignore[reportMissingImports] + ImageData, + MouseEvent, + document, +) +from pyodide.ffi import JsProxy # pyright: ignore[reportMissingImports] + + +class DOMRect: + """Bounding box typehint.""" + + bottom: float + height: float + left: float + right: float + top: float + width: float + x: float + y: float + + +class ImageBitmap: + """Image bitmap typehint.""" + + height: float + width: float + + +class TextMetrics: + """TextMetrics typehint.""" + + actualBoundingBoxAscent: float # noqa: N815 + actualBoundingBoxDescent: float # noqa: N815 + actualBoundingBoxLeft: float # noqa: N815 + actualBoundingBoxRight: float # noqa: N815 + alphabeticBaseline: float # noqa: N815 + emHeightAscent: float # noqa: N815 + emHeightDescent: float # noqa: N815 + fontBoundingBoxAscent: float # noqa: N815 + fontBoundingBoxDescent: float # noqa: N815 + hangingBaseline: float # noqa: N815 + ideographicBaseline: float # noqa: N815 + width: float + + +class CanvasSettings: + """`CanvasSettings` for `CanvasContext`.""" + + def __init__(self) -> None: + self.willReadFrequently = True + + +class CanvasContext: + """`CanvasContext` for a HTML5 Canvas element.""" + + # Custom attributes + scaled_by: float = 2 # Better resolution + drawing: bool = False + action: Literal["pen", "eraser", "smudge", "rectangle", "triangle", "star"] = "pen" + type: Literal["smooth", "pixel"] = "smooth" + current_img: Any + bounding_rect: Any + last_x: float + last_y: float + smudge_data: ImageData + prev_data: ImageData + moving_image: bool + writing_text: bool + text_value: str + prev_operation: Literal[ + "source-over", + "source-in", + "source-out", + "source-atop", + "destination-over", + "destination-in", + "destination-out", + "destination-atop", + "lighter", + "copy", + "xor", + "multiply", + "screen", + "overlay", + "darken", + "lighten", + "color-dodge", + "color-burn", + "hard-light", + "soft-light", + "difference", + "exclusion", + "hue", + "saturation", + "color", + "luminosity", + ] + text_settings: dict[str, str | bool | int] + history: list[Any] + history_index: int + text_placed: bool + clipping: bool + moving_clip: bool + start_coords: list[float] + prev_stroke_style: str + prev_line_width: int + size_change: int + rotation: float + is_rotating: bool + current_position: list[float] + drawing_shape: bool + + # Builtin attributes + canvas: Any + direction: Any + fillStyle: Any # noqa: N815 + filter: Any + font: Any + fontKerning: Any # noqa: N815 + fontStretch: Any # noqa: N815 + fontVariantCaps: Any # noqa: N815 + globalAlpha: Any # noqa: N815 + globalCompositeOperation: Literal[ # noqa: N815 + "source-over", + "source-in", + "source-out", + "source-atop", + "destination-over", + "destination-in", + "destination-out", + "destination-atop", + "lighter", + "copy", + "xor", + "multiply", + "screen", + "overlay", + "darken", + "lighten", + "color-dodge", + "color-burn", + "hard-light", + "soft-light", + "difference", + "exclusion", + "hue", + "saturation", + "color", + "luminosity", + ] = "source-over" + + imageSmoothingEnabled: bool # noqa: N815 + imageSmoothingQuality: Any # noqa: N815 + langExperimental: Any # noqa: N815 + letterSpacing: Any # noqa: N815 + lineCap: str = "round" # noqa: N815 + lineDashOffset: Any # noqa: N815 + lineJoin: str = "round" # noqa: N815 + lineWidth: float # noqa: N815 + miterLimit: Any # noqa: N815 + shadowBlur: Any # noqa: N815 + shadowColor: Any # noqa: N815 + shadowOffsetX: Any # noqa: N815 + shadowOffsetY: Any # noqa: N815 + strokeStyle: str # noqa: N815 + textAlign: Any # noqa: N815 + textBaseline: Any # noqa: N815 + textRendering: Any # noqa: N815 + wordSpacing: Any # noqa: N815 + + def __init__( + self, + settings: CanvasSettings, + ) -> None: + """Get the canvas context 2d.""" + self.canvas = document.getElementById("image-canvas") + self.ctx = self.canvas.getContext("2d", settings) + + ########################################################################### + # properties + ########################################################################### + @property + def rect_left(self) -> float: + """The left side of the bounding rect.""" + return self.getBoundingClientRect().left + + @property + def rect_right(self) -> float: + """The right side of the bounding rect.""" + return self.getBoundingClientRect().left + + @property + def rect_top(self) -> float: + """The top side of the bounding rect.""" + return self.getBoundingClientRect().left + + @property + def rect_bottom(self) -> float: + """The bottom side of the bounding rect.""" + return self.getBoundingClientRect().left + + ########################################################################### + # Cutstom Methods + ########################################################################### + def getBoundingClientRect(self) -> DOMRect: # noqa: N802 + """Get the canvas getBoundingClientRect.""" + return self.canvas.getBoundingClientRect() + + def get_canvas_coords(self, event: MouseEvent) -> tuple[float, float]: + """Give the canvas coordinates. + + Args: + event (MouseEvent): The mouse event + + Returns: + tuple[float, float]: The x and y coordinates + + """ + x = (event.pageX - self.rect_left) * self.scaled_by + y = (event.pageY - self.rect_top) * self.scaled_by + return (x, y) + + ########################################################################### + # Builtin Methods + ########################################################################### + def arc( # noqa: PLR0913 This method has this many arguments. + self, + x: float, + y: float, + radius: float, + startAngle: float, # noqa: N803 + endAngle: float, # noqa: N803 + *, + counterclockwise: bool = False, + ) -> None: + """Add arc.""" + self.ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise) + + def arcTo(self) -> None: # noqa: N802 + """Add arcTo.""" + self.ctx.arcTo() + + def beginPath(self) -> None: # noqa: N802 + """Add beginPath.""" + self.ctx.beginPath() + + def bezierCurveTo(self, cp1x: float, cp1y: float, cp2x: float, cp2y: float, x: float, y: float) -> None: # noqa: N802 + """Add bezierCurveTo.""" + self.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) + + def clearRect(self, x: float, y: float, width: float, height: float) -> None: # noqa: N802 + """Add clearRect.""" + self.ctx.clearRect(x, y, width, height) + + def clip(self) -> None: + """Add clip.""" + self.ctx.clip() + + def closePath(self) -> None: # noqa: N802 + """Add closePath.""" + self.ctx.closePath() + + def createConicGradient(self) -> None: # noqa: N802 + """Add createConicGradient.""" + self.ctx.createConicGradient() + + def createImageData(self) -> None: # noqa: N802 + """Add createImageData.""" + self.ctx.createImageData() + + def createLinearGradient(self) -> None: # noqa: N802 + """Add createLinearGradient.""" + self.ctx.createLinearGradient() + + def createPattern(self) -> None: # noqa: N802 + """Add createPattern.""" + self.ctx.createPattern() + + def createRadialGradient(self) -> None: # noqa: N802 + """Add createRadialGradient.""" + self.ctx.createRadialGradient() + + def drawFocusIfNeeded(self) -> None: # noqa: N802 + """Add drawFocusIfNeeded.""" + self.ctx.drawFocusIfNeeded() + + def drawImage( # noqa: N802 + self, + image: JsProxy, + dx: float, + dy: float, + dWidth: float | None = None, # noqa: N803 + dHeight: float | None = None, # noqa: N803 + ) -> None: + """Add drawImage.""" + self.ctx.drawImage(image, dx, dy, dWidth, dHeight) + + def ellipse( # noqa: PLR0913 This method has this many arguments. + self, + x: float, + y: float, + radiusX: float, # noqa: N803 + radiusY: float, # noqa: N803 + rotation: float, + startAngle: float, # noqa: N803 + endAngle: float, # noqa: N803 + *, + counterclockwise: bool = False, + ) -> None: + """Add ellipse.""" + self.ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) + + def fill(self) -> None: + """Add fill.""" + self.ctx.fill() + + def fillRect(self, x: float, y: float, width: float, height: float) -> None: # noqa: N802 + """Add fillRect.""" + self.ctx.fillRect(x, y, width, height) + + def fillText(self, text: str, x: float, y: float) -> None: # noqa: N802 + """Add fillText.""" + self.ctx.fillText(text, x, y) + + def getContextAttributes(self) -> None: # noqa: N802 + """Add getContextAttributes.""" + self.ctx.getContextAttributes() + + def getImageData(self, sx: float, sy: float, sw: float, sh: float) -> ImageData: # noqa: N802 + """Get the image data from the canvas.""" + return self.ctx.getImageData(sx, sy, sw, sh) + + def getLineDash(self) -> None: # noqa: N802 + """Add getLineDash.""" + self.ctx.getLineDash() + + def getTransform(self) -> None: # noqa: N802 + """Add getTransform.""" + self.ctx.getTransform() + + def isContextLost(self) -> None: # noqa: N802 + """Add isContextLost.""" + self.ctx.isContextLost() + + def isPointInPath(self) -> None: # noqa: N802 + """Add isPointInPath.""" + self.ctx.isPointInPath() + + def isPointInStroke(self) -> None: # noqa: N802 + """Add isPointInStroke.""" + self.ctx.isPointInStroke() + + def lineTo(self, x: float, y: float) -> None: # noqa: N802 + """Make a line to the x, y given.""" + self.ctx.lineTo(x, y) + + def measureText(self, text: str) -> TextMetrics: # noqa: N802 + """Add measureText.""" + return self.ctx.measureText(text) + + def moveTo(self, x: float, y: float) -> None: # noqa: N802 + """Move to the x, y given.""" + self.ctx.moveTo(x, y) + + def putImageData( # # noqa: N802, PLR0913 This method has this many arguments. + self, + imageData: ImageData, # noqa: N803 + dx: float, + dy: float, + dirtyX: float | None = None, # noqa: N803 + dirtyY: float | None = None, # noqa: N803 + dirtyWidth: float | None = None, # noqa: N803 + dirtyHeight: float | None = None, # noqa: N803 + ) -> None: + """Paint rectangle onto canvas. + + Paints data from the given ImageData object onto the canvas. If a dirty rectangle is provided, only the + pixels from that rectangle are painted. This method is not affected by the canvas transformation matrix. + + Parameters + ---------- + imageData + An ImageData object containing the array of pixel values. + + dx: float + Horizontal position (x coordinate) at which to place the image data + in the destination canvas. + + dy: float + Vertical position (y coordinate) at which to place the image data + in the destination canvas. + + dirtyX: float | None = None + Horizontal position (x coordinate) of the top-left corner from + which the image data will be extracted. Defaults to 0. + + dirtyY: float | None = None + Vertical position (y coordinate) of the top-left corner from which + the image data will be extracted. Defaults to 0. + + dirtyWidth: float | None = None + Width of the rectangle to be painted. Defaults to the width of the + image data. + + dirtyHeight: float | None = None + Height of the rectangle to be painted. Defaults to the height of + the image data. + + """ + self.ctx.putImageData( + imageData, + dx, + dy, + dirtyX, + dirtyY, + dirtyWidth, + dirtyHeight, + ) + + def quadraticCurveTo(self) -> None: # noqa: N802 + """Add quadraticCurveTo.""" + self.ctx.quadraticCurveTo() + + def rect(self, x: float, y: float, width: float, height: float) -> None: + """Set the rect.""" + self.ctx.rect(x, y, width, height) + + def reset(self) -> None: + """Add reset.""" + self.ctx.reset() + + def resetTransform(self) -> None: # noqa: N802 + """Add resetTransform.""" + self.ctx.resetTransform() + + def restore(self) -> None: + """Add restore.""" + self.ctx.restore() + + def rotate(self, angle: float) -> None: + """Add rotate.""" + self.ctx.rotate(angle) + + def roundRect(self, x: float, y: float, width: float, height: float, radii: list[float]) -> None: # noqa: N802 + """Add roundRect.""" + self.ctx.roundRect(x, y, width, height, radii) + + def save(self) -> None: + """Add save.""" + self.ctx.save() + + def scale(self, x: float, y: float) -> None: + """Add scale.""" + self.ctx.scale(x, y) + + def setLineDash(self, segments: list[float]) -> None: # noqa: N802 + """Add setLineDash.""" + self.ctx.setLineDash(segments) + + def setTransform(self) -> None: # noqa: N802 + """Add setTransform.""" + self.ctx.setTransform() + + def stroke(self) -> None: + """Add stroke.""" + self.ctx.stroke() + + def strokeRect(self, x: float, y: float, width: float, height: float) -> None: # noqa: N802 + """Add strokeRect.""" + self.ctx.strokeRect(x, y, width, height) + + def strokeText(self) -> None: # noqa: N802 + """Add strokeText.""" + self.ctx.strokeText() + + def transform(self) -> None: + """Add transform.""" + self.ctx.transform() + + def translate(self, x: float, y: float) -> None: + """Add translate.""" + self.ctx.translate(x, y) diff --git a/heavenly-hostas/packages/editor/scripts/editor.py b/heavenly-hostas/packages/editor/scripts/editor.py new file mode 100644 index 00000000..c839cfe4 --- /dev/null +++ b/heavenly-hostas/packages/editor/scripts/editor.py @@ -0,0 +1,1233 @@ +# This should be under the other imports but because it isn't imported in the traditional way, it's above them. +from math import cos, pi, sin +from typing import Literal + +from canvas_ctx import CanvasContext, ImageBitmap # pyright: ignore[reportMissingImports] + +# Following imports have the ignore flag as they are not pip installed +from js import ( # pyright: ignore[reportMissingImports] + Event, + Image, + ImageData, + KeyboardEvent, + Math, + MouseEvent, + Object, + createImageBitmap, + document, + localStorage, + window, +) +from pyodide.ffi import create_proxy # pyright: ignore[reportMissingImports] +from pyscript import when # pyright: ignore[reportMissingImports] + +canvas = document.getElementById("image-canvas") +buffer = document.getElementById("buffer-canvas") +text_input = document.getElementById("text-input") + +bold_input = document.getElementById("bold-text") +italics_input = document.getElementById("italics-text") +font_family_input = document.getElementById("text-font-family").querySelector("input") + +settings = Object() +settings.willReadFrequently = True + +ctx: CanvasContext = canvas.getContext("2d", settings) +buffer_ctx: CanvasContext = buffer.getContext("2d", settings) + +canvas.style.imageRendering = "pixelated" +canvas.style.imageRendering = "crisp-edges" + +buffer.style.imageRendering = "pixelated" +buffer.style.imageRendering = "crisp-edges" + +# Settings properties of the canvas. +display_height = window.innerHeight * 0.95 # 95vh +display_width = display_height * (2**0.5) # Same ratio as an A4 sheet of paper + +ctx.scaled_by = 2 # Better resolution + +canvas.style.height = f"{display_height}px" +canvas.style.width = f"{display_width}px" + +canvas.height = display_height * ctx.scaled_by +canvas.width = display_width * ctx.scaled_by + +buffer.style.height = f"{display_height}px" +buffer.style.width = f"{display_width}px" + +buffer.height = display_height * ctx.scaled_by +buffer.width = display_width * ctx.scaled_by + + +ctx.imageSmoothingEnabled = False +ctx.strokeStyle = "black" +ctx.lineWidth = 5 +ctx.lineCap = "round" +ctx.lineJoin = "round" +ctx.font = "50px Arial" + +# Custom attributes attached so we don't need to use global vars +ctx.drawing = False +ctx.action = "pen" +ctx.type = "smooth" +ctx.bounding_rect = canvas.getBoundingClientRect() +ctx.current_img = Image.new() +ctx.moving_image = False +ctx.writing_text = False +ctx.text_placed = True +ctx.prev_operation = "source-over" +ctx.text_settings = {"bold": False, "italics": False, "size": 50, "font-family": "Arial"} +ctx.clipping = False +ctx.moving_clip = False +ctx.drawing_shape = False +ctx.start_coords = [0, 0] +ctx.prev_stroke_style = "black" +ctx.prev_line_width = 5 +ctx.size_change = 0 +ctx.rotation = 0 +ctx.is_rotating = False +ctx.current_position = [0, 0] + + +buffer_ctx.imageSmoothingEnabled = False +buffer_ctx.strokeStyle = "black" +buffer_ctx.lineWidth = 5 +buffer_ctx.lineCap = "round" +buffer_ctx.lineJoin = "round" +buffer_ctx.font = f"{ctx.text_settings['size']}px {ctx.text_settings['font-family']}" + +ctx.history = [] +ctx.history_index = -1 +MAX_HISTORY = 50 +MIN_RESIZE_SIZE = 20 +MAX_RESIZE_SIZE = 200 +ROTATION_SPEED = 3 + +PIXEL_SIZE = 8 +SMUDGE_BLEND_FACTOR = 0.5 + + +def save_history() -> None: + """Save the historical data.""" + ctx.history = ctx.history[: ctx.history_index + 1] + if len(ctx.history) >= MAX_HISTORY: + ctx.history.pop(0) + ctx.history_index -= 1 + + ctx.history.append(ctx.getImageData(0, 0, canvas.width, canvas.height)) + ctx.history_index += 1 + save_change_to_browser() + + +def save_change_to_browser() -> None: + """Save change to browser storage.""" + localStorage.setItem("cj12-hhh-image-data", canvas.toDataURL("image/webp")) + localStorage.setItem("cj12-hhh-drawing-mode", ctx.type) + + +@when("click", "#undo-button") +def undo(_: Event) -> None: + """Undo history.""" + if ctx.history_index <= 0: + return + ctx.history_index -= 1 + + def place_history(img_bitmap: ImageBitmap) -> None: + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.prev_operation = ctx.globalCompositeOperation + ctx.globalCompositeOperation = "source-over" + ctx.drawImage(img_bitmap, 0, 0, canvas.width, canvas.height) + ctx.globalCompositeOperation = ctx.prev_operation + localStorage.setItem("cj12-hhh-image-data", canvas.toDataURL("image/webp")) + localStorage.setItem("cj12-hhh-drawing-mode", ctx.type) + + createImageBitmap(ctx.history[ctx.history_index]).then(place_history) + + +@when("click", "#redo-button") +def redo(_: Event) -> None: + """Redo history.""" + if ctx.history_index >= len(ctx.history) - 1: + return + ctx.history_index += 1 + + def place_history(img_bitmap: ImageBitmap) -> None: + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.prev_operation = ctx.globalCompositeOperation + ctx.globalCompositeOperation = "source-over" + ctx.drawImage(img_bitmap, 0, 0, canvas.width, canvas.height) + ctx.globalCompositeOperation = ctx.prev_operation + localStorage.setItem("cj12-hhh-image-data", canvas.toDataURL("image/webp")) + localStorage.setItem("cj12-hhh-drawing-mode", ctx.type) + + createImageBitmap(ctx.history[ctx.history_index]).then(place_history) + + +def draw_pixel(x: float, y: float) -> None: + """Draws the pixel on the canvas. + + Args: + x (float): X coordinate + y (float): Y coordinate + """ + ctx.fillStyle = ctx.strokeStyle + ctx.fillRect(x - PIXEL_SIZE // 2, y - PIXEL_SIZE // 2, PIXEL_SIZE, PIXEL_SIZE) + + +def show_action_icon(x: float, y: float) -> bool: + """Show icon to let user know what the action would look like. + + Args: + x (float): X coordinate + y (float): Y coordinate + + Returns: + bool: If True is returned mousemove doesn't do anything else + """ + + def draw_clip(img_bitmap: ImageBitmap) -> None: + buffer_ctx.clearRect(0, 0, canvas.width, canvas.height) + + # Canvas matrix to cursor coords first + buffer_ctx.translate( + x, + y, + ) + buffer_ctx.rotate(ctx.rotation) # Apply rotation + + # Ratio for scaling up/down + ratio = img_bitmap.width / img_bitmap.height + + if img_bitmap.width < img_bitmap.height: + buffer_ctx.strokeRect( + -(img_bitmap.width + ctx.size_change) / 2, # Shift the horizontal centre to be on the cursor + -(img_bitmap.height + ctx.size_change * ratio) / 2, # Shift the verical centre to be on the cursor + img_bitmap.width + ctx.size_change, + img_bitmap.height + ctx.size_change * ratio, + ) + buffer_ctx.drawImage( + img_bitmap, + -(img_bitmap.width + ctx.size_change) / 2, + -(img_bitmap.height + ctx.size_change * ratio) / 2, + img_bitmap.width + ctx.size_change, + img_bitmap.height + ctx.size_change * ratio, + ) + else: + buffer_ctx.strokeRect( + -(img_bitmap.width + ctx.size_change * ratio) / 2, + -(img_bitmap.height + ctx.size_change) / 2, + img_bitmap.width + ctx.size_change * ratio, + img_bitmap.height + ctx.size_change, + ) + buffer_ctx.drawImage( + img_bitmap, + -(img_bitmap.width + ctx.size_change * ratio) / 2, + -(img_bitmap.height + ctx.size_change) / 2, + img_bitmap.width + ctx.size_change * ratio, + img_bitmap.height + ctx.size_change, + ) + + buffer_ctx.rotate(-ctx.rotation) # Undo rotation + + # Move canvas matrix back to top left corner + buffer_ctx.translate( + -x, + -y, + ) + + if ctx.moving_clip: + createImageBitmap(ctx.prev_data).then(draw_clip) + return True + + buffer_ctx.clearRect(0, 0, canvas.width, canvas.height) + + if ctx.moving_image: + buffer_ctx.translate( + x, + y, + ) + buffer_ctx.rotate(ctx.rotation) + + ratio = ctx.current_img.width / ctx.current_img.height + + if ctx.current_img.width < ctx.current_img.height: + buffer_ctx.drawImage( + ctx.current_img, + -(ctx.current_img.width + ctx.size_change) / 2, + -(ctx.current_img.height + ctx.size_change * ratio) / 2, + ctx.current_img.width + ctx.size_change, + ctx.current_img.height + ctx.size_change * ratio, + ) + else: + buffer_ctx.drawImage( + ctx.current_img, + -(ctx.current_img.width + ctx.size_change * ratio) / 2, + -(ctx.current_img.height + ctx.size_change) / 2, + ctx.current_img.width + ctx.size_change * ratio, + ctx.current_img.height + ctx.size_change, + ) + + buffer_ctx.rotate(-ctx.rotation) + buffer_ctx.translate( + -x, + -y, + ) + + return True + if ctx.writing_text and not ctx.text_placed: + buffer_ctx.translate( + x, + y, + ) + buffer_ctx.rotate(ctx.rotation) + + text_dimensions = ctx.measureText(ctx.text_value) + buffer_ctx.fillText( + ctx.text_value, + -text_dimensions.width / 2, + (text_dimensions.actualBoundingBoxAscent + text_dimensions.actualBoundingBoxDescent) / 2, + ) + + buffer_ctx.rotate(-ctx.rotation) + buffer_ctx.translate( + -x, + -y, + ) + + return True + if ctx.clipping: + buffer_ctx.strokeRect( + ctx.start_coords[0], + ctx.start_coords[1], + x - ctx.start_coords[0], + y - ctx.start_coords[1], + ) + return True + + regular_icon_show(x, y) + return False + + +def regular_icon_show(x: float, y: float) -> None: + """Show preview for regular actions. + + Args: + x (float): X coordinate + y (float): Y coordinate + """ + if ctx.type == "smooth": + buffer_ctx.beginPath() + buffer_ctx.arc(x, y, ctx.lineWidth / 2, 0, 2 * Math.PI) # Put a dot here + if ctx.action == "pen": + buffer_ctx.fill() + elif ctx.action == "eraser": + prev_width = buffer_ctx.lineWidth + prev_fill = buffer_ctx.fillStyle + + buffer_ctx.lineWidth = ctx.scaled_by + buffer_ctx.fillStyle = "white" + buffer_ctx.fill() + buffer_ctx.arc(x, y, ctx.lineWidth / 2, 0, 2 * Math.PI) + buffer_ctx.stroke() + + buffer_ctx.lineWidth = prev_width + buffer_ctx.fillStyle = prev_fill + + +def get_smudge_data(x: float, y: float) -> ImageData: + """Get the smudge data around the xy for smudgeing.""" + smudge_size = ctx.lineWidth + + return ctx.getImageData( + x - (smudge_size // 2), + y - (smudge_size // 2), + smudge_size, + smudge_size, + ) + + +def put_smudge_data(x: float, y: float) -> None: + """Put the smudge data around the xy for smudgeing.""" + smudge_size = ctx.lineWidth + + ctx.putImageData( + ctx.smudge_data, + x - (smudge_size // 2), + y - (smudge_size // 2), + ) + + +def update_smudge_data(x: float, y: float) -> None: + """Update the smudge data around the xy for smudgeing.""" + ctx.smudge_data = get_smudge_data(x, y) + ctx.last_x = x + ctx.last_y = y + + +def draw_smudge(event: MouseEvent) -> None: + """Draws the smudge data on the canvas. + + Args: + event (MouseEvent): The javascript mouse event + """ + x, y = get_canvas_coords(event) + # draw the pevious smudge data at the current xy. + put_smudge_data(x, y) + + update_smudge_data(x, y) + + +def get_canvas_coords(event: MouseEvent) -> tuple[float, float]: + """Give the canvas coordinates. + + Args: + event (MouseEvent): The mouse event + + Returns: + tuple[float, float]: The x and y coordinates + """ + x = (event.pageX - ctx.bounding_rect.left) * ctx.scaled_by + y = (event.pageY - ctx.bounding_rect.top) * ctx.scaled_by + if ctx.type == "pixel": + x = (int(x) + 5) // 10 * 10 + y = (int(y) + 5) // 10 * 10 + return (x, y) + + +@when("mousedown", "#image-canvas") +def start_path(event: MouseEvent) -> None: + """Start drawing the path. + + Args: + event (MouseEvent): The mouse event + """ + if event.button != 0: + return + + if ctx.moving_image or ctx.drawing_shape: + return + + ctx.drawing = True + + x, y = get_canvas_coords(event) + if ctx.action == "smudge": + update_smudge_data(x, y) + elif ctx.type == "smooth": + x, y = get_canvas_coords(event) + ctx.beginPath() + ctx.moveTo(x, y) + + +def get_triangle_shape_points( + x: float | int, + y: float | int, + dx: float | int, + dy: float | int, +): + """Get the points in a triangle shape given the start coordinate x,y and + the size of the triangle dx, dy.""" + top_mid_point = (x + (dx / 2), y) + bot_left_point = (x, y + dy) + bot_right_point = (x + dx, y + dy) + return [top_mid_point, bot_left_point, bot_right_point] + + +def get_star_shape_points( + x: float | int, + y: float | int, + dx: float | int, + dy: float | int, +): + """Get the points in a star shape given the start coordinate x,y and the + size of the star dx, dy.""" + center_x = x + dx / 2 + center_y = y + dy / 2 + + outer_radius_x = dx / 2 + inner_radius_x = outer_radius_x / 2 + + outer_radius_y = dy / 2 + inner_radius_y = outer_radius_y / 2 + + NO_OF_STAR_POINTS = 5 + + points: list[tuple[float, float]] = [] + + for i in range(NO_OF_STAR_POINTS * 2): + angle = i * pi / NO_OF_STAR_POINTS # 10 points in the star including inner points. + + radius_x = outer_radius_x if i % 2 == 0 else inner_radius_x + radius_y = outer_radius_y if i % 2 == 0 else inner_radius_y + + px = center_x + cos(angle) * radius_x + py = center_y + sin(angle) * radius_y + + points.append((px, py)) + + return points + + +def draw_python_logo( + x: float | int, + y: float | int, +): + """Draw python logo.""" + buffer_ctx.save() + x0, y0 = ctx.start_coords + x1, y1 = x, y + + left = min(x0, x1) + top = min(y0, y1) + width = abs(x1 - x0) + height = abs(y1 - y0) + + buffer_ctx.translate(left, top) + + scale_x = width / 40 + scale_y = height / 40 + if x1 < x0: + buffer_ctx.translate(width, 0) + scale_x *= -1 + if y1 < y0: + buffer_ctx.translate(0, height) + scale_y *= -1 + + buffer_ctx.scale(scale_x, scale_y) + + # the two rounded rects forming a cross. + buffer_ctx.beginPath() + buffer_ctx.roundRect(10, 0, 20, 40, [10, 5]) + buffer_ctx.roundRect(0, 10, 40, 20, [5, 10]) + buffer_ctx.fill() + + # the two eyes for each of the snakey bois. O.o + buffer_ctx.beginPath() + buffer_ctx.arc(14.5, 5, 1.85, 0, 2 * pi) + buffer_ctx.arc(25.5, 35, 1.85, 0, 2 * pi) + buffer_ctx.fillStyle = "white" + buffer_ctx.fill() + + # the lines that make the mouth of the snakey bois. :) + buffer_ctx.lineWidth = 1 + buffer_ctx.beginPath() + buffer_ctx.moveTo(10, 9.5) + buffer_ctx.lineTo(20, 9.5) + buffer_ctx.moveTo(20, 30.5) + buffer_ctx.lineTo(30, 30.5) + buffer_ctx.strokeStyle = "white" + buffer_ctx.stroke() + + # The lines the seperates the snakey bois from one another. :) :) + buffer_ctx.beginPath() + buffer_ctx.moveTo(9.5, 30) + buffer_ctx.bezierCurveTo(9.5, 20, 12, 20, 19.5, 20) + buffer_ctx.bezierCurveTo(28, 20, 30.5, 20, 30.5, 10) + buffer_ctx.stroke() + + buffer_ctx.restore() + return + + +def draw_shape( + x: float | int, + y: float | int, + shape_type: Literal["rectangle", "triangle", "star"], +) -> None: + """Draw a shape to the buffer_ctx sized by the x,y given relative to the + start x,y when the canvas was initially clicked.""" + if not ctx.drawing_shape: + return + init_x, init_y = ctx.start_coords + dx = x - init_x + dy = y - init_y + match shape_type: + case "rectangle": + buffer_ctx.fillRect(init_x, init_y, dx, dy) + return + case "circle": + buffer_ctx.ellipse( + init_x + dx / 2, + init_y + dy / 2, + abs(dx), + abs(dy), + 0, + 0, + 2 * pi, + ) + buffer_ctx.fill() + return + case "python": + draw_python_logo(x, y) + return + + case "triangle": + points = get_triangle_shape_points(init_x, init_y, dx, dy) + + case "star": + points = get_star_shape_points(init_x, init_y, dx, dy) + + buffer_ctx.beginPath() + buffer_ctx.moveTo(points[0][0], points[0][1]) + for px, py in points[1:]: + buffer_ctx.lineTo(px, py) + + buffer_ctx.closePath() + buffer_ctx.fill() + buffer_ctx.stroke() + return + + +@when("mousemove", "#image-canvas") +def mouse_tracker(event: MouseEvent) -> None: + """Draw the path following the mouse. + + Args: + event (MouseEvent): The mouse event + """ + x, y = get_canvas_coords(event) + ctx.current_position = [x, y] + if show_action_icon(x, y): + return + if not ctx.text_placed: + text_dimensions = ctx.measureText(ctx.text_value) + + buffer_ctx.fillText( + ctx.text_value, + x - text_dimensions.width / 2, + y + (text_dimensions.actualBoundingBoxAscent + text_dimensions.actualBoundingBoxDescent) / 2, + ) + if ctx.writing_text: + return + if not ctx.drawing: + return + if ctx.drawing_shape: + draw_shape(x, y, ctx.action) # pyright: ignore[reportArgumentType] The action will be of the correct literal + return + draw_action(event, x, y) + + +def draw_action(event: MouseEvent, x: float, y: float) -> None: + """Draw the event on the screen. + + Args: + event (MouseEvent): Mouse event + x (float): X coordinate + y (float): Y coordinate + """ + match ctx.type: + case "smooth": + if ctx.action == "smudge": + draw_smudge(event) + elif ctx.action in ("pen", "eraser"): + ctx.lineTo(x, y) + ctx.stroke() + case "pixel": + if ctx.action == "pen": + draw_pixel(x, y) + elif ctx.action == "eraser": + ctx.clearRect(x - PIXEL_SIZE // 2, y - PIXEL_SIZE // 2, PIXEL_SIZE, PIXEL_SIZE) + + +@when("mouseup", "body") +def stop_path(_: MouseEvent) -> None: + """Stop drawing path. + + Args: + event (MouseEvent): The mouse event + """ + if ctx.drawing and not ctx.clipping and not ctx.drawing_shape: + ctx.drawing = False + save_history() + + +@when("mouseup", "body") +def drop_media(event: MouseEvent) -> None: + """Place text or clipping. + + Args: + event (MouseEvent): Mouse event + """ + x, y = get_canvas_coords(event) + if ctx.text_placed: + ctx.writing_text = False + if ctx.clipping: + ctx.clipping = False + ctx.moving_clip = True + + ctx.prev_data = ctx.getImageData( + ctx.start_coords[0], + ctx.start_coords[1], + x - ctx.start_coords[0], + y - ctx.start_coords[1], + ) + ctx.clearRect( + ctx.start_coords[0], + ctx.start_coords[1], + x - ctx.start_coords[0], + y - ctx.start_coords[1], + ) + if ctx.drawing_shape: + ctx.drawing_shape = False + ctx.drawing = False + ctx.drawImage(buffer, 0, 0) + save_history() + + +@when("mouseenter", "#image-canvas") +def start_reentry_path(event: MouseEvent) -> None: + """Start a new path from the edge upon canvas entry. + + Args: + event (MouseEvent): Mouse event + """ + if ctx.drawing: + x, y = get_canvas_coords(event) + ctx.beginPath() + ctx.moveTo(x, y) + + +@when("mouseout", "#image-canvas") +def leaves_canvas(event: MouseEvent) -> None: + """Handle mouse leaving canvas. + + Args: + event (MouseEvent): The mouse event + """ + if ( + not ctx.drawing + or ctx.clipping + or ctx.drawing_shape + or ctx.action in ("circle", "rectangle", "triangle", "star", "python") + ): + return + + if ctx.type == "smooth" and ctx.action != "smudge": # "pen" or "eraser" + x, y = get_canvas_coords(event) + ctx.lineTo(x, y) + ctx.stroke() + + +@when("mousedown", "#image-canvas") +def canvas_click(event: MouseEvent) -> None: + """Handle mouse clicking canvas. + + Args: + event (MouseEvent): The mouse event + """ + if event.button != 0: + return + x, y = get_canvas_coords(event) + if special_actions(x, y): + return + if ctx.type == "smooth": + if ctx.action == "clip" and not ctx.moving_clip: + ctx.clipping = True + ctx.start_coords = [x, y] + + ctx.setLineDash([2, 10]) + buffer_ctx.setLineDash([2, 10]) + + ctx.prev_stroke_style = ctx.strokeStyle + ctx.prev_line_width = ctx.lineWidth + + ctx.strokeStyle = "black" + ctx.lineWidth = 5 + + buffer_ctx.strokeStyle = "black" + buffer_ctx.lineWidth = 5 + + elif ctx.action in ("circle", "rectangle", "triangle", "star", "python"): + ctx.drawing_shape = True + ctx.start_coords = [x, y] + + else: + ctx.beginPath() + ctx.ellipse(x, y, ctx.lineWidth / 100, ctx.lineWidth / 100, 0, 0, 2 * Math.PI) # Put a dot here + if ctx.action in ("pen", "eraser"): + ctx.stroke() + elif ctx.type == "pixel": + if ctx.action == "pen": + draw_pixel(x, y) + elif ctx.action == "eraser": + ctx.clearRect(x - PIXEL_SIZE // 2, y - PIXEL_SIZE // 2, PIXEL_SIZE, PIXEL_SIZE) + + +def special_actions(x: float, y: float) -> bool: + """Draw special action on canvas. + + Args: + x (float): X coordinate + y (float): Y coordinate + + Returns: + bool: Whether to skip the regular drawing process or not + """ + if ctx.moving_image: + draw_image(x, y) + + return True + if ctx.writing_text: + ctx.text_placed = True + + buffer_ctx.clearRect(0, 0, canvas.width, canvas.height) + + ctx.translate( + x, + y, + ) + ctx.rotate(ctx.rotation) + + text_dimensions = ctx.measureText(ctx.text_value) + + ctx.fillText( + ctx.text_value, + -text_dimensions.width / 2, + (text_dimensions.actualBoundingBoxAscent + text_dimensions.actualBoundingBoxDescent) / 2, + ) + ctx.globalCompositeOperation = ctx.prev_operation + + ctx.rotate(-ctx.rotation) + ctx.translate( + -x, + -y, + ) + + ctx.rotation = 0 + return True + + if ctx.moving_clip: + ctx.moving_clip = False + + def draw_clip(img_bitmap: ImageBitmap) -> None: + buffer_ctx.clearRect(0, 0, canvas.width, canvas.height) + + ctx.translate( + x, + y, + ) + ctx.rotate(ctx.rotation) + + ratio = img_bitmap.width / img_bitmap.height + + if img_bitmap.width < img_bitmap.height: + ctx.drawImage( + img_bitmap, + -(img_bitmap.width + ctx.size_change) / 2, + -(img_bitmap.height + ctx.size_change * ratio) / 2, + img_bitmap.width + ctx.size_change, + img_bitmap.height + ctx.size_change * ratio, + ) + else: + ctx.drawImage( + img_bitmap, + -(img_bitmap.width + ctx.size_change * ratio) / 2, + -(img_bitmap.height + ctx.size_change) / 2, + img_bitmap.width + ctx.size_change * ratio, + img_bitmap.height + ctx.size_change, + ) + + ctx.size_change = 0 + + ctx.rotate(-ctx.rotation) + ctx.translate( + -x, + -y, + ) + ctx.rotation = 0 + + createImageBitmap(ctx.prev_data).then(draw_clip) + + ctx.setLineDash([]) + buffer_ctx.setLineDash([]) + + ctx.strokeStyle = ctx.prev_stroke_style + ctx.lineWidth = ctx.prev_line_width + + buffer_ctx.strokeStyle = ctx.prev_stroke_style + buffer_ctx.lineWidth = ctx.prev_line_width + + return True + return False + + +def draw_image(x: float, y: float) -> None: + """Draws the uploaded image to the canvas. + + Args: + x (float): X coordinate + y (float): Y coordinate + """ + ctx.moving_image = False + + buffer_ctx.clearRect(0, 0, canvas.width, canvas.height) + + ctx.translate( + x, + y, + ) + ctx.rotate(ctx.rotation) + + ratio = ctx.current_img.width / ctx.current_img.height + + if ctx.current_img.width < ctx.current_img.height: + ctx.drawImage( + ctx.current_img, + -(ctx.current_img.width + ctx.size_change) / 2, + -(ctx.current_img.height + ctx.size_change * ratio) / 2, + ctx.current_img.width + ctx.size_change, + ctx.current_img.height + ctx.size_change * ratio, + ) + else: + ctx.drawImage( + ctx.current_img, + -(ctx.current_img.width + ctx.size_change * ratio) / 2, + -(ctx.current_img.height + ctx.size_change) / 2, + ctx.current_img.width + ctx.size_change * ratio, + ctx.current_img.height + ctx.size_change, + ) + + ctx.rotate(-ctx.rotation) + ctx.translate( + -x, + -y, + ) + + ctx.globalCompositeOperation = ctx.prev_operation + ctx.size_change = 0 + ctx.rotation = 0 + save_history() + + +@when("colourChange", "body") +def colour_change(_: Event) -> None: + """Handle colour change. + + Args: + _ (Event): Change event + """ + ctx.strokeStyle = window.pen.colour + ctx.fillStyle = window.pen.colour + + buffer_ctx.strokeStyle = window.pen.colour + buffer_ctx.fillStyle = window.pen.colour + + +@when("change", ".width-input") +def width_change(event: Event) -> None: + """Handle colour change. + + Args: + event (Event): Change event + """ + ctx.lineWidth = int(event.target.getAttribute("aria-valuenow")) + buffer_ctx.lineWidth = ctx.lineWidth + + +@when("change", "#action-select") +def action_change(event: Event) -> None: + """Handle action change from `pen` to `eraser` or vice versa. + + Args: + event (Event): Change event + """ + ctx.action = event.target.getAttribute("value") + match ctx.action: + case "pen" | "smudge" | "clip": + ctx.globalCompositeOperation = "source-over" + case "eraser": + ctx.globalCompositeOperation = "destination-out" + + +@when("addText", "#text-input") +def add_text(_: Event) -> None: + """Add text to canvas. + + Args: + _ (Event): Add text event + """ + ctx.text_value = text_input.value + if ctx.text_value: + ctx.writing_text = True + ctx.text_placed = False + + ctx.prev_operation = ctx.globalCompositeOperation + ctx.globalCompositeOperation = "source-over" + + ctx.text_settings["bold"] = "bold" if bold_input.getAttribute("aria-checked") == "true" else "normal" + ctx.text_settings["italics"] = "italic" if italics_input.getAttribute("aria-checked") == "true" else "normal" + + ctx.text_settings["font-family"] = font_family_input.value + # I know it's too long but it doesn't work otherwise + ctx.font = f"{ctx.text_settings['italics']} {ctx.text_settings['bold']} {ctx.text_settings['size']}px {ctx.text_settings['font-family']}" # noqa: E501 + buffer_ctx.font = f"{ctx.text_settings['italics']} {ctx.text_settings['bold']} {ctx.text_settings['size']}px {ctx.text_settings['font-family']}" # noqa: E501 + + +@when("change", "#type-select") +def type_change(event: Event) -> None: + """Handle type change. + + Args: + event (Event): Change event + """ + ctx.type = event.target.getAttribute("value") + if ctx.type == "smooth": + ctx.imageSmoothingEnabled = True + ctx.scaled_by = 2 + elif ctx.type == "pixel": + ctx.imageSmoothingEnabled = False + ctx.scaled_by = 0.5 + buffer_ctx.clearRect(0, 0, canvas.width, canvas.height) + + resize(event, keep_content=False) + + # As far as I know there's no way to check when we change from pixel to smooth in the history so there's + # no way to switch the modes in the UI. Hence I've decided to just clear the history instead. + ctx.history.clear() + ctx.history_index = 0 + save_history() + + +@when("reset", "body") +def reset_board(_: Event) -> None: + """Reset the canvas. + + Args: + _ (Event): Reset event + """ + ctx.clearRect(0, 0, canvas.width, canvas.height) + save_history() + + +@when("click", "#download-button") +def download_image(_: Event) -> None: + """Download the canvas content as an image. + + Args: + _ (Event): Click event + """ + link = document.createElement("a") + link.download = "download.webp" + link.href = canvas.toDataURL() + link.click() + link.remove() + + +@when("change", "#file-upload") +def upload_image(e: Event) -> None: + """Handle image upload. + + Args: + e (Event): Upload event + """ + ctx.prev_operation = ctx.globalCompositeOperation + ctx.globalCompositeOperation = "source-over" + ctx.prev_data = ctx.getImageData(0, 0, canvas.width, canvas.height) + ctx.current_img.src = e.target.src + ctx.moving_image = True + + +@create_proxy +def resize(_: Event, keep_content: dict | bool = True) -> None: # noqa: FBT001, FBT002 keep_content has to be a positional arg + """Resize canvas according to window. + + Args: + _ (Event): Resize event + keep_content (bool): Flag to keep the existing content. It's technically not a dict. It's an Object, + but I can't type hint with it. + """ + data = ctx.getImageData(0, 0, canvas.width, canvas.height) + + line_width = ctx.lineWidth + stroke_style = ctx.strokeStyle + font = ctx.font + global_composite_operation = ctx.globalCompositeOperation + + display_height = window.innerHeight * 0.95 + display_width = display_height * (2**0.5) + + canvas.style.height = f"{display_height}px" + canvas.style.width = f"{display_width}px" + + canvas.height = display_height * ctx.scaled_by + canvas.width = display_width * ctx.scaled_by + ctx.bounding_rect = canvas.getBoundingClientRect() + + if isinstance(keep_content, bool): + if keep_content: + createImageBitmap(data).then( + lambda img_bitmap: ctx.drawImage(img_bitmap, 0, 0, canvas.width, canvas.height), + ) + # I don't know why but keep_content is an object sometimes + elif keep_content.keep_content: # pyright: ignore[reportAttributeAccessIssue] + createImageBitmap(data).then( + lambda img_bitmap: ctx.drawImage(img_bitmap, 0, 0, canvas.width, canvas.height), + ) + + ctx.lineWidth = line_width + ctx.strokeStyle = stroke_style + ctx.fillStyle = stroke_style + + ctx.imageSmoothingEnabled = False + ctx.lineCap = "round" + ctx.lineJoin = "round" + ctx.font = font + ctx.globalCompositeOperation = global_composite_operation + + buffer.style.height = f"{display_height}px" + buffer.style.width = f"{display_width}px" + + buffer.height = display_height * ctx.scaled_by + buffer.width = display_width * ctx.scaled_by + + buffer_ctx.imageSmoothingEnabled = False + buffer_ctx.strokeStyle = stroke_style + buffer_ctx.lineWidth = line_width + buffer_ctx.fillStyle = stroke_style + buffer_ctx.lineCap = "round" + buffer_ctx.lineJoin = "round" + buffer_ctx.font = font + + +@create_proxy +def handle_scroll(e: Event) -> None: + """Handle scrolling on the canvas. Used to increase/decrease the size of + images/text etc. + + Args: + e (Event): Scroll event + """ + e.preventDefault() + x, y = get_canvas_coords(e) + if ctx.writing_text: + # ctx.text_settings["size"] is an int + if e.deltaY > 0 and ctx.text_settings["size"] > MIN_RESIZE_SIZE: # pyright: ignore[reportOperatorIssue] + ctx.text_settings["size"] -= 5 # pyright: ignore[reportOperatorIssue] + elif e.deltaY < 0 and ctx.text_settings["size"] < MAX_RESIZE_SIZE: # pyright: ignore[reportOperatorIssue] + ctx.text_settings["size"] += 5 # pyright: ignore[reportOperatorIssue] + ctx.font = f"{ctx.text_settings['italics']} {ctx.text_settings['bold']} {ctx.text_settings['size']}px {ctx.text_settings['font-family']}" # noqa: E501 + buffer_ctx.font = f"{ctx.text_settings['italics']} {ctx.text_settings['bold']} {ctx.text_settings['size']}px {ctx.text_settings['font-family']}" # noqa: E501 + show_action_icon(x, y) + elif ctx.moving_image: + if ( + e.deltaY > 0 + and min(ctx.current_img.width + ctx.size_change, ctx.current_img.height + ctx.size_change) + > MIN_RESIZE_SIZE + ): + ctx.size_change -= 10 + elif ( + e.deltaY < 0 + and max(ctx.current_img.width + ctx.size_change, ctx.current_img.height + ctx.size_change) + < MAX_RESIZE_SIZE * 100 + ): + ctx.size_change += 10 + show_action_icon(x, y) + elif ctx.moving_clip: + if ( + e.deltaY > 0 + and min(ctx.prev_data.width + ctx.size_change, ctx.prev_data.height + ctx.size_change) > MIN_RESIZE_SIZE + ): + ctx.size_change -= 10 + elif ( + e.deltaY < 0 + and max(ctx.prev_data.width + ctx.size_change, ctx.prev_data.height + ctx.size_change) + < MAX_RESIZE_SIZE * 100 + ): + ctx.size_change += 10 + show_action_icon(x, y) + + +@create_proxy +def keydown_event(event: KeyboardEvent) -> None: + """Handle keydown events. + + Args: + event (KeyboardEvent): Keydown event + """ + if event.key == "Backspace": + if ctx.moving_image: + ctx.moving_image = False + + ctx.globalCompositeOperation = ctx.prev_operation + ctx.size_change = 0 + elif ctx.moving_clip: + ctx.moving_clip = False + + ctx.setLineDash([]) + ctx.strokeStyle = ctx.prev_stroke_style + ctx.lineWidth = ctx.prev_line_width + + buffer_ctx.setLineDash([]) + buffer_ctx.strokeStyle = ctx.prev_stroke_style + buffer_ctx.lineWidth = ctx.prev_line_width + elif ctx.writing_text: + ctx.writing_text = False + + ctx.text_placed = True + ctx.globalCompositeOperation = ctx.prev_operation + show_action_icon(ctx.current_position[0], ctx.current_position[1]) + elif event.key == "Alt": + ctx.is_rotating = True + elif event.key == "ArrowLeft" and ctx.is_rotating and (ctx.moving_image or ctx.moving_clip or ctx.writing_text): + ctx.rotation -= Math.PI / 180 * ROTATION_SPEED + show_action_icon(ctx.current_position[0], ctx.current_position[1]) + elif event.key == "ArrowRight" and ctx.is_rotating and (ctx.moving_image or ctx.moving_clip or ctx.writing_text): + ctx.rotation += Math.PI / 180 * ROTATION_SPEED + show_action_icon(ctx.current_position[0], ctx.current_position[1]) + + +@create_proxy +def keyup_event(event: KeyboardEvent) -> None: + """Handle keyup event. + + Args: + event (KeyboardEvent): Keyup event + """ + if event.key == "Alt": + ctx.is_rotating = False + + +@create_proxy +def load_image(event: Event = None) -> None: + """Load image from the browser storage.""" + data_url = localStorage.getItem("cj12-hhh-image-data") + drawing_mode = localStorage.getItem("cj12-hhh-drawing-mode") + + if data_url: + if drawing_mode == "pixel": + ctx.type = "pixel" + ctx.imageSmoothingEnabled = False + ctx.scaled_by = 0.5 + + saved_canvas_data = Image.new() + saved_canvas_data.src = data_url + saved_canvas_data.addEventListener( + "load", + create_proxy( + lambda _: ctx.drawImage(saved_canvas_data, 0, 0, canvas.width, canvas.height), + ), + ) + + if drawing_mode == "pixel": + resize(event) + save_history() + + +# Load image from storage +if document.readyState == "loading": + window.addEventListener("DOMContentLoaded", load_image) +else: + load_image() + + +window.addEventListener("resize", resize) + +ctx.current_img.addEventListener("load", resize) + +document.addEventListener("keydown", keydown_event) +document.addEventListener("keyup", keyup_event) + +# The wheel event is for most browsers. The mousewheel event is deprecated +# but the wheel event is not supported by Safari and Webviewer on iOS. +canvas.addEventListener("wheel", handle_scroll) +canvas.addEventListener("mousewheel", handle_scroll) diff --git a/heavenly-hostas/packages/editor/scripts/shortcuts.py b/heavenly-hostas/packages/editor/scripts/shortcuts.py new file mode 100644 index 00000000..26ccc790 --- /dev/null +++ b/heavenly-hostas/packages/editor/scripts/shortcuts.py @@ -0,0 +1,91 @@ +# Disable missing imports as Pyscript is loaded at runtime +from js import Element, KeyboardEvent # pyright: ignore[reportMissingImports] +from pyscript import document, when # pyright: ignore[reportMissingImports] + +shortcuts_dict = {} +text_input = document.getElementById("text-input") +holding_keys = {"control": False} +undo_button = document.getElementById("undo-button") +redo_button = document.getElementById("redo-button") + + +def handle_toggle(elem: Element, data: list[str]) -> None: + """Add elements to dictionary. + + Args: + elem (Element): Toggle element + data (list[str]): Keybind data + + """ + btn_dict = {btn.innerText: btn for btn in elem.children} + + for d in data: + key, value = d.split(":") + action = btn_dict[value].click + shortcuts_dict[key] = action + + +def handle_btn(elem: Element, data: list[str]) -> None: + """Add elements to dictionary. + + Args: + elem (Element): Button element + data (list[str]): Keybind data + + """ + action = elem.click + key = data[0] + + shortcuts_dict[key] = action + + +for elem in document.getElementsByClassName("keyboard-shortcuts"): + if elem.getAttribute("shortcut_data"): + data = elem.getAttribute("shortcut_data").split(",") + if not data: + continue + if data[0] == "toggle": + handle_toggle(elem, data[1:]) + continue + if data[0] == "btn": + handle_btn(elem, data[1:]) + continue + + +@when("keydown", "body") +def handle_keydown(event: KeyboardEvent) -> None: + """Switch action when keybind is pressed. + + Args: + event (KeyboardEvent): Keydown event + + """ + # Disable keybinds when writing text + if event.target == text_input: + return + if event.key == "Control": + holding_keys["control"] = True + if holding_keys["control"] and event.key == "z": + undo_button.click() + elif holding_keys["control"] and event.key == "Z": + redo_button.click() + if event.repeat: # runs only once when same key is pressed more than once or held down + return + action = shortcuts_dict.get(event.key, None) + if action: + action() + + +@when("keyup", "body") +def handle_up(event: KeyboardEvent) -> None: + """Switch action when keybind is released. + + Args: + event (KeyboardEvent): Keydown event + + """ + # Disable keybinds when writing text + if event.target == text_input: + return + if event.key == "Control": + holding_keys["control"] = False diff --git a/heavenly-hostas/packages/editor/static/forking-1.png b/heavenly-hostas/packages/editor/static/forking-1.png new file mode 100644 index 00000000..204fa71e Binary files /dev/null and b/heavenly-hostas/packages/editor/static/forking-1.png differ diff --git a/heavenly-hostas/packages/editor/static/forking-2.png b/heavenly-hostas/packages/editor/static/forking-2.png new file mode 100644 index 00000000..3dd53fcb Binary files /dev/null and b/heavenly-hostas/packages/editor/static/forking-2.png differ diff --git a/heavenly-hostas/packages/editor/static/installing-app.png b/heavenly-hostas/packages/editor/static/installing-app.png new file mode 100644 index 00000000..c3c59c27 Binary files /dev/null and b/heavenly-hostas/packages/editor/static/installing-app.png differ diff --git a/heavenly-hostas/packages/gallery/README.md b/heavenly-hostas/packages/gallery/README.md new file mode 100644 index 00000000..64a18941 --- /dev/null +++ b/heavenly-hostas/packages/gallery/README.md @@ -0,0 +1,22 @@ +This is the image gallery part of the project. The final product can be accessed [here!!](https://heavenly-hostas-hosting.github.io/HHH/) + +## Concept + +This is the section of the project that manages the Image Gallery. It's a 3D place that has on display every picture that every user has ever posted on the app. + +You are able to navigate the room in 3D from a first-person perspective, being able to fly around the place. You also have the ability to share its different artworks by generating a link that will place you on the exact spot to admire said piece in all of its glory. + +## Development + +We utilize the [*pyscript*](https://github.com/pyscript/pyscript) framework, which allows the execution of Python code inside the browser, including also some very useful interop with JavaScript. This last feature has been very important for the making of this section, as it allows us to have [three.js](https://github.com/mrdoob/three.js) bindings that enable fast 3D rendering in the web browser (the interface in question being similar to how you can use compiled *C* code through libraries like *numpy*). The use of said APIs along with some homemade assets built with Blender (all source files in the repo) have made this project possible. + +Only external asset used is the free [HDRi image 'Lebombo' by Greg Zaal (CC0)](https://polyhaven.com/a/lebombo), thank you Greg!! + +## Dev guide + +The easiest way to test the website locally is just to run a basic HTTP server. You can do that in Python by running the following in the directory that contains this file: + +``` +python3 -m http.server +``` +If you run the project locally you might also encounter issues with CORS permissions as the page is intended to access an external, global repository. To substitute said repo with a local placeholder for testing purposes, you can set the `USE_LOCALHOST` global to `True` on `./main.py`. diff --git a/heavenly-hostas/packages/gallery/assets/css/style.css b/heavenly-hostas/packages/gallery/assets/css/style.css new file mode 100644 index 00000000..44890bc4 --- /dev/null +++ b/heavenly-hostas/packages/gallery/assets/css/style.css @@ -0,0 +1,78 @@ + +#loading { + outline: none; + border: none; + background: transparent; +} + +#help-menu { + outline: 5px solid gray; + border: none; + background: white; + width: 50%; + box-shadow: 10cm; +} + +#instructions { + position: absolute; + top: 14%; + left: 50%; + transform: translate(-50%, -50%); /* perfectly center horizontally & vertically */ + max-width: 80%; /* optional, keeps text from stretching too wide */ + text-align: center; + color: black; + font-size: 28px; + font-family: sans-serif; + font-weight: bold; + background-color: white; /* highlight effect */ + padding: 12px 20px; /* padding inside box */ + border-radius: 8px; /* rounded corners */ + box-shadow: 0 4px 8px rgba(0,0,0,0.2); /* subtle shadow for depth */ + display: none; + cursor: pointer; +} + + +#editor { + position: absolute; + top: 20%; + left: 50%; + transform: translate(-50%, -50%); /* perfectly center horizontally & vertically */ + max-width: 80%; /* optional, keeps text from stretching too wide */ + text-align: center; + color: black; + font-size: 28px; + font-family: sans-serif; + font-weight: bold; + background-color: white; /* highlight effect */ + padding: 12px 20px; /* padding inside box */ + border-radius: 8px; /* rounded corners */ + box-shadow: 0 4px 8px rgba(0,0,0,0.2); /* subtle shadow for depth */ + display: none; +} + +li { + font-size: 24px; + line-height: 1.5; + margin-bottom: 8px; +} + +button { + font-size: 16px; + line-height: 1.5; + margin-bottom: 8px; + padding: 12px 20px; + border: none; + border-radius: 8px; + background-color: #007bff; + color: white; + cursor: pointer; +} + +#loading_tips { + font-size: 20px; +} + +body { + margin: 0; +} diff --git a/heavenly-hostas/packages/gallery/assets/editor.html b/heavenly-hostas/packages/gallery/assets/editor.html new file mode 100644 index 00000000..5e4ed029 --- /dev/null +++ b/heavenly-hostas/packages/gallery/assets/editor.html @@ -0,0 +1,17 @@ + + + + + + + + + Page Redirection + + + + If you are not redirected automatically, follow this link to https://cj12.matiiss.com/editor. + + diff --git a/heavenly-hostas/packages/gallery/assets/gallery.blend b/heavenly-hostas/packages/gallery/assets/gallery.blend new file mode 100644 index 00000000..535cef2c Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/gallery.blend differ diff --git a/heavenly-hostas/packages/gallery/assets/gallery_0.glb b/heavenly-hostas/packages/gallery/assets/gallery_0.glb new file mode 100644 index 00000000..5fdbd8f2 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/gallery_0.glb differ diff --git a/heavenly-hostas/packages/gallery/assets/gallery_1.glb b/heavenly-hostas/packages/gallery/assets/gallery_1.glb new file mode 100644 index 00000000..ca1f3a04 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/gallery_1.glb differ diff --git a/heavenly-hostas/packages/gallery/assets/gallery_2c.glb b/heavenly-hostas/packages/gallery/assets/gallery_2c.glb new file mode 100644 index 00000000..56a78926 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/gallery_2c.glb differ diff --git a/heavenly-hostas/packages/gallery/assets/gallery_2s.glb b/heavenly-hostas/packages/gallery/assets/gallery_2s.glb new file mode 100644 index 00000000..ad5e2d38 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/gallery_2s.glb differ diff --git a/heavenly-hostas/packages/gallery/assets/gallery_3.glb b/heavenly-hostas/packages/gallery/assets/gallery_3.glb new file mode 100644 index 00000000..c3249279 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/gallery_3.glb differ diff --git a/heavenly-hostas/packages/gallery/assets/gallery_4.glb b/heavenly-hostas/packages/gallery/assets/gallery_4.glb new file mode 100644 index 00000000..7cd9a7fc Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/gallery_4.glb differ diff --git a/heavenly-hostas/packages/gallery/assets/images/test-image-nobg.png b/heavenly-hostas/packages/gallery/assets/images/test-image-nobg.png new file mode 100644 index 00000000..9d9f5cc5 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/images/test-image-nobg.png differ diff --git a/heavenly-hostas/packages/gallery/assets/images/test-image.webp b/heavenly-hostas/packages/gallery/assets/images/test-image.webp new file mode 100644 index 00000000..ab22bb66 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/images/test-image.webp differ diff --git a/heavenly-hostas/packages/gallery/assets/images/tree-test-image.avif b/heavenly-hostas/packages/gallery/assets/images/tree-test-image.avif new file mode 100644 index 00000000..f10ca8d7 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/images/tree-test-image.avif differ diff --git a/heavenly-hostas/packages/gallery/assets/lebombo_1k.hdr b/heavenly-hostas/packages/gallery/assets/lebombo_1k.hdr new file mode 100644 index 00000000..3aee1937 Binary files /dev/null and b/heavenly-hostas/packages/gallery/assets/lebombo_1k.hdr differ diff --git a/heavenly-hostas/packages/gallery/assets/map.txt b/heavenly-hostas/packages/gallery/assets/map.txt new file mode 100644 index 00000000..c41ff1b4 --- /dev/null +++ b/heavenly-hostas/packages/gallery/assets/map.txt @@ -0,0 +1,15 @@ +x - x - x - x - x - x - x - x +| | | | +x - x x x x - x - x +| | | | | +x x x x - x - x +| | | | +x - x - x - x - x - x - x - x +| | | | +x - x - x x x - x - x +| | | +x - x x x +| | | | +x x x x x - x - x +| | | | | | +x - x - x - x - x - x - x - x diff --git a/heavenly-hostas/packages/gallery/assets/test-image-listing.json b/heavenly-hostas/packages/gallery/assets/test-image-listing.json new file mode 100644 index 00000000..79dc5e45 --- /dev/null +++ b/heavenly-hostas/packages/gallery/assets/test-image-listing.json @@ -0,0 +1,387 @@ +{"artworks":[ + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "test-image-nobg.png"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "tree-test-image.avif"], + ["a", "test-image.webp"] +]} diff --git a/heavenly-hostas/packages/gallery/assets/tips.txt b/heavenly-hostas/packages/gallery/assets/tips.txt new file mode 100644 index 00000000..abbe35d2 --- /dev/null +++ b/heavenly-hostas/packages/gallery/assets/tips.txt @@ -0,0 +1,4 @@ +This is just a list of "tips" we can display to the user with the loading scene + +1) "z" toggles run +2) "shift" is used to go down diff --git a/heavenly-hostas/packages/gallery/favicon.ico b/heavenly-hostas/packages/gallery/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/heavenly-hostas/packages/gallery/index.html b/heavenly-hostas/packages/gallery/index.html new file mode 100644 index 00000000..5fb4fe0d --- /dev/null +++ b/heavenly-hostas/packages/gallery/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + Image Gallery + + + + + + +

Loading...

+

+
+ + +

+

HELP MENU

+
    +
  1. WASD or arrow keys to move forward, left, backward and right respectively.
  2. +
  3. Z and Ctrl toggle running
  4. +
  5. Space to up and Shift to go down
  6. +
  7. H to open help menu
  8. +
+

+ + +
+ +
Click here to look and move around!
+ + + + + + + diff --git a/heavenly-hostas/packages/gallery/main.py b/heavenly-hostas/packages/gallery/main.py new file mode 100644 index 00000000..7a54232d --- /dev/null +++ b/heavenly-hostas/packages/gallery/main.py @@ -0,0 +1,746 @@ +# -------------------------------------- IMPORTS -------------------------------------- +import asyncio +import json +import warnings +from collections import defaultdict + +# Typing +from collections.abc import Callable +from enum import Enum +from typing import Any + +from js import ( # pyright: ignore[reportMissingImports] + THREE, + GLTFLoader, + Math, + Object, + PointerLockControls, + RGBELoader, + console, +) + +# Local +from map_loader import MAP, ROOM_TYPES, get_gallery_room, get_map_layout +from pyodide.ffi import create_proxy, to_js # pyright: ignore[reportMissingImports] +from pyodide.http import pyfetch # pyright: ignore[reportMissingImports] +from pyscript import document, when, window # pyright: ignore[reportMissingImports] + +# -------------------------------------- GLOBAL VARIABLES -------------------------------------- +USE_LOCALHOST = False +print("GLOBAL VARIABLES") + +# Renderer set up +RENDERER = THREE.WebGLRenderer.new({"antialias": False}) +RENDERER.shadowMap.enabled = False +RENDERER.shadowMap.type = THREE.PCFSoftShadowMap +RENDERER.shadowMap.needsUpdate = True +RENDERER.setSize(window.innerWidth, window.innerHeight) +document.body.appendChild(RENDERER.domElement) + +# Scene setup +setcolor = "#8B8B8B" # Nicer than just black +SCENE = THREE.Scene.new() +SCENE.background = THREE.Color.new(setcolor) + +# Camera setup +CAMERA = THREE.PerspectiveCamera.new(53, window.innerWidth / window.innerHeight, 0.01, 500) +CAMERA.position.set(3, 1, 3.5) +CAMERA.rotation.set(0, 0.4, 0) +SCENE.add(CAMERA) + + +# Building blocks for the rooms, will be filled later +GALLERY_BLOCKS: dict[ROOM_TYPES, THREE.Group] = {} + + +# Picture group to know which paintings have been loaded +PICTURES: THREE.Group = THREE.Group.new() +PICTURES.name = "Picture_group" +SCENE.add(PICTURES) + + +# Other global variables +ROOMS: list[THREE.Group] = [] # a list of all rooms in the scene +PAINTINGS: list[THREE.Object3D] = [] # a list of all the paintings in the scene +LOADED_ROOMS: list[THREE.Group] = [] # a list of all the rooms that are currently loaded +IMAGES_LIST: list[str] = [] # a list of the names of the paintings that have to be loaded in order +LOADED_SLOTS: list[int] = [] # a list of all slots that have been loaded + +# Related to Moving +RUN_STATE: bool = False # to toggle running +CAN_MOVE: bool = False # so that the player cant move unless he is "locked in" + +# distance which we maintain from walls +OFFSET = 0.2 + +VELOCITY = THREE.Vector3.new() + +if USE_LOCALHOST: + REPO_URL = ( + r"https://cdn.jsdelivr.net/gh/" + r"Matiiss/pydis-cj12-heavenly-hostas@dev/" + r"packages/gallery/assets/images/" + ) +else: + REPO_URL = ( + r"https://cdn.jsdelivr.net/gh/" + r"heavenly-hostas-hosting/HHH@data/" + ) + + +# For Type Hinting +V3 = tuple[float, float, float] +V2 = tuple[float, float] + + +# -------------------------------------- HELPER FUNCTIONS -------------------------------------- + + +def tree_print(x, indent=0): + # Prints a 3D model's tree structure + qu = '"' + output = " " * 4 * indent + f"{x.name or qu * 2} ({x.type})" + for i in x.children: + output += "\n" + tree_print(i, indent=indent + 1) + return output + + +def convert_dict_to_js_object(my_dict: dict): + """Convert a Python dict to a JavaScript object.""" + return Object.fromEntries(to_js(my_dict)) + + +def mathRandom(num=1): + setNumber = -Math.random() * num + Math.random() * num + return setNumber + + +def get_painting_info(p: THREE.Mesh) -> tuple[V3, V3, V2]: + v_pos = THREE.Vector3.new() + p.getWorldPosition(v_pos) + position: V3 = v_pos.x, v_pos.y, v_pos.z + + # Beware possible Y-up/Z-up shenanigans + n_array = p.geometry.attributes.normal.array + world_matrix = p.matrixWorld + normal_matrix = THREE.Matrix3.new().getNormalMatrix(world_matrix) + world_normal = ( + THREE.Vector3.new( + -n_array[2], + n_array[1], + n_array[0], + ) + .applyMatrix3(normal_matrix) + .normalize() + ) + normal = world_normal.x, world_normal.y, world_normal.z + + bb = p.geometry.boundingBox + size_xyz = [abs(getattr(bb.max, i) - getattr(bb.min, i)) for i in ("x", "y", "z")] + # Height is Z + size_wh: V2 = (max(size_xyz[0], size_xyz[1]), (size_xyz[2])) + + return (position, normal, size_wh) + + +def get_player_chunk(room_apothem: float) -> tuple[int, int]: + x_coord = round((CAMERA.position.x) / (room_apothem * 2)) + z_coord = round((CAMERA.position.z) / (room_apothem * 2)) + + if x_coord < 0: + x_coord = 0 + if z_coord < 0: + z_coord = 0 + + return x_coord, z_coord + + +# -------------------------------------- MOVEMENT CONTROLS -------------------------------------- + +# Movement Controls +INPUTS = Enum("INPUTS", ["FORW", "LEFT", "RIGHT", "BACK", "UP", "DOWN", "RUN"]) +KEY_MAPPINGS: dict[INPUTS, set[str]] = { + INPUTS.FORW: {"KeyW", "ArrowUp"}, + INPUTS.LEFT: {"KeyA", "ArrowLeft"}, + INPUTS.BACK: {"KeyS", "ArrowDown"}, + INPUTS.RIGHT: {"KeyD", "ArrowRight"}, + INPUTS.UP: {"Space"}, + INPUTS.DOWN: {"ShiftLeft", "ShiftRight"}, + # + INPUTS.RUN: {"KeyZ", "ControlLeft", "ControlRight"}, +} +KEY_STATES: dict[str, bool] = defaultdict(bool) +document.addEventListener("keydown", create_proxy(lambda x: KEY_STATES.__setitem__(x.code, True))) +document.addEventListener("keyup", create_proxy(lambda x: KEY_STATES.__setitem__(x.code, False))) + + +def toggle_run(event): + global RUN_STATE + if event.code in KEY_MAPPINGS[INPUTS.RUN]: + RUN_STATE = not RUN_STATE + if event.key == "h": + openHelpMenu() + + +document.addEventListener("keydown", create_proxy(toggle_run)) + + +# Main move function +def move_character(delta_time: float) -> THREE.Vector3: # noqa: C901 + if not CAN_MOVE: + return THREE.Vector3.new(0, 0, 0) + pressed_keys = {k for k, v in KEY_MAPPINGS.items() if any(KEY_STATES[i] for i in v)} + damping = 8 + if RUN_STATE: + acceleration = 25 * 3 + max_speed = 50 * 3 + + CAMERA.fov = min(CAMERA.fov + 60 * delta_time, 60) + else: + acceleration = 10 * 3 + max_speed = 20 * 3 + + CAMERA.fov = max(CAMERA.fov - 60 * delta_time, 53) + CAMERA.updateProjectionMatrix() + + move = THREE.Vector3.new() + if INPUTS.FORW in pressed_keys: + move.z -= 1 + if INPUTS.BACK in pressed_keys: + move.z += 1 + + if INPUTS.LEFT in pressed_keys: + move.x -= 1 + if INPUTS.RIGHT in pressed_keys: + move.x += 1 + + if INPUTS.UP in pressed_keys: + move.y += 1 + if INPUTS.DOWN in pressed_keys: + move.y -= 1 + + if move.length() > 0: + q = CAMERA.quaternion + yaw = Math.atan2(2 * (q.w * q.y + q.x * q.z), 1 - 2 * (q.y * q.y + q.z * q.z)) + yaw_q = THREE.Quaternion.new() + yaw_q.setFromAxisAngle(THREE.Vector3.new(0, 1, 0), yaw) + + move.applyQuaternion(yaw_q).normalize() + VELOCITY.addScaledVector(move, acceleration * delta_time) + + if VELOCITY.length() > max_speed: + VELOCITY.setLength(max_speed) + + VELOCITY.multiplyScalar(1 - min(damping * delta_time, 1)) + return VELOCITY + + +# -------------------------------------- MOUSE CONTROLS -------------------------------------- + +MOUSE = THREE.Vector2.new() + + +# Mouse Lock Functions +def cam_lock(e): + global CAN_MOVE + setattr( + document.getElementById("instructions").style, + "display", + "none", + ) + setattr( + document.getElementById("editor").style, + "display", + "none", + ) + + CAN_MOVE = True + + +def cam_unlock(e): + global CAN_MOVE + setattr( + document.getElementById("instructions").style, + "display", + "block", + ) + setattr( + document.getElementById("editor").style, + "display", + "block", + ) + CAN_MOVE = False + + +# Mouse Lock +CONTROLS = PointerLockControls.new(CAMERA, document.body) +document.getElementById("instructions").addEventListener("click", create_proxy(CONTROLS.lock)) +CONTROLS.addEventListener( + "lock", + create_proxy(cam_lock), +) +CONTROLS.addEventListener("unlock", create_proxy(cam_unlock)) + + +# Mouse Controls +@when("mousemove", "body") +def onMouseMove(event): + event.preventDefault() + MOUSE.x = (event.clientX / window.innerWidth) * 2 - 1 + MOUSE.y = -(event.clientY / window.innerHeight) * 2 + 1 + + +# -------------------------------------- COLLISION DETECTION -------------------------------------- + + +async def check_collision(velocity: THREE.Vector3, delta_time: float) -> bool: + """ + Checks for collision with walls (cubes) and triggers + returns true if it is safe to move and false if movement should be stopped + """ + raycaster = THREE.Raycaster.new() + direction = velocity.clone().normalize() + raycaster.set(CAMERA.position, direction) + + await check_collision_with_trigger(velocity, delta_time, raycaster) + + return check_collision_with_wall(velocity, delta_time, raycaster) + + +def check_collision_with_wall(velocity: THREE.Vector3, delta_time: float, raycaster: THREE.Raycaster) -> bool: + cubes = [] + [cubes.extend(c.getObjectByName("Cubes").children) for c in ROOMS] + intersections = raycaster.intersectObjects(cubes, recursive=True) + if not intersections: + return True + return intersections[0].distance > velocity.length() * delta_time + OFFSET + + +async def check_collision_with_trigger(velocity: THREE.Vector3, delta_time: float, raycaster: THREE.Raycaster): + triggers = [] + [triggers.extend(c.getObjectByName("Triggers").children) for c in ROOMS] + + intersections = raycaster.intersectObjects(triggers, recursive=True) + if intersections and intersections[0].distance <= velocity.length() * delta_time: + room = intersections[0].object.parent.parent + asyncio.ensure_future(updated_loaded_rooms(room)) + + +# -------------------------------------- HELP MENU -------------------------------------- + + +def closeHelpMenu(e=None): + help_menu = document.getElementById("help-menu") + help_menu.close() + + instructions = document.getElementById("instructions") + instructions.style.display = "block" + + editor = document.getElementById("editor") + editor.style.display = "block" + + +def openHelpMenu(e=None): + CONTROLS.unlock() + + help_menu = document.getElementById("help-menu") + help_menu.showModal() + + instructions = document.getElementById("instructions") + instructions.style.display = "none" + + editor = document.getElementById("editor") + editor.style.display = "none" + + +document.getElementById("close-help-menu").addEventListener("click", create_proxy(closeHelpMenu)) + + +# -------------------------------------- ROOM CREATION -------------------------------------- + + +def room_objects_handling(room: THREE.Group) -> None: + assert room.children[0].name.startswith("Cube") + room.children[0].name = "Cubes" + for v in room.children[0].children: + v.material.side = THREE.FrontSide + + triggers = THREE.Group.new() + triggers.name = "Triggers" + + pictures = THREE.Group.new() + pictures.name = "Pictures" + + for v in room.children[1:]: + if v.name.startswith("trigger"): + room.remove(v) + triggers.add(v) + v.visible = False + + if v.name.startswith("pic"): + room.remove(v) + pictures.add(v) + v.visible = False + + room.add(triggers) + room.add(pictures) + + +def load_image(slot: int): + if slot >= len(PAINTINGS): + warnings.warn( + f"WARNING: slot to be accessed '{slot}' is greater than the maximum available " + f"one '{len(PAINTINGS) - 1}'. The image will not be loaded." + ) + + if slot >= len(IMAGES_LIST): + # this slot does not have a corresponding painting yet + return + + image_loc = IMAGES_LIST[slot] + textureLoader = THREE.TextureLoader.new() + + def inner_loader(loaded_obj): + # Put texture on a plane + perms = convert_dict_to_js_object( + { + "map": loaded_obj, + "transparent": True, + } + ) + geometry = THREE.PlaneGeometry.new(1, 1, 1) + material = THREE.MeshBasicMaterial.new(perms) + plane = THREE.Mesh.new(geometry, material) + plane.scale.x = 1.414 + + # Snap the plane to its slot + (x, y, z), (nx, ny, nz), (w, h) = get_painting_info(PAINTINGS[slot]) + plane.position.set(x, y, z) + + q = THREE.Quaternion.new() + q.setFromUnitVectors(THREE.Vector3.new(-1, 0, 0), THREE.Vector3.new(nx, ny, nz)) + plane.quaternion.copy(q) + + # Add the plane to the scene + plane.name = f"picture_{PAINTINGS[slot].parent.parent.name[5:]}_{slot:03d}" + PICTURES.add(plane) + LOADED_SLOTS.append(slot) + + try: + textureLoader.load( + REPO_URL + image_loc, + create_proxy(inner_loader), + None, + create_proxy(lambda _: None), + ) + except Exception as e: + console.error(e) + + +async def load_images_from_listing() -> int: + if USE_LOCALHOST: + r = await pyfetch("./assets/test-image-listing.json") + else: + r = await pyfetch("https://cj12.matiiss.com/api/artworks") + data = await r.text() + n_existing_images = len(IMAGES_LIST) + for username, img in json.loads(data)["artworks"][n_existing_images:]: + IMAGES_LIST.append(img) + + n_added_images = len(IMAGES_LIST) - n_existing_images + + return n_added_images + + +def create_room( + chunk_coords: tuple[int, int], + room_apothem: float, + room_type: ROOM_TYPES, + rotation: float = 0, +) -> None: + """ + chunk_coords represent the coordinates of the room + room_apothem is the perp distance from the center of the room to its edges + room_type represents the type of the room + rotation represents the rotation of the room, which is supposed to be multiples of pi/2 + """ + + room = GALLERY_BLOCKS[room_type].clone() + room.name = f"room_{chunk_coords[0]}_{chunk_coords[1]}" + ROOMS.append(room) + + position = (chunk_coords[0] * room_apothem * 2, 0, chunk_coords[1] * room_apothem * 2) + room.rotation.y = rotation + room.position.set(*position) + + # Add its children to a global list of paintings + for i in room.getObjectByName("Pictures").children: + i.name = f"pic_{len(PAINTINGS):03d}" + PAINTINGS.append(i) + + SCENE.add(room) + + +async def clone_rooms(chunks: list[tuple[int, int]], layout: MAP, apothem: float): + for x, y in chunks: + room, rotation = get_gallery_room(x, y, layout) + create_room((x, y), apothem, room, rotation) + + +# -------------------------------------- LAZY LOADING -------------------------------------- + + +async def load_room(room: THREE.Group) -> None: + """Loads a room and/or makes it visible.""" + paintings = room.getObjectByName("Pictures") + + if any(p.name.startswith(f"picture_{room.name[5:]}") for p in PICTURES.children): + # Checks whther the room has any "photoframes" in it, if yes then it has been previously loaded and we just + # need to set it to be visible + + for p in PICTURES.children: + if p.name.startswith(f"picture_{room.name[5:]}"): + p.visible = True + else: + # This is the first time we are loading this room so we need to load its paintings too + for p in paintings.children: + if p.name.startswith("pic_"): + slot = int(p.name.split("_")[1]) + if slot < len(IMAGES_LIST): + load_image(slot) + + +async def unload_room(room: THREE.Group) -> None: + """Makes the paintings invisible""" + for p in PICTURES.children: + if p.name.startswith(f"picture_{room.name[5:]}"): + p.visible = False + + +async def updated_loaded_rooms( + current_room: THREE.Group, + force_reload: bool = False, + r: int = 2, +) -> None: + """Loads all rooms which are at some r distance from the current room""" + + def get_chunk_coords(room): + return tuple(int(i) for i in room.name.split("_")[1:]) + + def calc(room): + return ( + sum( + abs(i - j) + for i, j in zip( + get_chunk_coords(current_room), + get_chunk_coords(room), + strict=True, + ) + ) + <= r + ) + + for room in ROOMS: + if room in LOADED_ROOMS: + if not calc(room): + await unload_room(room) + LOADED_ROOMS.remove(room) + elif force_reload: + await load_room(room) + else: + if calc(room): + await load_room(room) + LOADED_ROOMS.append(room) + + +# -------------------------------------- GALLERY LOADING -------------------------------------- + + +def generate_global_lights(): + # Global lighting + ambient_light = THREE.AmbientLight.new(0xFF_FF_FF, 0.5) + # Lighting for floors + hemi_light = THREE.HemisphereLight.new(0xFF_FF_FF, 0x44_44_44, 0.2) + hemi_light = THREE.HemisphereLight.new(0x0, 0xFF_FF_FF, 0.3) + # Adds some depth + main_light = THREE.DirectionalLight.new(0xFF_FF_FF, 1.2) + main_light.position.set(10, 20, 10) + main_light.castShadow = True + + SCENE.add(ambient_light) + SCENE.add(hemi_light) + SCENE.add(main_light) + + # Sexy reflections + loader = RGBELoader.new() + + def inner_loader(loaded_obj, *_): + pmrem = THREE.PMREMGenerator.new(RENDERER) + env_map = pmrem.fromEquirectangular(loaded_obj).texture + SCENE.environment = env_map + loaded_obj.dispose() + pmrem.dispose() + + loader.load( + "./assets/lebombo_1k.hdr", + create_proxy(inner_loader), + ) + + +async def load_gallery_blocks() -> None: + loader = GLTFLoader.new() + + # Needs to do it this way or python will reference the same 'i' + def inner_loader_factory(i: ROOM_TYPES) -> Callable[[Any], None]: + def inner_loader(loaded_obj): + room = loaded_obj.scene + # Backface culling, invisible objects, etc. + room_objects_handling(room) + GALLERY_BLOCKS[i] = room + + return inner_loader + + def inner_progress(xhr): + print(str(xhr.loaded) + " loaded") + + def inner_error(error): + print(f"error: {error}") + + inner_progress_proxy = create_proxy(inner_progress) + inner_error_proxy = create_proxy(inner_error) + + for i in ROOM_TYPES: + inner_loader = inner_loader_factory(i) + + loader.load( + f"./assets/gallery_{i.value}.glb", + create_proxy(inner_loader), + inner_progress_proxy, + inner_error_proxy, + ) + + # Ensure they are loaded + while True: + for i in ROOM_TYPES: + if i not in GALLERY_BLOCKS: + await asyncio.sleep(0.02) + break + else: + break + + +def get_room_apothem() -> float: + # Get the corner room, estimate distance from center + room = GALLERY_BLOCKS[ROOM_TYPES._2c] + assert room.children[0].name.startswith("Cube") + # Centers + cx, cz = room.position.x, room.position.z + + triggers: list[THREE.Group] = [i for i in room.getObjectByName("Triggers").children] + trigger_centers: list[tuple[float, float]] = [(i.position.x, i.position.z) for i in triggers] + + apothems = [max(abs(cx - ix), abs(cz - iz)) for ix, iz in trigger_centers] + output = sum(apothems) / len(apothems) + return output + + +async def load_gallery() -> None: + _, layout = await asyncio.gather( + load_gallery_blocks(), + get_map_layout(), + ) + apothem = get_room_apothem() + # Get all layout points, sorted by Hamiltonian distance from (0, 0) + layout_points = sorted( + [(x, y) for y in range(len(layout)) for x in range(len(layout)) if layout[y][x] is not None], + key=lambda p: abs(p[0]) + abs(p[1]), + ) + await clone_rooms(layout_points, layout, apothem) + + +async def image_query_loop(): + apothem = get_room_apothem() + while True: + await asyncio.sleep(15) + + # Might error out because of downtime, no big deal + try: + n_added_images = await load_images_from_listing() + if n_added_images: + chunk_x, chunk_z = get_player_chunk(apothem) + print(f"New images to be added: {n_added_images}") + await updated_loaded_rooms( + SCENE.getObjectByName(f"room_{chunk_x}_{chunk_z}"), + force_reload=True, + r=3, # A slightly bigger radius, just in case + ) + except Exception: + ... + + +def tp_to_slot(slot: int) -> None: + print(f"Going to image on index {slot}...") + try: + painting = PAINTINGS[slot] + except IndexError: + print("Invalid index to tp camera to") + return + (x, y, z), (nx, ny, nz), (w, h) = get_painting_info(painting) + pos = THREE.Vector3.new(x, y, z) + + CAMERA.position.copy(pos) + apothem = get_room_apothem() + chunk_x, chunk_z = get_player_chunk(apothem) + CAMERA.position.set(chunk_x * apothem * 2, CAMERA.position.y, chunk_z * apothem * 2) + + +def url_process() -> None: + params = window.URLSearchParams.new(window.location.search) + + idx_raw = params.get("idx") + picture = params.get("picture") + if idx_raw is not None: + idx = int(idx_raw) + elif picture is not None: + try: + idx = IMAGES_LIST.index(picture) + print(f"Image with name {picture} found") + except ValueError: + print(f"Image with name {picture} not found") + return + else: + return + + tp_to_slot(idx) + + +async def main(): + await load_images_from_listing() + + while not SCENE.getObjectByName("room_0_0"): + await asyncio.sleep(0.05) + + asyncio.ensure_future(updated_loaded_rooms(SCENE.getObjectByName("room_0_0"))) + + asyncio.ensure_future(image_query_loop()) + + # TP camera + url_process() + + clock = THREE.Clock.new() + while True: + delta = clock.getDelta() + velocity = move_character(delta) + if velocity == THREE.Vector3.new(0, 0, 0): + continue + if await check_collision(velocity, delta): + CAMERA.position.addScaledVector(velocity, delta) + + RENDERER.render(SCENE, CAMERA) + await asyncio.sleep(0.02) + + +if __name__ == "__main__": + asyncio.ensure_future(load_gallery()) + generate_global_lights() + asyncio.ensure_future(main()) diff --git a/heavenly-hostas/packages/gallery/map_loader.py b/heavenly-hostas/packages/gallery/map_loader.py new file mode 100644 index 00000000..bf62d212 --- /dev/null +++ b/heavenly-hostas/packages/gallery/map_loader.py @@ -0,0 +1,113 @@ +from enum import Enum +from math import pi + +from pyodide.http import pyfetch # pyright: ignore[reportMissingImports] + +__all__ = [ + "NODE", + "MAP", + "get_map_layout", + # + "ROOM_TYPES", + "get_gallery_room", +] + +NODE = tuple[bool, bool, bool, bool] +MAP = list[list[NODE | None]] + + +async def get_map_layout() -> MAP: + r = await pyfetch("./assets/map.txt") + text = await r.text() + data = [i for i in text.split("\n") if i] + + # (x, y) = (0, 0) is top left corner + output: MAP = [[] for _ in range(0, len(data), 2)] + + for y in range(0, len(data), 2): + for x in range(0, len(data[y]), 4): + if data[y][x] != "x": + output[y // 2].append(None) + continue + + north = False + if (y - 1 > 0) and (data[y - 1][x] == "|"): + north = True + + east = False + if (x + 2 < len(data[y])) and (data[y][x + 2] == "-"): + east = True + + south = False + if (y + 1 < len(data)) and (data[y + 1][x] == "|"): + south = True + + west = False + if (x - 2 > 0) and (data[y][x - 2] == "-"): + west = True + + output[y // 2].append((north, east, south, west)) + + return output + + +class ROOM_TYPES(Enum): + _1 = "1" + _2s = "2s" + _2c = "2c" + _3 = "3" + _4 = "4" + + +def get_gallery_room( # noqa: C901 + x: int, + y: int, + layout: MAP, +) -> tuple[ROOM_TYPES, float]: + node = layout[y][x] + assert node is not None + north, east, south, west = node + + match (north, east, south, west): + # 4 exits + case z if all(z): + return ROOM_TYPES._4, 0 + # no exit + case z if not any(z): + assert False + + # 1 exit + case z if sum(z) == 1: + for idx, i in enumerate(z): + if i: + break + else: + assert False + return ROOM_TYPES._1, (pi / 2 * ((-idx + 3) % 4)) + + # 2 exits + # straight + case (False, True, False, True): + return ROOM_TYPES._2s, 0 + case (True, False, True, False): + return ROOM_TYPES._2s, pi / 2 + # corner + case z if sum(z) == 2: + for idx, i in enumerate(z): + if i and z[idx - 1]: + break + else: + assert False + return ROOM_TYPES._2c, (pi / 2 * ((-idx + 0) % 4)) + + # 3 exits + case z if sum(z) == 3: + for idx, i in enumerate(z): + if not i: + break + else: + assert False + return ROOM_TYPES._3, (pi / 2 * ((-idx + 2) % 4)) + + case _: + assert False, "This one is serious" diff --git a/heavenly-hostas/packages/gallery/pyproject.toml b/heavenly-hostas/packages/gallery/pyproject.toml new file mode 100644 index 00000000..58768172 --- /dev/null +++ b/heavenly-hostas/packages/gallery/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "gallery" +version = "0.1.0" +description = "An online Image Gallery rendered through Three.js and WebGL and programmed in PyScript." +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] diff --git a/heavenly-hostas/packages/gallery/pyscript.toml b/heavenly-hostas/packages/gallery/pyscript.toml new file mode 100644 index 00000000..c9185a4e --- /dev/null +++ b/heavenly-hostas/packages/gallery/pyscript.toml @@ -0,0 +1,5 @@ +name = "Image Gallery" +description = "An online Image Gallery rendered through Three.js and WebGL and programmed in PyScript." +[[fetch]] +files = ["map_loader.py"] +from = '.' diff --git a/heavenly-hostas/pyproject.toml b/heavenly-hostas/pyproject.toml new file mode 100644 index 00000000..8d930fcc --- /dev/null +++ b/heavenly-hostas/pyproject.toml @@ -0,0 +1,62 @@ +[project] +name = "heavenly-hostas-hosting" +description = "A little app for the Python Discord Code Jam 12 from the Heavenly Hostas team." +authors = [ + { name = "Matiiss", email = "matiiss@matiiss.com" }, + { name = "PiLogic" }, + { name = "HiPeople21", email = "hipeople3.14159@gmail.com" }, + { name = "Sergio Díaz-Esparza" }, + { name = "Alan Manning" }, + { name = "Aadil Goyal", email = "aadilgoyal1@gmail.com" }, +] +version = "0.1.0" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] + +[dependency-groups] +# This `dev` group contains all the development requirements for our linting toolchain. +dev = ["pre-commit~=4.2.0", "ruff~=0.12.2"] + +[tool.ruff] +line-length = 119 +target-version = "py312" # Target Python 3.12 +# Automatically fix auto-fixable issues. +fix = true +# The directory containing the source code. If you choose a different project layout +# you will need to update this value. +src = ["src"] # TODO: fix + +[tool.ruff.lint] +select = ["E", "F", "I", "C901"] + +# select = ["ALL"] +# Ignore some of the most obnoxious linting errors. +# ignore = [ +# # Missing docstrings. +# "D100", +# "D104", +# "D105", +# "D106", +# "D107", +# # Docstring whitespace. +# "D203", +# "D213", +# # Docstring punctuation. +# "D415", +# # Docstring quotes. +# "D301", +# # Builtins. +# "A", +# # Print statements. +# "T20", +# # TODOs. +# "TD002", +# "TD003", +# "FIX", +# # Namespace packages. +# "INP001", +# ] + +[tool.uv.workspace] +members = ["packages/backend", "packages/editor", "packages/gallery"] diff --git a/heavenly-hostas/set_up_supabase_volumes.sh b/heavenly-hostas/set_up_supabase_volumes.sh new file mode 100644 index 00000000..afac3cd4 --- /dev/null +++ b/heavenly-hostas/set_up_supabase_volumes.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# inspired by https://supabase.com/docs/guides/self-hosting/docker#installing-and-running-supabase + +SB_DST=./supabase-repository +SHA=671aea0a4af7119131393e3bc2d187bc54c8604a + +git init $SB_DST +cd $SB_DST +git remote add origin https://github.com/supabase/supabase + +git sparse-checkout init --cone +git sparse-checkout set docker/volumes + +git fetch --filter=blob:none origin $SHA +git checkout $SHA + +cd .. + +cp -r $SB_DST/docker/volumes ./volumes +rm -rf $SB_DST diff --git a/heavenly-hostas/src/app/__init__.py b/heavenly-hostas/src/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/heavenly-hostas/uv.lock b/heavenly-hostas/uv.lock new file mode 100644 index 00000000..df307860 --- /dev/null +++ b/heavenly-hostas/uv.lock @@ -0,0 +1,1529 @@ +version = 1 +revision = 1 +requires-python = ">=3.12" + +[manifest] +members = [ + "backend", + "editor", + "gallery", + "heavenly-hostas-hosting", +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896 }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333 }, + { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948 }, + { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787 }, + { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590 }, + { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241 }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335 }, + { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491 }, + { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929 }, + { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733 }, + { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790 }, + { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245 }, + { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899 }, + { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459 }, + { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434 }, + { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045 }, + { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591 }, + { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266 }, + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741 }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407 }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703 }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532 }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794 }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865 }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238 }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566 }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270 }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294 }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958 }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553 }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688 }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157 }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050 }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647 }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067 }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213 }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "backend" +version = "0.1.0" +source = { virtual = "packages/backend" } +dependencies = [ + { name = "cryptography" }, + { name = "fastapi" }, + { name = "httpx" }, + { name = "psycopg", extra = ["binary", "pool"] }, + { name = "pyjwt" }, + { name = "python-multipart" }, + { name = "supabase" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "cryptography", specifier = ">=45.0.6" }, + { name = "fastapi", specifier = ">=0.116.1" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.2.9" }, + { name = "pyjwt", specifier = ">=2.10.1" }, + { name = "python-multipart", specifier = ">=0.0.20" }, + { name = "supabase", specifier = ">=2.18.0" }, + { name = "uvicorn", specifier = ">=0.35.0" }, +] + +[[package]] +name = "bidict" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764 }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "cryptography" +version = "45.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702 }, + { url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483 }, + { url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679 }, + { url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553 }, + { url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499 }, + { url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484 }, + { url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281 }, + { url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890 }, + { url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247 }, + { url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045 }, + { url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923 }, + { url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805 }, + { url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111 }, + { url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169 }, + { url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273 }, + { url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211 }, + { url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732 }, + { url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655 }, + { url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956 }, + { url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859 }, + { url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254 }, + { url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815 }, + { url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147 }, + { url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459 }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, +] + +[[package]] +name = "docutils" +version = "0.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/86/5b41c32ecedcfdb4c77b28b6cb14234f252075f8cdb254531727a35547dd/docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f", size = 2277984 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/57/8db39bc5f98f042e0153b1de9fb88e1a409a33cda4dd7f723c2ed71e01f6/docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e", size = 630709 }, +] + +[[package]] +name = "editor" +version = "0.1.0" +source = { virtual = "packages/editor" } +dependencies = [ + { name = "nicegui" }, +] + +[package.metadata] +requires-dist = [{ name = "nicegui", specifier = ">=2.22.2" }] + +[[package]] +name = "fastapi" +version = "0.116.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424 }, + { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952 }, + { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688 }, + { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084 }, + { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524 }, + { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493 }, + { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116 }, + { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557 }, + { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820 }, + { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542 }, + { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350 }, + { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093 }, + { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482 }, + { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590 }, + { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785 }, + { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487 }, + { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874 }, + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791 }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165 }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881 }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409 }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132 }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638 }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539 }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646 }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233 }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996 }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280 }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717 }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644 }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879 }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502 }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169 }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219 }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880 }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498 }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296 }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103 }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869 }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467 }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028 }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294 }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898 }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465 }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385 }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771 }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206 }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620 }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059 }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516 }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106 }, +] + +[[package]] +name = "gallery" +version = "0.1.0" +source = { virtual = "packages/gallery" } + +[[package]] +name = "gotrue" +version = "2.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "pydantic" }, + { name = "pyjwt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/67/ae47f68daae1bbb56a9fbf960dfb7d08b3dec52a6ad1e96f69c2ba5b3116/gotrue-2.12.3.tar.gz", hash = "sha256:f874cf9d0b2f0335bfbd0d6e29e3f7aff79998cd1c14d2ad814db8c06cee3852", size = 38323 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/fa/4165d298ef89254c9f742faa3f99a61fe6fd3552b4ba44df6924f8d307d7/gotrue-2.12.3-py3-none-any.whl", hash = "sha256:b1a3c6a5fe3f92e854a026c4c19de58706a96fd5fbdcc3d620b2802f6a46a26b", size = 44022 }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, +] + +[[package]] +name = "h2" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, +] + +[[package]] +name = "heavenly-hostas-hosting" +version = "0.1.0" +source = { virtual = "." } + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "ruff" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit", specifier = "~=4.2.0" }, + { name = "ruff", specifier = "~=0.12.2" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, +] + +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, +] + +[[package]] +name = "identify" +version = "2.6.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "ifaddr" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4", size = 10485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314 }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markdown2" +version = "2.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/f8/b2ae8bf5f28f9b510ae097415e6e4cb63226bb28d7ee01aec03a755ba03b/markdown2-2.5.4.tar.gz", hash = "sha256:a09873f0b3c23dbfae589b0080587df52ad75bb09a5fa6559147554736676889", size = 145652 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/06/2697b5043c3ecb720ce0d243fc7cf5024c0b5b1e450506e9b21939019963/markdown2-2.5.4-py3-none-any.whl", hash = "sha256:3c4b2934e677be7fec0e6f2de4410e116681f4ad50ec8e5ba7557be506d3f439", size = 49954 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "multidict" +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516 }, + { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394 }, + { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591 }, + { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215 }, + { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299 }, + { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357 }, + { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369 }, + { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341 }, + { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100 }, + { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584 }, + { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018 }, + { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477 }, + { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575 }, + { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649 }, + { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505 }, + { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888 }, + { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072 }, + { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222 }, + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848 }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060 }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269 }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158 }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076 }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694 }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350 }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250 }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900 }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355 }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061 }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675 }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247 }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960 }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078 }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708 }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912 }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076 }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812 }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313 }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777 }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321 }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954 }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612 }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528 }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329 }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928 }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228 }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869 }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446 }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299 }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926 }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383 }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775 }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100 }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501 }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313 }, +] + +[[package]] +name = "nicegui" +version = "2.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiohttp" }, + { name = "certifi" }, + { name = "docutils" }, + { name = "fastapi" }, + { name = "h11" }, + { name = "httpx" }, + { name = "ifaddr" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markdown2" }, + { name = "orjson", marker = "platform_machine != 'i386' and platform_machine != 'i686'" }, + { name = "pygments" }, + { name = "python-engineio" }, + { name = "python-multipart" }, + { name = "python-socketio", extra = ["asyncio-client"] }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "uvicorn", extra = ["standard"] }, + { name = "vbuild" }, + { name = "watchfiles" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/81/aaf1432abf54f25d62a3c346a087db39c4d7a38833795c3f1eb90feab5fd/nicegui-2.22.2.tar.gz", hash = "sha256:5c0aaf2d2365c665ae42955b17d6fa7ba19526d7aa9bd02ab547917e7bd1338c", size = 13107276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/2b/141c85335df3982ca9a54da95d0c8954efe9756da8f2edaba2afbd262014/nicegui-2.22.2-py3-none-any.whl", hash = "sha256:a0e371918419af5bae7b22e6b55a29065a7a4222908af66584fd599e391b5b5f", size = 13494497 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "orjson" +version = "3.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/1d/5e0ae38788bdf0721326695e65fdf41405ed535f633eb0df0f06f57552fa/orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309", size = 5470739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/02/46054ebe7996a8adee9640dcad7d39d76c2000dc0377efa38e55dc5cbf78/orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486", size = 226528 }, + { url = "https://files.pythonhosted.org/packages/e2/c6/6b6f0b4d8aea1137436546b990f71be2cd8bd870aa2f5aa14dba0fcc95dc/orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1", size = 115931 }, + { url = "https://files.pythonhosted.org/packages/ae/05/4205cc97c30e82a293dd0d149b1a89b138ebe76afeca66fc129fa2aa4e6a/orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131", size = 111382 }, + { url = "https://files.pythonhosted.org/packages/50/c7/b8a951a93caa821f9272a7c917115d825ae2e4e8768f5ddf37968ec9de01/orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c", size = 116271 }, + { url = "https://files.pythonhosted.org/packages/17/03/1006c7f8782d5327439e26d9b0ec66500ea7b679d4bbb6b891d2834ab3ee/orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14", size = 119086 }, + { url = "https://files.pythonhosted.org/packages/44/61/57d22bc31f36a93878a6f772aea76b2184102c6993dea897656a66d18c74/orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448", size = 120724 }, + { url = "https://files.pythonhosted.org/packages/78/a9/4550e96b4c490c83aea697d5347b8f7eb188152cd7b5a38001055ca5b379/orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c", size = 123577 }, + { url = "https://files.pythonhosted.org/packages/3a/86/09b8cb3ebd513d708ef0c92d36ac3eebda814c65c72137b0a82d6d688fc4/orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804", size = 121195 }, + { url = "https://files.pythonhosted.org/packages/37/68/7b40b39ac2c1c644d4644e706d0de6c9999764341cd85f2a9393cb387661/orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307", size = 119234 }, + { url = "https://files.pythonhosted.org/packages/40/7c/bb6e7267cd80c19023d44d8cbc4ea4ed5429fcd4a7eb9950f50305697a28/orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219", size = 392250 }, + { url = "https://files.pythonhosted.org/packages/64/f2/6730ace05583dbca7c1b406d59f4266e48cd0d360566e71482420fb849fc/orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45", size = 134572 }, + { url = "https://files.pythonhosted.org/packages/96/0f/7d3e03a30d5aac0432882b539a65b8c02cb6dd4221ddb893babf09c424cc/orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e", size = 123869 }, + { url = "https://files.pythonhosted.org/packages/45/80/1513265eba6d4a960f078f4b1d2bff94a571ab2d28c6f9835e03dfc65cc6/orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e", size = 124430 }, + { url = "https://files.pythonhosted.org/packages/fb/61/eadf057b68a332351eeb3d89a4cc538d14f31cd8b5ec1b31a280426ccca2/orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732", size = 119598 }, + { url = "https://files.pythonhosted.org/packages/6b/3f/7f4b783402143d965ab7e9a2fc116fdb887fe53bdce7d3523271cd106098/orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36", size = 114052 }, + { url = "https://files.pythonhosted.org/packages/c2/f3/0dd6b4750eb556ae4e2c6a9cb3e219ec642e9c6d95f8ebe5dc9020c67204/orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219", size = 226419 }, + { url = "https://files.pythonhosted.org/packages/44/d5/e67f36277f78f2af8a4690e0c54da6b34169812f807fd1b4bfc4dbcf9558/orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad", size = 115803 }, + { url = "https://files.pythonhosted.org/packages/24/37/ff8bc86e0dacc48f07c2b6e20852f230bf4435611bab65e3feae2b61f0ae/orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2", size = 111337 }, + { url = "https://files.pythonhosted.org/packages/b9/25/37d4d3e8079ea9784ea1625029988e7f4594ce50d4738b0c1e2bf4a9e201/orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe", size = 116222 }, + { url = "https://files.pythonhosted.org/packages/b7/32/a63fd9c07fce3b4193dcc1afced5dd4b0f3a24e27556604e9482b32189c9/orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/b4/b6/400792b8adc3079a6b5d649264a3224d6342436d9fac9a0ed4abc9dc4596/orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6", size = 120721 }, + { url = "https://files.pythonhosted.org/packages/40/f3/31ab8f8c699eb9e65af8907889a0b7fef74c1d2b23832719a35da7bb0c58/orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1", size = 123574 }, + { url = "https://files.pythonhosted.org/packages/bd/a6/ce4287c412dff81878f38d06d2c80845709c60012ca8daf861cb064b4574/orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa", size = 121225 }, + { url = "https://files.pythonhosted.org/packages/69/b0/7a881b2aef4fed0287d2a4fbb029d01ed84fa52b4a68da82bdee5e50598e/orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e", size = 119201 }, + { url = "https://files.pythonhosted.org/packages/cf/98/a325726b37f7512ed6338e5e65035c3c6505f4e628b09a5daf0419f054ea/orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15", size = 392193 }, + { url = "https://files.pythonhosted.org/packages/cb/4f/a7194f98b0ce1d28190e0c4caa6d091a3fc8d0107ad2209f75c8ba398984/orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac", size = 134548 }, + { url = "https://files.pythonhosted.org/packages/e8/5e/b84caa2986c3f472dc56343ddb0167797a708a8d5c3be043e1e2677b55df/orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8", size = 123798 }, + { url = "https://files.pythonhosted.org/packages/9c/5b/e398449080ce6b4c8fcadad57e51fa16f65768e1b142ba90b23ac5d10801/orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5", size = 124402 }, + { url = "https://files.pythonhosted.org/packages/b3/66/429e4608e124debfc4790bfc37131f6958e59510ba3b542d5fc163be8e5f/orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d", size = 119498 }, + { url = "https://files.pythonhosted.org/packages/7b/04/f8b5f317cce7ad3580a9ad12d7e2df0714dfa8a83328ecddd367af802f5b/orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535", size = 114051 }, + { url = "https://files.pythonhosted.org/packages/74/83/2c363022b26c3c25b3708051a19d12f3374739bb81323f05b284392080c0/orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7", size = 226406 }, + { url = "https://files.pythonhosted.org/packages/b0/a7/aa3c973de0b33fc93b4bd71691665ffdfeae589ea9d0625584ab10a7d0f5/orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81", size = 115788 }, + { url = "https://files.pythonhosted.org/packages/ef/f2/e45f233dfd09fdbb052ec46352363dca3906618e1a2b264959c18f809d0b/orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f", size = 111318 }, + { url = "https://files.pythonhosted.org/packages/3e/23/cf5a73c4da6987204cbbf93167f353ff0c5013f7c5e5ef845d4663a366da/orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7", size = 121231 }, + { url = "https://files.pythonhosted.org/packages/40/1d/47468a398ae68a60cc21e599144e786e035bb12829cb587299ecebc088f1/orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4", size = 119204 }, + { url = "https://files.pythonhosted.org/packages/4d/d9/f99433d89b288b5bc8836bffb32a643f805e673cf840ef8bab6e73ced0d1/orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f", size = 392237 }, + { url = "https://files.pythonhosted.org/packages/d4/dc/1b9d80d40cebef603325623405136a29fb7d08c877a728c0943dd066c29a/orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7", size = 134578 }, + { url = "https://files.pythonhosted.org/packages/45/b3/72e7a4c5b6485ef4e83ef6aba7f1dd041002bad3eb5d1d106ca5b0fc02c6/orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6", size = 123799 }, + { url = "https://files.pythonhosted.org/packages/c8/3e/a3d76b392e7acf9b34dc277171aad85efd6accc75089bb35b4c614990ea9/orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f", size = 124461 }, + { url = "https://files.pythonhosted.org/packages/fb/e3/75c6a596ff8df9e4a5894813ff56695f0a218e6ea99420b4a645c4f7795d/orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8", size = 119494 }, + { url = "https://files.pythonhosted.org/packages/5b/3d/9e74742fc261c5ca473c96bb3344d03995869e1dc6402772c60afb97736a/orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67", size = 114046 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, +] + +[[package]] +name = "postgrest" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "httpx", extra = ["http2"] }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/3e/1b50568e1f5db0bdced4a82c7887e37326585faef7ca43ead86849cb4861/postgrest-1.1.1.tar.gz", hash = "sha256:f3bb3e8c4602775c75c844a31f565f5f3dd584df4d36d683f0b67d01a86be322", size = 15431 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/71/188a50ea64c17f73ff4df5196ec1553a8f1723421eb2d1069c73bab47d78/postgrest-1.1.1-py3-none-any.whl", hash = "sha256:98a6035ee1d14288484bfe36235942c5fb2d26af6d8120dfe3efbe007859251a", size = 22366 }, +] + +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674 }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570 }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094 }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958 }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894 }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672 }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395 }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510 }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949 }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258 }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036 }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684 }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562 }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142 }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711 }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479 }, + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286 }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425 }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846 }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871 }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720 }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203 }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365 }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016 }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596 }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977 }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220 }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642 }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789 }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880 }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220 }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678 }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560 }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676 }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701 }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934 }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316 }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619 }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896 }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111 }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334 }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026 }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724 }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868 }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322 }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778 }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175 }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857 }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663 }, +] + +[[package]] +name = "pscript" +version = "0.7.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/68/f918702e270eddc5f7c54108f6a2f2afc2d299985820dbb0db9beb77d66d/pscript-0.7.7.tar.gz", hash = "sha256:8632f7a4483f235514aadee110edee82eb6d67336bf68744a7b18d76e50442f8", size = 176138 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/bc/980e2ebd442d2a8f1d22780f73db76f2a1df3bf79b3fb501b054b4b4dd03/pscript-0.7.7-py3-none-any.whl", hash = "sha256:b0fdac0df0393a4d7497153fea6a82e6429f32327c4c0a4817f1cd68adc08083", size = 126689 }, +] + +[[package]] +name = "psycopg" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705 }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] +pool = [ + { name = "psycopg-pool" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205 }, + { url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795 }, + { url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043 }, + { url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972 }, + { url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516 }, + { url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160 }, + { url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518 }, + { url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598 }, + { url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289 }, + { url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493 }, + { url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400 }, + { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127 }, + { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322 }, + { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097 }, + { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114 }, + { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693 }, + { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423 }, + { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667 }, + { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576 }, + { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439 }, + { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477 }, + { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009 }, +] + +[[package]] +name = "psycopg-pool" +version = "3.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556 }, +] + +[[package]] +name = "python-engineio" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "simple-websocket" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/0b/67295279b66835f9fa7a491650efcd78b20321c127036eef62c11a31e028/python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa", size = 91677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f", size = 59536 }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, +] + +[[package]] +name = "python-socketio" +version = "5.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bidict" }, + { name = "python-engineio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/1a/396d50ccf06ee539fa758ce5623b59a9cb27637fc4b2dc07ed08bf495e77/python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029", size = 121125 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800 }, +] + +[package.optional-dependencies] +asyncio-client = [ + { name = "aiohttp" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "realtime" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/ca/e408fbdb6b344bf529c7e8bf020372d21114fe538392c72089462edd26e5/realtime-2.7.0.tar.gz", hash = "sha256:6b9434eeba8d756c8faf94fc0a32081d09f250d14d82b90341170602adbb019f", size = 18860 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/07/a5c7aef12f9a3497f5ad77157a37915645861e8b23b89b2ad4b0f11b48ad/realtime-2.7.0-py3-none-any.whl", hash = "sha256:d55a278803529a69d61c7174f16563a9cfa5bacc1664f656959694481903d99c", size = 22409 }, +] + +[[package]] +name = "ruff" +version = "0.12.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315 }, + { url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653 }, + { url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690 }, + { url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923 }, + { url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612 }, + { url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745 }, + { url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885 }, + { url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381 }, + { url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271 }, + { url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783 }, + { url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672 }, + { url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626 }, + { url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162 }, + { url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212 }, + { url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382 }, + { url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482 }, + { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718 }, +] + +[[package]] +name = "simple-websocket" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "starlette" +version = "0.47.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984 }, +] + +[[package]] +name = "storage3" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "httpx", extra = ["http2"] }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/e2/280fe75f65e7a3ca680b7843acfc572a63aa41230e3d3c54c66568809c85/storage3-0.12.1.tar.gz", hash = "sha256:32ea8f5eb2f7185c2114a4f6ae66d577722e32503f0a30b56e7ed5c7f13e6b48", size = 10198 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3b/c5f8709fc5349928e591fee47592eeff78d29a7d75b097f96a4e01de028d/storage3-0.12.1-py3-none-any.whl", hash = "sha256:9da77fd4f406b019fdcba201e9916aefbf615ef87f551253ce427d8136459a34", size = 18420 }, +] + +[[package]] +name = "strenum" +version = "0.4.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff", size = 23384 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851 }, +] + +[[package]] +name = "supabase" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gotrue" }, + { name = "httpx" }, + { name = "postgrest" }, + { name = "realtime" }, + { name = "storage3" }, + { name = "supafunc" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/e1/511411018596cfecfac99d9091630606e9a03cb07bd5dec2f15621d38b84/supabase-2.18.0.tar.gz", hash = "sha256:b1e98f0faff6e041e5347393a82524d9d5c2ddb87cb844e7557f967f23d8bc70", size = 11521 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/78/67238701e0253fdfef222a39df32f9883de2a8b046fa68d1ae47f3e52979/supabase-2.18.0-py3-none-any.whl", hash = "sha256:73774e6edd5ffd02653a99e2e98f92191c4ffb5089dcff3ed93f5d7c18ddaeb3", size = 18677 }, +] + +[[package]] +name = "supafunc" +version = "0.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "strenum" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/4b/16f94bcae8a49f5e09544a4fb0e6ad1c2288038036cefdeedb72fcffd92c/supafunc-0.10.1.tar.gz", hash = "sha256:a5b33c8baecb6b5297d25da29a2503e2ec67ee6986f3d44c137e651b8a59a17d", size = 5036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/4a/9cbea12d86a741d4e73a6e278c2b1d6479fb03d1002efb00e8e71aea76db/supafunc-0.10.1-py3-none-any.whl", hash = "sha256:26df9bd25ff2ef56cb5bfb8962de98f43331f7f8ff69572bac3ed9c3a9672040", size = 8028 }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, +] + +[[package]] +name = "vbuild" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pscript" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/be/f0c6204a36440bbcc086bfa25964d009b7391c5a3c74d6e73188efd47adb/vbuild-0.8.2.tar.gz", hash = "sha256:270cd9078349d907dfae6c0e6364a5a5e74cb86183bb5093613f12a18b435fa9", size = 8937 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/3d/7b22abbdb059d551507275a2815bc2b1974e3b9f6a13781c1eac9e858965/vbuild-0.8.2-py2.py3-none-any.whl", hash = "sha256:d76bcc976a1c53b6a5776ac947606f9e7786c25df33a587ebe33ed09dd8a1076", size = 9371 }, +] + +[[package]] +name = "virtualenv" +version = "20.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/60/4f20960df6c7b363a18a55ab034c8f2bcd5d9770d1f94f9370ec104c1855/virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8", size = 6082160 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362 }, +] + +[[package]] +name = "watchfiles" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339 }, + { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409 }, + { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939 }, + { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270 }, + { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370 }, + { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654 }, + { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667 }, + { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213 }, + { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718 }, + { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098 }, + { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209 }, + { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786 }, + { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343 }, + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004 }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671 }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772 }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789 }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551 }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420 }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950 }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706 }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814 }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820 }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194 }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349 }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836 }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343 }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916 }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582 }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752 }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436 }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016 }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727 }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864 }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626 }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744 }, + { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114 }, + { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879 }, + { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026 }, + { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917 }, + { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602 }, + { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758 }, + { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601 }, + { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936 }, + { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243 }, + { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073 }, + { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872 }, + { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877 }, + { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645 }, + { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424 }, + { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584 }, + { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675 }, + { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363 }, + { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240 }, + { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607 }, + { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315 }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667 }, + { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025 }, + { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709 }, + { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287 }, + { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429 }, + { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429 }, + { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862 }, + { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616 }, + { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954 }, + { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575 }, + { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061 }, + { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142 }, + { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894 }, + { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378 }, + { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069 }, + { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249 }, + { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710 }, + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811 }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078 }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748 }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595 }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616 }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324 }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676 }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614 }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766 }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615 }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982 }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792 }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049 }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774 }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252 }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198 }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346 }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826 }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217 }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700 }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644 }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452 }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378 }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261 }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987 }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361 }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460 }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486 }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219 }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693 }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803 }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709 }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591 }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003 }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542 }, +] diff --git a/iridescent-ivies/.github/workflows/lint.yaml b/iridescent-ivies/.github/workflows/lint.yaml new file mode 100644 index 00000000..7f67e803 --- /dev/null +++ b/iridescent-ivies/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/iridescent-ivies/.gitignore b/iridescent-ivies/.gitignore new file mode 100644 index 00000000..08ef4ac4 --- /dev/null +++ b/iridescent-ivies/.gitignore @@ -0,0 +1,32 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ +uv.lock +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store +src/sql_bsky.egg-info/* diff --git a/iridescent-ivies/.pre-commit-config.yaml b/iridescent-ivies/.pre-commit-config.yaml new file mode 100644 index 00000000..de7f5fd9 --- /dev/null +++ b/iridescent-ivies/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + - id: ruff-format + + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.8.8 + hooks: + - id: pip-compile + args: [ + "--universal", + "--python-version=3.12", + "pyproject.toml", + "--group=dev", + "-o", + "requirements.txt" + ] diff --git a/iridescent-ivies/LICENSE.txt b/iridescent-ivies/LICENSE.txt new file mode 100644 index 00000000..2f024be1 --- /dev/null +++ b/iridescent-ivies/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Iridescent Ivies + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/iridescent-ivies/README.md b/iridescent-ivies/README.md new file mode 100644 index 00000000..97fa38d2 --- /dev/null +++ b/iridescent-ivies/README.md @@ -0,0 +1,131 @@ +## The Social Query Language (SQL-BSky) + +[![Python](https://img.shields.io/badge/Python-3.12+-blue.svg)](https://python.org) +[![BlueSky](https://img.shields.io/badge/BlueSky-AT_Protocol-00D4FF.svg)](https://bsky.app) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.txt) +[![Status](https://img.shields.io/badge/Status-Active-brightgreen.svg)]() + +A retro terminal-style SQL interface for querying the BlueSky social network. Experience social media through the lens of structured query language with authentic CRT visual effects. + +![App Initialization](assets/Init_sql_app.gif) + +## Features + +- **Dual Authentication**: Full BlueSky login or anonymous "stealth mode" +- **Public API Access**: Query public content without authentication +- **ASCII Art Images**: View embedded images as beautiful ASCII art +- **Real-time Validation**: Live SQL syntax checking as you type +- **Retro CRT Interface**: Authentic 1980s terminal experience with visual effects +- **Fast Performance**: Optimized queries with scrolling support +- **Easter Eggs**: Hidden surprises for the adventurous + +## Quick Start + +### Installation + +1. Clone the repository: + ```bash + git clone git@github.com:A5rocks/code-jam-12.git + + # move to the dir + cd code-jam-12 + ``` +2. Start the development server: + ```bash + python3 dev.py + ``` + +3. That's it! Open your browser to: [http://localhost:8000](http://localhost:8000) + +### First Steps + +1. **Choose Authentication Mode**: + - **Authenticated**: Login with BlueSky credentials for full access + - **Stealth Mode**: Browse public content anonymously + +> [!NOTE] +> If the page is slow, try disabling the CRT effect at this point. + +2. **Try Your First Query**: + ```sql + SELECT * FROM tables + ``` + + ![Running Test Query](assets/run_test_query.gif) + +3. **Explore Public Profiles**: + ```sql + SELECT * FROM profile WHERE actor = 'bsky.app' + ``` + +## Query Reference + +### Available Tables + +| Table | Description | Auth Required | Parameters | +|-------|-------------|---------------|------------| +| `tables` | List all available tables | No | None | +| `profile` | User profile information | No | `actor` (optional) | +| `feed` | Posts from a specific user | No | `author` (required) | +| `timeline` | Your personal timeline | Yes | None | +| `suggestions` | Suggested users to follow | No | None | +| `suggested_feed` | Recommended feeds | No | None | +| `followers` | User's followers | No | `actor` (required) | +| `following` | Who user follows | No | `actor` (required) | +| `mutuals` | Mutual connections | No | `actor` (required) | +| `likes` | User's liked posts | Yes | `actor` (required) | + +### Example Queries + +```sql +SELECT * FROM feed WHERE author='bsky.app' +``` +- This will get all fields from all posts from the author's feed + +```sql +SELECT description FROM followers WHERE author='bsky.app' +``` +- This will get the bio of all followers of the author + +```sql +SELECT * FROM tables +``` +- This will get all available table names + +## Known Issues + +> [!WARNING] +> Please be aware of these current limitations before using the application. + +> [!NOTE] +> Queries to non-existent tables or fields will return empty rows instead of proper error messages. + +**Example:** +```sql +-- Both of these return empty rows (same behavior) +SELECT likes FROM feed WHERE author = "bsky.app" +SELECT apples FROM feed WHERE author = "bsky.app" +``` + +### KeyError in Feed Processing +> [!IMPORTANT] +> There's a known KeyError where the system looks for `"feeds"` but should be looking for `"feed"`. This is a human error we discovered after the Code Jam programming time had ended, so we weren't able to fix it, but we're aware of the issue and it may cause some like-table-related queries to fail unexpectedly. + +##### Table `likes` Not Functional +> [!CAUTION] +> The `likes` table is currently broken and behaves like a non-existent table. This is due to the KeyError +## Team - Iridescent Ivies + +- **A5rocks** - [GitHub](https://github.com/A5rocks) (Team Leader) +- **TheHeretic** - [GitHub](https://github.com/DannyTheHeretic) +- **Walkercito** - [GitHub](https://github.com/Walkercito) +- **Joshdtbx** - [GitHub](https://github.com/giplgwm) +- **Mimic** - [GitHub](https://github.com/Drakariboo) + +## License + +This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details. + +--- + +**Thank you for exploring our project!!** diff --git a/iridescent-ivies/assets/Init_sql_app.gif b/iridescent-ivies/assets/Init_sql_app.gif new file mode 100644 index 00000000..f0c6634d Binary files /dev/null and b/iridescent-ivies/assets/Init_sql_app.gif differ diff --git a/iridescent-ivies/assets/run_test_query.gif b/iridescent-ivies/assets/run_test_query.gif new file mode 100644 index 00000000..a1adab67 Binary files /dev/null and b/iridescent-ivies/assets/run_test_query.gif differ diff --git a/iridescent-ivies/dev.py b/iridescent-ivies/dev.py new file mode 100644 index 00000000..95f5e3e4 --- /dev/null +++ b/iridescent-ivies/dev.py @@ -0,0 +1,29 @@ +import http.server +import os +import socketserver +import sys +from pathlib import Path + +# use src as start point +src_dir = Path(__file__).parent / "src" +if src_dir.exists(): + os.chdir(src_dir) + print(f"[*] Serving from: {src_dir.absolute()}") +else: + print("[-] src/ dir not found") + sys.exit(1) + +PORT = 8000 +Handler = http.server.SimpleHTTPRequestHandler + +try: + with socketserver.TCPServer(("", PORT), Handler) as httpd: + print(f"[*] Server running at: http://localhost:{PORT}") + print(f"[*] Open: http://localhost:{PORT}/") + print("[-] Press Ctrl+C to stop") + httpd.serve_forever() +except KeyboardInterrupt: + print("\nServer stopped") +except OSError as e: + print(f"[-] Error: {e}") + print("[-] Try a different port: python dev.py --port 8001") diff --git a/iridescent-ivies/pyproject.toml b/iridescent-ivies/pyproject.toml new file mode 100644 index 00000000..f4c1c29c --- /dev/null +++ b/iridescent-ivies/pyproject.toml @@ -0,0 +1,54 @@ +[project] +name = "sql-bsky" +description = "Social query language" +version = "0.1.0" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "ascii-magic>=2.3.0", +] + +[dependency-groups] +dev = [ + "pre-commit~=4.2.0", + "ruff~=0.12.2", + "pytest", +] + +[tool.ruff] +line-length = 119 +target-version = "py312" +fix = true +src = ["src"] + +[tool.ruff.lint] +# Enable all linting rules. +select = ["ALL"] +# Ignore some of the most obnoxious linting errors. +ignore = [ + # Missing docstrings. + "D100", + "D104", + "D105", + "D106", + "D107", + # Docstring whitespace. + "D203", + "D213", + # Docstring punctuation. + "D415", + # Docstring quotes. + "D301", + # Builtins. + "A", + # Print statements. + "T20", + # TODOs. + "TD002", + "TD003", + "FIX", + # Conflicts with ruff format. + "COM812", + # Asserts are good, actually. + "S101", +] diff --git a/iridescent-ivies/requirements.txt b/iridescent-ivies/requirements.txt new file mode 100644 index 00000000..3721e7c9 --- /dev/null +++ b/iridescent-ivies/requirements.txt @@ -0,0 +1,40 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --universal --python-version=3.12 pyproject.toml --group=dev -o requirements.txt +ascii-magic==2.3.0 + # via sql-bsky (pyproject.toml) +cfgv==3.4.0 + # via pre-commit +colorama==0.4.6 + # via + # ascii-magic + # pytest +distlib==0.4.0 + # via virtualenv +filelock==3.18.0 + # via virtualenv +identify==2.6.12 + # via pre-commit +iniconfig==2.1.0 + # via pytest +nodeenv==1.9.1 + # via pre-commit +packaging==25.0 + # via pytest +pillow==11.3.0 + # via ascii-magic +platformdirs==4.3.8 + # via virtualenv +pluggy==1.6.0 + # via pytest +pre-commit==4.2.0 + # via sql-bsky (pyproject.toml:dev) +pygments==2.19.2 + # via pytest +pytest==8.4.1 + # via sql-bsky (pyproject.toml:dev) +pyyaml==6.0.2 + # via pre-commit +ruff==0.12.8 + # via sql-bsky (pyproject.toml:dev) +virtualenv==20.33.1 + # via pre-commit diff --git a/iridescent-ivies/src/__init__.py b/iridescent-ivies/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/iridescent-ivies/src/api/__init__.py b/iridescent-ivies/src/api/__init__.py new file mode 100644 index 00000000..4b9a314e --- /dev/null +++ b/iridescent-ivies/src/api/__init__.py @@ -0,0 +1 @@ +# The main portion of fetching and recieving from Bsky diff --git a/iridescent-ivies/src/api/auth_session.py b/iridescent-ivies/src/api/auth_session.py new file mode 100644 index 00000000..e5f6d9e5 --- /dev/null +++ b/iridescent-ivies/src/api/auth_session.py @@ -0,0 +1,276 @@ +# Imports +import json +from typing import Literal + +from pyodide.http import FetchResponse, pyfetch # The system we will actually use + +LIMIT = 50 # The default limit amount + + +class PyfetchSession: + """Pyfetch Session, emulating the request Session.""" + + def __init__(self, headers: dict | None = None) -> None: + """Pyfetch Session, emulating the request Session.""" + self.default_headers = headers or {} + + async def get(self, url: str, headers: dict | None = None) -> FetchResponse: + """Get request for the pyfetch. + + Args: + url (str): The Endpoint to hit + headers (dict | None, optional): Any headers that will get added to the request. Defaults to "". + + Returns: + FetchResponse: The return data from the request + + """ + merged_headers = self.default_headers.copy() + if headers: + merged_headers.update(headers) + return await pyfetch( + url, + method="GET", + headers=merged_headers, + ) + + async def post( + self, + url: str, + data: str | dict | None = "", + headers: dict | None = None, + ) -> FetchResponse: + """Post request. + + Args: + url (str): The Endpoint to hit + data (str | dict | None, optional): A dictionary or string to use for the body. Defaults to "". + headers (dict | None, optional): Any headers that will get added to the request. Defaults to "". + + Returns: + FetchResponse: The return data from the request + + """ + merged_headers = self.default_headers.copy() + if headers: + merged_headers.update(headers) + return await pyfetch( + url, + method="POST", + headers=merged_headers, + body=json.dumps(data) if isinstance(data, dict) else data, + ) + + +class BskySession: + """Class to establish an auth session.""" + + def __init__(self, username: str, password: str) -> None: + # Bluesky credentials + self.username = username + self.password = password + self.pds_host = "https://public.api.bsky.app" + # Instance client + self.client = PyfetchSession() + # Access token + self.access_jwt = None + # Refresh token + self.refresh_jwt = None + + async def login(self) -> None: + """Create an authenticated session and save tokens.""" + endpoint: str = "https://bsky.social/xrpc/com.atproto.server.createSession" + session_info: FetchResponse = await self.client.post( + endpoint, + headers={"Content-Type": "application/json"}, + data={ + "identifier": self.username, + "password": self.password, + }, + ) + session_info: dict = await session_info.json() + try: + self.access_jwt: str = session_info["accessJwt"] + self.refresh_jwt: str = session_info["refreshJwt"] + self.did: str = session_info["did"] + self.handle: str = session_info["handle"] + self.pds_host = "https://bsky.social" + self.client.default_headers.update( + { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.access_jwt}", + }, + ) + except KeyError: + # TODO: Handle the error on the front end + return False + else: + return True + + async def refresh_token(self) -> None: + """Refresh the token.""" + endpoint = f"{self.pds_host}/xrpc/com.atproto.server.refreshSession" + + session_info = await self.client.post( + endpoint, data="", headers={"Authorization": f"Bearer {self.refresh_jwt}"} + ) + session_info = await session_info.json() + self.access_jwt = session_info["accessJwt"] + self.refresh_jwt = session_info["refreshJwt"] + self.did = session_info["did"] + + self.client.default_headers.update( + { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.access_jwt}", + }, + ) + + ### Start of the actual endpoints -> https://docs.bsky.app/docs/api/at-protocol-xrpc-api + async def get_preferences(self) -> dict: + """Get the logged in users preferences.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.actor.getPreferences" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_profile(self, actor: str) -> dict: + """Get a user profile.""" + # If no actor specified and we're authenticated, use our handle + if actor is None: + if hasattr(self, "handle") and self.handle: + actor = self.handle + else: + # Return special error object for stealth mode + return {"stealth_error": True} + + endpoint = f"{self.pds_host}/xrpc/app.bsky.actor.getProfile?actor={actor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_suggestions(self, limit: int = LIMIT, cursor: str = "") -> dict: + """Get the logged in users suggestion.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.actor.getSuggestions?limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def search_actors(self, q: str, limit: int = LIMIT, cursor: str = "") -> dict: + """Search for actors.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.actor.searchActors?q={q}&limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_actor_likes(self, actor: str, limit: int = LIMIT, cursor: str = "") -> dict: # Requires Auth + """Get a given actors likes.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.feed.getActorLikes?actor={actor}&limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_author_feed(self, actor: str, limit: int = LIMIT) -> dict: + """Get a specific user feed.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.feed.getAuthorFeed?actor={actor}&limit={limit}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_feed(self, feed: str, limit: int = LIMIT, cursor: str = "") -> dict: + """Get a specified feed.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.feed.getFeed?feed={feed}&limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_suggested_feeds(self, limit: int = LIMIT, cursor: str = "") -> dict: + """Get suggested feeds.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.feed.getSuggestedFeeds?limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_timeline(self) -> dict: + """Get a users timeline.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.feed.getTimeline" + response = await self.client.get( + endpoint, + ) + return await response.json() + + # Only function that needs this many params, I am not making a data class for it + async def search_posts( # noqa: PLR0913 + self, + q: str, + limit: int = LIMIT, + sort: Literal["top", "latest"] = "latest", + since: str = "", + until: str = "", + mentions: str = "", + author: str = "", + tag: str = "", + cursor: str = "", + ) -> dict: + """Search for bluesky posts. + + Args: + q (str): the given query + sort (Literal["top", "latest"], optional): The sort Order. Defaults to "latest". + since (str, optional): Since when in YYYY-MM-DD format. Defaults to "". + until (str, optional): Until when in YYYY-MM-DD format. Defaults to "". + mentions (str, optional): Post mentions the given account. Defaults to "". + author (str, optional): Author of a given post. Defaults to "". + tag (str, optional): Tags on the post. Defaults to "". + limit (int, optional): Limit the number returned. Defaults to LIMIT. + cursor (str, optional): Bsky Cursor. Defaults to "". + + """ + endpoint = ( + f"{self.pds_host}/xrpc/app.bsky.feed.searchPosts" + f"?q={q}&sort={sort}&since={since}&until={until}" + f"&mentions={mentions}&author={author}&tag={tag}" + f"&limit={limit}&cursor={cursor}" + ) + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_followers(self, actor: str, limit: int = LIMIT, cursor: str = "") -> dict: + """Get a users followers.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.graph.getFollowers?actor={actor}&limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_follows(self, actor: str, limit: int = LIMIT, cursor: str = "") -> dict: + """Get a users follows.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.graph.getFollows?actor={actor}&limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_mutual_follows(self, actor: str, limit: int = LIMIT, cursor: str = "") -> dict: + """Get a users mutual follows.""" + endpoint = f"{self.pds_host}/xrpc/app.bsky.graph.getKnownFollowers?actor={actor}&limit={limit}&cursor={cursor}" + response = await self.client.get( + endpoint, + ) + return await response.json() + + async def get_blob(self, url: str) -> str: + """Get a specific blob.""" + did, cid = url.split("/")[-2:] + cid = cid.split("@")[0] + return f"https://bsky.social/xrpc/com.atproto.sync.getBlob?did={did}&cid={cid}" diff --git a/iridescent-ivies/src/core/__init__.py b/iridescent-ivies/src/core/__init__.py new file mode 100644 index 00000000..446c6f0e --- /dev/null +++ b/iridescent-ivies/src/core/__init__.py @@ -0,0 +1 @@ +# The core of the Social Query Language diff --git a/iridescent-ivies/src/core/functions.py b/iridescent-ivies/src/core/functions.py new file mode 100644 index 00000000..c4a31046 --- /dev/null +++ b/iridescent-ivies/src/core/functions.py @@ -0,0 +1,374 @@ +"""The main script file for Pyodide.""" + +import frontend +from frontend import CLEAR_BUTTON, EXECUTE_BUTTON, QUERY_INPUT, clear_interface, update_table +from js import Event, document, window +from parser import Parent, ParentKind, Token, TokenKind, Tree, parse, tokenize +from pyodide.ffi import create_proxy +from pyodide.ffi.wrappers import set_timeout + + +def flatten_response(data: dict) -> dict: + """Flatten a dictionary.""" + flattened_result = {} + + def _flatten(current: dict, name: str = "") -> dict: + if isinstance(current, dict): + for field, value in current.items(): + _flatten(value, name + field + "_") + elif isinstance(current, list): + """old code + # for idx, i in enumerate(current): + # _flatten(i, name + str(idx) + "_") + """ + else: + flattened_result[name[:-1].lower()] = current # Drops the extra _ + + _flatten(data) + return flattened_result + + +def blue_screen_of_death() -> None: + """Easter Egg: Show WinXP Blue Screen of Death.""" + input_field = document.getElementById("query-input") + if input_field: + input_field.value = "" + + bsod = document.createElement("div") + bsod.className = "bsod-overlay" + bsod.innerHTML = ( + '
' + '
A problem has been detected and Windows has been shut down to prevent damage ' + " to your computer.
" + '
IRQL_NOT_LESS_OR_EQUAL
' + '
' + " If this is the first time you've seen this stop error screen, " + " restart your computer. If this screen appears again, follow these steps:

" + " Check to make sure any new hardware or software is properly installed. " + " If this is a new installation, ask your hardware or software manufacturer " + " for any Windows updates you might need.

" + " If problems continue, disable or remove any newly installed hardware or software. " + " Disable BIOS memory options such as caching or shadowing. If you need to use " + " Safe Mode to remove or disable components, restart your computer, press F8 " + " to select Advanced Startup Options, and then select Safe Mode." + "
" + '
' + " Technical information:

" + " *** STOP: 0x0000000A (0xFE520004, 0x00000001, 0x00000001, 0x804F9319)

" + " *** Address 804F9319 base at 804D7000, DateStamp 3844d96e - ntoskrnl.exe

" + " Beginning dump of physical memory
" + " Physical memory dump complete.
" + " Contact your system administrator or technical support group for further assistance." + "
" + "
" + ) + + document.body.appendChild(bsod) + frontend.flash_screen("#0000ff", 100) + + def remove_bsod() -> None: + if bsod.parentNode: + document.body.removeChild(bsod) + frontend.update_status("System recovered from critical error", "warning") + frontend.trigger_electric_wave() + + set_timeout(create_proxy(remove_bsod), 4000) + + +def clean_value(text: str) -> str: + """Remove surrounding single/double quotes if present.""" + if isinstance(text, str) and (text[0] == text[-1]) and text[0] in ("'", '"'): + return text[1:-1] + return text + + +def get_text(node: Tree) -> str: + """Recursively get the string value from a node (Parent or Token).""" + if hasattr(node, "text"): + return node.text + if hasattr(node, "children"): + return " ".join(get_text(child) for child in node.children) + return str(node) + + +def walk_where(node: Tree) -> list[tuple | str]: + """Flatten sql expressions into [tuple, 'AND', tuple, ...].""" + if getattr(node, "kind", None).name == "EXPR_BINARY": + left, op, right = node.children + op_text = getattr(op, "text", None) + + if op_text in ("AND", "OR"): + return [*walk_where(left), op_text, *walk_where(right)] + + return [(clean_value(get_text(left)), op_text, clean_value(get_text(right)))] + + if hasattr(node, "children"): + result = [] + for child in node.children: + result.extend(walk_where(child)) + return result + + return [] + + +def get_limit(node: Tree) -> int | None: + """Get what the LIMIT clause of this SQL query contains.""" + assert node.kind is ParentKind.FILE + stmt = node.children[0] + for it in stmt.children: + if it.kind is ParentKind.LIMIT_CLAUSE: + return int(it.children[1].text) + + return None + + +def extract_where(tree: Tree) -> tuple[str, str] | None: + """Extract the where clause from the tree.""" + if not tree.kind == ParentKind.FILE: + raise ValueError + stmt = tree.children[0] + for c in stmt.children: + if c.kind == ParentKind.WHERE_CLAUSE: + return walk_where(c.children[1]) + return [] + + +def extract_fields(tree: Tree) -> list[Token] | None: + """Extract the fields from the tree.""" + if not tree.kind == ParentKind.FILE: + raise ValueError + stmt = tree.children[0] + for c in stmt.children: + if c.kind == ParentKind.FIELD_LIST: + return c.children[::2] + return [] + + +def extract_table(tree: Tree) -> str: + """Extract the Table from the tree.""" + if tree.kind != ParentKind.FILE: + raise ValueError + + stmt = tree.children[0] # SELECT_STMT + for c in stmt.children: + if c.kind == ParentKind.FROM_CLAUSE: + for child in c.children: + if child.kind == TokenKind.IDENTIFIER: + return child.text + break + return "" + + +async def parse_input(_: Event) -> None: + """Start of the parser.""" + query = QUERY_INPUT.value.strip() + + clean_query = query.upper().replace(";", "").replace(",", "").strip() + if "DROP TABLE USERS" in clean_query: + frontend.update_status("what could go wrong?", "warning") + blue_screen_of_death() + return + + tree = parse(tokenize(query)) + if not check_query(tree): + return + + await sql_to_api_handler(tree) + + +async def processor(api: tuple[str, str], table: str, limit: int | None) -> dict: # noqa: C901, PLR0912, PLR0915 + """Process the sql statements into a api call.""" + val = {} + if table == "feed": + if api[0] in ["actor", "author"]: + feed = await window.session.get_author_feed(api[2], limit=limit) + val = feed["feed"] + elif api[0] == "feed": + feed = await window.session.get_feed(api[2], limit=limit) + val = feed["feed"] + + elif table == "timeline": + feed = await window.session.get_timeline() + val = feed["feed"] + elif table == "profile": + if api[0] in ["actor", "author"]: + feed = await window.session.get_profile(api[2]) + val = feed + else: + feed = await window.session.get_profile(None) + if isinstance(feed, dict) and feed.get("stealth_error"): + return "stealth_error" + val = feed + elif table == "suggestions": + feed = await window.session.get_suggestions(limit=limit) + val = feed["actors"] + elif table == "suggested_feed": + feed = await window.session.get_suggested_feeds(limit=limit) + val = feed["feeds"] + elif table == "likes": + if api[0] in ["actor", "author"]: + feed = await window.session.get_actor_likes(api[2], limit=limit) + val = feed["feeds"] + else: + pass + elif table == "followers": + if api[0] in ["actor", "author"]: + feed = await window.session.get_followers(api[2], limit=limit) + val = feed["followers"] + else: + pass + elif table == "following": + if api[0] in ["actor", "author"]: + feed = await window.session.get_follows(api[2], limit=limit) + val = feed["followers"] + else: + pass + elif table == "mutuals": + if api[0] in ["actor", "author"]: + feed = await window.session.get_mutual_follows(api[2], limit=limit) + val = feed["followers"] + else: + pass + elif table == "tables": + val = [ + {"Table_Name": _} + for _ in [ + "feed", + "timeline", + "profile", + "suggestions", + "suggested_feed", + "likes", + "followers", + "following", + "mutuals", + ] + ] + return val + + +def _extract_images_from_post(data: dict) -> str: + """Extract any embedded images from a post and return them as a delimited string.""" + if not isinstance(data, dict): + return "" + + if "post" not in data: + return "" + + post = data["post"] + + # Check if the post has embedded content + if "embed" not in post: + return "" + + embed_type = post["embed"].get("$type", "") + + # Only process image embeds + if embed_type != "app.bsky.embed.images#view": + return "" + + images = post["embed"].get("images", []) + if not images: + return "" + + image_links = [] + for image in images: + image_link = f"{image['thumb']},{image['fullsize']},{image['alt']}" + image_links.append(image_link) + + return " | ".join(image_links) + + +async def sql_to_api_handler(tree: Tree) -> dict: + """Handle going from SQL to the API.""" + where_expr = extract_where(tree) + table = extract_table(tree) + fields = extract_fields(tree) + field_tokens = [i.children[0] for i in fields if i.kind != TokenKind.STAR] + + for i in where_expr: + if i[0] in ["actor", "author", "feed"]: + api = i + break + else: + # No Where Expression Matches + api = ["", ""] + + limit = get_limit(tree) + val = await processor(api, table, limit if limit is not None else 50) + if not val: + frontend.show_empty_table() + frontend.update_status(f"Error getting from {table}. Try: SELECT * FROM tables", "error") # noqa: S608 Not sql injection + frontend.trigger_electric_wave() + return {} + + # Handle stealth mode error for profile queries + if val == "stealth_error": + frontend.show_empty_table() + frontend.update_status( + "Cannot get own profile in stealth mode. Try: SELECT * FROM profile WHERE actors = 'username.bsky.social'", + "warning", + ) + frontend.trigger_electric_wave() + return {} + + if isinstance(val, dict): + val = [val] + + tb = document.getElementById("table-body") + tb.innerHTML = "" + head = [] + if field_tokens: + head = [j.text for j in field_tokens] + body = [] + + for i in val: + data = i + + # Only try to extract images if the data structure supports it + images = _extract_images_from_post(data) + if images and "post" in data: + data["post"]["images"] = images + + d = flatten_response(data) + + if field_tokens: + body.append({j: d.get(j.lower(), "") for j in head}) + else: + body.append(d) + [head.append(k) for k in d if k not in head] + + update_table(head, body) + frontend.update_status(f"Data successfully retrieved from {table}", "success") + return val + + +async def check_query_input(_: Event) -> None: + """Check the query that is currently input.""" + check_query(parse(tokenize(QUERY_INPUT.value.strip()))) + + +def check_query(tree: Tree) -> bool: + """Check a given query and update the status bar.""" + errors = [] + _check_query(tree, errors) + if errors: + frontend.update_status("\n".join(errors), "error") + return False + frontend.update_status("Query is OK", "success") + return True + + +def _check_query(tree: Tree, errors: list[str]) -> None: + """Check a given query recursively.""" + errors.extend([f"- {error}" for error in tree.errors]) + if isinstance(tree, Parent): + for child in tree.children: + _check_query(child, errors) + if tree.kind is ParentKind.ERROR_TREE: + errors.append("- large error") + + +EXECUTE_BUTTON.addEventListener("click", create_proxy(parse_input)) +CLEAR_BUTTON.addEventListener("click", create_proxy(clear_interface)) +QUERY_INPUT.addEventListener("keydown", create_proxy(check_query_input)) diff --git a/iridescent-ivies/src/core/parser.py b/iridescent-ivies/src/core/parser.py new file mode 100644 index 00000000..3599b690 --- /dev/null +++ b/iridescent-ivies/src/core/parser.py @@ -0,0 +1,559 @@ +from __future__ import annotations + +import string +import textwrap +from dataclasses import dataclass, field +from enum import Enum, auto +from typing import Literal + + +# tokenizer: +@dataclass +class Token: + """A token produced by tokenization.""" + + kind: TokenKind + text: str + start_pos: int + end_pos: int + errors: list[str] = field(default_factory=list) + + +class TokenKind(Enum): + """What the token represents.""" + + # keywords + SELECT = auto() + FROM = auto() + WHERE = auto() + LIMIT = auto() + + # literals + STRING = auto() + INTEGER = auto() + IDENTIFIER = auto() + STAR = auto() + + # operators + EQUALS = auto() + AND = auto() + GT = auto() + LT = auto() + + # structure + COMMA = auto() + ERROR = auto() + EOF = auto() # this is a fake token only made and used in the parser + + +KEYWORDS = { + "SELECT": TokenKind.SELECT, + "FROM": TokenKind.FROM, + "WHERE": TokenKind.WHERE, + "AND": TokenKind.AND, + "LIMIT": TokenKind.LIMIT, +} + + +@dataclass +class Cursor: + """Helper class to allow peeking into a stream of characters.""" + + contents: str + index: int = 0 + + def peek(self) -> str: + """Look one character ahead in the stream.""" + return self.contents[self.index : self.index + 1] + + def next(self) -> str: + """Get the next character in the stream.""" + c = self.peek() + if c != "": + self.index += 1 + return c + + +def tokenize(query: str) -> list[Token]: # noqa: PLR0912, C901 + """Turn a query into a list of tokens.""" + result = [] + + cursor = Cursor(query) + while True: + idx = cursor.index + char = cursor.next() + + if char == "": + break + + if char in string.ascii_letters: + char = cursor.peek() + + while char in string.ascii_letters + "._": + cursor.next() + char = cursor.peek() + if char == "": + break + + identifier = cursor.contents[idx : cursor.index] + kind = KEYWORDS.get(identifier, TokenKind.IDENTIFIER) + result.append(Token(kind, identifier, idx, cursor.index)) + + elif char in string.digits: + char = cursor.peek() + + while char in string.digits: + cursor.next() + char = cursor.peek() + if char == "": + break + + result.append(Token(TokenKind.INTEGER, cursor.contents[idx : cursor.index], idx, cursor.index)) + + elif char == ",": + result.append(Token(TokenKind.COMMA, ",", idx, cursor.index)) + + elif char == "*": + result.append(Token(TokenKind.STAR, "*", idx, cursor.index)) + + elif char == "'": + # idk escaping rules in SQL lol + char = cursor.peek() + while char != "'": + cursor.next() + char = cursor.peek() + if char == "": + break + + cursor.next() # get the last ' + + string_result = cursor.contents[idx : cursor.index] + kind = TokenKind.STRING if string_result.endswith("'") and len(string_result) > 1 else TokenKind.ERROR + result.append(Token(kind, string_result, idx, cursor.index)) + + elif char == "=": + result.append(Token(TokenKind.EQUALS, "=", idx, cursor.index)) + + elif char == ">": + # TODO: gte? + result.append(Token(TokenKind.GT, ">", idx, cursor.index)) + + elif char == "<": + result.append(Token(TokenKind.LT, "<", idx, cursor.index)) + + return result + + +# parser +# heavily inspired by https://matklad.github.io/2023/05/21/resilient-ll-parsing-tutorial.html +@dataclass +class Parser: + """Helper class that provides useful parser functionality.""" + + contents: list[Token] + events: list[Event] = field(default_factory=list) + index: int = 0 + unreported_errors: list[str] = field(default_factory=list) + + def eof(self) -> bool: + """Check whether the token stream is done.""" + return self.index == len(self.contents) + + def peek(self) -> TokenKind: + """Look at the next kind of token in the stream.""" + if self.eof(): + return TokenKind.EOF + return self.contents[self.index].kind + + def advance(self) -> None: + """Move to the next token in the stream.""" + self.index += 1 + self.events.append("ADVANCE") + + def advance_with_error(self, error: str) -> None: + """Mark the current token as being wrong.""" + if self.eof(): + # this should probably be done better... + self.unreported_errors.append(error) + else: + self.contents[self.index].errors.append(error) + self.advance() + + def open(self) -> int: + """Start nesting children.""" + result = len(self.events) + self.events.append(("OPEN", ParentKind.ERROR_TREE)) + return result + + def open_before(self, index: int) -> int: + """Start nesting children before a given point.""" + self.events.insert(index, ("OPEN", ParentKind.ERROR_TREE)) + return index + + def close(self, kind: ParentKind, where: int) -> int: + """Stop nesting children and note the tree type.""" + self.events[where] = ("OPEN", kind) + self.events.append("CLOSE") + return where + + def expect(self, kind: TokenKind, error: str) -> None: + """Ensure the next token is a specific kind and advance.""" + if self.at(kind): + self.advance() + else: + self.advance_with_error(error) + + def at(self, kind: TokenKind) -> None: + """Check if the next token is a specific kind.""" + return self.peek() == kind + + +@dataclass +class Parent: + """Syntax tree element with children.""" + + kind: ParentKind + children: list[Tree] + errors: list[str] = field(default_factory=list) + + +class ParentKind(Enum): + """Kinds of syntax tree elements that have children.""" + + SELECT_STMT = auto() + ERROR_TREE = auto() + FIELD_LIST = auto() + FROM_CLAUSE = auto() + WHERE_CLAUSE = auto() + LIMIT_CLAUSE = auto() + EXPR_NAME = auto() + EXPR_STRING = auto() + EXPR_INTEGER = auto() + EXPR_BINARY = auto() + FILE = auto() + + +Tree = Parent | Token +Event = Literal["ADVANCE", "CLOSE"] | tuple[Literal["OPEN"], ParentKind] + + +def turn_tokens_into_events(tokens: list[Token]) -> list[Event]: + """Parse a token stream into a list of events.""" + parser = Parser(tokens, []) + while not parser.eof(): + _parse_stmt(parser) + return parser.events, parser.unreported_errors + + +def parse(tokens: list[Token]) -> Tree: + """Parse a token stream into a syntax tree.""" + events, errors = turn_tokens_into_events(tokens) + stack = [("OPEN", ParentKind.FILE)] + events.append("CLOSE") + + i = 0 + for event in events: + if event == "ADVANCE": + stack.append(tokens[i]) + i += 1 + elif event == "CLOSE": + inner = [] + while True: + e = stack.pop() + if isinstance(e, tuple) and e[0] == "OPEN": + inner.reverse() + stack.append(Parent(e[1], inner)) + break + inner.append(e) + else: + assert isinstance(event, tuple) + assert event[0] == "OPEN" + stack.append(event) + + assert i == len(tokens) + assert len(stack) == 1 + result = stack[0] + assert isinstance(result, Tree) + assert result.kind == ParentKind.FILE + result.errors.extend(errors) + return result + + +# free parser functions +def _parse_stmt(parser: Parser) -> None: + # + _parse_select_stmt(parser) + + +def _parse_select_stmt(parser: Parser) -> None: + # 'SELECT' [ ',' ]* [ 'FROM' IDENTIFIER ] [ 'WHERE' ] + start = parser.open() + parser.expect(TokenKind.SELECT, "only SELECT is supported") + + fields_start = parser.open() + _parse_field(parser) + while parser.at(TokenKind.COMMA): + parser.advance() + _parse_field(parser) + parser.close(ParentKind.FIELD_LIST, fields_start) + + if parser.at(TokenKind.FROM): + # from clause + from_start = parser.open() + parser.advance() + + parser.expect(TokenKind.IDENTIFIER, "expected to select from a table") + parser.close(ParentKind.FROM_CLAUSE, from_start) + + if parser.at(TokenKind.WHERE): + # where clause + where_start = parser.open() + parser.advance() + + _parse_expr(parser) + parser.close(ParentKind.WHERE_CLAUSE, where_start) + + if parser.at(TokenKind.LIMIT): + limit_start = parser.open() + parser.advance() + parser.expect(TokenKind.INTEGER, "expected an integer") + parser.close(ParentKind.LIMIT_CLAUSE, limit_start) + + parser.close(ParentKind.SELECT_STMT, start) + + +def _parse_field(parser: Parser) -> None: + # '*' | + if parser.at(TokenKind.STAR): + parser.advance() + else: + _parse_expr(parser) + + +def _parse_expr(parser: Parser) -> None: + # | = + _parse_expr_inner(parser, TokenKind.EOF) + + +def _parse_expr_inner(parser: Parser, left_op: TokenKind) -> None: + left = _parse_small_expr(parser) + + while True: + right_op = parser.peek() + if right_goes_first(left_op, right_op): + # if we have A B C ..., + # then we need to parse (A (B C ...)) + outer = parser.open_before(left) + parser.advance() + _parse_expr_inner(parser, right_op) # (B C ...) + parser.close(ParentKind.EXPR_BINARY, outer) + else: + # (A B) C will be handled + # (if this were toplevel, right_goes_first will happen) + break + + +def _parse_small_expr(parser: Parser) -> int: + # IDENTIFIER + # TODO: it looks like this parser.open() is unnecessary + start = parser.open() + if parser.at(TokenKind.IDENTIFIER): + parser.advance() + return parser.close(ParentKind.EXPR_NAME, start) + if parser.at(TokenKind.STRING): + parser.advance() + return parser.close(ParentKind.EXPR_STRING, start) + if parser.at(TokenKind.INTEGER): + parser.advance() + return parser.close(ParentKind.EXPR_INTEGER, start) + parser.advance_with_error("expected expression") + return parser.close(ParentKind.ERROR_TREE, start) + + +TABLE = [[TokenKind.AND], [TokenKind.EQUALS, TokenKind.GT, TokenKind.LT]] + + +def right_goes_first(left: TokenKind, right: TokenKind) -> bool: + """Understand which token type binds tighter. + + We say that A B C is equivalent to: + - A (B C) if we return True + - (A B) C if we return False + """ + left_idx = next((i for i, r in enumerate(TABLE) if left in r), None) + right_idx = next((i for i, r in enumerate(TABLE) if right in r), None) + + if right_idx is None: + # evaluate left-to-right + return False + if left_idx is None: + # well, maybe left doesn't exist? + assert left == TokenKind.EOF + return True + + return right_idx > left_idx + + +##### tests: (this should be moved to a proper tests folder) + + +def check_tok(before: str, after: TokenKind) -> None: + """Test helper which checks a string tokenizes to a single given token kind.""" + assert [tok.kind for tok in tokenize(before)] == [after] + + +def stringify_tokens(query: str) -> str: + """Test helper which turns a query into a repr of the tokens. + + Used for manual snapshot testing. + """ + tokens = tokenize(query) + result = "" + for i, c in enumerate(query): + for tok in tokens: + if tok.end_pos == i: + result += "<" + + for tok in tokens: + if tok.start_pos == i: + result += ">" + + result += c + + i += 1 + for tok in tokens: + if tok.end_pos == i: + result += "<" + + return result + + +def _stringify_tree(tree: Tree) -> list[str]: + result = [] + if isinstance(tree, Parent): + result.append(f"{tree.kind.name}") + result.extend(" " + line for child in tree.children for line in _stringify_tree(child)) + else: + repr = f'{tree.kind.name} ("{tree.text}")' + if tree.errors: + repr += " -- " + repr += " / ".join(tree.errors) + result.append(repr) + + return result + + +def stringify_tree(tree: Tree) -> str: + """Test helper that turns a syntax tree into a representation of it. + + Used for manual snapshot testing + """ + assert not tree.errors + return "\n".join(_stringify_tree(tree)) + + +def test_simple_tokens() -> None: + """Tests that various things tokenize correct in minimal cases.""" + assert [tok.kind for tok in tokenize("")] == [] + check_tok("SELECT", TokenKind.SELECT) + check_tok("FROM", TokenKind.FROM) + check_tok("WHERE", TokenKind.WHERE) + check_tok("AND", TokenKind.AND) + check_tok("'hello :)'", TokenKind.STRING) + check_tok("12345", TokenKind.INTEGER) + check_tok(",", TokenKind.COMMA) + check_tok("*", TokenKind.STAR) + check_tok("username", TokenKind.IDENTIFIER) + check_tok("username_b", TokenKind.IDENTIFIER) + + +def test_tokenize_simple_select() -> None: + """Tests that tokenization works in more general cases.""" + assert stringify_tokens("SELECT * FROM posts") == ">SELECT< >*< >FROM< >posts<" + + +def test_parse_simple() -> None: + """Tests that parsing works in some specific cases.""" + assert ( + stringify_tree(parse(tokenize("SELECT * FROM posts"))) + == textwrap.dedent(""" + FILE + SELECT_STMT + SELECT ("SELECT") + FIELD_LIST + STAR ("*") + FROM_CLAUSE + FROM ("FROM") + IDENTIFIER ("posts") + """).strip() + ) + + assert ( + stringify_tree(parse(tokenize("SELECT * WHERE actor = 'aaa'"))) + == textwrap.dedent(""" + FILE + SELECT_STMT + SELECT ("SELECT") + FIELD_LIST + STAR ("*") + WHERE_CLAUSE + WHERE ("WHERE") + EXPR_BINARY + EXPR_NAME + IDENTIFIER ("actor") + EQUALS ("=") + EXPR_STRING + STRING ("'aaa'") + """).strip() + ) + + assert ( + stringify_tree(parse(tokenize("SELECT 4 WHERE actor = 'a' AND likes > 10"))) + == textwrap.dedent(""" + FILE + SELECT_STMT + SELECT ("SELECT") + FIELD_LIST + EXPR_INTEGER + INTEGER ("4") + WHERE_CLAUSE + WHERE ("WHERE") + EXPR_BINARY + EXPR_BINARY + EXPR_NAME + IDENTIFIER ("actor") + EQUALS ("=") + EXPR_STRING + STRING ("'a'") + AND ("AND") + EXPR_BINARY + EXPR_NAME + IDENTIFIER ("likes") + GT (">") + EXPR_INTEGER + INTEGER ("10") + """).strip() + ) + + assert ( + stringify_tree(parse(tokenize("SELECT 4 LIMIT 0"))) + == textwrap.dedent(""" + FILE + SELECT_STMT + SELECT ("SELECT") + FIELD_LIST + EXPR_INTEGER + INTEGER ("4") + LIMIT_CLAUSE + LIMIT ("LIMIT") + INTEGER ("0") + """).strip() + ) + + +if __name__ == "__main__": + query = input("query> ") + print(stringify_tokens(query)) + + print() + print(stringify_tree(parse(tokenize(query)))) diff --git a/iridescent-ivies/src/core/setup.py b/iridescent-ivies/src/core/setup.py new file mode 100644 index 00000000..812eda31 --- /dev/null +++ b/iridescent-ivies/src/core/setup.py @@ -0,0 +1,34 @@ +"""The Setup Script for pyodide.""" + +from pathlib import Path + +import micropip +from pyodide.http import pyfetch + + +async def setup_pyodide_scripts() -> None: + """Script to do everything for pyodide.""" + response = await pyfetch("./core/functions.py") + with Path.open("functions.py", "wb") as f: + f.write(await response.bytes()) + + response = await pyfetch("./core/parser.py") + with Path.open("parser.py", "wb") as f: + f.write(await response.bytes()) + + response = await pyfetch("./ui/image_modal.py") + with Path.open("image_modal.py", "wb") as f: + f.write(await response.bytes()) + + response = await pyfetch("./ui/frontend.py") + with Path.open("frontend.py", "wb") as f: + f.write(await response.bytes()) + await micropip.install("ascii_magic") + + response = await pyfetch("./api/auth_session.py") + with Path.open("auth_session.py", "wb") as f: + f.write(await response.bytes()) + + response = await pyfetch("./ui/auth_modal.py") + with Path.open("auth_modal.py", "wb") as f: + f.write(await response.bytes()) diff --git a/iridescent-ivies/src/index.html b/iridescent-ivies/src/index.html new file mode 100644 index 00000000..6a06811a --- /dev/null +++ b/iridescent-ivies/src/index.html @@ -0,0 +1,310 @@ + + + + + + The Social Query Language v1.0 + + + + + + + +
+ +
+ +
+
+ The Social Query Language v1.0 + © 1987 Iridescent Ivies +
+ +
+
+ +
+
SQL COMMAND
+
+ +
+ + + +
+
+
+ + +
+
QUERY RESULTS
+ + +
+ + + + + + + + + + +
+ No data loaded. Execute a query to see results. +
+
+ + +
+
+ Rows: 0 | Status: Waiting +
+
+
+
+
+
+ + +
+
+
+
EXECUTING QUERY...
+
+
+ + +
+ +
+ + +
+ +
+ + + + + diff --git a/iridescent-ivies/src/ui/__init__.py b/iridescent-ivies/src/ui/__init__.py new file mode 100644 index 00000000..a3096e30 --- /dev/null +++ b/iridescent-ivies/src/ui/__init__.py @@ -0,0 +1 @@ +# The front end elements for SQL diff --git a/iridescent-ivies/src/ui/auth_modal.py b/iridescent-ivies/src/ui/auth_modal.py new file mode 100644 index 00000000..40ece8fb --- /dev/null +++ b/iridescent-ivies/src/ui/auth_modal.py @@ -0,0 +1,352 @@ +from js import Element, Event, document, window +from pyodide.ffi import create_proxy +from pyodide.ffi.wrappers import set_timeout + +try: + import frontend +except ImportError: + frontend = None +from auth_session import BskySession + +# dom +AUTH_MODAL = None +LOGIN_BTN = None +STEALTH_BTN = None +CRT_TOGGLE_BTN = None +USERNAME_INPUT = None +PASSWORD_INPUT = None +AUTH_FORM = None +STATUS_TEXT = None + +# authentication data +auth_data = None +is_modal_visible = False +crt_enabled = True # CRT starts enabled by default + + +def init_auth_modal() -> None: + """Initialize the authentication modal.""" + global AUTH_MODAL, LOGIN_BTN, STEALTH_BTN, USERNAME_INPUT, PASSWORD_INPUT, AUTH_FORM, STATUS_TEXT, CRT_TOGGLE_BTN # noqa: PLW0603 + + AUTH_MODAL = document.getElementById("auth-modal") + LOGIN_BTN = document.getElementById("login-btn") + STEALTH_BTN = document.getElementById("stealth-btn") + CRT_TOGGLE_BTN = document.getElementById("crt-toggle-btn") + USERNAME_INPUT = document.getElementById("bluesky-username") + PASSWORD_INPUT = document.getElementById("bluesky-password") + AUTH_FORM = document.getElementById("auth-form") + STATUS_TEXT = document.getElementById("security-notice") + + setup_event_listeners() + print("Auth modal initialized") + + +def setup_event_listeners() -> None: + """Configure event listeners for the modal.""" + USERNAME_INPUT.addEventListener("input", create_proxy(on_input_change)) + PASSWORD_INPUT.addEventListener("input", create_proxy(on_input_change)) + AUTH_FORM.addEventListener("submit", create_proxy(on_form_submit)) + STEALTH_BTN.addEventListener("click", create_proxy(on_stealth_click)) + + # Configure CRT toggle button event listener + if CRT_TOGGLE_BTN: + CRT_TOGGLE_BTN.addEventListener("click", create_proxy(on_crt_toggle_click)) + print("CRT toggle event listener attached") + else: + print("ERROR: CRT toggle button not found!") + + document.addEventListener("keydown", create_proxy(on_keydown)) + + +def on_input_change(event: Event) -> None: + """Trigger visual effects on input change.""" + target = event.target + target.style.borderColor = "#66ff66" + + def reset_border() -> None: + target.style.borderColor = "#00ff00" + + set_timeout(create_proxy(reset_border), 300) + + +def on_form_submit(event: Event) -> None: + """Handle form submission.""" + event.preventDefault() + handle_authentication() + + +def on_stealth_click(_event: Event) -> None: + """Handle click on stealth mode.""" + handle_stealth_mode() + + +def on_crt_toggle_click(_event: Event) -> None: + """Handle CRT effect toggle.""" + print("CRT toggle button clicked") + toggle_crt_effect() + + +def on_keydown(event: Event) -> None: + """Keyboard shortcuts - not visually indicated *yet?.""" + if not is_modal_visible: + return + + if event.key == "Escape": + handle_stealth_mode() + elif event.key == "Enter" and (event.ctrlKey or event.metaKey): + handle_authentication() + + +def toggle_crt_effect() -> None: + """Toggle the CRT effect on/off.""" + global crt_enabled # noqa: PLW0603 + + print(f"Toggle CRT called. Current state: {crt_enabled}") + + if not CRT_TOGGLE_BTN: + print("ERROR: CRT toggle button is None!") + return + + body = document.body + + if crt_enabled: + # Disable CRT effect + body.classList.remove("crt") + CRT_TOGGLE_BTN.innerHTML = "CRT EFFECT: OFF" + CRT_TOGGLE_BTN.style.background = "#333300" + CRT_TOGGLE_BTN.style.borderColor = "#ffff00" + CRT_TOGGLE_BTN.style.color = "#ffff00" + crt_enabled = False + print("CRT effect disabled") + else: + # Enable CRT effect + body.classList.add("crt") + CRT_TOGGLE_BTN.innerHTML = "CRT EFFECT: ON" + CRT_TOGGLE_BTN.style.background = "#003300" + CRT_TOGGLE_BTN.style.borderColor = "#00ff00" + CRT_TOGGLE_BTN.style.color = "#00ff00" + crt_enabled = True + print("CRT effect enabled") + + CRT_TOGGLE_BTN.style.transform = "scale(0.95)" + + def reset_scale() -> None: + CRT_TOGGLE_BTN.style.transform = "scale(1.0)" + + set_timeout(create_proxy(reset_scale), 150) + + +def show_modal() -> None: + """Show the modal.""" + global is_modal_visible # noqa: PLW0603 + + if AUTH_MODAL: + AUTH_MODAL.classList.add("show") + is_modal_visible = True + + def focus_username() -> None: + if USERNAME_INPUT: + USERNAME_INPUT.focus() + + set_timeout(create_proxy(focus_username), 500) + print("Auth modal shown") + + +def hide_modal() -> None: + """Hide the modal.""" + if AUTH_MODAL: + AUTH_MODAL.style.opacity = "0" + + def complete_hide() -> None: + global is_modal_visible # noqa: PLW0603 + AUTH_MODAL.classList.remove("show") + AUTH_MODAL.style.display = "none" + is_modal_visible = False + + set_timeout(create_proxy(complete_hide), 500) + print("Auth modal hidden") + + +def handle_authentication() -> None: + """Capture authentication data.""" + username = USERNAME_INPUT.value.strip() + password = PASSWORD_INPUT.value.strip() + + if not username or not password: + show_input_error() + return + + LOGIN_BTN.disabled = True + LOGIN_BTN.innerHTML = 'AUTHENTICATING' + + print(f"Capturing auth data for: {username}") + + async def complete_auth() -> None: + global auth_data # noqa: PLW0603 + + # catch and store authentication data + auth_data = {"username": username, "password": password, "mode": "authenticated"} + window.session = BskySession(username, password) + is_logged_in = await window.session.login() + if not is_logged_in: + handle_failed_auth() + return + LOGIN_BTN.innerHTML = "AUTHENTICATED ✓" + LOGIN_BTN.style.background = "#004400" + + def finish_auth() -> None: + hide_modal() + on_auth_complete(auth_data) + + set_timeout(create_proxy(finish_auth), 1000) + + set_timeout(create_proxy(complete_auth), 2000) + + +def handle_failed_auth() -> None: + """Handle a failed login.""" + LOGIN_BTN.disabled = False + LOGIN_BTN.innerHTML = "LOGIN" + USERNAME_INPUT.style.borderColor = "#ff0000" + PASSWORD_INPUT.style.borderColor = "#ff0000" + PASSWORD_INPUT.value = "" + STATUS_TEXT.innerText = "Incorrect Username or Password." + + def reset_status() -> None: + STATUS_TEXT.innerText = "Your credentials are processed locally and never stored permanently. \ + Stealth mode allows read-only access to public posts." + USERNAME_INPUT.style.borderColor = "#00ff00" + PASSWORD_INPUT.style.borderColor = "#00ff00" + + set_timeout(create_proxy(reset_status), 2000) + + +def handle_stealth_mode() -> None: + """Enable stealth/anonymous mode.""" + STEALTH_BTN.disabled = True + STEALTH_BTN.innerHTML = 'INITIALIZING STEALTH' + + print("Entering stealth mode") + + def complete_stealth() -> None: + global auth_data # noqa: PLW0603 + + # save stealth mode + auth_data = {"mode": "stealth"} + window.session = BskySession("", "") + STEALTH_BTN.innerHTML = "STEALTH ACTIVE ✓" + STEALTH_BTN.style.background = "#444400" + + def finish_stealth() -> None: + hide_modal() + on_auth_complete(auth_data) + + set_timeout(create_proxy(finish_stealth), 1000) + + set_timeout(create_proxy(complete_stealth), 1500) + + +def show_input_error() -> None: + """Show visual error on empty fields.""" + for input_field in [USERNAME_INPUT, PASSWORD_INPUT]: + if not input_field.value.strip(): + input_field.style.borderColor = "#ff0000" + input_field.style.boxShadow = "inset 0 0 10px rgba(255, 0, 0, 0.3)" + + def reset_field_style(field: Element = input_field) -> None: + field.style.borderColor = "#00ff00" + field.style.boxShadow = "" + + set_timeout(create_proxy(reset_field_style), 1000) + + +def on_auth_complete(auth_result: dict) -> None: + """Complete authentication and show interface.""" + safe_to_print = auth_result.copy() + + if safe_to_print["mode"] != "stealth": + safe_to_print["password"] = "********" # noqa: S105 + print(f"Authentication completed: {safe_to_print}") + + # update global JavaScript state + if hasattr(window, "AppState"): + window.AppState.authData = auth_result + window.AppState.isAuthenticated = auth_result["mode"] == "authenticated" + + # show main interface + main_interface = document.querySelector(".interface") + if main_interface: + main_interface.style.transition = "opacity 0.5s ease" + main_interface.style.opacity = "1" + + # update the frontend if it is available + if frontend is not None: + mode = "authenticated user" if auth_result["mode"] == "authenticated" else "stealth mode" + frontend.update_status(f"Connected as {mode}", "success") + frontend.update_connection_info(0, mode) + frontend.trigger_electric_wave() + else: + print("Frontend module not available yet") + + +# functions to use in other modules + + +def get_auth_data() -> dict | None: + """Get all authentication data.""" + return auth_data + + +def is_authenticated() -> bool: + """Verify if the user is authenticated.""" + return auth_data is not None and auth_data.get("mode") == "authenticated" + + +def get_username() -> str | None: + """Get username of authenticated user.""" + if is_authenticated(): + return auth_data.get("username") + return None + + +def get_password() -> str | None: + """Get password of authenticated user.""" + if is_authenticated(): + return auth_data.get("password") + return None + + +def get_auth_mode() -> str: + """Get mode: 'authenticated', 'stealth', 'none'.""" + if auth_data: + return auth_data.get("mode", "none") + return "none" + + +def is_stealth_mode() -> bool: + """Verify if it is in stealth mode.""" + return auth_data is not None and auth_data.get("mode") == "stealth" + + +def is_crt_enabled() -> bool: + """Check if CRT effect is currently enabled.""" + return crt_enabled + + +def get_crt_status() -> str: + """Get current CRT status as string.""" + return "ON" if crt_enabled else "OFF" + + +def show_auth_modal_after_boot() -> None: + """Show modal after boot sequence.""" + print("Initializing authentication modal...") + init_auth_modal() + + def delayed_show() -> None: + show_modal() + + set_timeout(create_proxy(delayed_show), 200) + + +print("Auth modal module loaded") diff --git a/iridescent-ivies/src/ui/frontend.py b/iridescent-ivies/src/ui/frontend.py new file mode 100644 index 00000000..257653eb --- /dev/null +++ b/iridescent-ivies/src/ui/frontend.py @@ -0,0 +1,339 @@ +from typing import Literal + +from image_modal import show_image_modal +from js import Element, Event, Math, document +from pyodide.ffi import create_proxy +from pyodide.ffi.wrappers import set_interval, set_timeout + +# constants for random effects +ELECTRIC_WAVE_PROBABILITY = 0.03 +SCREEN_FLICKER_PROBABILITY = 0.05 + +QUERY_INPUT = document.getElementById("query-input") +EXECUTE_BUTTON = document.getElementById("execute-btn") +CANCEL_BUTTON = document.getElementById("cancel-btn") +CLEAR_BUTTON = document.getElementById("clear-btn") +TABLE_HEAD = document.getElementById("table-head") +TABLE_BODY = document.getElementById("table-body") +STATUS_MESSAGE = document.getElementById("status-message") +CONNECTION_INFO = document.getElementById("connection-info") +LOADING_OVERLAY = document.getElementById("loading-overlay") +ELECTRIC_WAVE = document.getElementById("electric-wave") + + +def electric_wave_trigger() -> None: + """Roll to see if you will activate the electric wave.""" + if Math.random() < ELECTRIC_WAVE_PROBABILITY: + ELECTRIC_WAVE.classList.remove("active") + + def _activate() -> None: + ELECTRIC_WAVE.classList.add("active") + + set_timeout(create_proxy(_activate), 50) + + +def update_status(message: str, stat_type: Literal["success", "error", "warning", "info"] = "info") -> None: + """Update the status with a given message.""" + STATUS_MESSAGE.textContent = message + STATUS_MESSAGE.className = f"status-{stat_type}" + + # blink effect for errors + if stat_type == "error": + STATUS_MESSAGE.style.animation = "blink 0.5s 3" + + def _deactivate() -> None: + STATUS_MESSAGE.style.animation = "" + + set_timeout(create_proxy(_deactivate), 1500) + + +def clear_query_input() -> None: + """Clear the Query field.""" + QUERY_INPUT.style.opacity = "0.3" + + def _clear() -> None: + QUERY_INPUT.value = "" + QUERY_INPUT.style.transition = "opacity 0.3s ease" + QUERY_INPUT.style.opacity = "1" + + set_timeout(create_proxy(_clear), 150) + + +def show_empty_table() -> int: + """Empty the table.""" + empty_row = document.createElement("tr") + empty_cell = document.createElement("td") + empty_cell.textContent = "no data found" + empty_cell.colSpan = 8 + empty_cell.style.textAlign = "center" + empty_cell.style.padding = "40px 20px" + empty_cell.style.color = "#666" + empty_cell.style.fontStyle = "italic" + empty_row.appendChild(empty_cell) + + TABLE_HEAD.innerHTML = "No Columns" + TABLE_BODY.replaceChildren(empty_row) + + TABLE_HEAD.style.opacity = "1" + TABLE_BODY.style.opacity = "1" + update_connection_info(0, "no results") + return 1 + + +def update_connection_info(row: int, status: str) -> None: + """Update the connection info.""" + CONNECTION_INFO.textContent = f"rows: {row} | status: {status}" + + +def clear_interface(_: Event) -> None: + """Clear the user interface.""" + clear_query_input() + show_empty_table() + update_status("interface cleared", "info") + update_connection_info(0, "waiting") + + +def show_loading(*, show: bool = True) -> None: + """Show/hide loading overlay with spinner.""" + if show: + LOADING_OVERLAY.classList.add("show") + trigger_electric_wave() # automatic effect when loading + else: + LOADING_OVERLAY.classList.remove("show") + + +def trigger_electric_wave() -> None: + """Trigger the electric wave effect.""" + ELECTRIC_WAVE.classList.remove("active") + + def _activate() -> None: + ELECTRIC_WAVE.classList.add("active") + + set_timeout(create_proxy(_activate), 50) + + +def _create_table_headers(headers: list) -> None: + """Create table headers with staggered animation.""" + header_row = document.createElement("tr") + for index, header in enumerate(headers): + th = document.createElement("th") + th.textContent = header.upper() + th.style.opacity = "0" + header_row.appendChild(th) + + # staggered header animation + def _show_header(element: Element = th, delay: int = index * 50) -> None: + def _animate() -> None: + element.style.transition = "opacity 0.3s ease" + element.style.opacity = "1" + + set_timeout(create_proxy(_animate), delay) + + _show_header() + + TABLE_HEAD.appendChild(header_row) + + +EMBED_IMAGE_LEN = 3 + + +def _handle_image(image: str) -> Element: + """Take in an image string and create the hyperlink element. + + The string is expected to be either 1 link or a comma-separated list of 3. + """ + items = image.split(",") + thumbnail_link = items[0] + full_size_link = "" + alt_text = "" + # handle embedded images vs profile pics + if len(items) == EMBED_IMAGE_LEN: + full_size_link = items[1] + alt_text = items[2] + hyperlink = document.createElement("a") + hyperlink.href = "#" + hyperlink.textContent = "Image" + + def create_click_handler(img_url: str, fullsize_url: str, alt: str) -> callable: + """Capture the image value. + + without this there is a weird issue where all of the images are the same. + """ + + async def _handler( + _: Event, img_url: str = img_url, fullsize_url: str = fullsize_url, alt: str = alt + ) -> callable: + await show_image_modal(img_url, fullsize_url, alt) + + return _handler + + hyperlink.addEventListener("click", create_proxy(create_click_handler(thumbnail_link, full_size_link, alt_text))) + return hyperlink + + +def _create_table_rows(headers: list, rows: list[dict]) -> None: + """Create table rows with appearing effect.""" + for row_index, row_data in enumerate(rows): + tr = document.createElement("tr") + tr.style.opacity = "0" + + cell_values = [str(row_data.pop(header, "")) for header in headers] + for cell_data in cell_values: + td = document.createElement("td") + # handle image links + if cell_data.startswith("https://cdn.bsky.app/img/"): + images = cell_data.split(" | ") + for image in images: + image_element = _handle_image(image) + td.append(image_element) + else: + td.textContent = str(cell_data) if cell_data else "" + tr.appendChild(td) + + TABLE_BODY.appendChild(tr) + + # staggered row animation + def _show_row(element: Element = tr, delay: int = (row_index * 100) + 200) -> None: + def _animate() -> None: + element.style.transition = "opacity 0.4s ease" + element.style.opacity = "1" + + set_timeout(create_proxy(_animate), delay) + + _show_row() + + +def update_table(headers: list, rows: list[dict]) -> None: + """Populate table with data and appearing effects.""" + # fade out effect before updating + TABLE_HEAD.style.opacity = "0.3" + TABLE_BODY.style.opacity = "0.3" + + def _update_content() -> None: + # clear table + TABLE_HEAD.innerHTML = "" + TABLE_BODY.innerHTML = "" + + if not headers or not rows or len(rows) == 0: + show_empty_table() + return + + _create_table_headers(headers) + _create_table_rows(headers, rows) + + # restore container opacity + def _restore_opacity() -> None: + TABLE_HEAD.style.opacity = "1" + TABLE_BODY.style.opacity = "1" + + set_timeout(create_proxy(_restore_opacity), 300) + + # update counter + update_connection_info(len(rows), "connected") + + # final success effect + def _final_effect() -> None: + trigger_electric_wave() + + set_timeout(create_proxy(_final_effect), (len(rows) * 100) + 500) + + set_timeout(create_proxy(_update_content), 200) + + +def set_buttons_disabled(*, disabled: bool) -> None: + """Enable/disable buttons with visual effects.""" + EXECUTE_BUTTON.disabled = disabled + CLEAR_BUTTON.disabled = disabled + CANCEL_BUTTON.disabled = not disabled # cancel only available when executing + + # visual effects on buttons + if disabled: + EXECUTE_BUTTON.style.opacity = "0.5" + CLEAR_BUTTON.style.opacity = "0.5" + CANCEL_BUTTON.style.opacity = "1" + CANCEL_BUTTON.style.animation = "blink 1s infinite" + else: + EXECUTE_BUTTON.style.opacity = "1" + CLEAR_BUTTON.style.opacity = "1" + CANCEL_BUTTON.style.opacity = "0.7" + CANCEL_BUTTON.style.animation = "" + + +def get_current_query() -> str: + """Get current query from input.""" + return QUERY_INPUT.value.strip() + + +def show_input_error() -> None: + """Error effect on input field.""" + QUERY_INPUT.style.borderColor = "#ff0000" + QUERY_INPUT.style.boxShadow = "inset 0 0 10px rgba(255, 0, 0, 0.3)" + + def _reset() -> None: + QUERY_INPUT.style.borderColor = "#00ff00" + QUERY_INPUT.style.boxShadow = "inset 0 0 5px rgba(0, 255, 0, 0.3)" + + set_timeout(create_proxy(_reset), 1000) + + +def show_input_success() -> None: + """Success effect on input field.""" + QUERY_INPUT.style.borderColor = "#00ff00" + QUERY_INPUT.style.boxShadow = "inset 0 0 10px rgba(0, 255, 0, 0.5)" + + def _reset() -> None: + QUERY_INPUT.style.boxShadow = "inset 0 0 5px rgba(0, 255, 0, 0.3)" + + set_timeout(create_proxy(_reset), 1000) + + +def flash_screen(color: str = "#00ff00", duration: int = 200) -> None: + """Fullscreen flash effect for important results.""" + flash = document.createElement("div") + flash.style.position = "fixed" + flash.style.top = "0" + flash.style.left = "0" + flash.style.width = "100%" + flash.style.height = "100%" + flash.style.backgroundColor = color + flash.style.opacity = "0.1" + flash.style.pointerEvents = "none" + flash.style.zIndex = "9999" + + document.body.appendChild(flash) + + def _fade_out() -> None: + flash.style.transition = f"opacity {duration}ms ease" + flash.style.opacity = "0" + + def _remove() -> None: + document.body.removeChild(flash) + + set_timeout(create_proxy(_remove), duration) + + set_timeout(create_proxy(_fade_out), 50) + + +def screen_flicker_effect() -> None: + """Occasional screen flicker (retro effect).""" + if Math.random() < SCREEN_FLICKER_PROBABILITY: + screen = document.querySelector(".screen") + if screen: + screen.style.opacity = "0.9" + + def _restore() -> None: + screen.style.opacity = "1" + + set_timeout(create_proxy(_restore), 100) + + +# automatic system effects setup +set_interval(create_proxy(electric_wave_trigger), 1000) +set_interval(create_proxy(screen_flicker_effect), 5000) + +# setup initial ui +update_status("system ready", "success") +update_connection_info(0, "waiting") +show_empty_table() + +print("ready") diff --git a/iridescent-ivies/src/ui/image_modal.py b/iridescent-ivies/src/ui/image_modal.py new file mode 100644 index 00000000..8b36e2c8 --- /dev/null +++ b/iridescent-ivies/src/ui/image_modal.py @@ -0,0 +1,51 @@ +from io import BytesIO + +from ascii_magic import AsciiArt +from js import Event, document, window +from pyodide.ffi import create_proxy +from pyodide.http import pyfetch + +IMAGE_MODAL = document.getElementById("image-modal") +ASCII_DISPLAY = document.getElementById("ascii-display") +ALT_TEXT = document.getElementById("image-alt-text") +FULL_SIZE_LINK = document.getElementById("image-modal-full-link") +CLOSE_BUTTON = document.getElementById("image-modal-close") + +_image_cache = {} + + +async def show_image_modal(thumb_link: str, fullsize_link: str, alt: str) -> None: + """Show the image modal with the given link.""" + IMAGE_MODAL.style.display = "block" + FULL_SIZE_LINK.href = fullsize_link or thumb_link + ALT_TEXT.textContent = alt + ASCII_DISPLAY.textContent = "" + ascii_img = await load_image(thumb_link) + ASCII_DISPLAY.textContent = ascii_img + + +def hide_image_modal(_: Event) -> None: + """Hide the image modal.""" + IMAGE_MODAL.style.display = "none" + ASCII_DISPLAY.textContent = "" + + +# TODO: Fix styling ;) + + +async def load_image(url: str) -> str: + """Load an image as monochrome ascii.""" + if url in _image_cache: # "Cache" the images for speeding up things. + return _image_cache[url] + + blob_url = await window.session.get_blob(url) + res = await pyfetch(blob_url) + bites = BytesIO(await res.bytes()) + ascii_image = AsciiArt.from_image(bites) + ascii_image = AsciiArt.from_image(bites) + ascii_str = ascii_image.to_ascii(columns=100, monochrome=True) + _image_cache[url] = ascii_str + return ascii_str + + +CLOSE_BUTTON.addEventListener("click", create_proxy(hide_image_modal)) diff --git a/iridescent-ivies/src/web/boot.js b/iridescent-ivies/src/web/boot.js new file mode 100644 index 00000000..19c8a676 --- /dev/null +++ b/iridescent-ivies/src/web/boot.js @@ -0,0 +1,437 @@ +// boot.js +// exposes boot progress API for actual pyodide loading + +const bootMessages = [ + { + text: "BIOS Version 2.1.87 - Copyright (C) 1987 Iridescent Ivies", + delay: 500, + stage: "bios" + }, + { text: "Memory Test: 640K OK", delay: 400, stage: "memory" }, + { text: "Extended Memory Test: 15360K OK", delay: 300, stage: "memory" }, + { text: "", delay: 200, stage: "memory" }, + { text: "Detecting Hardware...", delay: 400, stage: "hardware" }, + { text: " - Primary Hard Disk.......... OK", delay: 300, stage: "hardware" }, + { text: " - Network Interface.......... OK", delay: 300, stage: "hardware" }, + { text: " - Math Coprocessor........... OK", delay: 200, stage: "hardware" }, + { text: "", delay: 200, stage: "hardware" }, + { text: "Loading SQL Social Network v1.0...", delay: 400, stage: "init" }, + { text: "Initializing Python Runtime Environment...", delay: 300, stage: "pyodide_start" }, + { text: "Loading Pyodide Kernel", delay: 0, showProgress: true, stage: "pyodide_load", waitForCallback: true }, + { text: "Installing setup scripts...", delay: 0, showProgress: true, stage: "setup_load", waitForCallback: true }, + { text: "Configuring Python modules...", delay: 0, showProgress: true, stage: "modules_load", waitForCallback: true }, + { text: "Loading parser and functions...", delay: 0, showProgress: true, stage: "functions_load", waitForCallback: true }, + { text: "Establishing database connections...", delay: 400, stage: "db_init" }, + { text: "Loading sample datasets...", delay: 300, stage: "data_init" }, + { text: "", delay: 200, stage: "complete" }, + { text: "System Ready!", delay: 300, blink: true, stage: "complete" }, + { text: "Press any key to continue...", delay: 500, blink: true, stage: "complete" }, +]; + +let bootScreen = null; +let isBootComplete = false; +let continuePressed = false; +let currentMessageIndex = 0; +let bootContent = null; +let progressCallbacks = {}; + + +window.bootProgress = { + start: startBootSequence, + pyodideLoaded: () => advanceToStage("setup_load"), + setupLoaded: () => advanceToStage("modules_load"), + modulesLoaded: () => advanceToStage("functions_load"), + functionsLoaded: () => advanceToStage("db_init"), + complete: finishBoot, + isComplete: () => isBootComplete, + + updateProgress: updateCurrentProgress, + setProgressMessage: setProgressMessage +}; + +// update current progress bar to a specific percentage +function updateCurrentProgress(percentage) { + const currentProgressBars = document.querySelectorAll('[id^="progress-bar-"]'); + const lastBar = currentProgressBars[currentProgressBars.length - 1]; + if (lastBar) { + // clear any existing interval for this bar to prevent conflicts + const barId = lastBar.id; + if (progressCallbacks[barId]) { + clearInterval(progressCallbacks[barId]); + delete progressCallbacks[barId]; + } + + lastBar.style.width = Math.min(85, Math.max(0, percentage)) + "%"; + } +} + +// add a custom progress message (optional) +function setProgressMessage(message) { + const bootLines = bootContent.querySelectorAll('.boot-line'); + const lastLine = bootLines[bootLines.length - 1]; + if (lastLine && !lastLine.classList.contains('boot-blink')) { + const progressDiv = lastLine.querySelector('.boot-progress'); + if (progressDiv) { + lastLine.innerHTML = message + progressDiv.outerHTML; + } + } +} + +async function startBootSequence() { + continuePressed = false; + currentMessageIndex = 0; + + bootScreen = document.createElement("div"); + bootScreen.className = "boot-screen"; + bootScreen.innerHTML = '
'; + + document.body.appendChild(bootScreen); + document.querySelector(".interface").style.opacity = "0"; + + bootContent = document.getElementById("boot-content"); + + // start showing messages up to first callback point + await showMessagesUpToStage("pyodide_load"); + + console.log("Boot sequence waiting for pyodide load..."); +} + +async function showMessagesUpToStage(targetStage) { + while (currentMessageIndex < bootMessages.length) { + if (continuePressed) { + // fast-forward remaining messages + showRemainingMessages(); + break; + } + + const message = bootMessages[currentMessageIndex]; + + // stop if we hit a callback stage that doesn't match the target + if (message.waitForCallback && message.stage !== targetStage) { + break; + } + + await showMessage(message, currentMessageIndex); + currentMessageIndex++; + + // if this was the target stage and it's a callback, stop here + if (message.stage === targetStage && message.waitForCallback) { + break; + } + } +} + +async function advanceToStage(targetStage) { + console.log(`Advancing boot to stage: ${targetStage}`); + + // complete current progress bar smoothly if there is one + await completeCurrentProgressBar(); + + // continue to next stage + currentMessageIndex++; + await showMessagesUpToStage(targetStage); +} + +function completeCurrentProgressBar() { + return new Promise((resolve) => { + const currentProgressBars = document.querySelectorAll('[id^="progress-bar-"]'); + if (currentProgressBars.length === 0) { + resolve(); + return; + } + + let completed = 0; + currentProgressBars.forEach(bar => { + const barId = bar.id; + + if (progressCallbacks[barId]) { + clearInterval(progressCallbacks[barId]); + delete progressCallbacks[barId]; + } + + const currentWidth = parseFloat(bar.style.width) || 0; + + if (currentWidth >= 100) { + completed++; + if (completed === currentProgressBars.length) { + resolve(); + } + return; + } + + const interval = setInterval(() => { + const width = parseFloat(bar.style.width) || 0; + if (width >= 100) { + bar.style.width = "100%"; + clearInterval(interval); + completed++; + if (completed === currentProgressBars.length) { + resolve(); + } + } else { + bar.style.width = Math.min(100, width + 12) + "%"; + } + }, 30); + }); + }); +} + +async function showMessage(message, index) { + const line = document.createElement("div"); + line.className = "boot-line"; + + if (message.showProgress) { + line.innerHTML = message.text + + '
'; + } else { + line.textContent = message.text; + } + + if (message.blink) { + line.classList.add("boot-blink"); + } + + bootContent.appendChild(line); + + setTimeout(() => { + line.classList.add("boot-show"); + }, 50); + + if (message.showProgress && !message.waitForCallback) { + await animateProgressBar("progress-bar-" + index); + } else if (message.showProgress && message.waitForCallback) { + startProgressBar("progress-bar-" + index); + } + + if (message.delay > 0) { + await new Promise(resolve => setTimeout(resolve, message.delay)); + } +} + +function startProgressBar(barId) { + const progressBar = document.getElementById(barId); + if (!progressBar) return; + + // start at 0% and slowly increase until callback + let progress = 0; + progressBar.style.width = progress + "%"; + + const interval = setInterval(() => { + if (continuePressed) { + progress = 100; + progressBar.style.width = progress + "%"; + clearInterval(interval); + return; + } + + // slowly increase but never complete without callback + // slow down as it gets closer to 90% + const increment = progress < 50 ? Math.random() * 8 : Math.random() * 2; + progress += increment; + if (progress > 85) progress = 85; + progressBar.style.width = progress + "%"; + }, 300); + + progressCallbacks[barId] = interval; +} + +function animateProgressBar(barId) { + return new Promise((resolve) => { + const progressBar = document.getElementById(barId); + if (!progressBar) { + resolve(); + return; + } + + let progress = 0; + const interval = setInterval(() => { + if (continuePressed) { + progress = 100; + clearInterval(interval); + resolve(); + return; + } + + progress += Math.random() * 15; + if (progress >= 100) { + progress = 100; + clearInterval(interval); + resolve(); + } + progressBar.style.width = progress + "%"; + }, 100); + }); +} + +function showRemainingMessages() { + const remainingMessages = bootMessages.slice(currentMessageIndex); + remainingMessages.forEach((msg, i) => { + const line = document.createElement("div"); + line.className = "boot-line boot-show"; + line.textContent = msg.text; + if (msg.blink) line.classList.add("boot-blink"); + bootContent.appendChild(line); + }); + currentMessageIndex = bootMessages.length; +} + +async function finishBoot() { + console.log("Finishing boot sequence..."); + + // complete any remaining progress bars + completeCurrentProgressBar(); + + // show remaining messages + currentMessageIndex++; + await showMessagesUpToStage("complete"); + await waitForContinue(); + + if (bootScreen) { + bootScreen.style.opacity = "0"; + bootScreen.style.transition = "opacity 0.5s ease"; + + setTimeout(() => { + if (bootScreen && bootScreen.parentNode) { + document.body.removeChild(bootScreen); + } + bootScreen = null; + + if (window.pyodide && window.pkg) { + try { + const auth_modal = window.pyodide.pyimport("auth_modal"); + auth_modal.show_auth_modal_after_boot(); + console.log("Auth modal initialized via Python"); + } catch (error) { + console.error("Error loading auth modal:", error); + // fallback: show interface directly + showMainInterface(); + } + } else { + console.log("Pyodide not ready, showing interface directly"); + showMainInterface(); + } + + }, 500); + } + + isBootComplete = true; + console.log("Boot sequence complete - authentication phase"); +} + +function showMainInterface() { + const mainInterface = document.querySelector(".interface"); + if (mainInterface) { + mainInterface.style.transition = "opacity 0.5s ease"; + mainInterface.style.opacity = "1"; + } + console.log("Main interface shown directly"); +} + +function waitForContinue() { + return new Promise((resolve) => { + const handleInteraction = (e) => { + e.preventDefault(); + e.stopPropagation(); + continuePressed = true; + + document.removeEventListener("keydown", handleInteraction, true); + document.removeEventListener("click", handleInteraction, true); + if (bootScreen) { + bootScreen.removeEventListener("click", handleInteraction, true); + } + + resolve(); + }; + + document.addEventListener("keydown", handleInteraction, true); + document.addEventListener("click", handleInteraction, true); + if (bootScreen) { + bootScreen.addEventListener("click", handleInteraction, true); + } + + // Auto-continue after 3 seconds + const timeoutId = setTimeout(() => { + if (!continuePressed) { + continuePressed = true; + document.removeEventListener("keydown", handleInteraction, true); + document.removeEventListener("click", handleInteraction, true); + if (bootScreen) { + bootScreen.removeEventListener("click", handleInteraction, true); + } + resolve(); + } + }, 3000); + + const originalResolve = resolve; + resolve = () => { + clearTimeout(timeoutId); + originalResolve(); + }; + }); +} + +// styles for boot screen (inject into document head) +const bootStyles = ` +.boot-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + color: #00ff00; + font-family: "JetBrains Mono", "Courier New", monospace; + z-index: 10000; + padding: 20px; + font-size: 14px; + line-height: 1.4; + overflow-y: auto; + cursor: pointer; +} + +.boot-content { + max-width: 800px; + margin: 0 auto; +} + +.boot-line { + opacity: 0; + margin-bottom: 2px; + transition: opacity 0.3s ease; +} + +.boot-line.boot-show { + opacity: 1; +} + +.boot-line.boot-blink { + animation: bootBlink 0.5s infinite; +} + +.boot-progress { + display: inline-block; + width: 200px; + height: 8px; + border: 1px solid #00ff00; + margin-left: 10px; + position: relative; + vertical-align: middle; +} + +.boot-progress-bar { + height: 100%; + background: #00ff00; + width: 0%; + transition: width 0.2s ease; +} + +@keyframes bootBlink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0; } +} +`; + +const styleSheet = document.createElement("style"); +styleSheet.textContent = bootStyles; +document.head.appendChild(styleSheet); diff --git a/iridescent-ivies/src/web/favicon.png b/iridescent-ivies/src/web/favicon.png new file mode 100644 index 00000000..a7162119 Binary files /dev/null and b/iridescent-ivies/src/web/favicon.png differ diff --git a/iridescent-ivies/src/web/styles.css b/iridescent-ivies/src/web/styles.css new file mode 100644 index 00000000..a2f3d5b4 --- /dev/null +++ b/iridescent-ivies/src/web/styles.css @@ -0,0 +1,1077 @@ +/* fonts */ +@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap"); + +/* Thanks to this article on the CRT effect: https://aleclownes.com/2017/02/01/crt-display.html */ +/* Read for reference on the values used */ + +@keyframes flicker { + 0% { + opacity: 0.27861; + } + 5% { + opacity: 0.34769; + } + 10% { + opacity: 0.23604; + } + 15% { + opacity: 0.90626; + } + 20% { + opacity: 0.18128; + } + 25% { + opacity: 0.83891; + } + 30% { + opacity: 0.65583; + } + 35% { + opacity: 0.67807; + } + 40% { + opacity: 0.26559; + } + 45% { + opacity: 0.84693; + } + 50% { + opacity: 0.96019; + } + 55% { + opacity: 0.08594; + } + 60% { + opacity: 0.20313; + } + 65% { + opacity: 0.71988; + } + 70% { + opacity: 0.53455; + } + 75% { + opacity: 0.37288; + } + 80% { + opacity: 0.71428; + } + 85% { + opacity: 0.70419; + } + 90% { + opacity: 0.7003; + } + 95% { + opacity: 0.36108; + } + 100% { + opacity: 0.24387; + } +} + + +@keyframes textShadow { + 0% { + text-shadow: 0.4389924193300864px 0 1px rgba(0,30,255,0.5), -0.4389924193300864px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 5% { + text-shadow: 2.7928974010788217px 0 1px rgba(0,30,255,0.5), -2.7928974010788217px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 10% { + text-shadow: 0.02956275843481219px 0 1px rgba(0,30,255,0.5), -0.02956275843481219px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 15% { + text-shadow: 0.40218538552878136px 0 1px rgba(0,30,255,0.5), -0.40218538552878136px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 20% { + text-shadow: 3.4794037899852017px 0 1px rgba(0,30,255,0.5), -3.4794037899852017px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 25% { + text-shadow: 1.6125630401149584px 0 1px rgba(0,30,255,0.5), -1.6125630401149584px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 30% { + text-shadow: 0.7015590085143956px 0 1px rgba(0,30,255,0.5), -0.7015590085143956px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 35% { + text-shadow: 3.896914047650351px 0 1px rgba(0,30,255,0.5), -3.896914047650351px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 40% { + text-shadow: 3.870905614848819px 0 1px rgba(0,30,255,0.5), -3.870905614848819px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 45% { + text-shadow: 2.231056963361899px 0 1px rgba(0,30,255,0.5), -2.231056963361899px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 50% { + text-shadow: 0.08084290417898504px 0 1px rgba(0,30,255,0.5), -0.08084290417898504px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 55% { + text-shadow: 2.3758461067427543px 0 1px rgba(0,30,255,0.5), -2.3758461067427543px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 60% { + text-shadow: 2.202193051050636px 0 1px rgba(0,30,255,0.5), -2.202193051050636px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 65% { + text-shadow: 2.8638780614874975px 0 1px rgba(0,30,255,0.5), -2.8638780614874975px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 70% { + text-shadow: 0.48874025155497314px 0 1px rgba(0,30,255,0.5), -0.48874025155497314px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 75% { + text-shadow: 1.8948491305757957px 0 1px rgba(0,30,255,0.5), -1.8948491305757957px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 80% { + text-shadow: 0.0833037308038857px 0 1px rgba(0,30,255,0.5), -0.0833037308038857px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 85% { + text-shadow: 0.09769827255241735px 0 1px rgba(0,30,255,0.5), -0.09769827255241735px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 90% { + text-shadow: 3.443339761481782px 0 1px rgba(0,30,255,0.5), -3.443339761481782px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 95% { + text-shadow: 2.1841838852799786px 0 1px rgba(0,30,255,0.5), -2.1841838852799786px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } + 100% { + text-shadow: 2.6208764473832513px 0 1px rgba(0,30,255,0.5), -2.6208764473832513px 0 1px rgba(255,0,80,0.3), 0 0 3px; + } +} +.crt { + animation: textShadow 1.6s infinite; +} + +.crt::before { + content: " "; + display: block; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06)); + z-index: 2; + background-size: 100% 2px, 3px 100%; + pointer-events: none; +} +.crt::after { + content: " "; + display: block; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(18, 16, 16, 0.1); + opacity: 0; + z-index: 2; + pointer-events: none; + animation: flicker 0.15s infinite; +} +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* base layout */ +body { + font-family: "JetBrains Mono", "Courier New", monospace; + background: #000; + color: #00ff00; + height: 100vh; + overflow: hidden; +} + +.screen { + width: 100vw; + height: 100vh; + background: radial-gradient(ellipse at center, #001a00 0%, #000000 70%); + position: relative; + overflow: hidden; + box-shadow: inset 0 0 100px rgba(0, 255, 0, 0.1); +} + +/* crt effect */ +.screen::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 255, 0, 0.02) 2px, + rgba(0, 255, 0, 0.02) 4px + ); + pointer-events: none; + z-index: 10; +} + +.screen::after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient( + ellipse at center, + transparent 60%, + rgba(0, 0, 0, 0.4) 100% + ); + pointer-events: none; + z-index: 5; +} + +/* electric wave */ +.electric-wave { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 0deg, + transparent 0%, + rgba(0, 255, 0, 0.1) 49%, + rgba(0, 255, 0, 0.3) 50%, + rgba(0, 255, 0, 0.1) 51%, + transparent 100% + ); + transform: translateY(-100%); + pointer-events: none; + z-index: 15; + opacity: 0; +} + +.electric-wave.active { + animation: electricWave 2s ease-out; +} + +@keyframes electricWave { + 0% { + transform: translateY(-100%); + opacity: 0; + } + 10% { + opacity: 1; + } + 90% { + opacity: 1; + } + 100% { + transform: translateY(100vh); + opacity: 0; + } +} + +/* main interface */ +.interface { + position: relative; + z-index: 1; + height: 100%; + padding: 30px; + display: flex; + flex-direction: column; +} + +.title-bar { + background: #00ff00; + color: #000; + padding: 8px 16px; + font-weight: bold; + font-size: 16px; + margin-bottom: 2px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.window { + border: 2px solid #00ff00; + background: #000; + flex: 1; + display: flex; + flex-direction: column; +} + +.content-area { + flex: 1; + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; +} + +/* panels */ +.query-panel, +.results-panel { + border: 1px solid #00ff00; + background: #001100; +} + +.panel-header { + background: #003300; + padding: 4px 8px; + font-size: 14px; + font-weight: bold; + border-bottom: 1px solid #00ff00; +} + +.panel-content { + padding: 12px; +} + +/* query input */ +.sql-input { + width: 100%; + height: 100px; + background: #000; + border: 1px inset #00ff00; + color: #00ff00; + font-family: inherit; + font-size: 15px; + padding: 8px; + resize: none; + outline: none; +} + +.sql-input:focus { + background: #001100; + box-shadow: inset 0 0 5px rgba(0, 255, 0, 0.3); +} + +.sql-input::placeholder { + color: #006600; + opacity: 0.7; +} + +/* buttons */ +.button-row { + margin-top: 8px; + display: flex; + gap: 8px; +} + +.btn { + background: #003300; + border: 2px outset #00ff00; + color: #00ff00; + padding: 8px 20px; + font-family: inherit; + font-size: 14px; + cursor: pointer; + font-weight: bold; + transition: background-color 0.2s ease; +} + +.btn:hover { + background: #004400; +} + +.btn:active { + border: 2px inset #00ff00; + background: #002200; +} + +.btn:disabled { + background: #001100; + color: #004400; + cursor: not-allowed; + border-color: #004400; +} + +.btn-danger { + background: #330000; + border-color: #ff0000; + color: #ff0000; +} + +.btn-danger:hover { + background: #440000; +} + +.btn-danger:active { + background: #220000; +} + +/* results table */ +.results-panel { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + +.table-area { + flex: 1; + overflow: auto; + background: #000; + border: 1px inset #00ff00; + margin: 12px; + max-height: calc(100vh - 400px); +} + +.data-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; + min-width: 800px; +} + +.data-table th { + background: #003300; + border: 1px solid #00ff00; + padding: 8px 15px; + text-align: left; + font-weight: bold; + position: sticky; + top: 0; + z-index: 2; +} + +.data-table td { + border: 1px solid #004400; + padding: 8px 15px; + white-space: nowrap; +} + +.data-table tr:nth-child(even) { + background: #001100; +} + +.data-table tr:hover { + background: #002200; +} + +/* empty state */ +.data-table .empty-state { + text-align: center; + padding: 40px 20px; + color: #666; + font-style: italic; +} + +/* status line */ +.status-line { + background: #003300; + border-top: 1px solid #00ff00; + padding: 4px 12px; + font-size: 11px; + display: flex; + justify-content: space-between; + flex-shrink: 0; +} + +/* status message colors */ +.status-success { + color: #00ff00; +} + +.status-error { + color: #ff0000; +} + +.status-warning { + color: #ffff00; +} + +.status-info { + color: #00ffff; +} + +/* loading overlay */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + z-index: 1000; + display: none; + align-items: center; + justify-content: center; +} + +.loading-overlay.show { + display: flex; +} + +.loading-content { + text-align: center; + color: #00ff00; +} + +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid #003300; + border-top: 3px solid #00ff00; + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto 20px; +} + +.loading-text { + font-size: 18px; + font-weight: bold; + letter-spacing: 2px; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* scrollbars */ +.table-area::-webkit-scrollbar { + width: 16px; + height: 16px; +} + +.table-area::-webkit-scrollbar-track { + background: #003300; + border: 1px solid #00ff00; +} + +.table-area::-webkit-scrollbar-thumb { + background: #00ff00; + border: 1px solid #003300; +} + +.table-area::-webkit-scrollbar-corner { + background: #003300; +} + +/* responsive (it works :D!!) */ +@media (max-width: 768px) { + .interface { + padding: 10px; + } + + .button-row { + flex-direction: column; + } + + .btn { + width: 100%; + } + + .title-bar { + font-size: 14px; + padding: 6px 12px; + } + + .title-bar span:last-child { + display: none; + } + + .sql-input { + height: 80px; + font-size: 13px; + } + + .data-table { + font-size: 12px; + min-width: 600px; + } + + .data-table th, + .data-table td { + padding: 6px 10px; + } +} + +@media (max-width: 480px) { + .interface { + padding: 5px; + } + + .content-area { + padding: 8px; + gap: 8px; + } + + .panel-content { + padding: 8px; + } + + .sql-input { + height: 60px; + font-size: 12px; + } +} + +#image-modal { + position: fixed; + top: 80px; + left: 50%; + height: 85vh; + width: 50%; + transform: translateX(-50%); + z-index: 2; + display: none; + text-align: center; + overflow: scroll; + -ms-overflow-style: none; + scrollbar-width: none; +} + +#image-modal-container { + max-width: 100%; +} + +#image-modal-close { + cursor: pointer; + float: right; +} + +#ascii-display { + width: 100ch; + font-size: 8px; + margin: 0 auto 20px auto; + text-align: center; + display: block; +} + +#image-alt-text { + display: block; + margin-top: 15px; + margin-bottom: 20px; + color: #00ff00; + font-size: 18px; + padding: 0 10px; +} + +.modal-footer { + margin-top: auto; + text-align: center; + padding: 10px; + border-top: 1px solid #004400; +} + +#image-modal-full-link { + font-size: 10px; + color: #66ff66; + text-decoration: none; + padding: 4px 8px; + border: 1px solid #004400; + background: #001100; + transition: all 0.2s ease; +} + +#image-modal-full-link:hover { + background: #002200; + border-color: #00ff00; + color: #00ff00; +} + +#image-modal::-webkit-scrollbar { + display: none; +} + +.auth-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.98); + z-index: 10001; + display: flex; + align-items: center; + justify-content: center; + font-family: "JetBrains Mono", "Courier New", monospace; + color: #00ff00; + opacity: 0; + transition: opacity 0.5s ease; + display: none; +} + +.auth-modal.show { + opacity: 1; + display: flex; +} + +.auth-modal::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 255, 0, 0.02) 2px, + rgba(0, 255, 0, 0.02) 4px + ); + pointer-events: none; + z-index: 10; +} + +.modal-container { + background: radial-gradient(ellipse at center, #001a00 0%, #000000 70%); + border: 2px solid #00ff00; + max-width: 500px; + width: 90%; + position: relative; + box-shadow: inset 0 0 100px rgba(0, 255, 0, 0.1); +} + +.modal-container { + animation: textShadow 1.6s infinite; +} + +.modal-header { + background: #00ff00; + color: #000; + padding: 8px 16px; + font-weight: bold; + font-size: 16px; + margin-bottom: 2px; + text-align: center; + text-transform: uppercase; + letter-spacing: 2px; +} + +.modal-body { + border: 1px solid #00ff00; + background: #001100; + padding: 16px; +} + +.auth-title { + text-align: center; + font-size: 18px; + font-weight: bold; + margin-bottom: 8px; + color: #00ff00; +} + +.auth-subtitle { + text-align: center; + font-size: 12px; + color: #66ff66; + margin-bottom: 20px; + opacity: 0.8; +} + +.form-group { + margin-bottom: 12px; +} + +.form-label { + display: block; + margin-bottom: 4px; + font-size: 14px; + font-weight: bold; + color: #00ff00; + text-transform: uppercase; +} + +.form-input { + width: 100%; + /* same effect: .sql-input */ + background: #000; + border: 1px inset #00ff00; + color: #00ff00; + font-family: inherit; + font-size: 14px; + padding: 8px; + outline: none; +} + +.form-input:focus { + /* same effect: .sql-input:focus */ + background: #001100; + box-shadow: inset 0 0 5px rgba(0, 255, 0, 0.3); +} + +.form-input::placeholder { + color: #006600; + opacity: 0.7; +} + +.button-container { + display: flex; + gap: 8px; + margin-top: 16px; +} + +.modal-btn { + flex: 1; + background: #003300; + border: 2px outset #00ff00; + color: #00ff00; + padding: 8px 20px; + font-family: inherit; + font-size: 14px; + cursor: pointer; + font-weight: bold; + transition: background-color 0.2s ease; + text-transform: uppercase; +} + +.modal-btn:hover { + background: #004400; +} + +.modal-btn:active { + border: 2px inset #00ff00; + background: #002200; +} + +.modal-btn:disabled { + background: #001100; + color: #004400; + cursor: not-allowed; + border-color: #004400; +} + +.btn-primary { + background: #004400; +} + +.btn-secondary { + background: #333300; + border-color: #ffff00; + color: #ffff00; +} + +.btn-secondary:hover { + background: #444400; +} + +.btn-secondary:active { + background: #222200; +} + +/* CRT Toggle Button Styles */ +.crt-toggle-container { + margin: 12px 0 8px 0; + display: flex; + justify-content: center; +} + +.btn-crt { + background: #003300; + border: 2px outset #00ff00; + color: #00ff00; + padding: 8px 20px; + font-family: inherit; + font-size: 14px; + cursor: pointer; + font-weight: bold; + transition: all 0.2s ease; + text-transform: uppercase; + letter-spacing: 1px; + min-width: 150px; + transform: scale(1.0); +} + +.btn-crt:hover { + background: #004400; + box-shadow: 0 0 8px rgba(0, 255, 0, 0.3); +} + +.btn-crt:active { + border: 2px inset #00ff00; + background: #002200; + transform: scale(0.98); +} + +/* CRT effect disabled state styles */ +.btn-crt.crt-disabled { + background: #333300; + border-color: #ffff00; + color: #ffff00; +} + +.btn-crt.crt-disabled:hover { + background: #444400; + box-shadow: 0 0 8px rgba(255, 255, 0, 0.3); +} + +.security-notice { + background: #330000; + border: 1px solid #ff6600; + color: #ff9999; + padding: 8px; + margin-top: 12px; + font-size: 10px; +} + +.security-notice::before { + content: "⚠ "; + color: #ff6600; + font-weight: bold; +} + +/* loading points effect */ +.loading-dots::after { + content: ""; + animation: loadingDots 1.5s infinite; +} + +@keyframes loadingDots { + 0%, 20% { content: ""; } + 40% { content: "."; } + 60% { content: ".."; } + 80%, 100% { content: "..."; } +} + +/* responsive */ +@media (max-width: 768px) { + .modal-container { + width: 95%; + } + + .modal-body { + padding: 12px; + } + + .button-container { + flex-direction: column; + } + + .modal-btn { + width: 100%; + } + + .btn-crt { + width: 100%; + margin: 8px 0; + } + + .modal-header { + font-size: 14px; + padding: 6px 12px; + } + + .form-input { + font-size: 13px; + } +} + +@media (max-width: 480px) { + .modal-body { + padding: 8px; + } + + .form-input { + font-size: 12px; + } + + .btn-crt { + font-size: 14px; + padding: 8px 20px; + } +} + +/* BSOD Styles */ +.bsod-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #0000aa; + color: white; + font-family: Consolas, "Lucida Console", "Courier New", monospace; + z-index: 99999; + display: flex; + align-items: center; + justify-content: center; + animation: bsodFlicker 0.1s ease-in; +} + +.bsod-content { + max-width: 800px; + padding: 40px; + line-height: 1.4; + font-size: 13px; + font-weight: normal; +} + +.bsod-header { + font-size: 14px; + margin-bottom: 20px; + font-weight: normal; +} + +.bsod-error { + font-size: 16px; + font-weight: bold; + margin: 20px 0; + color: #ffffff; + text-decoration: underline; +} + +.bsod-text { + margin: 20px 0; + line-height: 1.5; + font-size: 13px; +} + +.bsod-technical { + margin-top: 30px; + font-family: Consolas, "Lucida Console", "Courier New", monospace; + font-size: 11px; + color: #cccccc; + line-height: 1.3; +} + +@keyframes bsodFlicker { + 0% { opacity: 0; } + 10% { opacity: 1; } + 15% { opacity: 0; } + 20% { opacity: 1; } + 25% { opacity: 0; } + 30% { opacity: 1; } + 100% { opacity: 1; } +} + +/* Responsive BSOD */ +@media (max-width: 768px) { + .bsod-content { + padding: 20px; + font-size: 11px; + } + + .bsod-header { + font-size: 12px; + } + + .bsod-error { + font-size: 14px; + } + + .bsod-technical { + font-size: 9px; + } +} + +@media (max-width: 480px) { + .bsod-content { + padding: 15px; + font-size: 10px; + } + + .bsod-header { + font-size: 11px; + } + + .bsod-error { + font-size: 12px; + } +}