models 721a6abfda65
97 files
663.7 KB
216.0k tokens
Showing preview only (697K chars total). The displayed content is truncated. Use the JSON API for full output.
Repository: phoenixthrush/AniWorld-Downloader
Branch: models
Commit: 721a6abfda65
Files: 97
Total size: 663.7 KB

Directory structure:
gitextract_r78ceu71/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       ├── build-nuitka.yaml
│       ├── docker-publish.yml
│       └── publish.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── docker-compose.yaml
├── examples/
│   ├── aniworld_to/
│   │   ├── aniworld_episode.py
│   │   ├── aniworld_episode_aniskip.py
│   │   ├── aniworld_episode_muxing.py
│   │   ├── aniworld_season.py
│   │   └── aniworld_series.py
│   └── s_to/
│       ├── serienstream_episode.py
│       ├── serienstream_episode_muxing.py
│       ├── serienstream_season.py
│       └── serienstream_series.py
├── pyproject.toml
├── src/
│   └── aniworld/
│       ├── __init__.py
│       ├── __main__.py
│       ├── anime4k/
│       │   ├── __init__.py
│       │   └── anime4k.py
│       ├── aniskip/
│       │   ├── __init__.py
│       │   ├── aniskip.py
│       │   ├── jikan.py
│       │   └── scripts/
│       │       ├── aniskip.lua
│       │       ├── autoexit.lua
│       │       └── autostart.lua
│       ├── arguments.py
│       ├── ascii/
│       │   ├── ASCII.txt
│       │   ├── __init__.py
│       │   └── ascii.py
│       ├── autodeps.py
│       ├── common/
│       │   ├── __init__.py
│       │   └── common.py
│       ├── config.py
│       ├── entry.py
│       ├── env.py
│       ├── extractors/
│       │   ├── __init__.py
│       │   └── provider/
│       │       ├── doodstream.py
│       │       ├── filemoon.py
│       │       ├── loadx.py
│       │       ├── luluvdo.py
│       │       ├── streamtape.py
│       │       ├── vidmoly.py
│       │       ├── vidoza.py
│       │       └── voe.py
│       ├── logger.py
│       ├── menu.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── aniworld_to/
│       │   │   ├── __init__.py
│       │   │   ├── episode.py
│       │   │   ├── season.py
│       │   │   └── series.py
│       │   ├── common/
│       │   │   ├── __init__.py
│       │   │   └── common.py
│       │   ├── filmpalast_to/
│       │   │   └── episode.py
│       │   ├── hanime_tv/
│       │   │   ├── __init__.py
│       │   │   └── episode.py
│       │   ├── hianime_to/
│       │   │   ├── __init__.py
│       │   │   ├── episode.py
│       │   │   ├── season.py
│       │   │   └── series.py
│       │   └── s_to/
│       │       ├── __init__.py
│       │       ├── episode.py
│       │       ├── season.py
│       │       └── series.py
│       ├── nuitka/
│       │   └── manual_build.py
│       ├── playwright/
│       │   ├── __init__.py
│       │   ├── captcha.py
│       │   └── test.py
│       ├── providers.py
│       ├── search.py
│       └── web/
│           ├── __init__.py
│           ├── app.py
│           ├── auth.py
│           ├── captcha.py
│           ├── db.py
│           ├── static/
│           │   ├── app.js
│           │   ├── autosync.js
│           │   ├── library.js
│           │   ├── queue.js
│           │   ├── settings.js
│           │   └── style.css
│           └── templates/
│               ├── autosync.html
│               ├── base.html
│               ├── index.html
│               ├── library.html
│               ├── login.html
│               ├── settings.html
│               └── setup.html
└── tests/
    ├── test_aniworld_models.py
    └── test_aniworld_providers.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: phoenixthrush


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Something is broken / not working as expected
title: ""
labels: ["bug"]
---

## What happened?

A short, specific description of the problem.

## What did you expect to happen?

What you thought the tool would do instead.

## How to reproduce

Copy/paste commands if possible.

1.
2.
3.

## Output / error

Paste the full output (best with `--debug`). If it’s long, attach a file.

```text
(paste here)
```

## Environment

- OS: (Windows/macOS/Linux + version)
- Install method: (pip / pipx / from source / exe)
- AniWorld-Downloader version: (`aniworld --version` / `pip show aniworld` / git hash)
- Python version: (`python --version`) (skip if using exe)
- How you ran it: (terminal, PowerShell, double-click, scheduler, etc.)

## Config file (optional, helps a lot)

If possible, attach your AniWorld config file:

- Linux/macOS: `~/.aniworld/.env`
- Windows: `%USERPROFILE%\.aniworld\.env`

If you added anything custom, quickly review it first and remove anything you wouldn’t want public.

## Checklist

- [ ] I searched existing issues to avoid duplicates


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea or improvement
title: ""
labels: ["enhancement"]
---

## What do you want to add/change?

A one-paragraph description of the feature.

## Why?

What problem does this solve, or what gets better?

## How should it work?

Be specific if you can.

- CLI example (if relevant):

```bash
aniworld ...
```

- Expected output/behavior:

```text
...
```

## When is this useful? (examples)

Give 1–3 real situations.

1.
2.
3.

## Alternatives you’ve considered

What other ways could solve the same problem? (including “do nothing”)

## Anything else?

Links, screenshots, related issues, rough implementation ideas (optional).

## Checklist

- [ ] I searched existing issues to avoid duplicates


================================================
FILE: .github/workflows/build-nuitka.yaml
================================================
name: Build Executable with Nuitka

on:
  # release:
  #  types: [published]
  workflow_dispatch:

jobs:
  build:
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]

    runs-on: ${{ matrix.os }}

    steps:
      - name: Check-out repository
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'
          cache: 'pip'

      - name: Install your Dependencies
        run: |
          pip install .

      - name: Build Executable with Nuitka
        uses: Nuitka/Nuitka-Action@main
        with:
          nuitka-version: main
          script-name: src/aniworld

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: ${{ runner.os }} Build
          path: |
            build/*.exe
            build/*.bin
            build/*.app/**/*


================================================
FILE: .github/workflows/docker-publish.yml
================================================
name: Build Docker Image

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
  release:
    types: [published]

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: ${{ github.repository }}


jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # This is used to complete the identity challenge
      # with sigstore/fulcio when running outside of PRs.
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      # Install the cosign tool
      # https://github.com/sigstore/cosign-installer
      - name: Install cosign
        uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
        with:
          cosign-release: 'v2.2.4'

      # Add QEMU for multi-platform support (REQUIRED for ARM builds on x64 runners)
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      # Set up BuildKit Docker container builder to be able to build
      # multi-platform images and export cache
      # https://github.com/docker/setup-buildx-action
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

      # Login against a Docker registry
      # https://github.com/docker/login-action
      - name: Log into registry ${{ env.REGISTRY }}
        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Extract metadata (tags, labels) for Docker
      # https://github.com/docker/metadata-action
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      # Build and push Docker image with Buildx
      # https://github.com/docker/build-push-action
      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
        with:
          context: .
          push: true
          platforms: linux/amd64,linux/arm64 # <--- Hinzugefügt für Multi-Arch Support
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      # Sign the resulting Docker image digest.
      # This will only write to the public Rekor transparency log when the Docker
      # repository is public to avoid leaking data.  If you would like to publish
      # transparency data even for private images, pass --force to cosign below.
      # https://github.com/sigstore/cosign
      - name: Sign the published Docker image
        env:
          # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
          TAGS: ${{ steps.meta.outputs.tags }}
          DIGEST: ${{ steps.build-and-push.outputs.digest }}
        # This step uses the identity token to provision an ephemeral certificate
        # against the sigstore community Fulcio instance.
        run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}


================================================
FILE: .github/workflows/publish.yaml
================================================
name: Publish to PyPI

on:
  release:
    types: [published]
  workflow_dispatch:

jobs:
  pypi-publish:
    name: Upload release to PyPI
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/aniworld
    permissions:
      id-token: write
      contents: write

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
          cache: 'pip'

      - name: Install build dependencies
        run: |
          python -m pip install --upgrade pip
          pip install build

      - name: Build distribution
        run: python -m build

      - name: Upload dist files to GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: |
            dist/*.tar.gz
            dist/*.whl

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#   Usually these files are written by a python script from a template
#   before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
# Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
# uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# poetry.lock
# poetry.toml

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#   pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
#   https://pdm-project.org/en/latest/usage/project/#working-with-version-control
# pdm.lock
# pdm.toml
.pdm-python
.pdm-build/

# pixi
#   Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
# pixi.lock
#   Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
#   in the .venv directory. It is recommended not to include this directory in version control.
.pixi

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# Redis
*.rdb
*.aof
*.pid

# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/

# ActiveMQ
activemq-data/

# SageMath parsed files
*.sage.py

# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#   JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#   be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#   and can be added to the global gitignore or merged into this file.  For a more nuclear
#   option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

# Abstra
#   Abstra is an AI-powered process automation framework.
#   Ignore directories containing user credentials, local state, and settings.
#   Learn more at https://abstra.io/docs
.abstra/

# Visual Studio Code
#   Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
#   that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
#   and can be added to the global gitignore or merged into this file. However, if you prefer, 
#   you could uncomment the following to ignore the entire vscode folder
# .vscode/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/

# Streamlit
.streamlit/secrets.toml

# Docker volume mount
/Downloads/


================================================
FILE: Dockerfile
================================================
FROM python:3.13-slim

WORKDIR /app

RUN mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix

# Install ffmpeg, Xvfb and system dependencies required by Chromium (patchright)
RUN apt-get update && apt-get install -y --no-install-recommends \
    ffmpeg \
    xvfb \
    libnss3 \
    libnspr4 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libcups2 \
    libxkbcommon0 \
    libxcomposite1 \
    libxdamage1 \
    libxfixes3 \
    libxrandr2 \
    libgbm1 \
    libpango-1.0-0 \
    libcairo2 \
    libasound2 \
    libx11-6 \
    libx11-xcb1 \
    libxcb1 \
    libxext6 \
    && rm -rf /var/lib/apt/lists/*

# Create an unprivileged user and pre-create the app/runtime directories it needs
# This avoids running the app as root and prevents permission issues at runtime
RUN adduser --disabled-password --gecos "" aniworld \
    && mkdir -p /app/Downloads /home/aniworld/.aniworld \
    && chown -R aniworld:aniworld /app /home/aniworld

# Container-friendly Python defaults:
# - Disable .pyc bytecode writes (keeps layers/volumes cleaner)
# - Unbuffer stdout/stderr so logs appear immediately
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

# Default download directory used by the application
ENV ANIWORLD_DOWNLOAD_PATH=/app/Downloads

# Virtual display for headless Chromium (patchright) — headed mode works via Xvfb
ENV DISPLAY=:99

# Copy packaging metadata first to maximize Docker layer cache hits for dependency installs
COPY pyproject.toml /app/
COPY README.md LICENSE MANIFEST.in /app/

# Keep pip current
RUN pip install --no-cache-dir --upgrade pip

# Copy the application source code
COPY src/ /app/src/

# Install the project into the image
RUN pip install --no-cache-dir .

# Pre-install patchright Chromium into the image so it's available at runtime
RUN python -m patchright install chromium

# Ensure the runtime directories are still writable after COPY overwrote ownership
RUN chown -R aniworld:aniworld /app/Downloads /home/aniworld/.aniworld

# Drop privileges for runtime
USER aniworld

# Expose the web UI port
EXPOSE 8080

# Start Xvfb virtual display on :99, then launch the web UI
CMD Xvfb :99 -screen 0 1280x720x24 -nolisten tcp & sleep 1 && exec aniworld --web-ui --web-expose --no-browser --web-port 8080

================================================
FILE: LICENSE
================================================
Copyright (c) 2024-2026 phoenixthrush, SiroxCW, Tmaster055

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.


================================================
FILE: MANIFEST.in
================================================
include src/aniworld/.env.example
include src/aniworld/ascii/ASCII.txt
include src/aniworld/aniskip/scripts/aniskip.lua
include src/aniworld/aniskip/scripts/autoexit.lua
include src/aniworld/aniskip/scripts/autostart.lua
recursive-include src/aniworld/web/templates *.html
recursive-include src/aniworld/web/static *.css *.js

================================================
FILE: README.md
================================================
<a id="readme-top"></a>

# AniWorld Downloader v4

AniWorld Downloader is a cross-platform tool for streaming and downloading anime from aniworld.to, as well as series from s.to. It runs on Windows, macOS, and Linux, providing a seamless experience for offline viewing or instant playback.

![GitHub Release](https://img.shields.io/github/v/release/phoenixthrush/AniWorld-Downloader)
[![PyPI Downloads](https://static.pepy.tech/badge/aniworld)](https://pepy.tech/projects/aniworld)
![PyPI - Downloads](https://img.shields.io/pypi/dm/aniworld)
![GitHub License](https://img.shields.io/github/license/phoenixthrush/AniWorld-Downloader)
![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/phoenixthrush/AniWorld-Downloader)
![GitHub Repo stars](https://img.shields.io/github/stars/phoenixthrush/AniWorld-Downloader)
![GitHub forks](https://img.shields.io/github/forks/phoenixthrush/AniWorld-Downloader)

Menu | WebUI (AniWorld) | WebUI (SerienStream)
:-------------------------:|:-------------------------:|:-------------------------:
![AniWorld Downloader - Demo](https://github.com/phoenixthrush/AniWorld-Downloader/blob/models/.github/assets/demo.png?raw=true) | ![AniWorld Downloader - Demo](https://github.com/phoenixthrush/AniWorld-Downloader/blob/models/.github/assets/demo-aniworld.png?raw=true) | ![AniWorld Downloader - Demo](https://github.com/phoenixthrush/AniWorld-Downloader/blob/models/.github/assets/demo-serienstream.png?raw=true)

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## TL;DR - Quick Start

```bash
# Install stable release
pip install -U aniworld

# Or install latest GitHub commit
pip install --upgrade git+https://github.com/phoenixthrush/AniWorld-Downloader.git@models#egg=aniworld

# Launch AniWorld Downloader
aniworld

# Using WebUI
aniworld -w
```

> **Tip**: Use the stable release for general use. The GitHub version includes the latest features and fixes but may be less stable.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Still in Development

This project is actively being improved. Current work in progress includes:

- [ ] Split Web UI SSO dependencies into separate `extras` section
- [ ] Implement `keep-watching` argument for continuous playback
- [ ] Review and optimize dependency manager on Windows
- [ ] Fix Nuitka build crash: use Python 3.12 (non-MSVC builds unsupported on newer versions)
- [ ] Remove empty lines below actions when running `docker run -it`

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Features

- **Downloading** – Grab full series, individual seasons, or single episodes for offline viewing
- **Streaming** – Watch episodes instantly using **mpv**, **IINA**, or **Syncplay**
- **Auto-Next Playback** – Seamlessly move to the next episode without interruption
- **Multiple Providers** – Stream from various sources on **aniworld.to** and **s.to**
- **Language Preferences** – Switch between **German Dub**, **English Sub**, or **German Sub**
- **Muxing** – Automatically combine video and audio streams into a single file
- **AniSkip Integration** – Skip intros and outros on AniWorld for a smoother experience
- **Group Watching** – Sync anime and series sessions with friends via **Syncplay**
- **Web Interface** – Browse, download, and manage your queue with a modern web UI
- **Docker Ready** – Deploy easily using **Docker** or **Docker Compose**

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Supported Providers

| Provider | Status | Last Tested |
| --- | --- | --- |
| VOE | ✅ Working | 02/26 |
| Vidoza | ✅ Working | 02/26 |
| Vidmoly | ✅ Working | 02/26 |
| Filemoon | ❌ Broken | 02/26 |
| Doodstream | ❌ Broken | 02/26 |
| Hanime | ⏳ Not Implemented | — |
| LoadX | ⏳ Not Implemented | — |
| Luluvdo | ⏳ Not Implemented | — |
| Streamtape | ⏳ Not Implemented | — |

### Currently Prioritized Providers

- **AniWorld** – VOE, Filemoon, Vidmoly
- **SerienStream** – VOE, Vidoza

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Docker

Build the AniWorld Downloader Docker image:

```bash
docker build -t aniworld .
```

### Running the Container

- **macOS / Linux (bash/zsh):**

```bash
docker run -it --rm \
  -v "${PWD}/Downloads:/app/Downloads" \
  aniworld python -m aniworld
```

- **Windows (PowerShell):**

```powershell
docker run -it --rm `
  -v "${PWD}\Downloads:/app/Downloads" `
  aniworld python -m aniworld
```

- **Windows (CMD):**

```cmd
docker run -it --rm ^
  -v "%cd%\Downloads:/app/Downloads" ^
  aniworld python -m aniworld
```

> **Note:**
> Mount your local `Downloads` folder to `/app/Downloads` in the container to save downloaded episodes. You can adjust the host path as needed.

### Docker Compose (with Web UI)

Start AniWorld Downloader using Docker Compose:

```bash
docker-compose up -d --build
```

This command will:

- **Build the Docker image** if it hasn’t been built yet
- **Start the container** in detached mode (`-d`)
- Enable the **Web UI** for easier interaction
- Automatically **restart the container unless stopped manually** (`restart: unless-stopped`)

To stop the container:

```bash
docker-compose down
```

> **Tip:** Ensure your `docker-compose.yml` correctly configures volumes and ports if you want to persist downloads or access the Web UI externally.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Documentation

For full user guides, tutorials, and troubleshooting, visit the [official documentation](https://www.phoenixthrush.com/AniWorld-Downloader-Docs/).

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Contributing

Contributions to AniWorld Downloader are **highly appreciated**! You can help improve the project in several ways:

- **Report Bugs** – Identify and report issues to improve functionality
- **Suggest Features** – Share ideas to expand the tool's capabilities
- **Submit Pull Requests** – Contribute code to fix bugs or add new features
- **Improve Documentation** – Help enhance user guides, tutorials, and technical documentation

Before submitting contributions, please check the repository for existing issues or feature requests to avoid duplicates.

### Contributors

<a href="https://github.com/phoenixthrush/AniWorld-Downloader/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=phoenixthrush/AniWorld-Downloader" alt="Contributors" />
</a>

- **Lulu** (since Sep 14, 2024)  
  [![wakatime](https://wakatime.com/badge/user/ebc8f6ad-7a1c-4f3a-ad43-cc402feab5fc/project/f39b2952-8865-4176-8ccc-4716e73d0df3.svg)](https://wakatime.com/badge/user/ebc8f6ad-7a1c-4f3a-ad43-cc402feab5fc/project/f39b2952-8865-4176-8ccc-4716e73d0df3)

- **Tmaster055** (since Oct 21, 2024)  
  [![Wakatime Badge](https://wakatime.com/badge/user/79a1926c-65a1-4f1c-baf3-368712ebbf97/project/5f191c34-1ee2-4850-95c3-8d85d516c449.svg)](https://wakatime.com/badge/user/79a1926c-65a1-4f1c-baf3-368712ebbf97/project/5f191c34-1ee2-4850-95c3-8d85d516c449.svg)

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Dependencies

AniWorld Downloader uses a small set of Python packages for networking, terminal UI, media handling, web features, authentication, and configuration.

### Core dependencies

- **niquests** – HTTP requests
- **npyscreen** – Text-based terminal UI
- **ffmpeg-python** – Python bindings for FFmpeg (requires FFmpeg installed on your system)
- **python-dotenv** – Loads environment variables from a `.env` file
- **rich** – Styled terminal output
- **fake-useragent** – Generates user-agent strings
- **packaging** – Version parsing and comparison
- **cryptography** – Cryptographic utilities
- **patchright** – Browser automation support for captcha handling

### Web / server dependencies

- **requests** – Standard HTTP library
- **flask** – Web framework
- **flask-wtf** – Forms and CSRF protection for Flask
- **authlib** – OAuth and authentication helpers
- **waitress** – Production WSGI server

### Platform-specific dependencies

- **windows-curses** – Enables curses support for `npyscreen` on Windows (installed only on Windows and only for Python versions below 3.14)

All dependencies are installed automatically when AniWorld Downloader is installed with `pip`.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Credits

AniWorld Downloader builds upon the work of several outstanding open-source projects:

- **[mpv](https://github.com/mpv-player/mpv.git)** – A versatile media player used for seamless video streaming
- **[IINA](https://github.com/iina/iina.git)** – Modern macOS media player built on mpv, offering a sleek interface and advanced playback features
- **[Syncplay](https://github.com/Syncplay/syncplay.git)** – Enables synchronized playback sessions with friends
- **[Anime4K](https://github.com/bloc97/Anime4K)** – Real-time upscaler for enhancing anime video quality
- **[Aniskip](https://api.aniskip.com/api-docs)** – Provides opening and ending skip times for the Aniskip extension

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Other Cool Projects

- **[Jellyfin AniWorld Downloader](https://github.com/SiroxCW/Jellyfin-AniWorld-Downloader)** by **[SiroxCW](https://github.com/SiroxCW)** – A Jellyfin plugin that lets you browse and download anime & series directly from AniWorld, fully integrated into your media server.

- **[AniBridge](https://github.com/Zzackllack/AniBridge)** by **[Zzackllack](https://github.com/Zzackllack)** – A minimal FastAPI service that bridges anime and series streaming catalogues (AniWorld, SerienStream/s.to, MegaKino) with automation tools.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Support

If you need help with AniWorld Downloader, you have several options:

- **Submit an issue** on the [GitHub Issues](https://github.com/phoenixthrush/AniWorld-Downloader/issues) page – preferred for installation problems, bug reports, or feature requests, as it helps others benefit from shared solutions
- **Contact directly** via email at [contact@phoenixthrush.com](mailto:contact@phoenixthrush.com) **or on our Discord server**. [Join here](https://discord.gg/BfDvrKd8V5)

While email support is available, opening a GitHub issue is encouraged whenever possible.

If you find AniWorld Downloader useful, please star the repository on GitHub. Your support is greatly appreciated and motivates continued development.

Thank you for using AniWorld Downloader!

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Legal Disclaimer

AniWorld Downloader is a **client-side** tool that enables access to content hosted on third-party websites. It **does not host, upload, store, or distribute any media itself**.

This software is **not intended to promote piracy or copyright infringement**. You are solely responsible for how you use AniWorld Downloader and for ensuring that your use **complies with applicable laws** and the **terms of service of the websites you access**.

The developer provides this project **"as is"** and is **not responsible for**:

- Third-party content
- External links
- The availability, accuracy, legality, or reliability of any third-party service

If you have concerns about specific content, **contact the relevant website owner, administrator, or hosting provider**.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=phoenixthrush/AniWorld-Downloader&type=Date)](https://star-history.com/#phoenixthrush/AniWorld-Downloader&Date)

<p align="right">(<a href="#readme-top">back to top</a>)</p>

## License

This project is licensed under the **[MIT License](LICENSE)**.
For full terms and conditions, please see the LICENSE file included with this project.


================================================
FILE: docker-compose.yaml
================================================
services:
  aniworld:
    image: ghcr.io/phoenixthrush/aniworld-downloader:latest
    container_name: aniworld-downloader
  # build: .
    ports:
      - "8080:8080"
    volumes:
      # Persist downloads on the host so they survive container recreation.
      - ./Downloads:/app/Downloads
      # Persist application config and auth database across restarts.
      - aniworld-data:/home/aniworld/.aniworld

    # NOTE: Create ./Downloads before running: mkdir -p Downloads
    # Docker creates missing bind-mount directories as root, causing permission errors.

    # environment:
    # --- General settings (uncomment the environment block to customise) ---
    #   ANIWORLD_LANGUAGE: "German Dub"               # Default language (German Dub | English Sub | German Sub | English Dub)
    #   ANIWORLD_PROVIDER: "VOE"                      # Default streaming provider
    #   ANIWORLD_NAMING_TEMPLATE: "{title} ({year}) [imdbid-{imdbid}]/Season {season}/{title} S{season}E{episode}.mkv"
    #   ANIWORLD_VIDEO_CODEC: "copy"                  # Video codec: copy | h264 | h265 | av1
    #   ANIWORLD_LANG_SEPARATION: "0"                 # Separate downloads into language subfolders (german-dub/, english-sub/)
    #   ANIWORLD_DISABLE_ENGLISH_SUB: "0"             # Block English Sub downloads entirely
    #   ANIWORLD_DEBUG_MODE: "0"                      # Enable debug logging

    # --- Authentication (uncomment the environment block to enable) ---
    #   ANIWORLD_WEB_AUTH: "1"                        # Enable local username/password login
    #   ANIWORLD_WEB_ADMIN_USER: ""                   # Auto-create admin on first run (skip setup page)
    #   ANIWORLD_WEB_ADMIN_PASS: ""                   # Password for auto-created admin
    #   ANIWORLD_WEB_SSO: "1"                         # Enable OIDC SSO login button
    #   ANIWORLD_WEB_FORCE_SSO: "1"                   # SSO-only mode (no local accounts)
    #   # OIDC / SSO provider settings (required when SSO is enabled)
    #   ANIWORLD_OIDC_ISSUER_URL: "https://keycloak.example.com/realms/myrealm"
    #   ANIWORLD_OIDC_CLIENT_ID: "aniworld"
    #   ANIWORLD_OIDC_CLIENT_SECRET: "secret"
    #   ANIWORLD_OIDC_DISPLAY_NAME: "SSO"             # Label shown on login button
    #   ANIWORLD_OIDC_ADMIN_USER: ""                  # SSO username auto-promoted to admin
    #   ANIWORLD_OIDC_ADMIN_SUBJECT: ""               # OIDC subject claim for auto-admin (preferred over username)
    #   ANIWORLD_WEB_BASE_URL: "https://aniworld.example.com"  # External URL for redirects

    restart: unless-stopped

volumes:
  aniworld-data:


================================================
FILE: examples/aniworld_to/aniworld_episode.py
================================================
from aniworld.config import Audio, Subtitles
from aniworld.models import AniworldEpisode

episode_url = "https://aniworld.to/anime/stream/highschool-dxd/staffel-1/episode-1"

episode = AniworldEpisode(episode_url)

print("=== EPISODE INFO ===")
print("URL:", episode.url)
print("Title DE:", episode.title_de)
print("Title EN:", episode.title_en)
print("Episode Number:", episode.episode_number)
print("Provider Data:", episode.provider_data)
print("Selected Path:", episode.selected_path)
print("Selected Language:", episode.selected_language)
print("Selected Provider:", episode.selected_provider)
print("Redirect URL:", episode.redirect_url)
print("Provider URL:", episode.provider_url)
print("Stream URL:", episode.stream_url)
print("Base Folder:", episode._base_folder)
print("Folder Path:", episode._folder_path)
print("File Name:", episode._file_name)
print("File Extension:", episode._file_extension)
print("Episode Path:", episode._episode_path)
print("Is Movie:", episode.is_movie)
print("Is Downloaded:", episode.is_downloaded)
print("Skip Times:", episode.skip_times)
print()

print("=== SERIES INFO ===")
print("Title:", episode.series.title)
print()

print("=== SEASON INFO ===")
print("Season URL:", episode.season.url)
print()

print("=== CUSTOM PROVIDER LINK ===")
language = (Audio.GERMAN, Subtitles.NONE)
provider = "VOE"

provider_link = episode.provider_link(language=language, provider=provider)
print("Provider Link:", provider_link)

# episode.download()
# episode.watch()
# episode.syncplay()


================================================
FILE: examples/aniworld_to/aniworld_episode_aniskip.py
================================================
import os

from aniworld.models import AniworldEpisode

episode = AniworldEpisode(
    "https://aniworld.to/anime/stream/highschool-dxd/staffel-1/episode-1"
)

# Enable AniSkip feature
os.environ["ANIWORLD_ANISKIP"] = "1"

# Watch the episode with AniSkip enabled
episode.watch()


================================================
FILE: examples/aniworld_to/aniworld_episode_muxing.py
================================================
from aniworld.models import AniworldEpisode

url = "https://aniworld.to/anime/stream/highschool-dxd/staffel-1/episode-1"

# ----------------------------
# Language variations
# ----------------------------
variations = [
    ("German Dub", "VOE"),  # German audio, no subtitles
    ("English Sub", "VOE"),  # Japanese audio, English subtitles
    ("German Sub", "VOE"),  # Japanese audio, German subtitles
]

print("Downloading all available variations into a single MKV file...")

for language, provider in variations:
    print(f"Downloading: {language} via {provider}")

    episode = AniworldEpisode(
        url=url, selected_language=language, selected_provider=provider
    )

    # Download will mux everything into one MKV file
    episode.download()

print("Done!")


================================================
FILE: examples/aniworld_to/aniworld_season.py
================================================
from aniworld.models import AniworldSeason

season_url = "https://aniworld.to/anime/stream/highschool-dxd/staffel-1"

season = AniworldSeason(season_url)

print("=== SEASON INFO ===")
print("URL:", season.url)
print("Are Movies:", season.are_movies)
print("Season Number:", season.season_number)
print("Episode Count:", season.episode_count)
print("Episodes:", season.episodes)
print()

print("=== SERIES INFO ===")
print("Title:", season.series.title)
print()

# season.download()
# season.watch()
# season.syncplay()


================================================
FILE: examples/aniworld_to/aniworld_series.py
================================================
from aniworld.models import AniworldSeries

url = "https://aniworld.to/anime/stream/highschool-dxd"

series = AniworldSeries(url)

print("=== SERIES INFO ===")
print("URL:", series.url)
print("Title:", series.title)
print("Title Clean:", series.title_cleaned)
print("Description:", series.description)
print("Genres:", series.genres)
print("Release year:", series.release_year)
print("Poster URL:", series.poster_url)
print("Directors:", series.directors)
print("Actors:", series.actors)
print("Producer:", series.producer)
print("Country:", series.country)
print("Age rating:", series.age_rating)
print("Rating:", series.rating)
print("IMDB ID:", series.imdb)
print("MAL ID:", series.mal_id)
print("Has movies:", series.has_movies)
print("Seasons:", series.seasons)
print("Season count:", series.season_count)
print()

# series.download()
# series.watch()
# series.syncplay()


================================================
FILE: examples/s_to/serienstream_episode.py
================================================
from aniworld.config import Audio, Subtitles
from aniworld.models import SerienstreamEpisode

episode_url = "https://serienstream.to/serie/american-horror-story-die-dunkle-seite-in-dir/staffel-1/episode-1"

episode = SerienstreamEpisode(episode_url)

print("=== EPISODE INFO ===")
print("URL:", episode.url)
print("Title DE:", episode.title_de)
print("Title EN:", episode.title_en)
print("Episode Number:", episode.episode_number)
print("Provider Data:", episode.provider_data)
print("Selected Path:", episode.selected_path)
print("Selected Language:", episode.selected_language)
print("Selected Provider:", episode.selected_provider)
print("Redirect URL:", episode.redirect_url)
print("Provider URL:", episode.provider_url)
print("Stream URL:", episode.stream_url)
print("Base Folder:", episode._base_folder)
print("Folder Path:", episode._folder_path)
print("File Name:", episode._file_name)
print("File Extension:", episode._file_extension)
print("Episode Path:", episode._episode_path)
print("Is Downloaded:", episode.is_downloaded)
print()

print("=== SERIES INFO ===")
print("Title:", episode.series.title)
print()

print("=== SEASON INFO ===")
print("Season URL:", episode.season.url)
print()

print("=== CUSTOM PROVIDER LINK ===")
language = (Audio.GERMAN, Subtitles.NONE)
provider = "VOE"

provider_link = episode.provider_link(language=language, provider=provider)
print("Provider Link:", provider_link)

# episode.download()
# episode.watch()
# episode.syncplay()


================================================
FILE: examples/s_to/serienstream_episode_muxing.py
================================================
from aniworld.models import SerienstreamEpisode

url = "https://s.to/serie/american-horror-story-die-dunkle-seite-in-dir/staffel-1/episode-1"

# ----------------------------
# Language variations
# ----------------------------
variations = [
    ("German Dub", "VOE"),  # German audio, no subtitles
    ("English Dub", "VOE"),  # English audio, no subtitles
]

print("Downloading all available variations into a single MKV file...")

for language, provider in variations:
    print(f"Downloading: {language} via {provider}")

    episode = SerienstreamEpisode(
        url=url, selected_language=language, selected_provider=provider
    )

    # Download will mux everything into one MKV file
    episode.download()

print("Done!")


================================================
FILE: examples/s_to/serienstream_season.py
================================================
from aniworld.models import SerienstreamSeason

season_url = "https://serienstream.to/serie/american-horror-story-die-dunkle-seite-in-dir/staffel-1"

season = SerienstreamSeason(season_url)

print("=== SEASON INFO ===")
print("URL:", season.url)
print("Season Number:", season.season_number)
print("Episode Count:", season.episode_count)
print("Episodes:", season.episodes)
print()

print("=== SERIES INFO ===")
print("Title:", season.series.title)
print()

# season.download()
# season.watch()
# season.syncplay()


================================================
FILE: examples/s_to/serienstream_series.py
================================================
from aniworld.models import SerienstreamSeries

series_url = (
    "https://serienstream.to/serie/american-horror-story-die-dunkle-seite-in-dir"
)

series = SerienstreamSeries(series_url)

print("=== SERIES INFO ===")
print("URL:", series.url)
print("Title:", series.title)
print("Title Clean:", series.title_cleaned)
print("Description:", series.description)
print("Genres:", series.genres)
print("Release year:", series.release_year)
print("Poster URL:", series.poster_url)
print("Directors:", series.directors)
print("Actors:", series.actors)
print("Producer:", series.producer)
print("Country:", series.country)
print("Age rating:", series.age_rating)
print("IMDB ID:", series.imdb)
print("Seasons:", series.seasons)
print("Number of seasons:", series.season_count)
print()

# series.download()
# series.watch()
# series.syncplay()


================================================
FILE: pyproject.toml
================================================
[project]
name = "aniworld"
version = "4.3.2"
authors = [{ name = "Phoenixthrush UwU", email = "contact@phoenixthrush.com" }]
maintainers = [
  { name = "Phoenixthrush UwU", email = "contact@phoenixthrush.com" },
]
description = "AniWorld-Downloader is a command-line tool for downloading and streaming movies and series."
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
  "niquests",
  "npyscreen",
  "ffmpeg-python",
  "python-dotenv",
  "rich",
  "fake-useragent",
  "flask",
  "flask-wtf",
  "authlib",
  "requests",
  "waitress",
  "packaging",
  "cryptography",
  "patchright",
  'windows-curses<3.14; sys_platform == "win32" and python_version < "3.14"', # npyscreen on Windows
]

classifiers = [
  "Programming Language :: Python :: 3",
  "Operating System :: OS Independent",
]
license = "MIT"
license-files = ["LICENSE"]

[project.urls]
homepage = "https://github.com/phoenixthrush/AniWorld-Downloader"
changelog = "https://github.com/phoenixthrush/AniWorld-Downloader/commits/models"
documentation = "https://readthedocs.org"
bugs = "https://github.com/phoenixthrush/AniWorld-Downloader/issues"
issues = "https://github.com/phoenixthrush/AniWorld-Downloader/issues"
tracker = "https://github.com/phoenixthrush/AniWorld-Downloader/issues"
download = "https://pypi.org/project/aniworld/#files"
sponsor = "https://github.com/sponsors/phoenixthrush"
funding = "https://github.com/sponsors/phoenixthrush"
donate = "https://github.com/sponsors/phoenixthrush"
discord = "https://discord.com/invite/BfDvrKd8V5"

[tool.setuptools.package-data]
aniworld = ["web/templates/*.html", "web/static/*.css", "web/static/*.js"]

[project.scripts]
aniworld = "aniworld.__main__:main"


================================================
FILE: src/aniworld/__init__.py
================================================
from .models.aniworld_to import (
    AniworldEpisode,
    AniworldSeason,
    AniworldSeries,
)
from .models.hanime_tv import HanimeTVEpisode
from .models.hianime_to import HiAnimeEpisode, HiAnimeSeason, HiAnimeSeries
from .models.s_to import SerienstreamEpisode, SerienstreamSeason, SerienstreamSeries

__all__ = [
    "AniworldSeries",
    "AniworldSeason",
    "AniworldEpisode",
    "HanimeTVEpisode",
    "SerienstreamSeries",
    "SerienstreamSeason",
    "SerienstreamEpisode",
    "HiAnimeSeries",
    "HiAnimeSeason",
    "HiAnimeEpisode",
]


================================================
FILE: src/aniworld/__main__.py
================================================
# ========================
# Nuitka project configuration
# ========================

# Basic flags
# nuitka-project: --static-libpython=no
# nuitka-project: --assume-yes-for-downloads
# nuitka-project: --python-flag=-m

# Include hidden imports (dynamically loaded modules that Nuitka can't detect)
# nuitka-project: --include-package=urllib3.contrib
# nuitka-project: --include-package=aniworld.extractors

# Include data files/directories
# nuitka-project: --include-data-dir=src/aniworld/web/templates=aniworld/web/templates
# nuitka-project: --include-data-dir=src/aniworld/web/static=aniworld/web/static
# nuitka-project: --include-data-file=src/aniworld/.env.example=aniworld/.env.example
# nuitka-project: --include-data-file=src/aniworld/ascii/ASCII.txt=aniworld/ascii/ASCII.txt
# nuitka-project: --include-data-file=src/aniworld/aniskip/scripts/aniskip.lua=aniworld/aniskip/scripts/aniskip.lua
# nuitka-project: --include-data-file=src/aniworld/aniskip/scripts/autoexit.lua=aniworld/aniskip/scripts/autoexit.lua
# nuitka-project: --include-data-file=src/aniworld/aniskip/scripts/autostart.lua=aniworld/aniskip/scripts/autostart.lua

# Platform-specific flags
# nuitka-project-if: {OS} == "Darwin":
#    nuitka-project: --standalone
#    nuitka-project: --macos-create-app-bundle
#    nuitka-project: --macos-app-name=AniWorld
#    nuitka-project: --macos-app-icon=src/aniworld/nuitka/icon.webp

# nuitka-project-if: {OS} in ("Windows", "Linux", "FreeBSD"):
#    nuitka-project: --onefile
#    nuitka-project: --windows-icon-from-ico=src/aniworld/nuitka/icon.webp

# ========================
# Python entrypoint
# ========================

import sys

from .entry import aniworld

sys.exit(aniworld())


================================================
FILE: src/aniworld/anime4k/__init__.py
================================================
from .anime4k import anime4k

__all__ = ["anime4k"]


================================================
FILE: src/aniworld/anime4k/anime4k.py
================================================
import shutil
import sys
from pathlib import Path

try:
    from ..common import get_latest_github_release, unzip
    from ..config import ANIWORLD_CONFIG_DIR, GLOBAL_SESSION, MPV_CONFIG_DIR, logger
except ImportError:
    from aniworld.common import get_latest_github_release, unzip
    from aniworld.config import (
        ANIWORLD_CONFIG_DIR,
        GLOBAL_SESSION,
        MPV_CONFIG_DIR,
        logger,
    )


def get_anime4k_folder_names():
    """Return platform-specific Anime4K folder names."""
    platform_folders = {
        "win": {"low": "GLSL_Windows_Low-end", "high": "GLSL_Windows_High-end"},
        "linux": {"low": "GLSL_Mac_Linux_Low-end", "high": "GLSL_Mac_Linux_High-end"},
        "darwin": {"low": "GLSL_Mac_Linux_Low-end", "high": "GLSL_Mac_Linux_High-end"},
    }

    for key, folders in platform_folders.items():
        if sys.platform.startswith(key):
            return folders

    raise RuntimeError(f"Unsupported platform: {sys.platform}")


def get_anime4k_urls():
    """Return platform-specific Anime4K GLSL URLs."""
    repo = "Tama47/Anime4K"
    release = get_latest_github_release(repo)

    folder_names = get_anime4k_folder_names()

    base = f"https://github.com/{repo}/releases/download/{release}/"
    return {
        "low": base + folder_names["low"] + ".zip",
        "high": base + folder_names["high"] + ".zip",
    }


def download_anime4k(target_dir=None, mode="high"):
    """Download Anime4K GLSL assets only if not already extracted."""
    target_dir = Path(target_dir or ANIWORLD_CONFIG_DIR) / "Anime4K"
    target_dir.mkdir(parents=True, exist_ok=True)

    if mode == "remove":
        if target_dir.exists():
            shutil.rmtree(target_dir)
            logger.debug(f"[REMOVED] Anime4K directory: {target_dir}")
        return []

    urls = get_anime4k_urls()
    if mode not in urls:
        raise ValueError(f"Invalid mode '{mode}'. Use 'high', 'low', or 'remove'.")

    downloaded_files = []
    url = urls[mode]
    filename = Path(url).name
    extracted_dir = target_dir / Path(filename).stem

    if extracted_dir.exists():
        logger.debug(f"{extracted_dir} exists, skipping download of {filename}")
        downloaded_files.append(target_dir / filename)
    else:
        filepath = target_dir / filename
        logger.debug(f"Downloading {filename}...")
        with GLOBAL_SESSION.get(url, stream=True) as response:
            response.raise_for_status()
            with open(filepath, "wb") as f:
                shutil.copyfileobj(response.raw, f)
        logger.debug(f"Downloaded {filename} to {target_dir}")
        downloaded_files.append(filepath)

    return downloaded_files


def extract_anime4k(files, target_dir=None):
    """Extract downloaded zip files and clean up."""
    target_dir = Path(target_dir or ANIWORLD_CONFIG_DIR) / "Anime4K"
    extracted_dirs = []

    for filepath in files:
        extracted_dir = target_dir / filepath.stem
        if extracted_dir.exists():
            logger.debug(f"{extracted_dir} exists, skipping extraction.")
        else:
            unzip(filepath, extracted_dir)
            macosx_dir = extracted_dir / "__MACOSX"
            if macosx_dir.exists():
                shutil.rmtree(macosx_dir)
            logger.debug(f"Extracted {filepath.name} -> {extracted_dir}")
        filepath.unlink(missing_ok=True)
        extracted_dirs.append(extracted_dir)

    return extracted_dirs


def detect_current_mode():
    """Detect the currently installed Anime4K mode from input.conf."""
    input_conf = MPV_CONFIG_DIR / "input.conf"
    if not input_conf.exists():
        return None

    with open(input_conf, "r", encoding="utf-8") as f:
        content = f.read()

    if "# Optimized shaders for lower-end GPU:" in content:
        return "low"
    if "# Optimized shaders for higher-end GPU:" in content:
        return "high"
    return None


def copy_with_markers(src_file, dst_file):
    """Copy a config file and wrap it with Anime4K markers."""
    with open(src_file, "r", encoding="utf-8") as f:
        content = f.read()

    content = f"# BEGIN Anime4K CONFIG\n{content}\n# END Anime4K CONFIG\n"

    with open(dst_file, "w", encoding="utf-8") as f:
        f.write(content)

    logger.debug(f"Copied {src_file} -> {dst_file} with markers")


def setup_anime4k(mode="low"):
    """Copy shaders and config files to MPV directory."""
    mpv_shaders_dir = MPV_CONFIG_DIR / "shaders"
    mpv_shaders_dir.mkdir(parents=True, exist_ok=True)

    # Get the correct folder names for this platform
    mode_folders = get_anime4k_folder_names()

    if mode not in mode_folders:
        logger.error(f"Unknown mode: {mode}. Valid modes: {list(mode_folders.keys())}")
        return

    source_dir = Path(ANIWORLD_CONFIG_DIR) / "Anime4K" / mode_folders[mode]
    if not source_dir.exists():
        logger.warning(f"{source_dir} does not exist. Nothing to set up.")
        return

    # Use the specific mode directory instead of iterating through all folders
    folder = source_dir

    # Copy shaders
    shaders_dir = folder / "shaders"
    if shaders_dir.exists():
        for shader in shaders_dir.iterdir():
            dst_file = mpv_shaders_dir / shader.name
            if not dst_file.exists():
                shutil.copy(shader, dst_file)
                logger.debug(f"Copied shader {shader} -> {dst_file}")

    # Copy configs with markers
    for conf_name in ("mpv.conf", "input.conf"):
        src_conf = folder / conf_name
        dst_conf = MPV_CONFIG_DIR / conf_name
        if src_conf.exists() and not dst_conf.exists():
            copy_with_markers(src_conf, dst_conf)


def remove_anime4k_lines(file_path):
    """Remove Anime4K block from a config file."""
    if not file_path.exists():
        return

    start_marker = "# BEGIN Anime4K CONFIG"
    end_marker = "# END Anime4K CONFIG"

    with open(file_path, "r", encoding="utf-8") as f:
        lines = f.readlines()

    final_lines = []
    in_block = False
    for line in lines:
        if line.strip() == start_marker:
            in_block = True
            continue
        if line.strip() == end_marker:
            in_block = False
            continue
        if not in_block:
            final_lines.append(line)

    if not final_lines:
        file_path.unlink()
        logger.debug(f"[REMOVED] {file_path} (empty after removing Anime4K block)")
    else:
        with open(file_path, "w", encoding="utf-8") as f:
            f.writelines(final_lines)
        logger.debug(f"[REMOVED] Anime4K lines from {file_path}")


def anime4k(mode="high"):
    """Main entry point for Anime4K setup and removal with mode detection."""
    mpv_shaders_dir = MPV_CONFIG_DIR / "shaders"

    if mode not in ("high", "low", "remove"):
        raise ValueError(f"Invalid mode '{mode}'. Use 'high', 'low', or 'remove'.")

    # Remove mode
    if mode == "remove":
        if mpv_shaders_dir.exists():
            for shader in mpv_shaders_dir.iterdir():
                if shader.is_file() and shader.name.startswith("Anime4K_"):
                    shader.unlink()
                    logger.debug(f"[REMOVED] {shader}")
            if not any(mpv_shaders_dir.iterdir()):
                mpv_shaders_dir.rmdir()
                logger.debug(f"[REMOVED] Empty shaders folder: {mpv_shaders_dir}")

        for conf_name in ("mpv.conf", "input.conf"):
            remove_anime4k_lines(MPV_CONFIG_DIR / conf_name)

        logger.debug("Anime4K assets, shaders, and configs removed successfully.")
        return

    # Detect current installed mode
    current_mode = detect_current_mode()
    if current_mode == mode:
        logger.debug(f"Anime4K already installed in '{mode}' mode. Skipping setup.")
        return
    elif current_mode is not None and current_mode != mode:
        logger.debug(f"Switching Anime4K from '{current_mode}' to '{mode}' mode...")
        # Remove previous mode first
        anime4k(mode="remove")

    # Normal setup
    downloaded = download_anime4k(mode=mode)
    extract_anime4k(downloaded)
    setup_anime4k(mode=mode)
    logger.debug(f"Anime4K setup complete in '{mode}' mode.")


if __name__ == "__main__":
    anime4k(mode="high")  # options: "high", "low", "remove"


================================================
FILE: src/aniworld/aniskip/__init__.py
================================================
from .aniskip import build_mpv_flags, get_skip_times, setup_aniskip
from .jikan import get_all_seasons_by_query

__all__ = [
    "get_skip_times",
    "build_mpv_flags",
    "get_all_seasons_by_query",
    "setup_aniskip",
]


================================================
FILE: src/aniworld/aniskip/aniskip.py
================================================
import shutil
import tempfile
from pathlib import Path

try:
    from ..config import GLOBAL_SESSION, MPV_SCRIPTS_DIR, logger
    # from .jikan import get_all_seasons_by_query
except ImportError:
    # from aniworld.aniskip import get_all_seasons_by_query
    from aniworld.config import GLOBAL_SESSION, MPV_SCRIPTS_DIR, logger

ANISKIP_API_URL = "https://api.aniskip.com/v1/skip-times/{}/{}?types=op&types=ed"


def setup_aniskip():
    """
    Copy AniSkip Lua scripts (aniskip.lua, autoexit.lua, autostart.lua)
    to the mpv scripts directory, only if they don't already exist.
    """

    # Source folder where your Lua scripts are stored
    src_dir = Path(__file__).parent / "scripts"
    scripts_to_copy = ["aniskip.lua", "autoexit.lua", "autostart.lua"]

    # Ensure the scripts directory exists
    MPV_SCRIPTS_DIR.mkdir(parents=True, exist_ok=True)

    # Copy each Lua script if it doesn't exist in the destination
    for script in scripts_to_copy:
        src_path = src_dir / script
        dest_path = MPV_SCRIPTS_DIR / script

        if not src_path.exists():
            logger.warning(f"[WARNING] Script not found: {src_path}")
            continue

        if not dest_path.exists():
            shutil.copy2(src_path, dest_path)
            logger.debug(f"[COPIED] {script} -> {MPV_SCRIPTS_DIR}")
        else:
            logger.debug(f"[SKIPPED] {script} already exists in {MPV_SCRIPTS_DIR}")

    logger.debug("[SETUP COMPLETE] AniSkip scripts are ready in mpv scripts directory.")


def get_skip_times(mal_id: int, episode_number: int):
    url = ANISKIP_API_URL.format(mal_id, episode_number)
    res = GLOBAL_SESSION.get(url)
    if res.status_code == 200:
        return res.json()
    return None


def ftoi(seconds: float) -> int:
    """Convert seconds to milliseconds as integer."""
    return int(round(seconds * 1000))


def build_mpv_flags(skip_data) -> str:
    """
    Build MPV command flags based on AniSkip API response.
    Returns a string with --chapters-file and --script-opts options.
    """
    if not skip_data or skip_data.get("found") is not True:
        return ""

    chapters_file = tempfile.NamedTemporaryFile("w", delete=False)
    chapters_file.write(";FFMETADATA1\n")

    options_list = []

    for entry in skip_data.get("results", []):
        st = entry["interval"]["start_time"]
        ed = entry["interval"]["end_time"]
        skip_type = entry["skip_type"]  # 'op' or 'ed'

        # Write chapters format for FFMETADATA
        chapters_file.write(
            f"[CHAPTER]\nTIMEBASE=1/1000\nSTART={ftoi(st)}\nEND={ftoi(ed)}\nTITLE={skip_type.upper()}\n"
        )

        # Build script options
        options_list.append(f"skip-{skip_type}_start={st},skip-{skip_type}_end={ed}")

    chapters_file.flush()
    chapters_file.close()

    options_str = ",".join(options_list)

    return f"--chapters-file={chapters_file.name} --script-opts={options_str}"


if __name__ == "__main__":
    setup_aniskip()

    """
    query = "Highschool DxD"

    mal_ids = get_all_seasons_by_query(query)

    print(f"Found {len(mal_ids)} seasons for '{query}':\n")

    for idx, mal_id in enumerate(mal_ids, start=1):
        print(f"{idx}. MAL ID: {mal_id}")

        episode_number = 1
        skip_times = get_skip_times(mal_id, episode_number)

        try:
            mpv_flags = build_mpv_flags(skip_times)
            print("  MPV command flags:")
            print(f"    {mpv_flags}\n")
        except ValueError as e:
            print(f"  Error building MPV flags: {e}\n")
    """


================================================
FILE: src/aniworld/aniskip/jikan.py
================================================
import time

try:
    from ..config import GLOBAL_SESSION, logger
except ImportError:
    from aniworld.config import GLOBAL_SESSION, logger

JIKAN_SEARCH_URL = "https://api.jikan.moe/v4/anime"
JIKAN_ANIME_URL = "https://api.jikan.moe/v4/anime/{id}"
JIKAN_RELATIONS_URL = "https://api.jikan.moe/v4/anime/{id}/relations"

# Rate limiting delay
API_DELAY = 0.8


def search_jikan(query, sfw=False, limit=5):
    """Search Jikan API for TV anime."""
    params = {
        "q": query,
        "type": "tv",
        "sfw": str(sfw).lower(),
        "limit": limit,
        "order_by": "popularity",
        "sort": "asc",  # earliest / most popular first
    }
    try:
        logger.debug(
            f"Searching Jikan API URL: {JIKAN_SEARCH_URL} with params: {params}"
        )
        res = GLOBAL_SESSION.get(JIKAN_SEARCH_URL, params=params)
        res.raise_for_status()
        return res.json().get("data", [])
    except Exception as e:
        logger.error(f"Error searching Jikan API for query '{query}': {e}")
        return []


# Caches to avoid repeated API calls
_type_cache = {}
_relations_cache = {}


def is_tv_series(mal_id):
    """Check if an anime is a TV series using caching."""
    if mal_id in _type_cache:
        return _type_cache[mal_id]

    try:
        logger.debug(f"Fetching anime type for MAL ID: {mal_id}")
        res = GLOBAL_SESSION.get(JIKAN_ANIME_URL.format(id=mal_id))
        res.raise_for_status()
        anime_type = res.json().get("data", {}).get("type")
        is_tv = anime_type == "TV"
        _type_cache[mal_id] = is_tv
        logger.debug(f"Waiting to respect Jikan API rate limits... {API_DELAY}s")
        time.sleep(API_DELAY)
        return is_tv
    except Exception as e:
        logger.error(f"Error checking anime type for MAL ID {mal_id}: {e}")
        _type_cache[mal_id] = False
        return False


def get_anime_relations_cached(mal_id):
    """Get anime relations with caching."""
    if mal_id in _relations_cache:
        return _relations_cache[mal_id]

    try:
        logger.debug(f"Fetching relations for MAL ID: {mal_id}")
        res = GLOBAL_SESSION.get(JIKAN_RELATIONS_URL.format(id=mal_id))
        res.raise_for_status()
        relations = res.json().get("data", [])
        _relations_cache[mal_id] = relations
        logger.debug(f"Waiting to respect Jikan API rate limits... {API_DELAY}s")
        time.sleep(API_DELAY)
        return relations
    except Exception as e:
        logger.error(f"Error fetching relations for MAL ID {mal_id}: {e}")
        _relations_cache[mal_id] = []
        return []


def get_all_related_ids(season1_id):
    """Iteratively fetch all related anime IDs starting from season1_id."""
    collected = set()
    stack = [season1_id]

    while stack:
        mal_id = stack.pop()
        if mal_id in collected:
            continue
        collected.add(mal_id)

        for rel in get_anime_relations_cached(mal_id):
            if rel.get("relation") in {
                "Sequel",
                "Prequel",
                "Parent story",
                "Side story",
            }:
                for entry in rel.get("entry", []):
                    anime_id = entry.get("mal_id")
                    if (
                        anime_id
                        and is_tv_series(anime_id)
                        and anime_id not in collected
                    ):
                        stack.append(anime_id)

    return collected


def get_all_seasons_by_query(query="love is war"):
    """Fetch all seasons starting from Season 1 and follow sequels/prequels."""
    seasons = search_jikan(query)
    if not seasons:
        logger.warning(f"No TV seasons found for query: {query}")
        return []

    # Use the most popular TV series
    season1_id = next((s["mal_id"] for s in seasons if s.get("type") == "TV"), None)
    if not season1_id:
        logger.warning(f"No TV seasons found in search results for query: {query}")
        return []

    all_ids = get_all_related_ids(season1_id)
    logger.debug(f"All related MAL IDs found: {all_ids}")
    return sorted(all_ids)


if __name__ == "__main__":
    print(get_all_seasons_by_query())


================================================
FILE: src/aniworld/aniskip/scripts/aniskip.lua
================================================
local mpv = require('mp')
local mpv_options = require("mp.options")

local options = {
    op_start = 0, op_end = 0, ed_start = 0, ed_end = 0,
}
mpv_options.read_options(options, "skip")

local function skip()
    local current_time = mp.get_property_number("time-pos")
    if not current_time then return end

    if options.op_start ~= options.op_end and current_time >= options.op_start and current_time < (options.op_start + 1) then
        mp.set_property_number("time-pos", options.op_end)
    end

    if options.ed_start ~= options.ed_end and current_time >= options.ed_start and current_time < (options.ed_start + 1) then
        mp.set_property_number("time-pos", options.ed_end)
    end
end

mp.observe_property("time-pos", "number", skip)

================================================
FILE: src/aniworld/aniskip/scripts/autoexit.lua
================================================
function check_time()
    local current_time = mp.get_property_number("time-pos")
    local total_time = mp.get_property_number("duration")
    
    if current_time and total_time then
        if total_time - current_time <= 1 then
            mp.command("quit")
        end
    end
end

mp.add_periodic_timer(1, check_time)

================================================
FILE: src/aniworld/aniskip/scripts/autostart.lua
================================================
mp.register_event("file-loaded", function()
    mp.set_property("pause", "no")
end)

================================================
FILE: src/aniworld/arguments.py
================================================
import argparse
import logging
import os
import sys

from rich.console import Console
from rich.panel import Panel
from rich.text import Text

from .anime4k import anime4k
from .config import ACTION_METHODS, LANG_LABELS, SUPPORTED_PROVIDERS, VERSION
from .logger import get_logger

logger = get_logger(__name__)
console = Console()

EXAMPLES = r"""
[bold underline cyan]Command-Line Arguments Example[/]

[dim]AniWorld Downloader provides command-line options for downloading and streaming anime without relying on the interactive menu or webui.[/]

[bold yellow]Example 1: Download a Single Episode (default action)[/]
[dim]To download episode 1 of "Demon Slayer: Kimetsu no Yaiba":[/]
[green]aniworld[/] [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/]

[bold yellow]Example 2: Download Multiple Episodes (default action)[/]
[dim]To download multiple episodes of "Demon Slayer":[/]
[green]aniworld[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-2[/]

[bold yellow]Example 3: Watch Episodes with Aniskip[/]
[dim]To watch an episode while skipping intros and outros:[/]
[green]aniworld[/] [magenta]--action[/] [cyan]Watch[/] [magenta]--aniskip[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/]

[bold yellow]Example 4: Syncplay with Friends (+ Keep Watching)[/]
[dim]To Syncplay a specific episode with friends:[/]
[green]aniworld[/] [magenta]--action[/] [cyan]Syncplay[/] [magenta]--keep-watching[/] \
  [magenta]--syncplay-host[/] [cyan]syncplay.pl:8998[/] \
  [magenta]--syncplay-room[/] [cyan]"MyRoom"[/] \
  [magenta]--syncplay-username[/] [cyan]"phoenixthrush"[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/]

[dim]Language Options for Syncplay[/]

[dim]For German Dub:[/]
[green]aniworld[/] [magenta]--action[/] [cyan]Syncplay[/] [magenta]--keep-watching[/] [magenta]--language[/] [cyan]"German Dub"[/] [magenta]--aniskip[/] \
  [magenta]--syncplay-host[/] [cyan]syncplay.pl:8998[/] [magenta]--syncplay-room[/] [cyan]"MyRoom"[/] [magenta]--syncplay-username[/] [cyan]"phoenixthrush"[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/]

[dim]For English Sub:[/]
[green]aniworld[/] [magenta]--action[/] [cyan]Syncplay[/] [magenta]--keep-watching[/] [magenta]--language[/] [cyan]"English Sub"[/] [magenta]--aniskip[/] \
  [magenta]--syncplay-host[/] [cyan]syncplay.pl:8998[/] [magenta]--syncplay-room[/] [cyan]"MyRoom"[/] [magenta]--syncplay-username[/] [cyan]"phoenixthrush"[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/]

[dim]To restrict access, set a password for the room:[/]
[green]aniworld[/] [magenta]--action[/] [cyan]Syncplay[/] [magenta]--keep-watching[/] [magenta]--language[/] [cyan]"English Sub"[/] [magenta]--aniskip[/] \
  [magenta]--syncplay-host[/] [cyan]syncplay.pl:8998[/] [magenta]--syncplay-room[/] [cyan]"MyRoom"[/] [magenta]--syncplay-username[/] [cyan]"phoenixthrush"[/] \
  [magenta]--syncplay-password[/] [cyan]beans[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/]

[bold yellow]Example 5: Download with Specific Provider and Language (default action)[/]
[dim]To download using the VOE provider with English subtitles:[/]
[green]aniworld[/] [magenta]--provider[/] [cyan]VOE[/] [magenta]--language[/] [cyan]"English Sub"[/] \
  [blue]https://aniworld.to/anime/stream/demon-slayer-kimetsu-no-yaiba/staffel-1/episode-1[/]

[bold yellow]Example 6: Use an Episode File (default action)[/]
[dim]To download URLs listed in a file:[/]
[green]aniworld[/] [magenta]--episode-file[/] [cyan]test.txt[/] [magenta]--language[/] [cyan]"German Dub"[/]

[bold yellow]Example 7: Use a custom provider URL[/]
[dim]Download a provider page URL directly (resolved to a direct media URL and saved via ffmpeg).[/]
[dim]Important: you must specify --provider so the right extractor (and headers) are used.[/]
[green]aniworld[/] [magenta]--provider[/] [cyan]VOE[/] [magenta]--provider-url[/] [blue]https://voe.sx/e/ayginbzzb6bi[/]

[bold]Notes[/]
- [magenta]--aniskip[/] and [magenta]--keep-watching[/] can be combined with Watch and Syncplay.
- URLs are positional arguments, so you can paste one or many at the end of the command.
""".strip()


def parse_args():
    parser = argparse.ArgumentParser(
        prog="aniworld",
        description=(
            "AniWorld Downloader is a cross-platform tool for streaming and "
            "downloading anime from aniworld.to, as well as movies and series "
            "from s.to. It runs on Windows, macOS, and Linux, providing a "
            "seamless experience for offline viewing or instant playback."
        ),
        epilog='Run "aniworld --examples" to see more usage examples.',
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    # =========================
    # General / Core options
    # =========================
    general = parser.add_argument_group("General Options")
    general.add_argument(
        "-d", "--debug", action="store_true", help="Enable debug logging"
    )
    general.add_argument(
        "-V",
        "--version",
        action="store_true",
        help="Show version information and exit",
    )
    general.add_argument(
        "-nm",
        "--no-menu",
        action="store_true",
        help="Disable interactive menu",
    )
    general.add_argument(
        "-x",
        "--examples",
        action="store_true",
        help="Show extended command-line examples and exit",
    )

    # =========================
    # Playback / Download options
    # =========================
    playback = parser.add_argument_group("Playback & Download Options")
    playback.add_argument(
        "-a",
        "--action",
        choices=sorted(ACTION_METHODS.keys()),
        help="Choose action method",
    )
    playback.add_argument(
        "-l",
        "--language",
        choices=sorted(LANG_LABELS.values()),
        help="Choose language",
    )
    playback.add_argument(
        "-p",
        "--provider",
        choices=sorted(SUPPORTED_PROVIDERS),
        help="Choose provider",
    )

    playback.add_argument(
        "-sk",
        "--aniskip",
        action="store_true",
        help="Skip intros/outros when watching (AniSkip integration)",
    )
    playback.add_argument(
        "-kw",
        "--keep-watching",
        action="store_true",
        help="Automatically continue with the next episode",
    )
    playback.add_argument(
        "-o",
        "--output",
        help="Output file path",
    )

    # =========================
    # Discovery / Random
    # =========================
    discovery = parser.add_argument_group("Discovery Options")
    discovery.add_argument(
        "-r",
        "--random-anime",
        action="store_true",
        help="Fetch a random anime series",
    )
    discovery.add_argument(
        "-sto",
        "--use-sto-search",
        action="store_true",
        help="Prefer s.to for interactive searches.",
    )

    # =========================
    # Anime4K
    # =========================
    a4k = parser.add_argument_group("Anime4K Options")
    a4k.add_argument(
        "-A",
        "--anime4k",
        choices=["High", "Low", "Remove"],
        help="Enable Anime4K upscaling with specified mode",
    )

    # =========================
    # Input sources
    # =========================
    inputs = parser.add_argument_group("Input Options")
    inputs.add_argument(
        "-f",
        "--episode-file",
        help="Path to a text file containing episode URLs (one URL per line)",
    )
    inputs.add_argument(
        "url",
        nargs="*",
        help="URLs of series, season, or episodes",
    )

    # =========================
    # Provider direct URL (custom)
    # =========================
    provider = parser.add_argument_group("Provider URL Options")
    provider.add_argument(
        "-pu",
        "--provider-url",
        help="Custom provider URL",
    )

    # =========================
    # WebUI
    # =========================
    webui = parser.add_argument_group("WebUI Options")
    webui.add_argument(
        "-w",
        "--web-ui",
        action="store_true",
        help="Start the web UI",
    )

    webui.add_argument(
        "-wP",
        "--web-port",
        type=int,
        default=8080,
        help="Port for the web UI (default: 8080)",
    )

    webui.add_argument(
        "-wN",
        "--no-browser",
        action="store_true",
        help="Don't open the browser automatically when starting the web UI",
    )

    webui.add_argument(
        "-wE",
        "--web-expose",
        action="store_true",
        help="Bind the web UI to all interfaces (0.0.0.0) instead of localhost only",
    )

    webui.add_argument(
        "-wA",
        "--web-auth",
        action="store_true",
        help="Enable local authentication for the web UI",
    )

    webui.add_argument(
        "-wS",
        "--web-sso",
        action="store_true",
        help="Enable SSO (OIDC) login for the web UI",
    )

    webui.add_argument(
        "-wFS",
        "--web-force-sso",
        action="store_true",
        help="Force SSO-only authentication (implies --web-auth and --web-sso)",
    )

    # =========================
    # Syncplay (only meaningful with --action Syncplay)
    # =========================
    syncplay = parser.add_argument_group(
        "Syncplay Options (requires --action Syncplay)"
    )
    syncplay.add_argument(
        "-sH",
        "--syncplay-host",
        help="Specify the Syncplay server host",
    )
    syncplay.add_argument(
        "-sR",
        "--syncplay-room",
        help="Specify the Syncplay room name",
    )
    syncplay.add_argument(
        "-sU",
        "--syncplay-username",
        help="Specify the Syncplay username",
    )
    syncplay.add_argument(
        "-sP",
        "--syncplay-password",
        help="Specify the Syncplay password (if required)",
    )

    args = parser.parse_args()

    if args.examples:
        console.print(
            Panel.fit(
                Text.from_markup(EXAMPLES),
                title="[bold]aniworld --examples[/bold]",
                border_style="cyan",
            )
        )
        raise SystemExit(0)

    if args.language:
        os.environ["ANIWORLD_LANGUAGE"] = args.language

    if args.provider:
        os.environ["ANIWORLD_PROVIDER"] = args.provider

    if args.random_anime:
        os.environ["ANIWORLD_RANDOM_ANIME"] = "1"

    if args.no_menu:
        os.environ["ANIWORLD_NO_MENU"] = "1"

    if args.aniskip:
        os.environ["ANIWORLD_ANISKIP"] = "1"

    if args.keep_watching:
        os.environ["ANIWORLD_KEEP_WATCHING"] = "1"

    if args.use_sto_search:
        os.environ["ANIWORLD_USE_STO_SEARCH"] = "1"

    if args.output:
        os.environ["ANIWORLD_DOWNLOAD_PATH"] = (
            os.path.abspath(args.output)
            if not os.path.isabs(args.output)
            else args.output
        )

    if args.anime4k:
        mode = args.anime4k.lower()
        logger.debug(f"Anime4K upscaling set to: {mode}")
        anime4k(mode)

    if args.debug:
        os.environ["ANIWORLD_DEBUG_MODE"] = "1"

        logging.getLogger().setLevel(logging.DEBUG)
        for name in logging.Logger.manager.loggerDict:
            logging.getLogger(name).setLevel(logging.DEBUG)

        logger.debug("Debug mode enabled")

    if args.action == "Syncplay":
        if args.syncplay_host:
            os.environ["ANIWORLD_SYNCPLAY_HOST"] = args.syncplay_host
        if args.syncplay_room:
            os.environ["ANIWORLD_SYNCPLAY_ROOM"] = args.syncplay_room
        if args.syncplay_username:
            os.environ["ANIWORLD_SYNCPLAY_USERNAME"] = args.syncplay_username
        if args.syncplay_password:
            os.environ["ANIWORLD_SYNCPLAY_PASSWORD"] = args.syncplay_password

    if args.episode_file:
        try:
            with open(args.episode_file, "r") as f:
                for line in f:
                    u = line.strip()
                    if u:
                        args.url.append(u)
            logger.debug(f"Loaded {len(args.url)} URLs from {args.episode_file}")
        except Exception as e:
            logger.error(f"Failed to read episode file: {e}")
            sys.exit(1)

    if args.provider_url and args.provider:
        import ffmpeg

        from .config import PROVIDER_HEADERS_D
        from .extractors import provider_functions

        provider_key = (args.provider or "").strip()
        headers = PROVIDER_HEADERS_D.get(provider_key, {})

        headers_str = "".join(f"{k}: {v}\r\n" for k, v in headers.items())

        direct_link = provider_functions[
            f"get_direct_link_from_{provider_key.lower()}"
        ](args.provider_url)

        download_dir = os.getenv("ANIWORLD_DOWNLOAD_PATH", ".")
        output_path = os.path.join(download_dir, "input.mkv")

        (
            ffmpeg.input(
                direct_link,
                headers=headers_str if headers_str else None,
            )
            .output(
                output_path,
                c="copy",
                f="matroska",
            )
            .run()
        )

        sys.exit(0)

    if args.version:
        # TODO: add logic
        is_newest_version = True
        latest_version = VERSION

        version_message = (
            "You are on the latest version."
            if is_newest_version
            else f"Your version is outdated.\nPlease update to the latest version (v.{latest_version})."
        )

        cowsay = Rf"""______________________________
< AniWorld-Downloader v.{VERSION} >
------------------------------
    \   ^__^
     \  (oo)\_______
        (__)\       )\/\
            ||----w |
            ||     ||

{version_message}"""

        print(cowsay.strip())
        sys.exit(0)

    return args


================================================
FILE: src/aniworld/ascii/ASCII.txt
================================================
=== banner: LOGO ===
   _____         .__ __      __            .__       .___
  /  _  \   ____ |__/  \    /  \___________|  |    __| _/
 /  /_\  \ /    \|  \   \/\/   /  _ \_  __ \  |   / __ |
/    |    \   |  \  |\        (  <_> )  | \/  |__/ /_/ |
\____|__  /___|  /__| \__/\  / \____/|__|  |____/\____ |
        \/     \/          \/                         \/
________                      .__                    .___
\______ \   ______  _  ______ |  |   _________     __| _/___________
 |    |  \ /  _ \ \/ \/ /    \|  |  /  _ \__  \   / __ |/ __ \_  __ \
 |    `   (  <_> )     /   |  \  |_(  <_> ) __ \_/ /_/ \  ___/|  | \/
/_______  /\____/ \/\_/|___|  /____/\____/____  /\____ |\___  >__|
        \/                  \/                \/      \/    \/
=== art: LUCKY_STAR ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠳⢬⣳⣄⣠⠤⠤⠶⠶⠒⠋⠀⠀⠀⠀⠹⡀⠀⠀⠀⠀⠈⠉⠛⠲⢦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠤⠖⠋⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⢳⠦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣠⠖⠋⠀⠀⠀⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⢃⠈⠙⠲⣄⡀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢠⠞⠁⠀⠀⠀⢀⢾⠃⠀⠀⠀⠀⠀⠀⠀⠀⢢⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⣹⠮⣄⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣰⠋⠀⠀⢀⡤⡴⠃⠈⠦⣀⠀⠀⠀⠀⠀⠀⢀⣷⢸⠀⠀⠀⠀⢀⣀⠘⡄⠤⠤⢤⠔⠒⠂⠉⠁⠀⠀⠀⠑⢄⡀⠀⠀⠙⢦⡀⠀⠀⠀
    ⠀⠀⠀⠀⣼⠃⠀⠀⢠⣞⠟⠀⠀⠀⡄⠀⠉⠒⠢⣤⣤⠄⣼⢻⠸⠀⠀⠀⠀⠉⢤⠀⢿⡖⠒⠊⢦⠤⠤⣀⣀⡀⠀⠀⠀⠈⠻⡝⠲⢤⣀⠙⢦⠀⠀
    ⠀⠀⠀⢰⠃⠀⠀⣴⣿⠎⠀⠀⢀⣜⠤⠄⢲⠎⠉⠀⠀⡼⠸⠘⡄⡇⠀⠀⠀⠀⢸⠀⢸⠘⢆⠀⠘⡄⠀⠀⠀⢢⠉⠉⠀⠒⠒⠽⡄⠀⠈⠙⠮⣷⡀
    ⠀⠀⠀⡟⠀⠀⣼⢻⠧⠐⠂⠉⡜⠀⠀⡰⡟⠀⠀⠀⡰⠁⡇⠀⡇⡇⠀⠀⠀⠀⢺⠇⠀⣆⡨⢆⠀⢽⠀⠀⠀⠈⡷⡄⠀⠀⠀⠀⠹⡄⠀⠀⠀⠈⠁
    ⠀⠀⢸⠃⠀⠀⢃⠎⠀⠀⠀⣴⠃⠀⡜⠹⠁⠀⠀⡰⠁⢠⠁⠀⢸⢸⠀⠀⠀⢠⡸⢣⠔⡏⠀⠈⢆⠀⣇⠀⠀⠀⢸⠘⢆⠀⠀⠀⠀⢳⠀⠀⠀⠀⠀
    ⠀⠀⢸⠀⠀⠀⡜⠀⠀⢀⡜⡞⠀⡜⠈⠏⠀⠈⡹⠑⠒⠼⡀⠀⠀⢿⠀⠀⠀⢀⡇⠀⢇⢁⠀⠀⠈⢆⢰⠀⠀⠀⠈⡄⠈⢢⠀⠀⠀⠈⣇⠀⠀⠀⠀
    ⠀⠀⢸⡀⠀⢰⠁⠀⢀⢮⠀⠇⡜⠀⠘⠀⠀⢰⠃⠀⠀⡇⠈⠁⠀⢘⡄⠀⠀⢸⠀⠀⣘⣼⠤⠤⠤⣈⡞⡀⠀⠀⠀⡇⠰⡄⢣⡀⠀⠀⢻⠀⠀⠀⠀
    ⠀⠀⠈⡇⠀⡜⠀⢀⠎⢸⢸⢰⠁⠀⠄⠀⢠⠃⠀⠀⢸⠀⠀⠀⠀⠀⡇⠀⠀⡆⠀⠀⣶⣿⡿⠿⡛⢻⡟⡇⠀⠀⠀⡇⠀⣿⣆⢡⠀⠀⢸⡇⠀⠀⠀
    ⠀⠀⢠⡏⠀⠉⢢⡎⠀⡇⣿⠊⠀⠀⠀⢠⡏⠀⠀⠀⠎⠀⠀⠀⠀⠀⡇⠀⡸⠀⠀⠀⡇⠀⢰⡆⡇⢸⢠⢹⠀⠀⠀⡇⠀⢹⠈⢧⣣⠀⠘⡇⠀⠀⠀
    ⠀⠀⢸⡇⠀⠀⠀⡇⠀⡇⢹⠀⠀⠀⢀⡾⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⢠⠃⠀⠀⠠⠟⡯⣻⣇⢃⠇⢠⠏⡇⠀⢸⡆⠀⢸⠀⠈⢳⡀⠀⡇⠀⠀⠀
    ⠀⠀⠀⣇⠀⡔⠋⡇⠀⢱⢼⠀⠀⡂⣼⡇⢹⣶⣶⣶⣤⣤⣀⠀⠀⠀⣇⠇⠀⠀⠀⠀⣶⡭⢃⣏⡘⠀⡎⠀⠇⠀⡾⣷⠀⣼⠀⠀⠀⢻⡄⡇⠀⠀⠀
    ⠀⠀⠀⣹⠜⠋⠉⠓⢄⡏⢸⠀⠀⢳⡏⢸⠹⢀⣉⢭⣻⡽⠿⠛⠓⠀⠋⠀⠀⠀⠀⠀⠘⠛⠛⠓⠀⡄⡇⠀⢸⢰⡇⢸⡄⡟⠀⠀⠀⠀⢳⡇⠀⠀⠀
    ⠀⣠⠞⠁⠀⠀⠀⠀⠀⢙⠌⡇⠀⣿⠁⠀⡇⡗⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠀⠀⠀⠀⠀⠀⠁⠁⠀⢸⣼⠀⠈⣇⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⢸⠁⠀⠀⢀⡠⠔⠚⠉⠉⢱⣇⢸⢧⠀⠀⠸⣱⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⡤⠦⡔⠀⠀⠀⠀⠀⢀⡼⠀⠀⣼⡏⠀⠀⢹⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⢸⠀⠀⠀⠋⠀⠀⠀⢀⡠⠤⣿⣾⣇⣧⠀⠀⢫⡆⠀⠀⠀⠀⠀⠀⠀⢨⠀⠀⣠⠇⠀⠀⢀⡠⣶⠋⠀⠀⡸⣾⠁⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⢸⡄⠀⠀⠀⠀⠠⠊⠁⠀⠀⢸⢃⠘⡜⡵⡀⠈⢿⡱⢲⡤⠤⢀⣀⣀⡀⠉⠉⣀⡠⡴⠚⠉⣸⢸⠀⠀⢠⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⢧⠀⠀⠀⠀⠀⠀⠀⣀⠤⠚⠚⣤⣵⡰⡑⡄⠀⢣⡈⠳⡀⠀⠀⠀⢨⡋⠙⣆⢸⠀⠀⣰⢻⡎⠀⠀⡎⡇⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠈⢷⡀⠀⠀⠀⠀⠀⠁⠀⠀⠀⡸⢌⣳⣵⡈⢦⡀⠳⡀⠈⢦⡀⠀⠘⠏⠲⣌⠙⢒⠴⡧⣸⡇⠀⡸⢸⠇⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⢠⣿⠢⡀⠀⠀⠀⠠⠄⡖⠋⠀⠀⠙⢿⣳⡀⠑⢄⠹⣄⡀⠙⢄⡠⠤⠒⠚⡖⡇⠀⠘⣽⡇⢠⠃⢸⢀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⣾⠃⠀⠀⠀⠀⠀⢀⡼⣄⠀⠀⠀⠀⠀⠑⣽⣆⠀⠑⢝⡍⠒⠬⢧⣀⡠⠊⠀⠸⡀⠀⢹⡇⡎⠀⡿⢸⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⡼⠁⠀⠀⠀⠀⠀⠀⢀⠻⣺⣧⠀⠀⠀⠰⢢⠈⢪⡷⡀⠀⠙⡄⠀⠀⠱⡄⠀⠀⠀⢧⠀⢸⡻⠀⢠⡇⣾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⢰⠇⠀⠀⠀⠀⠀⠀⠀⢸⠀⡏⣿⠀⠀⠀⠀⢣⢇⠀⠑⣄⠀⠀⠸⡄⠀⠀⠘⡄⠀⠀⠸⡀⢸⠁⠀⡾⢰⡏⢳⡀⠀⠀
=== art: CINNAMOROLL ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⡤⠤⠤⠤⣤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠞⠋⠁⠀⠀⠀⠀⠀⠀⠀⠉⠛⢦⣤⠶⠦⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢀⣴⠞⢋⡽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠃⠀⠀⠙⢶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⣰⠟⠁⠀⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡀⠀⠀⠉⠓⠦⣤⣤⣤⣤⣤⣤⣄⣀⠀⠀⠀
    ⠀⠀⠀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣷⡄⠀⠀⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣆⠀
    ⠀⠀⣠⠞⠁⠀⠀⣀⣠⣏⡀⠀⢠⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⠿⡃⠀⠀⠄⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡆
    ⢀⡞⠁⠀⣠⠶⠛⠉⠉⠉⠙⢦⡸⣿⡿⠀⠀⠀⡄⢀⣀⣀⡶⠀⠀⠀⢀⡄⣀⠀⣢⠟⢦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠃
    ⡞⠀⠀⠸⠁⠀⠀⠀⠀⠀⠀⠀⢳⢀⣠⠀⠀⠀⠉⠉⠀⠀⣀⠀⠀⠀⢀⣠⡴⠞⠁⠀⠀⠈⠓⠦⣄⣀⠀⠀⠀⠀⣀⣤⠞⠁⠀
    ⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⠀⠁⠀⢀⣀⣀⡴⠋⢻⡉⠙⠾⡟⢿⣅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠙⠛⠉⠉⠀⠀⠀⠀
    ⠘⣦⡀⠀⠀⠀⠀⠀⠀⣀⣤⠞⢉⣹⣯⣍⣿⠉⠟⠀⠀⣸⠳⣄⡀⠀⠀⠙⢧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠈⠙⠒⠒⠒⠒⠚⠋⠁⠀⡴⠋⢀⡀⢠⡇⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⢀⡾⠋⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⢸⡀⠸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⢠⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣇⠀⠀⠉⠋⠻⣄⠀⠀⠀⠀⠀⣀⣠⣴⠞⠋⠳⠶⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⠦⢤⠤⠶⠋⠙⠳⣆⣀⣈⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: CAT ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⠟⠉⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠙⢻⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⣠⣄⠀⢻⣿⣿⣿⣿⣿⡿⠀⣠⣄⠀⠀⠀⢻⣿⣿⣏⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⠀⠀⠀⠀⠰⣿⣿⠀⢸⣿⣿⣿⣿⣿⡇⠀⣿⣿⡇⠀⠀⢸⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠙⠃⠀⣼⣿⣿⣿⣿⣿⣇⠀⠙⠛⠁⠀⠀⣼⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣷⣤⣄⣀⣠⣤⣾⣿⣿⣿⣿⣽⣿⣿⣦⣄⣀⣀⣤⣾⣿⣿⣿⣿⠃⠀⠀⢀⣀⠀⠀
    ⠰⡶⠶⠶⠶⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠛⠉⠉⠙⠛⠋⠀
    ⠀⠀⢀⣀⣠⣤⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠷⠶⠶⠶⢤⣤⣀⠀
    ⠀⠛⠋⠉⠁⠀⣀⣴⡿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣤⣀⡀⠀⠀⠀⠀⠘⠃
    ⠀⠀⢀⣤⡶⠟⠉⠁⠀⠀⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠟⠉⠀⠀⠀⠉⠙⠳⠶⣄⡀⠀⠀
    ⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠁⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀
=== art: CODING ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⠶⣻⠝⠋⠠⠔⠛⠁⡀⠀⠈⢉⡙⠓⠶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⢋⣴⡮⠓⠋⠀⠀⢄⠀⠀⠉⠢⣄⠀⠈⠁⠀⡀⠙⢶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⢁⣔⠟⠁⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠈⢦⡀⠀⠀⠘⢯⢢⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡼⠃⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠀⠀⠀⢳⣦⡀⠀⠀⢯⠀⠈⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⠆⡄⢠⢧⠀⣸⠀⠀⠀⠀⠀⠀⠀⢰⠀⣄⠀⠀⠀⠀⢳⡈⢶⡦⣿⣷⣿⢉⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣯⣿⣁⡟⠈⠣⡇⠀⠀⢸⠀⠀⠀⠀⢸⡄⠘⡄⠀⠀⠀⠈⢿⢾⣿⣾⢾⠙⠻⣾⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⡿⣮⠇⢙⠷⢄⣸⡗⡆⠀⢘⠀⠀⠀⠀⢸⠧⠀⢣⠀⠀⠀⡀⡸⣿⣿⠘⡎⢆⠈⢳⣽⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢠⡟⢻⢷⣄⠀⠀⠀⠀⠀⠀⣾⣳⡿⡸⢀⣿⠀⠀⢸⠙⠁⠀⠼⠀⠀⠀⠀⢸⣇⠠⡼⡤⠴⢋⣽⣱⢿⣧⠀⢳⠈⢧⠀⢻⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢀⡿⣠⡣⠃⣿⠃⠀⠀⠀⠀⣸⣳⣿⠇⣇⢸⣿⢸⣠⠼⠀⠀⠀⡇⠀⡀⠉⠒⣾⢾⣆⢟⣳⡶⠓⠶⠿⢼⣿⣇⠈⡇⠘⢆⠈⢿⡘⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠈⢷⣍⣤⡶⣿⡄⠀⠀⠀⢠⣿⠃⣿⠀⡏⢸⣿⣿⠀⢸⠀⠀⢠⡗⢀⠇⠀⢠⡟⠀⠻⣾⣿⠀⠀⠀⠀⡏⣿⣿⡀⢹⡀⠈⢦⠈⢷⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢁⣤⣄⠁⠀⠀⠀⣼⡏⢰⣟⠀⣇⠘⣿⣿⣾⣾⣆⢀⣾⠃⣼⢠⣶⣿⣭⣷⣶⣾⣿⣤⠀⠀⠀⡇⡯⣍⣧⠀⣷⠄⠈⢳⡀⢻⡁⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠺⣿⡿⠀⠀⠀⠀⡿⢀⣾⣧⠀⡗⡄⢿⣿⡙⣽⣿⣟⠛⠚⠛⠙⠉⢹⣿⣿⣦⠀⢸⡿⠀⠀⠀⢰⡯⣌⢻⡀⢸⢠⢰⡄⠹⡷⣿⣦⣤⠤⣶⡇⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⣇⣾⣿⢸⢠⣧⢧⠘⣿⡇⠸⣿⢿⡆⠀⠀⠀⠀⠘⣯⠇⣿⠂⣸⢰⠀⠀⢀⣸⡧⣊⣼⡇⢸⣼⣸⣷⢣⢻⣄⠉⠙⠛⠉⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣳⣤⣴⣿⣏⣿⣾⢸⣿⡘⣧⣘⢿⣀⡙⣞⠁⠀⠀⠀⠀⢀⡬⢀⣉⢠⣧⡏⠀⠀⡎⣿⣿⣿⣿⠃⣸⡏⣿⣿⡎⢿⡘⡆⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⣠⣼⣿⣿⣿⣼⣿⣧⢿⣿⣿⣯⡻⠟⠀⠀⠀⠀⠀⠐⢯⠣⡽⢟⣽⠀⠀⢘⡇⣿⣿⣿⡟⣴⣿⣷⣿⣿⣧⣿⣷⡽⠀⠀⠀⠀⠀⠀⠀
    ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣼⣹⣿⣇⣸⣿⣿⣿⣻⣚⣿⡿⣿⣿⣦⣤⣀⡉⠃⠀⢀⣀⣤⡶⠛⡏⠀⢀⣼⢸⣿⣿⣿⣿⣿⣿⣿⢋⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀
    ⣿⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠒⠒⠒⢭⢻⣽⣿⣿⣿⣿⣿⣿⢿⠿⣿⡏⠀⡼⠁⣀⣾⣿⣿⣿⣿⡿⣿⣿⣟⡻⣿⣿⡿⠣⠟⠀⠀⠀⠀⠀⠀⠀⠀
    ⠸⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢧⢿⣯⡽⠿⠛⠋⣵⢟⣋⣿⣶⣞⣤⣾⣿⣿⡟⢉⡿⢋⠻⢯⡉⢻⡟⢿⡅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡞⣿⣆⡀⠀⡼⡏⠉⠚⠭⢉⣠⠬⠛⠛⢁⡴⣫⠖⠁⠀⠀⣩⠟⠁⣸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠈⢷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣽⣿⣿⣾⠳⡙⣦⡤⠜⠊⠁⠀⣀⡴⠯⠾⠗⠒⠒⠛⠛⠛⠛⠛⠓⠿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠘⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢷⣻⣿⣿⠔⢪⠓⠬⢍⠉⣩⣽⢻⣤⣶⣦⠀⠀⠀⢀⣀⣤⣴⣾⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠹⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣾⡏⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣯⣿⣿⠀⠀⣇⠀⣠⠎⠁⢹⡎⡟⡏⣷⣶⠿⠛⡟⠛⠛⣫⠟⠉⢿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣷⠈⢷⡤⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣾⣷⡀⣀⣀⣷⡅⠀⠀⠈⣷⢳⡇⣿⠀⠀⣸⠁⢠⡾⣟⣛⣻⣟⡿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢯⢻⣏⡵⠿⠿⢤⣄⠀⢀⣿⢸⣹⣿⣀⣴⣿⣴⣿⣛⠋⠉⠉⡉⠛⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠘⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡎⣿⣥⣶⠖⢉⣿⡿⣿⣿⡿⣿⣟⠿⠿⣿⣿⣿⡯⠻⣿⣿⣿⣷⡽⣿⡗⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡘⣿⣩⠶⣛⣋⡽⠿⣷⢬⣙⣻⣿⣿⣿⣯⣛⠳⣤⣬⡻⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀
=== art: FEMALE ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡤⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠁⣬⡳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢠⠊⣰⠀⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡎⠀⢳⠈⢺⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢠⡃⡁⡇⠀⠈⠛⢤⡀⠀⠀⠀⠀⠀⣀⣀⣀⣀⡀⠀⠀⢀⠔⠋⠀⠀⢸⢼⡄⠈⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⡏⣀⡏⡇⠀⠀⠉⠢⠈⣖⣉⡉⠉⣹⢧⣀⣀⠤⠬⠭⢹⣋⠀⠀⠀⠀⡜⠈⠹⣄⢹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢸⠻⡁⠀⢧⠀⠀⢀⡤⠚⠉⠀⠀⠀⠘⡾⣷⡀⠀⠀⠀⠀⠈⢍⡒⢤⣰⠃⠀⠀⣼⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢸⢶⡃⠀⠈⣦⠞⠁⠀⢠⣮⣤⣴⣾⣿⣷⠹⣿⣿⣷⣿⣿⣶⣦⣷⣄⣈⡳⣄⠀⡬⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⣠⣟⢀⡜⠁⠀⠀⢠⣿⣿⣿⣿⣿⣿⣽⣧⠹⣿⣿⣿⣯⣵⣾⣿⣶⣲⠃⠈⢳⣳⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣸⠃⢸⠏⠀⠀⠀⢀⣿⣿⣿⠿⡿⢿⠿⠟⠛⢧⠙⢟⠉⠙⠛⠛⢻⠛⠻⡄⠀⢄⠹⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⣰⡏⢀⠏⠀⠀⠀⠀⡜⠁⠀⠀⢠⡇⡜⠀⠀⠀⠀⠳⡌⢻⡀⠀⠀⠈⡆⠀⢸⡀⠈⠆⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢰⠻⠀⡼⠀⠀⠀⠀⢰⠃⠀⠀⢠⠇⡇⣷⠀⣧⠀⠦⠀⢸⢦⡹⣄⠀⠀⢧⠀⠀⡇⠠⠘⡄⢱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢼⡇⢀⠇⠀⠀⠀⠀⢸⠀⠀⣾⣸⠀⡇⡿⡀⣿⡆⠀⠀⢸⡀⣷⢮⣦⡀⢸⡆⠀⢸⠀⠀⢱⡀⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⣾⠀⢸⠀⠀⠀⠀⠀⠸⡆⢰⢹⡇⠀⢸⡇⢧⣷⣽⡄⠀⢸⣧⣻⣎⣿⡿⣾⣿⠀⢸⠀⠀⠀⢃⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⡍⠀⡇⢀⠀⠀⠀⠀⠀⣷⢼⠬⣧⣀⣈⣷⠘⢧⣹⢿⣄⠈⣏⢻⣞⣿⣷⣼⠿⡷⣦⣀⠀⠀⠘⠌⡆⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⡇⠀⡆⠀⣇⠀⠀⠀⠀⡿⣼⠀⠀⠀⠀⠙⣍⠉⠃⠀⠉⠓⠾⣉⣙⣈⣈⠃⠀⣷⠃⠉⠓⡦⣄⡀⣇⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⡇⠀⡇⠀⠘⣆⠀⠀⠀⣇⠙⠟⠛⣻⣿⡏⠉⠀⠀⠀⠀⠀⠈⠉⢛⣿⣟⡟⠋⢸⠆⠀⣠⠇⠀⠈⡏⠙⠒⠲⠦⠤⠄⠀⠀
    ⠀⠀⠀⡇⠀⡷⡀⠀⠘⣆⠀⠀⢹⠀⠀⠀⠙⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠋⠀⢸⠀⣠⠋⠀⠀⢀⡇⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠃⠀⣧⠱⡄⠀⠈⢧⡀⠘⣇⠀⣴⡶⡢⠾⠂⠀⠀⠀⠀⠀⠀⠀⠺⠊⢴⡷⢴⣏⡴⠃⠀⠀⢠⣾⠁⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢧⠀⢸⡀⠙⢄⠀⠀⠙⢦⣘⣆⠉⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⢀⢾⡟⠀⠀⠀⡴⠃⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢸⡀⠀⢳⡀⠈⠣⡀⠀⠈⢻⡙⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡼⠀⠀⢠⠞⠀⡼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢸⢃⠀⠀⠳⣄⠀⠈⢦⡀⠀⢧⣀⠀⠀⠀⠀⠀⠀⠉⠉⠀⠀⠀⠀⢀⡴⣾⠁⢀⡔⠁⢀⢾⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⡇⢸⡆⠀⠀⠈⡷⣦⡀⠙⢄⠘⣿⡝⠲⣤⣀⡀⠀⠀⠀⢀⣠⡴⠚⠁⣷⠇⡰⠋⣀⡴⠋⣸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⡼⣡⢣⢷⠀⠀⢰⠀⡇⠈⠳⣄⠃⢹⣷⡿⠷⣄⣉⡉⠒⣊⡩⠿⣿⠀⠀⡟⣰⠕⣫⣾⠀⠀⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⣴⡵⠁⡜⠘⠀⣀⡦⠴⠥⢤⠤⢾⠳⣜⡏⡷⣤⠶⠤⣍⣷⣣⡤⠴⢻⢳⣶⣧⢣⠾⠖⠛⠒⠤⣼⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⡾⠋⢀⡜⠀⢠⠏⠁⠀⠀⠀⠈⣦⠎⠀⠘⢿⡇⢸⠀⢐⣺⣧⣿⣋⣀⣼⣀⣿⢰⢳⠀⠰⡄⠀⠀⠀⠙⡷⣄⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⣠⠎⠀⢠⠇⠀⠀⡄⠀⢀⠔⠃⠀⠀⢠⠟⡇⠈⡏⢡⣾⡿⠿⣿⡍⢹⡏⢹⡇⠈⠣⡀⡇⠀⠀⠀⠀⠘⡄⠉⠒⠒⠒⠢⣤⡶⠞
    ⠞⠁⠀⠀⡸⠀⠀⠀⢸⣶⠋⢦⣀⣀⣴⡟⠀⢧⠀⡼⠼⣇⠀⠀⢀⠗⠛⣿⠘⣦⣀⠀⠈⢳⣄⠀⠀⠀⠀⢻⢿⣉⠉⠉⠉⠁⠀⠀
=== art: CATGIRL ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡒⣢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⠃⠀⠀⠀⠉⣁⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠈⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣶⣟⣛⠛⠋⠉⠉⠉⠉⠉⠉⠉⠉⠉⠙⢛⣛⣷⡦⢀⣤⣶⡶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⣴⣶⣶⠂⠤⢄⣀⠀⠀⠀⠈⠉⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠋⠉⣰⣿⣿⣿⣇⢹⡄⠀⠀⠀⣀⠀⣠⠤⠠⡄⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣀⡯⠹⢲⡄⠀⠀⠀⣿⣿⣿⣷⣤⡀⠈⣹⣶⠦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⠀⢷⠀⠀⢰⡇⠋⠀⠀⢠⠇⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠛⠒⣎⡏⠀⠀⠀⠀⣿⣿⣿⡏⠙⠛⢦⡙⠉⠀⠀⠉⠓⢦⣀⠀⠀⢀⣀⣀⣀⣀⣀⡀⠀⢠⣿⣿⣿⠟⠻⣿⣿⡇⢸⡇⠀⠀⠓⠒⣦⠀⠛⢦⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠿⠓⠂⠀⡠⠽⢦⡀⠀⠀⠀⠈⠛⢛⡉⢉⠉⠀⠀⠙⠛⠋⢛⣿⢯⡉⠛⠀⠀⠘⠈⢿⠗⢻⠀⠀⠀⠀⠛⠦⠶⠋⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣇⠀⠀⢠⠋⠀⢀⡾⢛⡆⠀⠀⠀⢉⡽⠛⠁⠈⣏⢦⠐⢶⣤⡹⣿⠒⠁⠀⠀⢀⡠⠤⢼⢣⢸⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⡴⠲⢤⡀⣀⣀⠀⠀⢸⣿⡗⠀⡇⠀⣠⣾⠟⠛⠡⣾⡴⢶⡯⠀⠤⠀⠀⢸⠸⡇⠀⡙⣿⣌⠻⣤⣀⡠⠋⠀⠀⢸⡏⢠⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⡇⠀⠀⠉⠁⣸⠀⠀⠀⢿⣿⠤⣫⡾⣿⣿⢱⣀⡼⠛⢒⡿⠀⠀⠀⠀⠀⠸⡇⢳⠐⠛⠉⠻⣇⢹⢿⣟⣦⣀⣸⣍⣷⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⡴⠄⠀⢰⠚⠁⠀⠀⠀⠘⢿⣿⡿⣤⠉⠁⣠⡿⠁⠀⣼⠁⠀⠀⣀⣀⡤⠂⡇⢸⡀⠀⠀⠀⠹⡆⠀⠀⠀⢿⣿⣤⣿⣿⣿⡇⠀⠀⠀⢀⢤⡀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠸⣄⣀⣀⡼⠀⠀⠀⠀⠀⠀⣼⣿⡶⠟⠀⣴⣿⠶⢦⢰⡟⡆⠀⣀⣩⣀⠀⢰⡇⣸⡓⠄⢀⣀⡀⢿⡀⡴⠛⢶⠘⣿⣿⣿⣿⣿⡀⠀⠘⢧⣀⡕⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿⠃⠀⠀⢠⣿⠷⣤⡾⣼⡇⠁⢸⣏⠁⢘⡷⢘⡗⣿⣧⠆⣿⣈⡿⢺⣇⠉⢳⠟⠀⢸⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣽⡓⠀⠠⣼⢺⡅⠀⠂⣿⡩⡧⠀⠀⠛⠶⡛⠉⠁⣿⡏⠻⣬⠄⠨⠀⠀⢻⠀⠐⡄⠂⠀⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⡿⡄⠀⢸⣿⣿⠁⣀⣿⣟⣀⣇⠆⢠⣀⣤⣄⠀⠀⢿⣿⣶⣻⣮⡀⠀⠀⣼⠀⠀⡏⠁⠀⢸⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⡿⣿⢿⠀⠀⣿⣿⣿⠀⣿⠟⠉⠀⢹⡄⢸⣿⣿⣿⣿⣦⣸⡟⠀⠈⢻⣷⣆⡐⣿⡂⠀⡇⠀⠀⠘⣿⣿⣿⣿⣿⠀⠀⠀⠀⢀⡶⡄⠀⠀
    ⢀⣦⡀⠀⠀⠀⠠⠴⠾⠛⠋⠉⠀⣿⠀⠀⠀⣿⠁⣿⣿⠉⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠙⢿⣿⣿⠀⠀⣅⠁⠀⠀⣿⣿⣿⣿⣿⠀⠀⠀⠸⡍⣰⣧⠀⠀
    ⠈⣇⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠀⠀⠀⣿⠄⣿⠃⠀⠀⠀⠀⠀⢹⣿⣿⣿⠟⠛⠿⠛⢇⠀⠀⠀⠀⠀⠻⣿⠀⢸⣇⠀⠀⣼⣿⣿⣿⣿⣿⠀⠀⠀⠐⠷⠃⠉⠀⠀
    ⠀⢹⡀⠀⠙⢄⠀⠀⠀⠀⠀⠀⣾⣿⠀⠀⠀⣿⣆⣿⣠⣴⣶⣶⣤⣅⡒⢻⡀⠉⠳⣾⣷⣦⣸⣭⣴⡶⢶⣤⣤⣾⠀⣸⠧⠀⣸⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⢻⡷⠀⠀⠈⠳⣄⠀⠀⠀⣸⣿⣿⣀⠄⠀⣿⣷⣿⠛⣋⣉⣀⡀⠉⠉⠀⠀⠀⠀⠀⠉⠈⠉⣉⣈⡉⠉⠛⠿⡿⠆⣿⠂⢰⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⣤⣳⡕⠄⠀⣦⠘⣦⠀⢠⣿⣿⣿⣿⣆⠀⠘⣿⣿⣜⠿⢳⠻⠁⠀⠀⢠⣠⢄⣀⣠⡄⠀⢚⢏⢎⣿⡿⡴⣸⣧⣾⣏⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀
    ⠀⢻⡌⡛⠀⢀⣘⣿⣿⣄⣾⣿⣿⣿⣿⣿⣷⣤⣽⠟⠉⠉⠙⠒⢤⡀⠀⠘⢆⠀⠀⢠⠃⠀⠈⢈⡤⠞⠋⠉⠉⠛⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣼⣆⠀⠀⠀⠀⠀⠀
    ⠀⠀⠻⣮⣀⣺⣿⣿⣿⣟⣿⣿⣿⣿⣿⡻⢿⡿⠏⠀⠀⠀⠀⠀⠀⠙⢦⠀⠈⠑⠒⠋⠀⢀⡴⠋⠀⠀⠀⠀⠀⠀⢀⠈⠻⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀
    ⠀⠀⢺⣿⣿⣿⣿⣿⣿⣿⣵⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⣷⣶⣦⣤⣶⣶⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡆⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀
    ⠀⠀⠀⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣍⣉⣽⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀
    ⠀⠀⠀⠘⣿⣿⣿⣿⣿⢟⣱⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣖
=== art: SPY ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⡶⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣽⠟⣿⢸⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣟⡾⠁⠀⣿⢸⡅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣹⡇⠀⠀⣿⡞⢃⣀⣠⢤⢤⣤⣤⣤⣤⣤⠤⠤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣧⠀⢠⡟⣿⡽⠟⠚⠋⠉⠉⠉⠉⠉⠉⠉⠛⠒⠿⣭⣗⡲⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⢴⣖⣿⡽⠆⠘⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠳⢮⣕⢦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣻⠞⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢮⣿⣭⣭⣽⣭⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⡴⣻⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠙⢷⡄⠀⢠⡿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢀⣜⡿⠃⢀⣤⣤⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⡄⠀⠀⠀⠀⠀⠀⠈⢻⣄⣸⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣠⢞⣟⣡⣴⠿⠯⣿⢿⣿⣦⠀⠀⠀⠀⠀⠀⠀⢀⣠⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⡆⠀⠀⠀⠠⡄⠀⠀⢻⡟⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⣠⣴⣞⣽⡷⠟⠋⠁⠀⠀⠀⢿⣦⣿⣿⡆⠀⠀⠀⡆⠀⠰⠋⠀⢸⠂⠀⠀⠀⠀⠀⠀⠀⠰⣄⠀⠀⢹⣆⠀⠀⠀⠈⠢⠀⠀⣧⣿⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀
    ⠀⠹⡿⣿⡀⠀⠀⠀⠀⠀⠀⠀⢼⡏⢹⣿⡇⠀⠀⢠⠃⠀⠀⠀⣀⣀⣀⡀⠀⢠⡄⠀⠀⠀⠀⠀⠀⠶⢾⣿⡶⠦⠄⢰⠀⠀⠀⣹⣿⡇⠀⢀⣀⣠⣤⣞⣷⢿⠀⠀
    ⠀⠀⠈⠻⣿⣦⡀⠀⠀⠀⠀⠀⣨⡿⣾⣿⡇⠀⠀⣼⠀⢠⠖⠋⠁⠀⣼⠇⠀⢀⣷⠀⠀⠀⠀⠀⠀⠀⣼⣉⣧⡀⠀⣸⡆⠀⢀⣿⡟⣷⣾⣿⠟⠛⠳⠟⣿⡿⠀⠀
    ⠀⠀⠀⠀⢸⢸⠻⣦⡀⠀⠀⢸⡇⣰⣿⡿⠀⠀⠀⡏⠀⡌⠀⢀⣠⣴⣿⣄⣀⣾⠻⡆⠀⣠⠀⠀⢠⣾⣟⣛⡛⠿⣶⣟⣿⣠⣾⡟⣷⣿⣿⠁⠀⠀⠀⢸⣽⣧⣄⠀
    ⠀⠀⠀⠀⣸⣸⠀⠈⢿⣿⣏⣩⣿⣿⠟⠁⠀⠀⢰⡇⢸⢁⣴⣿⠿⠭⣍⡉⠙⠷⢤⣧⣴⢻⣠⡴⢻⡟⠁⠀⠙⢷⡈⢻⣿⣿⡞⠁⣿⣽⠋⠀⠀⠀⠀⣀⣭⠿⣿⠇
    ⠀⠀⠀⠀⡇⣿⡆⠀⠈⠻⣷⠾⠛⢹⡄⠀⠀⠀⢸⣇⣿⣿⡟⠁⠀⠀⠀⠹⣄⠀⠀⠉⠁⠈⠁⠀⢸⠀⡠⠒⣦⣈⡇⠈⣿⣿⠃⠀⣿⡟⢷⡀⢠⢾⣿⠷⠋⠉⠁⠀
    ⠀⠀⠀⠀⣿⡿⠀⠀⠀⣴⣿⠀⠀⠀⠙⣦⠀⠀⠸⣿⢿⣿⠆⠀⠰⠒⢶⣀⣽⡄⠀⠀⠀⠀⠀⠀⢸⡍⠁⠀⡀⢀⡇⠀⣿⡟⠀⢀⣿⡇⠈⢿⣿⡞⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢰⣽⠃⠀⠀⢠⠇⣿⠀⠀⠀⠀⠈⢷⣄⠀⢿⣛⣿⣏⠀⠉⠓⠬⠤⣼⠇⠀⠀⠀⠀⠀⠀⠀⠻⢄⣀⡠⠞⡀⠀⢰⡇⢀⡞⢹⡇⠀⢸⡇⣷⠀⠀⠀⠀⠀⠀
    ⠀⠀⢰⣿⠇⠀⠀⠀⣾⠀⣿⠀⠀⠀⠀⢀⠀⠻⣷⣼⣯⠀⠉⠛⠦⣤⣤⠴⢋⡤⠀⠀⠀⠀⠀⠀⠀⠀⠘⠓⠒⠚⠁⠀⠈⠉⢻⡅⣼⡇⠀⢸⣇⣿⠀⠀⠀⠀⠀⠀
    ⠀⠀⣮⡞⠀⠀⠀⢀⡏⠀⢹⡇⠀⠀⠀⠀⠳⣤⡈⠻⢿⣷⡄⠀⠀⠀⠉⠉⠉⠀⠀⡴⠚⠋⠙⢿⡇⠀⠀⠀⠀⠀⠀⠀⠀⢀⡼⢣⠟⣿⠀⣼⣿⡟⠀⠀⠀⠀⠀⠀
    ⠀⣼⡼⠁⠀⠀⠀⢸⠁⠀⠀⣷⠀⠀⠀⠀⠀⠀⠙⠦⣄⡉⠛⢧⣄⠀⠀⠀⠀⠀⠀⢷⣄⠀⠀⣰⠇⠀⠀⠀⠀⠀⢀⣠⡴⠏⣠⡏⠀⣿⣾⣿⡼⠀⠀⠀⠀⠀⠀⠀
    ⢰⣳⠇⢠⠀⠀⠀⢸⡆⠀⠀⠸⣧⠀⠀⠀⠀⠀⠀⠀⠀⠙⠳⢤⣈⡻⣄⠀⠀⠀⠀⠀⠉⠛⠛⠁⠀⢀⣀⣤⡤⠞⠛⠁⣀⡼⠋⠀⢀⡿⠛⡿⡇⠀⠀⠀⠀⠀⠀⠀
    ⡼⡿⠀⢸⠀⠀⠀⠸⡇⠀⠀⠀⠙⢧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⡆⠒⠒⠒⠒⢲⣶⣶⣾⣿⣿⣯⡀⠀⢀⣤⠖⠋⠀⠀⠀⣾⠃⠀⡇⡇⠀⠀⠀⠀⠀⠀⠀
    ⡇⡇⠀⢸⡄⠀⠀⠀⣿⡄⠀⠀⠀⠈⠻⣦⡀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⢻⡀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣷⣄⢻⠁⠀⠀⠀⢀⣼⣷⣄⣼⣻⠃⠀⠀⠀⠀⠀⠀⠀
    ⢷⣧⠀⢸⣧⡀⠀⠀⠘⣷⣄⠀⠀⠀⠀⠈⠻⢦⣀⡀⠈⠳⢦⡀⠀⠀⠀⢸⠇⠠⠀⠀⠀⢀⣽⣿⣿⣿⣿⣿⡿⣿⠀⠀⠀⣠⣾⢟⣵⣟⡵⠃⠀⠀⠀⠀⠀⠀⠀⠀
    ⠈⢻⣧⣸⣹⣧⣀⠀⠀⠹⣟⣷⢤⣀⣥⡀⠀⠀⢈⣽⡿⠶⢶⣭⣷⡆⠀⣼⣦⣶⣶⣶⣿⣿⣿⠿⠟⠛⣿⣿⢿⣟⣠⠴⣻⠵⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠹⢝⣫⠗⠯⢿⣒⣦⣿⣾⡟⢶⡽⠧⣴⣞⣻⠟⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⠿⠟⡋⢩⣤⣀⣀⣀⣽⢿⡼⡯⠒⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠀⠈⠉⠉⣽⣿⠏⠀⠈⠀⠀⠀⠈⣿⣿⡿⠋⠀⠀⣠⣿⠛⣿⠹⣿⠉⠁⠈⣧⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣳⣟⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⣤⣴⣾⡇⢸⠀⢹⡆⢸⣧⣠⣤⡿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢝⣳⢶⣤⣀⣀⣀⣀⣾⢿⣿⣾⣷⡿⣤⠼⣿⣛⡷⠞⠿⠷⠒⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠓⠒⠫⠭⠭⠥⠋⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: SPYXFAMILY ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢲⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠄⠂⢉⠤⠐⠋⠈⠡⡈⠉⠐⠠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⡀⢠⣤⠔⠁⢀⠀⠀⠀⠀⠀⠀⠀⠈⢢⠀⠀⠈⠱⡤⣤⠄⣀⠀⠀⠀⠀⠀
    ⠀⠀⠰⠁⠀⣰⣿⠃⠀⢠⠃⢸⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⠈⢞⣦⡀⠈⡇⠀⠀⠀
    ⠀⠀⠀⢇⣠⡿⠁⠀⢀⡃⠀⣈⠀⠀⠀⠀⢰⡀⠀⠀⠀⠀⢢⠰⠀⠀⢺⣧⢰⠀⠀⠀⠀
    ⠀⠀⠀⠈⣿⠁⡘⠀⡌⡇⠀⡿⠸⠀⠀⠀⠈⡕⡄⠀⠐⡀⠈⠀⢃⠀⠀⠾⠇⠀⠀⠀⠀
    ⠀⠀⠀⠀⠇⡇⠃⢠⠀⠶⡀⡇⢃⠡⡀⠀⠀⠡⠈⢂⡀⢁⠀⡁⠸⠀⡆⠘⡀⠀⠀⠀⠀
    ⠀⠀⠀⠸⠀⢸⠀⠘⡜⠀⣑⢴⣀⠑⠯⡂⠄⣀⣣⢀⣈⢺⡜⢣⠀⡆⡇⠀⢣⠀⠀⠀⠀
    ⠀⠀⠀⠇⠀⢸⠀⡗⣰⡿⡻⠿⡳⡅⠀⠀⠀⠀⠈⡵⠿⠿⡻⣷⡡⡇⡇⠀⢸⣇⠀⠀⠀
    ⠀⠀⢰⠀⠀⡆⡄⣧⡏⠸⢠⢲⢸⠁⠀⠀⠀⠀⠐⢙⢰⠂⢡⠘⣇⡇⠃⠀⠀⢹⡄⠀⠀
    ⠀⠀⠟⠀⠀⢰⢁⡇⠇⠰⣀⢁⡜⠀⠀⠀⠀⠀⠀⠘⣀⣁⠌⠀⠃⠰⠀⠀⠀⠈⠰⠀⠀
    ⠀⡘⠀⠀⠀⠀⢊⣤⠀⠀⠤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠤⠄⠀⢸⠃⠀⠀⠀⠀⠀⠃⠀
    ⢠⠁⢀⠀⠀⠀⠈⢿⡀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⢀⠏⠀⠀⠀⠀⠀⠀⠸⠀
    ⠘⠸⠘⡀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠁⠀⠃⠀⠀⠀⠀⢀⠎⠀⠀⠀⠀⠀⢠⠀⠀⡇
    ⠀⠇⢆⢃⠀⠀⠀⠀⠀⡏⢲⢤⢀⡀⠀⠀⠀⠀⠀⢀⣠⠄⡚⠀⠀⠀⠀⠀⠀⣾⠀⠀⠀
    ⢰⠈⢌⢎⢆⠀⠀⠀⠀⠁⣌⠆⡰⡁⠉⠉⠀⠉⠁⡱⡘⡼⠇⠀⠀⠀⠀⢀⢬⠃⢠⠀⡆
    ⠀⢢⠀⠑⢵⣧⡀⠀⠀⡿⠳⠂⠉⠀⠀⠀⠀⠀⠀⠀⠁⢺⡀⠀⠀⢀⢠⣮⠃⢀⠆⡰⠀
    ⠀⠀⠑⠄⣀⠙⡭⠢⢀⡀⠀⠁⠄⣀⣀⠀⢀⣀⣀⣀⡠⠂⢃⡀⠔⠱⡞⢁⠄⣁⠔⠁⠀
    ⠀⠀⠀⠀⠀⢠⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠉⠁⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀
=== art: NOKO ===
    ⡧⠡⠡⢑⣌⣍⣍⣍⣍⣍⣍⣍⣍⣦⠀⣷⡈⢢⠘⡄⠱⡈⡆⣨⣩⣩⣩⣩⣩⣩⣩⣩⣩⣡⡊⠌⠌⠌⢼
    ⣿⣾⣨⣿⠿⠟⠛⠛⠉⠉⠙⠙⠛⠾⣧⣿⣿⡌⢂⠘⡀⠡⣱⣿⣿⠿⠛⠛⠋⠋⠙⠛⠛⠛⠿⢾⣅⣷⣿
    ⠟⢋⣩⣤⣶⡶⣛⣭⣭⣭⣛⣿⣿⣷⣶⣿⣿⣿⡄⠣⢩⣾⣿⣿⣶⣾⣿⣿⣛⣭⣭⣭⣛⣿⣷⣦⣄⡙⠻
    ⣾⣿⣿⣿⡿⣾⣿⣿⣿⣿⣿⣷⢿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⣿⣿⣿⡿⣾⣿⣿⣿⣿⣿⣷⢿⣿⣿⣿⣷
    ⣿⣿⣿⣿⣇⣿⣿⣿⣿⣿⣿⣿⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⣿⣿⣿⣿⣿⣿⣿⣸⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⣞⢿⣿⣿⣿⡿⣳⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣞⢿⣿⣿⣿⡿⣳⣿⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⣝⡻⡿⢿⢿⢿⢟⣮⡻⡿⡿⡿⢿⢟⣫⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⢹⣿⣿⣿⣿⣿⣿⣿⡏⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⢻⣿⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣭⣭⣭⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
=== art: GOJO ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠚⣷⠀⠀⣀⣤⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡞⣟⢀⡴⠋⠀⠀⣿⠖⠋⢀⡏⠀⠀⠀⡀⡀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢀⡀⡼⠀⢸⡟⡸⠀⠀⠀⠃⠀⠀⢸⡧⠜⠛⠛⣻⠃⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢺⢾⡃⠀⠈⣴⠁⢻⡀⠀⠀⢀⡠⠀⠀⠀⠀⢸⣇⣤⡀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠸⡜⠂⠀⠀⣟⠀⢸⠑⠀⠰⠁⠀⠀⠀⠀⠀⠛⠉⡼⠁⠀
    ⠀⠀⠀⠀⠀⠀⠀⠈⣷⣾⣿⣿⣿⣿⣾⣶⣶⣤⣀⡀⢰⠕⠋⠀⠀⠸⠧⣤⡄
    ⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣔⠈⣤⣶⡚⠁⠀
    ⠀⠀⠀⠀⣠⣶⡀⢸⡟⠿⡿⠿⡟⠻⠿⣿⣿⣿⣿⣿⣿⣿⠿⣿⠋⠁⠀⠀⠀
    ⠀⠀⠀⢰⢧⡷⡿⢘⡎⠀⠀⠐⣶⢶⣲⠈⠙⠋⠉⠉⠁⡘⡯⣿⡶⣆⡀⠀⠀
    ⠀⠀⠀⢾⢈⣼⣿⣤⣿⣶⣶⣶⣿⣿⣧⣤⣄⣀⣀⣤⣾⣿⣿⢯⢇⣿⢳⠀⠀
    ⠀⠀⠀⠈⠙⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣌⣷⣬⠏⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠉⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠻⠟⠋⠁⠀⠀⠀
    ⠀⠀⠀⠀⢀⣀⣀⡀⣰⣿⣿⣿⣿⣿⣿⣿⡿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⢀⣤⣶⣴⣿⣿⣿⡧⠀⠉⠙⢿⣿⣿⣿⣿⣾⣶⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠉⠛⠛⠿⣿⣿⡇⠀⠀⠀⠀⠻⣿⣿⣿⡿⠿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠁⠀⠀⠸⣿⣿⠁⠀⠀⠀
=== art: GIRL4 ===
    ⠀⠀⠀⠀⠀⠀⠀⡠⠖⡡⠁⣠⡮⠝⣛⠊⢄⠀⢉⣐⢄⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⣔⠟⢠⠑⢡⣵⡱⢒⠏⠁⠀⠀⠀⢆⠑⡷⠔⢄⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢠⡏⢎⣴⡟⢸⣾⠟⢡⢪⠀⠀⣠⢆⡇⢸⣔⠜⣧⠀⠧⡀⠀⠀⠀⠀
    ⠀⠀⢀⣼⣟⣿⣁⡇⢸⣯⣱⣻⣀⣄⣔⠽⠊⢱⣼⠻⡖⢡⢡⢰⣷⢶⡀⠀⠀
    ⠀⠘⠛⣿⢿⢍⡟⢇⢸⡟⠁⠉⠁⠀⠀⠀⠀⠀⠀⠀⠈⢣⡆⣸⢛⠟⠛⠃⠀
    ⠀⠀⠀⡏⠿⣾⢿⢸⢸⠀⡠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠢⡙⣹⡇⠀⠀⠀⠀
    ⠀⠀⠀⣇⢸⡿⣼⠀⣾⠀⣠⡶⠶⢄⠀⠀⠀⢀⠴⠂⠤⡀⠀⡇⡇⠀⠀⠀⠀
    ⠀⠀⠀⣼⢸⠛⢽⠀⡟⡼⢡⠶⠿⣆⠀⠀⠀⠀⠞⢿⠶⢸⣦⠀⡇⠀⠀⠀⠀
    ⠀⠀⠀⣟⢄⠀⡠⡄⢸⠇⢫⠷⢤⡞⠀⠀⠀⠀⢏⣥⡶⠀⣻⠀⡇⠀⠀⠠⠀
    ⠀⠀⠀⣽⡇⠢⣭⣇⢸⡀⠀⠉⠄⠀⠀⠀⠀⠀⠀⠀⠀⢰⣽⡄⡷⠶⡶⠁⠀
    ⡔⡂⡐⢿⢠⠁⣏⢟⣸⢳⣄⡀⠀⠀⠀⡀⣀⠀⠀⢀⣴⣿⠹⠁⣷⡀⡇⠀⠀
    ⠘⡉⣑⢸⡾⠀⢿⢸⢹⠸⡿⣿⣳⣦⠤⠀⣀⣤⣾⡟⣿⣿⡐⢀⢿⡆⢱⡄⠀
    ⠀⢳⡂⢾⠃⣠⣸⣜⡸⠡⣿⣿⣿⣈⣥⣾⣿⣿⣿⣄⣏⣿⡄⢸⢸⠃⢸⡆⠁
    ⢠⣋⡰⣽⢚⡡⠚⢿⡇⡆⢿⣿⣿⣿⣋⣠⣤⣬⣿⣿⣿⡖⡇⢸⣸⡆⢸⡇⠀
    ⠅⡇⡜⠀⠀⠀⠸⡈⣿⡁⠘⣿⣿⣿⡻⣿⡿⠿⣿⣿⣿⣿⠃⠀⠸⡇⠸⡇⠀
=== art: CAT_GIRL_TONGUE ===
    ⣿⣿⣿⣿⣿⣿⡿⠋⠁⠀⠀⠀⢀⣾⡿⠋⠉⠁⢠⣿⠏⢁⣿⣿⣿⠏⠉⢸⣿⡏⠉⢻⣿⣿⡇⠈⠙⢿⣿⣿⣿⣷⡆⠀⠀⢇⠢⣈⡒⡤
    ⣿⣿⣿⣿⣿⣏⣴⡇⠀⠀⠀⢠⡿⠋⠀⠀⠀⣰⣿⠋⠀⣼⣿⣿⠏⠀⠀⡾⣿⠁⠀⠀⣿⣿⣷⠀⠀⠀⠹⣿⣿⣿⣿⣄⢀⣿⣷⣿⣶⣶
    ⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⢠⡟⠁⠀⠀⠀⢰⣿⠇⠀⢰⣿⣿⡷⠀⣀⣜⣁⣻⣀⣀⣀⣸⢻⣿⡇⠀⠀⠀⠘⣿⣿⣷⠹⣿⣿⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⣿⠃⠀⠀⣠⣿⠀⠀⠀⠀⢠⣿⡏⠀⢀⣿⣿⡿⠛⣩⣿⠿⠛⢻⡏⠉⠉⢹⠉⢿⣿⡀⠀⠀⠀⠹⣿⣿⡆⠙⣿⣿⣿⣿⣿
    ⣿⣿⣿⣿⣿⡏⠀⠀⣰⣿⡟⠀⠀⠀⢀⣾⣿⠀⠀⣸⣿⡿⢥⣶⠝⠁⠀⠀⠀⡇⠀⠀⡜⠰⡀⢻⡇⠀⠀⠀⠀⢿⣿⣿⠀⠸⣿⣿⣿⡿
    ⣿⣿⢿⣿⣿⡃⠀⣼⣿⣿⡇⣀⣀⣤⣾⣿⡇⠀⠰⣼⡟⣠⠎⠁⠀⢀⣠⠔⠁⢹⣀⣠⣧⣶⣦⣤⣷⣤⣤⣠⣤⠼⣿⣿⡆⠀⢹⣿⣿⣿
    ⣿⣿⣿⣿⣿⠀⣼⣿⣿⣿⣏⣁⣼⣿⣿⣿⠀⢠⣾⣟⡔⠁⠀⠀⠀⢸⠁⠀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⣿⣿⣇⠀⠀⣿⣿⣿
    ⣿⣿⣿⣿⣿⣸⣿⣿⣿⣿⣟⣹⣿⣿⣿⡿⣴⣿⡿⠋⠀⠀⠀⠀⠀⠀⢰⣾⢟⣻⡿⠛⢻⢋⣻⣏⣱⠞⠙⣄⡀⠀⣿⡽⣿⠀⠀⢸⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠉⢘⡅⠚⠋⠉⠁⠀⠀⠀⠀⣔⣾⢏⢦⢄⣿⣧⣿⡆⠀⠈⣿⣿
    ⣿⣿⣿⣿⣿⡿⢸⣿⣿⣿⡿⢻⣿⣿⡿⠋⠀⠀⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠟⠉⠀⢂⢻⣿⣿⣿⡇⠀⠀⢽⣿
    ⣿⣿⣿⣿⣿⢃⣿⣿⣿⣿⣷⣿⣿⣿⣿⡷⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠉⠀⢀⣠⣾⣟⡖⢻⣿⣿⠃⠀⠀⣿⣿
    ⣿⣿⣿⣿⣟⣼⣿⣿⣿⣿⣿⣿⠟⣻⠿⠓⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠾⠿⠛⠉⠁⢰⢸⣿⡿⠀⠀⠀⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⠋⠰⢟⣿⠌⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠇⠀⠀⢠⡇⣿
    ⣿⣿⣿⣿⣿⣿⣏⠓⠓⠚⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⠿⠿⢾⣿⡟⠀⠀⠀⣼⠃⣿
    ⣿⣿⣿⣿⣿⣿⣿⣷⢦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡤⠤⠤⠤⠤⠤⠄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⡿⠀⠀⠀⣰⠏⢰⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⡀⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⣠⢚⣡⣴⣾⣿⣿⣿⣿⡿⠿⢿⠀⠀⠀⠀⠀⠀⠀⠀⣼⡿⠁⠀⠀⣰⠏⣠⣿⣿
    ⣿⣿⣿⣿⣿⣿⣿⣿⣧⣤⣴⡾⠋⠀⠀⠀⠀⠀⠀⠀⣃⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⡸⠀⠀⠀⠀⠀⠀⠀⣼⣿⣃⠤⠚⠚⠉⠉⠉⠙⠛
    ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⠉⠀⢀⡄⠀⠀⠀⠀⠀⠀⠙⢽⣿⠿⠋⠉⠁⠀⢠⡆⢰⠃⠀⠀⠀⠀⠀⠀⣼⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣴⡿⠁⠀⠀⠀⠀⠀⠀⠀⠈⢯⠀⠀⠀⠀⢀⡞⢠⠋⠀⠀⠀⠀⠀⢀⣾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡄⠀⠀⠀⣸⢠⠃⠀⠀⠀⠀⠀⢠⣾⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⡆⢠⠉⢿⣿⣿⡟⣿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠳⡀⠀⠀⡇⠇⠀⠀⠀⠀⠀⣠⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⣿⣼⠀⠈⢿⣿⡇⢹⣿⣿⣿⣿⣿⣿⣿⡗⠤⣀⠀⠀⠀⠀⠀⠑⢄⣀⡞⠀⠀⠀⠀⢀⣴⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⣿⣿⣷⠀⠀⠹⢇⠀⢻⣿⣿⣿⣿⣿⣿⣧⠀⠈⠻⣷⣦⣤⣀⡀⠀⠉⠀⠀⠀⣀⡴⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⣿⣿⠟⠀⠀⠀⠈⠳⣄⢻⣿⣿⣿⣿⣿⣿⣆⠀⠀⠈⠻⢿⣿⣿⣿⣷⣶⣴⣾⣿⣿⣿⣿⡿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⣿⣿⣄⠀⠀⠀⠀⠀⠈⠳⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠉⡿⢿⣿⣿⣿⣿⣿⣿⣿⡿⢱⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: SUGOI ===
    ⣿⣿⣿⡷⠊⡢⡹⣦⡑⢂⢕⢂⢕⢂⢕⢂⠕⠔⠌⠝⠛⠶⠶⢶⣦⣄⢂⢕⢂⢕
    ⣿⣿⠏⣠⣾⣦⡐⢌⢿⣷⣦⣅⡑⠕⠡⠐⢿⠿⣛⠟⠛⠛⠛⠛⠡⢷⡈⢂⢕⢂
    ⠟⣡⣾⣿⣿⣿⣿⣦⣑⠝⢿⣿⣿⣿⣿⣿⡵⢁⣤⣶⣶⣿⢿⢿⢿⡟⢻⣤⢑⢂
    ⣾⣿⣿⡿⢟⣛⣻⣿⣿⣿⣦⣬⣙⣻⣿⣿⣷⣿⣿⢟⢝⢕⢕⢕⢕⢽⣿⣿⣷⣔
    ⣿⣿⠵⠚⠉⢀⣀⣀⣈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣗⢕⢕⢕⢕⢕⢕⣽⣿⣿⣿⣿
    ⢷⣂⣠⣴⣾⡿⡿⡻⡻⣿⣿⣴⣿⣿⣿⣿⣿⣿⣷⣵⣵⣵⣷⣿⣿⣿⣿⣿⣿⡿
    ⢌⠻⣿⡿⡫⡪⡪⡪⡪⣺⣿⣿⣿⣿⣿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃
    ⠣⡁⠹⡪⡪⡪⡪⣪⣾⣿⣿⣿⣿⠋⠐⢉⢍⢄⢌⠻⣿⣿⣿⣿⣿⣿⣿⣿⠏⠈
    ⡣⡘⢄⠙⣾⣾⣾⣿⣿⣿⣿⣿⣿⡀⢐⢕⢕⢕⢕⢕⡘⣿⣿⣿⣿⣿⣿⠏⠠⠈
    ⠌⢊⢂⢣⠹⣿⣿⣿⣿⣿⣿⣿⣿⣧⢐⢕⢕⢕⢕⢕⢅⣿⣿⣿⣿⡿⢋⢜⠠⠈
=== art: GOJO2 ===
    ⠀⠀⠀⠀⠀⠀⠀⢀⠀⠔⡀⠀⢀⠞⢰⠂⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢸⠘⢰⡃⠔⠩⠤⠦⠤⢀⡀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢀⠄⢒⠒⠺⠆⠈⠀⠀⢐⣂⠤⠄⡀⠯⠕⣒⣒⡀⠀
    ⠀⠀⢐⡡⠔⠁⠆⠀⠀⠀⠀⠀⢀⠠⠙⢆⠀⠈⢁⠋⠥⣀⣀
    ⠈⠉⠀⣰⠀⠀⠀⠀⡀⠀⢰⣆⢠⠠⢡⡀⢂⣗⣖⢝⡎⠉⠀
    ⢠⡴⠛⡇⠀⠐⠀⡄⣡⢇⠸⢸⢸⡇⠂⡝⠌⢷⢫⢮⡜⡀⠀
    ⠀⠀⢰⣜⠘⡀⢡⠰⠳⣎⢂⣟⡎⠘⣬⡕⣈⣼⠢⠹⡟⠇⠀
    ⠀⠠⢋⢿⢳⢼⣄⣆⣦⣱⣿⣿⣿⣷⠬⣿⣿⣿⣿⠑⠵⠀⠀
    ⠀⠀⠀⡜⢩⣯⢝⡀⠁⠀⠙⠛⠛⠃⠀⠈⠛⠛⡿⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣿⠢⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⣀⡇⠀⠑⠀⠀⠀⠀⠐⢄⠄⢀⡼⠃⠀⠀⠀⠀
    ⠀⠀⠀⠀⢸⣿⣷⣤⣀⠈⠲⡤⣀⣀⠀⡰⠋⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣶⣤⣙⣷⣅⡀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⢀⣾⣿⣿⣿⣿⣻⢿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀
    ⠀⡠⠟⠁⠙⠟⠛⠛⢿⣿⣾⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀
=== art: GOJO_CAT ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠟⠻⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠀⠀⠈⠻⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣶⣦⡀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⡇⠀⠀⠀⠀⠈⠙⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⠾⠋⠁⢸⣿⡇⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣇⡇⠀⠀⠀⠀⠀⠀⠀⠙⢷⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡾⠛⠁⠀⠀⠀⣿⣼⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡏⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡾⠛⠁⠀⠀⠀⠀⠀⣸⡿⣿⠂⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣇⣿⠀⠀⠀⠀⠀⠶⠶⠶⠶⠶⠶⠿⠷⠶⠶⠤⣤⣤⣀⣀⡀⢀⣤⡾⠛⠁⠀⠀⠀⠀⠀⠀⠀⢠⣿⢣⡟⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⣽⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡷⣸⠇⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⢣⡿⠁⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣼⠃⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠇⠀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡏⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣿⣿⡾⠛⠉⣉⣽⣿⣶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠶⠛⢛⣿⣿⣷⣶⣤⣀⠀⠀⠀⠀⠀⠀⢸⣿⡀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢰⣾⠛⢉⣵⡟⣃⣤⣶⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⣠⣾⠏⣡⣴⣾⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⢈⡹⣇⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠙⣷⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣀⣀⣀⣀⣰⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠶⠖⠲⠾⣿⣿⣦⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⣠⣴⡾⠋⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠛⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠈⠙⢿⣄⠀⠀⠀⠀
    ⠀⠀⣿⡛⠉⠁⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢷⣄⠀⠀
    ⠀⠀⣾⣷⣦⣀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣧⠀
    ⠀⡀⠈⠻⢿⣿⣿⣷⠆⠀⠙⠻⠿⣿⣿⡿⢿⣿⠋⠀⠀⠀⣴⠇⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡆
    ⠀⠻⣟⠛⠛⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠿⣿⣆⣀⣠⣼⢿⣧⠀⠀⠀⢀⣿⠿⢿⣿⣿⣿⣿⣿⣿⣿⠿⣛⠹⣮⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣷
    ⠀⠀⠈⠻⢦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢩⠿⠻⣯⢻⣷⣶⣿⡿⠋⠀⠀⠀⠉⠉⠉⠉⠁⠀⣐⣭⣾⡿⠋⢻⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿
    ⠀⠀⠀⢀⣰⣿⣻⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡿⠛⣍⠡⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡟
    ⠀⠀⠀⠛⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡿⠁
    ⠀⠀⠀⢐⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠟⠀⠀
    ⠀⠀⠀⣼⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠃⠀⠀⠀
    ⠀⠀⠀⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⡟⠀⠀⠀⠀⠀
    ⠀⠀⣰⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠛⠀⠀⠀⠀⠀⠀
=== art: EMU ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣠⣤⣤⣤⣄⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠙⠳⣦⣴⠟⠁⠀⠀⣠⡴⠋⠀⠈⢷⣄⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣴⣶⣿⣿⣿⣿⡿⠿⠿⠿⠿⠿⠿⣿⣿⣿⣿⣷⣦⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀⢀⣴⠟⠁⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣶⣿⣿⡿⠟⠋⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠻⢿⣿⣿⣶⣄⡀⠀⠀⠀⠺⣏⠀⠀⣀⡴⠟⠁⢀⣀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⠿⠋⠁⠀⢀⣴⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢶⣬⡙⠿⣿⣿⣶⣄⠀⠀⠙⢷⡾⠋⢀⣤⠾⠋⠙⢷⡀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⡿⠋⠁⠀⠀⠀⢠⣾⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣦⣠⣤⠽⣿⣦⠈⠙⢿⣿⣷⣄⠀⠀⠀⠺⣏⠁⠀⠀⣀⣼⠿⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⡿⠋⠀⠀⠀⠀⠀⣰⣿⠟⠀⠀⠀⢠⣤⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⣿⣧⠀⠀⠈⢿⣷⣄⠀⠙⢿⣿⣷⣄⠀⠀⠙⣧⡴⠟⠋⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⠏⠀⠀⠀⠀⠀⠀⢷⣿⡟⠀⣰⡆⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⣿⣿⡀⠀⠀⠈⢿⣿⣦⠀⠀⠙⢿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⡿⠁⠀⠦⣤⣀⠀⠀⢀⣿⣿⡇⢰⣿⠇⠀⢸⣿⡆⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⢸⣿⣿⣆⠀⠀⠈⣿⣿⣧⣠⣤⠾⢿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣵⣿⠀⠀⠀⠉⠀⠀⣼⣿⢿⡇⣾⣿⠀⠀⣾⣿⡇⢸⠀⠀⠀⠀⠀⠀⣿⡇⠀⣼⣿⢻⣿⣦⠴⠶⢿⣿⣿⣇⠀⠀⠀⢻⣿⣧⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⢠⣿⡟⡌⣼⣿⣿⠉⢁⣿⣿⣷⣿⡗⠒⠚⠛⠛⢛⣿⣯⣯⣿⣿⠀⢻⣿⣧⠀⢸⣿⣿⣿⡄⠀⠀⠀⠙⢿⣿⣷⣤⣀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⢸⣿⡇⣼⣿⣿⣿⣶⣾⣿⣿⢿⣿⡇⠀⠀⠀⠀⢸⣿⠟⢻⣿⣿⣿⣶⣿⣿⣧⢸⣿⣿⣿⣧⠀⠀⠀⢰⣷⡈⠛⢿⣿⣿⣶⣦⣤⣤⣀
    ⠀⠀⠀⠀⢀⣤⣾⣿⣿⢫⡄⠀⠀⠀⠀⠀⠀⣿⣿⣹⣿⠏⢹⣿⣿⣿⣿⣿⣼⣿⠃⠀⠀⠀⢀⣿⡿⢀⣿⣿⠟⠀⠀⠀⠹⣿⣿⣿⠇⢿⣿⡄⠀⠀⠈⢿⣿⣷⣶⣶⣿⣿⣿⣿⣿⡿
    ⣴⣶⣶⣿⣿⣿⣿⣋⣴⣿⣇⠀⠀⠀⠀⠀⢀⣿⣿⣿⣟⣴⠟⢿⣿⠟⣿⣿⣿⣿⣶⣶⣶⣶⣾⣿⣿⣿⠿⣫⣤⣶⡆⠀⠀⣻⣿⣿⣶⣸⣿⣷⡀⠀⠀⠸⣿⣿⣿⡟⠛⠛⠛⠉⠁⠀
    ⠻⣿⣿⣿⣿⣿⣿⡿⢿⣿⠋⠀⢠⠀⠀⠀⢸⣿⣿⣿⣿⣁⣀⣀⣁⠀⠀⠉⠉⠉⠉⠉⠉⠉⠁⠀⠀⠀⠸⢟⣫⣥⣶⣿⣿⣿⠿⠟⠋⢻⣿⡟⣇⣠⡤⠀⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠉⠉⢹⣿⡇⣾⣿⠀⠀⢸⡆⠀⠀⢸⣿⣿⡟⠿⠿⠿⠿⣿⣿⣿⣿⣷⣦⡄⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣯⣥⣤⣄⣀⡀⢸⣿⠇⢿⢸⡇⠀⢹⣿⣿⣿⡇⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣾⣿⡇⣿⣿⠀⠀⠸⣧⠀⠀⢸⣿⣿⠀⢀⣀⣤⣤⣶⣾⣿⠿⠟⠛⠁⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠙⠛⢛⣛⠛⠛⠛⠃⠸⣿⣆⢸⣿⣇⠀⢸⣿⣿⣿⣷⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢻⣿⡇⢻⣿⡄⠀⠀⣿⡄⠀⢸⣿⡷⢾⣿⠿⠟⠛⠉⠉⠀⠀⠀⢠⣶⣾⣿⣿⣿⣿⣿⣶⣶⠀⠀⢀⡾⠋⠁⢠⡄⠀⣤⠀⢹⣿⣦⣿⡇⠀⢸⣿⣿⣿⣿⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢸⣿⣇⢸⣿⡇⠀⠀⣿⣧⠀⠈⣿⣷⠀⠀⢀⣀⠀⢙⣧⠀⠀⠀⢸⣿⡇⠀⠀⠀⠀⢀⣿⡏⠀⠀⠸⣇⠀⠀⠘⠛⠘⠛⠀⢀⣿⣿⣿⡇⠀⣼⣿⢻⣿⡿⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠸⣿⣿⣸⣿⣿⠀⠀⣿⣿⣆⠀⢿⣿⡀⠀⠸⠟⠀⠛⣿⠃⠀⠀⢸⣿⡇⠀⠀⠀⠀⢸⣿⡇⠀⠀⠀⠙⠷⣦⣄⡀⠀⢀⣴⣿⡿⣱⣾⠁⠀⣿⣿⣾⣿⡇⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣇⠀⢿⢹⣿⣆⢸⣿⣧⣀⠀⠀⠴⠞⠁⠀⠀⠀⠸⣿⡇⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⠀⢀⣨⣽⣾⣿⣿⡏⢀⣿⣿⠀⣸⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠈⢻⣿⣿⣿⣿⣆⢸⡏⠻⣿⣦⣿⣿⣿⣿⣶⣦⣤⣀⣀⣀⣀⠀⣿⣷⠀⠀⠀⣸⣿⣏⣀⣤⣤⣶⣾⣿⣿⣿⠿⠛⢹⣿⣧⣼⣿⣿⣰⣿⣿⠛⠛⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠙⣿⣿⣦⣷⠀⢻⣿⣿⣿⣿⡝⠛⠻⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠛⠉⠁⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣿⣿⣄⢸⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⠟⠻⣿⡿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⡌⠙⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠛⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: BANNER ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀
    ⢸⠉⣹⠋⠉⢉⡟⢩⢋⠋⣽⡻⠭⢽⢉⠯⠭⠭⠭⢽⡍⢹⡍⠙⣯⠉⠉⠉⠉⠉⣿⢫⠉⠉⠉⢉⡟⠉⢿⢹⠉⢉⣉⢿⡝⡉⢩⢿⣻⢍⠉⠉⠩⢹⣟⡏⠉⠹⡉⢻⡍⡇
    ⢸⢠⢹⠀⠀⢸⠁⣼⠀⣼⡝⠀⠀⢸⠘⠀⠀⠀⠀⠈⢿⠀⡟⡄⠹⣣⠀⠀⠐⠀⢸⡘⡄⣤⠀⡼⠁⠀⢺⡘⠉⠀⠀⠀⠫⣪⣌⡌⢳⡻⣦⠀⠀⢃⡽⡼⡀⠀⢣⢸⠸⡇
    ⢸⡸⢸⠀⠀⣿⠀⣇⢠⡿⠀⠀⠀⠸⡇⠀⠀⠀⠀⠀⠘⢇⠸⠘⡀⠻⣇⠀⠀⠄⠀⡇⢣⢛⠀⡇⠀⠀⣸⠇⠀⠀⠀⠀⠀⠘⠄⢻⡀⠻⣻⣧⠀⠀⠃⢧⡇⠀⢸⢸⡇⡇
    ⢸⡇⢸⣠⠀⣿⢠⣿⡾⠁⠀⢀⡀⠤⢇⣀⣐⣀⠀⠤⢀⠈⠢⡡⡈⢦⡙⣷⡀⠀⠀⢿⠈⢻⣡⠁⠀⢀⠏⠀⠀⠀⢀⠀⠄⣀⣐⣀⣙⠢⡌⣻⣷⡀⢹⢸⡅⠀⢸⠸⡇⡇
    ⢸⡇⢸⣟⠀⢿⢸⡿⠀⣀⣶⣷⣾⡿⠿⣿⣿⣿⣿⣿⣶⣬⡀⠐⠰⣄⠙⠪⣻⣦⡀⠘⣧⠀⠙⠄⠀⠀⠀⠀⠀⣨⣴⣾⣿⠿⣿⣿⣿⣿⣿⣶⣯⣿⣼⢼⡇⠀⢸⡇⡇⠇
    ⢸⢧⠀⣿⡅⢸⣼⡷⣾⣿⡟⠋⣿⠓⢲⣿⣿⣿⡟⠙⣿⠛⢯⡳⡀⠈⠓⠄⡈⠚⠿⣧⣌⢧⠀⠀⠀⠀⠀⣠⣺⠟⢫⡿⠓⢺⣿⣿⣿⠏⠙⣏⠛⣿⣿⣾⡇⢀⡿⢠⠀⡇
    ⢸⢸⠀⢹⣷⡀⢿⡁⠀⠻⣇⠀⣇⠀⠘⣿⣿⡿⠁⠐⣉⡀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠉⠓⠳⠄⠀⠀⠀⠀⠋⠀⠘⡇⠀⠸⣿⣿⠟⠀⢈⣉⢠⡿⠁⣼⠁⣼⠃⣼⠀⡇
    ⢸⠸⣀⠈⣯⢳⡘⣇⠀⠀⠈⡂⣜⣆⡀⠀⠀⢀⣀⡴⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢽⣆⣀⠀⠀⠀⣀⣜⠕⡊⠀⣸⠇⣼⡟⢠⠏⠀⡇
    ⢸⠀⡟⠀⢸⡆⢹⡜⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠋⣾⡏⡇⡎⡇⠀⡇
    ⢸⠀⢃⡆⠀⢿⡄⠑⢽⣄⠀⠀⠀⢀⠂⠠⢁⠈⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠄⡐⢀⠂⠀⠀⣠⣮⡟⢹⣯⣸⣱⠁⠀⡇
    ⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁
=== art: CUTE_GIRL ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⢔⣦⣶⣿⣿⣿⣿⡷⠖⠒⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠉⠁⠂⠀⠀⢀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣰⠶⣦⡤⣄⠀⠀⠀⠀⠀⠀⣠⠖⢩⣶⣿⣿⣿⣿⣿⠟⢉⣠⠔⠊⠁⠀⠀⠀⣀⣄⠀⠀⠉⠑⢦⣠⣤⣤⡀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠘⢷⣌⡧⡾⠀⠀⠀⠀⡠⠊⢁⣴⣿⣿⣿⣿⣿⢟⣠⡾⠟⠁⠀⣀⣤⣶⠞⣫⠟⠁⠀⢀⠄⠀⢀⠙⢿⣿⣿⣷⣄
    ⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠉⠉⠁⠀⠀⣠⣾⣶⣶⣿⣿⣿⣿⣿⣿⣷⡿⠋⣀⣤⣶⣿⣿⣋⣴⡞⠁⠀⠀⣠⠊⠀⠀⢸⡄⢨⣿⣿⣿⣿
    ⠀⠀⠀⠀⠀⢃⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⠿⠿⢻⣿⣿⣿⣿⣿⣿⠿⠛⢉⣴⣿⢿⣿⠏⠀⠀⠀⡴⠃⣰⢀⠀⢸⣿⣤⣏⢻⣿⣿
    ⠀⠀⠀⠀⠀⠘⡆⠀⠀⠀⠀⠀⢀⣾⡿⣿⡿⠁⠀⢀⣾⣿⣿⣿⡿⠋⠁⠀⣠⣿⠟⢡⣿⡟⠀⢀⣤⣾⠁⣼⣿⢸⡇⢸⣿⣿⣿⡈⣿⣿
    ⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢀⣾⠋⣼⣿⠁⢀⠀⣼⣿⣿⠟⠁⠀⠀⠀⣰⡿⠋⢠⣿⡿⠁⢠⣾⣿⡏⢀⣿⣿⣾⣿⢸⣿⣿⣿⡇⢹⣿
    ⠀⠀⠀⢰⡶⣤⣤⣄⠀⠀⠀⡼⠁⣼⣿⣿⣾⣿⣰⣿⠟⠁⠀⠀⠀⠀⢠⡿⠁⠀⣾⣿⠃⢠⣿⢿⡿⠁⠸⢿⣿⣿⣿⣿⣿⣿⣿⣿⠸⣿
    ⠀⠀⠀⠘⣧⣈⣷⡟⠀⠀⣰⠁⡼⢻⣿⣿⣿⣿⡿⠋⠀⠀⠂⠒⠒⠒⣾⠋⠀⢠⣿⡏⢠⣿⢃⡿⠁⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⠀⣿
    ⠀⠀⠀⠀⠈⠉⠁⠀⠀⢠⠇⣰⠁⠸⣹⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠐⡇⠀⠀⢸⣿⢃⣿⠋⣿⡀⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⠀⡇
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣰⠃⡀⢀⣿⣿⡏⢘⣶⣶⣶⣷⣒⣄⠀⠀⠀⠀⠀⠸⣿⣾⠃⠰⠁⠙⢦⡀⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⢠⡇
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡔⢁⠞⢁⣾⣿⣿⣷⠟⠁⣠⣾⣿⣿⣧⠀⠀⠀⠀⠀⠀⣿⡏⠀⠀⠀⠀⠀⠙⢦⡀⠀⢿⣿⣿⣿⣿⣿⣿⣸⠃
    ⠀⠀⠀⠀⠀⠀⠀⣠⣾⡖⠁⣠⣾⣿⣿⣿⡏⠀⢰⠿⢿⣿⣯⣼⠁⠀⠀⠀⠀⠀⠹⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⢬⣿⣿⡘⣿⣿⣏⣿⠀
    ⠀⠀⠀⠀⠀⣠⣾⢟⠋⣠⣾⣿⡿⠋⢿⣿⠀⠀⢼⠀⠀⢀⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣙⣷⣴⣆⡀⠀⠀⠈⢿⣧⠸⣿⣿⣿⣿
    ⠀⠀⢀⡤⠞⢋⣴⣯⣾⡿⠟⠋⠀⠀⢸⣿⡆⠀⠸⡀⠉⢉⡼⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣶⣆⠀⠈⢿⣧⠹⣿⣿⣿
    ⠀⠀⠀⣸⣶⣿⣿⠟⠋⠀⠀⠀⠀⣴⡎⠈⠻⡀⠈⠛⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠿⠛⠛⠻⣿⣬⡏⠻⣷⡀⠈⢻⣿⣿⣿⣿
    ⢂⣠⣴⣿⡿⢋⣼⣿⣿⣿⣿⣿⠋⢹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠺⡄⠀⠀⢀⡾⣿⠁⠀⢹⡇⠀⢠⣿⣿⣿⣿
    ⣿⣿⣽⣯⣴⣿⣿⠿⡿⠟⠛⢻⡤⠚⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡇⠈⠉⢉⡴⠃⠀⠀⢸⠇⢠⣿⣿⣿⣿⣿
    ⣿⡇⣿⠿⣯⡀⠀⠀⠈⣦⡴⠋⠀⠀⢀⠨⠓⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠷⠒⠋⠀⠀⠀⠀⠃⣴⣿⠿⣡⣿⠏⠀
    ⠁⠀⠃⠀⠈⠳⣤⠴⡻⠋⠀⢀⡠⠊⠁⠀⠀⢀⡽⢄⠀⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⠏⣺⠟⠁⠀⠀
    ⠀⠀⠀⠀⠀⡰⠋⢰⠁⠀⠀⠀⠀⠀⣀⠤⠊⠁⠀⠀⢱⡀⠘⢆⡀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠖⠛⠛⢉⣤⠞⠁⠀⠀⠀⠀
    ⠀⠀⠀⠀⡜⠁⠀⠈⢢⡀⠀⠀⠀⠀⠁⠀⠀⠀⣀⠔⠋⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠉⠀⠀⠀⣀⠀⠀⠈
    ⠀⠀⠀⡜⠀⠀⠀⠀⢰⠑⢄⠀⠀⠀⠀⠀⠀⠊⠀⢀⣀⢀⠇⠀⡠⠒⠒⢶⠈⠉⠑⡖⠈⠓⢢⠤⢄⣀⣴⣾⣏⠉⠛⠋⠉⠉⠀⠀⠀⢠
    ⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠑⣄⡀⠀⠀⠀⠀⠀⠀⣹⡿⢤⣼⠃⠀⠀⢸⠀⠀⠀⡇⠀⠀⢸⠀⠀⠈⣿⣿⣿⣦⣀⣀⣀⣀⣀⣶⢶⣿
    ⠀⠀⠀⠀⠀⣠⠔⠒⢻⠀⠀⠀⠃⠉⠒⠤⣀⡀⠤⠚⠁⣇⡰⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠌⠀⠀⣰⠟⠋⠁⠀⠀⠀⠀⠈⠉⠛⠦⡻
    ⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈
=== art: POCHITA ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣼⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⣸⣿⣿⣷⣤⣴⣦⣀⣠⣶⡶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⢠⣄⡀⠀⣼⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠘⣿⣿⣿⣿⣿⣿⣿⡾⢛⠋⡛⠻⣿⣿⣿⣿⣧⣴⣶⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⢸⣿⣿⣿⣿⣽⡏⠰⡈⢆⢡⣷⢀⠻⣿⣿⣿⣿⣇⡀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⢻⣿⣿⣿⣻⣿⠄⢣⠘⡄⢺⡏⢄⢣⡌⠻⣿⣿⣿⣿⣿⣿⣷⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣷⣶⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣀⣸⣿⣿⣿⣿⣿⡏⢄⠣⢌⣹⠇⡌⣼⢇⠱⡈⠿⣿⣿⣿⡿⠿⠛⠛⠛⠛⠛⠛⠛⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠹⢿⣿⣿⣿⣷⣿⣿⣦⠑⣂⡿⠰⡐⡿⢈⠆⡑⢢⢙⡿⢉⠐⡠⠑⣈⠂⠥⠘⡀⢃⠰⠀⡌⠙⠯⣉⢩⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠈⠹⣿⣿⣿⣽⣿⣷⣜⠏⡰⢱⡟⡠⢊⠔⣡⡿⢁⠂⡡⠄⢡⠠⠌⢠⠁⠒⡈⠄⡡⢀⠃⠤⣹⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠈⣻⣿⣿⣿⣿⣿⣷⣤⠹⢇⠰⡁⢎⣾⠁⠂⡔⠠⠘⡀⢂⠌⠄⠌⠡⡐⢈⡐⠄⢊⣼⣿⣿⣿⣿⣿⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠙⠛⠛⢻⣿⣿⣯⣿⣿⣷⣌⠢⠑⢬⡇⠌⠡⠠⠑⢂⢁⢢⡈⠌⡐⠡⠠⢁⡐⠈⢼⣿⣿⣿⣿⡿⢁⢻⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠈⣻⣿⣿⣿⣿⣿⣧⣍⢾⠃⠌⢂⡁⢎⣶⣿⣯⣭⡘⠰⡡⢁⠂⠤⢉⠈⠿⣿⣿⠟⢀⠂⠄⠛⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⡙⢹⣿⣿⣿⣿⣿⣿⠈⠔⠂⢤⣿⣿⣿⣿⣿⣅⠀⠹⠄⡘⢀⢂⠡⠂⢄⠠⢈⠄⠊⠌⡐⢉⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢰⡇⡇⣾⣿⣿⡿⣿⣿⣿⡈⠄⢃⠘⣿⣿⣿⣿⣿⣿⠀⢠⠃⠄⠃⠄⡂⢉⠄⢂⠡⢈⠌⡐⢈⠄⡘⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⣓⡘⠿⣿⢷⠙⠛⣻⠡⢈⠄⢊⡘⢿⢿⠿⠟⠃⢠⠞⣨⠐⡉⡐⢈⠤⠈⡄⢂⡁⢂⠌⠄⡂⠔⠘⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣯⠱⢈⠐⠂⢾⣁⣂⣽⣆⠂⣌⣼⠇⠠⢉⠐⡀⠉⠤⢈⠳⠇⡐⠠⢁⠂⡡⠐⡠⠐⠂⠌⡐⠐⡨⠐⠸⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣇⠂⠌⣁⠂⡉⢹⣟⣿⣻⡯⠁⠌⢂⡁⠢⢈⢁⠒⠠⠒⡀⠆⣁⠂⠡⠄⡑⠠⠑⣈⠐⡈⠔⠠⠑⢂⢹⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣧⠌⣀⠒⡈⡐⠻⣮⡷⠃⠌⢂⠡⠠⠑⣀⠊⠄⡑⠠⢁⠂⠤⢈⠁⠆⡐⠡⠌⣀⠂⠡⠌⢂⠡⢂⠘⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢳⣤⠒⠠⠐⠡⣀⠐⡈⠔⡈⠤⠑⡈⠄⠌⡐⢈⡁⢂⠡⠒⡈⠰⠈⢄⡁⠆⠠⠌⠡⣈⠐⡐⡈⠌⣻⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢷⡈⠅⠒⡀⢂⠁⠆⠰⠀⡅⠂⠌⠒⡈⠄⠰⠈⢄⠡⠐⠡⠌⢠⠐⡈⢡⠈⠔⡀⠒⢠⠐⡈⣿⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠠⢁⠂⢿⡆⠌⡁⢺⠇⡁⢊⠐⣄⠉⠄⠃⡄⠊⠌⡐⠌⡀⠆⡁⢂⠌⡐⠠⠉⠄⢂⠁⣾⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠘⣧⠒⡈⠄⢶⢀⠡⡞⡐⠠⢁⣞⠂⢌⠘⢠⠐⣡⡬⠴⢒⠃⡐⡈⠄⢂⠌⠡⠘⡈⠄⢊⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠹⣧⠀⢀⣤⠾⢷⡐⡈⢼⡆⢸⡇⠡⢘⣼⢳⡿⣦⠈⣤⠿⢁⢂⠁⠆⡈⠔⠠⢁⠊⠄⡡⠡⢐⠈⣼⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⢃⢹⡷⠋⢄⠂⠜⠹⢶⠾⢁⠚⢿⡴⠟⢡⣿⠓⠸⣿⠋⡐⠄⢂⠉⡐⡐⠨⠐⠡⡈⢄⡛⢁⠂⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⢰⠀⡂⠿⠈⢄⡈⢂⡁⢂⠐⠂⡌⠠⠐⡈⠴⣿⢀⠡⠘⣆⠰⢈⠂⢡⠐⣀⠃⠡⢒⡼⠋⡐⠈⣴⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: GIRL ===
    ⠀⠀⠀⠀⢀⡠⠤⠔⢲⢶⡖⠒⠤⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⣠⡚⠁⢀⠀⠀⢄⢻⣿⠀⠀⠀⡙⣷⢤⡀⠀⠀⠀⠀⠀⠀
    ⠀⡜⢱⣇⠀⣧⢣⡀⠀⡀⢻⡇⠀⡄⢰⣿⣷⡌⣢⡀⠀⠀⠀⠀
    ⠸⡇⡎⡿⣆⠹⣷⡹⣄⠙⣽⣿⢸⣧⣼⣿⣿⣿⣶⣼⣆⠀⠀⠀
    ⣷⡇⣷⡇⢹⢳⡽⣿⡽⣷⡜⣿⣾⢸⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀
    ⣿⡇⡿⣿⠀⠣⠹⣾⣿⣮⠿⣞⣿⢸⣿⣛⢿⣿⡟⠯⠉⠙⠛⠓
    ⣿⣇⣷⠙⡇⠀⠁⠀⠉⣽⣷⣾⢿⢸⣿⠀⢸⣿⢿⠀⠀⠀⠀⠀
    ⡟⢿⣿⣷⣾⣆⠀⠀⠘⠘⠿⠛⢸⣼⣿⢖⣼⣿⠘⡆⠀⠀⠀⠀
    ⠃⢸⣿⣿⡘⠋⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⡆⠇⠀⠀⠀⠀
    ⠀⢸⡿⣿⣇⠀⠈⠀⠤⠀⠀⢀⣿⣿⣿⣿⣿⣿⣧⢸⠀⠀⠀⠀
    ⠀⠈⡇⣿⣿⣷⣤⣀⠀⣀⠔⠋⣿⣿⣿⣿⣿⡟⣿⡞⡄⠀⠀⠀
    ⠀⠀⢿⢸⣿⣿⣿⣿⣿⡇⠀⢠⣿⡏⢿⣿⣿⡇⢸⣇⠇⠀⠀⠀
    ⠀⠀⢸⡏⣿⣿⣿⠟⠋⣀⠠⣾⣿⠡⠀⢉⢟⠷⢼⣿⣿⠀⠀⠀
    ⠀⠀⠈⣷⡏⡱⠁⠀⠊⠀⠀⣿⣏⣀⡠⢣⠃⠀⠀⢹⣿⡄⠀⠀
    ⠀⠀⠘⢼⣿⠀⢠⣤⣀⠉⣹⡿⠀⠁⠀⡸⠀⠀⠀⠈⣿⡇⠀⠀
=== art: FRIEREN ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⠀⠠⠀⠄⠠⠠⠀⠤⠀⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠐⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠀⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⢀⠔⠁⠀⠀⠀⢀⠤⠀⠀⠀⠀⠀⠀⠠⢀⠀⠀⠀⠀⡈⠢⡀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⠤⠑⡖⠁⠀⠀⠀⠀⠔⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠈⠠⡘⢖⠁⠈⠐⡄⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢠⠂⡐⠀⠀⡊⠀⠀⠀⠀⠀⡡⠒⠀⠀⠀⢀⠆⢣⠀⠀⠀⠀⠀⢄⠡⡀⠀⠀⠈⢌⢆⠂⠄⠈⢢⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠆⠐⠀⠀⡜⢀⠀⠀⠀⠀⡔⠀⠀⠀⠀⡠⠃⠀⠀⠣⡀⠀⠀⠀⠀⠐⢵⡀⠀⠀⠈⡌⡂⠈⠆⠀⢢⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⡘⠀⠉⠀⢀⡇⠌⠀⠀⠀⡔⠀⠀⠀⣀⠖⠀⠀⠀⠀⠀⠈⠦⣀⠀⠀⠀⠀⠇⠀⠀⠀⠘⢧⠀⠸⠀⠀⢇⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⡁⠀⠀⠀⢸⢢⠁⠀⠀⠐⠀⠀⡠⠚⠀⠉⠂⠀⠀⠀⠀⠀⠘⠀⠸⢅⠀⠀⠸⠀⠀⠀⠀⣿⡀⠀⡇⠀⠈⠄⠀⠀⠀⠀
    ⢰⠠⣤⠄⠸⠀⠀⠀⣀⡜⡌⠀⠀⠀⢸⠉⣅⣤⣀⣒⠄⠀⠀⠀⠀⠀⠀⠠⢐⣠⣤⣤⣱⠒⡇⠀⠀⠀⢸⡡⠀⠃⠤⠤⢤⡄⠖⢒⠆
    ⠀⠱⢄⠈⢅⠒⢐⠠⢄⠈⡇⠀⠀⠐⢻⠟⢋⠟⢋⠙⣗⡄⠀⠀⠀⠀⠀⢐⡟⢉⠙⢮⠙⢷⡟⠀⠀⠀⢸⢀⠄⠂⢠⠍⠀⢀⠄⠊⠀
    ⠀⠀⢀⠕⠠⡀⠈⠂⠣⠀⠆⠀⠀⠀⠸⠂⢸⣀⠻⢇⢸⠀⠀⠀⠀⠀⠀⠸⣀⠿⢄⢸⠀⢁⠃⠀⠀⠠⢸⢨⠀⠀⠀⡠⠔⢡⠀⠀⠀
    ⠀⢀⠌⠀⠀⠑⠠⡀⠀⠂⡆⡆⠀⠀⡀⡆⠈⢫⢀⠸⠊⠀⠀⠀⠀⠀⠀⠀⠫⢄⠨⠊⠀⡘⠀⠀⠀⢰⢸⠈⢀⡠⠐⠁⠀⠈⡆⠀⠀
    ⢀⠎⢀⠎⠀⠀⡘⠘⠈⠐⢣⢰⡀⠀⠸⣜⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⢥⠆⠀⡠⢨⠎⠉⢡⠐⠀⠀⠀⠀⢰⠀⠀
    ⡎⠰⢉⠀⠀⠀⣣⠂⠀⠀⠀⠻⠔⠄⡀⢯⡘⠂⠀⠀⠀⠀⠀⠰⣓⡄⠀⠀⠀⠀⠨⡰⢀⡞⠠⠊⠻⠊⠀⠀⠈⠰⠀⠀⠀⠆⢰⠀⠀
    ⡇⡆⢈⠀⡇⠀⠃⠀⠀⠀⠀⠀⠀⠀⠈⠉⠑⠢⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠘⠁⠀⠀⠀⠀⠀⠀⠀⢰⣸⠀⠀⡘⢀⡌⠀⠀
    ⠈⠣⠨⡐⠘⠆⡘⢄⡀⠀⠀⠀⠀⠀⢨⠑⡀⠀⠀⢀⣸⢲⡆⠠⠀⠤⢒⣾⣻⣄⠎⡇⢀⠞⠠⠀⠀⠀⠀⠀⢋⠆⢀⠔⠡⠋⠀⠀⠀
    ⠀⠀⠀⠈⠁⠀⠈⠁⠀⠀⠀⠐⢏⠢⡠⢆⠱⡔⠒⠉⢾⢱⢫⢉⠉⠉⠛⠘⠁⣷⠀⢎⡍⢀⢦⠔⢋⠄⠀⠀⠉⠀⠁⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⢄⡊⠢⡀⠙⠂⠐⣄⠀⠋⣾⢨⠿⠿⠀⢀⡠⡸⢞⠀⠈⠀⠚⢁⠄⠋⢤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⠢⢀⡈⠁⡠⠐⡐⠈⠐⠁⡩⠏⡚⠛⠙⣂⣏⠓⠚⠐⠄⠐⢀⠀⣀⡠⢝⡗⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢠⠖⠋⠮⡥⣴⡀⠀⠐⠁⠀⡠⠃⠀⠀⠀⠀⠊⠀⠀⡁⠢⠀⠀⠁⠀⢀⡺⢩⡞⡂⢄⠓⢄⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⡴⠖⡡⡎⣸⠈⡌⠀⠀⡴⢊⠀⠀⠀⠀⠀⠀⠀⢀⠚⠀⠀⠀⠱⢣⠈⢡⠃⡈⡃⡄⠀⠐⠈⢄⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠰⠁⢀⠃⢇⢉⠆⠱⡀⡴⠁⡘⠀⠀⠀⠀⠀⠀⢀⠊⠀⠀⠀⠀⣆⡈⡢⠊⢠⡡⡱⠁⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢰⠀⠈⠀⠈⢦⠉⡢⡈⠑⡺⠃⠀⠀⠀⠀⢀⠔⠁⠀⠀⠀⠀⠀⠈⠣⣅⠔⢔⠔⠁⠀⠀⠀⠀⠜⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠉⢢⣼⠏⠀⠀⠀⠀⠀⡠⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠈⢗⠤⠀⣀⡀⡠⠊⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠂⠤⠄⠊⠒⠁⠧⠀⠀⠀⠀⠘⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠺⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: HU_TAO ===
    ⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣖⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⣾⡟⣉⣽⣿⢿⡿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢠⣿⣿⣿⡗⠋⠙⡿⣷⢌⣿⣿⠀⠀⠀⠀⠀⠀⠀
    ⣷⣄⣀⣿⣿⣿⣿⣷⣦⣤⣾⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀
    ⠈⠙⠛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡀⠀⢀⠀⠀⠀⠀
    ⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠻⠿⠿⠋⠀⠀⠀⠀
    ⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⡄
    ⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⢀⡾⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣷⣶⣴⣾⠏⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠛⠛⠋⠁⠀⠀⠀
=== art: SMIRK ===
    ⠀⠀⣸⣾⡗⡏⠉⣗⠿⡆⠀⠀⢈⠀⣆⡀⠀⠀⠀⠀⠈⠂⡀⠀⢸⠀⠀⠀⠁⠀
    ⠀⢠⡟⠀⡇⣂⠠⠼⣦⢳⠀⠀⢸⢰⠄⠀⠀⠀⠀⠀⠀⠀⠙⣤⠀⢆⠀⠀⠀⠀
    ⠀⣸⠀⠒⡇⠀⠀⠀⢹⢯⢇⠀⠘⡼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⡈⢆⠀⠀⠀
    ⢀⠆⠀⠀⢠⠀⠀⠀⠀⠳⣫⠂⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢎⡆⠀⠀
    ⡜⡇⠀⠀⠀⠀⠀⠀⠀⠀⠙⠧⠀⢰⠀⠀⠀⢐⣂⣀⣤⣤⣤⣤⣤⣤⣤⡍⠄⠀
    ⢓⢴⠀⠀⠀⡀⣀⣀⠄⠀⠀⠈⢑⣄⡆⠀⠀⠸⠿⠿⠿⣛⡻⢿⣿⠟⡟⠀⢠⡀
    ⡿⠸⣄⣮⣷⣿⣿⣿⡆⠀⠀⠀⠀⠙⢿⡀⠀⠀⠀⡀⠀⠿⠷⠾⠶⠞⠚⣀⠨⣣
    ⣷⡎⢏⢿⠡⣬⣝⠯⠇⠐⠀⠀⠀⠀⠀⠑⠀⠀⠀⠀⠀⠀⠐⠘⠈⠀⠀⠉⠀⠘
    ⣟⡇⢺⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂
    ⣿⡇⣸⠀⠀⠀⠀⠀⠀⠀⣀⡀⠄⠤⠤⠄⠒⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠
    ⣿⠃⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⢠
    ⣇⠀⡿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼
    ⠱⡀⡇⡏⢷⢤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢠⣴⣧⠏
    ⠀⠐⣇⡇⣶⢀⠀⠈⣗⠖⡤⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⠄⠂⠀⢳⠀⣼⠀
    ⠀⠀⠘⢱⡏⠀⣰⠀⡇⠠⣇⠀⡀⠉⣗⣒⡶⠂⠀⡆⠁⠀⠀⠀⢀⠄⠚⠁⡖⣠
=== art: ZERO_TWO ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⢀⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⣴⣤⡀⠀⢀⣀⣤⠤⠤⠶⠖⠒⠒⠒⠒⠒⠲⠶⠤⢤⣀⡀⣼⣛⣧⠀⢁⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣸⣏⢻⣍⠁⠀⢀⡀⠤⠄⠒⠒⠒⠒⠒⠒⠀⠤⠄⠀⠀⢸⡳⢾⢹⡀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣠⠖⠋⠀⢯⡞⣎⡆⠁⠀⠀⠀⢀⡀⠀⠤⠤⠤⠤⠄⠀⡀⠀⠀⠻⣽⣻⡌⠹⣄⠀⠐⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢀⡾⠁⠀⠀⢀⢾⣹⢿⣸⠀⣰⠎⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠆⠹⡿⣏⢆⠈⢷⡀⠀⠆⠀⠀
    ⠀⠀⠀⠀⣰⠏⠀⠀⢀⠔⠛⠄⠙⠫⠇⢀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢄⠠⠒⠒⠵⡈⢳⡀⠀⠀⠀
    ⠀⠄⠀⡰⠁⠀⠀⢠⠊⠄⠂⠁⠈⠁⠒⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⡀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠄⢳⡀⠈⠀
    ⠈⠀⣸⠃⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠀⠐⠀⠀⠐⠀⢀⠀⠀⠀⠀⢷⠀⠀
    ⠀⢠⠇⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⠀⠀⠰⠀⠀⠀⠀⠀⠀⡄⠀⡀⠆⢰⠀⠀⠀⡄⠀⠀⠀⠸⡄⠀
    ⠀⣼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠉⠀⡄⠀⢀⠀⠀⡄⠂⠆⠀⠀⠀⠀⢁⠀⢁⠀⢸⠀⢇⠀⡇⠀⠀⠀⠀⣧⠀
    ⠀⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠰⡃⠄⠈⡄⠀⡇⢀⢰⠀⠀⠀⠀⡼⠀⠸⢰⠀⣤⣅⣁⣴⠀⠀⠀⠀⢻⠀
    ⢠⡇⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠱⢀⣁⣤⣧⣴⣧⣄⡇⢸⣸⡄⠀⢀⣆⠀⣦⠊⢹⣿⣿⡛⠻⢿⠀⠀⠀⠀⢸⡇
    ⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⣃⠀⠀⢴⣿⠟⠉⢈⣿⣿⣿⡟⠇⠀⠀⠀⠀⠀⠀⢸⣶⣿⣿⡿⣧⠀⢸⡇⠀⢃⠀⢸⡇
    ⠈⡇⠀⠀⠀⠀⠀⠀⠀⡀⢉⡄⠀⢸⠁⠀⣷⣾⣿⣿⡟⣿⠀⠀⠀⠀⠀⠀⠀⠀⢧⠙⠋⢁⡟⢀⡦⢧⠀⠸⡇⢸⡇
    ⠀⣿⠀⠀⠀⠀⠀⠀⢀⠔⠪⡄⠀⠸⣁⠀⠹⣉⠉⠉⢠⠏⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢲⠛⠆⢉⠀⢸⠀⢀⢇⢸⡇
    ⠀⢿⠀⠀⠀⠀⠀⢀⠃⡐⠐⣴⠀⠀⠏⠉⠖⠉⠋⡙⠁⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⢀⡠⠀⠊⠄⠌⢘⠀⠀⠸⢸⠀
    ⠀⢸⠀⠀⠀⠀⠀⠈⣆⢃⠘⠘⡀⠀⡸⡘⡐⡐⠠⠁⠀⡴⡖⣲⠒⠊⠉⠉⠉⠙⢿⣤⡇⠀⠀⠀⠈⢐⠀⠀⠁⣿⠀
    ⠀⠘⡇⠀⠀⠀⠀⠀⠈⢶⠬⣁⡇⠀⠀⠑⠐⠤⠐⠀⠀⡇⠉⠀⠀⠀⠀⠀⠀⠀⠀⢙⠇⠀⠀⠀⠀⣼⢀⠀⠀⣿⠀
    ⠀⠀⣇⠀⠀⠀⢰⠀⠀⠈⠀⠂⡇⠀⠃⢡⠀⠀⠀⠀⠀⠹⡄⠀⠀⠀⠀⠀⠀⠀⣠⠎⠀⠀⢀⡴⡞⡉⠈⠀⠀⣿⠀
    ⠀⠀⣹⠀⠀⠀⠀⠀⠀⠀⠀⡀⡇⠀⢰⠈⡷⡀⠀⠀⠀⠀⠸⢶⣀⠀⠀⢀⣰⠎⠁⢀⡶⠏⠁⣈⠆⠁⡀⠰⢸⡇⠀
    ⠀⠀⢸⡀⢸⠀⠀⠆⠀⠀⠀⠀⡇⠀⠀⠀⢡⡄⡏⢆⠒⠢⠤⠤⠤⢨⠥⡴⠒⠚⠉⠉⠀⠀⡠⠁⡘⢠⠁⢀⠆⡇⠀
    ⠀⠀⢸⡇⠀⡀⠀⠀⠀⠀⢠⢠⠁⠀⠘⡀⠠⣷⠃⠀⠀⠀⠀⠉⢰⠈⢱⠄⡀⡄⠀⢸⠀⠐⠀⠰⠁⠀⠀⡞⠄⣷⠀
    ⠀⠀⠀⣷⠀⡇⠀⠀⠀⠸⠀⡈⠀⠀⢂⠃⠀⡄⠇⠀⠀⠀⠀⠀⢔⠳⠀⠀⠣⠍⠒⠤⣰⠁⢠⠃⢠⠀⠀⠅⠀⢻⡀
    ⠀⠀⠀⠉⠀⠁⠀⠀⠀⠀⠀⠁⠀⠀⠈⠀⠀⠁⠈⠀⠀⠀⠀⠀⠀⠉⠁⠀⠀⠈⠁⠀⠈⠀⠁⠀⠈⠀⠀⠁⠀⠈⠁
=== art: K_ON ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⣤⢤⡤⣤⠤⣔⡲⡔⢦⣒⠦⡤⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣶⢻⢥⣛⡜⢮⣼⢲⣙⢦⢳⡹⢲⡍⣞⡱⢫⡜⣝⡢⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⡿⢣⣿⢾⡱⢮⣽⢧⢣⢏⣾⢣⡝⣣⠞⣥⢏⣳⢚⣬⠳⣍⠶⣩⠦⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⡿⣫⣾⣽⣿⠟⡦⢽⣾⣏⢮⡓⢮⢞⡗⣮⢱⡛⢦⣋⣖⢫⠖⡽⢬⡓⢧⡛⡼⣡⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⡀⠁⠀⠈⡐⠈⠀⠀⠀⠀⠀⢀⣼⣿⡿⣱⣿⣿⣿⢏⡞⣭⣿⣿⢚⢦⣋⠗⣮⣿⣜⡣⣝⢣⡳⢬⡳⣋⢾⡥⢫⠧⣝⢲⢣⡝⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠂⠀⢠⠀⠀⠀⠃⠀⠈⠀⠀⠀⢠⣿⣿⡿⣱⡿⣿⣿⡟⡼⣘⣾⣿⣯⡙⣶⡏⡞⢦⠽⣧⣓⢎⡧⣝⢣⡳⠽⣞⢯⢣⣛⡬⣓⢯⡜⡜⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠀⢠⣿⣿⣿⣳⣿⣿⣎⡻⣸⡱⢻⡟⠁⡧⡝⡏⣷⣙⢮⣙⣿⣎⡷⢞⡬⢧⣙⢧⣋⢿⣷⡘⠶⣍⠾⣝⡼⡸⠄⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⢀⡄⠀⠀⡄⠀⠀⠀⠀⠀⠀⠁⠀⣾⣿⣿⣷⣿⣿⣿⣿⢣⢎⡺⠋⠀⠇⡷⣙⢃⢺⡱⢎⡞⡼⣧⣳⠈⢺⡱⢎⡶⣩⠶⣹⡭⣛⢬⡛⣼⠶⣹⢹⡀⠀⠀⠀⠀⠀⠀⠀
    ⢀⠂⠀⠀⠀⠀⠠⠀⠀⠀⡀⠀⠀⠘⣰⣿⣿⢿⣿⣿⠻⣿⡟⡜⢮⠋⠀⠀⠈⡷⣹⠀⢀⢏⠷⣸⡱⢿⣏⡀⠀⠹⣇⡞⣥⢛⡴⣏⠳⣎⡵⢎⣷⢣⢧⢣⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠁⠀⠀⢃⠀⠀⠀⡇⠀⠀⠀⢹⣿⣿⣿⣿⣿⣷⣄⡙⠭⡇⠀⠁⠄⠀⢱⣹⠀⠀⠨⣯⠵⣩⢛⣯⡇⠀⠀⠙⢜⠶⣩⠶⣩⢗⡣⢞⣱⢚⡧⡞⣭⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⡃⠀⠀⡤⠀⠀⠀⢇⠀⠀⠀⠈⣿⣿⣿⣷⣿⣿⣿⠜⡕⠀⠀⠀⠀⠀⠂⣿⡀⠀⠀⠀⠻⡥⣋⢿⡅⢠⠠⠂⠈⢛⠦⣏⠵⣪⠝⣮⣱⢋⣷⣙⠖⣆⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⡇⠀⠀⠸⠂⠀⠀⠉⠀⠀⠀⠀⢼⣿⣿⣾⣿⣿⣿⡞⡇⠀⠀⢀⣀⣤⣄⠸⡌⠀⠀⠀⠀⠘⢭⢖⣇⠀⣀⣠⣀⡈⠳⣎⢻⡥⣛⣼⡣⢏⣼⣹⡞⣱⣄⠀⠀⠀⠀⠀
    ⠀⠀⠐⠰⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠸⣿⣿⣿⣿⣿⣷⡿⠀⢠⣾⠟⢋⣭⣟⣆⠁⠀⠀⠀⠀⠀⠀⠙⢼⢰⣟⣯⣛⠻⢷⣽⣙⡶⢣⣾⣏⠳⣼⡇⣟⡴⢫⢦⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠉⠿⠿⢿⣿⣻⣿⡇⠀⡿⠁⢠⣿⣽⣤⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣧⣧⠈⣿⠙⣽⢣⣿⣿⡳⣜⠯⣵⢺⡟⠚⢵⢠⡀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢀⠠⠀⠀⠀⠀⠃⢀⠀⠀⠀⠀⢹⣿⣿⣷⠀⠘⠅⢸⣿⣿⢿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⢺⣿⣿⣿⡿⠀⠏⠰⣹⢾⡟⢿⣿⣎⡟⡼⣇⢿⠀⠀⠀⠉⠉
    ⠀⠀⠀⠀⡀⠄⠀⠀⠀⠀⠀⡀⢀⢀⠐⠀⠀⠀⠀⣼⡿⣸⢻⣧⠀⠀⠂⡣⣦⠾⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠾⠴⠾⠁⠀⠀⠀⡝⣾⢭⢻⣿⡜⣣⣝⡷⣊⡇⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠐⠁⠰⠀⠀⠀⢀⣿⡱⢣⢏⡞⣣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡟⡧⢎⡳⣿⠼⡱⢎⡷⣩⢶⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀⠀⢸⣷⢭⡓⡞⡼⣕⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡟⣗⡫⣕⣻⢭⠳⣭⣿⣱⢺⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣷⣯⣼⣵⢎⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣵⣿⣇⡗⣎⢾⣍⠳⣖⣿⡧⢻⠀⠀⠀⠀
    ⠀⢢⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣷⣞⣢⠀⠀⠀⠀⠀⠀⠔⠀⠐⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⡗⡼⣊⢶⢪⢽⣞⣿⣓⣻⠀⠀⠀⠀
    ⠀⠀⠣⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣟⣿⣱⠳⣍⢮⡓⢾⣿⡿⣒⠇⠀⠀⠀⠀
    ⠀⠀⢠⣆⠀⠀⠀⠀⠀⠀⠀⢀⡀⢀⠀⠔⠁⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⣀⢄⠀⠀⠀⠀⠀⠀⠀⠀⢀⢀⣴⣶⣿⣿⡿⣿⣿⣿⢔⡻⣜⢲⡙⢾⠟⢧⣹⠀⠀⠀⠀⠀
    ⠀⠀⠸⣿⡇⠤⠭⠀⠀⠁⠉⠁⠀⠀⠀⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⠒⢁⠀⠄⠀⢠⠐⠀⠀⢿⣿⣿⢿⡿⠁⣿⣿⣏⠮⣕⢎⢧⡻⡏⠀⢮⠁⠀⠀⠀⠀⠀
    ⠀⠀⠀⢹⣿⡀⠐⡀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡀⠐⠤⠀⠁⠀⠀⠱⠈⣿⣿⡟⠁⠀⣹⣿⢥⡛⡜⣎⢾⢳⠀⠀⠁⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⣿⣿⠀⠣⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⣈⠔⠀⢠⣿⣿⣿⣦⠀⣿⡟⢦⣹⠕⠁⣸⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢹⣿⡀⠀⠈⠠⢀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⣠⣆⠥⡀⠀⢸⣿⣿⣿⣿⣿⣤⠛⠊⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: CINNAMOROLL_MUSIC ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣤⠶⡆⠀⠀⠀⠀⣾⠗⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⡇⣴⣇⠀⣤⠀⠀⣿⡾⠃⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠘⠋⠀⠛⠁⣠⣿⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡟⣷⠠⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⣡⣤⡶⠶⠶⠶⢶⣤⣄⡀⠀⠀⠰⠿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣟⣫⣤⣶⣶⣶⣶⣶⣤⣍⡛⢷⣄⣀⣀⡀⠀⠀⢀⣀⣤⣴⠶⣦⣄⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⠿⠛⠋⠉⠉⠉⠉⠛⠻⢿⣷⣽⣿⣟⠛⠛⠛⠛⠉⠁⠀⠀⠀⢻⣦
    ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⠹⣇⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿
    ⠀⠀⠀⠀⠀⠀⠀⣤⣿⣿⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⢶⣤⣤⡄⠀⣿⣿⡀⣿⣀⠀⠀⠀⠀⣀⣠⣴⠟⠁
    ⠀⠀⠀⠀⣀⣴⢿⣿⡏⣿⢀⣀⣤⡶⠀⢀⣀⣀⣴⡄⠀⠀⠀⠀⠀⢹⣿⣷⡿⡛⠛⠛⠛⠻⠿⠉⠁⠀⠀
    ⠀⣠⣴⠾⠛⠉⢸⣿⣇⣿⡈⠉⠁⠀⠀⠘⠛⠉⠉⢀⣀⣀⣀⣤⡶⠟⠏⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣼⠟⠁⠀⠀⠀⠀⠻⣿⣾⣿⣧⣀⣀⣀⣀⣤⣤⡴⢾⡍⠻⠏⢻⣶⠶⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⣿⡀⠀⠀⠀⠀⠀⢀⣾⢿⣧⠈⢋⣹⣿⣿⣅⠀⠀⠘⠋⠀⠀⠀⢻⣶⣄⢻⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠘⢷⣧⣤⣤⣴⡾⠏⣡⣿⠏⣴⡿⡟⠚⠿⠛⠁⠀⠀⠀⢀⣴⠿⣾⣿⠟⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢀⣴⡿⠃⠀⣿⣵⣆⠀⠀⠀⠀⠀⠀⠀⣾⠋⢀⣿⠶⢿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⢠⣤⣴⠾⠛⠉⠀⠀⠀⠈⠉⠙⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: KANAO ===
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⣸⣿⣿⣷⣄⠀⠀⠀⠀⣀⡀⠀⠀⠀⢀⣠⣤⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣿⣿⣿⣿⡿⣿⣿⣷⣦⣴⣿⣿⣿⣄⣠⠶⣿⡿⢿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⡿⠿⢛⣿⣿⣿⣷⣮⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣯⠽⢿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⠟⠉⠀⠀⠀⠀⠈⠛⠻⠿⣽⣿⣿⣿⢻⣿⡹⣶⡾⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⢟⣾⢏⡤⠴⠶⠆⠀⠀⠀⠀⠀⠤⠤⣄⣻⣿⣿⣾⡏⣷⠽⣿⡋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣠⣾⣿⣧⣾⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢹⣿⣿⣿⡇⣿⣷⣿⣿⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢠⣾⠿⠛⣿⣿⣿⣠⣤⡴⣦⣝⣦⠀⠀⠀⠀⠖⣋⣡⣤⣸⡇⢻⣿⣿⣿⣿⣼⢻⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠈⠁⠀⠀⣿⣿⣧⠟⢁⣠⣄⡉⠹⠀⠀⠀⠀⠚⠉⢹⡉⠙⠻⣦⣿⣿⣯⣭⡿⢦⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣹⣿⣿⣦⣮⣶⣷⣽⣤⠀⠀⠀⠀⣰⣯⣅⡙⢧⠀⢹⣿⣿⣿⣿⣇⠀⠹⣷⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣰⣋⣿⡇⠈⢻⣿⣿⠿⠁⠀⠀⠀⠀⠻⣿⣿⣿⣿⠁⠀⣾⣿⡆⣀⡌⢧⠀⠘⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣾⣬⣹⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠁⠀⢰⣿⣿⣿⠙⡆⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠘⢦⣽⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⡟⣿⣿⡏⣡⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⡿⢳⡀⠀⠀⠀⠀⠐⢶⠀⠀⠀⠀⠀⠀⠀⣸⣿⡇⣿⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠶⣿⣿⣇⣿⢻⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣿⠻⣿⣿⣿⣷⡙⠦⣤⡀⠀⢀⣠⡤⠖⠛⣡⣾⣿⣿⣿⣿⠈⢿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠟⠀⢹⣿⢻⣿⣿⣦⣌⡉⠉⠉⢀⣀⣠⣾⣿⣿⣿⡏⢹⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡏⠀⣹⣿⣿⣿⡏⠀⢸⣿⣿⣿⣿⣿⣯⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢁⡼⠁⡿⣿⣿⣧⣀⣜⣿⣿⡿⢟⣡⡞⠀⠙⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⣸⣿⡿⢿⣴⣿⣿⣷⣶⣿⣿⣿⠁⠀⠀⠈⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⠁⠀⢀⣿⢷⣶⣿⠋⠙⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⣿⠹⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢠⣿⠃⠀⠀⣼⣿⠸⣿⣧⣀⣴⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⣿⠀⠘⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⡾⠛⠀⣠⣾⣿⣿⡇⣿⣿⣿⣿⣿⡏⠀⠀⢹⠀⠀⠀⠀⢀⡇⠀⠀⢸⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
=== art: ZERO_TWO_2 ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡷⡀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠺⣟⡽⠀⠀⠀⠀⠀⠀⠀⠈⠑⠂⢤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⠊⠁⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠈⠳⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢠⠎⠀⠀⠆⠀⢰⡃⠀⠐⡄⠀⢲⠀⠃⢻⠀⠀⠀⡄⠀⠀⠈⠱⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠸⣶⣄⣺⢇⡄⠀⡇⠀⢰⢺⠱⣄⠀⠘⢆⠈⡆⠀⠸⠀⠀⠀⡇⠀⠀⠀⠰⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠙⢿⡏⢸⠀⠀⡇⠀⢸⣾⢀⣨⣷⡄⠈⢣⣇⠀⠀⡆⠀⠀⡇⠀⠀⠀⠀⠀⠸⡄⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢸⠃⢸⡄⢸⡇⡀⢸⢿⡏⠀⣀⣹⣦⡀⢿⠀⠀⡇⠀⠀⡇⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣼⡄⠈⡇⠈⣿⣿⣼⣞⣷⠾⣿⣿⡿⠉⢻⠀⠀⡇⠀⠀⡿⠟⢷⡄⠀⠀⠀⡘⡄⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⡇⣇⢸⢿⡀⠇⠻⠉⠸⠏⣠⠟⠉⠀⠀⠀⡄⠀⠿⠀⠀⣿⡀⠀⡇⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣿⣿⣾⣷⣧⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⢀⡿⢃⣼⠁⠀⠀⠃⠀⢸⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢻⡻⢯⣻⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢷⠀⠀⠀⢸⡧⡎⢸⠃⠀⠀⢠⢀⣸⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢸⡇⢸⠉⠧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⢸⡇⡇⢸⠀⠀⠀⢸⡈⣿⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢸⠃⢸⡀⠀⠀⠀⢀⡠⠴⠚⠀⠀⠀⠀⠀⢸⠀⠀⠀⢸⡇⢸⢸⡀⠀⠀⠈⡇⢻⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠘⡆⠀⢇⠀⠀⠰⠋⠀⠀⠀⠀⠀⠀⠀⠀⢨⠀⠀⠀⢸⠃⠈⡇⡇⠀⠀⠀⢱⢸⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⣿⠀⠈⢢⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠍⠀⠀⠀⣿⠀⠀⢡⠱⠀⠀⠀⠸⡜⡇⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⣽⢆⠀⠀⠀⠀⠀⣠⠒⠁⠉⢀⡆⠀⠀⣿⡄⠀⠸⡄⠀⠀⠀⠀⢇⠇⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢸⡄⠀⠀⣿⠀⠓⠤⠔⠒⠻⡇⢀⡴⠚⠉⡇⠀⠀⡧⢇⠀⠀⢱⡀⠀⠀⠀⠘⣾⡀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢸⡆⠀⢰⣿⡄⡆⠀⠀⠀⠀⢷⠋⠀⠀⠀⡇⠀⢀⠁⢈⠷⢶⣤⡷⣤⣤⢤⡤⠞⠓⠒⠦⢄⡀⠀
    ⠀⠀⠀⠀⠀⠀⢸⠀⠀⢸⡇⡇⢁⠀⠀⠀⢀⢻⠀⠀⠀⠀⡇⠀⢸⠀⠣⢤⣀⣞⡏⠉⡱⠋⠀⠀⠀⠀⠀⠀⠙⡆
    ⠀⠀⠀⠀⠀⠀⢸⠀⠀⡞⠀⢡⢸⡀⠀⣀⡼⠀⢳⠀⠀⠀⡇⠀⡏⠀⢀⣾⣿⣾⣟⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻
    ⠀⠀⠀⠀⠀⠀⢸⠀⢠⠇⠀⠈⢇⡷⣺⢿⠏⡞⠑⠚⠉⢀⠀⢠⠁⣠⣿⣿⣻⣟⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿
    ⠀⠀⠀⠀⠀⠀⣿⠀⡘⢀⣠⠞⣉⠵⣡⠏⠀⢧⠀⠀⠀⣸⠀⣾⣼⣉⣉⣍⡿⡝⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
=== art: SPIRITED_AWAY ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠟⠋⠉⠉⠉⠉⠛⢿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⠇⢀⣾⡄⠀⠀⠀⠀⠀⠀⣷⡄⠈⢿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⡟⠀⢸⣿⡇⠀⠀⠀⠀⠀⠀⣿⣿⠀⢸⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⡇⠀⠈⠛⠁⠀⠀⠀⠀⠀⠀⠉⠁⠀⢸⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⡇⠰⣿⣿⠆⠀⠀⠀⠀⠀⠰⠿⠿⠗⢸⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⡇⠀⠠⠤⠀⠀⠀⠀⠀⠀⠀⠒⠒⠀⢸⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡇⠀⢰⣿⡆⠀⠀⠀⠀⠀⠀⢸⣿⡆⢸⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⠀⠈⣿⡇⠀⠀⠀⠀⠀⠀⢸⡿⠀⢸⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⡆⠀⢹⠁⠀⠀⠀⠀⠀⠀⠸⠁⠀⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠺⠿⠿⠿⠿⠟⠀⢀⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠤⠤⠄⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣤⣤⣴⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀
    ⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀
    ⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀
    ⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀
    ⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀
    ⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀
    ⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀
    ⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀
    ⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀
    ⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀
=== art: GIRL2 ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠤⠖⠚⢉⣩⣭⡭⠛⠓⠲⠦⣄⡀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢀⡴⠋⠁⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠳⢦⡀⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⡴⠃⢀⡴⢳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣆⠀⠀⠀
    ⠀⠀⠀⠀⡾⠁⣠⠋⠀⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢧⠀⠀
    ⠀⠀⠀⣸⠁⢰⠃⠀⠀⠂⠊⢣⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣇⠀
    ⠀⠀⠀⡇⠀⡾⡀⠀⠀⠀⠀⣀⣹⣆⡈⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⠀
    ⠀⠀⢸⠃⢀⣇⡈⠀⠀⠀⠀⠀⠀⢀⡑⢄⡀⢁⡁⠤⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇
    ⠀⠀⢸⠀⢻⡟⡻⢶⡆⠀⠀⠀⠀⡼⠟⡳⢿⣦⡑⢄⠀⠉⠠⠔⠀⠀⠀⠀⢸⡇
    ⠀⠀⣸⠀⢸⠃⡇⢀⠇⠀⠀⠀⠀⠀⡼⠀⠀⠈⣿⣗⠆⠄⠂⠁⠁⠁⠀⠁⢸⠁
    ⠀⠀⡏⠀⣼⠀⢳⠊⠀⠀⠀⠀⠀⠀⠱⣀⣀⠔⣹⠁⠀⠀⠀⠀⠀⠀⠀⢠⡟⠀
    ⠀⠀⡇⢀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⢸⠃⠀
    ⠀⢸⠃⠘⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠁⠀⠀⢀⠀⠀⠀⠀⠀⣾⠀⠀
    ⠀⣸⠀⠀⠹⡄⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀⠸⠀⠀⠀⠀⠀⡇⠀⠀
    ⠀⡏⠀⠀⠀⠙⣆⠀⠀⠀⠀⠀⠀⠀⢀⣠⢶⡇⠀⠀⢰⡀⠀⠀⠀⠀⠀⡇⠀⠀
    ⢰⠇⡄⠀⠀⠀⡿⢣⣀⣀⣀⡤⠴⡞⠉⠀⢸⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⣧⠀⠀
    ⣸⠀⡇⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⢹⠀⠀⢸⠀⠀⢀⣿⠇⠀⠀⠀⠁⠀⢸⠀⠀
    ⣿⠀⡇⠀⠀⠀⠀⠀⢀⡤⠤⠶⠶⠾⠤⠄⢸⠀⡀⠸⣿⣀⠀⠀⠀⠀⠀⠈⣇⠀
    ⡇⠀⡇⠀⠀⡀⠀⡴⠋⠀⠀⠀⠀⠀⠀⠀⠸⡌⣵⡀⢳⡇⠀⠀⠀⠀⠀⠀⢹⡀
    ⡇⠀⠇⠀⠀⡇⡸⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠮⢧⣀⣻⢂⠀⠀⠀⠀⠀⠀⢧
    ⣇⠀⢠⠀⠀⢳⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡎⣆⠀⠀⠀⠀⠀⠘
    ⢻⠀⠈⠰⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠘⢮⣧⡀⠀⠀⠀⠀
    ⠸⡆⠀⠀⠇⣾⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠆⠀⠀⠀⠀⠀⠀⠀⠙⠳⣄⡀⢢⡀
=== art: TOMIOKA ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣶⣦⣤⣴⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣷⣦⢄⡀⠀⠀⠀⠀⠀
    ⠀⠀⠒⠶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣷⢿⣿⣆⠀⠀⠀
    ⠀⠀⠀⠀⠀⢉⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣼⣿⣿⣿⣿⣿⡏⠀⠀⠀
    ⠀⠀⠀⠀⣰⣿⣿⣿⣿⣼⣿⣿⣿⠟⢻⣿⣿⠟⣹⠿⠋⠁⢿⣿⢿⣿⣇⠀⠀⠀
    ⠀⢤⣴⣾⣿⣿⣿⣿⣿⣿⣿⠟⠓⠒⢺⠟⣓⠊⠀⠀⠶⠦⠼⣇⢸⣿⣿⣦⠀⠀
    ⠀⠀⠀⠀⠉⠉⣽⣿⣿⣟⣽⠒⠛⣻⣍⠑⠀⠀⠀⠘⠒⢒⡒⠮⢘⣿⣿⣿⣿⣦
    ⠀⠀⢀⣀⣤⣾⣿⣿⣿⣿⣿⠀⡀⡙⡋⠀⠀⠀⠀⠀⠀⣛⣋⡀⢸⣿⣿⡀⠀⠀
    ⠀⠀⠀⠀⠀⢀⣼⡿⡙⣝⢿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⣿⡿⣷⣄⠀
    ⣀⣀⣀⣀⣐⣛⣛⣻⣌⠫⠢⠁⠀⠀⠀⠀⠦⠤⠤⠆⠀⠀⠀⢠⠟⡸⣷⡀⠀⠀
    ⠈⠙⠻⢿⣿⣿⣿⣿⣿⣿⣶⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠇⠊⠀⠀⠀⠀⠀
    ⠀⠀⠀⢀⣨⣿⢿⣿⣿⡻⣿⣟⢎⠐⠄⡀⠀⠀⠀⢀⣠⢚⣷⣄⠀⠀⠀⠀⠀⠀
    ⠀⠀⢀⣽⣿⣿⣿⣿⡿⡟⢹⣿⣶⣌⡐⠠⠭⢀⠨⠕⣊⣼⣸⡿⠿⠶⠄⠀⠀⠀
    ⠀⠀⠉⠀⣰⡿⠛⠁⢠⠁⢸⣿⣿⣿⣿⣶⡆⡜⢰⣾⣿⣿⣷⢷⢄⠀⠀⠀⠀⠀
    ⠀⢀⡀⠒⠉⠇⠀⠀⠃⠀⢸⣿⣾⣿⣿⡿⡇⣈⣼⣿⣽⣿⡏⢶⠯⣈⡐⠤⠀⠀
=== art: MISAKA ===
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣢⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡔⠤⡶⠞⠙⡏⠉⠉⠹⠯⡭⢓⡲⢤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠴⠚⡟⠀⠈⠒⣺⠟⡱⢄⠀⢀⣀⡀⠀⠉⠢⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⢞⣵⡥⠔⠓⠒⠠⣞⢴⣫⣴⠚⣿⣏⠞⠑⠿⣺⣟⢶⣄⡈⠳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣏⡼⠟⠡⠴⠶⠲⠤⠬⣆⣹⡿⣟⣫⡞⠀⠙⠳⣦⡙⢷⡵⣽⣷⣾⣤⣀⣀⣀⡀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠟⠋⠀⠀⠀⠀⢀⣀⣀⡤⣴⣖⡲⡒⢲⣏⠙⢲⣤⡙⢿⣆⠙⢾⣆⠀⠈⢗⠦⣀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠚⠁⠀⠀⣠⡴⡾⣹⠉⢱⢸⣧⠹⣏⠺⣮⣆⢻⣆⠀⠘⣟⢷⡻⣆⠈⠻⡄⠀⢹⣦⡈⠑⢦⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣠⠔⠉⠀⠀⠀⣠⡞⠁⢧⣿⣿⢼⠀⣿⢩⢣⣹⡤⠝⡿⡞⡟⡄⠀⠘⡤⢝⡽⡜⢷⡵⡑⢾⣿⣿⣤⡀⠑⠀⠀
    ⠀⠀⠀⠀⢀⡴⠊⠀⠀⠀⠀⣠⣎⢸⡟⠀⣸⣼⣏⣞⣇⢻⡀⢣⠛⣷⣠⡼⣿⡷⣶⡀⠀⢣⣄⣻⡱⠀⠹⣆⢀⠱⣟⣎⠉⠉⠁⠀
    ⠀⠀⣠⠜⠁⠀⠀⠀⠀⢀⡔⢹⢸⡜⡇⡄⣿⣼⢷⣼⣽⢮⢧⠀⠻⠾⠋⣷⣿⣿⢸⡟⠁⢸⢣⣶⢸⣄⠀⠘⣆⠙⢞⢯⢧⠀⠀⠀
    ⠀⡴⠃⠀⠀⠀⠀⠀⣠⠋⠀⠸⡸⣇⢱⣣⢿⣏⢸⢺⣿⠀⠙⠃⠀⠀⠀⠝⠫⠾⠗⠀⠀⠈⣺⣹⡸⠹⣦⠀⠸⣄⠸⣯⠫⢇⠀⠀
    ⢰⠃⠀⠀⠀⠀⢀⡴⠃⠀⠀⠀⢣⣿⡄⣷⣷⣿⡬⠿⠾⠂⡄⠀⠀⠀⠀⠴⠾⠗⠀⠀⠀⠀⢙⣵⠇⣤⡹⡆⠘⣿⣇⢹⣿⣼⠀⠀
    ⣿⠀⠀⠀⠀⠀⠋⠳⣄⠀⠀⠀⠀⠹⣷⣹⣿⣿⠿⠟⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣧⡸⣿⣻⢨⡿⣾⣼⡿⣿⠀⠀
    ⠈⠳⣄⠀⠀⠀⠀⠀⠈⠙⠦⣄⠀⠀⢨⣿⠿⣿⡆⠀⠀⠀⠰⠤⠔⠒⠉⠀⠀⠀⢀⡄⣼⣿⣿⣿⣿⣇⢹⢙⣼⠁⠈⡿⢂⠏⠀⠀
    ⠀⠀⠈⠓⢄⠀⠀⠀⠀⠀⠀⠈⠓⣶⣞⣿⢸⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠏⢸⣿⣿⣿⣿⣷⠹⡾⠸⠃⠀⠀⠁⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠑⢦⡀⠀⠀⠀⠀⠀⠘⡆⢹⣾⣿⣿⣿⣿⣿⣶⣤⣀⣀⣤⣾⡿⠁⢀⣘⣿⡝⢿⣿⢯⡣⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠉⠳⣄⡀⠀⠀⠇⢧⠘⡽⠑⠙⣟⠻⣯⢟⠛⠛⡿⡿⠋⢀⢿⣯⢉⠍⠉⠓⣿⠈⠳⠅⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡾⠙⠷⣤⡀⢸⠀⡇⠀⠣⢸⡸⠙⠈⡄⠀⣻⠀⠀⣜⠇⡇⠘⠀⠀⢀⣱⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣴⣒⠀⠙⣻⢸⢣⠀⢀⠴⠁⢸⠀⣔⡾⢃⡤⢪⠏⢰⠁⢀⣤⠒⣹⠛⠁⠉⠉⠙⢶⡀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡄⠉⢂⡇⡜⣿⠒⠯⠒⣹⣿⡄⡟⡴⠋⣠⠏⣠⣿⡶⢹⢃⡾⡁⠀⢀⢦⠀⠀⠀⢧⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣠⣾⡞⣈⠁⢐⠎⣰⣿⡟⢹⡟⡡⠞⣡⡾⢋⠞⢀⠇⡾⡹⠀⢠⠏⡎⠀⠀⠀⠸⡄⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠒⠺⣷⠋⢰⢿⡿⢁⣞⠏⢀⣴⠏⡠⠋⠀⡾⢠⠇⡇⢀⠏⡸⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡜⠁⠀⣸⣸⡇⡾⣠⣶⢟⣡⠎⠀⠀⢰⡇⢸⢸⠁⠈⢀⠇⠀⠀⣰⠀⠀⢱⡄⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠎⠀⠀⠀⣟⣿⣿⠿⣫⠴⠋⠀⠀⠀⠀⢺⠀⠼⢸⠀⠀⡼⠀⠀⠀⢿⡀⠀⣸⠳⣄⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠻⡨⠖⠋⠀⠀⠀⠀⠀⠀⠀⠈⣆⢹⠘⠆⢰⡇⠀⠀⠀⠈⢣⡀⢿⡤⠟⣷⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠁⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢮⣷⣥⢸⠃⠀⣠⠴⢒⣉⣭⣭⣴⣾⡟⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣇⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⢿⢸⡠⢊⣡⠖⠉⠀⢿⣿⣿⠟⠀⠀⠀
=== art: GIRL3 ===
    ⠀⠀⠀⠀⠀⠀⢣⠀⠀⢷⠀⠀⢣⠀⠀⠻⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠀⢀⡟⢦⡽⢷⠀⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢸⠀⠀⢸⡄⠀⠈⢇⠀⠀⠈⠿⣿⡿⣷⣄⠀⠀⢀⣀⡠⠤⠤⠤⠤⡤⢴⡟⠓⠀⠀⠀⠀⣷⣾⡆⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⡇⠀⢤⠼⠀⣃⠉⠂⢨⣧⡥⠟⢒⠉⠁⠀⠀⠀⠀⠀⠀⠈⠢⡄⠤⡄⠀⠀⠀⢻⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⢸⡄⣀⡤⠿⠀⡠⠗⠉⢀⡠⠔⠊⠁⢀⡔⠁⠀⢦⠀⠀⠀⠀⠀⠀⠀⠀⠨⠷⣤⠀⠀⠀⠘⠿⡇⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠘⠓⢆⡴⠋⠀⠀⠀⣠⠊⠀⠀⡇⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⣦⠀⠀⠀⠀⠘⢦⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠟⠀⠀⠀⠀⣰⠇⠀⠀⠀⡇⠀⠘⣆⠀⠀⠀⠀⠀⠀⠐⣿⣿⣿⣇⠀⢂⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⠀⠀⠀⠀⡼⡿⠀⠀⠀⠀⢸⠀⢠⠟⣆⠀⠀⠀⠀⢠⠀⠈⣿⣏⡘⡀⢸⡄⠀⠸⠀⢳⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⢡⠃⠀⡄⠀⡜⠀⡇⠀⠀⠀⠀⠘⡆⢸⠀⠈⢧⡀⠀⠀⠀⢣⠴⠁⠹⡀⢣⠘⠃⠀⠀⡇⠀⣇⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⣠⠞⠁⣴⡜⠀⠀⠀⡸⠧⣀⢻⠀⠀⠀⠀⠀⢳⣷⡤⠒⠉⠱⣄⠀⠀⠸⠀⠀⠀⠱⡸⠀⠀⠀⠀⣇⠀⢹⠀⠀⠀⠀
    ⠀⠀⠀⠀⢀⣠⣊⠡⠔⠊⡏⢠⠀⠀⣰⠁⠀⠈⠙⢷⠀⠀⠀⢠⡉⣇⢀⣀⢀⣀⣈⣦⡀⠀⢆⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⣾⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⠸⠀⢠⣷⡾⠿⢷⣮⣛⣆⠀⠀⠈⡗⠾⡿⠟⣿⣿⣿⠛⢿⡆⠸⡑⠢⢤⣤⠄⠭⠶⢄⠀⣠⡏⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢧⣧⣾⣯⠀⠀⣴⣿⣿⠈⠣⡀⠀⡇⠀⠀⢸⠿⡿⣿⠀⠈⢹⢆⣇⠀⠀⢿⡆⠀⢠⠂⠀⢹⠁⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠿⠇⢻⠀⠀⣿⠛⢿⡄⠀⠘⢆⢸⠀⠀⢸⣀⣠⠏⠀⢠⠏⠀⠙⠀⡀⢸⠁⠀⡎⠀⠀⢸⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠋⠀⠀⠀⡇⠀⠈⠒⠚⠀⠀⠀⠀⠙⠀⠀⠀⠀⠀⠀⣠⠃⠀⠀⣠⠞⠀⠈⡇⡸⠀⠀⡇⢸⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⢀⠞⡁⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠃⣀⠔⠊⠀⠀⠀⢠⣷⠁⠀⠀⢁⠈⡆⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠠⠶⠗⡾⠁⠀⠀⠀⢠⡇⠀⠀⠀⠀⢠⡤⠖⠒⠈⠒⡄⠀⠀⣰⠗⡻⠃⠀⠀⠀⠀⣀⣼⠃⠀⠀⠀⢸⠀⢳⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⡸⠁⠀⠀⢀⠔⡏⣹⡄⠀⠀⠀⠘⣆⠀⠀⠀⠀⡇⠀⠐⠁⡴⠁⠀⠀⠀⢀⣼⡟⣿⠀⠀⠀⠀⡜⠣⡈⢇⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢠⠇⠀⠀⡠⠋⠀⡷⠃⠈⠓⢤⣀⠀⠈⠀⠀⠀⠉⢀⣠⣴⢾⠃⠀⠀⠀⣴⡏⠈⠀⢸⠀⠀⠀⢠⠇⠀⠈⠛⠆⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⢸⠀⢀⠜⠁⠀⠀⠁⠀⠀⢠⠇⠈⢙⡶⠤⠤⠒⣾⣿⠟⢁⡏⠀⠀⣠⠊⣼⠀⠀⠀⢿⠂⠀⢠⠏⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠘⣄⠎⠀⠀⠀⠀⠀⠀⠀⡎⢀⠔⢹⠁⠀⢀⣶⠏⠀⢀⣾⠀⢠⣾⣧⡜⢹⢀⣀⡼⠾⢦⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⢠⡷⠋⠉⠙⢒⣿⣿⠋⠀⣴⣿⢧⣴⣿⣿⡏⠉⠉⢁⠌⠀⣀⠤⣧⠀⠀⠀⠐⡆⠀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠀⠀⢀⣴⣿⣿⡏⡠⠾⠟⠁⢸⣿⣿⡟⠀⠀⡠⠃⣠⢞⣥⣾⣿⠀⠀⠀⠀⢱⡀⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣇⣴⣿⣿⠏⢈⣝⡿⠃⠀⢠⣿⣿⣿⠁⠀⠰⢡⡞⣹⣿⣿⡟⢻⠀⠀⠀⠀⠸⡇⠀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣛⣛⢿⣇⣠⡾⠿⠠⠦⠐⠛⠛⢛⠇⠀⠀⡷⢁⡾⠉⠀⣹⠦⣼⡆⠀⠀⠀⠀⠹⡀⠀⠀
    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠗⠛⠉⠁⠀⢀⣉⡉⣉⣁⡶⠀⠀⢀⣀⢠⡎⠀⠀⣰⣻⣿⣷⡔⢋⡽⠤⢿⣇⠀⠀⠀⠀⠀⢃⠀⠀

================================================
FILE: src/aniworld/ascii/__init__.py
================================================
from .ascii import display_ascii_art, display_banner_art, display_traceback_art

__all__ = [
    "display_ascii_art",
    "display_banner_art",
    "display_traceback_art",
]


================================================
FILE: src/aniworld/ascii/ascii.py
================================================
import platform
import random
import re
from pathlib import Path


def __load_ascii_content():
    """Load the contents of the ASCII.txt file."""
    ascii_file = Path(__file__).parent / "ASCII.txt"
    with open(ascii_file, encoding="utf-8") as f:
        return f.read()


def __parse_ascii_blocks():
    """Parse ASCII content into categorized blocks."""
    content = __load_ascii_content()
    pattern = r"=== (banner|art|traceback): (\w+) ===\s*\n([\s\S]*?)(?=^=== (?:banner|art|traceback): |\Z)"
    matches = re.findall(pattern, content, flags=re.MULTILINE)

    blocks = {"banner": [], "art": [], "traceback": [], "all": []}
    for block_type, _, block in matches:
        cleaned = block.strip("\n")
        if cleaned:
            blocks[block_type].append(cleaned)
            blocks["all"].append(cleaned)

    return blocks


def __is_windows_legacy():
    """Return True if the system is Windows older than version 11."""
    if platform.system() != "Windows":
        return False
    try:
        return int(platform.release()) < 11
    except Exception:
        return True


def __random_block(blocks, fallback):
    """Return a random block from blocks or fallback if empty."""
    if blocks:
        return random.choice(blocks)
    if fallback:
        return random.choice(fallback)
    return ""


def display_ascii_art():
    """Return a random ASCII art string appropriate for the system."""
    blocks = __parse_ascii_blocks()
    if platform.system() == "Windows" and __is_windows_legacy():
        return __random_block(blocks["banner"], fallback=blocks["all"])
    print(__random_block(blocks["art"], fallback=blocks["all"]), flush=True)


def display_banner_art():
    """Return a random banner ASCII art string."""
    blocks = __parse_ascii_blocks()
    print(__random_block(blocks["banner"]), flush=True)


def display_traceback_art():
    """Return a random traceback ASCII art string."""
    blocks = __parse_ascii_blocks()
    print(__random_block(blocks["traceback"]), flush=True)


if __name__ == "__main__":
    print(display_ascii_art())


================================================
FILE: src/aniworld/autodeps.py
================================================
import os
import platform
import shutil
import subprocess
from pathlib import Path
from typing import List

PLATFORM = platform.system()

try:
    from .common import fetch_github_asset_urls
    from .config import GLOBAL_SESSION
    from .logger import get_logger

except ImportError:
    from aniworld.common import fetch_github_asset_urls
    from aniworld.config import GLOBAL_SESSION
    from aniworld.logger import get_logger


# -----------------------------
# MPV
# -----------------------------
def get_mpv_release_urls() -> dict[str, list[str]]:
    """
    Fetch all Windows MPV release URLs, separated into v3 and non-v3 builds.

    Returns:
        {
            "v3": [...],      # all v3 release URLs
            "non_v3": [...]   # all non-v3 release URLs
        }
    """
    repo = "shinchiro/mpv-winbuild-cmake"

    patterns = {
        "v3": r"mpv-x86_64-v3-\d{8}-git-[a-f0-9]+\.7z$",
        "non_v3": r"mpv-x86_64-(?!v3-)\d{8}-git-[a-f0-9]+\.7z$",
    }

    urls: dict[str, list[str]] = {}

    for key, pattern in patterns.items():
        urls[key] = fetch_github_asset_urls(repo, pattern)

    return urls


def get_mpv_windows_url(v3: bool = False) -> str:
    """
    Get a single Windows MPV URL.

    Args:
        v3: whether to get the v3 build (default False → non-v3)
    """
    all_urls = get_mpv_release_urls()
    key = "v3" if v3 else "non_v3"
    return all_urls.get(key, [None])[0]  # return first match or None


# -----------------------------
# FFmpeg
# -----------------------------
def get_ffmpeg_windows_url() -> str:
    """Get the latest Windows FFmpeg full-build ZIP URL from BtbN/FFmpeg-Builds."""
    repo = "BtbN/FFmpeg-Builds"
    pattern = r"ffmpeg-master-latest-win64-gpl\.zip$"
    urls = fetch_github_asset_urls(repo, pattern)
    return urls[0] if urls else None


# -----------------------------
# Syncplay
# -----------------------------
def get_syncplay_release_url() -> List[str]:
    """Fetch the URLs for the latest Windows Syncplay portable ZIP release."""
    repo = "Syncplay/syncplay"
    portable_pattern = r"Syncplay[_-]\d+(?:\.\d+)*_Portable\.zip$"
    return fetch_github_asset_urls(repo, portable_pattern)


def get_syncplay_windows_url() -> str:
    """Get Windows Syncplay URL (first match)."""
    urls = get_syncplay_release_url()
    return urls[0] if urls else None


# -----------------------------
# Dependencies
# -----------------------------
deps = {
    "mpv": {
        "Windows": {"package": "mpv.net", "url": None},
        "Linux": {"package": "mpv"},
        "Darwin": {"package": "mpv"},
    },
    "syncplay": {
        "Windows": {"package": "Syncplay.Syncplay", "url": None},
        "Linux": {"package": "syncplay"},
        "Darwin": {"package": "syncplay"},
    },
    "iina": {"Darwin": {"package": "iina"}},
    "7z": {"Windows": {"url": "https://7-zip.org/a/7zr.exe"}},
    "ffmpeg": {
        "Windows": {"package": "Gyan.FFmpeg", "url": None},
        "Linux": {"package": "ffmpeg"},
        "Darwin": {"package": "ffmpeg"},
    },
}


# -----------------------------
# Dependency Manager
# -----------------------------
class DependencyManager:
    """Manage binaries with system package manager or direct download."""

    def __init__(self, install_folder=None):
        self.deps = deps
        self.install_folder = Path(
            install_folder
            or os.getenv("ANIWORLD_INSTALL_FOLDER", Path.home() / ".aniworld")
        )
        self.install_folder.mkdir(parents=True, exist_ok=True)
        self.logger = get_logger(__name__)
        self.logger.debug(f"Dependency folder: {self.install_folder}")

    def fetch_binary(self, name: str) -> Path:
        dep_info = self.deps.get(name, {}).get(PLATFORM, {})

        # System-wide first
        sys_path = shutil.which(name)
        if sys_path:
            self.logger.debug(f"{name} found system-wide at {sys_path}")
            return Path(sys_path)

        url = dep_info.get("url")

        # Lazy resolution for MPV Windows URL
        if name == "mpv" and PLATFORM == "Windows" and not url:
            url = get_mpv_windows_url()
            dep_info["url"] = url

        # Lazy resolution for FFmpeg Windows URL
        if name == "ffmpeg" and PLATFORM == "Windows" and not url:
            url = get_ffmpeg_windows_url()
            dep_info["url"] = url

        local_path = self.install_folder / Path(url).name if url else None

        # Local folder
        if local_path and local_path.exists():
            self.logger.debug(f"{name} found in {self.install_folder}")
            return local_path

        # Package manager
        if self._install_with_package_manager(name):
            sys_path_after = shutil.which(name)
            if sys_path_after:
                return Path(sys_path_after)
            if local_path and local_path.exists():
                return local_path

        # Download fallback
        self.logger.debug(f"Downloading {name} for {PLATFORM} from {url}...")
        resp = GLOBAL_SESSION.get(url, stream=True)
        resp.raise_for_status()
        with open(local_path, "wb") as f:
            for chunk in resp.iter_content(chunk_size=8192):
                f.write(chunk)

        if PLATFORM != "Windows":
            local_path.chmod(0o755)

        self.logger.debug(f"{name} downloaded to {local_path}")
        return local_path

    def _install_with_package_manager(self, name: str) -> bool:
        dep_info = self.deps.get(name, {}).get(PLATFORM, {})
        pkg_name = dep_info.get("package")
        if not pkg_name:
            return False

        try:
            if PLATFORM == "Windows":
                subprocess.run(
                    ["winget", "install", "-e", "--id", pkg_name, "-h"], check=True
                )
            elif PLATFORM == "Darwin":
                subprocess.run(["brew", "install", pkg_name], check=True)
            else:
                if shutil.which("apt"):
                    subprocess.run(["sudo", "apt", "update"], check=True)
                    subprocess.run(
                        ["sudo", "apt", "install", "-y", pkg_name], check=True
                    )
                elif shutil.which("pacman"):
                    subprocess.run(["sudo", "pacman", "-Sy", pkg_name], check=True)
                else:
                    return False

            self.logger.debug(f"{name} installed via package manager on {PLATFORM}")
            return True

        except (subprocess.CalledProcessError, FileNotFoundError) as e:
            self.logger.debug(f"Package manager failed for {name} on {PLATFORM}: {e}")
            return False


# -----------------------------
# Player paths
# -----------------------------
def get_player_path() -> Path:
    manager = DependencyManager()
    use_iina = os.getenv("ANIWORLD_USE_IINA") == "1"
    use_aniskip = os.getenv("ANIWORLD_ANISKIP") == "1"

    if PLATFORM == "Darwin" and use_iina and not use_aniskip:
        return manager.fetch_binary("iina")

    return manager.fetch_binary("mpv")


def get_syncplay_path() -> Path:
    if PLATFORM == "Darwin":
        syncplay_path = Path("/Applications/Syncplay.app/Contents/MacOS/Syncplay")
        if syncplay_path.exists():
            return syncplay_path
    manager = DependencyManager()
    return manager.fetch_binary("syncplay")


# -----------------------------
# Testing
# -----------------------------
def _ensure_xvfb():
    """On headless Linux: install Xvfb if missing and start a virtual display on :99."""
    if PLATFORM != "Linux":
        return
    if os.environ.get("DISPLAY"):
        return
    _log = get_logger(__name__)
    if not shutil.which("Xvfb"):
        _log.info("Xvfb not found — installing via apt...")
        try:
            subprocess.run(["sudo", "apt-get", "install", "-y", "xvfb"],
                           check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except (subprocess.CalledProcessError, FileNotFoundError) as e:
            _log.warning(f"Could not install Xvfb: {e}")
            return
    _log.info("No DISPLAY found — starting Xvfb on :99")
    subprocess.Popen(
        ["Xvfb", ":99", "-screen", "0", "1280x720x24", "-nolisten", "tcp"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    os.environ["DISPLAY"] = ":99"


def ensure_patchright_chromium():
    """Install the patchright Chromium browser if not already present."""
    import sys
    _log = get_logger(__name__)
    try:
        import patchright  # noqa: F401
    except ImportError:
        _log.debug("patchright not installed, skipping chromium check")
        return

    _ensure_xvfb()
    try:
        _log.debug("Ensuring patchright chromium is installed...")
        _log.info("Installing patchright chromium (this may take a moment)...")
        subprocess.run(
            [sys.executable, "-m", "patchright", "install", "chromium"],
            check=True,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        _log.debug("patchright chromium is ready")
        _log.info("patchright chromium is ready")
    except (subprocess.CalledProcessError, FileNotFoundError) as e:
        _log.warning(f"patchright chromium install failed: {e}")


if __name__ == "__main__":
    print(get_player_path())
    print(get_syncplay_path())


================================================
FILE: src/aniworld/common/__init__.py
================================================
from .common import fetch_github_asset_urls, get_latest_github_release, unzip

__all__ = ["fetch_github_asset_urls", "get_latest_github_release", "unzip"]


================================================
FILE: src/aniworld/common/common.py
================================================
import re
import subprocess
import sys
from pathlib import Path

try:
    from ..config import GLOBAL_SESSION
except ImportError:
    from aniworld.config import GLOBAL_SESSION


def get_latest_github_release(repo):
    """
    Fetch the latest release tag of a GitHub repository.

    Args:
        repo: GitHub repo in "owner/repo" format, e.g. "shinchiro/mpv-winbuild-cmake"

    Returns:
        The tag name of the latest release
    """
    api_url = f"https://api.github.com/repos/{repo}/releases/latest"
    resp = GLOBAL_SESSION.get(api_url)
    resp.raise_for_status()
    release_data = resp.json()
    return release_data.get("tag_name")


def fetch_github_asset_urls(repo, asset_patterns, release="latest"):
    """
    Fetch all download URLs of GitHub release assets matching one or more regex patterns.

    Args:
        repo: GitHub repo in "owner/repo" format, e.g. "shinchiro/mpv-winbuild-cmake"
        asset_patterns: Regex pattern(s) to match asset file names
        release: Release tag or "latest" (default)

    Returns:
        List of URLs matching any of the patterns (empty list if none found)
    """
    if isinstance(asset_patterns, str):
        asset_patterns = [asset_patterns]

    if release == "latest":
        release = get_latest_github_release(repo)

    api_url = f"https://api.github.com/repos/{repo}/releases/tags/{release}"
    resp = GLOBAL_SESSION.get(api_url)
    resp.raise_for_status()
    assets = resp.json().get("assets", [])

    matched_urls = []

    for pattern_str in asset_patterns:
        pattern = re.compile(pattern_str, re.IGNORECASE)
        for asset in assets:
            url = asset.get("browser_download_url")
            if url and pattern.search(url):
                matched_urls.append(url)

    return matched_urls


def unzip(file_path, target_dir):
    file_path = Path(file_path)
    target_dir = Path(target_dir)
    target_dir.mkdir(parents=True, exist_ok=True)

    if file_path.suffix.lower() == ".zip":
        if sys.platform.startswith("win"):
            # TODO: implement
            pass
        else:
            # Use system unzip on macOS/Linux
            print(f"Extracting ZIP: {file_path} -> {target_dir}")
            subprocess.run(
                ["unzip", "-o", str(file_path), "-d", str(target_dir)], check=True
            )
    elif file_path.suffix.lower() == ".7z":
        # use 7z
        if sys.platform.startswith("win"):
            # TODO: implement
            pass
        else:
            # Use system 7z on macOS/Linux
            print(f"Extracting 7z: {file_path} -> {target_dir}")
            subprocess.run(
                ["7z", "x", str(file_path), f"-o{str(target_dir)}"], check=True
            )
    else:
        raise ValueError(f"Unsupported archive format: {file_path}")


================================================
FILE: src/aniworld/config.py
================================================
import os
import re
from enum import Enum
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path

import fake_useragent
from niquests import RequestException, Session
from packaging.version import parse as parse_version

from .env import merge_env
from .logger import get_logger

VERSION = None

try:
    VERSION = version("aniworld")
except PackageNotFoundError:
    VERSION = None


def is_newest_version() -> bool:
    """Checks if the installed version is the newest available on PyPI."""
    if not VERSION:
        return False

    try:
        response = GLOBAL_SESSION.get("https://pypi.org/pypi/aniworld/json")
        response.raise_for_status()
        latest_version = response.json()["info"]["version"]
        return parse_version(VERSION) >= parse_version(latest_version)
    except RequestException:
        # Could not fetch PyPI info, assume not newest
        return False


# AniWorld configuration directory
ANIWORLD_CONFIG_DIR = Path.home() / ".aniworld"

# Load .env file whenever config is imported
merge_env(
    Path(__file__).resolve().parent / ".env.example",
    ANIWORLD_CONFIG_DIR / ".env",
)

logger = get_logger(__name__)

NAMING_TEMPLATE = os.getenv(
    "ANIWORLD_NAMING_TEMPLATE",
    "{title} ({year}) [imdbid-{imdbid}]/Season {season}/{title} S{season}E{episode}.mkv",
)

# Video codec configuration
VIDEO_CODEC = os.getenv("ANIWORLD_VIDEO_CODEC", "copy")

# Simple codec mapping using ffmpeg defaults
VIDEO_CODEC_MAP = {
    "copy": "copy",
    "h264": "libx264",
    "h265": "libx265",
    "av1": "libsvtav1",
}

ACTION_METHODS = {
    "Download": "download",
    "Watch": "watch",
    "Syncplay": "syncplay",
}


def get_video_codec():
    """Get and validate video codec from environment variable."""
    codec = VIDEO_CODEC
    if codec not in VIDEO_CODEC_MAP:
        logger.warning(
            f"Invalid video codec '{codec}', falling back to 'copy'. Valid options: {list(VIDEO_CODEC_MAP.keys())}"
        )
        return "copy"
    return VIDEO_CODEC_MAP[codec]


# NIQUESTS

try:
    DEFAULT_USER_AGENT = str(
        fake_useragent.UserAgent(os=["Windows", "Mac OS X"]).random
    )
except fake_useragent.errors.FakeUserAgentError:
    # TODO: fix - currently happens on nuitka builds
    DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"

LULUVDO_USER_AGENT = (
    "Mozilla/5.0 (Android 15; Mobile; rv:132.0) Gecko/132.0 Firefox/132.0"
)

GLOBAL_SESSION = Session(
    resolver=["doh+google://"],
    headers={
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Sec-Fetch-Site": "none",
        "Sec-Fetch-Dest": "document",
        "Accept-Language": "en-US,en;q=0.9",
        "Sec-Fetch-Mode": "navigate",
        "User-Agent": DEFAULT_USER_AGENT,
        "Accept-Encoding": "gzip, deflate, br",
        "Referer": "https://aniworld.to/search",
        "Priority": "u=0, i",
    },
)

logger.debug("Config initialized successfully")

# -----------------------------
# Provider Stuff
# -----------------------------
SUPPORTED_PROVIDERS = (
    "VOE",
    "Vidmoly",
    "Vidoza",
    # "Doodstream",
    # "Filemoon",
    # "LoadX",
    # "Luluvdo",
    # "Streamtape",
)

PROVIDER_HEADERS_D = {
    "Vidmoly": {"Referer": "https://vidmoly.biz"},
    "Doodstream": {"Referer": "https://dood.li/"},
    "VOE": {
        "User-Agent": DEFAULT_USER_AGENT,
        "Accept": "*/*",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "keep-alive",
        "Referer": "https://voe.sx/",
        "Origin": "https://voe.sx",
    },
    "LoadX": {"Accept": "*/*"},
    "Filemoon": {"User-Agent": DEFAULT_USER_AGENT, "Referer": "https://filemoon.to"},
    "Luluvdo": {
        "User-Agent": LULUVDO_USER_AGENT,
        "Accept-Language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
        "Origin": "https://luluvdo.com",
        "Referer": "https://luluvdo.com/",
    },
}

PROVIDER_HEADERS_W = {
    "Vidmoly": {"Referer": "https://vidmoly.biz"},
    "Doodstream": {"Referer": "https://dood.li/"},
    "VOE": {"User-Agent": DEFAULT_USER_AGENT},
    "Luluvdo": {"User-Agent": LULUVDO_USER_AGENT},
    "Filemoon": {"User-Agent": DEFAULT_USER_AGENT, "Referer": "https://filemoon.to"},
}


# -----------------------------
# Language Stuff
# -----------------------------
class Audio(Enum):
    """
    Available audio language options:

        - JAPANESE: Japanese dubbed audio
        - GERMAN:   German dubbed audio
        - ENGLISH:  English dubbed audio

    Required source for each option:

        Japanese Dub -> Source: German Sub, English Sub
        German Dub   -> Source: German Dub
        English Dub  -> Source: English Dub
    """

    JAPANESE = "Japanese"
    GERMAN = "German"
    ENGLISH = "English"


class Subtitles(Enum):
    """
    Available subtitle language options:

        - NONE:    No subtitles
        - GERMAN:  German subtitles
        - ENGLISH: English subtitles

    Required source for each option:

        German Sub   -> Source: German Sub
        English Sub  -> Source: English Sub
    """

    NONE = "None"
    GERMAN = "German"
    ENGLISH = "English"


# Map site-specific language keys to semantic meaning
LANG_KEY_MAP = {
    "1": (Audio.GERMAN, Subtitles.NONE),  # German Dub
    "2": (Audio.JAPANESE, Subtitles.ENGLISH),  # English Sub
    "3": (Audio.JAPANESE, Subtitles.GERMAN),  # German Sub
    "4": (Audio.ENGLISH, Subtitles.NONE),  # English Dub
}

LANG_LABELS = {
    "1": "German Dub",
    "2": "English Sub",
    "3": "German Sub",
    "4": "English Dub",
}

LANG_CODE_MAP = {
    Audio.ENGLISH: "eng",
    Audio.GERMAN: "deu",
    Audio.JAPANESE: "jpn",
    Subtitles.ENGLISH: "eng",
    Subtitles.GERMAN: "deu",
    Subtitles.NONE: None,
}


INVERSE_LANG_KEY_MAP = {v: k for k, v in LANG_KEY_MAP.items()}
INVERSE_LANG_LABELS = {v: k for k, v in LANG_LABELS.items()}

# -----------------------------
# Patterns
# -----------------------------


ANIWORLD_SERIES_PATTERN = re.compile(
    r"^https?://(www\.)?aniworld\.to/anime/stream/[a-zA-Z0-9\-]+/?$", re.IGNORECASE
)

# series slug + (/staffel-N or /filme)
ANIWORLD_SEASON_PATTERN = re.compile(
    r"^https?://(www\.)?aniworld\.to/anime/stream/"
    r"[a-zA-Z0-9\-]+/"
    r"(staffel-\d+|filme)"
    r"/?$",
    re.IGNORECASE,
)

ANIWORLD_EPISODE_PATTERN = re.compile(
    r"^https?://(www\.)?aniworld\.to/anime/stream/"
    r"[a-zA-Z0-9\-]+/"  # series slug
    r"(staffel-\d+/episode-\d+|"  # season/episode
    r"filme/film-\d+)"  # movie/film
    r"/?$",
    re.IGNORECASE,
)

HANIME_TV_SERIES_PATTERN = re.compile(
    r"^https?://(?:www\.)?hanime\.tv/videos/hentai/[A-Za-z0-9\-]+/?$",
    re.IGNORECASE,
)

SERIENSTREAM_SERIES_PATTERN = re.compile(
    r"^https?://(www\.)?(serienstream|s)\.to/serie/[a-zA-Z0-9\-]+/?$", re.IGNORECASE
)

SERIENSTREAM_SEASON_PATTERN = re.compile(
    r"^https?://(www\.)?(serienstream|s)\.to/serie/"
    r"[a-zA-Z0-9\-]+/"
    r"staffel-\d+"
    r"/?$",
    re.IGNORECASE,
)

SERIENSTREAM_EPISODE_PATTERN = re.compile(
    r"^https?://(www\.)?(serienstream|s)\.to/serie/"
    r"[a-zA-Z0-9\-]+/"
    r"staffel-\d+/episode-\d+"
    r"/?$",
    re.IGNORECASE,
)

HIANIME_SERIES_PATTERN = re.compile(r"", re.IGNORECASE)

HIANIME_SEASON_PATTERN = re.compile(r"", re.IGNORECASE)

HIANIME_EPISODE_PATTERN = re.compile(r"", re.IGNORECASE)

# -----------------------------
# Directories
# -----------------------------

# TODO: add many other directories and use them throughout the app

# Determine mpv scripts directory
# On Linux/macOS: ~/.config/mpv/scripts
# On Windows: %APPDATA%\mpv\scripts
if os.name == "nt":
    MPV_CONFIG_DIR = Path(os.getenv("APPDATA")) / "mpv"
else:
    MPV_CONFIG_DIR = Path.home() / ".config" / "mpv"

MPV_SCRIPTS_DIR = MPV_CONFIG_DIR / "scripts"


================================================
FILE: src/aniworld/entry.py
================================================
import os
import sys
from pathlib import Path

from .arguments import parse_args
from .autodeps import ensure_patchright_chromium
from .config import ACTION_METHODS, ANIWORLD_CONFIG_DIR, VERSION
from .env import merge_env
from .logger import get_logger
from .menu import app
from .providers import resolve_provider
from .search import search

merge_env(
    Path(__file__).resolve().parent / ".env.example",
    ANIWORLD_CONFIG_DIR / ".env",
)

logger = get_logger(__name__)


def set_terminal_title():
    """Set the terminal title if running in a TTY"""
    if sys.stdout.isatty():
        title = f"AniWorld-Downloader v.{VERSION}"
        print(f"\033]0;{title}\007", end="", flush=True)


def validate_action(action: str):
    if action not in ACTION_METHODS.values():
        raise ValueError(f"Invalid action: {action}")


def run_action(obj, action: str):
    validate_action(action)
    getattr(obj, action)()


def aniworld():
    """Main entry point"""
    try:
        logger.debug("Starting...")
        logger.info("Starting AniWorld-Downloader...")
        set_terminal_title()
        logger.info("Checking dependencies...")
        ensure_patchright_chromium()
        logger.info("Dependencies OK")

        args = parse_args()

        if args.web_ui:
            from .web import start_web_ui

            host = "0.0.0.0" if args.web_expose else "127.0.0.1"
            port = args.web_port
            open_browser = not args.no_browser
            force_sso = args.web_force_sso
            sso_enabled = args.web_sso or force_sso
            auth_enabled = args.web_auth or force_sso

            if sso_enabled:
                oidc_vars = [
                    "ANIWORLD_OIDC_ISSUER_URL",
                    "ANIWORLD_OIDC_CLIENT_ID",
                    "ANIWORLD_OIDC_CLIENT_SECRET",
                ]
                missing = [v for v in oidc_vars if not os.environ.get(v, "").strip()]
                if missing:
                    if force_sso:
                        print(
                            f"Error: --web-force-sso requires OIDC env vars: {', '.join(missing)}",
                            file=sys.stderr,
                        )
                        return 1
                    print(
                        f"Warning: --web-sso enabled but OIDC env vars not set: {', '.join(missing)}\n"
                        "SSO login will not be available. Set the variables in your .env file.",
                        file=sys.stderr,
                    )
                    sso_enabled = False

            start_web_ui(
                host=host,
                port=port,
                open_browser=open_browser,
                auth_enabled=auth_enabled,
                sso_enabled=sso_enabled,
                force_sso=force_sso,
            )
            return 0

        action = (args.action or "download").lower()

        # ===== no-menu path =====
        if os.getenv("ANIWORLD_NO_MENU") == "1":
            urls = args.url
            logger.debug(urls)

            if not urls:
                raise ValueError("No URLs provided while using --no-menu")

            for url in urls:
                provider = resolve_provider(url)

                if provider.series_pattern and provider.series_pattern.fullmatch(url):
                    obj = provider.series_cls(url=url)

                elif provider.season_pattern and provider.season_pattern.fullmatch(url):
                    obj = provider.season_cls(url=url)

                elif provider.episode_pattern and provider.episode_pattern.fullmatch(
                    url
                ):
                    obj = provider.episode_cls(url=url)

                else:
                    raise ValueError(f"Invalid URL for provider: {url}")

                run_action(obj, action)

            return 0

        # ===== menu path =====
        # If multiple URLs are provided (e.g., via --episode-file), process them directly
        if args.episode_file and args.url:
            for url in args.url:
                provider = resolve_provider(url)
                if provider.episode_pattern.fullmatch(url):
                    obj = provider.episode_cls(url=url)
                elif provider.season_pattern and provider.season_pattern.fullmatch(url):
                    obj = provider.season_cls(url=url)
                elif provider.series_pattern and provider.series_pattern.fullmatch(url):
                    obj = provider.series_cls(url=url)
                else:
                    raise ValueError(f"Invalid URL for provider: {url}")
                run_action(obj, action)
            return 0

        url = args.url[0] if args.url else search()

        provider = resolve_provider(url)

        # If provider is NOT AniWorld -> bypass menu
        if provider.name != "AniWorld" and provider.name != "SerienStream":
            if provider.series_pattern and provider.series_pattern.fullmatch(url):
                obj = provider.series_cls(url=url)

            elif provider.season_pattern and provider.season_pattern.fullmatch(url):
                obj = provider.season_cls(url=url)

            elif provider.episode_pattern and provider.episode_pattern.fullmatch(url):
                obj = provider.episode_cls(url=url)

            else:
                raise ValueError(f"Invalid URL for provider: {url}")

            run_action(obj, action)
            return 0

        # If AniWorld but URL is episode OR season -> bypass menu too
        if provider.episode_pattern.fullmatch(url) or provider.season_pattern.fullmatch(
            url
        ):
            obj = (
                provider.episode_cls(url=url)
                if provider.episode_pattern.fullmatch(url)
                else provider.season_cls(url=url)
            )
            run_action(obj, action)
            return 0

        # AniWorld series -> show menu
        result = app(url=url)
        if not result:
            return 130

        action = result.get("action")
        episodes = result.get("episodes", [])
        selected_path = result.get("path")
        selected_language = result.get("language")
        selected_provider = result.get("provider")

        os.environ["ANIWORLD_ANISKIP"] = "1" if result.get("aniskip") else "0"

        if action in ACTION_METHODS:
            method_name = ACTION_METHODS[action]
            for episode_url in episodes:
                episode = provider.episode_cls(
                    url=episode_url,
                    selected_path=selected_path,
                    selected_language=selected_language,
                    selected_provider=selected_provider,
                )
                getattr(episode, method_name)()

        return 0

    except KeyboardInterrupt:
        print("\nQuitting.", file=sys.stderr)
        return 130

    except Exception as err:
        logger.error("Unexpected error occurred", exc_info=True)
        print(f"\nAn unexpected error occurred: {err}", file=sys.stderr)
        print("Please check the logs for more details.", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(aniworld())


================================================
FILE: src/aniworld/env.py
================================================
import re
from pathlib import Path

from dotenv import load_dotenv

# match lines like KEY=VALUE, ignoring comments and blank lines
ENV_LINE_RE = re.compile(r"^([^#\n=]+?)=(.*)$")


def merge_env(example_path: Path, env_path: Path):
    env_path.parent.mkdir(parents=True, exist_ok=True)
    example_lines = example_path.read_text().splitlines()

    # Load existing values from old env
    existing_values = {}
    if env_path.exists():
        for line in env_path.read_text().splitlines():
            m = ENV_LINE_RE.match(line)
            if m:
                existing_values[m.group(1).strip()] = m.group(2).strip()

    merged_lines = []
    for line in example_lines:
        m = ENV_LINE_RE.match(line)
        if not m:
            # keep comments, blank lines, formatting exactly
            merged_lines.append(line)
            continue

        key = m.group(1).strip()
        default_value = m.group(2)

        # replace value if user has one
        if key in existing_values:
            merged_lines.append(f"{key}={existing_values[key]}")
        else:
            merged_lines.append(f"{key}={default_value}")

    env_path.parent.mkdir(parents=True, exist_ok=True)
    env_path.write_text("\n".join(merged_lines) + "\n")

    # Load the merged env file
    load_dotenv(env_path)


================================================
FILE: src/aniworld/extractors/__init__.py
================================================
import importlib
import inspect
import pkgutil
from pathlib import Path

provider_functions = {}

provider_path = Path(__path__[0]) / "provider"

for _, module_name, _ in pkgutil.iter_modules([str(provider_path)]):
    mod = importlib.import_module(f".provider.{module_name}", __name__)
    for name, obj in inspect.getmembers(mod, inspect.isfunction):
        if name.startswith(("get_direct_link_from_", "get_preview_image_link_from_")):
            provider_functions[name] = obj

# Example usage:
# provider_functions["get_direct_link_from_voe"](url)


================================================
FILE: src/aniworld/extractors/provider/doodstream.py
================================================
import logging
import random
import re
import time
import warnings
from urllib.parse import urljoin

import niquests
from urllib3.exceptions import InsecureRequestWarning

try:
    from ...config import DEFAULT_USER_AGENT
except ImportError:
    from aniworld.config import DEFAULT_USER_AGENT

warnings.simplefilter("ignore", InsecureRequestWarning)

# -----------------------------
# Constants
# -----------------------------
DOODSTREAM_BASE_URL = "https://dood.li"
RANDOM_STRING_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
PASS_MD5_PATTERN = r"\$\.get\('([^']*\/pass_md5\/[^']*)'"
TOKEN_PATTERN = r"token=([a-zA-Z0-9]+)"


# -----------------------------
# Helper Functions
# -----------------------------
def _get_headers(referer=None):
    """Return headers for Doodstream requests."""
    return {
        "User-Agent": DEFAULT_USER_AGENT,
        "Referer": referer or f"{DOODSTREAM_BASE_URL}/",
    }


def _extract_regex(pattern, content, name, url):
    """Extract a regex match or raise ValueError."""
    match = re.search(pattern, content)
    if not match:
        raise ValueError(f"{name} not found in {url}")
    return match.group(1)


def _generate_random_string(length=10):
    """Generate a random alphanumeric string."""
    return "".join(random.choices(RANDOM_STRING_CHARS, k=length))


def _get_embed_page(embed_url, headers=None):
    """Fetch HTML content of the embed page."""
    headers = headers or _get_headers()
    resp = niquests.get(embed_url, headers=headers, verify=False)
    resp.raise_for_status()
    return resp.text


def _get_pass_md5_url(embed_html, embed_url):
    """Extract the pass_md5 URL from embed HTML."""
    pass_md5_url = _extract_regex(
        PASS_MD5_PATTERN, embed_html, "pass_md5 URL", embed_url
    )
    if not pass_md5_url.startswith("http"):
        pass_md5_url = urljoin(DOODSTREAM_BASE_URL, pass_md5_url)
    return pass_md5_url


def _get_token(embed_html, embed_url):
    """Extract the token from embed HTML."""
    return _extract_regex(TOKEN_PATTERN, embed_html, "token", embed_url)


# -----------------------------
# Main Doodstream Functions
# -----------------------------
def get_direct_link_from_doodstream(embed_url):
    """Extract the direct video link from a Doodstream embed URL."""
    if not embed_url:
        raise ValueError("Embed URL cannot be empty")

    logging.info(f"Extracting Doodstream direct link from: {embed_url}")
    headers = _get_headers(embed_url)

    embed_html = _get_embed_page(embed_url, headers)
    pass_md5_url = _get_pass_md5_url(embed_html, embed_url)
    token = _get_token(embed_html, embed_url)

    md5_resp = niquests.get(pass_md5_url, headers=headers, verify=False)
    md5_resp.raise_for_status()
    video_base_url = md5_resp.text.strip()
    if not video_base_url:
        raise ValueError(f"Empty video base URL returned from {pass_md5_url}")

    random_str = _generate_random_string(10)
    expiry = int(time.time())
    direct_link = f"{video_base_url}{random_str}?token={token}&expiry={expiry}"

    logging.info("Successfully extracted Doodstream direct link")
    return direct_link


def get_preview_image_link_from_doodstream(embed_url):
    """Get the preview image URL from a Doodstream embed (not implemented)."""
    raise NotImplementedError("Preview image extraction is not implemented yet.")


if __name__ == "__main__":
    # Tested on 2026/01 -> WORKING
    # Example URLs: https://doodsearch.site

    # logging.basicConfig(level=logging.DEBUG)

    link = input("Enter Doodstream Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_doodstream(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        # Preview image extraction not yet implemented
        try:
            preview_img = get_preview_image_link_from_doodstream(link)
            print("Preview image:", preview_img)
        except NotImplementedError:
            print("Preview image: Not implemented")
        print("=" * 25)

        print(
            f"mpv --http-header-fields='Referer: {DOODSTREAM_BASE_URL}/' '{direct_link}'"
        )
        print("=" * 25)

    except Exception as e:
        print("Error:", e)
        exit(1)


================================================
FILE: src/aniworld/extractors/provider/filemoon.py
================================================
import base64
import json
import logging
import re
from urllib.parse import urlparse

import niquests

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

try:
    from ...config import DEFAULT_USER_AGENT, GLOBAL_SESSION, PROVIDER_HEADERS_D
except ImportError:
    from aniworld.config import DEFAULT_USER_AGENT, GLOBAL_SESSION, PROVIDER_HEADERS_D

logger = logging.getLogger(__name__)

# -----------------------------
# Precompiled regex patterns
# -----------------------------
FILE_CODE_PATTERN = re.compile(r"/[de]/(?P<code>[a-zA-Z0-9]+)")
PACKED_JS_PATTERN = re.compile(
    r"eval\(function\(p,a,c,k,e,d\)\{.*?\}\('(?P<p>[^']+)',\s*(?P<a>\d+),\s*(?P<c>\d+),\s*'(?P<k>[^']+)'\.split\('\|'\)",
    re.DOTALL,
)
SOURCE_PATTERN = re.compile(
    r"sources\s*:\s*\[\s*\{[^}]*file:\s*['\"](?P<url>[^'\"]+)['\"]", re.DOTALL
)
HLS_PATTERN = re.compile(r"['\"](?P<url>https?://[^'\"]+\.m3u8[^'\"]*)['\"]")
FILE_URL_PATTERN = re.compile(r"file\s*:\s*['\"](?P<url>https?://[^'\"]+)['\"]")


# -----------------------------
# Helper functions
# -----------------------------
def _base64url_decode(s):
    """Base64url decode (RFC 4648 section 5)."""
    s = s.replace("-", "+").replace("_", "/")
    pad = 4 - len(s) % 4
    if pad != 4:
        s += "=" * pad
    return base64.b64decode(s)


def _extract_file_code(url):
    """Extract the file code from a Filemoon/Byse URL.

    e.g., https://bysezejataos.com/d/56q7gpy3qyo6 -> 56q7gpy3qyo6
    """
    match = FILE_CODE_PATTERN.search(url)
    return match.group("code") if match else None


def _decrypt_payload(playback, key, iv_prop, payload_prop):
    """Decrypt an AES-256-GCM encrypted payload."""
    iv_str = playback.get(iv_prop)
    payload_str = playback.get(payload_prop)
    if not iv_str or not payload_str:
        return None

    iv = _base64url_decode(iv_str)
    ciphertext = _base64url_decode(payload_str)

    # AES-GCM: last 16 bytes are the auth tag
    tag_size = 16
    if len(ciphertext) <= tag_size:
        return None

    aesgcm = AESGCM(key)
    # cryptography library expects nonce + ciphertext+tag combined
    plaintext = aesgcm.decrypt(iv, ciphertext, None)
    return json.loads(plaintext.decode("utf-8"))


def _decrypt_playback_data(playback):
    """Decrypt AES-256-GCM encrypted playback data from the Byse API.

    The key is formed by base64url-decoding and concatenating key_parts.
    """
    key_parts = playback.get("key_parts")
    if not key_parts or not isinstance(key_parts, list):
        return None

    key_bytes = b""
    for part in key_parts:
        if not part:
            return None
        key_bytes += _base64url_decode(part)

    # Try primary payload first, then payload2
    result = _decrypt_payload(playback, key_bytes, "iv", "payload")
    if result is not None:
        return result

    return _decrypt_payload(playback, key_bytes, "iv2", "payload2")


def _extract_best_source_url(data):
    """Extract the best quality video URL from decrypted sources JSON."""
    # Try "sources" array
    sources = data.get("sources")
    if isinstance(sources, list) and sources:
        best_url = None
        best_height = 0
        for source in sources:
            url = source.get("url")
            height = source.get("height", 0)
            if url and isinstance(height, int) and height >= best_height:
                best_url = url
                best_height = height
        if best_url:
            return best_url

    # Try "source" property
    source = data.get("source")
    if isinstance(source, str) and source:
        return source

    # Try "file" property
    file_url = data.get("file")
    if isinstance(file_url, str) and file_url:
        return file_url

    return None


def _try_byse_api(embed_url, file_code, headers):
    """Try the modern Byse-style REST API with AES-256-GCM decryption."""
    try:
        parsed = urlparse(embed_url)
        base_url = f"{parsed.scheme}://{parsed.netloc}"
        api_url = f"{base_url}/api/videos/{file_code}"

        logger.debug("Trying Byse API: %s", api_url)

        resp = GLOBAL_SESSION.get(api_url, headers=headers)
        if resp.status_code != 200:
            logger.debug("Byse API returned %s", resp.status_code)
            return None

        data = resp.json()
        playback = data.get("playback")
        if not playback:
            logger.debug("No playback data in API response")
            return None

        decrypted = _decrypt_playback_data(playback)
        if decrypted is None:
            logger.warning("Failed to decrypt Byse playback data")
            return None

        best_url = _extract_best_source_url(decrypted)
        if best_url:
            logger.info("Filemoon/Byse: extracted stream URL via API")
            return best_url

    except Exception as err:
        logger.debug("Byse API approach failed: %s", err)

    return None


def _decode_base_n(token, radix):
    """Convert a string from base-N to a decimal integer (up to base 62)."""
    if radix <= 10:
        try:
            return int(token)
        except ValueError:
            return -1

    result = 0
    for c in token:
        if "0" <= c <= "9":
            digit = ord(c) - ord("0")
        elif "a" <= c <= "z":
            digit = ord(c) - ord("a") + 10
        elif "A" <= c <= "Z":
            digit = ord(c) - ord("A") + 36
        else:
            return -1

        if digit >= radix:
            return -1
        result = result * radix + digit

    return result


def _unpack_js(packed, radix, count, keywords):
    """Unpack Dean Edwards' packed JavaScript (legacy Filemoon)."""

    def replacer(match):
        token = match.group(1)
        index = _decode_base_n(token, radix)
        if 0 <= index < len(keywords) and keywords[index]:
            return keywords[index]
        return token

    return re.sub(r"\b(\w+)\b", replacer, packed)


def _extract_url_from_string(text):
    """Extract a video URL from text content."""
    hls_match = HLS_PATTERN.search(text)
    if hls_match:
        return hls_match.group("url")

    src_match = SOURCE_PATTERN.search(text)
    if src_match:
        return src_match.group("url")

    file_match = FILE_URL_PATTERN.search(text)
    if file_match:
        url = file_match.group("url")
        if ".m3u8" in url.lower() or ".mp4" in url.lower():
            return url

    return None


def _try_extract_from_html(html):
    """Try to extract video URL from HTML (legacy Filemoon with packed JS)."""
    # Try packed JS
    match = PACKED_JS_PATTERN.search(html)
    if match:
        packed = match.group("p")
        radix = int(match.group("a"))
        keywords = match.group("k").split("|")

        unpacked = _unpack_js(packed, radix, 0, keywords)
        if unpacked:
            url = _extract_url_from_string(unpacked)
            if url:
                logger.info("Filemoon: extracted URL from packed JS")
                return url

    # Try direct patterns
    url = _extract_url_from_string(html)
    if url:
        logger.info("Filemoon: extracted URL from direct pattern in HTML")
    return url


# -----------------------------
# Main Filemoon functions
# -----------------------------
def get_direct_link_from_filemoon(embeded_filemoon_link, headers=None):
    """Get direct Filemoon video URL."""
    try:
        if headers is None:
            headers = PROVIDER_HEADERS_D.get(
                "Filemoon", {"User-Agent": DEFAULT_USER_AGENT}
            )

        # Strategy 1: Try modern Byse-style API (AES-256-GCM encrypted)
        file_code = _extract_file_code(embeded_filemoon_link)
        if file_code:
            api_url = _try_byse_api(embeded_filemoon_link, file_code, headers)
            if api_url:
                return api_url

        # Strategy 2: Try legacy HTML scraping (packed JS)
        resp = GLOBAL_SESSION.get(embeded_filemoon_link, headers=headers)
        resp.raise_for_status()
        html = resp.text

        url = _try_extract_from_html(html)
        if url:
            return url

        raise ValueError("No Filemoon video source found in page.")

    except niquests.RequestException as err:
        raise ValueError(f"Failed to fetch Filemoon page: {err}") from err


def get_preview_image_link_from_filemoon(embeded_filemoon_link, headers=None):
    """Get Filemoon preview image URL."""
    raise NotImplementedError(
        "get_preview_image_link_from_filemoon is not implemented yet."
    )


if __name__ == "__main__":
    # Tested on xxxx/xx/xx -> WORKING
    # Example: https://xxx

    # logging.basicConfig(level=logging.DEBUG)

    link = input("Enter Filemoon Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_filemoon(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        print("Preview image:", get_preview_image_link_from_filemoon(link))
        print("=" * 25)

        print(
            f'mpv "{direct_link}" --http-header-fields=User-Agent: "{DEFAULT_USER_AGENT}"'
        )

        print("=" * 25)
    except ValueError as e:
        print("Error:", e)


================================================
FILE: src/aniworld/extractors/provider/loadx.py
================================================
try:
    from ...config import DEFAULT_USER_AGENT
except ImportError:
    from aniworld.config import DEFAULT_USER_AGENT


def get_direct_link_from_loadx(embeded_loadx_link, headers=None):
    """Get direct LoadX video URL."""
    raise NotImplementedError("get_direct_link_from_loadx is not implemented yet.")


def get_preview_image_link_from_loadx(embeded_loadx_link, headers=None):
    """Get LoadX preview image URL."""
    raise NotImplementedError(
        "get_preview_image_link_from_loadx is not implemented yet."
    )


if __name__ == "__main__":
    # Tested on xxxx/xx/xx -> WORKING
    # Example: https://xxx

    # logging.basicConfig(level=logging.DEBUG)

    link = input("Enter LoadX Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_loadx(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        print("Preview image:", get_preview_image_link_from_loadx(link))
        print("=" * 25)

        print(
            f'mpv "{direct_link}" --http-header-fields=User-Agent: "{DEFAULT_USER_AGENT}"'
        )

        print("=" * 25)
    except ValueError as e:
        print("Error:", e)


================================================
FILE: src/aniworld/extractors/provider/luluvdo.py
================================================
try:
    from ...config import DEFAULT_USER_AGENT
except ImportError:
    from aniworld.config import DEFAULT_USER_AGENT


def get_direct_link_from_luluvdo(embeded_luluvdo_link, headers=None):
    """Get direct Luluvdo video URL."""
    raise NotImplementedError("get_direct_link_from_luluvdo is not implemented yet.")


def get_preview_image_link_from_luluvdo(embeded_luluvdo_link, headers=None):
    """Get Luluvdo preview image URL."""
    raise NotImplementedError(
        "get_preview_image_link_from_luluvdo is not implemented yet."
    )


if __name__ == "__main__":
    # Tested on xxxx/xx/xx -> WORKING
    # Example: https://xxx

    # logging.basicConfig(level=logging.DEBUG)

    link = input("Enter Luluvdo Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_luluvdo(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        print("Preview image:", get_preview_image_link_from_luluvdo(link))
        print("=" * 25)

        print(
            f'mpv "{direct_link}" --http-header-fields=User-Agent: "{DEFAULT_USER_AGENT}"'
        )

        print("=" * 25)
    except ValueError as e:
        print("Error:", e)


================================================
FILE: src/aniworld/extractors/provider/streamtape.py
================================================
try:
    from ...config import DEFAULT_USER_AGENT
except ImportError:
    from aniworld.config import DEFAULT_USER_AGENT


def get_direct_link_from_streamtape(embeded_streamtape_link, headers=None):
    """Get direct Streamtape video URL."""
    raise NotImplementedError("get_direct_link_from_streamtape is not implemented yet.")


def get_preview_image_link_from_streamtape(embeded_streamtape_link, headers=None):
    """Get Streamtape preview image URL."""
    raise NotImplementedError(
        "get_preview_image_link_from_streamtape is not implemented yet."
    )


if __name__ == "__main__":
    # Tested on xxxx/xx/xx -> WORKING
    # Example: https://xxx

    # logging.basicConfig(level=logging.DEBUG)

    link = input("Enter Streamtape Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_streamtape(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        print("Preview image:", get_preview_image_link_from_streamtape(link))
        print("=" * 25)

        print(
            f'mpv "{direct_link}" --http-header-fields=User-Agent: "{DEFAULT_USER_AGENT}"'
        )

        print("=" * 25)
    except ValueError as e:
        print("Error:", e)


================================================
FILE: src/aniworld/extractors/provider/vidmoly.py
================================================
import re

try:
    from ...config import GLOBAL_SESSION
except ImportError:
    from aniworld.config import GLOBAL_SESSION

# -----------------------------
# Constants
# -----------------------------
FILE_LINK_PATTERN = re.compile(r'file\s*:\s*[\'"]([^\'"]+?\.m3u8[^\'"]*)[\'"]')
PREVIEW_IMAGE_PATTERN = re.compile(
    r'image\s*:\s*[\'"]([^\'"]+\.(?:jpg|jpeg|png|webp))[\'"]'
)


# -----------------------------
# Helper Functions
# -----------------------------
def _get_headers():
    """Return headers for Vidmoly requests."""
    return {"Referer": "https://vidmoly.biz"}


def _extract_regex(pattern, content, name, url):
    """Extract regex match or raise ValueError."""
    if not content:
        raise ValueError(f"No HTML content for {url}")
    match = pattern.search(content)
    if not match:
        raise ValueError(f"{name} not found in {url}")
    return match.group(1)


def _extract_script_content(html):
    """Return all script contents concatenated."""
    scripts = re.findall(
        r"<script[^>]*>(.*?)</script>", html, re.DOTALL | re.IGNORECASE
    )
    return "\n".join(filter(None, scripts))  # join non-empty scripts


# -----------------------------
# Main Vidmoly Functions
# -----------------------------
def get_direct_link_from_vidmoly(embed_url):
    """Get direct Vidmoly video link."""
    if not embed_url:
        raise ValueError("Embed URL cannot be empty")

    resp = GLOBAL_SESSION.get(embed_url, headers=_get_headers())
    resp.raise_for_status()
    html = resp.text

    script_content = _extract_script_content(html)
    return _extract_regex(
        FILE_LINK_PATTERN, script_content, "Direct video URL", embed_url
    )


def get_preview_image_link_from_vidmoly(embed_url):
    """Get Vidmoly preview image URL."""
    if not embed_url:
        raise ValueError("Embed URL cannot be empty")

    resp = GLOBAL_SESSION.get(embed_url, headers=_get_headers())
    resp.raise_for_status()
    html = resp.text

    script_content = _extract_script_content(html)
    return _extract_regex(
        PREVIEW_IMAGE_PATTERN, script_content, "Preview image URL", embed_url
    )


if __name__ == "__main__":
    # Tested on 2026/02/18 -> WORKING
    # Example: https://vidmoly.net/embed-zquo82b8dm1k.html

    link = input("Enter Vidmoly Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_vidmoly(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        preview_img = get_preview_image_link_from_vidmoly(link)
        print("Preview image:", preview_img)
        print("=" * 25)

        print(
            f'mpv --http-header-fields="Referer: https://vidmoly.biz" "{direct_link}"'
        )
        print("=" * 25)

    except Exception as e:
        print("Error:", e)
        exit(1)


================================================
FILE: src/aniworld/extractors/provider/vidoza.py
================================================
import re

import niquests

try:
    from ...config import DEFAULT_USER_AGENT, GLOBAL_SESSION
except ImportError:
    from aniworld.config import DEFAULT_USER_AGENT, GLOBAL_SESSION


# Compile regex pattern once for better performance
SOURCE_LINK_PATTERN = re.compile(r'src:\s*"([^"]+)"')
IMAGE_LINK_PATTERN = re.compile(r'poster:\s*"([^"]+)"')


def get_direct_link_from_vidoza(embeded_vidoza_link):
    """Get direct Vidoza video URL."""
    try:
        resp = GLOBAL_SESSION.get(
            embeded_vidoza_link, headers={"User-Agent": DEFAULT_USER_AGENT}
        )
        resp.raise_for_status()
        html = resp.text

        if "sourcesCode:" in html:
            match = SOURCE_LINK_PATTERN.search(html)
            if match:
                return match.group(1)

    except niquests.RequestException as err:
        raise ValueError(f"Failed to fetch Vidoza page: {err}") from err


def get_preview_image_link_from_vidoza(embeded_vidoza_link):
    """Get Vidoza preview image URL."""
    try:
        resp = GLOBAL_SESSION.get(
            embeded_vidoza_link, headers={"User-Agent": DEFAULT_USER_AGENT}
        )
        resp.raise_for_status()
        html = resp.text

        if "sourcesCode:" in html:
            match = IMAGE_LINK_PATTERN.search(html)
            if match:
                return match.group(1)

    except niquests.RequestException as err:
        raise ValueError(f"Failed to fetch Vidoza page: {err}") from err


if __name__ == "__main__":
    # Tested on 2026/01/27 -> WORKING
    # Example: https://videzz.net/embed-xneznizpludf.html

    # logging.basicConfig(level=logging.DEBUG)

    link = input("Enter Vidoza Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_vidoza(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        print("Preview image:", get_preview_image_link_from_vidoza(link))
        print("=" * 25)

        print(
            f'mpv "{direct_link}" --http-header-fields=User-Agent: "{DEFAULT_USER_AGENT}"'
        )

        print("=" * 25)
    except ValueError as e:
        print("Error:", e)


================================================
FILE: src/aniworld/extractors/provider/voe.py
================================================
import base64
import binascii
import json
import logging
import re
import time
from urllib.parse import urlparse

import niquests

logger = logging.getLogger(__name__)

try:
    from ...config import DEFAULT_USER_AGENT, GLOBAL_SESSION, PROVIDER_HEADERS_D
    from ...playwright.captcha import is_captcha_page, solve_captcha
except ImportError:
    from aniworld.config import DEFAULT_USER_AGENT, GLOBAL_SESSION, PROVIDER_HEADERS_D
    from aniworld.playwright.captcha import is_captcha_page, solve_captcha

# -----------------------------
# Precompiled regex patterns
# -----------------------------
REDIRECT_PATTERN = re.compile(r"""['"](\s*https?://[^'"<>\s]+/e/[^'"<>\s]+)['"]""")
B64_PATTERN = re.compile(r"var a168c='([^']+)'")
HLS_PATTERN = re.compile(r"'hls': '(?P<hls>[^']+)'")
VOE_SCRIPT_PATTERN = re.compile(
    r'<script type="application/json">\s*"(?:\\.|[^"\\])*"\s*</script>', re.DOTALL
)
JUNK_PARTS = ["@$", "^^", "~@", "%?", "*~", "!!", "#&"]


# -----------------------------
# Helper functions
# -----------------------------
def shift_letters(input_str):
    """Apply ROT13 cipher to alphabetic characters."""
    result = []
    for c in input_str:
        code = ord(c)
        if 65 <= code <= 90:  # Uppercase A-Z
            code = (code - 65 + 13) % 26 + 65
        elif 97 <= code <= 122:  # Lowercase a-z
            code = (code - 97 + 13) % 26 + 97
        result.append(chr(code))
    return "".join(result)


def replace_junk(input_str):
    """Replace junk patterns with underscores."""
    for part in JUNK_PARTS:
        input_str = input_str.replace(part, "_")
    return input_str


def shift_back(s, n):
    """Shift characters back by n positions."""
    return "".join(chr(ord(c) - n) for c in s)


def decode_voe_string(encoded):
    """Decode VOE encoded string to a JSON object."""
    try:
        step1 = shift_letters(encoded)
        step2 = replace_junk(step1).replace("_", "")
        step3 = base64.b64decode(step2).decode()
        step4 = shift_back(step3, 3)
        step5 = base64.b64decode(step4[::-1]).decode()
        return json.loads(step5)
    except (binascii.Error, json.JSONDecodeError, UnicodeDecodeError) as err:
        raise ValueError(f"Failed to decode VOE string: {err}") from err


def extract_voe_source_from_html(html):
    """Extract VOE video source — tries all known encoding variants."""
    # Variant 1: <script type="application/json"> with encoded payload
    try:
        script_blocks = re.findall(
            r'<script\s+type=["\']application/json["\']>(.*?)</script>', html, re.DOTALL
        )
        for script_block in script_blocks:
            encoded_text = script_block.strip()
            if encoded_text.startswith('"') and encoded_text.endswith('"'):
                encoded_text = encoded_text[1:-1]
            try:
                decoded = decode_voe_string(encoded_text.encode().decode("unicode_escape"))
                source = decoded.get("source")
                if source:
                    return source
            except (ValueError, UnicodeDecodeError):
                continue
    except Exception:
        pass

    # Variant 2: var a168c='<encoded>' pattern
    try:
        m = B64_PATTERN.search(html)
        if m:
            decoded = decode_voe_string(m.group(1))
            source = decoded.get("source")
            if source:
                return source
    except Exception:
        pass

    # Variant 3: plain 'hls': '<url>' in page JS
    try:
        m = HLS_PATTERN.search(html)
        if m:
            return m.group("hls")
    except Exception:
        pass

    return None


# -----------------------------
# Main VOE functions
# -----------------------------
def get_direct_link_from_voe(embeded_voe_link, headers=None, max_retries=3, timeout=30):
    """Get direct VOE video URL with improved retry logic."""
    parsed_embed_url = urlparse((embeded_voe_link or "").strip())
    if not parsed_embed_url.scheme or not parsed_embed_url.netloc:
        raise ValueError(f"Invalid VOE URL: {embeded_voe_link!r}")

    if headers is None:
        headers = PROVIDER_HEADERS_D.get("VOE", {"User-Agent": DEFAULT_USER_AGENT})

    # Enhanced headers for better compatibility
    enhanced_headers = {
        **headers,
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
    }

    for attempt in range(max_retries):
        try:
            # Add delay between retries
            if attempt > 0:
                wait_time = 2**attempt  # Exponential backoff: 2, 4, 8 seconds
                logger.warning(f"Retry attempt {attempt + 1}/{max_retries}, waiting {wait_time}s...")
                time.sleep(wait_time)

            # First request to VOE
            resp = GLOBAL_SESSION.get(
                embeded_voe_link, headers=enhanced_headers, timeout=timeout
            )
            resp.raise_for_status()
            html = resp.text

            # Captcha on VOE page -> solve and retry this request
            if is_captcha_page(html, resp.status_code):
                solve_captcha(embeded_voe_link)
                resp = GLOBAL_SESSION.get(
                    embeded_voe_link, headers=enhanced_headers, timeout=timeout
                )
                resp.raise_for_status()
                html = resp.text

            # Try extracting source directly from the VOE embed page first
            source = extract_voe_source_from_html(html)
            if source:
                logger.warning(f"VOE source extracted on attempt {attempt + 1}")
                return source

            # Fallback: follow the redirect URL embedded in the page
            redirect_match = REDIRECT_PATTERN.search(html)
            if redirect_match:
                redirect_url = redirect_match.group(1).strip()

                # Second request with retry
                for redirect_attempt in range(max_retries):
                    try:
                        if redirect_attempt > 0:
                            wait_time = 2**redirect_attempt
                            logger.warning(f"Redirect retry {redirect_attempt + 1}/{max_retries}, waiting {wait_time}s...")
                            time.sleep(wait_time)

                        resp = GLOBAL_SESSION.get(
                            redirect_url, headers=enhanced_headers, timeout=timeout
                        )
                        resp.raise_for_status()
                        html = resp.text

                        # Captcha on redirect target solve and retry
                        if is_captcha_page(html, resp.status_code):
                            solve_captcha(redirect_url)
                            resp = GLOBAL_SESSION.get(
                                redirect_url, headers=enhanced_headers, timeout=timeout
                            )
                            resp.raise_for_status()
                            html = resp.text
                        break
                    except (niquests.RequestException, Exception) as err:
                        if redirect_attempt == max_retries - 1:
                            raise ValueError(
                                f"Failed to fetch redirect URL after {max_retries} attempts: {err}"
                            ) from err
                        continue

            source = extract_voe_source_from_html(html)
            if not source:
                raise ValueError("No VOE video source found in page.")

            logger.warning(f"VOE source extracted on attempt {attempt + 1}")
            return source

        except (niquests.RequestException, Exception) as err:
            if attempt == max_retries - 1:
                raise ValueError(
                    f"Failed to fetch VOE page after {max_retries} attempts: {err}"
                ) from err
            logger.warning(f"Attempt {attempt + 1} failed: {str(err)[:100]}...")
            continue

    raise ValueError("Unexpected error in get_direct_link_from_voe")


def get_preview_image_link_from_voe(embeded_voe_link, headers=None):
    """Get VOE preview image URL."""
    try:
        parsed_embed_url = urlparse((embeded_voe_link or "").strip())
        if not parsed_embed_url.scheme or not parsed_embed_url.netloc:
            raise ValueError(f"Invalid VOE URL: {embeded_voe_link!r}")

        if headers is None:
            headers = PROVIDER_HEADERS_D.get("VOE", {"User-Agent": DEFAULT_USER_AGENT})

        resp = GLOBAL_SESSION.get(embeded_voe_link, headers=headers)
        resp.raise_for_status()
        html = resp.text

        redirect_match = REDIRECT_PATTERN.search(html)
        if not redirect_match:
            raise ValueError("No redirect URL found in VOE response.")

        redirect_url = redirect_match.group(0)
        image_url = f"{redirect_url.replace('/e/', '/cache/')}_storyboard_L2.jpg"

        head_resp = GLOBAL_SESSION.head(
            image_url, headers=headers, allow_redirects=True
        )
        head_resp.raise_for_status()
        if "image" not in head_resp.headers.get("Content-Type", ""):
            raise ValueError("Preview image not reachable.")
        return image_url

    except niquests.RequestException as err:
        raise ValueError(f"Failed to fetch VOE preview image: {err}") from err


if __name__ == "__main__":
    # Tested on 2026/01/27 -> WORKING
    # Example: https://voe.sx/e/oa16zsjaqohr

    # logging.basicConfig(level=logging.DEBUG)

    link = input("Enter VOE Link: ").strip()
    if not link:
        print("Error: No link provided")
        exit(1)

    try:
        print("=" * 25)

        direct_link = get_direct_link_from_voe(link)
        print("Direct link:", direct_link)
        print("=" * 25)

        print("Preview image:", get_preview_image_link_from_voe(link))
        print("=" * 25)

        print(
            f'mpv "{direct_link}" --http-header-fields=User-Agent: "{DEFAULT_USER_AGENT}"'
        )

        print("=" * 25)
    except ValueError as e:
        print("Error:", e)


================================================
FILE: src/aniworld/logger.py
================================================
import logging
import os
import tempfile
from pathlib import Path

_global_logger = None

# ANSI color codes for console output
RESET = "\033[0m"

COLORS = {
    logging.DEBUG: "\033[36m",  # Cyan
    logging.INFO: "\033[32m",  # Green
    logging.WARNING: "\033[33m",  # Yellow
    logging.ERROR: "\033[31m",  # Red
    logging.CRITICAL: "\033[41m",  # Red background
}

TIME_COLOR = "\033[35m"  # Magenta
FUNC_COLOR = "\033[34m"  # Blue
MSG_COLOR = "\033[37m"  # White/Gray


class ColorFormatter(logging.Formatter):
    """Formatter for colored stdout logs."""

    def format(self, record):
        level_color = COLORS.get(record.levelno, RESET)
        record.levelname = f"{level_color}{record.levelname}{RESET}"

        cwd = os.getcwd()
        rel_path = os.path.relpath(record.pathname, cwd)
        record.func_info = (
            f"{FUNC_COLOR}{rel_path}:{record.lineno}:{record.funcName}{RESET}"
        )

        record.msg = f"{MSG_COLOR}{record.getMessage()}{RESET}"

        formatted = super().format(record)

        # Color timestamp
        parts = formatted.split(" - ", 1)
        if len(parts) == 2:
            timestamp, rest = parts
            formatted = f"{TIME_COLOR}{timestamp}{RESET} - {rest}"

        return formatted


class PlainFormatter(logging.Formatter):
    """Formatter for plain file logs (no color)."""

    def format(self, record):
        cwd = os.getcwd()
        rel_path = os.path.relpath(record.pathname, cwd)
        record.func_info = f"{rel_path}:{record.lineno}:{record.funcName}"
        return super().format(record)


# TODO: This does not respect env debug mode
def get_logger(name=__name__, level=None):
    """Return a logger that writes to both file and stdout, colored in console."""
    global _global_logger
    if _global_logger is None:
        _global_logger = logging.getLogger("aniworld")
        _global_logger.handlers.clear()

        log_format = "%(asctime)s - %(levelname)s - %(func_info)s - %(message)s"
        date_format = "%Y-%m-%d %H:%M:%S"

        # ------------------ File handler ------------------ #
        temp_dir = tempfile.gettempdir()
        log_file_path = Path(temp_dir) / "aniworld.log"
        file_handler = logging.FileHandler(log_file_path, mode="w", encoding="utf-8")
        file_handler.setFormatter(PlainFormatter(log_format, datefmt=date_format))
        _global_logger.addHandler(file_handler)

        # ------------------ Console handler ------------------ #
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(ColorFormatter(log_format, datefmt=date_format))
        _global_logger.addHandler(console_handler)

        # Determine log level from env or argument
        env_debug = os.getenv("ANIWORLD_DEBUG_MODE", "0")
        level = level or (logging.DEBUG if env_debug == "1" else logging.WARNING)
        _global_logger.setLevel(level)

        # Reduce noise from urllib3
        logging.getLogger("urllib3").setLevel(logging.WARNING)
        logging.getLogger("urllib3.connectionpool").setLevel(logging.ERROR)

    return _global_logger


================================================
FILE: src/aniworld/menu.py
================================================
import curses
import curses.ascii
import json
import os
import sys
from enum import Enum
from pathlib import Path

import npyscreen

from .config import INVERSE_LANG_KEY_MAP, LANG_LABELS, VERSION, logger
from .providers import resolve_provider


def _extract_menu_languages(provider_name: str, provider_data: dict) -> list[str]:
    languages: list[str] = []

    if provider_name == "AniWorld":
        for key in provider_data.keys():
            site_key = INVERSE_LANG_KEY_MAP.get(key)
            if site_key is None:
                continue
            label = LANG_LABELS.get(site_key)
            if label and label not in languages:
                languages.append(label)
        return languages

    if provider_name == "SerienStream":
        # SerienStream provider_data keys are usually tuples of Enums like
        # (Audio.GERMAN, Subtitles.NONE) or (Audio.ENGLISH, Subtitles.NONE).
        for key in provider_data.keys():
            if not (isinstance(key, tuple) and len(key) == 2):
                continue
            audio = getattr(key[0], "value", str(key[0]))
            audio_lower = str(audio).lower()
            if audio_lower == "german":
                label = "German Dub"
            elif audio_lower == "english":
                label = "English Dub"
            else:
                continue

            if label not in languages:
                languages.append(label)
        return languages

    return languages


def _extract_menu_providers(provider_data: dict) -> list[str]:
    provider_names: set[str] = set()

    for providers_map in provider_data.values():
        if isinstance(providers_map, dict):
            provider_names.update(str(p) for p in providers_map.keys())

    return sorted(provider_names)


# ============================================================
# Patch: Fix for Python 3.14+ buffer overflow in npyscreen
# ============================================================
if sys.version_info >= (3, 14):
    import npyscreen.proto_fm_screen_area as _npyscreen_area

    def _patched_max_physical(self):
        try:
            return (curses.LINES - 1, curses.COLS - 1)
        except Exception:
            try:
                size = os.get_terminal_size()
            except OSError:
                size = os.terminal_size((80, 24))
            return (size.lines - 1, size.columns - 1)

    _npyscreen_area.ScreenArea._max_physical = _patched_max_physical


# ============================================================
# Theme
# ============================================================
class CustomTheme(npyscreen.ThemeManager):
    """Color reference: https://npyscreen.readthedocs.io/color.html"""

    default_colors = {
        "DEFAULT": "WHITE_BLACK",
        "FORMDEFAULT": "MAGENTA_BLACK",
        "NO_EDIT": "BLUE_BLACK",
        "STANDOUT": "CYAN_BLACK",
        "CURSOR": "WHITE_BLACK",
        "CURSOR_INVERSE": "BLACK_WHITE",
        "LABEL": "CYAN_BLACK",
        "LABELBOLD": "CYAN_BLACK",
        "CONTROL": "GREEN_BLACK",
        "IMPORTANT": "GREEN_BLACK",
        "SAFE": "GREEN_BLACK",
        "WARNING": "YELLOW_BLACK",
        "DANGER": "RED_BLACK",
        "CRITICAL": "BLACK_RED",
        "GOOD": "GREEN_BLACK",
        "GOODHL": "GREEN_BLACK",
        "VERYGOOD": "BLACK_GREEN",
        "CAUTION": "YELLOW_BLACK",
        "CAUTIONHL": "BLACK_YELLOW",
    }


# ============================================================
# Base Form with Quit Handlers
# ============================================================
class QuitForm(npyscreen.Form):
    def set_up_handlers(self):
        super().set_up_handlers()
        self.add_handlers({curses.ascii.ETX: self.exit_editing})
        self.add_handlers({ord("q"): self.exit_editing})
        self.add_handlers({curses.KEY_RESIZE: self._handle_resize})

    def _handle_resize(self, _input):
        curses.update_lines_cols()
        self.resize()
        self.display()


# ============================================================
class Action(Enum):
    DOWNLOAD = "Download"
    WATCH = "Watch"
    SYNCPLAY = "Syncplay"


# ============================================================
# Application
# ============================================================
# TODO: auto rescale on terminal size change
class MenuApp(npyscreen.NPSApp):
    def __init__(self, url: str = ""):
        super().__init__()
        self.url = url
        self._episodes_widget = None

    def _calculate_layout(self, languages_count, providers_count):
        """Calculate optimal layout dimensions for the UI."""
        try:
            terminal_height = os.get_terminal_size().lines
        except OSError:
            terminal_height = 24

        # Calculate reserved height for all widgets (title + action + path/aniskip + language + provider + select all button + spacing)
        total_reserved_height = (
            2  # form title space
            + 4  # action widget
            + 2  # path/aniskip widget (only one visible at a time)
            + max(2, languages_count)  # language widget
            + max(2, providers_count)  # provider widget
            + 1  # select all button
            + 6  # spacing and bottom padding
        )

        max_episode_height = max(3, terminal_height - total_reserved_height)
        return max_episode_height, terminal_height

    def main(self):
        npyscreen.setTheme(CustomTheme)
        F = QuitForm(name=f"AniWorld-Downloader v.{VERSION}")

        # ============================================================
        # Get Values for series
        # ============================================================

        # Load series
        provider_meta = resolve_provider(self.url)
        series = provider_meta.series_cls(self.url)

        languages = []
        providers = []
        episodes = []

        # Only use the first season and first episode for language/provider info
        first_season = series.seasons[0]
        first_episode = first_season.episodes[0]

        # All episode URLs
        for season in series.seasons:
            for episode in season.episodes:
                episodes.append(episode.url)

        # Extract language/provider options from provider_data
        provider_data_dict = (
            first_episode.provider_data._data
            if hasattr(first_episode.provider_data, "_data")
            else first_episode.provider_data
        )

        languages = _extract_menu_languages(provider_meta.name, provider_data_dict)
        providers = _extract_menu_providers(provider_data_dict)

        if not languages:
            # Fallback to something sensible so the UI stays usable.
            # The episode model will validate on use.
            languages = [os.getenv("ANIWORLD_LANGUAGE", "German Dub")]

        if not providers:
            providers = [os.getenv("ANIWORLD_PROVIDER", "VOE")]

        # Track vertical position
        y = 2  # leave space for form title

        # --- Action ---
        is_docker = False

        if os.getenv("ANIWORLD_DOWNLOAD_PATH") == "/app/Downloads":
            is_docker = True

        action = F.add(
            npyscreen.TitleSelectOne,
            name="Action",
            values=[Action.DOWNLOAD.value]
            if is_docker
            else [Action.DOWNLOAD.value, Action.WATCH.value, Action.SYNCPLAY.value],
            value=[0],
            max_height=3,
            rely=y,
            scroll_exit=True,
        )

        # --- Path ---
        path = F.add(
            npyscreen.TitleFilenameCombo,
            name="Path",
            value=os.getenv("ANIWORLD_DOWNLOAD_PATH")
            or (Path("/app/Downloads") if is_docker else Path.home() / "Downloads"),
            rely=y + 4,
            max_height=2,
        )

        # --- Aniskip ---
        aniskip = F.add(
            npyscreen.TitleMultiSelect,
            name="Aniskip",
            values=["Enabled"],
            max_height=2,
            rely=y + 4,
            scroll_exit=True,
        )

        # --- Function to update visibility based on action ---
        def update_visibility():
            selected = action.get_selected_objects()
            selected_action = selected[0] if selected else Action.DOWNLOAD.value

            if selected_action in [Action.WATCH.value, Action.SYNCPLAY.value]:
                path.hidden = True
                aniskip.hidden = False
            else:  # DOWNLOAD
                path.hidden = False
                aniskip.hidden = True

            # Refresh the form layout
            F.display()

        # Attach handler: update visibility when action changes
        action.when_value_edited = update_visibility

        # Initialize visibility
        update_visibility()

        # --- Language ---
        language = F.add(
            npyscreen.TitleSelectOne,
            name="Language",
            values=languages,
            value=(
                os.getenv("ANIWORLD_LANGUAGE") in languages
                and [languages.index(os.getenv("ANIWORLD_LANGUAGE"))]
                or [0]
            ),
            max_height=max(2, len(languages)),
            rely=y + 6,
            scroll_exit=True,
        )

        # --- Provider ---
        provider = F.add(
            npyscreen.TitleSelectOne,
            name="Provider",
            values=providers,
            value=(
                os.getenv("ANIWORLD_PROVIDER") in providers
                and [providers.index(os.getenv("ANIWORLD_PROVIDER"))]
                or [0]
            ),
            max_height=max(2, len(providers)),
            rely=y + 6 + max(2, len(languages)) + 1,
            scroll_exit=True,
        )

        # --- Episodes ---
        max_episode_height, _ = self._calculate_layout(len(languages), len(providers))

        episodes_rely = y + 6 + max(2, len(languages)) + 1 + max(2, len(providers)) + 1
        episodes_widget = F.add(
            npyscreen.TitleMultiSelect,
            name="Episodes",
            values=episodes,
            rely=episodes_rely,
            max_height=max_episode_height,
            scroll_exit=True,
        )

        # Store reference for resize handling
        self._episodes_widget = episodes_widget

        # --- Select All Button ---
        select_all_button = F.add(
            npyscreen.ButtonPress,
            name="Select All",
            rely=episodes_rely + max_episode_height + 1,
        )

        def toggle_select_all():
            if len(episodes_widget.value) == len(episodes):
                episodes_widget.value = []
                select_all_button.name = "Select All"
            else:
                episodes_widget.value = list(range(len(episodes)))
                select_all_button.name = "Deselect All"
            F.display()

        select_all_button.whenPressed = toggle_select_all

        # Set up resize handler
        def handle_resize(input):
            curses.update_lines_cols()
            max_episode_height, _ = self._calculate_layout(
                len(languages), len(providers)
            )
            if self._episodes_widget:
                self._episodes_widget.max_height = max_episode_height
            F.resize()
            F.display()

        # Add resize handler
        F.add_handlers({curses.KEY_RESIZE: handle_resize})

        # --- Edit the form ---
        F.edit()

        # --- After editing, get all selected values ---
        selected_action = (
            action.get_selected_objec
gitextract_r78ceu71/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows/
│       ├── build-nuitka.yaml
│       ├── docker-publish.yml
│       └── publish.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── docker-compose.yaml
├── examples/
│   ├── aniworld_to/
│   │   ├── aniworld_episode.py
│   │   ├── aniworld_episode_aniskip.py
│   │   ├── aniworld_episode_muxing.py
│   │   ├── aniworld_season.py
│   │   └── aniworld_series.py
│   └── s_to/
│       ├── serienstream_episode.py
│       ├── serienstream_episode_muxing.py
│       ├── serienstream_season.py
│       └── serienstream_series.py
├── pyproject.toml
├── src/
│   └── aniworld/
│       ├── __init__.py
│       ├── __main__.py
│       ├── anime4k/
│       │   ├── __init__.py
│       │   └── anime4k.py
│       ├── aniskip/
│       │   ├── __init__.py
│       │   ├── aniskip.py
│       │   ├── jikan.py
│       │   └── scripts/
│       │       ├── aniskip.lua
│       │       ├── autoexit.lua
│       │       └── autostart.lua
│       ├── arguments.py
│       ├── ascii/
│       │   ├── ASCII.txt
│       │   ├── __init__.py
│       │   └── ascii.py
│       ├── autodeps.py
│       ├── common/
│       │   ├── __init__.py
│       │   └── common.py
│       ├── config.py
│       ├── entry.py
│       ├── env.py
│       ├── extractors/
│       │   ├── __init__.py
│       │   └── provider/
│       │       ├── doodstream.py
│       │       ├── filemoon.py
│       │       ├── loadx.py
│       │       ├── luluvdo.py
│       │       ├── streamtape.py
│       │       ├── vidmoly.py
│       │       ├── vidoza.py
│       │       └── voe.py
│       ├── logger.py
│       ├── menu.py
│       ├── models/
│       │   ├── __init__.py
│       │   ├── aniworld_to/
│       │   │   ├── __init__.py
│       │   │   ├── episode.py
│       │   │   ├── season.py
│       │   │   └── series.py
│       │   ├── common/
│       │   │   ├── __init__.py
│       │   │   └── common.py
│       │   ├── filmpalast_to/
│       │   │   └── episode.py
│       │   ├── hanime_tv/
│       │   │   ├── __init__.py
│       │   │   └── episode.py
│       │   ├── hianime_to/
│       │   │   ├── __init__.py
│       │   │   ├── episode.py
│       │   │   ├── season.py
│       │   │   └── series.py
│       │   └── s_to/
│       │       ├── __init__.py
│       │       ├── episode.py
│       │       ├── season.py
│       │       └── series.py
│       ├── nuitka/
│       │   └── manual_build.py
│       ├── playwright/
│       │   ├── __init__.py
│       │   ├── captcha.py
│       │   └── test.py
│       ├── providers.py
│       ├── search.py
│       └── web/
│           ├── __init__.py
│           ├── app.py
│           ├── auth.py
│           ├── captcha.py
│           ├── db.py
│           ├── static/
│           │   ├── app.js
│           │   ├── autosync.js
│           │   ├── library.js
│           │   ├── queue.js
│           │   ├── settings.js
│           │   └── style.css
│           └── templates/
│               ├── autosync.html
│               ├── base.html
│               ├── index.html
│               ├── library.html
│               ├── login.html
│               ├── settings.html
│               └── setup.html
└── tests/
    ├── test_aniworld_models.py
    └── test_aniworld_providers.py
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (720K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 22,
    "preview": "github: phoenixthrush\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 1093,
    "preview": "---\nname: Bug report\nabout: Something is broken / not working as expected\ntitle: \"\"\nlabels: [\"bug\"]\n---\n\n## What happene..."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 738,
    "preview": "---\nname: Feature request\nabout: Suggest an idea or improvement\ntitle: \"\"\nlabels: [\"enhancement\"]\n---\n\n## What do you wa..."
  },
  {
    "path": ".github/workflows/build-nuitka.yaml",
    "chars": 914,
    "preview": "name: Build Executable with Nuitka\n\non:\n  # release:\n  #  types: [published]\n  workflow_dispatch:\n\njobs:\n  build:\n    st..."
  },
  {
    "path": ".github/workflows/docker-publish.yml",
    "chars": 3547,
    "preview": "name: Build Docker Image\n\n# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-..."
  },
  {
    "path": ".github/workflows/publish.yaml",
    "chars": 931,
    "preview": "name: Publish to PyPI\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n\njobs:\n  pypi-publish:\n    name: Uploa..."
  },
  {
    "path": ".gitignore",
    "chars": 4595,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[codz]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packag..."
  },
  {
    "path": "Dockerfile",
    "chars": 2246,
    "preview": "FROM python:3.13-slim\n\nWORKDIR /app\n\nRUN mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix\n\n# Install ffmpeg, Xvfb an..."
  },
  {
    "path": "LICENSE",
    "chars": 1083,
    "preview": "Copyright (c) 2024-2026 phoenixthrush, SiroxCW, Tmaster055\n\nPermission is hereby granted, free of charge, to any person..."
  },
  {
    "path": "MANIFEST.in",
    "chars": 325,
    "preview": "include src/aniworld/.env.example\ninclude src/aniworld/ascii/ASCII.txt\ninclude src/aniworld/aniskip/scripts/aniskip.lua..."
  },
  {
    "path": "README.md",
    "chars": 11747,
    "preview": "<a id=\"readme-top\"></a>\n\n# AniWorld Downloader v4\n\nAniWorld Downloader is a cross-platform tool for streaming and downlo..."
  },
  {
    "path": "docker-compose.yaml",
    "chars": 2596,
    "preview": "services:\n  aniworld:\n    image: ghcr.io/phoenixthrush/aniworld-downloader:latest\n    container_name: aniworld-downloade..."
  },
  {
    "path": "examples/aniworld_to/aniworld_episode.py",
    "chars": 1517,
    "preview": "from aniworld.config import Audio, Subtitles\nfrom aniworld.models import AniworldEpisode\n\nepisode_url = \"https://aniworl..."
  },
  {
    "path": "examples/aniworld_to/aniworld_episode_aniskip.py",
    "chars": 280,
    "preview": "import os\n\nfrom aniworld.models import AniworldEpisode\n\nepisode = AniworldEpisode(\n    \"https://aniworld.to/anime/stream..."
  },
  {
    "path": "examples/aniworld_to/aniworld_episode_muxing.py",
    "chars": 776,
    "preview": "from aniworld.models import AniworldEpisode\n\nurl = \"https://aniworld.to/anime/stream/highschool-dxd/staffel-1/episode-1\"..."
  },
  {
    "path": "examples/aniworld_to/aniworld_season.py",
    "chars": 519,
    "preview": "from aniworld.models import AniworldSeason\n\nseason_url = \"https://aniworld.to/anime/stream/highschool-dxd/staffel-1\"\n\nse..."
  },
  {
    "path": "examples/aniworld_to/aniworld_series.py",
    "chars": 877,
    "preview": "from aniworld.models import AniworldSeries\n\nurl = \"https://aniworld.to/anime/stream/highschool-dxd\"\n\nseries = AniworldSe..."
  },
  {
    "path": "examples/s_to/serienstream_episode.py",
    "chars": 1475,
    "preview": "from aniworld.config import Audio, Subtitles\nfrom aniworld.models import SerienstreamEpisode\n\nepisode_url = \"https://ser..."
  },
  {
    "path": "examples/s_to/serienstream_episode_muxing.py",
    "chars": 732,
    "preview": "from aniworld.models import SerienstreamEpisode\n\nurl = \"https://s.to/serie/american-horror-story-die-dunkle-seite-in-dir..."
  },
  {
    "path": "examples/s_to/serienstream_season.py",
    "chars": 515,
    "preview": "from aniworld.models import SerienstreamSeason\n\nseason_url = \"https://serienstream.to/serie/american-horror-story-die-du..."
  },
  {
    "path": "examples/s_to/serienstream_series.py",
    "chars": 836,
    "preview": "from aniworld.models import SerienstreamSeries\n\nseries_url = (\n    \"https://serienstream.to/serie/american-horror-story-..."
  },
  {
    "path": "pyproject.toml",
    "chars": 1692,
    "preview": "[project]\nname = \"aniworld\"\nversion = \"4.3.2\"\nauthors = [{ name = \"Phoenixthrush UwU\", email = \"contact@phoenixthrush.co..."
  },
  {
    "path": "src/aniworld/__init__.py",
    "chars": 552,
    "preview": "from .models.aniworld_to import (\n    AniworldEpisode,\n    AniworldSeason,\n    AniworldSeries,\n)\nfrom .models.hanime_tv..."
  },
  {
    "path": "src/aniworld/__main__.py",
    "chars": 1711,
    "preview": "# ========================\n# Nuitka project configuration\n# ========================\n\n# Basic flags\n# nuitka-project: --..."
  },
  {
    "path": "src/aniworld/anime4k/__init__.py",
    "chars": 52,
    "preview": "from .anime4k import anime4k\n\n__all__ = [\"anime4k\"]\n"
  },
  {
    "path": "src/aniworld/anime4k/anime4k.py",
    "chars": 8245,
    "preview": "import shutil\nimport sys\nfrom pathlib import Path\n\ntry:\n    from ..common import get_latest_github_release, unzip\n    fr..."
  },
  {
    "path": "src/aniworld/aniskip/__init__.py",
    "chars": 225,
    "preview": "from .aniskip import build_mpv_flags, get_skip_times, setup_aniskip\nfrom .jikan import get_all_seasons_by_query\n\n__all__..."
  },
  {
    "path": "src/aniworld/aniskip/aniskip.py",
    "chars": 3550,
    "preview": "import shutil\nimport tempfile\nfrom pathlib import Path\n\ntry:\n    from ..config import GLOBAL_SESSION, MPV_SCRIPTS_DIR, l..."
  },
  {
    "path": "src/aniworld/aniskip/jikan.py",
    "chars": 4186,
    "preview": "import time\n\ntry:\n    from ..config import GLOBAL_SESSION, logger\nexcept ImportError:\n    from aniworld.config import GL..."
  },
  {
    "path": "src/aniworld/aniskip/scripts/aniskip.lua",
    "chars": 750,
    "preview": "local mpv = require('mp')\nlocal mpv_options = require(\"mp.options\")\n\nlocal options = {\n    op_start = 0, op_end = 0, ed_..."
  },
  {
    "path": "src/aniworld/aniskip/scripts/autoexit.lua",
    "chars": 324,
    "preview": "function check_time()\n    local current_time = mp.get_property_number(\"time-pos\")\n    local total_time = mp.get_property..."
  },
  {
    "path": "src/aniworld/aniskip/scripts/autostart.lua",
    "chars": 83,
    "preview": "mp.register_event(\"file-loaded\", function()\n    mp.set_property(\"pause\", \"no\")\nend)"
  },
  {
    "path": "src/aniworld/arguments.py",
    "chars": 14155,
    "preview": "import argparse\nimport logging\nimport os\nimport sys\n\nfrom rich.console import Console\nfrom rich.panel import Panel\nfrom..."
  },
  {
    "path": "src/aniworld/ascii/ASCII.txt",
    "chars": 37559,
    "preview": "=== banner: LOGO ===\n   _____         .__ __      __            .__       .___\n  /  _  \\   ____ |__/  \\    /  \\_________..."
  },
  {
    "path": "src/aniworld/ascii/__init__.py",
    "chars": 175,
    "preview": "from .ascii import display_ascii_art, display_banner_art, display_traceback_art\n\n__all__ = [\n    \"display_ascii_art\",..."
  },
  {
    "path": "src/aniworld/ascii/ascii.py",
    "chars": 2080,
    "preview": "import platform\nimport random\nimport re\nfrom pathlib import Path\n\n\ndef __load_ascii_content():\n    \"\"\"Load the contents..."
  },
  {
    "path": "src/aniworld/autodeps.py",
    "chars": 9347,
    "preview": "import os\nimport platform\nimport shutil\nimport subprocess\nfrom pathlib import Path\nfrom typing import List\n\nPLATFORM = p..."
  },
  {
    "path": "src/aniworld/common/__init__.py",
    "chars": 155,
    "preview": "from .common import fetch_github_asset_urls, get_latest_github_release, unzip\n\n__all__ = [\"fetch_github_asset_urls\", \"ge..."
  },
  {
    "path": "src/aniworld/common/common.py",
    "chars": 2804,
    "preview": "import re\nimport subprocess\nimport sys\nfrom pathlib import Path\n\ntry:\n    from ..config import GLOBAL_SESSION\nexcept Imp..."
  },
  {
    "path": "src/aniworld/config.py",
    "chars": 7883,
    "preview": "import os\nimport re\nfrom enum import Enum\nfrom importlib.metadata import PackageNotFoundError, version\nfrom pathlib impo..."
  },
  {
    "path": "src/aniworld/entry.py",
    "chars": 7154,
    "preview": "import os\nimport sys\nfrom pathlib import Path\n\nfrom .arguments import parse_args\nfrom .autodeps import ensure_patchright..."
  },
  {
    "path": "src/aniworld/env.py",
    "chars": 1304,
    "preview": "import re\nfrom pathlib import Path\n\nfrom dotenv import load_dotenv\n\n# match lines like KEY=VALUE, ignoring comments and..."
  },
  {
    "path": "src/aniworld/extractors/__init__.py",
    "chars": 555,
    "preview": "import importlib\nimport inspect\nimport pkgutil\nfrom pathlib import Path\n\nprovider_functions = {}\n\nprovider_path = Path(_..."
  },
  {
    "path": "src/aniworld/extractors/provider/doodstream.py",
    "chars": 4321,
    "preview": "import logging\nimport random\nimport re\nimport time\nimport warnings\nfrom urllib.parse import urljoin\n\nimport niquests\nfro..."
  },
  {
    "path": "src/aniworld/extractors/provider/filemoon.py",
    "chars": 9188,
    "preview": "import base64\nimport json\nimport logging\nimport re\nfrom urllib.parse import urlparse\n\nimport niquests\n\nfrom cryptography..."
  },
  {
    "path": "src/aniworld/extractors/provider/loadx.py",
    "chars": 1246,
    "preview": "try:\n    from ...config import DEFAULT_USER_AGENT\nexcept ImportError:\n    from aniworld.config import DEFAULT_USER_AGENT..."
  },
  {
    "path": "src/aniworld/extractors/provider/luluvdo.py",
    "chars": 1268,
    "preview": "try:\n    from ...config import DEFAULT_USER_AGENT\nexcept ImportError:\n    from aniworld.config import DEFAULT_USER_AGENT..."
  },
  {
    "path": "src/aniworld/extractors/provider/streamtape.py",
    "chars": 1301,
    "preview": "try:\n    from ...config import DEFAULT_USER_AGENT\nexcept ImportError:\n    from aniworld.config import DEFAULT_USER_AGENT..."
  },
  {
    "path": "src/aniworld/extractors/provider/vidmoly.py",
    "chars": 2879,
    "preview": "import re\n\ntry:\n    from ...config import GLOBAL_SESSION\nexcept ImportError:\n    from aniworld.config import GLOBAL_SESS..."
  },
  {
    "path": "src/aniworld/extractors/provider/vidoza.py",
    "chars": 2202,
    "preview": "import re\n\nimport niquests\n\ntry:\n    from ...config import DEFAULT_USER_AGENT, GLOBAL_SESSION\nexcept ImportError:\n    fr..."
  },
  {
    "path": "src/aniworld/extractors/provider/voe.py",
    "chars": 10182,
    "preview": "import base64\nimport binascii\nimport json\nimport logging\nimport re\nimport time\nfrom urllib.parse import urlparse\n\nimport..."
  },
  {
    "path": "src/aniworld/logger.py",
    "chars": 3094,
    "preview": "import logging\nimport os\nimport tempfile\nfrom pathlib import Path\n\n_global_logger = None\n\n# ANSI color codes for console..."
  },
  {
    "path": "src/aniworld/menu.py",
    "chars": 13093,
    "preview": "import curses\nimport curses.ascii\nimport json\nimport os\nimport sys\nfrom enum import Enum\nfrom pathlib import Path\n\nimpor..."
  },
  {
    "path": "src/aniworld/models/__init__.py",
    "chars": 524,
    "preview": "from .aniworld_to import (\n    AniworldEpisode,\n    AniworldSeason,\n    AniworldSeries,\n)\nfrom .hanime_tv import HanimeT..."
  },
  {
    "path": "src/aniworld/models/aniworld_to/__init__.py",
    "chars": 189,
    "preview": "from .episode import AniworldEpisode\nfrom .season import AniworldSeason\nfrom .series import AniworldSeries\n\n__all__ = [..."
  },
  {
    "path": "src/aniworld/models/aniworld_to/episode.py",
    "chars": 28530,
    "preview": "import os\nimport re\nfrom collections import defaultdict\nfrom pathlib import Path\nfrom urllib.parse import urlparse\n\nfrom..."
  },
  {
    "path": "src/aniworld/models/aniworld_to/season.py",
    "chars": 14420,
    "preview": "import re\n\nfrom ...config import ANIWORLD_SEASON_PATTERN, GLOBAL_SESSION, logger\nfrom .episode import AniworldEpisode..."
  },
  {
    "path": "src/aniworld/models/aniworld_to/series.py",
    "chars": 26923,
    "preview": "import re\nfrom html import unescape\n\nfrom ...config import ANIWORLD_SERIES_PATTERN, GLOBAL_SESSION, logger\nfrom ..common..."
  },
  {
    "path": "src/aniworld/models/common/__init__.py",
    "chars": 243,
    "preview": "from .common import (\n    ProviderData,\n    check_downloaded,\n    clean_title,\n    download,\n    syncplay,\n    watch,\n)..."
  },
  {
    "path": "src/aniworld/models/common/common.py",
    "chars": 23781,
    "preview": "import getpass\nimport hashlib\nimport os\nimport platform\nimport re\nimport shlex\nimport subprocess\nimport sys\nimport threa..."
  },
  {
    "path": "src/aniworld/models/filmpalast_to/episode.py",
    "chars": 18895,
    "preview": "import os\nimport re\nfrom pathlib import Path\n\ntry:\n    from ...config import (\n        GLOBAL_SESSION,\n        NAMING_TE..."
  },
  {
    "path": "src/aniworld/models/hanime_tv/__init__.py",
    "chars": 75,
    "preview": "from .episode import HanimeTVEpisode\n\n__all__ = [\n    \"HanimeTVEpisode\",\n]\n"
  },
  {
    "path": "src/aniworld/models/hanime_tv/episode.py",
    "chars": 358,
    "preview": "class HanimeTVEpisode:\n    # TODO: implement\n    def __init__(self, url):\n        self.url = url\n\n    def download(self)..."
  },
  {
    "path": "src/aniworld/models/hianime_to/__init__.py",
    "chars": 183,
    "preview": "from .episode import HiAnimeEpisode\nfrom .season import HiAnimeSeason\nfrom .series import HiAnimeSeries\n\n__all__ = [..."
  },
  {
    "path": "src/aniworld/models/hianime_to/episode.py",
    "chars": 9219,
    "preview": "from ...config import GLOBAL_SESSION, logger\nfrom ..common.common import (\n    download as episode_download,\n)\nfrom ..co..."
  },
  {
    "path": "src/aniworld/models/hianime_to/season.py",
    "chars": 4158,
    "preview": "from ...config import GLOBAL_SESSION, HIANIME_SEASON_PATTERN, logger\n\n\nclass HiAnimeSeason:\n    \"\"\"\n    Represents a sin..."
  },
  {
    "path": "src/aniworld/models/hianime_to/series.py",
    "chars": 7316,
    "preview": "from ...config import GLOBAL_SESSION, HIANIME_SERIES_PATTERN, logger\nfrom ..common import clean_title\n\n\nclass HiAnimeSer..."
  },
  {
    "path": "src/aniworld/models/s_to/__init__.py",
    "chars": 213,
    "preview": "from .episode import SerienstreamEpisode\nfrom .season import SerienstreamSeason\nfrom .series import SerienstreamSeries..."
  },
  {
    "path": "src/aniworld/models/s_to/episode.py",
    "chars": 21328,
    "preview": "import os\nimport re\nfrom enum import Enum\nfrom html import unescape\nfrom pathlib import Path\n\nfrom ...config import (..."
  },
  {
    "path": "src/aniworld/models/s_to/season.py",
    "chars": 5332,
    "preview": "import re\nfrom urllib.parse import urljoin\n\nfrom ...config import GLOBAL_SESSION, SERIENSTREAM_SEASON_PATTERN, logger..."
  },
  {
    "path": "src/aniworld/models/s_to/series.py",
    "chars": 31604,
    "preview": "import re\nfrom urllib.parse import urljoin, urlparse\n\nfrom ...config import GLOBAL_SESSION, SERIENSTREAM_SERIES_PATTERN,..."
  },
  {
    "path": "src/aniworld/nuitka/manual_build.py",
    "chars": 474,
    "preview": "import subprocess\n\n\ndef build():\n    # Include browsers.jsonl from fake_useragent package\n    # JSON_PATH = os.path.join..."
  },
  {
    "path": "src/aniworld/playwright/__init__.py",
    "chars": 84,
    "preview": "from .captcha import playwright_get_page_url\n\n__all__ = [\"playwright_get_page_url\"]\n"
  },
  {
    "path": "src/aniworld/playwright/captcha.py",
    "chars": 26738,
    "preview": "import threading as _threading\nimport queue as _queue_module\nimport time as _time\nimport random as _random\n\n# Threading-..."
  },
  {
    "path": "src/aniworld/playwright/test.py",
    "chars": 439,
    "preview": "from aniworld.models import SerienstreamEpisode\n\nepisode = SerienstreamEpisode(\"https://s.to/serie/mr-pickles/staffel-1/..."
  },
  {
    "path": "src/aniworld/providers.py",
    "chars": 3119,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Optional, Pattern, Type\nfrom ur..."
  },
  {
    "path": "src/aniworld/search.py",
    "chars": 17730,
    "preview": "import curses\nimport html as html_module\nimport os\nimport random\nimport re\n\ntry:\n    from .ascii import display_ascii_ar..."
  },
  {
    "path": "src/aniworld/web/__init__.py",
    "chars": 58,
    "preview": "from .app import start_web_ui\n\n__all__ = [\"start_web_ui\"]\n"
  },
  {
    "path": "src/aniworld/web/app.py",
    "chars": 63049,
    "preview": "import json\nimport re\nimport threading\nimport time\n\nimport requests\nfrom flask import Flask, jsonify, redirect, render_t..."
  },
  {
    "path": "src/aniworld/web/auth.py",
    "chars": 12572,
    "preview": "import os\nimport re\nimport secrets\nimport time\nfrom functools import wraps\n\nfrom authlib.integrations.flask_client impor..."
  },
  {
    "path": "src/aniworld/web/captcha.py",
    "chars": 305,
    "preview": "\"\"\"\r\nRe-export captcha helpers from the playwright module so any code that imports\r\nfrom ``aniworld.web.captcha`` (e.g...."
  },
  {
    "path": "src/aniworld/web/db.py",
    "chars": 27387,
    "preview": "import os\nimport sqlite3\n\nfrom werkzeug.security import check_password_hash, generate_password_hash\n\nfrom ..config impor..."
  },
  {
    "path": "src/aniworld/web/static/app.js",
    "chars": 29213,
    "preview": "const searchInput = document.getElementById(\"searchInput\");\nconst searchBtn = document.getElementById(\"searchBtn\");\ncons..."
  },
  {
    "path": "src/aniworld/web/static/autosync.js",
    "chars": 9107,
    "preview": "// Auto-Sync page logic\n\nconst autosyncList = document.getElementById(\"autosyncList\");\nconst autosyncEmpty = document.ge..."
  },
  {
    "path": "src/aniworld/web/static/library.js",
    "chars": 17257,
    "preview": "let libraryLocations = [];\nvar libraryLangSep = false;\n\n// --- Expanded state save/restore (uses semantic keys, survives..."
  },
  {
    "path": "src/aniworld/web/static/queue.js",
    "chars": 14176,
    "preview": "let queueModalOpen = false;\nlet queuePollTimer = null;\nlet badgePollTimer = null;\nlet queueCustomPaths = [];\n\n(async fun..."
  },
  {
    "path": "src/aniworld/web/static/settings.js",
    "chars": 12463,
    "preview": "// Download path settings\nconst downloadPathInput = document.getElementById(\"downloadPath\");\nconst langSeparationCb = do..."
  },
  {
    "path": "src/aniworld/web/static/style.css",
    "chars": 35834,
    "preview": "/* ===== Reset & Base ===== */\n* {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\nbody {\n  background: #0a0a0f;..."
  },
  {
    "path": "src/aniworld/web/templates/autosync.html",
    "chars": 3275,
    "preview": "{% extends \"base.html\" %} {% block title %}Auto-Sync — AniWorld Downloader{%\nendblock %} {% block content %}\n<div class=..."
  },
  {
    "path": "src/aniworld/web/templates/base.html",
    "chars": 3528,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-w..."
  },
  {
    "path": "src/aniworld/web/templates/index.html",
    "chars": 4413,
    "preview": "{% extends \"base.html\" %} {% block content %}\n<div class=\"container\">\n  <h1 id=\"pageHeading\">AniWorld Downloader</h1>..."
  },
  {
    "path": "src/aniworld/web/templates/library.html",
    "chars": 480,
    "preview": "{% extends \"base.html\" %} {% block title %}Library — AniWorld Downloader{%\nendblock %} {% block content %}\n<div class=\"l..."
  },
  {
    "path": "src/aniworld/web/templates/login.html",
    "chars": 1588,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-w..."
  },
  {
    "path": "src/aniworld/web/templates/settings.html",
    "chars": 6649,
    "preview": "{% extends \"base.html\" %} {% block title %}Settings — AniWorld Downloader{%\nendblock %} {% block content %}\n<div class=\"..."
  },
  {
    "path": "src/aniworld/web/templates/setup.html",
    "chars": 1583,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-w..."
  },
  {
    "path": "tests/test_aniworld_models.py",
    "chars": 2783,
    "preview": "import json\n\nfrom aniworld.models import (\n    AniworldEpisode,\n    AniworldSeason,\n    AniworldSeries,\n    Serienstream..."
  },
  {
    "path": "tests/test_aniworld_providers.py",
    "chars": 1354,
    "preview": "from aniworld.config import logger\nfrom aniworld.extractors import provider_functions\n\n\ndef run_test(name, func, url):..."
  }
]

About this extraction

This page contains the full source code of the phoenixthrush/AniWorld-Downloader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (663.7 KB), approximately 216.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!