[
  {
    "path": ".dockerignore",
    "content": "/src/target"
  },
  {
    "path": ".gitattributes",
    "content": "src/crusader-lib/assets/* linguist-vendored"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\r\n\r\non:\r\n  push:\r\n    branches: [master]\r\n  pull_request:\r\n\r\nenv:\r\n  CARGO_INCREMENTAL: 0\r\n\r\njobs:\r\n  build:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n\r\n      - uses: actions-rs/toolchain@v1\r\n        with:\r\n          toolchain: stable\r\n          override: true\r\n          components: rustfmt, clippy\r\n\r\n      - name: Build server-only binary\r\n        run: cargo build -p crusader --no-default-features\r\n        working-directory: src\r\n\r\n      - name: Build\r\n        run: cargo build\r\n        working-directory: src\r\n\r\n      - name: Lint\r\n        run: cargo clippy --all -- -D warnings\r\n        working-directory: src\r\n\r\n      - name: Format\r\n        run: cargo fmt --all -- --check\r\n        working-directory: src\r\n\r\n  android:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n\r\n      - uses: actions-rs/toolchain@v1\r\n        with:\r\n          toolchain: stable\r\n          override: true\r\n          components: rustfmt, clippy\r\n\r\n      - name: Install Rust targets\r\n        run: >\r\n          rustup target add\r\n          aarch64-linux-android\r\n          armv7-linux-androideabi\r\n          x86_64-linux-android\r\n          i686-linux-android\r\n\r\n      - name: Install cargo-ndk\r\n        run: cargo install cargo-ndk\r\n\r\n      - name: Setup Java\r\n        uses: actions/setup-java@v3\r\n        with:\r\n          distribution: 'temurin'\r\n          java-version: '17'\r\n\r\n      - name: Setup Android SDK\r\n        uses: android-actions/setup-android@v2\r\n\r\n      - name: Build Android Rust crates\r\n        working-directory: android\r\n        run: cargo ndk -t arm64-v8a -o app/src/main/jniLibs/ -- build\r\n\r\n      - name: Build Android APK\r\n        working-directory: android\r\n        run: ./gradlew buildDebug\r\n\r\n      # Wait for a new cargo ndk release for better clippy support\r\n      #- name: Lint\r\n      #  run: cargo ndk -t arm64-v8a -- clippy --all -- -D warnings\r\n      #  working-directory: android\r\n\r\n      - name: Format\r\n        run: cargo fmt --all -- --check\r\n        working-directory: android\r\n"
  },
  {
    "path": ".github/workflows/release.md",
    "content": "Crusader has pre-built binaries for a number of operating systems. Download the appropriate binary below for your OS."
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\r\non:\r\n  push:\r\n    tags:\r\n    - \"v*\"\r\n\r\nenv:\r\n  CARGO_INCREMENTAL: 0\r\n\r\njobs:\r\n  create-release:\r\n    name: create-release\r\n    runs-on: ubuntu-latest\r\n    outputs:\r\n      upload_url: ${{ steps.release.outputs.upload_url }}\r\n    permissions:\r\n      contents: write\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n\r\n      - name: Get the release version from the tag\r\n        shell: bash\r\n        run: echo \"TAG_NAME=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV\r\n\r\n      - name: Create GitHub release\r\n        id: release\r\n        uses: actions/create-release@v1.1.4\r\n        env:\r\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\r\n        with:\r\n          tag_name: ${{ env.TAG_NAME }}\r\n          release_name: Automated build of ${{ env.TAG_NAME }}\r\n          prerelease: true\r\n          body_path: .github/workflows/release.md\r\n\r\n  release-assets:\r\n    name: Release assets\r\n    needs: create-release\r\n    runs-on: ${{ matrix.build.os }}\r\n    strategy:\r\n      fail-fast: false\r\n      matrix:\r\n        build:\r\n          - os: ubuntu-latest\r\n            target: arm-unknown-linux-musleabihf\r\n            friendly: Linux-ARM-32-bit\r\n            exe_postfix:\r\n            cargo: cross\r\n            gui: false\r\n\r\n          - os: ubuntu-latest\r\n            target: aarch64-unknown-linux-musl\r\n            friendly: Linux-ARM-64-bit\r\n            exe_postfix:\r\n            cargo: cross\r\n            gui: false\r\n\r\n          - os: ubuntu-latest\r\n            target: x86_64-unknown-linux-musl\r\n            friendly: Linux-X86-64-bit\r\n            exe_postfix:\r\n            cargo: cargo\r\n            gui: false\r\n\r\n          - os: macos-latest\r\n            target: aarch64-apple-darwin\r\n            friendly: macOS-ARM-64-bit\r\n            exe_postfix:\r\n            cargo: cargo\r\n            gui: true\r\n\r\n          - os: macos-latest\r\n            target: x86_64-apple-darwin\r\n            friendly: macOS-X86-64-bit\r\n            exe_postfix:\r\n            cargo: cargo\r\n            gui: true\r\n\r\n          - os: windows-latest\r\n            target: i686-pc-windows-msvc\r\n            friendly: Windows-X86-32-bit\r\n            exe_postfix: .exe\r\n            cargo: cargo\r\n            gui: true\r\n\r\n          - os: windows-latest\r\n            target: x86_64-pc-windows-msvc\r\n            friendly: Windows-X86-64-bit\r\n            exe_postfix: .exe\r\n            cargo: cargo\r\n            gui: true\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n\r\n      - uses: actions-rs/toolchain@v1\r\n        with:\r\n          toolchain: stable\r\n          override: true\r\n          target: ${{ matrix.build.target }}\r\n\r\n      - name: Install cross\r\n        if: matrix.build.cargo == 'cross'\r\n        run: cargo install cross\r\n\r\n      - name: Install and use musl\r\n        if: matrix.build.os == 'ubuntu-latest' && matrix.build.cargo != 'cross'\r\n        run: |\r\n          sudo apt-get install -y --no-install-recommends musl-tools\r\n          echo \"CC=musl-gcc\" >> $GITHUB_ENV\r\n          echo \"AR=ar\" >> $GITHUB_ENV\r\n\r\n      - name: Build command line binary\r\n        if: ${{ !matrix.build.gui }}\r\n        run: ${{ matrix.build.cargo }} build -p crusader --target ${{ matrix.build.target }} --release\r\n        working-directory: src\r\n        env:\r\n          RUSTFLAGS: \"-C target-feature=+crt-static\"\r\n\r\n      - name: Build\r\n        if: matrix.build.gui\r\n        run: ${{ matrix.build.cargo }} build --target ${{ matrix.build.target }} --release\r\n        working-directory: src\r\n        env:\r\n          RUSTFLAGS: \"-C target-feature=+crt-static\"\r\n\r\n      - name: Build output\r\n        shell: bash\r\n        run: |\r\n          staging=\"Crusader-${{ matrix.build.friendly }}\"\r\n          mkdir -p \"$staging\"\r\n          cp src/target/${{ matrix.build.target }}/release/crusader${{ matrix.build.exe_postfix }} \"$staging/\"\r\n\r\n      - name: Copy GUI binary\r\n        if: matrix.build.gui\r\n        shell: bash\r\n        run: |\r\n          cp src/target/${{ matrix.build.target }}/release/crusader-gui${{ matrix.build.exe_postfix }} \"crusader-${{ matrix.build.friendly }}/\"\r\n\r\n      - name: Archive output\r\n        if: matrix.build.os == 'windows-latest'\r\n        shell: bash\r\n        run: |\r\n          staging=\"Crusader-${{ matrix.build.friendly }}\"\r\n          7z a \"$staging.zip\" \"$staging\"\r\n          echo \"ASSET=$staging.zip\" >> $GITHUB_ENV\r\n\r\n      - name: Archive output\r\n        if: matrix.build.os != 'windows-latest'\r\n        shell: bash\r\n        run: |\r\n          staging=\"Crusader-${{ matrix.build.friendly }}\"\r\n          tar czf \"$staging.tar.gz\" \"$staging\"\r\n          echo \"ASSET=$staging.tar.gz\" >> $GITHUB_ENV\r\n\r\n      - name: Upload archive\r\n        uses: actions/upload-release-asset@v1.0.2\r\n        env:\r\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\r\n        with:\r\n          upload_url: ${{ needs.create-release.outputs.upload_url }}\r\n          asset_name: ${{ env.ASSET }}\r\n          asset_path: ${{ env.ASSET }}\r\n          asset_content_type: application/octet-stream\r\n\r\n  release-android-assets:\r\n    name: Android APK\r\n    needs: create-release\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n\r\n      - uses: actions-rs/toolchain@v1\r\n        with:\r\n          toolchain: stable\r\n          override: true\r\n\r\n      - name: Install Rust targets\r\n        run: >\r\n          rustup target add\r\n          aarch64-linux-android\r\n          armv7-linux-androideabi\r\n          x86_64-linux-android\r\n          i686-linux-android\r\n\r\n      - name: Install cargo-ndk\r\n        run: cargo install cargo-ndk\r\n\r\n      - name: Setup Java\r\n        uses: actions/setup-java@v3\r\n        with:\r\n          distribution: 'temurin'\r\n          java-version: '17'\r\n\r\n      - name: Setup Android SDK\r\n        uses: android-actions/setup-android@v2\r\n\r\n      - name: Build Android Rust crates\r\n        working-directory: android\r\n        run: >\r\n          cargo ndk\r\n          -t arm64-v8a\r\n          -t armeabi-v7a\r\n          -t x86_64\r\n          -t x86\r\n          -o app/src/main/jniLibs/ -- build --release\r\n\r\n      - name: Decode Keystore\r\n        env:\r\n            ENCODED_STRING: ${{ secrets.KEYSTORE }}\r\n        run: echo \"$ENCODED_STRING\" | base64 -di > ../android.keystore\r\n\r\n      - name: Build Android APK\r\n        working-directory: android\r\n        run: ./gradlew build\r\n        env:\r\n          SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}\r\n          SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}\r\n          SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}\r\n\r\n      - name: Upload APK\r\n        uses: actions/upload-release-asset@v1.0.2\r\n        env:\r\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\r\n        with:\r\n          upload_url: ${{ needs.create-release.outputs.upload_url }}\r\n          asset_name: Crusader-Android.apk\r\n          asset_path: android/app/build/outputs/apk/release/app-release.apk\r\n          asset_content_type: application/octet-stream\r\n"
  },
  {
    "path": ".gitignore",
    "content": "/src/target\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\n\nThe **Crusader Network Tester** measures network rates and latency\nin the presence of upload and download traffic.\nIt produces plots of the traffic rates,\nlatency and packet loss.\n\nThis file lists the changes that have occurred since January 2024 in the project.\n\n## Unreleased\n\n## 0.3.2 - 2024-10-03\n\n* Fix saved raw data path printed after a test\n* Avoid duplicate legends when plotting transferred bytes\n* Make `--plot-transferred` increase default plot height\n* Fix unique output path generation\n\n## 0.3.1 - 2024-09-30\n\n* Increase samples used for clock synchronization and idle latency measurement\n* Clock synchronization now uses the average of the lowest 1/3rd of samples\n* Adjust for clock drift in tests\n* Fix connecting to servers on non-standard port with peers\n* Make discovery more robust by sending multiple packets\n\n## 0.3 - 2024-09-16\n\n* Show throughput, latency, and packet loss summaries in plots and with the `test` command\n* Rename both option to bidirectional\n* Rename `--latency-peer-server` to `--latency-peer-address`\n* Continuous clock synchronization with the latency monitor\n* Support opening result files in the GUI by drag and drop\n* Add `--out-name` command line option to specify result filename prefix\n* Change filename prefix for both raw result and plots to `test`\n* Add file dialog to save options in GUI\n* Add buttons to save and load from the `crusader-results` folder in GUI\n* Add an `export` command line command to convert result files to JSON\n* Change timeout when connecting a peer to the server to 8 seconds\n* Hide advanced parameters in GUI\n* Add a reset parameters button in GUI\n* Add an option to measure latency-only for the client in the GUI\n* Don't allow peers to connect with the regular server\n* Added average lines in GUI\n\n## 0.2 - 2024-08-29\n\n* Added support for local discovery of server and peers using UDP port 35483\n* The `test` command line option `--latency-peer` is renamed to `--latency-peer-server`.\n  A new flag `--latency-peer` will instead search for a local peer.\n* Improved error messages\n* Fix date/time display in remote web page\n* Rename the `Latency` tab to `Monitor`\n* Change default streams from 16 to 8.\n* Change default throughput sample interval from 20 ms to 60 ms.\n* Change default load duration from 5 s to 10 s.\n* Change default grace duration from 5 s to 10 s.\n* Fix serving from link-local interfaces on Linux\n* Fix peers on link-local interfaces\n* Show download and upload plots for aggregate tests in the GUI\n* Added a shortcut (space) to stop the latency monitor\n* Change timeout when connecting to servers and peers to 8 seconds\n* Added average lines to the plot output\n* Show interface IPs when starting servers\n\n## 0.1 - 2024-08-21\n\n* Added `crusader remote` command to start a web server listening on port 35482.\n   It allows starting tests on a separate machine and\n   displays the resulting charts in the web page.\n* Use system fonts in GUI\n* Improved error handling and error messages\n* Added `--idle` option to the client to test without traffic\n* Save results in a `crusader-results` folder\n* Allow building of a server-only binary\n* Generated files will use a YYYY-MM-DD HH.MM.SS format\n* Rename bandwidth to throughput\n* Rename sample rate to sample interval\n* Rename `Both` to `Aggregate` and `Total` to `Round-trip` in plots\n\n## 0.0.12 - 2024-07-31\n\n* Create UDP server for each server IP (fixes #22)\n* Improved error handling for log messages\n* Changed date format to use YYYY-MM-DD in logs\n\n## 0.0.11 - 2024-07-29\n\n* Log file includes timestamps and version number\n* Added peer latency measurements\n* Added version to title bar of GUI\n* Added `plot_max_bandwidth` and `plot_max_latency` command line options\n\n## 0.0.10 - 2024-01-09\n\n* Specify plot title\n* Ignore ENOBUFS error\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Crusader Network Tester\n\n[![GitHub Release](https://img.shields.io/github/v/release/Zoxc/crusader)](https://github.com/Zoxc/crusader/releases)\n[![Docker Hub](https://img.shields.io/badge/container-dockerhub-blue)](https://hub.docker.com/r/zoxc/crusader)\n[![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Zoxc/crusader/blob/master/LICENSE-MIT)\n[![Apache](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/Zoxc/crusader/blob/master/LICENSE-APACHE)\n\n![Crusader Results Screenshot](./media/Crusader-Result.png)\n\nThe **Crusader Network Tester** measures network throughput, latency and packet loss\nin the presence of upload and download traffic.\nIt also incorporates a continuous latency tester for\nmonitoring background responsiveness.\n\nCrusader makes throughput measurements using TCP on port 35481\nand latency tests using UDP port 35481.\nThe remote web server option uses TCP port 35482.\nLocal server discovery uses UDP port 35483.\n\n**Pre-built binaries** for Windows, Mac, Linux,\nand Android are available on the\n[Releases](https://github.com/Zoxc/crusader/releases) page.\nThe GUI is not prebuilt for Linux and must be built from source.\n\n**Documentation** See the [Documentation](#documentation)\nsection below.\n\n**Status:** The latest Crusader release version is shown above.\n  The [pre-built binaries](https://github.com/Zoxc/crusader/releases)\n  always provide the latest version.\n  See the [CHANGELOG.md](./CHANGELOG.md) file for details.\n\n## Crusader GUI\n\nA test run requires two separate computers,\nboth running Crusader:\na **server** that listens for connections, and\na **client** that initiates the test.\n\nThe Crusader GUI incorporates both the server and\nthe client and allows you to interact with results.\nTo use it, download the proper binary from the\n[Releases](https://github.com/Zoxc/crusader/releases) page.\n\nWhen you open the `crusader-gui` you see this window.\nEnter the address of another computer that's\nrunning the Crusader server, then click **Start test**.\nWhen the test is complete, the **Result** tab shows a\nchart like the second image below.\n\nAn easy way to use Crusader is to download\nthe Crusader GUI onto two computers, then\nstart the server on one computer, and the client on the other.\n\n![Crusader Client Screenshot](./media/Crusader-Client.png)\n\nThe Crusader GUI has five tabs:\n\n* **Client tab**\n  Runs the Crusader client program.\n  The options shown above are described in the\n  [Command-line options](./docs/CLI.md) page.\n\n* **Server tab**\n  Runs the Crusader server, listening for connections from other clients\n\n* **Remote tab**\n  Starts a webserver (default port 35482).\n  A browser that connects to that port can initiate\n  a test to a Crusader server.\n  \n* **Monitor tab**\n  Continually displays the latency to the selected\n  Crusader server until stopped.\n\n* **Result tab**\n  Displays the result of the most recent client run\n\n## The Result Tab\n\n![Crusader Results Screenshot](./media/Crusader-Result.png)\n\nA Crusader test creates three bursts of traffic.\nBy default, it generates ten seconds each of\ndownload only, upload only, then bi-directional traffic.\nEach burst is separated by several seconds of idle time.\n\nThe Crusader Result tab displays the results of the test with\nthree plots (see image above):\n\n* The **Throughput** plot shows the bursts of traffic.\nGreen is download (from server to client),\nblue is upload, and\nthe purple line is the instantaneous\nsum of the download plus upload.\n\n* The **Latency** plot shows the corresponding latency.\nGreen shows the  (uni-directional) time from the server to the client.\nBlue is the (uni-directional) time from the client to the server.\nBlack shows the sum from the client to the server\nand back (round-trip time).\n\n* The **Packet Loss** plot has green and blue marks\nthat indicate times when packets were lost.\n\nFor more details, see the\n[Understanding Crusader Results](./docs/RESULTS.md) page.\n\n## Documentation\n\n* [This README](./README.md)\n* [Understanding Crusader Results](./docs/RESULTS.md)\n* [Local Testing](./docs/LOCAL_TESTS.md)\n* [Command-line Options](./docs/CLI.md)\n* [Building Crusader from source](./docs/BUILDING.md)\n* [Troubleshooting](./docs/TROUBLESHOOTING.md)\n* [Docker container](https://hub.docker.com/r/zoxc/crusader)\n  for the server is available on\n  [dockerhub](https://hub.docker.com/r/zoxc/crusader).\n"
  },
  {
    "path": "android/.gitignore",
    "content": ".gradle\n/target\n/app/build\n/app/src/main/jniLibs"
  },
  {
    "path": "android/Cargo.toml",
    "content": "[package]\nname = \"crusader-android\"\nversion = \"0.1.0\"\nedition = \"2021\"\nresolver = \"2\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nlog = \"0.4\"\neframe = { version = \"0.28.1\", features = [\"wgpu\"] }\ncrusader-gui-lib = { path = \"../src/crusader-gui-lib\" }\ncrusader-lib = { path = \"../src/crusader-lib\" }\nwinit = \"0.29.15\"\njni = \"0.19.0\"\nndk-context = \"0.1\"\n\n[target.'cfg(target_os = \"android\")'.dependencies]\nandroid_logger = \"0.11.0\"\nandroid-activity = { version = \"0.5\", features = [\"game-activity\"] }\n\n[patch.crates-io]\nwinit = { git = \"https://github.com/Zoxc/winit\", branch = \"crusader2\" }\negui = { git = \"https://github.com/Zoxc/egui\", branch = \"crusader2\" }\nepaint = { git = \"https://github.com/Zoxc/egui\", branch = \"crusader2\" }\nemath = { git = \"https://github.com/Zoxc/egui\", branch = \"crusader2\" }\negui_plot = { git = \"https://github.com/Zoxc/egui_plot\", branch = \"crusader\" }\n\n[lib]\nname = \"main\"\ncrate-type = [\"cdylib\"]\n"
  },
  {
    "path": "android/app/build.gradle",
    "content": "plugins {\n    id 'com.android.application'\n}\n\nandroid {\n    compileSdk 31\n\n    defaultConfig {\n        applicationId \"zoxc.crusader\"\n        minSdk 28\n        targetSdk 31\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    signingConfigs {\n        release {\n            storeFile = file(\"../../../android.keystore\")\n            storePassword System.getenv(\"SIGNING_STORE_PASSWORD\")\n            keyAlias System.getenv(\"SIGNING_KEY_ALIAS\")\n            keyPassword System.getenv(\"SIGNING_KEY_PASSWORD\")\n        }\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            signingConfig signingConfigs.release\n        }\n        debug {\n            minifyEnabled false\n            //packagingOptions {\n            //    doNotStrip '**/*.so'\n            //}\n            //debuggable true\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n\ndependencies {\n    implementation 'com.google.android.material:material:1.5.0'\n    implementation \"androidx.games:games-activity:2.0.2\"\n\n    // To use the Games Controller Library\n    //implementation \"androidx.games:games-controller:1.1.0\"\n\n    // To use the Games Text Input Library\n    //implementation \"androidx.games:games-text-input:1.1.0\"\n}\n"
  },
  {
    "path": "android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"zoxc.crusader\">\n\n    <uses-feature android:glEsVersion=\"0x30000\" />\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <application android:label=\"Crusader Network Tester\" android:theme=\"@style/Theme.RustTemplate\">\n        <activity android:name=\".MainActivity\" android:configChanges=\"orientation|screenSize|screenLayout|keyboardHidden\" android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n            <meta-data android:name=\"android.app.lib_name\" android:value=\"main\" />\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "android/app/src/main/java/zoxc/crusader/MainActivity.java",
    "content": "package zoxc.crusader;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.view.WindowCompat;\nimport androidx.core.view.WindowInsetsCompat;\nimport androidx.core.view.WindowInsetsControllerCompat;\n\nimport com.google.androidgamesdk.GameActivity;\n\nimport android.os.Bundle;\nimport android.content.pm.PackageManager;\nimport android.os.Build.VERSION;\nimport android.os.Build.VERSION_CODES;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.util.Log;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.app.Activity;\nimport android.view.inputmethod.InputMethodManager;\nimport android.provider.OpenableColumns;\nimport android.database.Cursor;\nimport android.content.Context;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\npublic class MainActivity extends GameActivity {\n    static {\n        System.loadLibrary(\"main\");\n    }\n\n    public void showKeyboard(boolean show) {\n        InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);\n        if (show) {\n            input.showSoftInput(getWindow().getDecorView().getRootView(), 0);\n        } else {\n            input.hideSoftInputFromWindow(getWindow().getDecorView().getRootView().getWindowToken(), 0);\n        }\n    }\n\n    public void loadFile() {\n        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);\n        intent.addCategory(Intent.CATEGORY_OPENABLE);\n        intent.setType(\"*/*\");\n        startActivityForResult(intent, ACTIVITY_LOAD_FILE);\n    }\n\n    public void saveFile(boolean image, String name, byte[] data) {\n        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);\n        intent.addCategory(Intent.CATEGORY_OPENABLE);\n        if (image) {\n            intent.setType(\"image/png\");\n        } else {\n            intent.setType(\"application/octet-stream\");\n        }\n        intent.putExtra(Intent.EXTRA_TITLE, name);\n        saveFileData = data;\n        saveImage = image;\n        startActivityForResult(intent, ACTIVITY_CREATE_FILE);\n    }\n\n    private byte[] saveFileData = null;\n    private boolean saveImage;\n\n    private static final int ACTIVITY_LOAD_FILE = 1;\n    private static final int ACTIVITY_CREATE_FILE = 2;\n\n    static native void fileLoaded(String name, byte[] data); \n\n    static native void fileSaved(boolean image, String name); \n\n    @Override\n    public void onActivityResult(int requestCode, int resultCode,\n            Intent resultData) {\n        super.onActivityResult(requestCode, resultCode, resultData);\n\n        if (requestCode == ACTIVITY_CREATE_FILE) {\n            if (resultCode == Activity.RESULT_OK && resultData != null) {\n                Uri uri = resultData.getData();\n                String name = getName(uri);\n                try {\n                    OutputStream stream = getContentResolver().openOutputStream(uri);\n                    stream.write(saveFileData);\n                    stream.close();\n                    fileSaved(saveImage, name);\n                }\n                catch(Exception e) {}\n            }\n            saveFileData = null;\n        }\n\n        if (requestCode == ACTIVITY_LOAD_FILE\n        && resultCode == Activity.RESULT_OK\n        && resultData != null) {\n            Uri uri = resultData.getData();\n            String name = getName(uri);\n\n            try {\n                InputStream stream = getContentResolver().openInputStream(uri);\n                ByteArrayOutputStream buffer = new ByteArrayOutputStream();\n                int read;\n                byte[] byte_buffer = new byte[0x1000];\n                while ((read = stream.read(byte_buffer, 0, byte_buffer.length)) != -1) {\n                    buffer.write(byte_buffer, 0, read);\n                }\n                stream.close();\n                byte[] data = buffer.toByteArray();\n                fileLoaded(name, data);\n            }\n            catch(Exception e) {}\n        }\n    }\n\n    public String getName(Uri uri) {\n        Cursor cursor = getContentResolver().query(uri, null, null, null, null, null);\n        String name = \"\";\n        try {\n            if (cursor != null && cursor.moveToFirst()) {\n                int column = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);\n                if (column != -1) {\n                    name = cursor.getString(column);\n                }\n                return name;\n            }\n        } finally {\n            cursor.close();\n        }\n        return name;\n    }\n}"
  },
  {
    "path": "android/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n    <color name=\"status\">#FFE6E6E6</color>\n</resources>"
  },
  {
    "path": "android/app/src/main/res/values/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.RustTemplate\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/purple_500</item>\n        <item name=\"colorPrimaryVariant\">@color/purple_700</item>\n        <item name=\"colorPrimaryDark\">@color/status</item>\n        <item name=\"colorOnPrimary\">@color/white</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_700</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\" tools:targetApi=\"l\">@color/status</item>\n        <item name=\"android:windowLightStatusBar\">true</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "android/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    id 'com.android.application' version '7.1.2' apply false\n    id 'com.android.library' version '7.1.2' apply false\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "android/debugInstall.ps1",
    "content": "$ErrorActionPreference = \"Stop\"\ncargo ndk -t arm64-v8a -o app/src/main/jniLibs/ -- build --release\nif ($lastexitcode -ne 0) {\n    throw \"Error\"\n}\n\n./gradlew.bat buildDebug\nif ($lastexitcode -ne 0) {\n    throw \"Error\"\n}\n\n./gradlew.bat installDebug\nif ($lastexitcode -ne 0) {\n    throw \"Error\"\n}\n"
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon May 02 15:39:12 BST 2022\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.5-bin.zip\ndistributionPath=wrapper/dists\nzipStorePath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\n"
  },
  {
    "path": "android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true"
  },
  {
    "path": "android/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "android/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "android/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        gradlePluginPortal()\n        google()\n        mavenCentral()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\ninclude ':app'\n"
  },
  {
    "path": "android/src/lib.rs",
    "content": "#![allow(\n    clippy::field_reassign_with_default,\n    clippy::option_map_unit_fn,\n    clippy::missing_safety_doc\n)]\n\nuse crusader_gui_lib::Tester;\nuse crusader_lib::file_format::RawResult;\nuse eframe::egui::{self, vec2, Align, FontFamily, Layout};\nuse jni::{\n    objects::{JClass, JObject, JString},\n    sys::{jboolean, jbyteArray},\n    JNIEnv,\n};\nuse std::{\n    error::Error,\n    io::Cursor,\n    path::Path,\n    sync::{Arc, Mutex},\n};\n\n#[cfg(target_os = \"android\")]\nuse {\n    android_activity::AndroidApp,\n    crusader_lib::test::PlotConfig,\n    eframe::{NativeOptions, Renderer, Theme},\n    log::Level,\n    std::fs,\n    winit::platform::android::EventLoopBuilderExtAndroid,\n};\n\nstruct App {\n    tester: Tester,\n    keyboard_shown: bool,\n}\n\nimpl eframe::App for App {\n    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n        use eframe::egui::FontFamily::Proportional;\n        use eframe::egui::FontId;\n        use eframe::egui::TextStyle::*;\n\n        let mut style = ctx.style();\n        let style_ = Arc::make_mut(&mut style);\n        style_.spacing.button_padding = vec2(10.0, 0.0);\n        style_.spacing.interact_size.y = 40.0;\n        style_.spacing.item_spacing = vec2(10.0, 10.0);\n\n        style_.text_styles = [\n            (Heading, FontId::new(26.0, Proportional)),\n            (Body, FontId::new(16.0, Proportional)),\n            (Monospace, FontId::new(16.0, FontFamily::Monospace)),\n            (Button, FontId::new(16.0, Proportional)),\n            (Small, FontId::new(16.0, Proportional)),\n        ]\n        .into();\n\n        ctx.set_style(style);\n\n        egui::CentralPanel::default().show(ctx, |ui| {\n            let mut rect = ui.max_rect();\n            rect.set_top(rect.top() + 40.0);\n            rect.set_height(rect.height() - 60.0);\n            let mut ui = ui.child_ui(rect, Layout::left_to_right(Align::Center), None);\n            ui.vertical(|ui| {\n                ui.heading(\"Crusader Network Benchmark\");\n                ui.separator();\n\n                SAVED_FILE.lock().unwrap().take().map(|(image, name)| {\n                    if !image {\n                        self.tester.save_raw(Path::new(&name).to_owned());\n                    }\n                });\n\n                LOADED_FILE.lock().unwrap().take().map(|(name, data)| {\n                    RawResult::load_from_reader(Cursor::new(data))\n                        .map(|data| self.tester.load_file(Path::new(&name).to_owned(), data));\n                });\n\n                self.tester.show(ctx, ui);\n            });\n        });\n\n        if ctx.wants_keyboard_input() != self.keyboard_shown {\n            show_keyboard(ctx.wants_keyboard_input()).unwrap();\n            self.keyboard_shown = ctx.wants_keyboard_input();\n        }\n    }\n}\n\nfn show_keyboard(show: bool) -> Result<(), Box<dyn Error>> {\n    let context = ndk_context::android_context();\n\n    let vm = unsafe { jni::JavaVM::from_raw(context.vm().cast())? };\n\n    let activity: JObject = (context.context() as jni::sys::jobject).into();\n\n    let env = vm.attach_current_thread()?;\n\n    env.call_method(activity, \"showKeyboard\", \"(Z)V\", &[show.into()])?\n        .v()?;\n    Ok(())\n}\n\nfn save_file(image: bool, name: String, data: Vec<u8>) -> Result<(), Box<dyn Error>> {\n    let context = ndk_context::android_context();\n    let vm = unsafe { jni::JavaVM::from_raw(context.vm().cast())? };\n    let activity: JObject = (context.context() as jni::sys::jobject).into();\n    let env = vm.attach_current_thread()?;\n    env.call_method(\n        activity,\n        \"saveFile\",\n        \"(ZLjava/lang/String;[B)V\",\n        &[\n            image.into(),\n            env.new_string(name).unwrap().into(),\n            env.byte_array_from_slice(&data).unwrap().into(),\n        ],\n    )?\n    .v()?;\n    Ok(())\n}\n\nstatic SAVED_FILE: Mutex<Option<(bool, String)>> = Mutex::new(None);\n\n#[no_mangle]\npub unsafe extern \"C\" fn Java_zoxc_crusader_MainActivity_fileSaved(\n    env: JNIEnv,\n    _: JClass,\n    image: jboolean,\n    name: JString,\n) {\n    let name: String = env.get_string(name).unwrap().into();\n    *SAVED_FILE.lock().unwrap() = Some((image != 0, name));\n}\n\nfn load_file() -> Result<(), Box<dyn Error>> {\n    let context = ndk_context::android_context();\n    let vm = unsafe { jni::JavaVM::from_raw(context.vm().cast())? };\n    let activity: JObject = (context.context() as jni::sys::jobject).into();\n    let env = vm.attach_current_thread()?;\n    env.call_method(activity, \"loadFile\", \"()V\", &[])?.v()?;\n    Ok(())\n}\n\nstatic LOADED_FILE: Mutex<Option<(String, Vec<u8>)>> = Mutex::new(None);\n\n#[no_mangle]\npub unsafe extern \"C\" fn Java_zoxc_crusader_MainActivity_fileLoaded(\n    env: JNIEnv,\n    _: JClass,\n    name: JString,\n    data: jbyteArray,\n) {\n    let name: String = env.get_string(name).unwrap().into();\n    let data = env.convert_byte_array(data).unwrap();\n    *LOADED_FILE.lock().unwrap() = Some((name, data));\n}\n\n#[cfg(target_os = \"android\")]\n#[no_mangle]\nfn android_main(app: AndroidApp) {\n    android_logger::init_once(android_logger::Config::default().with_min_level(Level::Trace));\n\n    crusader_lib::plot::register_fonts();\n\n    let settings = app\n        .internal_data_path()\n        .map(|path| path.join(\"settings.toml\"));\n\n    let temp_plot = app.internal_data_path().map(|path| path.join(\"plot.png\"));\n\n    let mut options = NativeOptions::default();\n    options.follow_system_theme = false;\n    options.default_theme = Theme::Light;\n    options.renderer = Renderer::Wgpu;\n    options.event_loop_builder = Some(Box::new(move |builder| {\n        builder.with_android_app(app.clone());\n    }));\n    let mut tester = Tester::new(settings);\n    tester.file_loader = Some(Box::new(|_| load_file().unwrap()));\n    tester.plot_saver = Some(Box::new(move |result| {\n        let path = temp_plot.as_deref().unwrap();\n        crusader_lib::plot::save_graph_to_path(path, &PlotConfig::default(), result).unwrap();\n        let data = fs::read(path).unwrap();\n        fs::remove_file(path).unwrap();\n        let name = format!(\"{}.png\", crusader_lib::test::timed(\"plot\"));\n        save_file(true, name, data).unwrap();\n    }));\n    tester.raw_saver = Some(Box::new(|result| {\n        let mut writer = Cursor::new(Vec::new());\n        result.save_to_writer(&mut writer).unwrap();\n        let data = writer.into_inner();\n        let name = format!(\"{}.crr\", crusader_lib::test::timed(\"data\"));\n        save_file(false, name, data).unwrap();\n    }));\n    eframe::run_native(\n        \"Crusader Network Tester\",\n        options,\n        Box::new(|_cc| {\n            Ok(Box::new(App {\n                tester,\n                keyboard_shown: false,\n            }))\n        }),\n    )\n    .unwrap();\n}\n"
  },
  {
    "path": "docker/README.md",
    "content": "To build a statically linked server image:\r\n```\r\ndocker build .. -t crusader -f server-static.Dockerfile\r\n```\r\n\r\nTo build a statically linked remote image:\r\n```\r\ndocker build .. -t crusader -f remote-static.Dockerfile\r\n```\r\nThis image allow initiation of tests using the web application running on port 35482.\r\n\r\nSupported platforms:\r\n- `linux/i386`\r\n- `linux/x86_64`\r\n- `linux/arm/v7`\r\n- `linux/arm64`\r\n\r\nAvailable profiles:\r\n - `--build-arg PROFILE=release` (default)\r\n - `--build-arg PROFILE=speed`\r\n - `--build-arg PROFILE=size`"
  },
  {
    "path": "docker/remote-static.Dockerfile",
    "content": "FROM rust AS build\r\nARG TARGETARCH\r\nARG PROFILE=release\r\n\r\nCOPY src /src\r\nWORKDIR /src\r\n\r\nRUN echo no-target-detected > /target\r\n\r\nRUN if [ \"$TARGETARCH\" = \"386\" ]; then\\\r\n    echo i686-unknown-linux-musl > /target; fi\r\nRUN if [ \"$TARGETARCH\" = \"amd64\" ]; then\\\r\n    echo x86_64-unknown-linux-musl > /target; fi\r\nRUN if [ \"$TARGETARCH\" = \"arm\" ]; then\\\r\n    echo arm-unknown-linux-musleabihf > /target; fi\r\nRUN if [ \"$TARGETARCH\" = \"arm64\" ]; then\\\r\n    echo aarch64-unknown-linux-musl > /target; fi\r\n\r\nENV RUSTFLAGS=\"-C target-feature=+crt-static\"\r\n\r\nRUN rustup target add $(cat /target)\r\n\r\nRUN cargo build -p crusader --profile=$PROFILE --target $(cat /target)\r\n\r\nRUN cp target/$(cat /target)/$PROFILE/crusader /\r\n\r\nFROM scratch\r\nCOPY --from=build /crusader /\r\n\r\nEXPOSE 35482/tcp\r\nENTRYPOINT [ \"/crusader\", \"remote\" ]\r\n"
  },
  {
    "path": "docker/server-static.Dockerfile",
    "content": "FROM rust AS build\r\nARG TARGETARCH\r\nARG PROFILE=release\r\n\r\nCOPY src /src\r\nWORKDIR /src\r\n\r\nRUN echo no-target-detected > /target\r\n\r\nRUN if [ \"$TARGETARCH\" = \"386\" ]; then\\\r\n    echo i686-unknown-linux-musl > /target; fi\r\nRUN if [ \"$TARGETARCH\" = \"amd64\" ]; then\\\r\n    echo x86_64-unknown-linux-musl > /target; fi\r\nRUN if [ \"$TARGETARCH\" = \"arm\" ]; then\\\r\n    echo arm-unknown-linux-musleabihf > /target; fi\r\nRUN if [ \"$TARGETARCH\" = \"arm64\" ]; then\\\r\n    echo aarch64-unknown-linux-musl > /target; fi\r\n\r\nENV RUSTFLAGS=\"-C target-feature=+crt-static\"\r\n\r\nRUN rustup target add $(cat /target)\r\n\r\nRUN cargo build -p crusader --no-default-features --profile=$PROFILE --target $(cat /target)\r\n\r\nRUN cp target/$(cat /target)/$PROFILE/crusader /\r\n\r\nFROM scratch\r\nCOPY --from=build /crusader /\r\n\r\nEXPOSE 35481/tcp 35481/udp 35483/udp\r\nENTRYPOINT [ \"/crusader\", \"serve\" ]\r\n"
  },
  {
    "path": "docs/BUILDING.md",
    "content": "# Building Crusader from source\n\nReminder: [Pre-built binaries](https://github.com/Zoxc/crusader/releases)\nare available for everyday tests.\n\n## Required dependencies\n\n* A Rust and C toolchain.\n* `fontconfig` (optional, required for `crusader-gui`)\n\n_Note:_ To install `fontconfig` on Ubuntu:\n\n```sh\nsudo apt install libfontconfig1-dev\n```\n\n## Building Crusader\n\nTo develop or debug Crusader, use the commands below\nto build the full set of binaries.\nExecutables are placed in _src/target/release_\n\nTo build the `crusader` command line executable:\n\n```sh\ncd src\ncargo build -p crusader --release\n```\n\nTo build both command line and GUI executables:\n\n```sh\ncd src\ncargo build --release\n```\n\n## Debug build\n\nCreate a debug build by using `cargo build`\n(instead of `cargo build --release`).\nBinaries are saved in the _src/target/debug_ directory\n\n## Docker\n\nTo build a docker container that runs the server:\n\n```sh\ncd docker\ndocker build .. -t crusader -f server-static.Dockerfile\n```\n"
  },
  {
    "path": "docs/CLI.md",
    "content": "# Running Crusader from the command line\n\n## Server\n\nTo host a Crusader server, run this on the _server_ machine:\n\n```sh\ncrusader serve\n```\n\n## Client\n\nTo start a test, run this on the _client machine_.\nSee the [command-line options](#options-for-the-test-command) below for details.\n\n```sh\ncrusader test <server-ip>\n```\n\n## Remote\n\nTo host a web server that provides remote control of a Crusader client,\nrun the command below, then connect to\n`http://ip-of-the-crusader-device:35482`\n\n```sh\ncrusader remote\n```\n\n## Plot\n\nCrusader creates a `.png` file from a `.crr` file using `crusader plot path-to-crr-file`\nThe resulting `.png` is saved in the same directory as the input file.\n\n## Export\n\nCrusader exports raw data samples from a `.crr` file\ninto a `.json` file using `crusader export path-to-crr-file`\nThe resulting `.json` is saved in the same directory as the input file.\n\n## Options for the `test` command\n\n**Usage: `crusader test [OPTIONS] <SERVER-ADDRESS>`**\n\n**Arguments:** `<SERVER-ADDRESS>` address of a Crusader server\n\n**Options:**\n\n* **`--download`**\n          Run a download test\n* **`--upload`**\n          Run an upload test\n* **`--bidirectional`**\n          Run a test doing both download and upload\n* **`--idle`**\n          Run a test, only measuring latency without inducing traffic.\n          The duration is specified by `grace_duration`\n* **`--port <PORT>`**\n          Specify the TCP and UDP port used by the server\n          [default: 35481]\n* **`--streams <STREAMS>`**\n          The number of TCP connections used to generate\n           traffic in a single direction\n          [default: 8]\n* **`--stream-stagger <SECONDS>`**\n          The delay between the start of each stream\n          [default: 0.0]\n* **`--load-duration <SECONDS>`**\n          The duration in which traffic is generated\n          [default: 10.0]\n* **`--grace-duration <SECONDS>`**\n          The idle time between each test\n          [default: 2.0]\n* **`--latency-sample-interval <MILLISECONDS>`**\n          [default: 5.0]\n* **`--throughput-sample-interval <MILLISECONDS>`**\n          [default: 60.0]\n* **`--plot-transferred`**\n          Plot transferred bytes\n* **`--plot-split-throughput`**\n          Plot upload and download separately and plot streams\n* **`--plot-max-throughput <BPS>`**\n          Set the axis for throughput to at least this value.\n          SI units are supported so `100M` would specify 100 Mbps\n* **`--plot-max-latency <MILLISECONDS>`**\n          Set the axis for latency to at least this value\n* **`--plot-width <PIXELS>`**\n* **`--plot-height <PIXELS>`**\n* **`--plot-title <PLOT_TITLE>`**\n* **`--latency-peer-address <LATENCY_PEER_ADDRESS>`**\n          Address of another Crusader server (the \"peer\") which\n          concurrently measures the latency to the server and reports\n          the values to the client\n* **`--latency-peer`**\n          Trigger the client to instruct a peer (another Crusader server)\n          to begin measuring the latency to the main server\n          and report the latency back\n* **`--out-name <OUT_NAME>`**\n          The filename prefix used for the raw data and plot filenames\n* **`-h, --help`**\n          Print help (see a summary with '-h')\n"
  },
  {
    "path": "docs/LOCAL_TESTS.md",
    "content": "# Local network testing with Crusader\n\n**Background:**\nThe Crusader Network Tester measures network throughput\nand latency in the presence of upload and download traffic\nand produces plots of the traffic rates, latency and packet loss.\n\n## Making local tests - Wifi or Wired\n\nTo test the equipment between two points on your network,\nCrusader requires two computers,\none acting as a server, the other as a client.\n\nThe Crusader program can act as both client and server.\nInstall the latest pre-built binary of\n[Crusader](https://github.com/Zoxc/crusader/releases)\non two separate computers. Then:\n\n1. Connect one of those computers to your router's LAN port using an\n  Ethernet cable.\n  Start the Crusader program on it, then click the **Server** tab. See the\n  [screen shot](../media/Crusader-Server.png).\n  Click **Start server** and look for the\n  address(es) where the Crusader Server is listening.\n  (Note: The Crusader binary can also run on a small\n  computer such as a Raspberry Pi.\n  A Pi4 acting as a server can easily support 1Gbps speeds.)\n2. Connect the other computer either by Ethernet, Wi-fi,\n  or some other adapter.\n  Run the Crusader program and click the Client tab. See the\n  [screen shot](../media/Crusader-Client.png).\n  Enter the address of a Crusader Server into the GUI,\n  and click **Start test**\n3. When the test completes, you'll see charts of three bursts of traffic:\n  Download, Upload, and Bidirectional,\n  along with the latency during that activity.\n  See the\n  [Crusader README](../README.md) and\n  [Understanding Crusader Results](./RESULTS.md)\n  for details.\n\n**Note:** If both the computers (client and server) are on the LAN\n   side of the router (whether they are on Wi-Fi or Ethernet),\n   the results will reflect the _switching_ capability, not the\n   _routing_ capability of the router.\n   To test the routing capability, test against a server that's\n   on the WAN side of the router or the broader Internet.\n\n**Note:** The Crusader program has both a GUI and a command-line binary.\nBoth act as a client or a server.\nThese instructions tell how to run the client on a laptop.\nYou may find it convenient to run the server on a remote computer.\nGet both binaries from the\n[Releases page](https://github.com/Zoxc/crusader/releases).\n\n## What can we learn?\n\nYour local router, Wi-fi, and other network equipment\nall affect your total performance.\nEven with very high speed internet service,\nif the router is max'ed out it can hold back your throughput.\nAnd you're stuck with whatever latency the router and\nother local equipment create - it's added to any latency from your ISP network.\nIn particular:\n\n* Ethernet-to-Ethernet connections tend to be good:\n  high throughput with low latency.\n  But you should always check your network.\n  On a 1Gbps network, typical results are above 950 Mbps,\n  and less than a dozen milliseconds of latency.\n* Wi-fi has a reputation for \"being slow\".\n  This often is a result of the Wi-fi drivers injecting\n  hundreds of milliseconds of latency.\n  In addition, wireless connections will always have lower\n  throughput than wired connections.\n* Many powerline adapters (that provide an Ethernet connection\n  between two AC outlets) may have high specifications,\n  but in practice are known to give limited throughput\n  and have high latency.\n\nRunning a Crusader test between computers on the local network measures\nthe performance of that portion of the network.\n\n## Why is this important?\n\nThese test results - the actual throughput or latency numbers -\nare not very important.\nIf you are satisfied with you network's performance,\nthen these numerical results don't matter.\n\nBut if you are experiencing problems,\nthese tests help divide the troubleshooting problem in two.\n\n* If the local network is running fine,\n  performance problems must be elsewhere \n  (in your ISP or their upstream service,\n  which likely is out of your control).\n* But if the local performance is not what you expected,\n  you can start investigating your router, switch, etc.\n"
  },
  {
    "path": "docs/RESULTS.md",
    "content": "# Understanding Crusader Results\n\nThe Crusader GUI provides a compact summary of the test data.\nHere are some hints for evaluating the results.\n\n## Result window\n\n![Result with statistics](../media/Crusader-Result-with-stats.png)\n\nCrusader tests the connection using three bursts of traffic.\nThe Throughput, Latency, and Packet loss are shown in the charts.\nIn the image above notice:\n\n* **Hovering over a chart** shows crosshairs that give the throughput\n  or latency of that point in the chart.\n  In the screen shot above, the Down latency\n  peaks around 250 ms.\n* **Hovering over, or clicking the ⓘ symbol** opens a window that displays\n  a summary of the measurements.\n  See the description below for details.\n* **Clicking a legend** (\"color\") in the charts shows/hides\n  the associated graph.\n  In the screen shot above, the Latency's \"Round-trip\" legend has been clicked,\n  hiding the (black) round-trip trace,\n  and showing only the Up and Down latency plots.\n* The **Save to results** button saves two files: a plot (as `.png`)\n  and the data (as `.crr`) to the _crusader-results_ directory\n  in the _current directory_.\n* The **Open from results** button opens a `.crr` file\n  from the _crusader-results_ directory.\n* The **Save** button opens a file dialog to save the current `.crr` file.\n* The **Open** button opens a file dialog to select a `.crr` file to open.\n* The **Export plot** button opens a file dialog to save a `.png` file.\n\n## Numerical Summary Windows\n\nThe Crusader GUI displays charts showing Throughput, Latency,\nand Packet loss.\nThe ⓘ symbol opens a window showing a numerical summary of the charted data.\n\n### Throughput\n\n<img src=\"../media/Crusader-Throughput.png\" alt=\"description\" width=\"250\" />\n\n* Download - Steady state throughput, ignoring any startup transients,\n  during the Download portion of the test\n* Upload - Steady state throughput, ignoring any startup transients,\n  during the Upload portion of the test\n* Bidirectional - Sum of the Download and Upload throughputs\n  during the Bidirectional portion of the test.\n  Also displays the individual Download and Upload throughputs.\n* Streams - number of TCP connections used in each direction\n* Stream Stagger - The delay between the start of each stream\n* Throughput sample interval - Interval between throughput measurements\n\n### Latency\n\n<img src=\"../media/Crusader-Latency.png\" alt=\"description\" width=\"250\" />\n\nCrusader smooths all the latency samples over a 400 ms window.\nThe values shown in the window display the maximum of those smoothed values.\nThis emphasizes sustained peaks of latency.\n\n* Download - Summarizes the round-trip latency during the\n  Download portion of the test.\n  Also displays the measured one-way delay for Download (from server to client)\n  and Upload (client to server)\n* Upload - Summarizes the latency for the Upload portion of the test\n* Bidirectional - Summarizes the latency for the Bidirectional portion of the test\n* Idle latency - Measured latency when no traffic is present.\n* Latency sample interval - Interval between latency measurements\n\n### Packet loss\n\n<img src=\"../media/Crusader-Loss.png\" alt=\"description\" width=\"125\" />\n\nWhen it measures packet loss, Crusader is using the UDP packets\nthat it also uses for latency measurements.\n\n* Download - Summarizes packet loss during the Download portion of the test\n* Upload - Summarizes packet loss during the Upload portion of the test\n* Bidirectional - Summarizes packet loss during the Bidirectional\n  portion of the test\n"
  },
  {
    "path": "docs/TROUBLESHOOTING.md",
    "content": "# Troubleshooting\n\n* Crusader requires that TCP and UDP ports 35481 are open for its tests.\n  Crusader also uses ports 35482 for the remote webserver\n  and port 35483 for discovering other Crusader servers.\n  Check that your firewall is letting those ports through.\n\n* On macOS, the first time you double-click\n  the pre-built `crusader` or `crusader-gui` icon,\n  the OS refuses to let it run.\n  You must use **System Preferences -> Privacy & Security**\n  to approve Crusader to run.\n\n* The message\n  `Warning: Load termination timed out. There may be residual untracked traffic in the background.`\n  is not necessarily harmful.\n  It may happen due to the TCP termination being lost\n  or TCP incompatibilities between OSes.\n  It's likely benign if you see throughput and latency drop\n  to idle values after the tests in the graph.\n\n* The up and down latency measurements rely on symmetric stable latency\n  measurements to the server.\n  These values may be wrong if those assumption don't hold on test startup."
  },
  {
    "path": "media/Crusader Screen Shots.md",
    "content": "# Verify Crusader Screen Shots\n\nThis page is useful for checking the appearance of screen shots.\nCapture the screen shots (Cmd-Shift-5 on macOS) and save with filenames\nlike \"Client.png\", \"Server.png\", etc., one for each of the tabs.\n\nRun the `batch_add_border.sh` script - it finds all these files,\nremoves the drop shadow and adds \"Crusader-\" to each result file.\n\nRemove the original files before committing to git.\n\n## Client\n\n![Options](./Crusader-Client.png)\n\n## Server\n\n![Server](./Crusader-Server.png)\n\n## Remote\n\n![Remote](./Crusader-Remote.png)\n\n## Monitor\n\n![Monitor](./Crusader-Monitor.png)\n\n## Result\n\n![Result](./Crusader-Result.png)\n\n## Result with stats\n\n![Options](./Crusader-Result-with-stats.png)\n\n## Throughput popup\n\n![Throughput](./Crusader-Throughput.png)\n<img src=\"./Crusader-Throughput.png\" alt=\"description\" width=\"250\" />\n\n## Latency popup\n\n![Latency](./Crusader-Latency.png)\n<img src=\"./Crusader-Latency.png\" alt=\"description\" width=\"250\" />\n\n## Packet loss popup\n\n![Packet Loss](./Crusader-Loss.png)\n<img src=\"./Crusader-Loss.png\" alt=\"description\" width=\"125\" />\n"
  },
  {
    "path": "media/batch_add_border.sh",
    "content": "#!/bin/bash\n\n# add_border.sh - This script uses ImageMagick to process\n# macOS screen shots for publication\n\n# Usage: \n#  1. Take screen shots (Cmd-Shift-5) on macOS\n#  2. Save the file with a name that would be a good suffix (e.g. Remote.png)\n#  3. Run this script. The script outputs modified files prefixed with \"Crusader-\"\n#  4. Discard the original files\n\n# The script does the following - for the .png files in the directory:\n# - find all .png files that don't begin with \"Crusader\"\n# - remove the transparent area/drop shadow from a macOS screen shot\n# - shrink the image to the size of the image\n# - draw a grey border 1 px wide around the window.\n# - save the resulting file as \"Crusader-....png\"\n# Thanks, ChatGPT\n\n# Get the directory where the script is located\nscript_dir=\"$(dirname \"$0\")\"\n\n# Define the border size (1 pixel) and colors\nborder_size=1\nborder_color=\"gray\"\nbackground_color=\"white\"\n\n# Loop through all .png files \nfor input_file in \"$script_dir\"/[a-zA-Z]*.png; do\n  # Check if the file actually exists (in case no files match)\n  if [ ! -f \"$input_file\" ]; then\n    continue\n  fi\n\n  # Ignore files that already start with \"Crusader\"\n  if [[ $(basename \"$input_file\") == Crusader* ]]; then\n    echo \"Skipping: $input_file\"\n    continue\n  fi\n\n  # Output file name (prepend \"Crusader-\" to the original file name)\n  output_file=\"$script_dir/Crusader-$(basename \"$input_file\")\"\n\n  # Process the image:\n  # 1. Remove the alpha channel (transparency) by filling with white\n  # 2. Trim the image to its non-transparent content\n  # 3. Add a 1-pixel grey border\n  magick \"$input_file\" \\\n    -alpha off \\\n    -trim \\\n    -bordercolor $border_color \\\n    -border ${border_size}x${border_size} \\\n    \"$output_file\"\n\n  echo \"Processed: $input_file -> $output_file\"\ndone\n\necho \"All matching .png files processed.\"\n"
  },
  {
    "path": "src/Cargo.toml",
    "content": "[workspace]\r\nmembers = [\"crusader\", \"crusader-lib\", \"crusader-gui-lib\", \"crusader-gui\"]\r\nresolver = \"1\"\r\n\r\n[profile.dev]\r\npanic = \"abort\"\r\n\r\n[profile.release]\r\npanic = \"abort\"\r\nstrip = \"symbols\"\r\n\r\n[profile.speed]\r\nopt-level = 3\r\ncodegen-units = 1\r\ninherits = \"release\"\r\nlto = \"fat\"\r\n\r\n[profile.size]\r\ninherits = \"speed\"\r\nopt-level = \"z\"\r\n\r\n[patch.crates-io]\r\negui_plot = { git = \"https://github.com/Zoxc/egui_plot\", branch = \"crusader\" }\r\n"
  },
  {
    "path": "src/crusader/Cargo.toml",
    "content": "[package]\nname = \"crusader\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\ncrusader-lib = { path = \"../crusader-lib\" }\nclap = { version = \"4.5.13\", features = [\"derive\", \"string\"] }\nclap-num = \"1.1.1\"\nenv_logger = \"0.10.0\"\nanyhow = \"1.0.86\"\nserde_json = { version = \"1.0.122\", optional = true }\n\n[features]\ndefault = [\"client\"]\nclient = [\"crusader-lib/client\", \"dep:serde_json\"]\n"
  },
  {
    "path": "src/crusader/src/main.rs",
    "content": "use anyhow::Context;\nuse clap::{Parser, Subcommand};\nuse clap_num::si_number;\n#[cfg(feature = \"client\")]\nuse crusader_lib::file_format::RawResult;\n#[cfg(feature = \"client\")]\nuse crusader_lib::test::PlotConfig;\nuse crusader_lib::{protocol, version};\n#[cfg(feature = \"client\")]\nuse crusader_lib::{with_time, Config};\n#[cfg(feature = \"client\")]\nuse std::path::PathBuf;\nuse std::process;\n#[cfg(feature = \"client\")]\nuse {\n    anyhow::anyhow,\n    std::fs::OpenOptions,\n    std::io::{BufWriter, Write},\n    std::path::Path,\n    std::time::Duration,\n};\n\n#[derive(Parser)]\n#[command(version = version())]\nstruct Cli {\n    #[command(subcommand)]\n    command: Commands,\n}\n\n#[derive(clap::Args)]\nstruct PlotArgs {\n    #[arg(long, help = \"Plot transferred bytes\")]\n    plot_transferred: bool,\n    #[arg(long, help = \"Plot upload and download separately and plot streams\")]\n    plot_split_throughput: bool,\n    #[arg(long, value_parser=si_number::<u64>, value_name = \"BPS\",\n        long_help = \"Sets the axis for throughput to at least this value. \\\n            SI units are supported so `100M` would specify 100 Mbps\")]\n    plot_max_throughput: Option<u64>,\n    #[arg(\n        long,\n        value_name = \"MILLISECONDS\",\n        help = \"Sets the axis for latency to at least this value\"\n    )]\n    plot_max_latency: Option<u64>,\n    #[arg(long, value_name = \"PIXELS\")]\n    plot_width: Option<u64>,\n    #[arg(long, value_name = \"PIXELS\")]\n    plot_height: Option<u64>,\n    #[arg(long)]\n    plot_title: Option<String>,\n}\n\nimpl PlotArgs {\n    #[cfg(feature = \"client\")]\n    fn config(&self) -> PlotConfig {\n        PlotConfig {\n            transferred: self.plot_transferred,\n            split_throughput: self.plot_split_throughput,\n            max_throughput: self.plot_max_throughput,\n            max_latency: self.plot_max_latency,\n            width: self.plot_width,\n            height: self.plot_height,\n            title: self.plot_title.clone(),\n        }\n    }\n}\n\n#[derive(Subcommand)]\nenum Commands {\n    #[command(about = \"Runs the server\")]\n    Serve {\n        #[arg(long, default_value_t = protocol::PORT, help = \"Specifies the TCP and UDP port used by the server\")]\n        port: u16,\n        #[arg(long, help = \"Allow use and discovery as a peer\")]\n        peer: bool,\n    },\n    #[command(\n        long_about = \"Runs a test client against a specified server and saves the result to the current directory. \\\n        By default this does a download test, an upload test, and a test doing both download and upload while measuring the latency to the server\"\n    )]\n    #[cfg(feature = \"client\")]\n    Test {\n        server: Option<String>,\n        #[arg(long, help = \"Run a download test\")]\n        download: bool,\n        #[arg(long, help = \"Run an upload test\")]\n        upload: bool,\n        #[arg(long, help = \"Run a test doing both download and upload\")]\n        bidirectional: bool,\n        #[arg(\n            long,\n            long_help = \"Run a test only measuring latency. The duration is specified by `grace_duration`\"\n        )]\n        idle: bool,\n        #[arg(long, default_value_t = protocol::PORT, help = \"Specifies the TCP and UDP port used by the server\")]\n        port: u16,\n        #[arg(\n            long,\n            default_value_t = 8,\n            help = \"The number of TCP connections used to generate traffic in a single direction\"\n        )]\n        streams: u64,\n        #[arg(\n            long,\n            default_value_t = 0.0,\n            value_name = \"SECONDS\",\n            help = \"The delay between the start of each stream\"\n        )]\n        stream_stagger: f64,\n        #[arg(\n            long,\n            default_value_t = 10.0,\n            value_name = \"SECONDS\",\n            help = \"The duration in which traffic is generated\"\n        )]\n        load_duration: f64,\n        #[arg(\n            long,\n            default_value_t = 2.0,\n            value_name = \"SECONDS\",\n            help = \"The idle time between each test\"\n        )]\n        grace_duration: f64,\n        #[arg(long, default_value_t = 5, value_name = \"MILLISECONDS\")]\n        latency_sample_interval: u64,\n        #[arg(long, default_value_t = 60, value_name = \"MILLISECONDS\")]\n        throughput_sample_interval: u64,\n        #[command(flatten)]\n        plot: PlotArgs,\n        #[arg(\n            long,\n            long_help = \"Specifies another server (peer) which will also measure the latency to the server independently of the client\"\n        )]\n        latency_peer_address: Option<String>,\n        #[arg(\n            long,\n            help = \"Use another server (peer) which will also measure the latency to the server independently of the client\"\n        )]\n        latency_peer: bool,\n        #[arg(\n            long,\n            help = \"The filename prefix used for the test result raw data and plot filenames\"\n        )]\n        out_name: Option<String>,\n    },\n    #[cfg(feature = \"client\")]\n    #[command(about = \"Plots a previous result\")]\n    Plot {\n        data: PathBuf,\n        #[command(flatten)]\n        plot: PlotArgs,\n    },\n    #[cfg(feature = \"client\")]\n    #[command(about = \"Allows the client to be controlled over a web server\")]\n    Remote {\n        #[arg(\n            long,\n            default_value_t = protocol::PORT + 1,\n            help = \"Specifies the HTTP port used by the server\"\n        )]\n        port: u16,\n    },\n    #[cfg(feature = \"client\")]\n    #[command(about = \"Converts a result file to JSON\")]\n    Export {\n        data: PathBuf,\n        #[arg(\n            long,\n            short('o'),\n            help = \"The path where the output JSON will be stored\"\n        )]\n        output: Option<PathBuf>,\n        #[arg(long, short('f'), help = \"Overwrite the file if it exists\")]\n        force: bool,\n    },\n}\n\nfn run() -> Result<(), anyhow::Error> {\n    let cli = Cli::parse();\n\n    match &cli.command {\n        #[cfg(feature = \"client\")]\n        &Commands::Test {\n            ref server,\n            download,\n            upload,\n            bidirectional,\n            idle,\n            throughput_sample_interval,\n            latency_sample_interval,\n            ref plot,\n            port,\n            streams,\n            stream_stagger,\n            grace_duration,\n            load_duration,\n            ref latency_peer_address,\n            latency_peer,\n            ref out_name,\n        } => {\n            let mut config = Config {\n                port,\n                streams,\n                stream_stagger: Duration::from_secs_f64(stream_stagger),\n                grace_duration: Duration::from_secs_f64(grace_duration),\n                load_duration: Duration::from_secs_f64(load_duration),\n                download: !idle,\n                upload: !idle,\n                bidirectional: !idle,\n                ping_interval: Duration::from_millis(latency_sample_interval),\n                throughput_interval: Duration::from_millis(throughput_sample_interval),\n            };\n\n            if download || upload || bidirectional {\n                if idle {\n                    println!(\"Cannot run `idle` test with a load test\");\n                    process::exit(1);\n                }\n                config.download = download;\n                config.upload = upload;\n                config.bidirectional = bidirectional;\n            }\n\n            crusader_lib::test::test(\n                config,\n                plot.config(),\n                server.as_deref(),\n                (latency_peer || latency_peer_address.is_some())\n                    .then_some(latency_peer_address.as_deref()),\n                out_name.as_deref().unwrap_or(\"test\"),\n            )\n        }\n        &Commands::Serve { port, peer } => crusader_lib::serve::serve(port, peer),\n\n        #[cfg(feature = \"client\")]\n        Commands::Remote { port } => crusader_lib::remote::run(*port),\n\n        #[cfg(feature = \"client\")]\n        Commands::Plot { data, plot } => {\n            let result = RawResult::load(data).ok_or(anyhow!(\"Unable to load data\"))?;\n            let root = data.parent().unwrap_or(Path::new(\"\"));\n            let file = crusader_lib::plot::save_graph(\n                &plot.config(),\n                &result.to_test_result(),\n                data.file_stem()\n                    .and_then(|name| name.to_str())\n                    .unwrap_or(\"plot\"),\n                data.parent().unwrap_or(Path::new(\"\")),\n            )?;\n            println!(\n                \"{}\",\n                with_time(&format!(\"Saved plot as {}\", root.join(file).display()))\n            );\n            Ok(())\n        }\n        #[cfg(feature = \"client\")]\n        Commands::Export {\n            data,\n            output,\n            force,\n        } => {\n            let result = RawResult::load(data).ok_or(anyhow!(\"Unable to load data\"))?;\n            let output = output\n                .clone()\n                .unwrap_or_else(|| data.with_extension(\"json\"));\n            let file = OpenOptions::new()\n                .create_new(!*force)\n                .create(*force)\n                .truncate(true)\n                .write(true)\n                .open(output)\n                .context(\"Failed to create output file\")?;\n            let mut file = BufWriter::new(file);\n            serde_json::to_writer_pretty(&mut file, &result).context(\"Failed to serialize data\")?;\n            file.flush().context(\"Failed to flush output\")?;\n\n            Ok(())\n        }\n    }\n}\n\nfn main() {\n    env_logger::init();\n\n    #[cfg(feature = \"client\")]\n    crusader_lib::plot::register_fonts();\n\n    if let Err(error) = run() {\n        println!(\"Error: {:?}\", error);\n        process::exit(1);\n    }\n}\n"
  },
  {
    "path": "src/crusader-gui/Cargo.toml",
    "content": "[package]\nname = \"crusader-gui\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ncrusader-lib = { path = \"../crusader-lib\" }\ncrusader-gui-lib = { path = \"../crusader-gui-lib\" }\nenv_logger = \"0.10.0\"\neframe = \"0.28.1\"\nfont-kit = \"0.14.2\"\n"
  },
  {
    "path": "src/crusader-gui/src/main.rs",
    "content": "#![allow(\r\n    clippy::field_reassign_with_default,\r\n    clippy::option_map_unit_fn,\r\n    clippy::type_complexity\r\n)]\r\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")] // hide console window on Windows in release\r\n\r\nuse std::{error::Error, process, sync::Arc};\r\n\r\nuse crusader_gui_lib::Tester;\r\nuse crusader_lib::version;\r\nuse eframe::{\r\n    egui::{self, Context, FontData, FontDefinitions, FontFamily},\r\n    emath::vec2,\r\n    Theme,\r\n};\r\n#[allow(unused_imports)]\r\nuse font_kit::family_name::FamilyName;\r\nuse font_kit::{handle::Handle, properties::Properties, source::SystemSource};\r\n\r\nfn main() {\r\n    env_logger::init();\r\n\r\n    let mut options = eframe::NativeOptions::default();\r\n    options.follow_system_theme = false;\r\n    options.default_theme = Theme::Light;\r\n\r\n    crusader_lib::plot::register_fonts();\r\n\r\n    let settings = std::env::current_exe()\r\n        .ok()\r\n        .map(|exe| exe.with_extension(\"toml\"));\r\n\r\n    let result = eframe::run_native(\r\n        &format!(\"Crusader Network Tester {}\", version()),\r\n        options,\r\n        Box::new(move |cc| {\r\n            let ctx = &cc.egui_ctx;\r\n            let mut style = ctx.style();\r\n            let style_ = Arc::make_mut(&mut style);\r\n\r\n            style_.spacing.button_padding = vec2(6.0, 0.0);\r\n            style_.spacing.interact_size.y = 30.0;\r\n            style_.spacing.item_spacing = vec2(5.0, 5.0);\r\n\r\n            let font_size = if cfg!(target_os = \"macos\") {\r\n                13.5\r\n            } else {\r\n                12.5\r\n            };\r\n\r\n            style_.text_styles.get_mut(&egui::TextStyle::Body).map(|v| {\r\n                v.size = font_size;\r\n            });\r\n            style_\r\n                .text_styles\r\n                .get_mut(&egui::TextStyle::Button)\r\n                .map(|v| {\r\n                    v.size = font_size;\r\n                });\r\n            style_\r\n                .text_styles\r\n                .get_mut(&egui::TextStyle::Small)\r\n                .map(|v| {\r\n                    v.size = font_size;\r\n                });\r\n            style_\r\n                .text_styles\r\n                .get_mut(&egui::TextStyle::Monospace)\r\n                .map(|v| {\r\n                    v.size = font_size;\r\n                });\r\n            ctx.set_style(style);\r\n\r\n            load_system_font(ctx).ok();\r\n\r\n            Ok(Box::new(App {\r\n                tester: Tester::new(settings),\r\n            }))\r\n        }),\r\n    );\r\n    if let Err(e) = result {\r\n        eprintln!(\"Failed to run GUI: {}\", e);\r\n        process::exit(1);\r\n    }\r\n}\r\n\r\nfn load_system_font(ctx: &Context) -> Result<(), Box<dyn Error>> {\r\n    let mut fonts = FontDefinitions::default();\r\n\r\n    let handle = SystemSource::new().select_best_match(\r\n        &[\r\n            #[cfg(target_os = \"macos\")]\r\n            FamilyName::SansSerif,\r\n            #[cfg(windows)]\r\n            FamilyName::Title(\"Segoe UI\".to_string()),\r\n        ],\r\n        &Properties::new(),\r\n    )?;\r\n\r\n    let buf: Vec<u8> = match handle {\r\n        Handle::Memory { bytes, .. } => bytes.to_vec(),\r\n        Handle::Path { path, .. } => std::fs::read(path)?,\r\n    };\r\n\r\n    const UI_FONT: &str = \"System Sans Serif\";\r\n\r\n    fonts\r\n        .font_data\r\n        .insert(UI_FONT.to_owned(), FontData::from_owned(buf));\r\n\r\n    if let Some(vec) = fonts.families.get_mut(&FontFamily::Proportional) {\r\n        vec.insert(0, UI_FONT.to_owned());\r\n    }\r\n\r\n    if let Some(vec) = fonts.families.get_mut(&FontFamily::Monospace) {\r\n        vec.insert(0, UI_FONT.to_owned());\r\n    }\r\n\r\n    ctx.set_fonts(fonts);\r\n\r\n    Ok(())\r\n}\r\n\r\nstruct App {\r\n    tester: Tester,\r\n}\r\n\r\nimpl eframe::App for App {\r\n    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\r\n        egui::CentralPanel::default().show(ctx, |ui| {\r\n            self.tester.show(ctx, ui);\r\n        });\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/crusader-gui-lib/Cargo.toml",
    "content": "[package]\nname = \"crusader-gui-lib\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ntoml = \"0.5.9\"\nserde = { version = \"1.0.137\", features = [\"derive\"] }\ncrusader-lib = { path = \"../crusader-lib\", features = [\"server\", \"client\"] }\ntokio = { version = \"1.18.2\", features = [\"full\"] }\neframe = \"0.28.1\"\negui_plot = \"0.28.1\"\negui_extras = { version = \"0.28.1\", default-features = false }\nopen = \"5.3.0\"\n\n[target.'cfg(not(target_os = \"android\"))'.dependencies]\nrfd = { version = \"0.10.0\", default-features = false, features = [\n    \"xdg-portal\",\n] }\n"
  },
  {
    "path": "src/crusader-gui-lib/src/client.rs",
    "content": "use crate::{Tab, Tester};\nuse crusader_lib::{\n    file_format::RawResult,\n    protocol,\n    test::{self},\n    with_time, Config,\n};\nuse eframe::{\n    egui::{self, vec2, Grid, ScrollArea, TextEdit, Ui},\n    emath::Align,\n};\nuse serde::{Deserialize, Serialize};\nuse std::{mem, sync::Arc, time::Duration};\nuse tokio::sync::{\n    mpsc::{self},\n    oneshot,\n};\n\n#[derive(Serialize, Deserialize, Clone, PartialEq)]\n#[serde(default)]\npub struct ClientSettings {\n    pub server: String,\n    pub download: bool,\n    pub upload: bool,\n    pub bidirectional: bool,\n    pub streams: u64,\n    pub load_duration: f64,\n    pub grace_duration: f64,\n    pub stream_stagger: f64,\n    pub latency_sample_interval: u64,\n    pub throughput_sample_interval: u64,\n    pub latency_peer: bool,\n    pub latency_peer_server: String,\n    pub advanced: bool,\n    pub idle_test: bool,\n    pub idle_duration: f64,\n}\n\nimpl ClientSettings {\n    fn config(&self) -> Config {\n        Config {\n            port: protocol::PORT,\n            streams: self.streams,\n            grace_duration: Duration::from_secs_f64(self.grace_duration),\n            load_duration: Duration::from_secs_f64(self.load_duration),\n            stream_stagger: Duration::from_secs_f64(self.stream_stagger),\n            download: self.download,\n            upload: self.upload,\n            bidirectional: self.bidirectional,\n            ping_interval: Duration::from_millis(self.latency_sample_interval),\n            throughput_interval: Duration::from_millis(self.throughput_sample_interval),\n        }\n    }\n}\n\nimpl Default for ClientSettings {\n    fn default() -> Self {\n        Self {\n            server: String::new(),\n            download: true,\n            upload: true,\n            bidirectional: true,\n            streams: 8,\n            load_duration: 10.0,\n            grace_duration: 2.0,\n            stream_stagger: 0.0,\n            latency_sample_interval: 5,\n            throughput_sample_interval: 60,\n            latency_peer: false,\n            latency_peer_server: String::new(),\n            advanced: false,\n            idle_test: false,\n            idle_duration: 10.0,\n        }\n    }\n}\n\npub struct Client {\n    rx: mpsc::UnboundedReceiver<String>,\n    pub done: Option<oneshot::Receiver<Option<Result<RawResult, String>>>>,\n    pub abort: Option<oneshot::Sender<()>>,\n}\n\n#[derive(PartialEq, Eq)]\npub enum ClientState {\n    Stopped,\n    Stopping,\n    Running,\n}\n\nimpl Tester {\n    fn start_client(&mut self, ctx: &egui::Context) {\n        self.save_settings();\n        self.msgs.clear();\n        self.msg_scrolled = 0;\n\n        let (signal_done, done) = oneshot::channel();\n        let (tx, rx) = mpsc::unbounded_channel();\n\n        let ctx = ctx.clone();\n        let ctx_ = ctx.clone();\n\n        let config = if self.settings.client.idle_test {\n            let mut config = ClientSettings::default().config();\n            config.grace_duration = Duration::from_secs_f64(self.settings.client.idle_duration);\n            config.ping_interval =\n                Duration::from_millis(self.settings.client.latency_sample_interval);\n            config.bidirectional = false;\n            config.download = false;\n            config.upload = false;\n            config\n        } else {\n            self.settings.client.config()\n        };\n\n        let abort = test::test_callback(\n            config,\n            (!self.settings.client.server.trim().is_empty())\n                .then_some(&self.settings.client.server),\n            self.settings.client.latency_peer.then_some(\n                (!self.settings.client.latency_peer_server.trim().is_empty())\n                    .then_some(&self.settings.client.latency_peer_server),\n            ),\n            Arc::new(move |msg| {\n                tx.send(with_time(msg)).unwrap();\n                ctx.request_repaint();\n            }),\n            Box::new(move |result| {\n                signal_done.send(result).map_err(|_| ()).unwrap();\n                ctx_.request_repaint();\n            }),\n        );\n\n        self.client = Some(Client {\n            done: Some(done),\n            rx,\n            abort: Some(abort),\n        });\n        self.client_state = ClientState::Running;\n    }\n\n    fn idle_settings(&mut self, ui: &mut Ui) {\n        Grid::new(\"idle-settings\").show(ui, |ui| {\n            ui.label(\"Duration: \");\n            ui.add(\n                egui::DragValue::new(&mut self.settings.client.idle_duration)\n                    .range(0..=1000)\n                    .speed(0.05),\n            );\n            ui.label(\"seconds\");\n            ui.end_row();\n            if self.settings.client.advanced {\n                ui.label(\"Latency sample interval:\");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.latency_sample_interval)\n                        .range(1..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"milliseconds\");\n                ui.end_row();\n            }\n        });\n\n        if self.settings.client.advanced {\n            ui.separator();\n\n            ui.horizontal_wrapped(|ui| {\n                ui.checkbox(&mut self.settings.client.latency_peer, \"Latency peer:\").on_hover_text(\"Specifies another server (peer) which will also measure the latency to the server independently of the client\");\n                ui.add_enabled_ui(self.settings.client.latency_peer, |ui| {\n                    ui.add(\n                        TextEdit::singleline(&mut self.settings.client.latency_peer_server)\n                            .hint_text(\"(Locate local peer)\"),\n                    );\n                });\n            });\n        }\n\n        ui.separator();\n\n        if !self.settings.client.advanced {\n            let mut any = false;\n            let config = self.settings.client.clone();\n            let default = ClientSettings::default();\n\n            if config.latency_sample_interval != default.latency_sample_interval {\n                any = true;\n                ui.label(format!(\n                    \"Latency sample interval: {:.2} milliseconds\",\n                    config.latency_sample_interval\n                ));\n            }\n\n            if config.latency_peer != default.latency_peer {\n                any = true;\n                let server = (!config.latency_peer_server.trim().is_empty())\n                    .then_some(&*config.latency_peer_server);\n                ui.label(format!(\"Latency peer: {}\", server.unwrap_or(\"<Discover>\")));\n            }\n\n            if any {\n                ui.separator();\n            }\n        }\n    }\n\n    fn latency_under_load_settings(&mut self, ui: &mut Ui, compact: bool) {\n        if !self.settings.client.advanced || compact {\n            ui.horizontal_wrapped(|ui| {\n                ui.checkbox(&mut self.settings.client.download, \"Download\")\n                    .on_hover_text(\"Run a download test\");\n                ui.add_space(10.0);\n                ui.checkbox(&mut self.settings.client.upload, \"Upload\")\n                    .on_hover_text(\"Run an upload test\");\n                ui.add_space(10.0);\n                ui.checkbox(&mut self.settings.client.bidirectional, \"Bidirectional\")\n                    .on_hover_text(\"Run a test doing both download and upload\");\n            });\n            Grid::new(\"settings-compact\").show(ui, |ui| {\n                ui.label(\"Streams: \").on_hover_text(\n                    \"The number of TCP connections used to generate traffic in a single direction\",\n                );\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.streams)\n                        .range(1..=1000)\n                        .speed(0.05),\n                );\n                ui.end_row();\n                ui.label(\"Load duration: \")\n                    .on_hover_text(\"The duration in which traffic is generated\");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.load_duration)\n                        .range(0..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"seconds\");\n                ui.end_row();\n                if self.settings.client.advanced {\n                    ui.label(\"Grace duration: \")\n                        .on_hover_text(\"The idle time between each test\");\n                    ui.add(\n                        egui::DragValue::new(&mut self.settings.client.grace_duration)\n                            .range(0..=1000)\n                            .speed(0.05),\n                    );\n                    ui.label(\"seconds\");\n                    ui.end_row();\n                    ui.label(\"Stream stagger: \")\n                        .on_hover_text(\"The delay between the start of each stream\");\n                    ui.add(\n                        egui::DragValue::new(&mut self.settings.client.stream_stagger)\n                            .range(0..=1000)\n                            .speed(0.05),\n                    );\n                    ui.label(\"seconds\");\n                    ui.end_row();\n                    ui.label(\"Latency sample interval:\");\n                    ui.add(\n                        egui::DragValue::new(&mut self.settings.client.latency_sample_interval)\n                            .range(1..=1000)\n                            .speed(0.05),\n                    );\n                    ui.label(\"milliseconds\");\n                    ui.end_row();\n                    ui.label(\"Throughput sample interval:\");\n                    ui.add(\n                        egui::DragValue::new(&mut self.settings.client.throughput_sample_interval)\n                            .range(1..=1000)\n                            .speed(0.05),\n                    );\n                    ui.label(\"milliseconds\");\n                    ui.end_row();\n                }\n            });\n        } else {\n            Grid::new(\"settings\").show(ui, |ui| {\n                ui.checkbox(&mut self.settings.client.download, \"Download\")\n                    .on_hover_text(\"Run a download test\");\n                ui.allocate_space(vec2(1.0, 1.0));\n                ui.label(\"Streams: \").on_hover_text(\n                    \"The number of TCP connections used to generate traffic in a single direction\",\n                );\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.streams)\n                        .range(1..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"\");\n                ui.allocate_space(vec2(1.0, 1.0));\n\n                ui.label(\"Stream stagger: \")\n                    .on_hover_text(\"The delay between the start of each stream\");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.stream_stagger)\n                        .range(0..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"seconds\");\n                ui.end_row();\n\n                ui.checkbox(&mut self.settings.client.upload, \"Upload\")\n                    .on_hover_text(\"Run an upload test\");\n                ui.label(\"\");\n                ui.label(\"Load duration: \")\n                    .on_hover_text(\"The duration in which traffic is generated\");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.load_duration)\n                        .range(0..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"seconds\");\n                ui.label(\"\");\n\n                ui.label(\"Latency sample interval: \");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.latency_sample_interval)\n                        .range(1..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"milliseconds\");\n                ui.end_row();\n\n                ui.checkbox(&mut self.settings.client.bidirectional, \"Bidirectional\")\n                    .on_hover_text(\"Run a test doing both download and upload\");\n                ui.label(\"\");\n                ui.label(\"Grace duration: \")\n                    .on_hover_text(\"The idle time between each test\");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.grace_duration)\n                        .range(0..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"seconds\");\n                ui.label(\"\");\n                ui.label(\"Throughput sample interval: \");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.client.throughput_sample_interval)\n                        .range(1..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"milliseconds\");\n                ui.end_row();\n            });\n        }\n\n        if self.settings.client.advanced {\n            ui.separator();\n\n            ui.horizontal_wrapped(|ui| {\n                ui.checkbox(&mut self.settings.client.latency_peer, \"Latency peer:\").on_hover_text(\"Specifies another server (peer) which will also measure the latency to the server independently of the client\");\n                ui.add_enabled_ui(self.settings.client.latency_peer, |ui| {\n                    ui.add(\n                        TextEdit::singleline(&mut self.settings.client.latency_peer_server)\n                            .hint_text(\"(Locate local peer)\"),\n                    );\n                });\n            });\n        }\n\n        ui.separator();\n\n        if !self.settings.client.advanced {\n            let mut any = false;\n            let config = self.settings.client.clone();\n            let default = ClientSettings::default();\n\n            if config.grace_duration != default.grace_duration {\n                any = true;\n                ui.label(format!(\n                    \"Grace duration: {:.2} seconds\",\n                    config.grace_duration\n                ));\n            }\n\n            if config.stream_stagger != default.stream_stagger {\n                any = true;\n                ui.label(format!(\n                    \"Stream stagger: {:.2} seconds\",\n                    config.stream_stagger\n                ));\n            }\n\n            if config.latency_sample_interval != default.latency_sample_interval {\n                any = true;\n                ui.label(format!(\n                    \"Latency sample interval: {:.2} milliseconds\",\n                    config.latency_sample_interval\n                ));\n            }\n\n            if config.throughput_sample_interval != default.throughput_sample_interval {\n                any = true;\n                ui.label(format!(\n                    \"Throughput sample interval: {:.2} milliseconds\",\n                    config.throughput_sample_interval\n                ));\n            }\n\n            if config.latency_peer != default.latency_peer {\n                any = true;\n                let server = (!config.latency_peer_server.trim().is_empty())\n                    .then_some(&*config.latency_peer_server);\n                ui.label(format!(\"Latency peer: {}\", server.unwrap_or(\"<Discover>\")));\n            }\n\n            if any {\n                ui.separator();\n            }\n        }\n    }\n\n    pub fn client(&mut self, ctx: &egui::Context, ui: &mut Ui, compact: bool) {\n        let active = self.client_state == ClientState::Stopped;\n\n        ui.horizontal_wrapped(|ui| {\n            ui.add_enabled_ui(active, |ui| {\n                ui.label(\"Server address:\");\n                let response = ui.add(\n                    TextEdit::singleline(&mut self.settings.client.server)\n                        .hint_text(\"(Locate local server)\"),\n                );\n                if self.client_state == ClientState::Stopped\n                    && response.lost_focus()\n                    && ui.input(|i| i.key_pressed(egui::Key::Enter))\n                {\n                    self.start_client(ctx)\n                }\n            });\n\n            match self.client_state {\n                ClientState::Running => {\n                    if ui.button(\"Stop test\").clicked() {\n                        let client = self.client.as_mut().unwrap();\n                        mem::take(&mut client.abort).unwrap().send(()).unwrap();\n                        self.client_state = ClientState::Stopping;\n                    }\n                }\n                ClientState::Stopping => {\n                    ui.add_enabled_ui(false, |ui| {\n                        let _ = ui.button(\"Stopping test..\");\n                    });\n                }\n                ClientState::Stopped => {\n                    if ui.button(\"Start test\").clicked() {\n                        self.start_client(ctx)\n                    }\n                }\n            }\n        });\n\n        ui.separator();\n\n        ScrollArea::vertical()\n            .auto_shrink([false; 2])\n            .show(ui, |ui| {\n                ui.add_enabled_ui(active, |ui| {\n                    ui.horizontal(|ui| {\n                        ui.label(\"Measure:\");\n                        ui.selectable_value(\n                            &mut self.settings.client.idle_test,\n                            false,\n                            \"Latency under load\",\n                        );\n                        ui.selectable_value(\n                            &mut self.settings.client.idle_test,\n                            true,\n                            \"Latency only\",\n                        );\n                    });\n\n                    ui.separator();\n\n                    if self.settings.client.idle_test {\n                        self.idle_settings(ui);\n                    } else {\n                        self.latency_under_load_settings(ui, compact);\n                    }\n\n                    ui.horizontal(|ui| {\n                        let mut default = ClientSettings::default();\n                        default.idle_test = self.settings.client.idle_test;\n                        default.advanced = self.settings.client.advanced;\n                        default.server = self.settings.client.server.clone();\n                        default.latency_peer_server =\n                            self.settings.client.latency_peer_server.clone();\n\n                        let parameters_changed = self.settings.client != default;\n\n                        ui.add_enabled_ui(parameters_changed, |ui| {\n                            if ui.button(\"Reset settings\").clicked() {\n                                self.settings.client = default;\n                            }\n                        });\n\n                        ui.toggle_value(&mut self.settings.client.advanced, \"Advanced mode\");\n                    })\n                });\n\n                if self.client_state == ClientState::Running\n                    || self.client_state == ClientState::Stopping\n                {\n                    let client = self.client.as_mut().unwrap();\n\n                    while let Ok(msg) = client.rx.try_recv() {\n                        println!(\"[Client] {msg}\");\n                        self.msgs.push(msg);\n                    }\n\n                    if let Ok(result) = client.done.as_mut().unwrap().try_recv() {\n                        match result {\n                            Some(Ok(result)) => {\n                                self.msgs.push(with_time(\"Test complete\"));\n                                let result = result.to_test_result();\n                                self.set_result(result);\n                                if self.tab == Tab::Client {\n                                    self.tab = Tab::Result;\n                                }\n                            }\n                            Some(Err(error)) => {\n                                self.msgs.push(with_time(&format!(\"Error: {error}\")));\n                            }\n                            None => {\n                                self.msgs.push(with_time(\"Aborted...\"));\n                            }\n                        }\n                        self.client = None;\n                        self.client_state = ClientState::Stopped;\n                    }\n                }\n\n                if !self.msgs.is_empty() {\n                    ui.separator();\n                }\n\n                for (i, msg) in self.msgs.iter().enumerate() {\n                    let response = ui.label(msg);\n                    if self.msg_scrolled <= i {\n                        self.msg_scrolled = i + 1;\n                        response.scroll_to_me(Some(Align::Max));\n                    }\n                }\n            });\n    }\n}\n"
  },
  {
    "path": "src/crusader-gui-lib/src/lib.rs",
    "content": "#![allow(\n    clippy::field_reassign_with_default,\n    clippy::option_map_unit_fn,\n    clippy::type_complexity,\n    clippy::too_many_arguments\n)]\n\nuse std::ffi::OsStr;\nuse std::hash::Hash;\nuse std::{\n    fs, mem,\n    path::{Path, PathBuf},\n    sync::Arc,\n    time::Duration,\n};\n\nuse client::{Client, ClientSettings, ClientState};\nuse crusader_lib::plot::{smooth, LatencySummary};\nuse crusader_lib::test::timed;\nuse crusader_lib::{\n    file_format::{RawPing, RawResult, TestKind},\n    latency,\n    plot::{self, float_max, to_rates},\n    protocol, remote, serve,\n    test::{self, PlotConfig},\n    with_time,\n};\nuse eframe::egui::{AboveOrBelow, Label, Layout, TextWrapMode};\nuse eframe::{\n    egui::{\n        self, Grid, Id, PopupCloseBehavior, RichText, ScrollArea, TextEdit, TextStyle, Ui, Vec2b,\n    },\n    emath::Align,\n    epaint::Color32,\n};\nuse egui_extras::{Size, Strip, StripBuilder};\nuse egui_plot::{ColorConflictHandling, Legend, Line, Plot, PlotPoints};\n\n#[cfg(not(target_os = \"android\"))]\nuse rfd::FileDialog;\n\nuse serde::{Deserialize, Serialize};\nuse tokio::sync::{\n    mpsc::{self, error::TryRecvError},\n    oneshot,\n};\n\nmod client;\n\nstruct Server {\n    done: Option<oneshot::Receiver<()>>,\n    msgs: Vec<String>,\n    rx: mpsc::UnboundedReceiver<String>,\n    stop: Option<oneshot::Sender<()>>,\n    started: oneshot::Receiver<Result<(), String>>,\n}\n\nenum ServerState {\n    Stopped(Option<String>),\n    Starting,\n    Stopping,\n    Running,\n}\n\nstruct Latency {\n    done: Option<oneshot::Receiver<Option<Result<(), String>>>>,\n    abort: Option<oneshot::Sender<()>>,\n}\n\n#[derive(PartialEq, Eq)]\nenum Tab {\n    Client,\n    Server,\n    Remote,\n    Monitor,\n    Result,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq)]\n#[serde(default)]\npub struct LatencyMonitorSettings {\n    pub server: String,\n    pub history: f64,\n    pub latency_sample_interval: u64,\n}\n\nimpl Default for LatencyMonitorSettings {\n    fn default() -> Self {\n        Self {\n            server: \"\".to_owned(),\n            history: 60.0,\n            latency_sample_interval: 5,\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Default)]\n#[serde(default)]\npub struct Settings {\n    pub client: ClientSettings,\n    pub latency_monitor: LatencyMonitorSettings,\n}\n\nimpl Settings {\n    fn from_path(path: &Path) -> Self {\n        fs::read_to_string(path)\n            .ok()\n            .and_then(|data| toml::from_str(&data).ok())\n            .unwrap_or_default()\n    }\n}\n\npub struct Tester {\n    tab: Tab,\n    settings: Settings,\n    settings_path: Option<PathBuf>,\n    saved_settings: Settings,\n    server_state: ServerState,\n    server: Option<Server>,\n    remote_state: ServerState,\n    remote_server: Option<Server>,\n    client_state: ClientState,\n    client: Option<Client>,\n    result_plot_reset: bool,\n    result: Option<TestResult>,\n    raw_result_saved: Option<PathBuf>,\n    open_result: Vec<PathBuf>,\n    result_name: String,\n    msgs: Vec<String>,\n    msg_scrolled: usize,\n    pub file_loader: Option<Box<dyn Fn(&mut Tester)>>,\n    pub plot_saver: Option<Box<dyn Fn(&plot::TestResult)>>,\n    pub raw_saver: Option<Box<dyn Fn(&RawResult)>>,\n\n    latency_state: ClientState,\n    latency: Option<Latency>,\n    latency_data: Arc<latency::Data>,\n    latency_stop: Duration,\n    latency_error: Option<String>,\n    latency_plot_reset: bool,\n}\n\npub struct LatencyResult {\n    total: Vec<(f64, f64)>,\n    max: f64,\n    up: Vec<(f64, f64)>,\n    down: Vec<(f64, f64)>,\n    loss: Vec<(f64, Option<bool>)>,\n}\nimpl LatencyResult {\n    fn new(result: &plot::TestResult, pings: &[RawPing]) -> Self {\n        let start = result.start.as_secs_f64();\n        let total: Vec<_> = pings\n            .iter()\n            .filter(|p| p.sent >= result.start)\n            .filter_map(|p| {\n                p.latency.and_then(|latency| {\n                    latency\n                        .total\n                        .map(|total| (p.sent.as_secs_f64() - start, total.as_secs_f64() * 1000.0))\n                })\n            })\n            .collect();\n\n        let up: Vec<_> = pings\n            .iter()\n            .filter(|p| p.sent >= result.start)\n            .filter_map(|p| {\n                p.latency.map(|latency| {\n                    (\n                        p.sent.as_secs_f64() - start,\n                        latency.up.as_secs_f64() * 1000.0,\n                    )\n                })\n            })\n            .collect();\n\n        let down: Vec<_> = pings\n            .iter()\n            .filter(|p| p.sent >= result.start)\n            .filter_map(|p| {\n                p.latency.and_then(|latency| {\n                    latency\n                        .down()\n                        .map(|down| (p.sent.as_secs_f64() - start, down.as_secs_f64() * 1000.0))\n                })\n            })\n            .collect();\n\n        let loss = pings\n            .iter()\n            .filter(|p| p.sent >= result.start)\n            .filter_map(|ping| {\n                if ping.latency.and_then(|latency| latency.total).is_none() {\n                    let down_loss =\n                        (result.raw_result.version >= 2).then_some(ping.latency.is_some());\n                    Some((ping.sent.as_secs_f64() - start, down_loss))\n                } else {\n                    None\n                }\n            })\n            .collect();\n        let max = float_max(total.iter().map(|v| v.1));\n        LatencyResult {\n            total,\n            up,\n            down,\n            loss,\n            max,\n        }\n    }\n}\n\npub struct TestResult {\n    result: plot::TestResult,\n    download: Option<Vec<(f64, f64)>>,\n    download_avg: Option<Vec<(f64, f64)>>,\n    upload: Option<Vec<(f64, f64)>>,\n    upload_avg: Option<Vec<(f64, f64)>>,\n    both_download: Option<Vec<(f64, f64)>>,\n    both_download_avg: Option<Vec<(f64, f64)>>,\n    both_upload: Option<Vec<(f64, f64)>>,\n    both_upload_avg: Option<Vec<(f64, f64)>>,\n    both: Option<Vec<(f64, f64)>>,\n    both_avg: Option<Vec<(f64, f64)>>,\n    local_latency: LatencyResult,\n    peer_latency: Option<LatencyResult>,\n    throughput_max: f64,\n}\n\nimpl TestResult {\n    fn new(result: plot::TestResult) -> Self {\n        let smooth_interval =\n            Duration::from_secs_f64(1.0).min(result.raw_result.config.grace_duration);\n        let interval = result.raw_result.config.bandwidth_interval;\n\n        let start = result.start.as_secs_f64();\n\n        let download = result\n            .download_bytes\n            .as_ref()\n            .map(|bytes| handle_bytes(bytes, start));\n        let download_avg = result\n            .download_bytes\n            .as_ref()\n            .map(|bytes| smooth_bytes(bytes, start, interval, smooth_interval));\n\n        let upload = result\n            .upload_bytes\n            .as_ref()\n            .map(|bytes| handle_bytes(bytes, start));\n        let upload_avg = result\n            .upload_bytes\n            .as_ref()\n            .map(|bytes| smooth_bytes(bytes, start, interval, smooth_interval));\n\n        let both_upload = result\n            .both_upload_bytes\n            .as_ref()\n            .map(|bytes| handle_bytes(bytes, start));\n        let both_upload_avg = result\n            .both_upload_bytes\n            .as_ref()\n            .map(|bytes| smooth_bytes(bytes, start, interval, smooth_interval));\n\n        let both_download = result\n            .both_download_bytes\n            .as_ref()\n            .map(|bytes| handle_bytes(bytes, start));\n        let both_download_avg = result\n            .both_download_bytes\n            .as_ref()\n            .map(|bytes| smooth_bytes(bytes, start, interval, smooth_interval));\n\n        let both = result\n            .both_bytes\n            .as_ref()\n            .map(|bytes| handle_bytes(bytes, start));\n        let both_avg = result\n            .both_bytes\n            .as_ref()\n            .map(|bytes| smooth_bytes(bytes, start, interval, smooth_interval));\n\n        let download_max = download\n            .as_ref()\n            .map(|data| float_max(data.iter().map(|v| v.1)));\n        let upload_max = upload\n            .as_ref()\n            .map(|data| float_max(data.iter().map(|v| v.1)));\n        let both_upload_max = both_upload\n            .as_ref()\n            .map(|data| float_max(data.iter().map(|v| v.1)));\n        let both_download_max = both_download\n            .as_ref()\n            .map(|data| float_max(data.iter().map(|v| v.1)));\n        let both_max = both\n            .as_ref()\n            .map(|data| float_max(data.iter().map(|v| v.1)));\n        let throughput_max = float_max(\n            [\n                download_max,\n                upload_max,\n                both_upload_max,\n                both_download_max,\n                both_max,\n            ]\n            .into_iter()\n            .flatten(),\n        );\n\n        TestResult {\n            download,\n            download_avg,\n            upload,\n            upload_avg,\n            both_download,\n            both_download_avg,\n            both_upload,\n            both_upload_avg,\n            both,\n            both_avg,\n            throughput_max,\n            local_latency: LatencyResult::new(&result, &result.pings),\n            peer_latency: result\n                .raw_result\n                .peer_pings\n                .as_ref()\n                .map(|pings| LatencyResult::new(&result, pings)),\n            result,\n        }\n    }\n}\n\npub fn handle_bytes(data: &[(u64, f64)], start: f64) -> Vec<(f64, f64)> {\n    to_rates(data)\n        .into_iter()\n        .map(|(time, speed)| (Duration::from_micros(time).as_secs_f64() - start, speed))\n        .collect()\n}\n\npub fn smooth_bytes(\n    data: &[(u64, f64)],\n    start: f64,\n    interval: Duration,\n    smoothing_interval: Duration,\n) -> Vec<(f64, f64)> {\n    smooth(data, interval, smoothing_interval)\n        .into_iter()\n        .map(|(time, speed)| (Duration::from_micros(time).as_secs_f64() - start, speed))\n        .collect()\n}\n\nfn hover_popup(\n    ui: &mut Ui,\n    id_source: impl Hash,\n    position: AboveOrBelow,\n    popup: impl FnOnce(&mut Ui),\n) {\n    ui.scope(|ui| {\n        let id = ui.make_persistent_id(id_source);\n\n        ui.spacing_mut().interact_size.y = 18.0;\n        let active = id.with(\"active\");\n\n        let active_value = ui.memory_mut(|mem| {\n            let active = mem.data.get_temp_mut_or_default(active);\n            *active\n        });\n\n        let style = ui.style_mut();\n\n        style.visuals.widgets.inactive.rounding = 50.0.into();\n        style.visuals.widgets.hovered.rounding = 50.0.into();\n        style.visuals.widgets.active.rounding = 50.0.into();\n        style.visuals.widgets.inactive.fg_stroke.color = Color32::from_gray(140);\n\n        if active_value {\n            style.visuals.widgets.inactive.weak_bg_fill = style.visuals.selection.bg_fill;\n            style.visuals.widgets.hovered.weak_bg_fill = style.visuals.selection.bg_fill;\n        }\n\n        let stats = ui.button(\"i\");\n        let popup_id = id.with(\"popup\");\n\n        let contains_pointer =\n            if let Some(pointer) = ui.ctx().input(|input| input.pointer.latest_pos()) {\n                stats.interact_rect.expand(5.0).contains(pointer)\n            } else {\n                false\n            };\n\n        if stats.hovered() && contains_pointer {\n            ui.memory_mut(|mem| {\n                if !mem.any_popup_open() {\n                    mem.open_popup(popup_id);\n                }\n            });\n        } else if !active_value && !contains_pointer {\n            ui.memory_mut(|mem| {\n                if mem.is_popup_open(popup_id) {\n                    mem.close_popup();\n                }\n            });\n        }\n\n        if stats.clicked() {\n            ui.memory_mut(|mem| {\n                let active: &mut bool = mem.data.get_temp_mut_or_default(active);\n                *active = !*active;\n                if *active {\n                    mem.open_popup(popup_id);\n                } else {\n                    mem.close_popup();\n                }\n            });\n        }\n\n        egui::popup::popup_above_or_below_widget(\n            ui,\n            popup_id,\n            &stats,\n            position,\n            PopupCloseBehavior::CloseOnClickOutside,\n            |ui| {\n                popup(ui);\n            },\n        );\n\n        ui.memory_mut(|mem| {\n            if !mem.is_popup_open(popup_id) {\n                let active: &mut bool = mem.data.get_temp_mut_or_default(active);\n                if *active {\n                    //    ui.ctx().request_repaint();\n                }\n                *active = false;\n            }\n        });\n    });\n}\n\nimpl Drop for Tester {\n    fn drop(&mut self) {\n        self.save_settings();\n\n        // Stop client\n        self.client.as_mut().map(|client| {\n            mem::take(&mut client.abort).map(|abort| {\n                abort.send(()).unwrap();\n            });\n            mem::take(&mut client.done).map(|done| {\n                done.blocking_recv().ok();\n            });\n        });\n\n        // Stop server\n        self.server.as_mut().map(|server| {\n            mem::take(&mut server.stop).map(|stop| {\n                stop.send(()).unwrap();\n            });\n            mem::take(&mut server.done).map(|done| {\n                done.blocking_recv().ok();\n            });\n        });\n\n        // Stop latency\n        self.latency.as_mut().map(|latency| {\n            mem::take(&mut latency.abort).map(|abort| {\n                abort.send(()).unwrap();\n            });\n            mem::take(&mut latency.done).map(|done| {\n                done.blocking_recv().ok();\n            });\n        });\n    }\n}\n\nimpl Tester {\n    pub fn new(settings_path: Option<PathBuf>) -> Tester {\n        let settings = settings_path\n            .as_deref()\n            .map_or(Settings::default(), Settings::from_path);\n        Tester {\n            tab: Tab::Client,\n            saved_settings: settings.clone(),\n            settings,\n            settings_path,\n            client_state: ClientState::Stopped,\n            client: None,\n            result: None,\n            result_plot_reset: false,\n            raw_result_saved: None,\n            result_name: \"\".to_string(),\n            open_result: Vec::new(),\n            msgs: Vec::new(),\n            msg_scrolled: 0,\n            server_state: ServerState::Stopped(None),\n            server: None,\n            remote_state: ServerState::Stopped(None),\n            remote_server: None,\n            file_loader: None,\n            raw_saver: None,\n            plot_saver: None,\n            latency_state: ClientState::Stopped,\n            latency: None,\n            latency_data: Arc::new(latency::Data::new(0, Arc::new(|| {}))),\n            latency_stop: Duration::from_secs(0),\n            latency_error: None,\n            latency_plot_reset: false,\n        }\n    }\n\n    pub fn set_result(&mut self, result: plot::TestResult) {\n        self.result = Some(TestResult::new(result));\n        self.result_name = \"test\".to_owned();\n        self.result_plot_reset = true;\n        self.raw_result_saved = None;\n    }\n\n    pub fn load_file(&mut self, name: PathBuf, raw: RawResult) {\n        self.set_result(raw.to_test_result());\n        self.raw_result_saved = Some(name);\n    }\n\n    pub fn save_raw(&mut self, name: PathBuf) {\n        self.raw_result_saved = Some(name);\n    }\n\n    fn save_settings(&mut self) {\n        if self.settings != self.saved_settings {\n            self.settings_path.as_deref().map(|path| {\n                toml::ser::to_string_pretty(&self.settings)\n                    .map(|data| fs::write(path, data.as_bytes()))\n                    .ok();\n            });\n            self.saved_settings = self.settings.clone();\n        }\n    }\n\n    fn load_result(&mut self) {\n        #[cfg(not(target_os = \"android\"))]\n        {\n            FileDialog::new()\n                .add_filter(\"Crusader Raw Result\", &[\"crr\"])\n                .add_filter(\"All files\", &[\"*\"])\n                .pick_file()\n                .map(|file| {\n                    RawResult::load(&file).map(|raw| {\n                        self.load_file(file, raw);\n                    })\n                });\n        }\n        let file_loader = self.file_loader.take();\n        file_loader.as_ref().map(|loader| loader(self));\n        self.file_loader = file_loader;\n    }\n\n    fn latency_and_loss(\n        &mut self,\n        strip: &mut Strip<'_, '_>,\n        link: Id,\n        reset: bool,\n        peer: bool,\n        y_axis_size: f32,\n    ) {\n        let result = self.result.as_ref().unwrap();\n\n        let data = if peer {\n            result.peer_latency.as_ref().unwrap()\n        } else {\n            &result.local_latency\n        };\n\n        let latencies = if peer {\n            &result.result.peer_latencies\n        } else {\n            &result.result.latencies\n        };\n\n        let duration = result.result.duration.as_secs_f64() * 1.1;\n\n        strip.cell(|ui| {\n            ui.horizontal(|ui| {\n                let label = if peer { \"Peer latency\" } else { \"Latency\" };\n                ui.label(label);\n\n                hover_popup(\n                    ui,\n                    (label, \"Popup\"),\n                    if !peer && result.result.raw_result.idle() {\n                        AboveOrBelow::Below\n                    } else {\n                        AboveOrBelow::Above\n                    },\n                    |ui| {\n                        ui.spacing_mut().item_spacing.x = 0.0;\n                        ui.spacing_mut().interact_size.y = 10.0;\n\n                        let stats = |ui: &mut Ui, name, color, latency: &LatencySummary| {\n                            ui.vertical(|ui| {\n                                ui.add_space(5.0);\n                                ui.horizontal(|ui| {\n                                    ui.label(RichText::new(format!(\"{name}: \")).color(color));\n                                    ui.label(format!(\n                                        \"{:.01} ms\",\n                                        latency.total.as_secs_f64() * 1000.0\n                                    ));\n                                });\n                                ui.horizontal(|ui| {\n                                    ui.label(format!(\n                                        \"\\t\\t{:.01} ms \",\n                                        latency.down.as_secs_f64() * 1000.0\n                                    ));\n                                    ui.label(\n                                        RichText::new(\"down\").color(Color32::from_rgb(95, 145, 62)),\n                                    );\n                                });\n                                ui.horizontal(|ui| {\n                                    ui.label(format!(\n                                        \"\\t\\t{:.01} ms \",\n                                        latency.up.as_secs_f64() * 1000.0\n                                    ));\n                                    ui.label(\n                                        RichText::new(\"up\").color(Color32::from_rgb(37, 83, 169)),\n                                    );\n                                });\n                            });\n                        };\n\n                        if let Some(latency) = latencies.latencies.get(&Some(TestKind::Download)) {\n                            stats(ui, \"Download\", Color32::from_rgb(95, 145, 62), latency);\n                        }\n\n                        if let Some(latency) = latencies.latencies.get(&Some(TestKind::Upload)) {\n                            stats(ui, \"Upload\", Color32::from_rgb(37, 83, 169), latency);\n                        }\n\n                        if let Some(latency) =\n                            latencies.latencies.get(&Some(TestKind::Bidirectional))\n                        {\n                            stats(\n                                ui,\n                                \"Bidirectional\",\n                                Color32::from_rgb(149, 96, 153),\n                                latency,\n                            );\n                        }\n\n                        if let Some(latency) = latencies.latencies.get(&None) {\n                            stats(ui, \"Latency\", Color32::from_rgb(0, 0, 0), latency);\n                        }\n\n                        ui.vertical(|ui| {\n                            ui.add_space(5.0);\n                            ui.horizontal(|ui| {\n                                ui.label(\n                                    RichText::new(\"Idle latency: \")\n                                        .color(Color32::from_rgb(128, 128, 128)),\n                                );\n                                ui.label(format!(\n                                    \"{:.02} ms\",\n                                    result.result.raw_result.server_latency.as_secs_f64() * 1000.0\n                                ));\n                            });\n                        });\n\n                        ui.vertical(|ui| {\n                            ui.add_space(5.0);\n                            ui.horizontal(|ui| {\n                                ui.label(\n                                    RichText::new(\"Latency sample interval: \")\n                                        .color(Color32::from_rgb(128, 128, 128)),\n                                );\n                                ui.label(format!(\n                                    \"{:.02} ms\",\n                                    result.result.raw_result.config.ping_interval.as_secs_f64()\n                                        * 1000.0\n                                ));\n                            });\n                        });\n                    },\n                );\n            });\n\n            // Latency\n            let mut plot = Plot::new((peer, \"ping\"))\n                .legend(Legend::default().insertion_order(true))\n                .y_axis_min_width(y_axis_size)\n                .link_axis(link, true, false)\n                .link_cursor(link, true, false)\n                .include_x(0.0)\n                .include_x(duration)\n                .include_y(0.0)\n                .include_y(data.max * 1.1)\n                .label_formatter(|_, value| {\n                    format!(\"Latency = {:.2} ms\\nTime = {:.2} s\", value.y, value.x)\n                });\n\n            if reset {\n                plot = plot.reset();\n            }\n\n            plot.show(ui, |plot_ui| {\n                if result.result.raw_result.version >= 1 {\n                    let latency = data.up.iter().map(|v| [v.0, v.1]);\n                    let latency = Line::new(PlotPoints::from_iter(latency))\n                        .color(Color32::from_rgb(37, 83, 169))\n                        .name(\"Up\");\n\n                    plot_ui.line(latency);\n\n                    let latency = data.down.iter().map(|v| [v.0, v.1]);\n                    let latency = Line::new(PlotPoints::from_iter(latency))\n                        .color(Color32::from_rgb(95, 145, 62))\n                        .name(\"Down\");\n\n                    plot_ui.line(latency);\n                }\n\n                let latency = data.total.iter().map(|v| [v.0, v.1]);\n                let latency = Line::new(PlotPoints::from_iter(latency))\n                    .color(Color32::from_rgb(50, 50, 50))\n                    .name(\"Round-trip\");\n\n                plot_ui.line(latency);\n            });\n        });\n\n        strip.cell(|ui| {\n            ui.horizontal(|ui| {\n                let label = if peer {\n                    \"Peer packet loss\"\n                } else {\n                    \"Packet loss\"\n                };\n                ui.label(label);\n\n                hover_popup(ui, (label, \"Popup\"), AboveOrBelow::Above, |ui| {\n                    ui.spacing_mut().item_spacing.x = 0.0;\n                    ui.spacing_mut().interact_size.y = 10.0;\n\n                    let stats = |ui: &mut Ui, name, color, (down, up): (f64, f64)| {\n                        ui.vertical(|ui| {\n                            ui.add_space(5.0);\n                            ui.horizontal(|ui| {\n                                ui.label(RichText::new(format!(\"{name}: \")).color(color));\n                                if down == 0.0 && up == 0.0 {\n                                    ui.label(\"0%\");\n                                } else {\n                                    ui.label(format!(\n                                        \"{:.1$}% \",\n                                        down * 100.0,\n                                        if down == 0.0 { 0 } else { 2 }\n                                    ));\n                                    ui.label(\n                                        RichText::new(\"down\").color(Color32::from_rgb(95, 145, 62)),\n                                    );\n                                    ui.label(format!(\n                                        \", {:.1$}% \",\n                                        up * 100.0,\n                                        if up == 0.0 { 0 } else { 2 }\n                                    ));\n                                    ui.label(\n                                        RichText::new(\"up\").color(Color32::from_rgb(37, 83, 169)),\n                                    );\n                                }\n                            });\n                        });\n                    };\n\n                    if let Some(loss) = latencies.loss.get(&Some(TestKind::Download)) {\n                        stats(ui, \"Download\", Color32::from_rgb(95, 145, 62), *loss);\n                    }\n\n                    if let Some(loss) = latencies.loss.get(&Some(TestKind::Upload)) {\n                        stats(ui, \"Upload\", Color32::from_rgb(37, 83, 169), *loss);\n                    }\n\n                    if let Some(loss) = latencies.loss.get(&Some(TestKind::Bidirectional)) {\n                        stats(ui, \"Bidirectional\", Color32::from_rgb(149, 96, 153), *loss);\n                    }\n\n                    if let Some(loss) = latencies.loss.get(&None) {\n                        stats(ui, \"Packet loss\", Color32::from_rgb(0, 0, 0), *loss);\n                    }\n                });\n            });\n\n            // Packet loss\n            let mut plot = Plot::new((peer, \"loss\"))\n                .legend(Legend::default())\n                .show_axes([false, true])\n                .show_grid(Vec2b::new(true, false))\n                .y_axis_min_width(y_axis_size)\n                .y_axis_formatter(|_, _| String::new())\n                .link_axis(link, true, false)\n                .link_cursor(link, true, false)\n                .center_y_axis(true)\n                .allow_zoom(false)\n                .allow_boxed_zoom(false)\n                .include_x(0.0)\n                .include_x(duration)\n                .include_y(-1.0)\n                .include_y(1.0)\n                .height(30.0)\n                .label_formatter(|_, value| format!(\"Time = {:.2} s\", value.x));\n\n            if reset {\n                plot = plot.reset();\n            }\n\n            plot.show(ui, |plot_ui| {\n                for &(loss, down_loss) in &data.loss {\n                    let (color, s, e) = down_loss\n                        .map(|down_loss| {\n                            if down_loss {\n                                (Color32::from_rgb(95, 145, 62), 1.0, 0.0)\n                            } else {\n                                (Color32::from_rgb(37, 83, 169), -1.0, 0.0)\n                            }\n                        })\n                        .unwrap_or((Color32::from_rgb(193, 85, 85), -1.0, 1.0));\n\n                    plot_ui.line(\n                        Line::new(PlotPoints::from_iter(\n                            [[loss, s], [loss, e]].iter().copied(),\n                        ))\n                        .color(color),\n                    );\n\n                    if down_loss.is_some() {\n                        plot_ui.line(\n                            Line::new(PlotPoints::from_iter(\n                                [[loss, s], [loss, s - s / 5.0]].iter().copied(),\n                            ))\n                            .width(3.0)\n                            .color(color),\n                        );\n                    }\n                }\n            });\n        });\n    }\n\n    fn load_popup(&mut self, ui: &mut Ui) {\n        if cfg!(not(target_os = \"android\")) {\n            ui.add_space(10.0);\n\n            let popup_id = ui.make_persistent_id(\"Load-Popup\");\n\n            let button = ui.button(\"Open from results\");\n\n            if button.clicked() {\n                ui.memory_mut(|mem| {\n                    mem.toggle_popup(popup_id);\n                    if mem.is_popup_open(popup_id) {\n                        self.open_result = fs::read_dir(\"crusader-results\")\n                            .ok()\n                            .map(|dir| {\n                                dir.filter_map(|file| {\n                                    file.ok()\n                                        .map(|file| file.path())\n                                        .filter(|path| path.extension() == Some(OsStr::new(\"crr\")))\n                                })\n                                .collect()\n                            })\n                            .unwrap_or_default();\n                    }\n                });\n            }\n\n            egui::popup::popup_below_widget(\n                ui,\n                popup_id,\n                &button,\n                PopupCloseBehavior::CloseOnClickOutside,\n                |ui| {\n                    ui.set_min_width(300.0);\n                    ui.horizontal_wrapped(|ui| {\n                        ui.label(\"Results available in the\");\n                        if ui.link(\"crusader-results\").clicked() {\n                            open::that(\"crusader-results\").ok();\n                        }\n                        ui.label(\"folder:\");\n                    });\n\n                    ScrollArea::vertical().show(ui, |ui| {\n                        ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {\n                            for file in self.open_result.clone() {\n                                if let Some(prefix) =\n                                    file.file_name().and_then(|stem| stem.to_str())\n                                {\n                                    if ui.toggle_value(&mut false, prefix).clicked() {\n                                        ui.memory_mut(|mem| mem.close_popup());\n                                        RawResult::load(&file).map(|raw| {\n                                            self.load_file(file, raw);\n                                        });\n                                    }\n                                }\n                            }\n                        });\n                    });\n                },\n            );\n        }\n    }\n\n    fn result(&mut self, _ctx: &egui::Context, ui: &mut Ui) {\n        if self.result.is_none() {\n            ui.horizontal_wrapped(|ui| {\n                if ui.button(\"Open\").clicked() {\n                    self.load_result();\n                }\n                self.load_popup(ui);\n            });\n            ui.separator();\n            ui.label(\"No result.\");\n            return;\n        }\n\n        ui.horizontal_wrapped(|ui| {\n            if ui.button(\"Open\").clicked() {\n                self.load_result();\n            }\n\n            if ui.button(\"Save\").clicked() {\n                match self.raw_saver.as_ref() {\n                    Some(saver) => {\n                        saver(&self.result.as_ref().unwrap().result.raw_result);\n                    }\n                    None => {\n                        #[cfg(not(target_os = \"android\"))]\n                        {\n                            FileDialog::new()\n                                .add_filter(\"Crusader Raw Result\", &[\"crr\"])\n                                .add_filter(\"All files\", &[\"*\"])\n                                .set_file_name(&format!(\"{}.crr\", timed(\"test\")))\n                                .save_file()\n                                .map(|file| {\n                                    if self\n                                        .result\n                                        .as_ref()\n                                        .unwrap()\n                                        .result\n                                        .raw_result\n                                        .save(&file)\n                                        .is_ok()\n                                    {\n                                        self.raw_result_saved = Some(file);\n                                    }\n                                });\n                        }\n                    }\n                }\n            }\n\n            self.load_popup(ui);\n\n            if cfg!(not(target_os = \"android\")) {\n                let popup_id = ui.make_persistent_id(\"Save-Popup\");\n\n                let button = ui.button(\"Save to results\");\n\n                if button.clicked() {\n                    ui.memory_mut(|mem| {\n                        mem.toggle_popup(popup_id);\n                    });\n                }\n\n                egui::popup::popup_below_widget(\n                    ui,\n                    popup_id,\n                    &button,\n                    PopupCloseBehavior::CloseOnClickOutside,\n                    |ui| {\n                        ui.set_min_width(250.0);\n                        ui.horizontal_wrapped(|ui| {\n                            ui.label(\"This saves both the data and plot in the\");\n                            if ui.link(\"crusader-results\").clicked() {\n                                open::that(\"crusader-results\").ok();\n                            }\n                            ui.label(\"folder.\");\n                        });\n                        ui.horizontal(|ui| {\n                            ui.label(\"Name: \");\n                            let mut click = ui\n                                .add(\n                                    TextEdit::singleline(&mut self.result_name)\n                                        .desired_width(175.0),\n                                )\n                                .lost_focus()\n                                && ui.input(|i| i.key_pressed(egui::Key::Enter));\n                            click |= ui.button(\"Save\").clicked();\n                            if click {\n                                let name = timed(&self.result_name);\n                                self.raw_result_saved = test::save_raw(\n                                    &self.result.as_ref().unwrap().result.raw_result,\n                                    &name,\n                                    Path::new(\"crusader-results\"),\n                                )\n                                .ok();\n                                plot::save_graph(\n                                    &PlotConfig::default(),\n                                    &self.result.as_ref().unwrap().result,\n                                    &name,\n                                    Path::new(\"crusader-results\"),\n                                )\n                                .ok();\n                                ui.memory_mut(|mem| {\n                                    mem.close_popup();\n                                });\n                            }\n                        });\n                    },\n                );\n            }\n\n            ui.add_space(10.0);\n\n            if ui.button(\"Export plot\").clicked() {\n                match self.plot_saver.as_ref() {\n                    Some(saver) => {\n                        saver(&self.result.as_ref().unwrap().result);\n                    }\n                    None => {\n                        #[cfg(not(target_os = \"android\"))]\n                        {\n                            let name = self\n                                .raw_result_saved\n                                .as_ref()\n                                .and_then(|file| {\n                                    file.file_stem()\n                                        .unwrap_or_default()\n                                        .to_str()\n                                        .map(|s| s.to_owned())\n                                })\n                                .unwrap_or(timed(\"test\"));\n\n                            let mut dialog = FileDialog::new()\n                                .add_filter(\"Portable Network Graphics\", &[\"png\"])\n                                .add_filter(\"All files\", &[\"*\"])\n                                .set_file_name(&format!(\"{}.png\", name));\n\n                            if let Some(file) = self.raw_result_saved.as_ref() {\n                                if let Some(parent) = file.parent() {\n                                    dialog = dialog.set_directory(parent);\n                                }\n                            }\n\n                            dialog.save_file().map(|file| {\n                                if plot::save_graph_to_path(\n                                    &file,\n                                    &PlotConfig::default(),\n                                    &self.result.as_ref().unwrap().result,\n                                )\n                                .is_ok()\n                                {\n                                    file.file_name()\n                                        .unwrap_or_default()\n                                        .to_str()\n                                        .map(|s| s.to_owned());\n                                }\n                            });\n                        }\n                    }\n                }\n            }\n        });\n        ui.separator();\n\n        self.raw_result_saved\n            .as_ref()\n            .and_then(|file| {\n                file.file_name()\n                    .unwrap_or_default()\n                    .to_str()\n                    .map(|s| s.to_owned())\n            })\n            .map(|file| {\n                ui.label(format!(\"Saved as: {file}\"));\n                ui.separator();\n            });\n\n        let result = self.result.as_ref().unwrap();\n\n        if result.result.raw_result.server_overload {\n            ui.label(\"Warning: Server overload detected during test. Result should be discarded.\");\n            ui.separator();\n        }\n\n        if result.result.raw_result.load_termination_timeout {\n            ui.label(\"Warning: Load termination timed out. There may be residual untracked traffic in the background.\");\n            ui.separator();\n        }\n\n        let packet_loss_size = 75.0;\n\n        let result = self.result.as_ref().unwrap();\n\n        let link = ui.id().with(\"result-link\");\n\n        let mut strip = StripBuilder::new(ui);\n\n        if result.result.raw_result.streams() > 0 {\n            strip = strip.size(Size::remainder());\n        }\n\n        for _ in 0..(1 + result.peer_latency.is_some() as u8) {\n            strip = strip\n                .size(Size::remainder())\n                .size(Size::exact(packet_loss_size));\n        }\n\n        strip.vertical(|mut strip| {\n            let reset = mem::take(&mut self.result_plot_reset);\n\n            let result = self.result.as_ref().unwrap();\n\n            let y_axis_size = 30.0;\n\n            let duration = result.result.duration.as_secs_f64() * 1.1;\n\n            if result.result.raw_result.streams() > 0 {\n                strip.cell(|ui| {\n                    ui.horizontal(|ui| {\n                        ui.label(\"Throughput\");\n\n                        hover_popup(ui, \"Throughput-Popup\", AboveOrBelow::Below, |ui| {\n                            ui.spacing_mut().item_spacing.x = 0.0;\n                            ui.spacing_mut().interact_size.y = 10.0;\n\n                            if let Some(throughput) = result\n                                .result\n                                .throughputs\n                                .get(&(TestKind::Download, TestKind::Download))\n                            {\n                                ui.vertical(|ui| {\n                                    ui.add_space(5.0);\n                                    ui.horizontal(|ui| {\n                                        ui.label(\n                                            RichText::new(\"Download: \")\n                                                .color(Color32::from_rgb(95, 145, 62)),\n                                        );\n                                        ui.label(format!(\"{:.02} Mbps\", throughput));\n                                    });\n                                });\n                            }\n\n                            if let Some(throughput) = result\n                                .result\n                                .throughputs\n                                .get(&(TestKind::Upload, TestKind::Upload))\n                            {\n                                ui.vertical(|ui| {\n                                    ui.add_space(5.0);\n                                    ui.horizontal(|ui| {\n                                        ui.label(\n                                            RichText::new(\"Upload: \")\n                                                .color(Color32::from_rgb(37, 83, 169)),\n                                        );\n                                        ui.label(format!(\"{:.02} Mbps\", throughput));\n                                    });\n                                });\n                            }\n\n                            if let Some(throughput) = result\n                                .result\n                                .throughputs\n                                .get(&(TestKind::Bidirectional, TestKind::Bidirectional))\n                            {\n                                ui.vertical(|ui| {\n                                    ui.add_space(5.0);\n                                    ui.horizontal(|ui| {\n                                        ui.label(\n                                            RichText::new(\"Bidirectional: \")\n                                                .color(Color32::from_rgb(149, 96, 153)),\n                                        );\n                                        ui.label(format!(\"{:.02} Mbps \", throughput));\n                                    });\n                                    if let Some(down) = result\n                                        .result\n                                        .throughputs\n                                        .get(&(TestKind::Bidirectional, TestKind::Download))\n                                    {\n                                        if let Some(up) = result\n                                            .result\n                                            .throughputs\n                                            .get(&(TestKind::Bidirectional, TestKind::Upload))\n                                        {\n                                            ui.horizontal(|ui| {\n                                                ui.label(format!(\"\\t\\t{:.02} Mbps \", down));\n                                                ui.label(\n                                                    RichText::new(\"down\")\n                                                        .color(Color32::from_rgb(95, 145, 62)),\n                                                );\n                                            });\n                                            ui.horizontal(|ui| {\n                                                ui.label(format!(\"\\t\\t{:.02} Mbps \", up));\n                                                ui.label(\n                                                    RichText::new(\"up\")\n                                                        .color(Color32::from_rgb(37, 83, 169)),\n                                                );\n                                            });\n                                        }\n                                    }\n                                });\n                            }\n\n                            ui.vertical(|ui| {\n                                ui.add_space(5.0);\n                                ui.horizontal(|ui| {\n                                    ui.label(\n                                        RichText::new(\"Streams: \")\n                                            .color(Color32::from_rgb(128, 128, 128)),\n                                    );\n                                    ui.label(format!(\"{}\", result.result.raw_result.streams()));\n                                });\n                            });\n\n                            ui.vertical(|ui| {\n                                ui.add_space(5.0);\n                                ui.horizontal(|ui| {\n                                    ui.label(\n                                        RichText::new(\"Stream Stagger: \")\n                                            .color(Color32::from_rgb(128, 128, 128)),\n                                    );\n                                    ui.label(format!(\n                                        \"{:.02} seconds\",\n                                        result.result.raw_result.config.stagger.as_secs_f64()\n                                    ));\n                                });\n                            });\n\n                            ui.vertical(|ui| {\n                                ui.add_space(5.0);\n                                ui.horizontal(|ui| {\n                                    ui.label(\n                                        RichText::new(\"Throughput sample interval: \")\n                                            .color(Color32::from_rgb(128, 128, 128)),\n                                    );\n                                    ui.label(format!(\n                                        \"{:.02} ms\",\n                                        result\n                                            .result\n                                            .raw_result\n                                            .config\n                                            .bandwidth_interval\n                                            .as_secs_f64()\n                                            * 1000.0\n                                    ));\n                                });\n                            });\n                        });\n                    });\n\n                    // Throughput\n                    let mut plot = Plot::new(\"result\")\n                        .legend(\n                            Legend::default()\n                                .color_conflict_handling(ColorConflictHandling::PickFirst)\n                                .insertion_order(true),\n                        )\n                        .y_axis_min_width(y_axis_size)\n                        .link_axis(link, true, false)\n                        .link_cursor(link, true, false)\n                        .include_x(0.0)\n                        .include_x(duration)\n                        .include_y(0.0)\n                        .include_y(result.throughput_max * 1.1)\n                        .height(ui.available_height())\n                        .label_formatter(|_, value| {\n                            format!(\"Throughput = {:.2} Mbps\\nTime = {:.2} s\", value.y, value.x)\n                        });\n\n                    if reset {\n                        plot = plot.reset();\n                    }\n\n                    plot.show(ui, |plot_ui| {\n                        let width = 1.0;\n                        if let Some(data) = result.download.as_ref() {\n                            let download = data.iter().map(|v| [v.0, v.1]);\n                            let download = Line::new(PlotPoints::from_iter(download))\n                                .color(Color32::from_rgb(95, 145, 62))\n                                .width(width)\n                                .name(\"Download\");\n\n                            plot_ui.line(download);\n                        }\n                        if let Some(data) = result.upload.as_ref() {\n                            let upload = data.iter().map(|v| [v.0, v.1]);\n                            let upload = Line::new(PlotPoints::from_iter(upload))\n                                .color(Color32::from_rgb(37, 83, 169))\n                                .width(width)\n                                .name(\"Upload\");\n\n                            plot_ui.line(upload);\n                        }\n                        if let Some(data) = result.both_download.as_ref() {\n                            let download = data.iter().map(|v| [v.0, v.1]);\n                            let download = Line::new(PlotPoints::from_iter(download))\n                                .color(Color32::from_rgb(95, 145, 62))\n                                .width(width)\n                                .name(\"Download\");\n\n                            plot_ui.line(download);\n                        }\n                        if let Some(data) = result.both_upload.as_ref() {\n                            let upload = data.iter().map(|v| [v.0, v.1]);\n                            let upload = Line::new(PlotPoints::from_iter(upload))\n                                .color(Color32::from_rgb(37, 83, 169))\n                                .width(width)\n                                .name(\"Upload\");\n\n                            plot_ui.line(upload);\n                        }\n                        if let Some(data) = result.both.as_ref() {\n                            let both = data.iter().map(|v| [v.0, v.1]);\n                            let both = Line::new(PlotPoints::from_iter(both))\n                                .color(Color32::from_rgb(149, 96, 153))\n                                .width(width)\n                                .name(\"Aggregate\");\n\n                            plot_ui.line(both);\n                        }\n\n                        // Average lines\n                        let darken = 0.5;\n                        let alpha = 0.35;\n\n                        if let Some(data) = result.download_avg.as_ref() {\n                            let download = data.iter().map(|v| [v.0, v.1]);\n                            let download = Line::new(PlotPoints::from_iter(download))\n                                .color(\n                                    Color32::from_rgb(95, 145, 62)\n                                        .lerp_to_gamma(Color32::BLACK, darken)\n                                        .gamma_multiply(alpha),\n                                )\n                                .allow_hover(false)\n                                .width(3.5)\n                                .name(\"Download\");\n\n                            plot_ui.line(download);\n                        }\n                        if let Some(data) = result.upload_avg.as_ref() {\n                            let upload = data.iter().map(|v| [v.0, v.1]);\n                            let upload = Line::new(PlotPoints::from_iter(upload))\n                                .color(\n                                    Color32::from_rgb(37, 83, 169)\n                                        .lerp_to_gamma(Color32::BLACK, darken)\n                                        .gamma_multiply(alpha),\n                                )\n                                .allow_hover(false)\n                                .width(3.5)\n                                .name(\"Upload\");\n\n                            plot_ui.line(upload);\n                        }\n                        if let Some(data) = result.both_download_avg.as_ref() {\n                            let download = data.iter().map(|v| [v.0, v.1]);\n                            let download = Line::new(PlotPoints::from_iter(download))\n                                .color(\n                                    Color32::from_rgb(95, 145, 62)\n                                        .lerp_to_gamma(Color32::BLACK, darken)\n                                        .gamma_multiply(alpha),\n                                )\n                                .allow_hover(false)\n                                .width(3.5)\n                                .name(\"Download\");\n\n                            plot_ui.line(download);\n                        }\n                        if let Some(data) = result.both_upload_avg.as_ref() {\n                            let upload = data.iter().map(|v| [v.0, v.1]);\n                            let upload = Line::new(PlotPoints::from_iter(upload))\n                                .color(\n                                    Color32::from_rgb(37, 83, 169)\n                                        .lerp_to_gamma(Color32::BLACK, darken)\n                                        .gamma_multiply(alpha),\n                                )\n                                .allow_hover(false)\n                                .width(3.5)\n                                .name(\"Upload\");\n\n                            plot_ui.line(upload);\n                        }\n                        if let Some(data) = result.both_avg.as_ref() {\n                            let both = data.iter().map(|v| [v.0, v.1]);\n                            let both = Line::new(PlotPoints::from_iter(both))\n                                .color(\n                                    Color32::from_rgb(149, 96, 153)\n                                        .lerp_to_gamma(Color32::BLACK, darken)\n                                        .gamma_multiply(alpha),\n                                )\n                                .allow_hover(false)\n                                .width(3.5)\n                                .name(\"Aggregate\");\n\n                            plot_ui.line(both);\n                        }\n                    });\n                })\n            }\n\n            self.latency_and_loss(&mut strip, link, reset, false, y_axis_size);\n\n            let result = self.result.as_ref().unwrap();\n\n            if result.peer_latency.is_some() {\n                self.latency_and_loss(&mut strip, link, reset, true, y_axis_size);\n            }\n        });\n    }\n\n    fn server(&mut self, ctx: &egui::Context, ui: &mut Ui) {\n        match self.server_state {\n            ServerState::Stopped(ref error) => {\n                let (server_button, peer_button) = ui\n                    .horizontal_wrapped(|ui| (ui.button(\"Start server\"), ui.button(\"Start peer\")))\n                    .inner;\n\n                if let Some(error) = error {\n                    ui.separator();\n                    ui.label(format!(\"Unable to start server: {}\", error));\n                }\n\n                if server_button.clicked() || peer_button.clicked() {\n                    let ctx = ctx.clone();\n                    let ctx_ = ctx.clone();\n                    let ctx__ = ctx.clone();\n                    let (tx, rx) = mpsc::unbounded_channel();\n                    let (signal_started, started) = oneshot::channel();\n                    let (signal_done, done) = oneshot::channel();\n\n                    let stop = serve::serve_until(\n                        protocol::PORT,\n                        peer_button.clicked(),\n                        Box::new(move |msg| {\n                            tx.send(with_time(msg)).ok();\n                            ctx.request_repaint();\n                        }),\n                        Box::new(move |result| {\n                            signal_started.send(result).ok();\n                            ctx_.request_repaint();\n                        }),\n                        Box::new(move || {\n                            signal_done.send(()).ok();\n                            ctx__.request_repaint();\n                        }),\n                    )\n                    .ok();\n\n                    if let Some(stop) = stop {\n                        self.server = Some(Server {\n                            done: Some(done),\n                            stop: Some(stop),\n                            started,\n                            rx,\n                            msgs: Vec::new(),\n                        });\n                        self.server_state = ServerState::Starting;\n                    }\n                };\n                ui.separator();\n                ui.label(format!(\n                    \"A server listens on TCP and UDP port {}. It allows clients \\\n                    to run tests and measure latency against it. It can also act as a latency peer for tests connecting to another server.\",\n                    protocol::PORT\n                ));\n            }\n            ServerState::Running => {\n                let server = self.server.as_mut().unwrap();\n                let button = ui.button(\"Stop server\");\n\n                ui.separator();\n\n                loop {\n                    match server.rx.try_recv() {\n                        Ok(msg) => {\n                            println!(\"[Server] {msg}\");\n                            server.msgs.push(msg);\n                        }\n                        Err(TryRecvError::Disconnected) => panic!(),\n                        Err(TryRecvError::Empty) => break,\n                    }\n                }\n\n                ScrollArea::vertical()\n                    .stick_to_bottom(true)\n                    .auto_shrink([false; 2])\n                    .show_rows(\n                        ui,\n                        ui.text_style_height(&TextStyle::Body),\n                        server.msgs.len(),\n                        |ui, rows| {\n                            for row in rows {\n                                ui.label(&server.msgs[row]);\n                            }\n                        },\n                    );\n\n                if button.clicked() {\n                    mem::take(&mut server.stop).unwrap().send(()).unwrap();\n                    self.server_state = ServerState::Stopping;\n                };\n            }\n            ServerState::Starting => {\n                let server = self.server.as_mut().unwrap();\n\n                if let Ok(result) = server.started.try_recv() {\n                    if let Err(error) = result {\n                        self.server_state = ServerState::Stopped(Some(error));\n                        self.server = None;\n                    } else {\n                        self.server_state = ServerState::Running;\n                    }\n                }\n\n                ui.add_enabled_ui(false, |ui| {\n                    let _ = ui.button(\"Starting..\");\n                });\n            }\n            ServerState::Stopping => {\n                if let Ok(()) = self\n                    .server\n                    .as_mut()\n                    .unwrap()\n                    .done\n                    .as_mut()\n                    .unwrap()\n                    .try_recv()\n                {\n                    self.server_state = ServerState::Stopped(None);\n                    self.server = None;\n                }\n\n                ui.add_enabled_ui(false, |ui| {\n                    let _ = ui.button(\"Stopping..\");\n                });\n            }\n        }\n    }\n\n    fn remote(&mut self, ctx: &egui::Context, ui: &mut Ui) {\n        match self.remote_state {\n            ServerState::Stopped(ref error) => {\n                let button = ui\n                    .vertical(|ui| {\n                        let button = ui.button(\"Start server\");\n                        if let Some(error) = error {\n                            ui.separator();\n                            ui.label(format!(\"Unable to start server: {}\", error));\n                        }\n                        button\n                    })\n                    .inner;\n\n                if button.clicked() {\n                    let ctx = ctx.clone();\n                    let ctx_ = ctx.clone();\n                    let ctx__ = ctx.clone();\n                    let (tx, rx) = mpsc::unbounded_channel();\n                    let (signal_started, started) = oneshot::channel();\n                    let (signal_done, done) = oneshot::channel();\n\n                    let stop = remote::serve_until(\n                        protocol::PORT + 1,\n                        Box::new(move |msg| {\n                            tx.send(with_time(msg)).ok();\n                            ctx.request_repaint();\n                        }),\n                        Box::new(move |result| {\n                            signal_started.send(result).ok();\n                            ctx_.request_repaint();\n                        }),\n                        Box::new(move || {\n                            signal_done.send(()).ok();\n                            ctx__.request_repaint();\n                        }),\n                    )\n                    .ok();\n\n                    if let Some(stop) = stop {\n                        self.remote_server = Some(Server {\n                            done: Some(done),\n                            stop: Some(stop),\n                            started,\n                            rx,\n                            msgs: Vec::new(),\n                        });\n                        self.remote_state = ServerState::Starting;\n                    }\n                };\n                ui.separator();\n                ui.label(format!(\n                    \"A remote server runs a web server on TCP port {}. It allows web clients to remotely start \\\n                    tests against other servers.\",\n                    protocol::PORT + 1\n                ));\n            }\n            ServerState::Running => {\n                let remote_server = self.remote_server.as_mut().unwrap();\n                let button = ui.button(\"Stop server\");\n\n                ui.separator();\n\n                loop {\n                    match remote_server.rx.try_recv() {\n                        Ok(msg) => {\n                            println!(\"[Remote] {msg}\");\n                            remote_server.msgs.push(msg);\n                        }\n                        Err(TryRecvError::Disconnected) => panic!(),\n                        Err(TryRecvError::Empty) => break,\n                    }\n                }\n\n                ScrollArea::vertical()\n                    .stick_to_bottom(true)\n                    .auto_shrink([false; 2])\n                    .show_rows(\n                        ui,\n                        ui.text_style_height(&TextStyle::Body),\n                        remote_server.msgs.len(),\n                        |ui, rows| {\n                            for row in rows {\n                                ui.label(&remote_server.msgs[row]);\n                            }\n                        },\n                    );\n\n                if button.clicked() {\n                    mem::take(&mut remote_server.stop)\n                        .unwrap()\n                        .send(())\n                        .unwrap();\n                    self.remote_state = ServerState::Stopping;\n                };\n            }\n            ServerState::Starting => {\n                let remote_server = self.remote_server.as_mut().unwrap();\n\n                if let Ok(result) = remote_server.started.try_recv() {\n                    if let Err(error) = result {\n                        self.remote_state = ServerState::Stopped(Some(error));\n                        self.remote_server = None;\n                    } else {\n                        self.remote_state = ServerState::Running;\n                    }\n                }\n\n                ui.add_enabled_ui(false, |ui| {\n                    let _ = ui.button(\"Starting..\");\n                });\n            }\n            ServerState::Stopping => {\n                if let Ok(()) = self\n                    .remote_server\n                    .as_mut()\n                    .unwrap()\n                    .done\n                    .as_mut()\n                    .unwrap()\n                    .try_recv()\n                {\n                    self.remote_state = ServerState::Stopped(None);\n                    self.remote_server = None;\n                }\n\n                ui.add_enabled_ui(false, |ui| {\n                    let _ = ui.button(\"Stopping..\");\n                });\n            }\n        }\n    }\n\n    fn start_monitor(&mut self, ctx: &egui::Context) {\n        self.save_settings();\n\n        let (signal_done, done) = oneshot::channel();\n\n        let ctx_ = ctx.clone();\n        let data = Arc::new(latency::Data::new(\n            ((self.settings.latency_monitor.history * 1000.0)\n                / self.settings.latency_monitor.latency_sample_interval as f64)\n                .round() as usize,\n            Arc::new(move || {\n                ctx_.request_repaint();\n            }),\n        ));\n\n        let ctx_ = ctx.clone();\n        let abort = latency::test_callback(\n            latency::Config {\n                port: protocol::PORT,\n                ping_interval: Duration::from_millis(\n                    self.settings.latency_monitor.latency_sample_interval,\n                ),\n            },\n            (!self.settings.latency_monitor.server.trim().is_empty())\n                .then_some(&self.settings.latency_monitor.server),\n            data.clone(),\n            Box::new(move |result| {\n                signal_done.send(result).map_err(|_| ()).unwrap();\n                ctx_.request_repaint();\n            }),\n        );\n\n        self.latency = Some(Latency {\n            done: Some(done),\n            abort: Some(abort),\n        });\n        self.latency_state = ClientState::Running;\n        self.latency_data = data;\n        self.latency_error = None;\n        self.latency_plot_reset = true;\n    }\n\n    fn monitor(&mut self, ctx: &egui::Context, ui: &mut Ui) {\n        let running = self.latency_state != ClientState::Stopped;\n\n        if !running {\n            ui.horizontal_wrapped(|ui| {\n                ui.label(\"Server address:\");\n                let response = ui.add(\n                    TextEdit::singleline(&mut self.settings.latency_monitor.server)\n                        .hint_text(\"(Locate local server)\"),\n                );\n                let enter = response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));\n\n                if ui.button(\"Start test\").clicked() || enter {\n                    self.start_monitor(ctx)\n                }\n            });\n        }\n\n        if running {\n            ui.horizontal(|ui| {\n                match self.latency_state {\n                    ClientState::Running => {\n                        if ui.button(\"Stop test\").clicked()\n                            || ui.input(|i| i.key_pressed(egui::Key::Space))\n                        {\n                            let latency = self.latency.as_mut().unwrap();\n                            mem::take(&mut latency.abort).unwrap().send(()).unwrap();\n                            self.latency_state = ClientState::Stopping;\n                        }\n                    }\n                    ClientState::Stopping => {\n                        ui.add_enabled_ui(false, |ui| {\n                            let _ = ui.button(\"Stopping test..\");\n                        });\n                    }\n                    ClientState::Stopped => {}\n                }\n\n                let state = match *self.latency_data.state.lock() {\n                    latency::State::Connecting => \"Connecting..\".to_owned(),\n                    latency::State::Monitoring { ref at } => format!(\"Connected to {at}\"),\n                    latency::State::Syncing => \"Synchronizing clocks..\".to_owned(),\n                };\n                ui.add(Label::new(state).wrap_mode(TextWrapMode::Truncate));\n\n                let latency = self.latency.as_mut().unwrap();\n\n                if let Ok(result) = latency.done.as_mut().unwrap().try_recv() {\n                    self.latency_error = match result {\n                        Some(Ok(())) => None,\n                        Some(Err(error)) => Some(error),\n                        None => Some(\"Aborted...\".to_owned()),\n                    };\n                    self.latency_stop = self.latency_data.start.elapsed();\n                    self.latency = None;\n                    self.latency_state = ClientState::Stopped;\n                }\n            });\n        }\n\n        ui.separator();\n\n        ui.add_enabled_ui(!running, |ui| {\n            Grid::new(\"latency-settings-compact\").show(ui, |ui| {\n                ui.label(\"History: \");\n                ui.add(\n                    egui::DragValue::new(&mut self.settings.latency_monitor.history)\n                        .range(0..=1000)\n                        .speed(0.05),\n                );\n                ui.label(\"seconds\");\n                ui.end_row();\n                ui.label(\"Latency sample interval:\");\n                ui.add(\n                    egui::DragValue::new(\n                        &mut self.settings.latency_monitor.latency_sample_interval,\n                    )\n                    .range(1..=1000)\n                    .speed(0.05),\n                );\n                ui.label(\"milliseconds\");\n            });\n        });\n\n        ui.separator();\n\n        if let Some(error) = self.latency_error.as_ref() {\n            ui.label(format!(\"Error: {}\", error));\n            ui.separator();\n        }\n\n        self.latency_data(ctx, ui);\n    }\n\n    fn latency_data(&mut self, ctx: &egui::Context, ui: &mut Ui) {\n        ui.vertical(|ui| {\n            let packet_loss_size = 80.0;\n            let height = ui.available_height();\n\n            let duration = self.settings.latency_monitor.history;\n\n            let points = self.latency_data.points.blocking_lock().clone();\n\n            let now = if self.latency_state == ClientState::Running {\n                ctx.request_repaint();\n                self.latency_data.start.elapsed()\n            } else {\n                self.latency_stop\n            }\n            .as_secs_f64();\n\n            let reset = mem::take(&mut self.latency_plot_reset);\n\n            let link = ui.id().with(\"latency-link\");\n\n            let y_axis_size = 30.0;\n\n            // Latency\n            let mut plot = Plot::new(\"latency-ping\")\n                .legend(Legend::default().insertion_order(true))\n                .link_axis(link, true, false)\n                .link_cursor(link, true, false)\n                .include_x(-duration)\n                .include_x(0.0)\n                .include_x(duration * 0.20)\n                .include_y(0.0)\n                .include_y(10.0)\n                .height(height - packet_loss_size)\n                .y_axis_min_width(y_axis_size)\n                .auto_bounds(Vec2b::new(false, true))\n                .label_formatter(|_, value| {\n                    format!(\"Latency = {:.2} ms\\nTime = {:.2} s\", value.y, value.x)\n                });\n\n            if reset {\n                plot = plot.reset();\n            }\n\n            ui.label(\"Latency\");\n            plot.show(ui, |plot_ui| {\n                let latency = points.iter().filter_map(|point| {\n                    point.up.map(|up| {\n                        let up = if let Some(total) = point.total {\n                            up.min(total)\n                        } else {\n                            up\n                        };\n                        [point.sent.as_secs_f64() - now, 1000.0 * up.as_secs_f64()]\n                    })\n                });\n                let latency = Line::new(PlotPoints::from_iter(latency))\n                    .color(Color32::from_rgb(37, 83, 169))\n                    .name(\"Up\");\n\n                plot_ui.line(latency);\n\n                let latency = points.iter().filter_map(|point| {\n                    point\n                        .up\n                        .and_then(|up| point.total.map(|total| total.saturating_sub(up)))\n                        .map(|down| [point.sent.as_secs_f64() - now, 1000.0 * down.as_secs_f64()])\n                });\n                let latency = Line::new(PlotPoints::from_iter(latency))\n                    .color(Color32::from_rgb(95, 145, 62))\n                    .name(\"Down\");\n\n                plot_ui.line(latency);\n\n                let latency = points.iter().filter_map(|point| {\n                    point\n                        .total\n                        .map(|total| [point.sent.as_secs_f64() - now, 1000.0 * total.as_secs_f64()])\n                });\n                let latency = Line::new(PlotPoints::from_iter(latency))\n                    .color(Color32::from_rgb(50, 50, 50))\n                    .name(\"Round-trip\");\n\n                plot_ui.line(latency);\n            });\n\n            // Packet loss\n            let mut plot = Plot::new(\"latency-loss\")\n                .legend(Legend::default())\n                .show_axes([false, true])\n                .show_grid(Vec2b::new(true, false))\n                .y_axis_min_width(y_axis_size)\n                .y_axis_formatter(|_, _| String::new())\n                .link_axis(link, true, false)\n                .link_cursor(link, true, false)\n                .center_y_axis(true)\n                .allow_zoom(false)\n                .allow_boxed_zoom(false)\n                .include_x(-duration)\n                .include_x(0.0)\n                .include_x(duration * 0.15)\n                .include_y(-1.0)\n                .include_y(1.0)\n                .height(30.0)\n                .label_formatter(|_, value| format!(\"Time = {:.2} s\", value.x));\n\n            if reset {\n                plot = plot.reset();\n            }\n\n            ui.label(\"Packet loss\");\n            plot.show(ui, |plot_ui| {\n                let loss = points\n                    .iter()\n                    .filter(|point| !point.pending && point.total.is_none());\n\n                for point in loss {\n                    let loss = point.sent.as_secs_f64() - now;\n\n                    let (color, s, e) = if point.up.is_some() {\n                        (Color32::from_rgb(95, 145, 62), 1.0, 0.0)\n                    } else {\n                        (Color32::from_rgb(37, 83, 169), -1.0, 0.0)\n                    };\n\n                    plot_ui.line(\n                        Line::new(PlotPoints::from_iter(\n                            [[loss, s], [loss, e]].iter().copied(),\n                        ))\n                        .color(color),\n                    );\n\n                    plot_ui.line(\n                        Line::new(PlotPoints::from_iter(\n                            [[loss, s], [loss, s - s / 5.0]].iter().copied(),\n                        ))\n                        .width(3.0)\n                        .color(color),\n                    );\n                }\n            });\n        });\n    }\n\n    pub fn show(&mut self, ctx: &egui::Context, ui: &mut Ui) {\n        ctx.input(|input| {\n            if let Some(file) = input\n                .raw\n                .dropped_files\n                .first()\n                .and_then(|file| file.path.as_deref())\n            {\n                RawResult::load(file).map(|raw| {\n                    self.load_file(file.to_owned(), raw);\n                    self.tab = Tab::Result;\n                });\n            }\n        });\n\n        let compact = ui.available_width() < 660.0;\n        ui.horizontal_wrapped(|ui| {\n            ui.selectable_value(&mut self.tab, Tab::Client, \"Client\");\n            ui.selectable_value(&mut self.tab, Tab::Server, \"Server\");\n            ui.selectable_value(&mut self.tab, Tab::Remote, \"Remote\");\n            ui.selectable_value(&mut self.tab, Tab::Monitor, \"Monitor\");\n            ui.selectable_value(&mut self.tab, Tab::Result, \"Result\");\n        });\n        ui.separator();\n\n        match self.tab {\n            Tab::Client => self.client(ctx, ui, compact),\n            Tab::Server => self.server(ctx, ui),\n            Tab::Remote => self.remote(ctx, ui),\n            Tab::Monitor => self.monitor(ctx, ui),\n            Tab::Result => self.result(ctx, ui),\n        }\n    }\n}\n"
  },
  {
    "path": "src/crusader-lib/Cargo.toml",
    "content": "[package]\nname = \"crusader-lib\"\nversion = \"0.1.0\"\nedition = \"2021\"\nbuild = \"build.rs\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[features]\nserver = []\nclient = [\"dep:plotters\", \"dep:axum\", \"dep:image\", \"dep:snap\", \"dep:serde_json\"]\n\n[dependencies]\nplotters = { version = \"0.3.6\", default-features = false, optional = true, features = [\n    \"ab_glyph\",\n    \"bitmap_backend\",\n    \"line_series\",\n    \"bitmap_encoder\",\n] }\nchrono = \"0.4.19\"\nbincode = \"1.3.3\"\nserde = { version = \"1.0.137\", features = [\"derive\"] }\nserde_json = { version = \"1.0.122\", optional = true }\nrand = \"0.8.5\"\nparking_lot = \"0.12.0\"\nhostname = \"0.4.0\"\ntokio = { version = \"1.18.2\", features = [\"full\"] }\ntokio-util = { version = \"0.7.2\", features = [\"codec\"] }\nfutures = \"0.3.21\"\nbytes = \"1.1.0\"\nsnap = { version = \"1.0.5\", optional = true }\nrmp-serde = \"1.1.0\"\nsocket2 = \"0.4.6\"\nnix = { version = \"0.29.0\", features = [\"net\"] }\nlibc = \"0.2\"\nanyhow = \"1.0.86\"\naxum = { version = \"0.7.5\", features = [\n    \"ws\",\n    \"tokio\",\n    \"http1\",\n], default-features = false, optional = true }\nimage = { version = \"0.24.9\", optional = true }\n\n[target.\"cfg(target_os = \\\"windows\\\")\".dependencies]\nipconfig = { version = \"=0.3.2\", default-features = false }\nwidestring = \"=1.1.0\"\n"
  },
  {
    "path": "src/crusader-lib/UFL.txt",
    "content": "-------------------------------\r\nUBUNTU FONT LICENCE Version 1.0\r\n-------------------------------\r\n\r\nPREAMBLE\r\nThis licence allows the licensed fonts to be used, studied, modified and\r\nredistributed freely. The fonts, including any derivative works, can be\r\nbundled, embedded, and redistributed provided the terms of this licence\r\nare met. The fonts and derivatives, however, cannot be released under\r\nany other licence. The requirement for fonts to remain under this\r\nlicence does not require any document created using the fonts or their\r\nderivatives to be published under this licence, as long as the primary\r\npurpose of the document is not to be a vehicle for the distribution of\r\nthe fonts.\r\n\r\nDEFINITIONS\r\n\"Font Software\" refers to the set of files released by the Copyright\r\nHolder(s) under this licence and clearly marked as such. This may\r\ninclude source files, build scripts and documentation.\r\n\r\n\"Original Version\" refers to the collection of Font Software components\r\nas received under this licence.\r\n\r\n\"Modified Version\" refers to any derivative made by adding to, deleting,\r\nor substituting -- in part or in whole -- any of the components of the\r\nOriginal Version, by changing formats or by porting the Font Software to\r\na new environment.\r\n\r\n\"Copyright Holder(s)\" refers to all individuals and companies who have a\r\ncopyright ownership of the Font Software.\r\n\r\n\"Substantially Changed\" refers to Modified Versions which can be easily\r\nidentified as dissimilar to the Font Software by users of the Font\r\nSoftware comparing the Original Version with the Modified Version.\r\n\r\nTo \"Propagate\" a work means to do anything with it that, without\r\npermission, would make you directly or secondarily liable for\r\ninfringement under applicable copyright law, except executing it on a\r\ncomputer or modifying a private copy. Propagation includes copying,\r\ndistribution (with or without modification and with or without charging\r\na redistribution fee), making available to the public, and in some\r\ncountries other activities as well.\r\n\r\nPERMISSION & CONDITIONS\r\nThis licence does not grant any rights under trademark law and all such\r\nrights are reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a\r\ncopy of the Font Software, to propagate the Font Software, subject to\r\nthe below conditions:\r\n\r\n1) Each copy of the Font Software must contain the above copyright\r\nnotice and this licence. These can be included either as stand-alone\r\ntext files, human-readable headers or in the appropriate machine-\r\nreadable metadata fields within text or binary files as long as those\r\nfields can be easily viewed by the user.\r\n\r\n2) The font name complies with the following:\r\n(a) The Original Version must retain its name, unmodified.\r\n(b) Modified Versions which are Substantially Changed must be renamed to\r\navoid use of the name of the Original Version or similar names entirely.\r\n(c) Modified Versions which are not Substantially Changed must be\r\nrenamed to both (i) retain the name of the Original Version and (ii) add\r\nadditional naming elements to distinguish the Modified Version from the\r\nOriginal Version. The name of such Modified Versions must be the name of\r\nthe Original Version, with \"derivative X\" where X represents the name of\r\nthe new work, appended to that name.\r\n\r\n3) The name(s) of the Copyright Holder(s) and any contributor to the\r\nFont Software shall not be used to promote, endorse or advertise any\r\nModified Version, except (i) as required by this licence, (ii) to\r\nacknowledge the contribution(s) of the Copyright Holder(s) or (iii) with\r\ntheir explicit written permission.\r\n\r\n4) The Font Software, modified or unmodified, in part or in whole, must\r\nbe distributed entirely under this licence, and must not be distributed\r\nunder any other licence. The requirement for fonts to remain under this\r\nlicence does not affect any document created using the Font Software,\r\nexcept any version of the Font Software extracted from a document\r\ncreated using the Font Software may only be distributed under this\r\nlicence.\r\n\r\nTERMINATION\r\nThis licence becomes null and void if any of the above conditions are\r\nnot met.\r\n\r\nDISCLAIMER\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF\r\nCOPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER\r\nDEALINGS IN THE FONT SOFTWARE.\r\n"
  },
  {
    "path": "src/crusader-lib/assets/vue.js",
    "content": "/**\n* vue v3.4.35\n* (c) 2018-present Yuxi (Evan) You and Vue contributors\n* @license MIT\n**/\n/*! #__NO_SIDE_EFFECTS__ */\n// @__NO_SIDE_EFFECTS__\nfunction makeMap(str, expectsLowerCase) {\n  const set = new Set(str.split(\",\"));\n  return expectsLowerCase ? (val) => set.has(val.toLowerCase()) : (val) => set.has(val);\n}\n\nconst EMPTY_OBJ = Object.freeze({}) ;\nconst EMPTY_ARR = Object.freeze([]) ;\nconst NOOP = () => {\n};\nconst NO = () => false;\nconst isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // uppercase letter\n(key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);\nconst isModelListener = (key) => key.startsWith(\"onUpdate:\");\nconst extend = Object.assign;\nconst remove = (arr, el) => {\n  const i = arr.indexOf(el);\n  if (i > -1) {\n    arr.splice(i, 1);\n  }\n};\nconst hasOwnProperty$1 = Object.prototype.hasOwnProperty;\nconst hasOwn = (val, key) => hasOwnProperty$1.call(val, key);\nconst isArray = Array.isArray;\nconst isMap = (val) => toTypeString(val) === \"[object Map]\";\nconst isSet = (val) => toTypeString(val) === \"[object Set]\";\nconst isDate = (val) => toTypeString(val) === \"[object Date]\";\nconst isRegExp = (val) => toTypeString(val) === \"[object RegExp]\";\nconst isFunction = (val) => typeof val === \"function\";\nconst isString = (val) => typeof val === \"string\";\nconst isSymbol = (val) => typeof val === \"symbol\";\nconst isObject = (val) => val !== null && typeof val === \"object\";\nconst isPromise = (val) => {\n  return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch);\n};\nconst objectToString = Object.prototype.toString;\nconst toTypeString = (value) => objectToString.call(value);\nconst toRawType = (value) => {\n  return toTypeString(value).slice(8, -1);\n};\nconst isPlainObject = (val) => toTypeString(val) === \"[object Object]\";\nconst isIntegerKey = (key) => isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\nconst isReservedProp = /* @__PURE__ */ makeMap(\n  // the leading comma is intentional so empty string \"\" is also included\n  \",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted\"\n);\nconst isBuiltInDirective = /* @__PURE__ */ makeMap(\n  \"bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo\"\n);\nconst cacheStringFunction = (fn) => {\n  const cache = /* @__PURE__ */ Object.create(null);\n  return (str) => {\n    const hit = cache[str];\n    return hit || (cache[str] = fn(str));\n  };\n};\nconst camelizeRE = /-(\\w)/g;\nconst camelize = cacheStringFunction((str) => {\n  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : \"\");\n});\nconst hyphenateRE = /\\B([A-Z])/g;\nconst hyphenate = cacheStringFunction(\n  (str) => str.replace(hyphenateRE, \"-$1\").toLowerCase()\n);\nconst capitalize = cacheStringFunction((str) => {\n  return str.charAt(0).toUpperCase() + str.slice(1);\n});\nconst toHandlerKey = cacheStringFunction((str) => {\n  const s = str ? `on${capitalize(str)}` : ``;\n  return s;\n});\nconst hasChanged = (value, oldValue) => !Object.is(value, oldValue);\nconst invokeArrayFns = (fns, ...arg) => {\n  for (let i = 0; i < fns.length; i++) {\n    fns[i](...arg);\n  }\n};\nconst def = (obj, key, value, writable = false) => {\n  Object.defineProperty(obj, key, {\n    configurable: true,\n    enumerable: false,\n    writable,\n    value\n  });\n};\nconst looseToNumber = (val) => {\n  const n = parseFloat(val);\n  return isNaN(n) ? val : n;\n};\nconst toNumber = (val) => {\n  const n = isString(val) ? Number(val) : NaN;\n  return isNaN(n) ? val : n;\n};\nlet _globalThis;\nconst getGlobalThis = () => {\n  return _globalThis || (_globalThis = typeof globalThis !== \"undefined\" ? globalThis : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : typeof global !== \"undefined\" ? global : {});\n};\n\nconst PatchFlagNames = {\n  [1]: `TEXT`,\n  [2]: `CLASS`,\n  [4]: `STYLE`,\n  [8]: `PROPS`,\n  [16]: `FULL_PROPS`,\n  [32]: `NEED_HYDRATION`,\n  [64]: `STABLE_FRAGMENT`,\n  [128]: `KEYED_FRAGMENT`,\n  [256]: `UNKEYED_FRAGMENT`,\n  [512]: `NEED_PATCH`,\n  [1024]: `DYNAMIC_SLOTS`,\n  [2048]: `DEV_ROOT_FRAGMENT`,\n  [-1]: `HOISTED`,\n  [-2]: `BAIL`\n};\n\nconst slotFlagsText = {\n  [1]: \"STABLE\",\n  [2]: \"DYNAMIC\",\n  [3]: \"FORWARDED\"\n};\n\nconst GLOBALS_ALLOWED = \"Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error\";\nconst isGloballyAllowed = /* @__PURE__ */ makeMap(GLOBALS_ALLOWED);\n\nconst range = 2;\nfunction generateCodeFrame(source, start = 0, end = source.length) {\n  start = Math.max(0, Math.min(start, source.length));\n  end = Math.max(0, Math.min(end, source.length));\n  if (start > end) return \"\";\n  let lines = source.split(/(\\r?\\n)/);\n  const newlineSequences = lines.filter((_, idx) => idx % 2 === 1);\n  lines = lines.filter((_, idx) => idx % 2 === 0);\n  let count = 0;\n  const res = [];\n  for (let i = 0; i < lines.length; i++) {\n    count += lines[i].length + (newlineSequences[i] && newlineSequences[i].length || 0);\n    if (count >= start) {\n      for (let j = i - range; j <= i + range || end > count; j++) {\n        if (j < 0 || j >= lines.length) continue;\n        const line = j + 1;\n        res.push(\n          `${line}${\" \".repeat(Math.max(3 - String(line).length, 0))}|  ${lines[j]}`\n        );\n        const lineLength = lines[j].length;\n        const newLineSeqLength = newlineSequences[j] && newlineSequences[j].length || 0;\n        if (j === i) {\n          const pad = start - (count - (lineLength + newLineSeqLength));\n          const length = Math.max(\n            1,\n            end > count ? lineLength - pad : end - start\n          );\n          res.push(`   |  ` + \" \".repeat(pad) + \"^\".repeat(length));\n        } else if (j > i) {\n          if (end > count) {\n            const length = Math.max(Math.min(end - count, lineLength), 1);\n            res.push(`   |  ` + \"^\".repeat(length));\n          }\n          count += lineLength + newLineSeqLength;\n        }\n      }\n      break;\n    }\n  }\n  return res.join(\"\\n\");\n}\n\nfunction normalizeStyle(value) {\n  if (isArray(value)) {\n    const res = {};\n    for (let i = 0; i < value.length; i++) {\n      const item = value[i];\n      const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item);\n      if (normalized) {\n        for (const key in normalized) {\n          res[key] = normalized[key];\n        }\n      }\n    }\n    return res;\n  } else if (isString(value) || isObject(value)) {\n    return value;\n  }\n}\nconst listDelimiterRE = /;(?![^(]*\\))/g;\nconst propertyDelimiterRE = /:([^]+)/;\nconst styleCommentRE = /\\/\\*[^]*?\\*\\//g;\nfunction parseStringStyle(cssText) {\n  const ret = {};\n  cssText.replace(styleCommentRE, \"\").split(listDelimiterRE).forEach((item) => {\n    if (item) {\n      const tmp = item.split(propertyDelimiterRE);\n      tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n    }\n  });\n  return ret;\n}\nfunction stringifyStyle(styles) {\n  let ret = \"\";\n  if (!styles || isString(styles)) {\n    return ret;\n  }\n  for (const key in styles) {\n    const value = styles[key];\n    if (isString(value) || typeof value === \"number\") {\n      const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key);\n      ret += `${normalizedKey}:${value};`;\n    }\n  }\n  return ret;\n}\nfunction normalizeClass(value) {\n  let res = \"\";\n  if (isString(value)) {\n    res = value;\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      const normalized = normalizeClass(value[i]);\n      if (normalized) {\n        res += normalized + \" \";\n      }\n    }\n  } else if (isObject(value)) {\n    for (const name in value) {\n      if (value[name]) {\n        res += name + \" \";\n      }\n    }\n  }\n  return res.trim();\n}\nfunction normalizeProps(props) {\n  if (!props) return null;\n  let { class: klass, style } = props;\n  if (klass && !isString(klass)) {\n    props.class = normalizeClass(klass);\n  }\n  if (style) {\n    props.style = normalizeStyle(style);\n  }\n  return props;\n}\n\nconst HTML_TAGS = \"html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot\";\nconst SVG_TAGS = \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\";\nconst MATH_TAGS = \"annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics\";\nconst VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\nconst isHTMLTag = /* @__PURE__ */ makeMap(HTML_TAGS);\nconst isSVGTag = /* @__PURE__ */ makeMap(SVG_TAGS);\nconst isMathMLTag = /* @__PURE__ */ makeMap(MATH_TAGS);\nconst isVoidTag = /* @__PURE__ */ makeMap(VOID_TAGS);\n\nconst specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;\nconst isSpecialBooleanAttr = /* @__PURE__ */ makeMap(specialBooleanAttrs);\nconst isBooleanAttr = /* @__PURE__ */ makeMap(\n  specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected`\n);\nfunction includeBooleanAttr(value) {\n  return !!value || value === \"\";\n}\nconst isKnownHtmlAttr = /* @__PURE__ */ makeMap(\n  `accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap`\n);\nconst isKnownSvgAttr = /* @__PURE__ */ makeMap(\n  `xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`\n);\nfunction isRenderableAttrValue(value) {\n  if (value == null) {\n    return false;\n  }\n  const type = typeof value;\n  return type === \"string\" || type === \"number\" || type === \"boolean\";\n}\n\nfunction looseCompareArrays(a, b) {\n  if (a.length !== b.length) return false;\n  let equal = true;\n  for (let i = 0; equal && i < a.length; i++) {\n    equal = looseEqual(a[i], b[i]);\n  }\n  return equal;\n}\nfunction looseEqual(a, b) {\n  if (a === b) return true;\n  let aValidType = isDate(a);\n  let bValidType = isDate(b);\n  if (aValidType || bValidType) {\n    return aValidType && bValidType ? a.getTime() === b.getTime() : false;\n  }\n  aValidType = isSymbol(a);\n  bValidType = isSymbol(b);\n  if (aValidType || bValidType) {\n    return a === b;\n  }\n  aValidType = isArray(a);\n  bValidType = isArray(b);\n  if (aValidType || bValidType) {\n    return aValidType && bValidType ? looseCompareArrays(a, b) : false;\n  }\n  aValidType = isObject(a);\n  bValidType = isObject(b);\n  if (aValidType || bValidType) {\n    if (!aValidType || !bValidType) {\n      return false;\n    }\n    const aKeysCount = Object.keys(a).length;\n    const bKeysCount = Object.keys(b).length;\n    if (aKeysCount !== bKeysCount) {\n      return false;\n    }\n    for (const key in a) {\n      const aHasKey = a.hasOwnProperty(key);\n      const bHasKey = b.hasOwnProperty(key);\n      if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) {\n        return false;\n      }\n    }\n  }\n  return String(a) === String(b);\n}\nfunction looseIndexOf(arr, val) {\n  return arr.findIndex((item) => looseEqual(item, val));\n}\n\nconst isRef$1 = (val) => {\n  return !!(val && val.__v_isRef === true);\n};\nconst toDisplayString = (val) => {\n  return isString(val) ? val : val == null ? \"\" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef$1(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val);\n};\nconst replacer = (_key, val) => {\n  if (isRef$1(val)) {\n    return replacer(_key, val.value);\n  } else if (isMap(val)) {\n    return {\n      [`Map(${val.size})`]: [...val.entries()].reduce(\n        (entries, [key, val2], i) => {\n          entries[stringifySymbol(key, i) + \" =>\"] = val2;\n          return entries;\n        },\n        {}\n      )\n    };\n  } else if (isSet(val)) {\n    return {\n      [`Set(${val.size})`]: [...val.values()].map((v) => stringifySymbol(v))\n    };\n  } else if (isSymbol(val)) {\n    return stringifySymbol(val);\n  } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {\n    return String(val);\n  }\n  return val;\n};\nconst stringifySymbol = (v, i = \"\") => {\n  var _a;\n  return (\n    // Symbol.description in es2019+ so we need to cast here to pass\n    // the lib: es2016 check\n    isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v\n  );\n};\n\nfunction warn$2(msg, ...args) {\n  console.warn(`[Vue warn] ${msg}`, ...args);\n}\n\nlet activeEffectScope;\nclass EffectScope {\n  constructor(detached = false) {\n    this.detached = detached;\n    /**\n     * @internal\n     */\n    this._active = true;\n    /**\n     * @internal\n     */\n    this.effects = [];\n    /**\n     * @internal\n     */\n    this.cleanups = [];\n    this.parent = activeEffectScope;\n    if (!detached && activeEffectScope) {\n      this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(\n        this\n      ) - 1;\n    }\n  }\n  get active() {\n    return this._active;\n  }\n  run(fn) {\n    if (this._active) {\n      const currentEffectScope = activeEffectScope;\n      try {\n        activeEffectScope = this;\n        return fn();\n      } finally {\n        activeEffectScope = currentEffectScope;\n      }\n    } else {\n      warn$2(`cannot run an inactive effect scope.`);\n    }\n  }\n  /**\n   * This should only be called on non-detached scopes\n   * @internal\n   */\n  on() {\n    activeEffectScope = this;\n  }\n  /**\n   * This should only be called on non-detached scopes\n   * @internal\n   */\n  off() {\n    activeEffectScope = this.parent;\n  }\n  stop(fromParent) {\n    if (this._active) {\n      let i, l;\n      for (i = 0, l = this.effects.length; i < l; i++) {\n        this.effects[i].stop();\n      }\n      for (i = 0, l = this.cleanups.length; i < l; i++) {\n        this.cleanups[i]();\n      }\n      if (this.scopes) {\n        for (i = 0, l = this.scopes.length; i < l; i++) {\n          this.scopes[i].stop(true);\n        }\n      }\n      if (!this.detached && this.parent && !fromParent) {\n        const last = this.parent.scopes.pop();\n        if (last && last !== this) {\n          this.parent.scopes[this.index] = last;\n          last.index = this.index;\n        }\n      }\n      this.parent = void 0;\n      this._active = false;\n    }\n  }\n}\nfunction effectScope(detached) {\n  return new EffectScope(detached);\n}\nfunction recordEffectScope(effect, scope = activeEffectScope) {\n  if (scope && scope.active) {\n    scope.effects.push(effect);\n  }\n}\nfunction getCurrentScope() {\n  return activeEffectScope;\n}\nfunction onScopeDispose(fn) {\n  if (activeEffectScope) {\n    activeEffectScope.cleanups.push(fn);\n  } else {\n    warn$2(\n      `onScopeDispose() is called when there is no active effect scope to be associated with.`\n    );\n  }\n}\n\nlet activeEffect;\nclass ReactiveEffect {\n  constructor(fn, trigger, scheduler, scope) {\n    this.fn = fn;\n    this.trigger = trigger;\n    this.scheduler = scheduler;\n    this.active = true;\n    this.deps = [];\n    /**\n     * @internal\n     */\n    this._dirtyLevel = 4;\n    /**\n     * @internal\n     */\n    this._trackId = 0;\n    /**\n     * @internal\n     */\n    this._runnings = 0;\n    /**\n     * @internal\n     */\n    this._shouldSchedule = false;\n    /**\n     * @internal\n     */\n    this._depsLength = 0;\n    recordEffectScope(this, scope);\n  }\n  get dirty() {\n    if (this._dirtyLevel === 2 || this._dirtyLevel === 3) {\n      this._dirtyLevel = 1;\n      pauseTracking();\n      for (let i = 0; i < this._depsLength; i++) {\n        const dep = this.deps[i];\n        if (dep.computed) {\n          triggerComputed(dep.computed);\n          if (this._dirtyLevel >= 4) {\n            break;\n          }\n        }\n      }\n      if (this._dirtyLevel === 1) {\n        this._dirtyLevel = 0;\n      }\n      resetTracking();\n    }\n    return this._dirtyLevel >= 4;\n  }\n  set dirty(v) {\n    this._dirtyLevel = v ? 4 : 0;\n  }\n  run() {\n    this._dirtyLevel = 0;\n    if (!this.active) {\n      return this.fn();\n    }\n    let lastShouldTrack = shouldTrack;\n    let lastEffect = activeEffect;\n    try {\n      shouldTrack = true;\n      activeEffect = this;\n      this._runnings++;\n      preCleanupEffect(this);\n      return this.fn();\n    } finally {\n      postCleanupEffect(this);\n      this._runnings--;\n      activeEffect = lastEffect;\n      shouldTrack = lastShouldTrack;\n    }\n  }\n  stop() {\n    if (this.active) {\n      preCleanupEffect(this);\n      postCleanupEffect(this);\n      this.onStop && this.onStop();\n      this.active = false;\n    }\n  }\n}\nfunction triggerComputed(computed) {\n  return computed.value;\n}\nfunction preCleanupEffect(effect2) {\n  effect2._trackId++;\n  effect2._depsLength = 0;\n}\nfunction postCleanupEffect(effect2) {\n  if (effect2.deps.length > effect2._depsLength) {\n    for (let i = effect2._depsLength; i < effect2.deps.length; i++) {\n      cleanupDepEffect(effect2.deps[i], effect2);\n    }\n    effect2.deps.length = effect2._depsLength;\n  }\n}\nfunction cleanupDepEffect(dep, effect2) {\n  const trackId = dep.get(effect2);\n  if (trackId !== void 0 && effect2._trackId !== trackId) {\n    dep.delete(effect2);\n    if (dep.size === 0) {\n      dep.cleanup();\n    }\n  }\n}\nfunction effect(fn, options) {\n  if (fn.effect instanceof ReactiveEffect) {\n    fn = fn.effect.fn;\n  }\n  const _effect = new ReactiveEffect(fn, NOOP, () => {\n    if (_effect.dirty) {\n      _effect.run();\n    }\n  });\n  if (options) {\n    extend(_effect, options);\n    if (options.scope) recordEffectScope(_effect, options.scope);\n  }\n  if (!options || !options.lazy) {\n    _effect.run();\n  }\n  const runner = _effect.run.bind(_effect);\n  runner.effect = _effect;\n  return runner;\n}\nfunction stop(runner) {\n  runner.effect.stop();\n}\nlet shouldTrack = true;\nlet pauseScheduleStack = 0;\nconst trackStack = [];\nfunction pauseTracking() {\n  trackStack.push(shouldTrack);\n  shouldTrack = false;\n}\nfunction resetTracking() {\n  const last = trackStack.pop();\n  shouldTrack = last === void 0 ? true : last;\n}\nfunction pauseScheduling() {\n  pauseScheduleStack++;\n}\nfunction resetScheduling() {\n  pauseScheduleStack--;\n  while (!pauseScheduleStack && queueEffectSchedulers.length) {\n    queueEffectSchedulers.shift()();\n  }\n}\nfunction trackEffect(effect2, dep, debuggerEventExtraInfo) {\n  var _a;\n  if (dep.get(effect2) !== effect2._trackId) {\n    dep.set(effect2, effect2._trackId);\n    const oldDep = effect2.deps[effect2._depsLength];\n    if (oldDep !== dep) {\n      if (oldDep) {\n        cleanupDepEffect(oldDep, effect2);\n      }\n      effect2.deps[effect2._depsLength++] = dep;\n    } else {\n      effect2._depsLength++;\n    }\n    {\n      (_a = effect2.onTrack) == null ? void 0 : _a.call(effect2, extend({ effect: effect2 }, debuggerEventExtraInfo));\n    }\n  }\n}\nconst queueEffectSchedulers = [];\nfunction triggerEffects(dep, dirtyLevel, debuggerEventExtraInfo) {\n  var _a;\n  pauseScheduling();\n  for (const effect2 of dep.keys()) {\n    let tracking;\n    if (effect2._dirtyLevel < dirtyLevel && (tracking != null ? tracking : tracking = dep.get(effect2) === effect2._trackId)) {\n      effect2._shouldSchedule || (effect2._shouldSchedule = effect2._dirtyLevel === 0);\n      effect2._dirtyLevel = dirtyLevel;\n    }\n    if (effect2._shouldSchedule && (tracking != null ? tracking : tracking = dep.get(effect2) === effect2._trackId)) {\n      {\n        (_a = effect2.onTrigger) == null ? void 0 : _a.call(effect2, extend({ effect: effect2 }, debuggerEventExtraInfo));\n      }\n      effect2.trigger();\n      if ((!effect2._runnings || effect2.allowRecurse) && effect2._dirtyLevel !== 2) {\n        effect2._shouldSchedule = false;\n        if (effect2.scheduler) {\n          queueEffectSchedulers.push(effect2.scheduler);\n        }\n      }\n    }\n  }\n  resetScheduling();\n}\n\nconst createDep = (cleanup, computed) => {\n  const dep = /* @__PURE__ */ new Map();\n  dep.cleanup = cleanup;\n  dep.computed = computed;\n  return dep;\n};\n\nconst targetMap = /* @__PURE__ */ new WeakMap();\nconst ITERATE_KEY = Symbol(\"iterate\" );\nconst MAP_KEY_ITERATE_KEY = Symbol(\"Map key iterate\" );\nfunction track(target, type, key) {\n  if (shouldTrack && activeEffect) {\n    let depsMap = targetMap.get(target);\n    if (!depsMap) {\n      targetMap.set(target, depsMap = /* @__PURE__ */ new Map());\n    }\n    let dep = depsMap.get(key);\n    if (!dep) {\n      depsMap.set(key, dep = createDep(() => depsMap.delete(key)));\n    }\n    trackEffect(\n      activeEffect,\n      dep,\n      {\n        target,\n        type,\n        key\n      } \n    );\n  }\n}\nfunction trigger(target, type, key, newValue, oldValue, oldTarget) {\n  const depsMap = targetMap.get(target);\n  if (!depsMap) {\n    return;\n  }\n  let deps = [];\n  if (type === \"clear\") {\n    deps = [...depsMap.values()];\n  } else if (key === \"length\" && isArray(target)) {\n    const newLength = Number(newValue);\n    depsMap.forEach((dep, key2) => {\n      if (key2 === \"length\" || !isSymbol(key2) && key2 >= newLength) {\n        deps.push(dep);\n      }\n    });\n  } else {\n    if (key !== void 0) {\n      deps.push(depsMap.get(key));\n    }\n    switch (type) {\n      case \"add\":\n        if (!isArray(target)) {\n          deps.push(depsMap.get(ITERATE_KEY));\n          if (isMap(target)) {\n            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));\n          }\n        } else if (isIntegerKey(key)) {\n          deps.push(depsMap.get(\"length\"));\n        }\n        break;\n      case \"delete\":\n        if (!isArray(target)) {\n          deps.push(depsMap.get(ITERATE_KEY));\n          if (isMap(target)) {\n            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));\n          }\n        }\n        break;\n      case \"set\":\n        if (isMap(target)) {\n          deps.push(depsMap.get(ITERATE_KEY));\n        }\n        break;\n    }\n  }\n  pauseScheduling();\n  for (const dep of deps) {\n    if (dep) {\n      triggerEffects(\n        dep,\n        4,\n        {\n          target,\n          type,\n          key,\n          newValue,\n          oldValue,\n          oldTarget\n        } \n      );\n    }\n  }\n  resetScheduling();\n}\nfunction getDepFromReactive(object, key) {\n  const depsMap = targetMap.get(object);\n  return depsMap && depsMap.get(key);\n}\n\nconst isNonTrackableKeys = /* @__PURE__ */ makeMap(`__proto__,__v_isRef,__isVue`);\nconst builtInSymbols = new Set(\n  /* @__PURE__ */ Object.getOwnPropertyNames(Symbol).filter((key) => key !== \"arguments\" && key !== \"caller\").map((key) => Symbol[key]).filter(isSymbol)\n);\nconst arrayInstrumentations = /* @__PURE__ */ createArrayInstrumentations();\nfunction createArrayInstrumentations() {\n  const instrumentations = {};\n  [\"includes\", \"indexOf\", \"lastIndexOf\"].forEach((key) => {\n    instrumentations[key] = function(...args) {\n      const arr = toRaw(this);\n      for (let i = 0, l = this.length; i < l; i++) {\n        track(arr, \"get\", i + \"\");\n      }\n      const res = arr[key](...args);\n      if (res === -1 || res === false) {\n        return arr[key](...args.map(toRaw));\n      } else {\n        return res;\n      }\n    };\n  });\n  [\"push\", \"pop\", \"shift\", \"unshift\", \"splice\"].forEach((key) => {\n    instrumentations[key] = function(...args) {\n      pauseTracking();\n      pauseScheduling();\n      const res = toRaw(this)[key].apply(this, args);\n      resetScheduling();\n      resetTracking();\n      return res;\n    };\n  });\n  return instrumentations;\n}\nfunction hasOwnProperty(key) {\n  if (!isSymbol(key)) key = String(key);\n  const obj = toRaw(this);\n  track(obj, \"has\", key);\n  return obj.hasOwnProperty(key);\n}\nclass BaseReactiveHandler {\n  constructor(_isReadonly = false, _isShallow = false) {\n    this._isReadonly = _isReadonly;\n    this._isShallow = _isShallow;\n  }\n  get(target, key, receiver) {\n    const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow;\n    if (key === \"__v_isReactive\") {\n      return !isReadonly2;\n    } else if (key === \"__v_isReadonly\") {\n      return isReadonly2;\n    } else if (key === \"__v_isShallow\") {\n      return isShallow2;\n    } else if (key === \"__v_raw\") {\n      if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype\n      // this means the reciever is a user proxy of the reactive proxy\n      Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {\n        return target;\n      }\n      return;\n    }\n    const targetIsArray = isArray(target);\n    if (!isReadonly2) {\n      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {\n        return Reflect.get(arrayInstrumentations, key, receiver);\n      }\n      if (key === \"hasOwnProperty\") {\n        return hasOwnProperty;\n      }\n    }\n    const res = Reflect.get(target, key, receiver);\n    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {\n      return res;\n    }\n    if (!isReadonly2) {\n      track(target, \"get\", key);\n    }\n    if (isShallow2) {\n      return res;\n    }\n    if (isRef(res)) {\n      return targetIsArray && isIntegerKey(key) ? res : res.value;\n    }\n    if (isObject(res)) {\n      return isReadonly2 ? readonly(res) : reactive(res);\n    }\n    return res;\n  }\n}\nclass MutableReactiveHandler extends BaseReactiveHandler {\n  constructor(isShallow2 = false) {\n    super(false, isShallow2);\n  }\n  set(target, key, value, receiver) {\n    let oldValue = target[key];\n    if (!this._isShallow) {\n      const isOldValueReadonly = isReadonly(oldValue);\n      if (!isShallow(value) && !isReadonly(value)) {\n        oldValue = toRaw(oldValue);\n        value = toRaw(value);\n      }\n      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {\n        if (isOldValueReadonly) {\n          return false;\n        } else {\n          oldValue.value = value;\n          return true;\n        }\n      }\n    }\n    const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);\n    const result = Reflect.set(target, key, value, receiver);\n    if (target === toRaw(receiver)) {\n      if (!hadKey) {\n        trigger(target, \"add\", key, value);\n      } else if (hasChanged(value, oldValue)) {\n        trigger(target, \"set\", key, value, oldValue);\n      }\n    }\n    return result;\n  }\n  deleteProperty(target, key) {\n    const hadKey = hasOwn(target, key);\n    const oldValue = target[key];\n    const result = Reflect.deleteProperty(target, key);\n    if (result && hadKey) {\n      trigger(target, \"delete\", key, void 0, oldValue);\n    }\n    return result;\n  }\n  has(target, key) {\n    const result = Reflect.has(target, key);\n    if (!isSymbol(key) || !builtInSymbols.has(key)) {\n      track(target, \"has\", key);\n    }\n    return result;\n  }\n  ownKeys(target) {\n    track(\n      target,\n      \"iterate\",\n      isArray(target) ? \"length\" : ITERATE_KEY\n    );\n    return Reflect.ownKeys(target);\n  }\n}\nclass ReadonlyReactiveHandler extends BaseReactiveHandler {\n  constructor(isShallow2 = false) {\n    super(true, isShallow2);\n  }\n  set(target, key) {\n    {\n      warn$2(\n        `Set operation on key \"${String(key)}\" failed: target is readonly.`,\n        target\n      );\n    }\n    return true;\n  }\n  deleteProperty(target, key) {\n    {\n      warn$2(\n        `Delete operation on key \"${String(key)}\" failed: target is readonly.`,\n        target\n      );\n    }\n    return true;\n  }\n}\nconst mutableHandlers = /* @__PURE__ */ new MutableReactiveHandler();\nconst readonlyHandlers = /* @__PURE__ */ new ReadonlyReactiveHandler();\nconst shallowReactiveHandlers = /* @__PURE__ */ new MutableReactiveHandler(\n  true\n);\nconst shallowReadonlyHandlers = /* @__PURE__ */ new ReadonlyReactiveHandler(true);\n\nconst toShallow = (value) => value;\nconst getProto = (v) => Reflect.getPrototypeOf(v);\nfunction get(target, key, isReadonly2 = false, isShallow2 = false) {\n  target = target[\"__v_raw\"];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  if (!isReadonly2) {\n    if (hasChanged(key, rawKey)) {\n      track(rawTarget, \"get\", key);\n    }\n    track(rawTarget, \"get\", rawKey);\n  }\n  const { has: has2 } = getProto(rawTarget);\n  const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive;\n  if (has2.call(rawTarget, key)) {\n    return wrap(target.get(key));\n  } else if (has2.call(rawTarget, rawKey)) {\n    return wrap(target.get(rawKey));\n  } else if (target !== rawTarget) {\n    target.get(key);\n  }\n}\nfunction has(key, isReadonly2 = false) {\n  const target = this[\"__v_raw\"];\n  const rawTarget = toRaw(target);\n  const rawKey = toRaw(key);\n  if (!isReadonly2) {\n    if (hasChanged(key, rawKey)) {\n      track(rawTarget, \"has\", key);\n    }\n    track(rawTarget, \"has\", rawKey);\n  }\n  return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey);\n}\nfunction size(target, isReadonly2 = false) {\n  target = target[\"__v_raw\"];\n  !isReadonly2 && track(toRaw(target), \"iterate\", ITERATE_KEY);\n  return Reflect.get(target, \"size\", target);\n}\nfunction add(value, _isShallow = false) {\n  if (!_isShallow && !isShallow(value) && !isReadonly(value)) {\n    value = toRaw(value);\n  }\n  const target = toRaw(this);\n  const proto = getProto(target);\n  const hadKey = proto.has.call(target, value);\n  if (!hadKey) {\n    target.add(value);\n    trigger(target, \"add\", value, value);\n  }\n  return this;\n}\nfunction set(key, value, _isShallow = false) {\n  if (!_isShallow && !isShallow(value) && !isReadonly(value)) {\n    value = toRaw(value);\n  }\n  const target = toRaw(this);\n  const { has: has2, get: get2 } = getProto(target);\n  let hadKey = has2.call(target, key);\n  if (!hadKey) {\n    key = toRaw(key);\n    hadKey = has2.call(target, key);\n  } else {\n    checkIdentityKeys(target, has2, key);\n  }\n  const oldValue = get2.call(target, key);\n  target.set(key, value);\n  if (!hadKey) {\n    trigger(target, \"add\", key, value);\n  } else if (hasChanged(value, oldValue)) {\n    trigger(target, \"set\", key, value, oldValue);\n  }\n  return this;\n}\nfunction deleteEntry(key) {\n  const target = toRaw(this);\n  const { has: has2, get: get2 } = getProto(target);\n  let hadKey = has2.call(target, key);\n  if (!hadKey) {\n    key = toRaw(key);\n    hadKey = has2.call(target, key);\n  } else {\n    checkIdentityKeys(target, has2, key);\n  }\n  const oldValue = get2 ? get2.call(target, key) : void 0;\n  const result = target.delete(key);\n  if (hadKey) {\n    trigger(target, \"delete\", key, void 0, oldValue);\n  }\n  return result;\n}\nfunction clear() {\n  const target = toRaw(this);\n  const hadItems = target.size !== 0;\n  const oldTarget = isMap(target) ? new Map(target) : new Set(target) ;\n  const result = target.clear();\n  if (hadItems) {\n    trigger(target, \"clear\", void 0, void 0, oldTarget);\n  }\n  return result;\n}\nfunction createForEach(isReadonly2, isShallow2) {\n  return function forEach(callback, thisArg) {\n    const observed = this;\n    const target = observed[\"__v_raw\"];\n    const rawTarget = toRaw(target);\n    const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive;\n    !isReadonly2 && track(rawTarget, \"iterate\", ITERATE_KEY);\n    return target.forEach((value, key) => {\n      return callback.call(thisArg, wrap(value), wrap(key), observed);\n    });\n  };\n}\nfunction createIterableMethod(method, isReadonly2, isShallow2) {\n  return function(...args) {\n    const target = this[\"__v_raw\"];\n    const rawTarget = toRaw(target);\n    const targetIsMap = isMap(rawTarget);\n    const isPair = method === \"entries\" || method === Symbol.iterator && targetIsMap;\n    const isKeyOnly = method === \"keys\" && targetIsMap;\n    const innerIterator = target[method](...args);\n    const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive;\n    !isReadonly2 && track(\n      rawTarget,\n      \"iterate\",\n      isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY\n    );\n    return {\n      // iterator protocol\n      next() {\n        const { value, done } = innerIterator.next();\n        return done ? { value, done } : {\n          value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),\n          done\n        };\n      },\n      // iterable protocol\n      [Symbol.iterator]() {\n        return this;\n      }\n    };\n  };\n}\nfunction createReadonlyMethod(type) {\n  return function(...args) {\n    {\n      const key = args[0] ? `on key \"${args[0]}\" ` : ``;\n      warn$2(\n        `${capitalize(type)} operation ${key}failed: target is readonly.`,\n        toRaw(this)\n      );\n    }\n    return type === \"delete\" ? false : type === \"clear\" ? void 0 : this;\n  };\n}\nfunction createInstrumentations() {\n  const mutableInstrumentations2 = {\n    get(key) {\n      return get(this, key);\n    },\n    get size() {\n      return size(this);\n    },\n    has,\n    add,\n    set,\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, false)\n  };\n  const shallowInstrumentations2 = {\n    get(key) {\n      return get(this, key, false, true);\n    },\n    get size() {\n      return size(this);\n    },\n    has,\n    add(value) {\n      return add.call(this, value, true);\n    },\n    set(key, value) {\n      return set.call(this, key, value, true);\n    },\n    delete: deleteEntry,\n    clear,\n    forEach: createForEach(false, true)\n  };\n  const readonlyInstrumentations2 = {\n    get(key) {\n      return get(this, key, true);\n    },\n    get size() {\n      return size(this, true);\n    },\n    has(key) {\n      return has.call(this, key, true);\n    },\n    add: createReadonlyMethod(\"add\"),\n    set: createReadonlyMethod(\"set\"),\n    delete: createReadonlyMethod(\"delete\"),\n    clear: createReadonlyMethod(\"clear\"),\n    forEach: createForEach(true, false)\n  };\n  const shallowReadonlyInstrumentations2 = {\n    get(key) {\n      return get(this, key, true, true);\n    },\n    get size() {\n      return size(this, true);\n    },\n    has(key) {\n      return has.call(this, key, true);\n    },\n    add: createReadonlyMethod(\"add\"),\n    set: createReadonlyMethod(\"set\"),\n    delete: createReadonlyMethod(\"delete\"),\n    clear: createReadonlyMethod(\"clear\"),\n    forEach: createForEach(true, true)\n  };\n  const iteratorMethods = [\n    \"keys\",\n    \"values\",\n    \"entries\",\n    Symbol.iterator\n  ];\n  iteratorMethods.forEach((method) => {\n    mutableInstrumentations2[method] = createIterableMethod(method, false, false);\n    readonlyInstrumentations2[method] = createIterableMethod(method, true, false);\n    shallowInstrumentations2[method] = createIterableMethod(method, false, true);\n    shallowReadonlyInstrumentations2[method] = createIterableMethod(\n      method,\n      true,\n      true\n    );\n  });\n  return [\n    mutableInstrumentations2,\n    readonlyInstrumentations2,\n    shallowInstrumentations2,\n    shallowReadonlyInstrumentations2\n  ];\n}\nconst [\n  mutableInstrumentations,\n  readonlyInstrumentations,\n  shallowInstrumentations,\n  shallowReadonlyInstrumentations\n] = /* @__PURE__ */ createInstrumentations();\nfunction createInstrumentationGetter(isReadonly2, shallow) {\n  const instrumentations = shallow ? isReadonly2 ? shallowReadonlyInstrumentations : shallowInstrumentations : isReadonly2 ? readonlyInstrumentations : mutableInstrumentations;\n  return (target, key, receiver) => {\n    if (key === \"__v_isReactive\") {\n      return !isReadonly2;\n    } else if (key === \"__v_isReadonly\") {\n      return isReadonly2;\n    } else if (key === \"__v_raw\") {\n      return target;\n    }\n    return Reflect.get(\n      hasOwn(instrumentations, key) && key in target ? instrumentations : target,\n      key,\n      receiver\n    );\n  };\n}\nconst mutableCollectionHandlers = {\n  get: /* @__PURE__ */ createInstrumentationGetter(false, false)\n};\nconst shallowCollectionHandlers = {\n  get: /* @__PURE__ */ createInstrumentationGetter(false, true)\n};\nconst readonlyCollectionHandlers = {\n  get: /* @__PURE__ */ createInstrumentationGetter(true, false)\n};\nconst shallowReadonlyCollectionHandlers = {\n  get: /* @__PURE__ */ createInstrumentationGetter(true, true)\n};\nfunction checkIdentityKeys(target, has2, key) {\n  const rawKey = toRaw(key);\n  if (rawKey !== key && has2.call(target, rawKey)) {\n    const type = toRawType(target);\n    warn$2(\n      `Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`\n    );\n  }\n}\n\nconst reactiveMap = /* @__PURE__ */ new WeakMap();\nconst shallowReactiveMap = /* @__PURE__ */ new WeakMap();\nconst readonlyMap = /* @__PURE__ */ new WeakMap();\nconst shallowReadonlyMap = /* @__PURE__ */ new WeakMap();\nfunction targetTypeMap(rawType) {\n  switch (rawType) {\n    case \"Object\":\n    case \"Array\":\n      return 1 /* COMMON */;\n    case \"Map\":\n    case \"Set\":\n    case \"WeakMap\":\n    case \"WeakSet\":\n      return 2 /* COLLECTION */;\n    default:\n      return 0 /* INVALID */;\n  }\n}\nfunction getTargetType(value) {\n  return value[\"__v_skip\"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap(toRawType(value));\n}\nfunction reactive(target) {\n  if (isReadonly(target)) {\n    return target;\n  }\n  return createReactiveObject(\n    target,\n    false,\n    mutableHandlers,\n    mutableCollectionHandlers,\n    reactiveMap\n  );\n}\nfunction shallowReactive(target) {\n  return createReactiveObject(\n    target,\n    false,\n    shallowReactiveHandlers,\n    shallowCollectionHandlers,\n    shallowReactiveMap\n  );\n}\nfunction readonly(target) {\n  return createReactiveObject(\n    target,\n    true,\n    readonlyHandlers,\n    readonlyCollectionHandlers,\n    readonlyMap\n  );\n}\nfunction shallowReadonly(target) {\n  return createReactiveObject(\n    target,\n    true,\n    shallowReadonlyHandlers,\n    shallowReadonlyCollectionHandlers,\n    shallowReadonlyMap\n  );\n}\nfunction createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {\n  if (!isObject(target)) {\n    {\n      warn$2(\n        `value cannot be made ${isReadonly2 ? \"readonly\" : \"reactive\"}: ${String(\n          target\n        )}`\n      );\n    }\n    return target;\n  }\n  if (target[\"__v_raw\"] && !(isReadonly2 && target[\"__v_isReactive\"])) {\n    return target;\n  }\n  const existingProxy = proxyMap.get(target);\n  if (existingProxy) {\n    return existingProxy;\n  }\n  const targetType = getTargetType(target);\n  if (targetType === 0 /* INVALID */) {\n    return target;\n  }\n  const proxy = new Proxy(\n    target,\n    targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers\n  );\n  proxyMap.set(target, proxy);\n  return proxy;\n}\nfunction isReactive(value) {\n  if (isReadonly(value)) {\n    return isReactive(value[\"__v_raw\"]);\n  }\n  return !!(value && value[\"__v_isReactive\"]);\n}\nfunction isReadonly(value) {\n  return !!(value && value[\"__v_isReadonly\"]);\n}\nfunction isShallow(value) {\n  return !!(value && value[\"__v_isShallow\"]);\n}\nfunction isProxy(value) {\n  return value ? !!value[\"__v_raw\"] : false;\n}\nfunction toRaw(observed) {\n  const raw = observed && observed[\"__v_raw\"];\n  return raw ? toRaw(raw) : observed;\n}\nfunction markRaw(value) {\n  if (Object.isExtensible(value)) {\n    def(value, \"__v_skip\", true);\n  }\n  return value;\n}\nconst toReactive = (value) => isObject(value) ? reactive(value) : value;\nconst toReadonly = (value) => isObject(value) ? readonly(value) : value;\n\nconst COMPUTED_SIDE_EFFECT_WARN = `Computed is still dirty after getter evaluation, likely because a computed is mutating its own dependency in its getter. State mutations in computed getters should be avoided.  Check the docs for more details: https://vuejs.org/guide/essentials/computed.html#getters-should-be-side-effect-free`;\nclass ComputedRefImpl {\n  constructor(getter, _setter, isReadonly, isSSR) {\n    this.getter = getter;\n    this._setter = _setter;\n    this.dep = void 0;\n    this.__v_isRef = true;\n    this[\"__v_isReadonly\"] = false;\n    this.effect = new ReactiveEffect(\n      () => getter(this._value),\n      () => triggerRefValue(\n        this,\n        this.effect._dirtyLevel === 2 ? 2 : 3\n      )\n    );\n    this.effect.computed = this;\n    this.effect.active = this._cacheable = !isSSR;\n    this[\"__v_isReadonly\"] = isReadonly;\n  }\n  get value() {\n    const self = toRaw(this);\n    if ((!self._cacheable || self.effect.dirty) && hasChanged(self._value, self._value = self.effect.run())) {\n      triggerRefValue(self, 4);\n    }\n    trackRefValue(self);\n    if (self.effect._dirtyLevel >= 2) {\n      if (this._warnRecursive) {\n        warn$2(COMPUTED_SIDE_EFFECT_WARN, `\n\ngetter: `, this.getter);\n      }\n      triggerRefValue(self, 2);\n    }\n    return self._value;\n  }\n  set value(newValue) {\n    this._setter(newValue);\n  }\n  // #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x\n  get _dirty() {\n    return this.effect.dirty;\n  }\n  set _dirty(v) {\n    this.effect.dirty = v;\n  }\n  // #endregion\n}\nfunction computed$1(getterOrOptions, debugOptions, isSSR = false) {\n  let getter;\n  let setter;\n  const onlyGetter = isFunction(getterOrOptions);\n  if (onlyGetter) {\n    getter = getterOrOptions;\n    setter = () => {\n      warn$2(\"Write operation failed: computed value is readonly\");\n    } ;\n  } else {\n    getter = getterOrOptions.get;\n    setter = getterOrOptions.set;\n  }\n  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);\n  if (debugOptions && !isSSR) {\n    cRef.effect.onTrack = debugOptions.onTrack;\n    cRef.effect.onTrigger = debugOptions.onTrigger;\n  }\n  return cRef;\n}\n\nfunction trackRefValue(ref2) {\n  var _a;\n  if (shouldTrack && activeEffect) {\n    ref2 = toRaw(ref2);\n    trackEffect(\n      activeEffect,\n      (_a = ref2.dep) != null ? _a : ref2.dep = createDep(\n        () => ref2.dep = void 0,\n        ref2 instanceof ComputedRefImpl ? ref2 : void 0\n      ),\n      {\n        target: ref2,\n        type: \"get\",\n        key: \"value\"\n      } \n    );\n  }\n}\nfunction triggerRefValue(ref2, dirtyLevel = 4, newVal, oldVal) {\n  ref2 = toRaw(ref2);\n  const dep = ref2.dep;\n  if (dep) {\n    triggerEffects(\n      dep,\n      dirtyLevel,\n      {\n        target: ref2,\n        type: \"set\",\n        key: \"value\",\n        newValue: newVal,\n        oldValue: oldVal\n      } \n    );\n  }\n}\nfunction isRef(r) {\n  return !!(r && r.__v_isRef === true);\n}\nfunction ref(value) {\n  return createRef(value, false);\n}\nfunction shallowRef(value) {\n  return createRef(value, true);\n}\nfunction createRef(rawValue, shallow) {\n  if (isRef(rawValue)) {\n    return rawValue;\n  }\n  return new RefImpl(rawValue, shallow);\n}\nclass RefImpl {\n  constructor(value, __v_isShallow) {\n    this.__v_isShallow = __v_isShallow;\n    this.dep = void 0;\n    this.__v_isRef = true;\n    this._rawValue = __v_isShallow ? value : toRaw(value);\n    this._value = __v_isShallow ? value : toReactive(value);\n  }\n  get value() {\n    trackRefValue(this);\n    return this._value;\n  }\n  set value(newVal) {\n    const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);\n    newVal = useDirectValue ? newVal : toRaw(newVal);\n    if (hasChanged(newVal, this._rawValue)) {\n      const oldVal = this._rawValue;\n      this._rawValue = newVal;\n      this._value = useDirectValue ? newVal : toReactive(newVal);\n      triggerRefValue(this, 4, newVal, oldVal);\n    }\n  }\n}\nfunction triggerRef(ref2) {\n  triggerRefValue(ref2, 4, ref2.value );\n}\nfunction unref(ref2) {\n  return isRef(ref2) ? ref2.value : ref2;\n}\nfunction toValue(source) {\n  return isFunction(source) ? source() : unref(source);\n}\nconst shallowUnwrapHandlers = {\n  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n  set: (target, key, value, receiver) => {\n    const oldValue = target[key];\n    if (isRef(oldValue) && !isRef(value)) {\n      oldValue.value = value;\n      return true;\n    } else {\n      return Reflect.set(target, key, value, receiver);\n    }\n  }\n};\nfunction proxyRefs(objectWithRefs) {\n  return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n}\nclass CustomRefImpl {\n  constructor(factory) {\n    this.dep = void 0;\n    this.__v_isRef = true;\n    const { get, set } = factory(\n      () => trackRefValue(this),\n      () => triggerRefValue(this)\n    );\n    this._get = get;\n    this._set = set;\n  }\n  get value() {\n    return this._get();\n  }\n  set value(newVal) {\n    this._set(newVal);\n  }\n}\nfunction customRef(factory) {\n  return new CustomRefImpl(factory);\n}\nfunction toRefs(object) {\n  if (!isProxy(object)) {\n    warn$2(`toRefs() expects a reactive object but received a plain one.`);\n  }\n  const ret = isArray(object) ? new Array(object.length) : {};\n  for (const key in object) {\n    ret[key] = propertyToRef(object, key);\n  }\n  return ret;\n}\nclass ObjectRefImpl {\n  constructor(_object, _key, _defaultValue) {\n    this._object = _object;\n    this._key = _key;\n    this._defaultValue = _defaultValue;\n    this.__v_isRef = true;\n  }\n  get value() {\n    const val = this._object[this._key];\n    return val === void 0 ? this._defaultValue : val;\n  }\n  set value(newVal) {\n    this._object[this._key] = newVal;\n  }\n  get dep() {\n    return getDepFromReactive(toRaw(this._object), this._key);\n  }\n}\nclass GetterRefImpl {\n  constructor(_getter) {\n    this._getter = _getter;\n    this.__v_isRef = true;\n    this.__v_isReadonly = true;\n  }\n  get value() {\n    return this._getter();\n  }\n}\nfunction toRef(source, key, defaultValue) {\n  if (isRef(source)) {\n    return source;\n  } else if (isFunction(source)) {\n    return new GetterRefImpl(source);\n  } else if (isObject(source) && arguments.length > 1) {\n    return propertyToRef(source, key, defaultValue);\n  } else {\n    return ref(source);\n  }\n}\nfunction propertyToRef(source, key, defaultValue) {\n  const val = source[key];\n  return isRef(val) ? val : new ObjectRefImpl(source, key, defaultValue);\n}\n\nconst TrackOpTypes = {\n  \"GET\": \"get\",\n  \"HAS\": \"has\",\n  \"ITERATE\": \"iterate\"\n};\nconst TriggerOpTypes = {\n  \"SET\": \"set\",\n  \"ADD\": \"add\",\n  \"DELETE\": \"delete\",\n  \"CLEAR\": \"clear\"\n};\n\nconst stack$1 = [];\nfunction pushWarningContext(vnode) {\n  stack$1.push(vnode);\n}\nfunction popWarningContext() {\n  stack$1.pop();\n}\nlet isWarning = false;\nfunction warn$1(msg, ...args) {\n  if (isWarning) return;\n  isWarning = true;\n  pauseTracking();\n  const instance = stack$1.length ? stack$1[stack$1.length - 1].component : null;\n  const appWarnHandler = instance && instance.appContext.config.warnHandler;\n  const trace = getComponentTrace();\n  if (appWarnHandler) {\n    callWithErrorHandling(\n      appWarnHandler,\n      instance,\n      11,\n      [\n        // eslint-disable-next-line no-restricted-syntax\n        msg + args.map((a) => {\n          var _a, _b;\n          return (_b = (_a = a.toString) == null ? void 0 : _a.call(a)) != null ? _b : JSON.stringify(a);\n        }).join(\"\"),\n        instance && instance.proxy,\n        trace.map(\n          ({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>`\n        ).join(\"\\n\"),\n        trace\n      ]\n    );\n  } else {\n    const warnArgs = [`[Vue warn]: ${msg}`, ...args];\n    if (trace.length && // avoid spamming console during tests\n    true) {\n      warnArgs.push(`\n`, ...formatTrace(trace));\n    }\n    console.warn(...warnArgs);\n  }\n  resetTracking();\n  isWarning = false;\n}\nfunction getComponentTrace() {\n  let currentVNode = stack$1[stack$1.length - 1];\n  if (!currentVNode) {\n    return [];\n  }\n  const normalizedStack = [];\n  while (currentVNode) {\n    const last = normalizedStack[0];\n    if (last && last.vnode === currentVNode) {\n      last.recurseCount++;\n    } else {\n      normalizedStack.push({\n        vnode: currentVNode,\n        recurseCount: 0\n      });\n    }\n    const parentInstance = currentVNode.component && currentVNode.component.parent;\n    currentVNode = parentInstance && parentInstance.vnode;\n  }\n  return normalizedStack;\n}\nfunction formatTrace(trace) {\n  const logs = [];\n  trace.forEach((entry, i) => {\n    logs.push(...i === 0 ? [] : [`\n`], ...formatTraceEntry(entry));\n  });\n  return logs;\n}\nfunction formatTraceEntry({ vnode, recurseCount }) {\n  const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``;\n  const isRoot = vnode.component ? vnode.component.parent == null : false;\n  const open = ` at <${formatComponentName(\n    vnode.component,\n    vnode.type,\n    isRoot\n  )}`;\n  const close = `>` + postfix;\n  return vnode.props ? [open, ...formatProps(vnode.props), close] : [open + close];\n}\nfunction formatProps(props) {\n  const res = [];\n  const keys = Object.keys(props);\n  keys.slice(0, 3).forEach((key) => {\n    res.push(...formatProp(key, props[key]));\n  });\n  if (keys.length > 3) {\n    res.push(` ...`);\n  }\n  return res;\n}\nfunction formatProp(key, value, raw) {\n  if (isString(value)) {\n    value = JSON.stringify(value);\n    return raw ? value : [`${key}=${value}`];\n  } else if (typeof value === \"number\" || typeof value === \"boolean\" || value == null) {\n    return raw ? value : [`${key}=${value}`];\n  } else if (isRef(value)) {\n    value = formatProp(key, toRaw(value.value), true);\n    return raw ? value : [`${key}=Ref<`, value, `>`];\n  } else if (isFunction(value)) {\n    return [`${key}=fn${value.name ? `<${value.name}>` : ``}`];\n  } else {\n    value = toRaw(value);\n    return raw ? value : [`${key}=`, value];\n  }\n}\nfunction assertNumber(val, type) {\n  if (val === void 0) {\n    return;\n  } else if (typeof val !== \"number\") {\n    warn$1(`${type} is not a valid number - got ${JSON.stringify(val)}.`);\n  } else if (isNaN(val)) {\n    warn$1(`${type} is NaN - the duration expression might be incorrect.`);\n  }\n}\n\nconst ErrorCodes = {\n  \"SETUP_FUNCTION\": 0,\n  \"0\": \"SETUP_FUNCTION\",\n  \"RENDER_FUNCTION\": 1,\n  \"1\": \"RENDER_FUNCTION\",\n  \"WATCH_GETTER\": 2,\n  \"2\": \"WATCH_GETTER\",\n  \"WATCH_CALLBACK\": 3,\n  \"3\": \"WATCH_CALLBACK\",\n  \"WATCH_CLEANUP\": 4,\n  \"4\": \"WATCH_CLEANUP\",\n  \"NATIVE_EVENT_HANDLER\": 5,\n  \"5\": \"NATIVE_EVENT_HANDLER\",\n  \"COMPONENT_EVENT_HANDLER\": 6,\n  \"6\": \"COMPONENT_EVENT_HANDLER\",\n  \"VNODE_HOOK\": 7,\n  \"7\": \"VNODE_HOOK\",\n  \"DIRECTIVE_HOOK\": 8,\n  \"8\": \"DIRECTIVE_HOOK\",\n  \"TRANSITION_HOOK\": 9,\n  \"9\": \"TRANSITION_HOOK\",\n  \"APP_ERROR_HANDLER\": 10,\n  \"10\": \"APP_ERROR_HANDLER\",\n  \"APP_WARN_HANDLER\": 11,\n  \"11\": \"APP_WARN_HANDLER\",\n  \"FUNCTION_REF\": 12,\n  \"12\": \"FUNCTION_REF\",\n  \"ASYNC_COMPONENT_LOADER\": 13,\n  \"13\": \"ASYNC_COMPONENT_LOADER\",\n  \"SCHEDULER\": 14,\n  \"14\": \"SCHEDULER\",\n  \"COMPONENT_UPDATE\": 15,\n  \"15\": \"COMPONENT_UPDATE\"\n};\nconst ErrorTypeStrings$1 = {\n  [\"sp\"]: \"serverPrefetch hook\",\n  [\"bc\"]: \"beforeCreate hook\",\n  [\"c\"]: \"created hook\",\n  [\"bm\"]: \"beforeMount hook\",\n  [\"m\"]: \"mounted hook\",\n  [\"bu\"]: \"beforeUpdate hook\",\n  [\"u\"]: \"updated\",\n  [\"bum\"]: \"beforeUnmount hook\",\n  [\"um\"]: \"unmounted hook\",\n  [\"a\"]: \"activated hook\",\n  [\"da\"]: \"deactivated hook\",\n  [\"ec\"]: \"errorCaptured hook\",\n  [\"rtc\"]: \"renderTracked hook\",\n  [\"rtg\"]: \"renderTriggered hook\",\n  [0]: \"setup function\",\n  [1]: \"render function\",\n  [2]: \"watcher getter\",\n  [3]: \"watcher callback\",\n  [4]: \"watcher cleanup function\",\n  [5]: \"native event handler\",\n  [6]: \"component event handler\",\n  [7]: \"vnode hook\",\n  [8]: \"directive hook\",\n  [9]: \"transition hook\",\n  [10]: \"app errorHandler\",\n  [11]: \"app warnHandler\",\n  [12]: \"ref function\",\n  [13]: \"async component loader\",\n  [14]: \"scheduler flush\",\n  [15]: \"component update\"\n};\nfunction callWithErrorHandling(fn, instance, type, args) {\n  try {\n    return args ? fn(...args) : fn();\n  } catch (err) {\n    handleError(err, instance, type);\n  }\n}\nfunction callWithAsyncErrorHandling(fn, instance, type, args) {\n  if (isFunction(fn)) {\n    const res = callWithErrorHandling(fn, instance, type, args);\n    if (res && isPromise(res)) {\n      res.catch((err) => {\n        handleError(err, instance, type);\n      });\n    }\n    return res;\n  }\n  if (isArray(fn)) {\n    const values = [];\n    for (let i = 0; i < fn.length; i++) {\n      values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));\n    }\n    return values;\n  } else {\n    warn$1(\n      `Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}`\n    );\n  }\n}\nfunction handleError(err, instance, type, throwInDev = true) {\n  const contextVNode = instance ? instance.vnode : null;\n  if (instance) {\n    let cur = instance.parent;\n    const exposedInstance = instance.proxy;\n    const errorInfo = ErrorTypeStrings$1[type] ;\n    while (cur) {\n      const errorCapturedHooks = cur.ec;\n      if (errorCapturedHooks) {\n        for (let i = 0; i < errorCapturedHooks.length; i++) {\n          if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) {\n            return;\n          }\n        }\n      }\n      cur = cur.parent;\n    }\n    const appErrorHandler = instance.appContext.config.errorHandler;\n    if (appErrorHandler) {\n      pauseTracking();\n      callWithErrorHandling(\n        appErrorHandler,\n        null,\n        10,\n        [err, exposedInstance, errorInfo]\n      );\n      resetTracking();\n      return;\n    }\n  }\n  logError(err, type, contextVNode, throwInDev);\n}\nfunction logError(err, type, contextVNode, throwInDev = true) {\n  {\n    const info = ErrorTypeStrings$1[type];\n    if (contextVNode) {\n      pushWarningContext(contextVNode);\n    }\n    warn$1(`Unhandled error${info ? ` during execution of ${info}` : ``}`);\n    if (contextVNode) {\n      popWarningContext();\n    }\n    if (throwInDev) {\n      throw err;\n    } else {\n      console.error(err);\n    }\n  }\n}\n\nlet isFlushing = false;\nlet isFlushPending = false;\nconst queue = [];\nlet flushIndex = 0;\nconst pendingPostFlushCbs = [];\nlet activePostFlushCbs = null;\nlet postFlushIndex = 0;\nconst resolvedPromise = /* @__PURE__ */ Promise.resolve();\nlet currentFlushPromise = null;\nconst RECURSION_LIMIT = 100;\nfunction nextTick(fn) {\n  const p = currentFlushPromise || resolvedPromise;\n  return fn ? p.then(this ? fn.bind(this) : fn) : p;\n}\nfunction findInsertionIndex(id) {\n  let start = flushIndex + 1;\n  let end = queue.length;\n  while (start < end) {\n    const middle = start + end >>> 1;\n    const middleJob = queue[middle];\n    const middleJobId = getId(middleJob);\n    if (middleJobId < id || middleJobId === id && middleJob.pre) {\n      start = middle + 1;\n    } else {\n      end = middle;\n    }\n  }\n  return start;\n}\nfunction queueJob(job) {\n  if (!queue.length || !queue.includes(\n    job,\n    isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex\n  )) {\n    if (job.id == null) {\n      queue.push(job);\n    } else {\n      queue.splice(findInsertionIndex(job.id), 0, job);\n    }\n    queueFlush();\n  }\n}\nfunction queueFlush() {\n  if (!isFlushing && !isFlushPending) {\n    isFlushPending = true;\n    currentFlushPromise = resolvedPromise.then(flushJobs);\n  }\n}\nfunction invalidateJob(job) {\n  const i = queue.indexOf(job);\n  if (i > flushIndex) {\n    queue.splice(i, 1);\n  }\n}\nfunction queuePostFlushCb(cb) {\n  if (!isArray(cb)) {\n    if (!activePostFlushCbs || !activePostFlushCbs.includes(\n      cb,\n      cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex\n    )) {\n      pendingPostFlushCbs.push(cb);\n    }\n  } else {\n    pendingPostFlushCbs.push(...cb);\n  }\n  queueFlush();\n}\nfunction flushPreFlushCbs(instance, seen, i = isFlushing ? flushIndex + 1 : 0) {\n  {\n    seen = seen || /* @__PURE__ */ new Map();\n  }\n  for (; i < queue.length; i++) {\n    const cb = queue[i];\n    if (cb && cb.pre) {\n      if (instance && cb.id !== instance.uid) {\n        continue;\n      }\n      if (checkRecursiveUpdates(seen, cb)) {\n        continue;\n      }\n      queue.splice(i, 1);\n      i--;\n      cb();\n    }\n  }\n}\nfunction flushPostFlushCbs(seen) {\n  if (pendingPostFlushCbs.length) {\n    const deduped = [...new Set(pendingPostFlushCbs)].sort(\n      (a, b) => getId(a) - getId(b)\n    );\n    pendingPostFlushCbs.length = 0;\n    if (activePostFlushCbs) {\n      activePostFlushCbs.push(...deduped);\n      return;\n    }\n    activePostFlushCbs = deduped;\n    {\n      seen = seen || /* @__PURE__ */ new Map();\n    }\n    for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {\n      const cb = activePostFlushCbs[postFlushIndex];\n      if (checkRecursiveUpdates(seen, cb)) {\n        continue;\n      }\n      if (cb.active !== false) cb();\n    }\n    activePostFlushCbs = null;\n    postFlushIndex = 0;\n  }\n}\nconst getId = (job) => job.id == null ? Infinity : job.id;\nconst comparator = (a, b) => {\n  const diff = getId(a) - getId(b);\n  if (diff === 0) {\n    if (a.pre && !b.pre) return -1;\n    if (b.pre && !a.pre) return 1;\n  }\n  return diff;\n};\nfunction flushJobs(seen) {\n  isFlushPending = false;\n  isFlushing = true;\n  {\n    seen = seen || /* @__PURE__ */ new Map();\n  }\n  queue.sort(comparator);\n  const check = (job) => checkRecursiveUpdates(seen, job) ;\n  try {\n    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {\n      const job = queue[flushIndex];\n      if (job && job.active !== false) {\n        if (check(job)) {\n          continue;\n        }\n        callWithErrorHandling(\n          job,\n          job.i,\n          job.i ? 15 : 14\n        );\n      }\n    }\n  } finally {\n    flushIndex = 0;\n    queue.length = 0;\n    flushPostFlushCbs(seen);\n    isFlushing = false;\n    currentFlushPromise = null;\n    if (queue.length || pendingPostFlushCbs.length) {\n      flushJobs(seen);\n    }\n  }\n}\nfunction checkRecursiveUpdates(seen, fn) {\n  if (!seen.has(fn)) {\n    seen.set(fn, 1);\n  } else {\n    const count = seen.get(fn);\n    if (count > RECURSION_LIMIT) {\n      const instance = fn.i;\n      const componentName = instance && getComponentName(instance.type);\n      handleError(\n        `Maximum recursive updates exceeded${componentName ? ` in component <${componentName}>` : ``}. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.`,\n        null,\n        10\n      );\n      return true;\n    } else {\n      seen.set(fn, count + 1);\n    }\n  }\n}\n\nlet isHmrUpdating = false;\nconst hmrDirtyComponents = /* @__PURE__ */ new Map();\n{\n  getGlobalThis().__VUE_HMR_RUNTIME__ = {\n    createRecord: tryWrap(createRecord),\n    rerender: tryWrap(rerender),\n    reload: tryWrap(reload)\n  };\n}\nconst map = /* @__PURE__ */ new Map();\nfunction registerHMR(instance) {\n  const id = instance.type.__hmrId;\n  let record = map.get(id);\n  if (!record) {\n    createRecord(id, instance.type);\n    record = map.get(id);\n  }\n  record.instances.add(instance);\n}\nfunction unregisterHMR(instance) {\n  map.get(instance.type.__hmrId).instances.delete(instance);\n}\nfunction createRecord(id, initialDef) {\n  if (map.has(id)) {\n    return false;\n  }\n  map.set(id, {\n    initialDef: normalizeClassComponent(initialDef),\n    instances: /* @__PURE__ */ new Set()\n  });\n  return true;\n}\nfunction normalizeClassComponent(component) {\n  return isClassComponent(component) ? component.__vccOpts : component;\n}\nfunction rerender(id, newRender) {\n  const record = map.get(id);\n  if (!record) {\n    return;\n  }\n  record.initialDef.render = newRender;\n  [...record.instances].forEach((instance) => {\n    if (newRender) {\n      instance.render = newRender;\n      normalizeClassComponent(instance.type).render = newRender;\n    }\n    instance.renderCache = [];\n    isHmrUpdating = true;\n    instance.effect.dirty = true;\n    instance.update();\n    isHmrUpdating = false;\n  });\n}\nfunction reload(id, newComp) {\n  const record = map.get(id);\n  if (!record) return;\n  newComp = normalizeClassComponent(newComp);\n  updateComponentDef(record.initialDef, newComp);\n  const instances = [...record.instances];\n  for (let i = 0; i < instances.length; i++) {\n    const instance = instances[i];\n    const oldComp = normalizeClassComponent(instance.type);\n    let dirtyInstances = hmrDirtyComponents.get(oldComp);\n    if (!dirtyInstances) {\n      if (oldComp !== record.initialDef) {\n        updateComponentDef(oldComp, newComp);\n      }\n      hmrDirtyComponents.set(oldComp, dirtyInstances = /* @__PURE__ */ new Set());\n    }\n    dirtyInstances.add(instance);\n    instance.appContext.propsCache.delete(instance.type);\n    instance.appContext.emitsCache.delete(instance.type);\n    instance.appContext.optionsCache.delete(instance.type);\n    if (instance.ceReload) {\n      dirtyInstances.add(instance);\n      instance.ceReload(newComp.styles);\n      dirtyInstances.delete(instance);\n    } else if (instance.parent) {\n      instance.parent.effect.dirty = true;\n      queueJob(() => {\n        instance.parent.update();\n        dirtyInstances.delete(instance);\n      });\n    } else if (instance.appContext.reload) {\n      instance.appContext.reload();\n    } else if (typeof window !== \"undefined\") {\n      window.location.reload();\n    } else {\n      console.warn(\n        \"[HMR] Root or manually mounted instance modified. Full reload required.\"\n      );\n    }\n  }\n  queuePostFlushCb(() => {\n    hmrDirtyComponents.clear();\n  });\n}\nfunction updateComponentDef(oldComp, newComp) {\n  extend(oldComp, newComp);\n  for (const key in oldComp) {\n    if (key !== \"__file\" && !(key in newComp)) {\n      delete oldComp[key];\n    }\n  }\n}\nfunction tryWrap(fn) {\n  return (id, arg) => {\n    try {\n      return fn(id, arg);\n    } catch (e) {\n      console.error(e);\n      console.warn(\n        `[HMR] Something went wrong during Vue component hot-reload. Full reload required.`\n      );\n    }\n  };\n}\n\nlet devtools$1;\nlet buffer = [];\nlet devtoolsNotInstalled = false;\nfunction emit$1(event, ...args) {\n  if (devtools$1) {\n    devtools$1.emit(event, ...args);\n  } else if (!devtoolsNotInstalled) {\n    buffer.push({ event, args });\n  }\n}\nfunction setDevtoolsHook$1(hook, target) {\n  var _a, _b;\n  devtools$1 = hook;\n  if (devtools$1) {\n    devtools$1.enabled = true;\n    buffer.forEach(({ event, args }) => devtools$1.emit(event, ...args));\n    buffer = [];\n  } else if (\n    // handle late devtools injection - only do this if we are in an actual\n    // browser environment to avoid the timer handle stalling test runner exit\n    // (#4815)\n    typeof window !== \"undefined\" && // some envs mock window but not fully\n    window.HTMLElement && // also exclude jsdom\n    // eslint-disable-next-line no-restricted-syntax\n    !((_b = (_a = window.navigator) == null ? void 0 : _a.userAgent) == null ? void 0 : _b.includes(\"jsdom\"))\n  ) {\n    const replay = target.__VUE_DEVTOOLS_HOOK_REPLAY__ = target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [];\n    replay.push((newHook) => {\n      setDevtoolsHook$1(newHook, target);\n    });\n    setTimeout(() => {\n      if (!devtools$1) {\n        target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null;\n        devtoolsNotInstalled = true;\n        buffer = [];\n      }\n    }, 3e3);\n  } else {\n    devtoolsNotInstalled = true;\n    buffer = [];\n  }\n}\nfunction devtoolsInitApp(app, version) {\n  emit$1(\"app:init\" /* APP_INIT */, app, version, {\n    Fragment,\n    Text,\n    Comment,\n    Static\n  });\n}\nfunction devtoolsUnmountApp(app) {\n  emit$1(\"app:unmount\" /* APP_UNMOUNT */, app);\n}\nconst devtoolsComponentAdded = /* @__PURE__ */ createDevtoolsComponentHook(\n  \"component:added\" /* COMPONENT_ADDED */\n);\nconst devtoolsComponentUpdated = /* @__PURE__ */ createDevtoolsComponentHook(\"component:updated\" /* COMPONENT_UPDATED */);\nconst _devtoolsComponentRemoved = /* @__PURE__ */ createDevtoolsComponentHook(\n  \"component:removed\" /* COMPONENT_REMOVED */\n);\nconst devtoolsComponentRemoved = (component) => {\n  if (devtools$1 && typeof devtools$1.cleanupBuffer === \"function\" && // remove the component if it wasn't buffered\n  !devtools$1.cleanupBuffer(component)) {\n    _devtoolsComponentRemoved(component);\n  }\n};\n/*! #__NO_SIDE_EFFECTS__ */\n// @__NO_SIDE_EFFECTS__\nfunction createDevtoolsComponentHook(hook) {\n  return (component) => {\n    emit$1(\n      hook,\n      component.appContext.app,\n      component.uid,\n      component.parent ? component.parent.uid : void 0,\n      component\n    );\n  };\n}\nconst devtoolsPerfStart = /* @__PURE__ */ createDevtoolsPerformanceHook(\n  \"perf:start\" /* PERFORMANCE_START */\n);\nconst devtoolsPerfEnd = /* @__PURE__ */ createDevtoolsPerformanceHook(\n  \"perf:end\" /* PERFORMANCE_END */\n);\nfunction createDevtoolsPerformanceHook(hook) {\n  return (component, type, time) => {\n    emit$1(hook, component.appContext.app, component.uid, component, type, time);\n  };\n}\nfunction devtoolsComponentEmit(component, event, params) {\n  emit$1(\n    \"component:emit\" /* COMPONENT_EMIT */,\n    component.appContext.app,\n    component,\n    event,\n    params\n  );\n}\n\nlet currentRenderingInstance = null;\nlet currentScopeId = null;\nfunction setCurrentRenderingInstance(instance) {\n  const prev = currentRenderingInstance;\n  currentRenderingInstance = instance;\n  currentScopeId = instance && instance.type.__scopeId || null;\n  return prev;\n}\nfunction pushScopeId(id) {\n  currentScopeId = id;\n}\nfunction popScopeId() {\n  currentScopeId = null;\n}\nconst withScopeId = (_id) => withCtx;\nfunction withCtx(fn, ctx = currentRenderingInstance, isNonScopedSlot) {\n  if (!ctx) return fn;\n  if (fn._n) {\n    return fn;\n  }\n  const renderFnWithContext = (...args) => {\n    if (renderFnWithContext._d) {\n      setBlockTracking(-1);\n    }\n    const prevInstance = setCurrentRenderingInstance(ctx);\n    let res;\n    try {\n      res = fn(...args);\n    } finally {\n      setCurrentRenderingInstance(prevInstance);\n      if (renderFnWithContext._d) {\n        setBlockTracking(1);\n      }\n    }\n    {\n      devtoolsComponentUpdated(ctx);\n    }\n    return res;\n  };\n  renderFnWithContext._n = true;\n  renderFnWithContext._c = true;\n  renderFnWithContext._d = true;\n  return renderFnWithContext;\n}\n\nfunction validateDirectiveName(name) {\n  if (isBuiltInDirective(name)) {\n    warn$1(\"Do not use built-in directive ids as custom directive id: \" + name);\n  }\n}\nfunction withDirectives(vnode, directives) {\n  if (currentRenderingInstance === null) {\n    warn$1(`withDirectives can only be used inside render functions.`);\n    return vnode;\n  }\n  const instance = getComponentPublicInstance(currentRenderingInstance);\n  const bindings = vnode.dirs || (vnode.dirs = []);\n  for (let i = 0; i < directives.length; i++) {\n    let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i];\n    if (dir) {\n      if (isFunction(dir)) {\n        dir = {\n          mounted: dir,\n          updated: dir\n        };\n      }\n      if (dir.deep) {\n        traverse(value);\n      }\n      bindings.push({\n        dir,\n        instance,\n        value,\n        oldValue: void 0,\n        arg,\n        modifiers\n      });\n    }\n  }\n  return vnode;\n}\nfunction invokeDirectiveHook(vnode, prevVNode, instance, name) {\n  const bindings = vnode.dirs;\n  const oldBindings = prevVNode && prevVNode.dirs;\n  for (let i = 0; i < bindings.length; i++) {\n    const binding = bindings[i];\n    if (oldBindings) {\n      binding.oldValue = oldBindings[i].value;\n    }\n    let hook = binding.dir[name];\n    if (hook) {\n      pauseTracking();\n      callWithAsyncErrorHandling(hook, instance, 8, [\n        vnode.el,\n        binding,\n        vnode,\n        prevVNode\n      ]);\n      resetTracking();\n    }\n  }\n}\n\nconst leaveCbKey = Symbol(\"_leaveCb\");\nconst enterCbKey$1 = Symbol(\"_enterCb\");\nfunction useTransitionState() {\n  const state = {\n    isMounted: false,\n    isLeaving: false,\n    isUnmounting: false,\n    leavingVNodes: /* @__PURE__ */ new Map()\n  };\n  onMounted(() => {\n    state.isMounted = true;\n  });\n  onBeforeUnmount(() => {\n    state.isUnmounting = true;\n  });\n  return state;\n}\nconst TransitionHookValidator = [Function, Array];\nconst BaseTransitionPropsValidators = {\n  mode: String,\n  appear: Boolean,\n  persisted: Boolean,\n  // enter\n  onBeforeEnter: TransitionHookValidator,\n  onEnter: TransitionHookValidator,\n  onAfterEnter: TransitionHookValidator,\n  onEnterCancelled: TransitionHookValidator,\n  // leave\n  onBeforeLeave: TransitionHookValidator,\n  onLeave: TransitionHookValidator,\n  onAfterLeave: TransitionHookValidator,\n  onLeaveCancelled: TransitionHookValidator,\n  // appear\n  onBeforeAppear: TransitionHookValidator,\n  onAppear: TransitionHookValidator,\n  onAfterAppear: TransitionHookValidator,\n  onAppearCancelled: TransitionHookValidator\n};\nconst recursiveGetSubtree = (instance) => {\n  const subTree = instance.subTree;\n  return subTree.component ? recursiveGetSubtree(subTree.component) : subTree;\n};\nconst BaseTransitionImpl = {\n  name: `BaseTransition`,\n  props: BaseTransitionPropsValidators,\n  setup(props, { slots }) {\n    const instance = getCurrentInstance();\n    const state = useTransitionState();\n    return () => {\n      const children = slots.default && getTransitionRawChildren(slots.default(), true);\n      if (!children || !children.length) {\n        return;\n      }\n      let child = children[0];\n      if (children.length > 1) {\n        let hasFound = false;\n        for (const c of children) {\n          if (c.type !== Comment) {\n            if (hasFound) {\n              warn$1(\n                \"<transition> can only be used on a single element or component. Use <transition-group> for lists.\"\n              );\n              break;\n            }\n            child = c;\n            hasFound = true;\n          }\n        }\n      }\n      const rawProps = toRaw(props);\n      const { mode } = rawProps;\n      if (mode && mode !== \"in-out\" && mode !== \"out-in\" && mode !== \"default\") {\n        warn$1(`invalid <transition> mode: ${mode}`);\n      }\n      if (state.isLeaving) {\n        return emptyPlaceholder(child);\n      }\n      const innerChild = getKeepAliveChild(child);\n      if (!innerChild) {\n        return emptyPlaceholder(child);\n      }\n      let enterHooks = resolveTransitionHooks(\n        innerChild,\n        rawProps,\n        state,\n        instance,\n        // #11061, ensure enterHooks is fresh after clone\n        (hooks) => enterHooks = hooks\n      );\n      setTransitionHooks(innerChild, enterHooks);\n      const oldChild = instance.subTree;\n      const oldInnerChild = oldChild && getKeepAliveChild(oldChild);\n      if (oldInnerChild && oldInnerChild.type !== Comment && !isSameVNodeType(innerChild, oldInnerChild) && recursiveGetSubtree(instance).type !== Comment) {\n        const leavingHooks = resolveTransitionHooks(\n          oldInnerChild,\n          rawProps,\n          state,\n          instance\n        );\n        setTransitionHooks(oldInnerChild, leavingHooks);\n        if (mode === \"out-in\" && innerChild.type !== Comment) {\n          state.isLeaving = true;\n          leavingHooks.afterLeave = () => {\n            state.isLeaving = false;\n            if (instance.update.active !== false) {\n              instance.effect.dirty = true;\n              instance.update();\n            }\n          };\n          return emptyPlaceholder(child);\n        } else if (mode === \"in-out\" && innerChild.type !== Comment) {\n          leavingHooks.delayLeave = (el, earlyRemove, delayedLeave) => {\n            const leavingVNodesCache = getLeavingNodesForType(\n              state,\n              oldInnerChild\n            );\n            leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild;\n            el[leaveCbKey] = () => {\n              earlyRemove();\n              el[leaveCbKey] = void 0;\n              delete enterHooks.delayedLeave;\n            };\n            enterHooks.delayedLeave = delayedLeave;\n          };\n        }\n      }\n      return child;\n    };\n  }\n};\nconst BaseTransition = BaseTransitionImpl;\nfunction getLeavingNodesForType(state, vnode) {\n  const { leavingVNodes } = state;\n  let leavingVNodesCache = leavingVNodes.get(vnode.type);\n  if (!leavingVNodesCache) {\n    leavingVNodesCache = /* @__PURE__ */ Object.create(null);\n    leavingVNodes.set(vnode.type, leavingVNodesCache);\n  }\n  return leavingVNodesCache;\n}\nfunction resolveTransitionHooks(vnode, props, state, instance, postClone) {\n  const {\n    appear,\n    mode,\n    persisted = false,\n    onBeforeEnter,\n    onEnter,\n    onAfterEnter,\n    onEnterCancelled,\n    onBeforeLeave,\n    onLeave,\n    onAfterLeave,\n    onLeaveCancelled,\n    onBeforeAppear,\n    onAppear,\n    onAfterAppear,\n    onAppearCancelled\n  } = props;\n  const key = String(vnode.key);\n  const leavingVNodesCache = getLeavingNodesForType(state, vnode);\n  const callHook = (hook, args) => {\n    hook && callWithAsyncErrorHandling(\n      hook,\n      instance,\n      9,\n      args\n    );\n  };\n  const callAsyncHook = (hook, args) => {\n    const done = args[1];\n    callHook(hook, args);\n    if (isArray(hook)) {\n      if (hook.every((hook2) => hook2.length <= 1)) done();\n    } else if (hook.length <= 1) {\n      done();\n    }\n  };\n  const hooks = {\n    mode,\n    persisted,\n    beforeEnter(el) {\n      let hook = onBeforeEnter;\n      if (!state.isMounted) {\n        if (appear) {\n          hook = onBeforeAppear || onBeforeEnter;\n        } else {\n          return;\n        }\n      }\n      if (el[leaveCbKey]) {\n        el[leaveCbKey](\n          true\n          /* cancelled */\n        );\n      }\n      const leavingVNode = leavingVNodesCache[key];\n      if (leavingVNode && isSameVNodeType(vnode, leavingVNode) && leavingVNode.el[leaveCbKey]) {\n        leavingVNode.el[leaveCbKey]();\n      }\n      callHook(hook, [el]);\n    },\n    enter(el) {\n      let hook = onEnter;\n      let afterHook = onAfterEnter;\n      let cancelHook = onEnterCancelled;\n      if (!state.isMounted) {\n        if (appear) {\n          hook = onAppear || onEnter;\n          afterHook = onAfterAppear || onAfterEnter;\n          cancelHook = onAppearCancelled || onEnterCancelled;\n        } else {\n          return;\n        }\n      }\n      let called = false;\n      const done = el[enterCbKey$1] = (cancelled) => {\n        if (called) return;\n        called = true;\n        if (cancelled) {\n          callHook(cancelHook, [el]);\n        } else {\n          callHook(afterHook, [el]);\n        }\n        if (hooks.delayedLeave) {\n          hooks.delayedLeave();\n        }\n        el[enterCbKey$1] = void 0;\n      };\n      if (hook) {\n        callAsyncHook(hook, [el, done]);\n      } else {\n        done();\n      }\n    },\n    leave(el, remove) {\n      const key2 = String(vnode.key);\n      if (el[enterCbKey$1]) {\n        el[enterCbKey$1](\n          true\n          /* cancelled */\n        );\n      }\n      if (state.isUnmounting) {\n        return remove();\n      }\n      callHook(onBeforeLeave, [el]);\n      let called = false;\n      const done = el[leaveCbKey] = (cancelled) => {\n        if (called) return;\n        called = true;\n        remove();\n        if (cancelled) {\n          callHook(onLeaveCancelled, [el]);\n        } else {\n          callHook(onAfterLeave, [el]);\n        }\n        el[leaveCbKey] = void 0;\n        if (leavingVNodesCache[key2] === vnode) {\n          delete leavingVNodesCache[key2];\n        }\n      };\n      leavingVNodesCache[key2] = vnode;\n      if (onLeave) {\n        callAsyncHook(onLeave, [el, done]);\n      } else {\n        done();\n      }\n    },\n    clone(vnode2) {\n      const hooks2 = resolveTransitionHooks(\n        vnode2,\n        props,\n        state,\n        instance,\n        postClone\n      );\n      if (postClone) postClone(hooks2);\n      return hooks2;\n    }\n  };\n  return hooks;\n}\nfunction emptyPlaceholder(vnode) {\n  if (isKeepAlive(vnode)) {\n    vnode = cloneVNode(vnode);\n    vnode.children = null;\n    return vnode;\n  }\n}\nfunction getKeepAliveChild(vnode) {\n  if (!isKeepAlive(vnode)) {\n    return vnode;\n  }\n  if (vnode.component) {\n    return vnode.component.subTree;\n  }\n  const { shapeFlag, children } = vnode;\n  if (children) {\n    if (shapeFlag & 16) {\n      return children[0];\n    }\n    if (shapeFlag & 32 && isFunction(children.default)) {\n      return children.default();\n    }\n  }\n}\nfunction setTransitionHooks(vnode, hooks) {\n  if (vnode.shapeFlag & 6 && vnode.component) {\n    setTransitionHooks(vnode.component.subTree, hooks);\n  } else if (vnode.shapeFlag & 128) {\n    vnode.ssContent.transition = hooks.clone(vnode.ssContent);\n    vnode.ssFallback.transition = hooks.clone(vnode.ssFallback);\n  } else {\n    vnode.transition = hooks;\n  }\n}\nfunction getTransitionRawChildren(children, keepComment = false, parentKey) {\n  let ret = [];\n  let keyedFragmentCount = 0;\n  for (let i = 0; i < children.length; i++) {\n    let child = children[i];\n    const key = parentKey == null ? child.key : String(parentKey) + String(child.key != null ? child.key : i);\n    if (child.type === Fragment) {\n      if (child.patchFlag & 128) keyedFragmentCount++;\n      ret = ret.concat(\n        getTransitionRawChildren(child.children, keepComment, key)\n      );\n    } else if (keepComment || child.type !== Comment) {\n      ret.push(key != null ? cloneVNode(child, { key }) : child);\n    }\n  }\n  if (keyedFragmentCount > 1) {\n    for (let i = 0; i < ret.length; i++) {\n      ret[i].patchFlag = -2;\n    }\n  }\n  return ret;\n}\n\n/*! #__NO_SIDE_EFFECTS__ */\n// @__NO_SIDE_EFFECTS__\nfunction defineComponent(options, extraOptions) {\n  return isFunction(options) ? (\n    // #8326: extend call and options.name access are considered side-effects\n    // by Rollup, so we have to wrap it in a pure-annotated IIFE.\n    /* @__PURE__ */ (() => extend({ name: options.name }, extraOptions, { setup: options }))()\n  ) : options;\n}\n\nconst isAsyncWrapper = (i) => !!i.type.__asyncLoader;\n/*! #__NO_SIDE_EFFECTS__ */\n// @__NO_SIDE_EFFECTS__\nfunction defineAsyncComponent(source) {\n  if (isFunction(source)) {\n    source = { loader: source };\n  }\n  const {\n    loader,\n    loadingComponent,\n    errorComponent,\n    delay = 200,\n    timeout,\n    // undefined = never times out\n    suspensible = true,\n    onError: userOnError\n  } = source;\n  let pendingRequest = null;\n  let resolvedComp;\n  let retries = 0;\n  const retry = () => {\n    retries++;\n    pendingRequest = null;\n    return load();\n  };\n  const load = () => {\n    let thisRequest;\n    return pendingRequest || (thisRequest = pendingRequest = loader().catch((err) => {\n      err = err instanceof Error ? err : new Error(String(err));\n      if (userOnError) {\n        return new Promise((resolve, reject) => {\n          const userRetry = () => resolve(retry());\n          const userFail = () => reject(err);\n          userOnError(err, userRetry, userFail, retries + 1);\n        });\n      } else {\n        throw err;\n      }\n    }).then((comp) => {\n      if (thisRequest !== pendingRequest && pendingRequest) {\n        return pendingRequest;\n      }\n      if (!comp) {\n        warn$1(\n          `Async component loader resolved to undefined. If you are using retry(), make sure to return its return value.`\n        );\n      }\n      if (comp && (comp.__esModule || comp[Symbol.toStringTag] === \"Module\")) {\n        comp = comp.default;\n      }\n      if (comp && !isObject(comp) && !isFunction(comp)) {\n        throw new Error(`Invalid async component load result: ${comp}`);\n      }\n      resolvedComp = comp;\n      return comp;\n    }));\n  };\n  return defineComponent({\n    name: \"AsyncComponentWrapper\",\n    __asyncLoader: load,\n    get __asyncResolved() {\n      return resolvedComp;\n    },\n    setup() {\n      const instance = currentInstance;\n      if (resolvedComp) {\n        return () => createInnerComp(resolvedComp, instance);\n      }\n      const onError = (err) => {\n        pendingRequest = null;\n        handleError(\n          err,\n          instance,\n          13,\n          !errorComponent\n        );\n      };\n      if (suspensible && instance.suspense || false) {\n        return load().then((comp) => {\n          return () => createInnerComp(comp, instance);\n        }).catch((err) => {\n          onError(err);\n          return () => errorComponent ? createVNode(errorComponent, {\n            error: err\n          }) : null;\n        });\n      }\n      const loaded = ref(false);\n      const error = ref();\n      const delayed = ref(!!delay);\n      if (delay) {\n        setTimeout(() => {\n          delayed.value = false;\n        }, delay);\n      }\n      if (timeout != null) {\n        setTimeout(() => {\n          if (!loaded.value && !error.value) {\n            const err = new Error(\n              `Async component timed out after ${timeout}ms.`\n            );\n            onError(err);\n            error.value = err;\n          }\n        }, timeout);\n      }\n      load().then(() => {\n        loaded.value = true;\n        if (instance.parent && isKeepAlive(instance.parent.vnode)) {\n          instance.parent.effect.dirty = true;\n          queueJob(instance.parent.update);\n        }\n      }).catch((err) => {\n        onError(err);\n        error.value = err;\n      });\n      return () => {\n        if (loaded.value && resolvedComp) {\n          return createInnerComp(resolvedComp, instance);\n        } else if (error.value && errorComponent) {\n          return createVNode(errorComponent, {\n            error: error.value\n          });\n        } else if (loadingComponent && !delayed.value) {\n          return createVNode(loadingComponent);\n        }\n      };\n    }\n  });\n}\nfunction createInnerComp(comp, parent) {\n  const { ref: ref2, props, children, ce } = parent.vnode;\n  const vnode = createVNode(comp, props, children);\n  vnode.ref = ref2;\n  vnode.ce = ce;\n  delete parent.vnode.ce;\n  return vnode;\n}\n\nconst isKeepAlive = (vnode) => vnode.type.__isKeepAlive;\nconst KeepAliveImpl = {\n  name: `KeepAlive`,\n  // Marker for special handling inside the renderer. We are not using a ===\n  // check directly on KeepAlive in the renderer, because importing it directly\n  // would prevent it from being tree-shaken.\n  __isKeepAlive: true,\n  props: {\n    include: [String, RegExp, Array],\n    exclude: [String, RegExp, Array],\n    max: [String, Number]\n  },\n  setup(props, { slots }) {\n    const instance = getCurrentInstance();\n    const sharedContext = instance.ctx;\n    const cache = /* @__PURE__ */ new Map();\n    const keys = /* @__PURE__ */ new Set();\n    let current = null;\n    {\n      instance.__v_cache = cache;\n    }\n    const parentSuspense = instance.suspense;\n    const {\n      renderer: {\n        p: patch,\n        m: move,\n        um: _unmount,\n        o: { createElement }\n      }\n    } = sharedContext;\n    const storageContainer = createElement(\"div\");\n    sharedContext.activate = (vnode, container, anchor, namespace, optimized) => {\n      const instance2 = vnode.component;\n      move(vnode, container, anchor, 0, parentSuspense);\n      patch(\n        instance2.vnode,\n        vnode,\n        container,\n        anchor,\n        instance2,\n        parentSuspense,\n        namespace,\n        vnode.slotScopeIds,\n        optimized\n      );\n      queuePostRenderEffect(() => {\n        instance2.isDeactivated = false;\n        if (instance2.a) {\n          invokeArrayFns(instance2.a);\n        }\n        const vnodeHook = vnode.props && vnode.props.onVnodeMounted;\n        if (vnodeHook) {\n          invokeVNodeHook(vnodeHook, instance2.parent, vnode);\n        }\n      }, parentSuspense);\n      {\n        devtoolsComponentAdded(instance2);\n      }\n    };\n    sharedContext.deactivate = (vnode) => {\n      const instance2 = vnode.component;\n      invalidateMount(instance2.m);\n      invalidateMount(instance2.a);\n      move(vnode, storageContainer, null, 1, parentSuspense);\n      queuePostRenderEffect(() => {\n        if (instance2.da) {\n          invokeArrayFns(instance2.da);\n        }\n        const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted;\n        if (vnodeHook) {\n          invokeVNodeHook(vnodeHook, instance2.parent, vnode);\n        }\n        instance2.isDeactivated = true;\n      }, parentSuspense);\n      {\n        devtoolsComponentAdded(instance2);\n      }\n    };\n    function unmount(vnode) {\n      resetShapeFlag(vnode);\n      _unmount(vnode, instance, parentSuspense, true);\n    }\n    function pruneCache(filter) {\n      cache.forEach((vnode, key) => {\n        const name = getComponentName(vnode.type);\n        if (name && (!filter || !filter(name))) {\n          pruneCacheEntry(key);\n        }\n      });\n    }\n    function pruneCacheEntry(key) {\n      const cached = cache.get(key);\n      if (!current || !isSameVNodeType(cached, current)) {\n        unmount(cached);\n      } else if (current) {\n        resetShapeFlag(current);\n      }\n      cache.delete(key);\n      keys.delete(key);\n    }\n    watch(\n      () => [props.include, props.exclude],\n      ([include, exclude]) => {\n        include && pruneCache((name) => matches(include, name));\n        exclude && pruneCache((name) => !matches(exclude, name));\n      },\n      // prune post-render after `current` has been updated\n      { flush: \"post\", deep: true }\n    );\n    let pendingCacheKey = null;\n    const cacheSubtree = () => {\n      if (pendingCacheKey != null) {\n        if (isSuspense(instance.subTree.type)) {\n          queuePostRenderEffect(() => {\n            cache.set(pendingCacheKey, getInnerChild(instance.subTree));\n          }, instance.subTree.suspense);\n        } else {\n          cache.set(pendingCacheKey, getInnerChild(instance.subTree));\n        }\n      }\n    };\n    onMounted(cacheSubtree);\n    onUpdated(cacheSubtree);\n    onBeforeUnmount(() => {\n      cache.forEach((cached) => {\n        const { subTree, suspense } = instance;\n        const vnode = getInnerChild(subTree);\n        if (cached.type === vnode.type && cached.key === vnode.key) {\n          resetShapeFlag(vnode);\n          const da = vnode.component.da;\n          da && queuePostRenderEffect(da, suspense);\n          return;\n        }\n        unmount(cached);\n      });\n    });\n    return () => {\n      pendingCacheKey = null;\n      if (!slots.default) {\n        return null;\n      }\n      const children = slots.default();\n      const rawVNode = children[0];\n      if (children.length > 1) {\n        {\n          warn$1(`KeepAlive should contain exactly one component child.`);\n        }\n        current = null;\n        return children;\n      } else if (!isVNode(rawVNode) || !(rawVNode.shapeFlag & 4) && !(rawVNode.shapeFlag & 128)) {\n        current = null;\n        return rawVNode;\n      }\n      let vnode = getInnerChild(rawVNode);\n      const comp = vnode.type;\n      const name = getComponentName(\n        isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp\n      );\n      const { include, exclude, max } = props;\n      if (include && (!name || !matches(include, name)) || exclude && name && matches(exclude, name)) {\n        current = vnode;\n        return rawVNode;\n      }\n      const key = vnode.key == null ? comp : vnode.key;\n      const cachedVNode = cache.get(key);\n      if (vnode.el) {\n        vnode = cloneVNode(vnode);\n        if (rawVNode.shapeFlag & 128) {\n          rawVNode.ssContent = vnode;\n        }\n      }\n      pendingCacheKey = key;\n      if (cachedVNode) {\n        vnode.el = cachedVNode.el;\n        vnode.component = cachedVNode.component;\n        if (vnode.transition) {\n          setTransitionHooks(vnode, vnode.transition);\n        }\n        vnode.shapeFlag |= 512;\n        keys.delete(key);\n        keys.add(key);\n      } else {\n        keys.add(key);\n        if (max && keys.size > parseInt(max, 10)) {\n          pruneCacheEntry(keys.values().next().value);\n        }\n      }\n      vnode.shapeFlag |= 256;\n      current = vnode;\n      return isSuspense(rawVNode.type) ? rawVNode : vnode;\n    };\n  }\n};\nconst KeepAlive = KeepAliveImpl;\nfunction matches(pattern, name) {\n  if (isArray(pattern)) {\n    return pattern.some((p) => matches(p, name));\n  } else if (isString(pattern)) {\n    return pattern.split(\",\").includes(name);\n  } else if (isRegExp(pattern)) {\n    return pattern.test(name);\n  }\n  return false;\n}\nfunction onActivated(hook, target) {\n  registerKeepAliveHook(hook, \"a\", target);\n}\nfunction onDeactivated(hook, target) {\n  registerKeepAliveHook(hook, \"da\", target);\n}\nfunction registerKeepAliveHook(hook, type, target = currentInstance) {\n  const wrappedHook = hook.__wdc || (hook.__wdc = () => {\n    let current = target;\n    while (current) {\n      if (current.isDeactivated) {\n        return;\n      }\n      current = current.parent;\n    }\n    return hook();\n  });\n  injectHook(type, wrappedHook, target);\n  if (target) {\n    let current = target.parent;\n    while (current && current.parent) {\n      if (isKeepAlive(current.parent.vnode)) {\n        injectToKeepAliveRoot(wrappedHook, type, target, current);\n      }\n      current = current.parent;\n    }\n  }\n}\nfunction injectToKeepAliveRoot(hook, type, target, keepAliveRoot) {\n  const injected = injectHook(\n    type,\n    hook,\n    keepAliveRoot,\n    true\n    /* prepend */\n  );\n  onUnmounted(() => {\n    remove(keepAliveRoot[type], injected);\n  }, target);\n}\nfunction resetShapeFlag(vnode) {\n  vnode.shapeFlag &= ~256;\n  vnode.shapeFlag &= ~512;\n}\nfunction getInnerChild(vnode) {\n  return vnode.shapeFlag & 128 ? vnode.ssContent : vnode;\n}\n\nfunction injectHook(type, hook, target = currentInstance, prepend = false) {\n  if (target) {\n    const hooks = target[type] || (target[type] = []);\n    const wrappedHook = hook.__weh || (hook.__weh = (...args) => {\n      pauseTracking();\n      const reset = setCurrentInstance(target);\n      const res = callWithAsyncErrorHandling(hook, target, type, args);\n      reset();\n      resetTracking();\n      return res;\n    });\n    if (prepend) {\n      hooks.unshift(wrappedHook);\n    } else {\n      hooks.push(wrappedHook);\n    }\n    return wrappedHook;\n  } else {\n    const apiName = toHandlerKey(ErrorTypeStrings$1[type].replace(/ hook$/, \"\"));\n    warn$1(\n      `${apiName} is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup().` + (` If you are using async setup(), make sure to register lifecycle hooks before the first await statement.` )\n    );\n  }\n}\nconst createHook = (lifecycle) => (hook, target = currentInstance) => {\n  if (!isInSSRComponentSetup || lifecycle === \"sp\") {\n    injectHook(lifecycle, (...args) => hook(...args), target);\n  }\n};\nconst onBeforeMount = createHook(\"bm\");\nconst onMounted = createHook(\"m\");\nconst onBeforeUpdate = createHook(\"bu\");\nconst onUpdated = createHook(\"u\");\nconst onBeforeUnmount = createHook(\"bum\");\nconst onUnmounted = createHook(\"um\");\nconst onServerPrefetch = createHook(\"sp\");\nconst onRenderTriggered = createHook(\n  \"rtg\"\n);\nconst onRenderTracked = createHook(\n  \"rtc\"\n);\nfunction onErrorCaptured(hook, target = currentInstance) {\n  injectHook(\"ec\", hook, target);\n}\n\nconst COMPONENTS = \"components\";\nconst DIRECTIVES = \"directives\";\nfunction resolveComponent(name, maybeSelfReference) {\n  return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name;\n}\nconst NULL_DYNAMIC_COMPONENT = Symbol.for(\"v-ndc\");\nfunction resolveDynamicComponent(component) {\n  if (isString(component)) {\n    return resolveAsset(COMPONENTS, component, false) || component;\n  } else {\n    return component || NULL_DYNAMIC_COMPONENT;\n  }\n}\nfunction resolveDirective(name) {\n  return resolveAsset(DIRECTIVES, name);\n}\nfunction resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) {\n  const instance = currentRenderingInstance || currentInstance;\n  if (instance) {\n    const Component = instance.type;\n    if (type === COMPONENTS) {\n      const selfName = getComponentName(\n        Component,\n        false\n      );\n      if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) {\n        return Component;\n      }\n    }\n    const res = (\n      // local registration\n      // check instance[type] first which is resolved for options API\n      resolve(instance[type] || Component[type], name) || // global registration\n      resolve(instance.appContext[type], name)\n    );\n    if (!res && maybeSelfReference) {\n      return Component;\n    }\n    if (warnMissing && !res) {\n      const extra = type === COMPONENTS ? `\nIf this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.` : ``;\n      warn$1(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`);\n    }\n    return res;\n  } else {\n    warn$1(\n      `resolve${capitalize(type.slice(0, -1))} can only be used in render() or setup().`\n    );\n  }\n}\nfunction resolve(registry, name) {\n  return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]);\n}\n\nfunction renderList(source, renderItem, cache, index) {\n  let ret;\n  const cached = cache && cache[index];\n  if (isArray(source) || isString(source)) {\n    ret = new Array(source.length);\n    for (let i = 0, l = source.length; i < l; i++) {\n      ret[i] = renderItem(source[i], i, void 0, cached && cached[i]);\n    }\n  } else if (typeof source === \"number\") {\n    if (!Number.isInteger(source)) {\n      warn$1(`The v-for range expect an integer value but got ${source}.`);\n    }\n    ret = new Array(source);\n    for (let i = 0; i < source; i++) {\n      ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]);\n    }\n  } else if (isObject(source)) {\n    if (source[Symbol.iterator]) {\n      ret = Array.from(\n        source,\n        (item, i) => renderItem(item, i, void 0, cached && cached[i])\n      );\n    } else {\n      const keys = Object.keys(source);\n      ret = new Array(keys.length);\n      for (let i = 0, l = keys.length; i < l; i++) {\n        const key = keys[i];\n        ret[i] = renderItem(source[key], key, i, cached && cached[i]);\n      }\n    }\n  } else {\n    ret = [];\n  }\n  if (cache) {\n    cache[index] = ret;\n  }\n  return ret;\n}\n\nfunction createSlots(slots, dynamicSlots) {\n  for (let i = 0; i < dynamicSlots.length; i++) {\n    const slot = dynamicSlots[i];\n    if (isArray(slot)) {\n      for (let j = 0; j < slot.length; j++) {\n        slots[slot[j].name] = slot[j].fn;\n      }\n    } else if (slot) {\n      slots[slot.name] = slot.key ? (...args) => {\n        const res = slot.fn(...args);\n        if (res) res.key = slot.key;\n        return res;\n      } : slot.fn;\n    }\n  }\n  return slots;\n}\n\nfunction renderSlot(slots, name, props = {}, fallback, noSlotted) {\n  if (currentRenderingInstance.isCE || currentRenderingInstance.parent && isAsyncWrapper(currentRenderingInstance.parent) && currentRenderingInstance.parent.isCE) {\n    if (name !== \"default\") props.name = name;\n    return createVNode(\"slot\", props, fallback && fallback());\n  }\n  let slot = slots[name];\n  if (slot && slot.length > 1) {\n    warn$1(\n      `SSR-optimized slot function detected in a non-SSR-optimized render function. You need to mark this component with $dynamic-slots in the parent template.`\n    );\n    slot = () => [];\n  }\n  if (slot && slot._c) {\n    slot._d = false;\n  }\n  openBlock();\n  const validSlotContent = slot && ensureValidVNode(slot(props));\n  const rendered = createBlock(\n    Fragment,\n    {\n      key: (props.key || // slot content array of a dynamic conditional slot may have a branch\n      // key attached in the `createSlots` helper, respect that\n      validSlotContent && validSlotContent.key || `_${name}`) + // #7256 force differentiate fallback content from actual content\n      (!validSlotContent && fallback ? \"_fb\" : \"\")\n    },\n    validSlotContent || (fallback ? fallback() : []),\n    validSlotContent && slots._ === 1 ? 64 : -2\n  );\n  if (!noSlotted && rendered.scopeId) {\n    rendered.slotScopeIds = [rendered.scopeId + \"-s\"];\n  }\n  if (slot && slot._c) {\n    slot._d = true;\n  }\n  return rendered;\n}\nfunction ensureValidVNode(vnodes) {\n  return vnodes.some((child) => {\n    if (!isVNode(child)) return true;\n    if (child.type === Comment) return false;\n    if (child.type === Fragment && !ensureValidVNode(child.children))\n      return false;\n    return true;\n  }) ? vnodes : null;\n}\n\nfunction toHandlers(obj, preserveCaseIfNecessary) {\n  const ret = {};\n  if (!isObject(obj)) {\n    warn$1(`v-on with no argument expects an object value.`);\n    return ret;\n  }\n  for (const key in obj) {\n    ret[preserveCaseIfNecessary && /[A-Z]/.test(key) ? `on:${key}` : toHandlerKey(key)] = obj[key];\n  }\n  return ret;\n}\n\nconst getPublicInstance = (i) => {\n  if (!i) return null;\n  if (isStatefulComponent(i)) return getComponentPublicInstance(i);\n  return getPublicInstance(i.parent);\n};\nconst publicPropertiesMap = (\n  // Move PURE marker to new line to workaround compiler discarding it\n  // due to type annotation\n  /* @__PURE__ */ extend(/* @__PURE__ */ Object.create(null), {\n    $: (i) => i,\n    $el: (i) => i.vnode.el,\n    $data: (i) => i.data,\n    $props: (i) => shallowReadonly(i.props) ,\n    $attrs: (i) => shallowReadonly(i.attrs) ,\n    $slots: (i) => shallowReadonly(i.slots) ,\n    $refs: (i) => shallowReadonly(i.refs) ,\n    $parent: (i) => getPublicInstance(i.parent),\n    $root: (i) => getPublicInstance(i.root),\n    $emit: (i) => i.emit,\n    $options: (i) => resolveMergedOptions(i) ,\n    $forceUpdate: (i) => i.f || (i.f = () => {\n      i.effect.dirty = true;\n      queueJob(i.update);\n    }),\n    $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)),\n    $watch: (i) => instanceWatch.bind(i) \n  })\n);\nconst isReservedPrefix = (key) => key === \"_\" || key === \"$\";\nconst hasSetupBinding = (state, key) => state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key);\nconst PublicInstanceProxyHandlers = {\n  get({ _: instance }, key) {\n    if (key === \"__v_skip\") {\n      return true;\n    }\n    const { ctx, setupState, data, props, accessCache, type, appContext } = instance;\n    if (key === \"__isVue\") {\n      return true;\n    }\n    let normalizedProps;\n    if (key[0] !== \"$\") {\n      const n = accessCache[key];\n      if (n !== void 0) {\n        switch (n) {\n          case 1 /* SETUP */:\n            return setupState[key];\n          case 2 /* DATA */:\n            return data[key];\n          case 4 /* CONTEXT */:\n            return ctx[key];\n          case 3 /* PROPS */:\n            return props[key];\n        }\n      } else if (hasSetupBinding(setupState, key)) {\n        accessCache[key] = 1 /* SETUP */;\n        return setupState[key];\n      } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {\n        accessCache[key] = 2 /* DATA */;\n        return data[key];\n      } else if (\n        // only cache other properties when instance has declared (thus stable)\n        // props\n        (normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key)\n      ) {\n        accessCache[key] = 3 /* PROPS */;\n        return props[key];\n      } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {\n        accessCache[key] = 4 /* CONTEXT */;\n        return ctx[key];\n      } else if (shouldCacheAccess) {\n        accessCache[key] = 0 /* OTHER */;\n      }\n    }\n    const publicGetter = publicPropertiesMap[key];\n    let cssModule, globalProperties;\n    if (publicGetter) {\n      if (key === \"$attrs\") {\n        track(instance.attrs, \"get\", \"\");\n        markAttrsAccessed();\n      } else if (key === \"$slots\") {\n        track(instance, \"get\", key);\n      }\n      return publicGetter(instance);\n    } else if (\n      // css module (injected by vue-loader)\n      (cssModule = type.__cssModules) && (cssModule = cssModule[key])\n    ) {\n      return cssModule;\n    } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {\n      accessCache[key] = 4 /* CONTEXT */;\n      return ctx[key];\n    } else if (\n      // global properties\n      globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key)\n    ) {\n      {\n        return globalProperties[key];\n      }\n    } else if (currentRenderingInstance && (!isString(key) || // #1091 avoid internal isRef/isVNode checks on component instance leading\n    // to infinite warning loop\n    key.indexOf(\"__v\") !== 0)) {\n      if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {\n        warn$1(\n          `Property ${JSON.stringify(\n            key\n          )} must be accessed via $data because it starts with a reserved character (\"$\" or \"_\") and is not proxied on the render context.`\n        );\n      } else if (instance === currentRenderingInstance) {\n        warn$1(\n          `Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.`\n        );\n      }\n    }\n  },\n  set({ _: instance }, key, value) {\n    const { data, setupState, ctx } = instance;\n    if (hasSetupBinding(setupState, key)) {\n      setupState[key] = value;\n      return true;\n    } else if (setupState.__isScriptSetup && hasOwn(setupState, key)) {\n      warn$1(`Cannot mutate <script setup> binding \"${key}\" from Options API.`);\n      return false;\n    } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {\n      data[key] = value;\n      return true;\n    } else if (hasOwn(instance.props, key)) {\n      warn$1(`Attempting to mutate prop \"${key}\". Props are readonly.`);\n      return false;\n    }\n    if (key[0] === \"$\" && key.slice(1) in instance) {\n      warn$1(\n        `Attempting to mutate public property \"${key}\". Properties starting with $ are reserved and readonly.`\n      );\n      return false;\n    } else {\n      if (key in instance.appContext.config.globalProperties) {\n        Object.defineProperty(ctx, key, {\n          enumerable: true,\n          configurable: true,\n          value\n        });\n      } else {\n        ctx[key] = value;\n      }\n    }\n    return true;\n  },\n  has({\n    _: { data, setupState, accessCache, ctx, appContext, propsOptions }\n  }, key) {\n    let normalizedProps;\n    return !!accessCache[key] || data !== EMPTY_OBJ && hasOwn(data, key) || hasSetupBinding(setupState, key) || (normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key) || hasOwn(ctx, key) || hasOwn(publicPropertiesMap, key) || hasOwn(appContext.config.globalProperties, key);\n  },\n  defineProperty(target, key, descriptor) {\n    if (descriptor.get != null) {\n      target._.accessCache[key] = 0;\n    } else if (hasOwn(descriptor, \"value\")) {\n      this.set(target, key, descriptor.value, null);\n    }\n    return Reflect.defineProperty(target, key, descriptor);\n  }\n};\n{\n  PublicInstanceProxyHandlers.ownKeys = (target) => {\n    warn$1(\n      `Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.`\n    );\n    return Reflect.ownKeys(target);\n  };\n}\nconst RuntimeCompiledPublicInstanceProxyHandlers = /* @__PURE__ */ extend(\n  {},\n  PublicInstanceProxyHandlers,\n  {\n    get(target, key) {\n      if (key === Symbol.unscopables) {\n        return;\n      }\n      return PublicInstanceProxyHandlers.get(target, key, target);\n    },\n    has(_, key) {\n      const has = key[0] !== \"_\" && !isGloballyAllowed(key);\n      if (!has && PublicInstanceProxyHandlers.has(_, key)) {\n        warn$1(\n          `Property ${JSON.stringify(\n            key\n          )} should not start with _ which is a reserved prefix for Vue internals.`\n        );\n      }\n      return has;\n    }\n  }\n);\nfunction createDevRenderContext(instance) {\n  const target = {};\n  Object.defineProperty(target, `_`, {\n    configurable: true,\n    enumerable: false,\n    get: () => instance\n  });\n  Object.keys(publicPropertiesMap).forEach((key) => {\n    Object.defineProperty(target, key, {\n      configurable: true,\n      enumerable: false,\n      get: () => publicPropertiesMap[key](instance),\n      // intercepted by the proxy so no need for implementation,\n      // but needed to prevent set errors\n      set: NOOP\n    });\n  });\n  return target;\n}\nfunction exposePropsOnRenderContext(instance) {\n  const {\n    ctx,\n    propsOptions: [propsOptions]\n  } = instance;\n  if (propsOptions) {\n    Object.keys(propsOptions).forEach((key) => {\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => instance.props[key],\n        set: NOOP\n      });\n    });\n  }\n}\nfunction exposeSetupStateOnRenderContext(instance) {\n  const { ctx, setupState } = instance;\n  Object.keys(toRaw(setupState)).forEach((key) => {\n    if (!setupState.__isScriptSetup) {\n      if (isReservedPrefix(key[0])) {\n        warn$1(\n          `setup() return property ${JSON.stringify(\n            key\n          )} should not start with \"$\" or \"_\" which are reserved prefixes for Vue internals.`\n        );\n        return;\n      }\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => setupState[key],\n        set: NOOP\n      });\n    }\n  });\n}\n\nconst warnRuntimeUsage = (method) => warn$1(\n  `${method}() is a compiler-hint helper that is only usable inside <script setup> of a single file component. Its arguments should be compiled away and passing it at runtime has no effect.`\n);\nfunction defineProps() {\n  {\n    warnRuntimeUsage(`defineProps`);\n  }\n  return null;\n}\nfunction defineEmits() {\n  {\n    warnRuntimeUsage(`defineEmits`);\n  }\n  return null;\n}\nfunction defineExpose(exposed) {\n  {\n    warnRuntimeUsage(`defineExpose`);\n  }\n}\nfunction defineOptions(options) {\n  {\n    warnRuntimeUsage(`defineOptions`);\n  }\n}\nfunction defineSlots() {\n  {\n    warnRuntimeUsage(`defineSlots`);\n  }\n  return null;\n}\nfunction defineModel() {\n  {\n    warnRuntimeUsage(\"defineModel\");\n  }\n}\nfunction withDefaults(props, defaults) {\n  {\n    warnRuntimeUsage(`withDefaults`);\n  }\n  return null;\n}\nfunction useSlots() {\n  return getContext().slots;\n}\nfunction useAttrs() {\n  return getContext().attrs;\n}\nfunction getContext() {\n  const i = getCurrentInstance();\n  if (!i) {\n    warn$1(`useContext() called without active instance.`);\n  }\n  return i.setupContext || (i.setupContext = createSetupContext(i));\n}\nfunction normalizePropsOrEmits(props) {\n  return isArray(props) ? props.reduce(\n    (normalized, p) => (normalized[p] = null, normalized),\n    {}\n  ) : props;\n}\nfunction mergeDefaults(raw, defaults) {\n  const props = normalizePropsOrEmits(raw);\n  for (const key in defaults) {\n    if (key.startsWith(\"__skip\")) continue;\n    let opt = props[key];\n    if (opt) {\n      if (isArray(opt) || isFunction(opt)) {\n        opt = props[key] = { type: opt, default: defaults[key] };\n      } else {\n        opt.default = defaults[key];\n      }\n    } else if (opt === null) {\n      opt = props[key] = { default: defaults[key] };\n    } else {\n      warn$1(`props default key \"${key}\" has no corresponding declaration.`);\n    }\n    if (opt && defaults[`__skip_${key}`]) {\n      opt.skipFactory = true;\n    }\n  }\n  return props;\n}\nfunction mergeModels(a, b) {\n  if (!a || !b) return a || b;\n  if (isArray(a) && isArray(b)) return a.concat(b);\n  return extend({}, normalizePropsOrEmits(a), normalizePropsOrEmits(b));\n}\nfunction createPropsRestProxy(props, excludedKeys) {\n  const ret = {};\n  for (const key in props) {\n    if (!excludedKeys.includes(key)) {\n      Object.defineProperty(ret, key, {\n        enumerable: true,\n        get: () => props[key]\n      });\n    }\n  }\n  return ret;\n}\nfunction withAsyncContext(getAwaitable) {\n  const ctx = getCurrentInstance();\n  if (!ctx) {\n    warn$1(\n      `withAsyncContext called without active current instance. This is likely a bug.`\n    );\n  }\n  let awaitable = getAwaitable();\n  unsetCurrentInstance();\n  if (isPromise(awaitable)) {\n    awaitable = awaitable.catch((e) => {\n      setCurrentInstance(ctx);\n      throw e;\n    });\n  }\n  return [awaitable, () => setCurrentInstance(ctx)];\n}\n\nfunction createDuplicateChecker() {\n  const cache = /* @__PURE__ */ Object.create(null);\n  return (type, key) => {\n    if (cache[key]) {\n      warn$1(`${type} property \"${key}\" is already defined in ${cache[key]}.`);\n    } else {\n      cache[key] = type;\n    }\n  };\n}\nlet shouldCacheAccess = true;\nfunction applyOptions(instance) {\n  const options = resolveMergedOptions(instance);\n  const publicThis = instance.proxy;\n  const ctx = instance.ctx;\n  shouldCacheAccess = false;\n  if (options.beforeCreate) {\n    callHook$1(options.beforeCreate, instance, \"bc\");\n  }\n  const {\n    // state\n    data: dataOptions,\n    computed: computedOptions,\n    methods,\n    watch: watchOptions,\n    provide: provideOptions,\n    inject: injectOptions,\n    // lifecycle\n    created,\n    beforeMount,\n    mounted,\n    beforeUpdate,\n    updated,\n    activated,\n    deactivated,\n    beforeDestroy,\n    beforeUnmount,\n    destroyed,\n    unmounted,\n    render,\n    renderTracked,\n    renderTriggered,\n    errorCaptured,\n    serverPrefetch,\n    // public API\n    expose,\n    inheritAttrs,\n    // assets\n    components,\n    directives,\n    filters\n  } = options;\n  const checkDuplicateProperties = createDuplicateChecker() ;\n  {\n    const [propsOptions] = instance.propsOptions;\n    if (propsOptions) {\n      for (const key in propsOptions) {\n        checkDuplicateProperties(\"Props\" /* PROPS */, key);\n      }\n    }\n  }\n  if (injectOptions) {\n    resolveInjections(injectOptions, ctx, checkDuplicateProperties);\n  }\n  if (methods) {\n    for (const key in methods) {\n      const methodHandler = methods[key];\n      if (isFunction(methodHandler)) {\n        {\n          Object.defineProperty(ctx, key, {\n            value: methodHandler.bind(publicThis),\n            configurable: true,\n            enumerable: true,\n            writable: true\n          });\n        }\n        {\n          checkDuplicateProperties(\"Methods\" /* METHODS */, key);\n        }\n      } else {\n        warn$1(\n          `Method \"${key}\" has type \"${typeof methodHandler}\" in the component definition. Did you reference the function correctly?`\n        );\n      }\n    }\n  }\n  if (dataOptions) {\n    if (!isFunction(dataOptions)) {\n      warn$1(\n        `The data option must be a function. Plain object usage is no longer supported.`\n      );\n    }\n    const data = dataOptions.call(publicThis, publicThis);\n    if (isPromise(data)) {\n      warn$1(\n        `data() returned a Promise - note data() cannot be async; If you intend to perform data fetching before component renders, use async setup() + <Suspense>.`\n      );\n    }\n    if (!isObject(data)) {\n      warn$1(`data() should return an object.`);\n    } else {\n      instance.data = reactive(data);\n      {\n        for (const key in data) {\n          checkDuplicateProperties(\"Data\" /* DATA */, key);\n          if (!isReservedPrefix(key[0])) {\n            Object.defineProperty(ctx, key, {\n              configurable: true,\n              enumerable: true,\n              get: () => data[key],\n              set: NOOP\n            });\n          }\n        }\n      }\n    }\n  }\n  shouldCacheAccess = true;\n  if (computedOptions) {\n    for (const key in computedOptions) {\n      const opt = computedOptions[key];\n      const get = isFunction(opt) ? opt.bind(publicThis, publicThis) : isFunction(opt.get) ? opt.get.bind(publicThis, publicThis) : NOOP;\n      if (get === NOOP) {\n        warn$1(`Computed property \"${key}\" has no getter.`);\n      }\n      const set = !isFunction(opt) && isFunction(opt.set) ? opt.set.bind(publicThis) : () => {\n        warn$1(\n          `Write operation failed: computed property \"${key}\" is readonly.`\n        );\n      } ;\n      const c = computed({\n        get,\n        set\n      });\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => c.value,\n        set: (v) => c.value = v\n      });\n      {\n        checkDuplicateProperties(\"Computed\" /* COMPUTED */, key);\n      }\n    }\n  }\n  if (watchOptions) {\n    for (const key in watchOptions) {\n      createWatcher(watchOptions[key], ctx, publicThis, key);\n    }\n  }\n  if (provideOptions) {\n    const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions;\n    Reflect.ownKeys(provides).forEach((key) => {\n      provide(key, provides[key]);\n    });\n  }\n  if (created) {\n    callHook$1(created, instance, \"c\");\n  }\n  function registerLifecycleHook(register, hook) {\n    if (isArray(hook)) {\n      hook.forEach((_hook) => register(_hook.bind(publicThis)));\n    } else if (hook) {\n      register(hook.bind(publicThis));\n    }\n  }\n  registerLifecycleHook(onBeforeMount, beforeMount);\n  registerLifecycleHook(onMounted, mounted);\n  registerLifecycleHook(onBeforeUpdate, beforeUpdate);\n  registerLifecycleHook(onUpdated, updated);\n  registerLifecycleHook(onActivated, activated);\n  registerLifecycleHook(onDeactivated, deactivated);\n  registerLifecycleHook(onErrorCaptured, errorCaptured);\n  registerLifecycleHook(onRenderTracked, renderTracked);\n  registerLifecycleHook(onRenderTriggered, renderTriggered);\n  registerLifecycleHook(onBeforeUnmount, beforeUnmount);\n  registerLifecycleHook(onUnmounted, unmounted);\n  registerLifecycleHook(onServerPrefetch, serverPrefetch);\n  if (isArray(expose)) {\n    if (expose.length) {\n      const exposed = instance.exposed || (instance.exposed = {});\n      expose.forEach((key) => {\n        Object.defineProperty(exposed, key, {\n          get: () => publicThis[key],\n          set: (val) => publicThis[key] = val\n        });\n      });\n    } else if (!instance.exposed) {\n      instance.exposed = {};\n    }\n  }\n  if (render && instance.render === NOOP) {\n    instance.render = render;\n  }\n  if (inheritAttrs != null) {\n    instance.inheritAttrs = inheritAttrs;\n  }\n  if (components) instance.components = components;\n  if (directives) instance.directives = directives;\n}\nfunction resolveInjections(injectOptions, ctx, checkDuplicateProperties = NOOP) {\n  if (isArray(injectOptions)) {\n    injectOptions = normalizeInject(injectOptions);\n  }\n  for (const key in injectOptions) {\n    const opt = injectOptions[key];\n    let injected;\n    if (isObject(opt)) {\n      if (\"default\" in opt) {\n        injected = inject(\n          opt.from || key,\n          opt.default,\n          true\n        );\n      } else {\n        injected = inject(opt.from || key);\n      }\n    } else {\n      injected = inject(opt);\n    }\n    if (isRef(injected)) {\n      Object.defineProperty(ctx, key, {\n        enumerable: true,\n        configurable: true,\n        get: () => injected.value,\n        set: (v) => injected.value = v\n      });\n    } else {\n      ctx[key] = injected;\n    }\n    {\n      checkDuplicateProperties(\"Inject\" /* INJECT */, key);\n    }\n  }\n}\nfunction callHook$1(hook, instance, type) {\n  callWithAsyncErrorHandling(\n    isArray(hook) ? hook.map((h) => h.bind(instance.proxy)) : hook.bind(instance.proxy),\n    instance,\n    type\n  );\n}\nfunction createWatcher(raw, ctx, publicThis, key) {\n  const getter = key.includes(\".\") ? createPathGetter(publicThis, key) : () => publicThis[key];\n  if (isString(raw)) {\n    const handler = ctx[raw];\n    if (isFunction(handler)) {\n      watch(getter, handler);\n    } else {\n      warn$1(`Invalid watch handler specified by key \"${raw}\"`, handler);\n    }\n  } else if (isFunction(raw)) {\n    watch(getter, raw.bind(publicThis));\n  } else if (isObject(raw)) {\n    if (isArray(raw)) {\n      raw.forEach((r) => createWatcher(r, ctx, publicThis, key));\n    } else {\n      const handler = isFunction(raw.handler) ? raw.handler.bind(publicThis) : ctx[raw.handler];\n      if (isFunction(handler)) {\n        watch(getter, handler, raw);\n      } else {\n        warn$1(`Invalid watch handler specified by key \"${raw.handler}\"`, handler);\n      }\n    }\n  } else {\n    warn$1(`Invalid watch option: \"${key}\"`, raw);\n  }\n}\nfunction resolveMergedOptions(instance) {\n  const base = instance.type;\n  const { mixins, extends: extendsOptions } = base;\n  const {\n    mixins: globalMixins,\n    optionsCache: cache,\n    config: { optionMergeStrategies }\n  } = instance.appContext;\n  const cached = cache.get(base);\n  let resolved;\n  if (cached) {\n    resolved = cached;\n  } else if (!globalMixins.length && !mixins && !extendsOptions) {\n    {\n      resolved = base;\n    }\n  } else {\n    resolved = {};\n    if (globalMixins.length) {\n      globalMixins.forEach(\n        (m) => mergeOptions(resolved, m, optionMergeStrategies, true)\n      );\n    }\n    mergeOptions(resolved, base, optionMergeStrategies);\n  }\n  if (isObject(base)) {\n    cache.set(base, resolved);\n  }\n  return resolved;\n}\nfunction mergeOptions(to, from, strats, asMixin = false) {\n  const { mixins, extends: extendsOptions } = from;\n  if (extendsOptions) {\n    mergeOptions(to, extendsOptions, strats, true);\n  }\n  if (mixins) {\n    mixins.forEach(\n      (m) => mergeOptions(to, m, strats, true)\n    );\n  }\n  for (const key in from) {\n    if (asMixin && key === \"expose\") {\n      warn$1(\n        `\"expose\" option is ignored when declared in mixins or extends. It should only be declared in the base component itself.`\n      );\n    } else {\n      const strat = internalOptionMergeStrats[key] || strats && strats[key];\n      to[key] = strat ? strat(to[key], from[key]) : from[key];\n    }\n  }\n  return to;\n}\nconst internalOptionMergeStrats = {\n  data: mergeDataFn,\n  props: mergeEmitsOrPropsOptions,\n  emits: mergeEmitsOrPropsOptions,\n  // objects\n  methods: mergeObjectOptions,\n  computed: mergeObjectOptions,\n  // lifecycle\n  beforeCreate: mergeAsArray$1,\n  created: mergeAsArray$1,\n  beforeMount: mergeAsArray$1,\n  mounted: mergeAsArray$1,\n  beforeUpdate: mergeAsArray$1,\n  updated: mergeAsArray$1,\n  beforeDestroy: mergeAsArray$1,\n  beforeUnmount: mergeAsArray$1,\n  destroyed: mergeAsArray$1,\n  unmounted: mergeAsArray$1,\n  activated: mergeAsArray$1,\n  deactivated: mergeAsArray$1,\n  errorCaptured: mergeAsArray$1,\n  serverPrefetch: mergeAsArray$1,\n  // assets\n  components: mergeObjectOptions,\n  directives: mergeObjectOptions,\n  // watch\n  watch: mergeWatchOptions,\n  // provide / inject\n  provide: mergeDataFn,\n  inject: mergeInject\n};\nfunction mergeDataFn(to, from) {\n  if (!from) {\n    return to;\n  }\n  if (!to) {\n    return from;\n  }\n  return function mergedDataFn() {\n    return (extend)(\n      isFunction(to) ? to.call(this, this) : to,\n      isFunction(from) ? from.call(this, this) : from\n    );\n  };\n}\nfunction mergeInject(to, from) {\n  return mergeObjectOptions(normalizeInject(to), normalizeInject(from));\n}\nfunction normalizeInject(raw) {\n  if (isArray(raw)) {\n    const res = {};\n    for (let i = 0; i < raw.length; i++) {\n      res[raw[i]] = raw[i];\n    }\n    return res;\n  }\n  return raw;\n}\nfunction mergeAsArray$1(to, from) {\n  return to ? [...new Set([].concat(to, from))] : from;\n}\nfunction mergeObjectOptions(to, from) {\n  return to ? extend(/* @__PURE__ */ Object.create(null), to, from) : from;\n}\nfunction mergeEmitsOrPropsOptions(to, from) {\n  if (to) {\n    if (isArray(to) && isArray(from)) {\n      return [.../* @__PURE__ */ new Set([...to, ...from])];\n    }\n    return extend(\n      /* @__PURE__ */ Object.create(null),\n      normalizePropsOrEmits(to),\n      normalizePropsOrEmits(from != null ? from : {})\n    );\n  } else {\n    return from;\n  }\n}\nfunction mergeWatchOptions(to, from) {\n  if (!to) return from;\n  if (!from) return to;\n  const merged = extend(/* @__PURE__ */ Object.create(null), to);\n  for (const key in from) {\n    merged[key] = mergeAsArray$1(to[key], from[key]);\n  }\n  return merged;\n}\n\nfunction createAppContext() {\n  return {\n    app: null,\n    config: {\n      isNativeTag: NO,\n      performance: false,\n      globalProperties: {},\n      optionMergeStrategies: {},\n      errorHandler: void 0,\n      warnHandler: void 0,\n      compilerOptions: {}\n    },\n    mixins: [],\n    components: {},\n    directives: {},\n    provides: /* @__PURE__ */ Object.create(null),\n    optionsCache: /* @__PURE__ */ new WeakMap(),\n    propsCache: /* @__PURE__ */ new WeakMap(),\n    emitsCache: /* @__PURE__ */ new WeakMap()\n  };\n}\nlet uid$1 = 0;\nfunction createAppAPI(render, hydrate) {\n  return function createApp(rootComponent, rootProps = null) {\n    if (!isFunction(rootComponent)) {\n      rootComponent = extend({}, rootComponent);\n    }\n    if (rootProps != null && !isObject(rootProps)) {\n      warn$1(`root props passed to app.mount() must be an object.`);\n      rootProps = null;\n    }\n    const context = createAppContext();\n    const installedPlugins = /* @__PURE__ */ new WeakSet();\n    let isMounted = false;\n    const app = context.app = {\n      _uid: uid$1++,\n      _component: rootComponent,\n      _props: rootProps,\n      _container: null,\n      _context: context,\n      _instance: null,\n      version,\n      get config() {\n        return context.config;\n      },\n      set config(v) {\n        {\n          warn$1(\n            `app.config cannot be replaced. Modify individual options instead.`\n          );\n        }\n      },\n      use(plugin, ...options) {\n        if (installedPlugins.has(plugin)) {\n          warn$1(`Plugin has already been applied to target app.`);\n        } else if (plugin && isFunction(plugin.install)) {\n          installedPlugins.add(plugin);\n          plugin.install(app, ...options);\n        } else if (isFunction(plugin)) {\n          installedPlugins.add(plugin);\n          plugin(app, ...options);\n        } else {\n          warn$1(\n            `A plugin must either be a function or an object with an \"install\" function.`\n          );\n        }\n        return app;\n      },\n      mixin(mixin) {\n        {\n          if (!context.mixins.includes(mixin)) {\n            context.mixins.push(mixin);\n          } else {\n            warn$1(\n              \"Mixin has already been applied to target app\" + (mixin.name ? `: ${mixin.name}` : \"\")\n            );\n          }\n        }\n        return app;\n      },\n      component(name, component) {\n        {\n          validateComponentName(name, context.config);\n        }\n        if (!component) {\n          return context.components[name];\n        }\n        if (context.components[name]) {\n          warn$1(`Component \"${name}\" has already been registered in target app.`);\n        }\n        context.components[name] = component;\n        return app;\n      },\n      directive(name, directive) {\n        {\n          validateDirectiveName(name);\n        }\n        if (!directive) {\n          return context.directives[name];\n        }\n        if (context.directives[name]) {\n          warn$1(`Directive \"${name}\" has already been registered in target app.`);\n        }\n        context.directives[name] = directive;\n        return app;\n      },\n      mount(rootContainer, isHydrate, namespace) {\n        if (!isMounted) {\n          if (rootContainer.__vue_app__) {\n            warn$1(\n              `There is already an app instance mounted on the host container.\n If you want to mount another app on the same host container, you need to unmount the previous app by calling \\`app.unmount()\\` first.`\n            );\n          }\n          const vnode = createVNode(rootComponent, rootProps);\n          vnode.appContext = context;\n          if (namespace === true) {\n            namespace = \"svg\";\n          } else if (namespace === false) {\n            namespace = void 0;\n          }\n          {\n            context.reload = () => {\n              render(\n                cloneVNode(vnode),\n                rootContainer,\n                namespace\n              );\n            };\n          }\n          if (isHydrate && hydrate) {\n            hydrate(vnode, rootContainer);\n          } else {\n            render(vnode, rootContainer, namespace);\n          }\n          isMounted = true;\n          app._container = rootContainer;\n          rootContainer.__vue_app__ = app;\n          {\n            app._instance = vnode.component;\n            devtoolsInitApp(app, version);\n          }\n          return getComponentPublicInstance(vnode.component);\n        } else {\n          warn$1(\n            `App has already been mounted.\nIf you want to remount the same app, move your app creation logic into a factory function and create fresh app instances for each mount - e.g. \\`const createMyApp = () => createApp(App)\\``\n          );\n        }\n      },\n      unmount() {\n        if (isMounted) {\n          render(null, app._container);\n          {\n            app._instance = null;\n            devtoolsUnmountApp(app);\n          }\n          delete app._container.__vue_app__;\n        } else {\n          warn$1(`Cannot unmount an app that is not mounted.`);\n        }\n      },\n      provide(key, value) {\n        if (key in context.provides) {\n          warn$1(\n            `App already provides property with key \"${String(key)}\". It will be overwritten with the new value.`\n          );\n        }\n        context.provides[key] = value;\n        return app;\n      },\n      runWithContext(fn) {\n        const lastApp = currentApp;\n        currentApp = app;\n        try {\n          return fn();\n        } finally {\n          currentApp = lastApp;\n        }\n      }\n    };\n    return app;\n  };\n}\nlet currentApp = null;\n\nfunction provide(key, value) {\n  if (!currentInstance) {\n    {\n      warn$1(`provide() can only be used inside setup().`);\n    }\n  } else {\n    let provides = currentInstance.provides;\n    const parentProvides = currentInstance.parent && currentInstance.parent.provides;\n    if (parentProvides === provides) {\n      provides = currentInstance.provides = Object.create(parentProvides);\n    }\n    provides[key] = value;\n  }\n}\nfunction inject(key, defaultValue, treatDefaultAsFactory = false) {\n  const instance = currentInstance || currentRenderingInstance;\n  if (instance || currentApp) {\n    const provides = instance ? instance.parent == null ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides : currentApp._context.provides;\n    if (provides && key in provides) {\n      return provides[key];\n    } else if (arguments.length > 1) {\n      return treatDefaultAsFactory && isFunction(defaultValue) ? defaultValue.call(instance && instance.proxy) : defaultValue;\n    } else {\n      warn$1(`injection \"${String(key)}\" not found.`);\n    }\n  } else {\n    warn$1(`inject() can only be used inside setup() or functional components.`);\n  }\n}\nfunction hasInjectionContext() {\n  return !!(currentInstance || currentRenderingInstance || currentApp);\n}\n\nconst internalObjectProto = {};\nconst createInternalObject = () => Object.create(internalObjectProto);\nconst isInternalObject = (obj) => Object.getPrototypeOf(obj) === internalObjectProto;\n\nfunction initProps(instance, rawProps, isStateful, isSSR = false) {\n  const props = {};\n  const attrs = createInternalObject();\n  instance.propsDefaults = /* @__PURE__ */ Object.create(null);\n  setFullProps(instance, rawProps, props, attrs);\n  for (const key in instance.propsOptions[0]) {\n    if (!(key in props)) {\n      props[key] = void 0;\n    }\n  }\n  {\n    validateProps(rawProps || {}, props, instance);\n  }\n  if (isStateful) {\n    instance.props = isSSR ? props : shallowReactive(props);\n  } else {\n    if (!instance.type.props) {\n      instance.props = attrs;\n    } else {\n      instance.props = props;\n    }\n  }\n  instance.attrs = attrs;\n}\nfunction isInHmrContext(instance) {\n  while (instance) {\n    if (instance.type.__hmrId) return true;\n    instance = instance.parent;\n  }\n}\nfunction updateProps(instance, rawProps, rawPrevProps, optimized) {\n  const {\n    props,\n    attrs,\n    vnode: { patchFlag }\n  } = instance;\n  const rawCurrentProps = toRaw(props);\n  const [options] = instance.propsOptions;\n  let hasAttrsChanged = false;\n  if (\n    // always force full diff in dev\n    // - #1942 if hmr is enabled with sfc component\n    // - vite#872 non-sfc component used by sfc component\n    !isInHmrContext(instance) && (optimized || patchFlag > 0) && !(patchFlag & 16)\n  ) {\n    if (patchFlag & 8) {\n      const propsToUpdate = instance.vnode.dynamicProps;\n      for (let i = 0; i < propsToUpdate.length; i++) {\n        let key = propsToUpdate[i];\n        if (isEmitListener(instance.emitsOptions, key)) {\n          continue;\n        }\n        const value = rawProps[key];\n        if (options) {\n          if (hasOwn(attrs, key)) {\n            if (value !== attrs[key]) {\n              attrs[key] = value;\n              hasAttrsChanged = true;\n            }\n          } else {\n            const camelizedKey = camelize(key);\n            props[camelizedKey] = resolvePropValue(\n              options,\n              rawCurrentProps,\n              camelizedKey,\n              value,\n              instance,\n              false\n            );\n          }\n        } else {\n          if (value !== attrs[key]) {\n            attrs[key] = value;\n            hasAttrsChanged = true;\n          }\n        }\n      }\n    }\n  } else {\n    if (setFullProps(instance, rawProps, props, attrs)) {\n      hasAttrsChanged = true;\n    }\n    let kebabKey;\n    for (const key in rawCurrentProps) {\n      if (!rawProps || // for camelCase\n      !hasOwn(rawProps, key) && // it's possible the original props was passed in as kebab-case\n      // and converted to camelCase (#955)\n      ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey))) {\n        if (options) {\n          if (rawPrevProps && // for camelCase\n          (rawPrevProps[key] !== void 0 || // for kebab-case\n          rawPrevProps[kebabKey] !== void 0)) {\n            props[key] = resolvePropValue(\n              options,\n              rawCurrentProps,\n              key,\n              void 0,\n              instance,\n              true\n            );\n          }\n        } else {\n          delete props[key];\n        }\n      }\n    }\n    if (attrs !== rawCurrentProps) {\n      for (const key in attrs) {\n        if (!rawProps || !hasOwn(rawProps, key) && true) {\n          delete attrs[key];\n          hasAttrsChanged = true;\n        }\n      }\n    }\n  }\n  if (hasAttrsChanged) {\n    trigger(instance.attrs, \"set\", \"\");\n  }\n  {\n    validateProps(rawProps || {}, props, instance);\n  }\n}\nfunction setFullProps(instance, rawProps, props, attrs) {\n  const [options, needCastKeys] = instance.propsOptions;\n  let hasAttrsChanged = false;\n  let rawCastValues;\n  if (rawProps) {\n    for (let key in rawProps) {\n      if (isReservedProp(key)) {\n        continue;\n      }\n      const value = rawProps[key];\n      let camelKey;\n      if (options && hasOwn(options, camelKey = camelize(key))) {\n        if (!needCastKeys || !needCastKeys.includes(camelKey)) {\n          props[camelKey] = value;\n        } else {\n          (rawCastValues || (rawCastValues = {}))[camelKey] = value;\n        }\n      } else if (!isEmitListener(instance.emitsOptions, key)) {\n        if (!(key in attrs) || value !== attrs[key]) {\n          attrs[key] = value;\n          hasAttrsChanged = true;\n        }\n      }\n    }\n  }\n  if (needCastKeys) {\n    const rawCurrentProps = toRaw(props);\n    const castValues = rawCastValues || EMPTY_OBJ;\n    for (let i = 0; i < needCastKeys.length; i++) {\n      const key = needCastKeys[i];\n      props[key] = resolvePropValue(\n        options,\n        rawCurrentProps,\n        key,\n        castValues[key],\n        instance,\n        !hasOwn(castValues, key)\n      );\n    }\n  }\n  return hasAttrsChanged;\n}\nfunction resolvePropValue(options, props, key, value, instance, isAbsent) {\n  const opt = options[key];\n  if (opt != null) {\n    const hasDefault = hasOwn(opt, \"default\");\n    if (hasDefault && value === void 0) {\n      const defaultValue = opt.default;\n      if (opt.type !== Function && !opt.skipFactory && isFunction(defaultValue)) {\n        const { propsDefaults } = instance;\n        if (key in propsDefaults) {\n          value = propsDefaults[key];\n        } else {\n          const reset = setCurrentInstance(instance);\n          value = propsDefaults[key] = defaultValue.call(\n            null,\n            props\n          );\n          reset();\n        }\n      } else {\n        value = defaultValue;\n      }\n    }\n    if (opt[0 /* shouldCast */]) {\n      if (isAbsent && !hasDefault) {\n        value = false;\n      } else if (opt[1 /* shouldCastTrue */] && (value === \"\" || value === hyphenate(key))) {\n        value = true;\n      }\n    }\n  }\n  return value;\n}\nconst mixinPropsCache = /* @__PURE__ */ new WeakMap();\nfunction normalizePropsOptions(comp, appContext, asMixin = false) {\n  const cache = asMixin ? mixinPropsCache : appContext.propsCache;\n  const cached = cache.get(comp);\n  if (cached) {\n    return cached;\n  }\n  const raw = comp.props;\n  const normalized = {};\n  const needCastKeys = [];\n  let hasExtends = false;\n  if (!isFunction(comp)) {\n    const extendProps = (raw2) => {\n      hasExtends = true;\n      const [props, keys] = normalizePropsOptions(raw2, appContext, true);\n      extend(normalized, props);\n      if (keys) needCastKeys.push(...keys);\n    };\n    if (!asMixin && appContext.mixins.length) {\n      appContext.mixins.forEach(extendProps);\n    }\n    if (comp.extends) {\n      extendProps(comp.extends);\n    }\n    if (comp.mixins) {\n      comp.mixins.forEach(extendProps);\n    }\n  }\n  if (!raw && !hasExtends) {\n    if (isObject(comp)) {\n      cache.set(comp, EMPTY_ARR);\n    }\n    return EMPTY_ARR;\n  }\n  if (isArray(raw)) {\n    for (let i = 0; i < raw.length; i++) {\n      if (!isString(raw[i])) {\n        warn$1(`props must be strings when using array syntax.`, raw[i]);\n      }\n      const normalizedKey = camelize(raw[i]);\n      if (validatePropName(normalizedKey)) {\n        normalized[normalizedKey] = EMPTY_OBJ;\n      }\n    }\n  } else if (raw) {\n    if (!isObject(raw)) {\n      warn$1(`invalid props options`, raw);\n    }\n    for (const key in raw) {\n      const normalizedKey = camelize(key);\n      if (validatePropName(normalizedKey)) {\n        const opt = raw[key];\n        const prop = normalized[normalizedKey] = isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt);\n        const propType = prop.type;\n        let shouldCast = false;\n        let shouldCastTrue = true;\n        if (isArray(propType)) {\n          for (let index = 0; index < propType.length; ++index) {\n            const type = propType[index];\n            const typeName = isFunction(type) && type.name;\n            if (typeName === \"Boolean\") {\n              shouldCast = true;\n              break;\n            } else if (typeName === \"String\") {\n              shouldCastTrue = false;\n            }\n          }\n        } else {\n          shouldCast = isFunction(propType) && propType.name === \"Boolean\";\n        }\n        prop[0 /* shouldCast */] = shouldCast;\n        prop[1 /* shouldCastTrue */] = shouldCastTrue;\n        if (shouldCast || hasOwn(prop, \"default\")) {\n          needCastKeys.push(normalizedKey);\n        }\n      }\n    }\n  }\n  const res = [normalized, needCastKeys];\n  if (isObject(comp)) {\n    cache.set(comp, res);\n  }\n  return res;\n}\nfunction validatePropName(key) {\n  if (key[0] !== \"$\" && !isReservedProp(key)) {\n    return true;\n  } else {\n    warn$1(`Invalid prop name: \"${key}\" is a reserved property.`);\n  }\n  return false;\n}\nfunction getType(ctor) {\n  if (ctor === null) {\n    return \"null\";\n  }\n  if (typeof ctor === \"function\") {\n    return ctor.name || \"\";\n  } else if (typeof ctor === \"object\") {\n    const name = ctor.constructor && ctor.constructor.name;\n    return name || \"\";\n  }\n  return \"\";\n}\nfunction validateProps(rawProps, props, instance) {\n  const resolvedValues = toRaw(props);\n  const options = instance.propsOptions[0];\n  for (const key in options) {\n    let opt = options[key];\n    if (opt == null) continue;\n    validateProp(\n      key,\n      resolvedValues[key],\n      opt,\n      shallowReadonly(resolvedValues) ,\n      !hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key))\n    );\n  }\n}\nfunction validateProp(name, value, prop, props, isAbsent) {\n  const { type, required, validator, skipCheck } = prop;\n  if (required && isAbsent) {\n    warn$1('Missing required prop: \"' + name + '\"');\n    return;\n  }\n  if (value == null && !required) {\n    return;\n  }\n  if (type != null && type !== true && !skipCheck) {\n    let isValid = false;\n    const types = isArray(type) ? type : [type];\n    const expectedTypes = [];\n    for (let i = 0; i < types.length && !isValid; i++) {\n      const { valid, expectedType } = assertType(value, types[i]);\n      expectedTypes.push(expectedType || \"\");\n      isValid = valid;\n    }\n    if (!isValid) {\n      warn$1(getInvalidTypeMessage(name, value, expectedTypes));\n      return;\n    }\n  }\n  if (validator && !validator(value, props)) {\n    warn$1('Invalid prop: custom validator check failed for prop \"' + name + '\".');\n  }\n}\nconst isSimpleType = /* @__PURE__ */ makeMap(\n  \"String,Number,Boolean,Function,Symbol,BigInt\"\n);\nfunction assertType(value, type) {\n  let valid;\n  const expectedType = getType(type);\n  if (isSimpleType(expectedType)) {\n    const t = typeof value;\n    valid = t === expectedType.toLowerCase();\n    if (!valid && t === \"object\") {\n      valid = value instanceof type;\n    }\n  } else if (expectedType === \"Object\") {\n    valid = isObject(value);\n  } else if (expectedType === \"Array\") {\n    valid = isArray(value);\n  } else if (expectedType === \"null\") {\n    valid = value === null;\n  } else {\n    valid = value instanceof type;\n  }\n  return {\n    valid,\n    expectedType\n  };\n}\nfunction getInvalidTypeMessage(name, value, expectedTypes) {\n  if (expectedTypes.length === 0) {\n    return `Prop type [] for prop \"${name}\" won't match anything. Did you mean to use type Array instead?`;\n  }\n  let message = `Invalid prop: type check failed for prop \"${name}\". Expected ${expectedTypes.map(capitalize).join(\" | \")}`;\n  const expectedType = expectedTypes[0];\n  const receivedType = toRawType(value);\n  const expectedValue = styleValue(value, expectedType);\n  const receivedValue = styleValue(value, receivedType);\n  if (expectedTypes.length === 1 && isExplicable(expectedType) && !isBoolean(expectedType, receivedType)) {\n    message += ` with value ${expectedValue}`;\n  }\n  message += `, got ${receivedType} `;\n  if (isExplicable(receivedType)) {\n    message += `with value ${receivedValue}.`;\n  }\n  return message;\n}\nfunction styleValue(value, type) {\n  if (type === \"String\") {\n    return `\"${value}\"`;\n  } else if (type === \"Number\") {\n    return `${Number(value)}`;\n  } else {\n    return `${value}`;\n  }\n}\nfunction isExplicable(type) {\n  const explicitTypes = [\"string\", \"number\", \"boolean\"];\n  return explicitTypes.some((elem) => type.toLowerCase() === elem);\n}\nfunction isBoolean(...args) {\n  return args.some((elem) => elem.toLowerCase() === \"boolean\");\n}\n\nconst isInternalKey = (key) => key[0] === \"_\" || key === \"$stable\";\nconst normalizeSlotValue = (value) => isArray(value) ? value.map(normalizeVNode) : [normalizeVNode(value)];\nconst normalizeSlot = (key, rawSlot, ctx) => {\n  if (rawSlot._n) {\n    return rawSlot;\n  }\n  const normalized = withCtx((...args) => {\n    if (currentInstance && (!ctx || ctx.root === currentInstance.root)) {\n      warn$1(\n        `Slot \"${key}\" invoked outside of the render function: this will not track dependencies used in the slot. Invoke the slot function inside the render function instead.`\n      );\n    }\n    return normalizeSlotValue(rawSlot(...args));\n  }, ctx);\n  normalized._c = false;\n  return normalized;\n};\nconst normalizeObjectSlots = (rawSlots, slots, instance) => {\n  const ctx = rawSlots._ctx;\n  for (const key in rawSlots) {\n    if (isInternalKey(key)) continue;\n    const value = rawSlots[key];\n    if (isFunction(value)) {\n      slots[key] = normalizeSlot(key, value, ctx);\n    } else if (value != null) {\n      {\n        warn$1(\n          `Non-function value encountered for slot \"${key}\". Prefer function slots for better performance.`\n        );\n      }\n      const normalized = normalizeSlotValue(value);\n      slots[key] = () => normalized;\n    }\n  }\n};\nconst normalizeVNodeSlots = (instance, children) => {\n  if (!isKeepAlive(instance.vnode) && true) {\n    warn$1(\n      `Non-function value encountered for default slot. Prefer function slots for better performance.`\n    );\n  }\n  const normalized = normalizeSlotValue(children);\n  instance.slots.default = () => normalized;\n};\nconst assignSlots = (slots, children, optimized) => {\n  for (const key in children) {\n    if (optimized || key !== \"_\") {\n      slots[key] = children[key];\n    }\n  }\n};\nconst initSlots = (instance, children, optimized) => {\n  const slots = instance.slots = createInternalObject();\n  if (instance.vnode.shapeFlag & 32) {\n    const type = children._;\n    if (type) {\n      assignSlots(slots, children, optimized);\n      if (optimized) {\n        def(slots, \"_\", type, true);\n      }\n    } else {\n      normalizeObjectSlots(children, slots);\n    }\n  } else if (children) {\n    normalizeVNodeSlots(instance, children);\n  }\n};\nconst updateSlots = (instance, children, optimized) => {\n  const { vnode, slots } = instance;\n  let needDeletionCheck = true;\n  let deletionComparisonTarget = EMPTY_OBJ;\n  if (vnode.shapeFlag & 32) {\n    const type = children._;\n    if (type) {\n      if (isHmrUpdating) {\n        assignSlots(slots, children, optimized);\n        trigger(instance, \"set\", \"$slots\");\n      } else if (optimized && type === 1) {\n        needDeletionCheck = false;\n      } else {\n        assignSlots(slots, children, optimized);\n      }\n    } else {\n      needDeletionCheck = !children.$stable;\n      normalizeObjectSlots(children, slots);\n    }\n    deletionComparisonTarget = children;\n  } else if (children) {\n    normalizeVNodeSlots(instance, children);\n    deletionComparisonTarget = { default: 1 };\n  }\n  if (needDeletionCheck) {\n    for (const key in slots) {\n      if (!isInternalKey(key) && deletionComparisonTarget[key] == null) {\n        delete slots[key];\n      }\n    }\n  }\n};\n\nfunction setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {\n  if (isArray(rawRef)) {\n    rawRef.forEach(\n      (r, i) => setRef(\n        r,\n        oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),\n        parentSuspense,\n        vnode,\n        isUnmount\n      )\n    );\n    return;\n  }\n  if (isAsyncWrapper(vnode) && !isUnmount) {\n    return;\n  }\n  const refValue = vnode.shapeFlag & 4 ? getComponentPublicInstance(vnode.component) : vnode.el;\n  const value = isUnmount ? null : refValue;\n  const { i: owner, r: ref } = rawRef;\n  if (!owner) {\n    warn$1(\n      `Missing ref owner context. ref cannot be used on hoisted vnodes. A vnode with ref must be created inside the render function.`\n    );\n    return;\n  }\n  const oldRef = oldRawRef && oldRawRef.r;\n  const refs = owner.refs === EMPTY_OBJ ? owner.refs = {} : owner.refs;\n  const setupState = owner.setupState;\n  if (oldRef != null && oldRef !== ref) {\n    if (isString(oldRef)) {\n      refs[oldRef] = null;\n      if (hasOwn(setupState, oldRef)) {\n        setupState[oldRef] = null;\n      }\n    } else if (isRef(oldRef)) {\n      oldRef.value = null;\n    }\n  }\n  if (isFunction(ref)) {\n    callWithErrorHandling(ref, owner, 12, [value, refs]);\n  } else {\n    const _isString = isString(ref);\n    const _isRef = isRef(ref);\n    if (_isString || _isRef) {\n      const doSet = () => {\n        if (rawRef.f) {\n          const existing = _isString ? hasOwn(setupState, ref) ? setupState[ref] : refs[ref] : ref.value;\n          if (isUnmount) {\n            isArray(existing) && remove(existing, refValue);\n          } else {\n            if (!isArray(existing)) {\n              if (_isString) {\n                refs[ref] = [refValue];\n                if (hasOwn(setupState, ref)) {\n                  setupState[ref] = refs[ref];\n                }\n              } else {\n                ref.value = [refValue];\n                if (rawRef.k) refs[rawRef.k] = ref.value;\n              }\n            } else if (!existing.includes(refValue)) {\n              existing.push(refValue);\n            }\n          }\n        } else if (_isString) {\n          refs[ref] = value;\n          if (hasOwn(setupState, ref)) {\n            setupState[ref] = value;\n          }\n        } else if (_isRef) {\n          ref.value = value;\n          if (rawRef.k) refs[rawRef.k] = value;\n        } else {\n          warn$1(\"Invalid template ref type:\", ref, `(${typeof ref})`);\n        }\n      };\n      if (value) {\n        doSet.id = -1;\n        queuePostRenderEffect(doSet, parentSuspense);\n      } else {\n        doSet();\n      }\n    } else {\n      warn$1(\"Invalid template ref type:\", ref, `(${typeof ref})`);\n    }\n  }\n}\n\nconst TeleportEndKey = Symbol(\"_vte\");\nconst isTeleport = (type) => type.__isTeleport;\nconst isTeleportDisabled = (props) => props && (props.disabled || props.disabled === \"\");\nconst isTargetSVG = (target) => typeof SVGElement !== \"undefined\" && target instanceof SVGElement;\nconst isTargetMathML = (target) => typeof MathMLElement === \"function\" && target instanceof MathMLElement;\nconst resolveTarget = (props, select) => {\n  const targetSelector = props && props.to;\n  if (isString(targetSelector)) {\n    if (!select) {\n      warn$1(\n        `Current renderer does not support string target for Teleports. (missing querySelector renderer option)`\n      );\n      return null;\n    } else {\n      const target = select(targetSelector);\n      if (!target && !isTeleportDisabled(props)) {\n        warn$1(\n          `Failed to locate Teleport target with selector \"${targetSelector}\". Note the target element must exist before the component is mounted - i.e. the target cannot be rendered by the component itself, and ideally should be outside of the entire Vue component tree.`\n        );\n      }\n      return target;\n    }\n  } else {\n    if (!targetSelector && !isTeleportDisabled(props)) {\n      warn$1(`Invalid Teleport target: ${targetSelector}`);\n    }\n    return targetSelector;\n  }\n};\nconst TeleportImpl = {\n  name: \"Teleport\",\n  __isTeleport: true,\n  process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals) {\n    const {\n      mc: mountChildren,\n      pc: patchChildren,\n      pbc: patchBlockChildren,\n      o: { insert, querySelector, createText, createComment }\n    } = internals;\n    const disabled = isTeleportDisabled(n2.props);\n    let { shapeFlag, children, dynamicChildren } = n2;\n    if (isHmrUpdating) {\n      optimized = false;\n      dynamicChildren = null;\n    }\n    if (n1 == null) {\n      const placeholder = n2.el = createComment(\"teleport start\") ;\n      const mainAnchor = n2.anchor = createComment(\"teleport end\") ;\n      insert(placeholder, container, anchor);\n      insert(mainAnchor, container, anchor);\n      const target = n2.target = resolveTarget(n2.props, querySelector);\n      const targetAnchor = prepareAnchor(target, n2, createText, insert);\n      if (target) {\n        if (namespace === \"svg\" || isTargetSVG(target)) {\n          namespace = \"svg\";\n        } else if (namespace === \"mathml\" || isTargetMathML(target)) {\n          namespace = \"mathml\";\n        }\n      } else if (!disabled) {\n        warn$1(\"Invalid Teleport target on mount:\", target, `(${typeof target})`);\n      }\n      const mount = (container2, anchor2) => {\n        if (shapeFlag & 16) {\n          mountChildren(\n            children,\n            container2,\n            anchor2,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n        }\n      };\n      if (disabled) {\n        mount(container, mainAnchor);\n      } else if (target) {\n        mount(target, targetAnchor);\n      }\n    } else {\n      n2.el = n1.el;\n      n2.targetStart = n1.targetStart;\n      const mainAnchor = n2.anchor = n1.anchor;\n      const target = n2.target = n1.target;\n      const targetAnchor = n2.targetAnchor = n1.targetAnchor;\n      const wasDisabled = isTeleportDisabled(n1.props);\n      const currentContainer = wasDisabled ? container : target;\n      const currentAnchor = wasDisabled ? mainAnchor : targetAnchor;\n      if (namespace === \"svg\" || isTargetSVG(target)) {\n        namespace = \"svg\";\n      } else if (namespace === \"mathml\" || isTargetMathML(target)) {\n        namespace = \"mathml\";\n      }\n      if (dynamicChildren) {\n        patchBlockChildren(\n          n1.dynamicChildren,\n          dynamicChildren,\n          currentContainer,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds\n        );\n        traverseStaticChildren(n1, n2, true);\n      } else if (!optimized) {\n        patchChildren(\n          n1,\n          n2,\n          currentContainer,\n          currentAnchor,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds,\n          false\n        );\n      }\n      if (disabled) {\n        if (!wasDisabled) {\n          moveTeleport(\n            n2,\n            container,\n            mainAnchor,\n            internals,\n            1\n          );\n        } else {\n          if (n2.props && n1.props && n2.props.to !== n1.props.to) {\n            n2.props.to = n1.props.to;\n          }\n        }\n      } else {\n        if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {\n          const nextTarget = n2.target = resolveTarget(\n            n2.props,\n            querySelector\n          );\n          if (nextTarget) {\n            moveTeleport(\n              n2,\n              nextTarget,\n              null,\n              internals,\n              0\n            );\n          } else {\n            warn$1(\n              \"Invalid Teleport target on update:\",\n              target,\n              `(${typeof target})`\n            );\n          }\n        } else if (wasDisabled) {\n          moveTeleport(\n            n2,\n            target,\n            targetAnchor,\n            internals,\n            1\n          );\n        }\n      }\n    }\n    updateCssVars(n2);\n  },\n  remove(vnode, parentComponent, parentSuspense, { um: unmount, o: { remove: hostRemove } }, doRemove) {\n    const {\n      shapeFlag,\n      children,\n      anchor,\n      targetStart,\n      targetAnchor,\n      target,\n      props\n    } = vnode;\n    if (target) {\n      hostRemove(targetStart);\n      hostRemove(targetAnchor);\n    }\n    doRemove && hostRemove(anchor);\n    if (shapeFlag & 16) {\n      const shouldRemove = doRemove || !isTeleportDisabled(props);\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i];\n        unmount(\n          child,\n          parentComponent,\n          parentSuspense,\n          shouldRemove,\n          !!child.dynamicChildren\n        );\n      }\n    }\n  },\n  move: moveTeleport,\n  hydrate: hydrateTeleport\n};\nfunction moveTeleport(vnode, container, parentAnchor, { o: { insert }, m: move }, moveType = 2) {\n  if (moveType === 0) {\n    insert(vnode.targetAnchor, container, parentAnchor);\n  }\n  const { el, anchor, shapeFlag, children, props } = vnode;\n  const isReorder = moveType === 2;\n  if (isReorder) {\n    insert(el, container, parentAnchor);\n  }\n  if (!isReorder || isTeleportDisabled(props)) {\n    if (shapeFlag & 16) {\n      for (let i = 0; i < children.length; i++) {\n        move(\n          children[i],\n          container,\n          parentAnchor,\n          2\n        );\n      }\n    }\n  }\n  if (isReorder) {\n    insert(anchor, container, parentAnchor);\n  }\n}\nfunction hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized, {\n  o: { nextSibling, parentNode, querySelector, insert, createText }\n}, hydrateChildren) {\n  const target = vnode.target = resolveTarget(\n    vnode.props,\n    querySelector\n  );\n  if (target) {\n    const targetNode = target._lpa || target.firstChild;\n    if (vnode.shapeFlag & 16) {\n      if (isTeleportDisabled(vnode.props)) {\n        vnode.anchor = hydrateChildren(\n          nextSibling(node),\n          vnode,\n          parentNode(node),\n          parentComponent,\n          parentSuspense,\n          slotScopeIds,\n          optimized\n        );\n        vnode.targetStart = targetNode;\n        vnode.targetAnchor = targetNode && nextSibling(targetNode);\n      } else {\n        vnode.anchor = nextSibling(node);\n        let targetAnchor = targetNode;\n        while (targetAnchor) {\n          if (targetAnchor && targetAnchor.nodeType === 8) {\n            if (targetAnchor.data === \"teleport start anchor\") {\n              vnode.targetStart = targetAnchor;\n            } else if (targetAnchor.data === \"teleport anchor\") {\n              vnode.targetAnchor = targetAnchor;\n              target._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor);\n              break;\n            }\n          }\n          targetAnchor = nextSibling(targetAnchor);\n        }\n        if (!vnode.targetAnchor) {\n          prepareAnchor(target, vnode, createText, insert);\n        }\n        hydrateChildren(\n          targetNode && nextSibling(targetNode),\n          vnode,\n          target,\n          parentComponent,\n          parentSuspense,\n          slotScopeIds,\n          optimized\n        );\n      }\n    }\n    updateCssVars(vnode);\n  }\n  return vnode.anchor && nextSibling(vnode.anchor);\n}\nconst Teleport = TeleportImpl;\nfunction updateCssVars(vnode) {\n  const ctx = vnode.ctx;\n  if (ctx && ctx.ut) {\n    let node = vnode.children[0].el;\n    while (node && node !== vnode.targetAnchor) {\n      if (node.nodeType === 1) node.setAttribute(\"data-v-owner\", ctx.uid);\n      node = node.nextSibling;\n    }\n    ctx.ut();\n  }\n}\nfunction prepareAnchor(target, vnode, createText, insert) {\n  const targetStart = vnode.targetStart = createText(\"\");\n  const targetAnchor = vnode.targetAnchor = createText(\"\");\n  targetStart[TeleportEndKey] = targetAnchor;\n  if (target) {\n    insert(targetStart, target);\n    insert(targetAnchor, target);\n  }\n  return targetAnchor;\n}\n\nlet hasLoggedMismatchError = false;\nconst logMismatchError = () => {\n  if (hasLoggedMismatchError) {\n    return;\n  }\n  console.error(\"Hydration completed but contains mismatches.\");\n  hasLoggedMismatchError = true;\n};\nconst isSVGContainer = (container) => container.namespaceURI.includes(\"svg\") && container.tagName !== \"foreignObject\";\nconst isMathMLContainer = (container) => container.namespaceURI.includes(\"MathML\");\nconst getContainerType = (container) => {\n  if (isSVGContainer(container)) return \"svg\";\n  if (isMathMLContainer(container)) return \"mathml\";\n  return void 0;\n};\nconst isComment = (node) => node.nodeType === 8 /* COMMENT */;\nfunction createHydrationFunctions(rendererInternals) {\n  const {\n    mt: mountComponent,\n    p: patch,\n    o: {\n      patchProp,\n      createText,\n      nextSibling,\n      parentNode,\n      remove,\n      insert,\n      createComment\n    }\n  } = rendererInternals;\n  const hydrate = (vnode, container) => {\n    if (!container.hasChildNodes()) {\n      warn$1(\n        `Attempting to hydrate existing markup but container is empty. Performing full mount instead.`\n      );\n      patch(null, vnode, container);\n      flushPostFlushCbs();\n      container._vnode = vnode;\n      return;\n    }\n    hydrateNode(container.firstChild, vnode, null, null, null);\n    flushPostFlushCbs();\n    container._vnode = vnode;\n  };\n  const hydrateNode = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized = false) => {\n    optimized = optimized || !!vnode.dynamicChildren;\n    const isFragmentStart = isComment(node) && node.data === \"[\";\n    const onMismatch = () => handleMismatch(\n      node,\n      vnode,\n      parentComponent,\n      parentSuspense,\n      slotScopeIds,\n      isFragmentStart\n    );\n    const { type, ref, shapeFlag, patchFlag } = vnode;\n    let domType = node.nodeType;\n    vnode.el = node;\n    {\n      def(node, \"__vnode\", vnode, true);\n      def(node, \"__vueParentComponent\", parentComponent, true);\n    }\n    if (patchFlag === -2) {\n      optimized = false;\n      vnode.dynamicChildren = null;\n    }\n    let nextNode = null;\n    switch (type) {\n      case Text:\n        if (domType !== 3 /* TEXT */) {\n          if (vnode.children === \"\") {\n            insert(vnode.el = createText(\"\"), parentNode(node), node);\n            nextNode = node;\n          } else {\n            nextNode = onMismatch();\n          }\n        } else {\n          if (node.data !== vnode.children) {\n            warn$1(\n              `Hydration text mismatch in`,\n              node.parentNode,\n              `\n  - rendered on server: ${JSON.stringify(\n                node.data\n              )}\n  - expected on client: ${JSON.stringify(vnode.children)}`\n            );\n            logMismatchError();\n            node.data = vnode.children;\n          }\n          nextNode = nextSibling(node);\n        }\n        break;\n      case Comment:\n        if (isTemplateNode(node)) {\n          nextNode = nextSibling(node);\n          replaceNode(\n            vnode.el = node.content.firstChild,\n            node,\n            parentComponent\n          );\n        } else if (domType !== 8 /* COMMENT */ || isFragmentStart) {\n          nextNode = onMismatch();\n        } else {\n          nextNode = nextSibling(node);\n        }\n        break;\n      case Static:\n        if (isFragmentStart) {\n          node = nextSibling(node);\n          domType = node.nodeType;\n        }\n        if (domType === 1 /* ELEMENT */ || domType === 3 /* TEXT */) {\n          nextNode = node;\n          const needToAdoptContent = !vnode.children.length;\n          for (let i = 0; i < vnode.staticCount; i++) {\n            if (needToAdoptContent)\n              vnode.children += nextNode.nodeType === 1 /* ELEMENT */ ? nextNode.outerHTML : nextNode.data;\n            if (i === vnode.staticCount - 1) {\n              vnode.anchor = nextNode;\n            }\n            nextNode = nextSibling(nextNode);\n          }\n          return isFragmentStart ? nextSibling(nextNode) : nextNode;\n        } else {\n          onMismatch();\n        }\n        break;\n      case Fragment:\n        if (!isFragmentStart) {\n          nextNode = onMismatch();\n        } else {\n          nextNode = hydrateFragment(\n            node,\n            vnode,\n            parentComponent,\n            parentSuspense,\n            slotScopeIds,\n            optimized\n          );\n        }\n        break;\n      default:\n        if (shapeFlag & 1) {\n          if ((domType !== 1 /* ELEMENT */ || vnode.type.toLowerCase() !== node.tagName.toLowerCase()) && !isTemplateNode(node)) {\n            nextNode = onMismatch();\n          } else {\n            nextNode = hydrateElement(\n              node,\n              vnode,\n              parentComponent,\n              parentSuspense,\n              slotScopeIds,\n              optimized\n            );\n          }\n        } else if (shapeFlag & 6) {\n          vnode.slotScopeIds = slotScopeIds;\n          const container = parentNode(node);\n          if (isFragmentStart) {\n            nextNode = locateClosingAnchor(node);\n          } else if (isComment(node) && node.data === \"teleport start\") {\n            nextNode = locateClosingAnchor(node, node.data, \"teleport end\");\n          } else {\n            nextNode = nextSibling(node);\n          }\n          mountComponent(\n            vnode,\n            container,\n            null,\n            parentComponent,\n            parentSuspense,\n            getContainerType(container),\n            optimized\n          );\n          if (isAsyncWrapper(vnode)) {\n            let subTree;\n            if (isFragmentStart) {\n              subTree = createVNode(Fragment);\n              subTree.anchor = nextNode ? nextNode.previousSibling : container.lastChild;\n            } else {\n              subTree = node.nodeType === 3 ? createTextVNode(\"\") : createVNode(\"div\");\n            }\n            subTree.el = node;\n            vnode.component.subTree = subTree;\n          }\n        } else if (shapeFlag & 64) {\n          if (domType !== 8 /* COMMENT */) {\n            nextNode = onMismatch();\n          } else {\n            nextNode = vnode.type.hydrate(\n              node,\n              vnode,\n              parentComponent,\n              parentSuspense,\n              slotScopeIds,\n              optimized,\n              rendererInternals,\n              hydrateChildren\n            );\n          }\n        } else if (shapeFlag & 128) {\n          nextNode = vnode.type.hydrate(\n            node,\n            vnode,\n            parentComponent,\n            parentSuspense,\n            getContainerType(parentNode(node)),\n            slotScopeIds,\n            optimized,\n            rendererInternals,\n            hydrateNode\n          );\n        } else {\n          warn$1(\"Invalid HostVNode type:\", type, `(${typeof type})`);\n        }\n    }\n    if (ref != null) {\n      setRef(ref, null, parentSuspense, vnode);\n    }\n    return nextNode;\n  };\n  const hydrateElement = (el, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => {\n    optimized = optimized || !!vnode.dynamicChildren;\n    const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode;\n    const forcePatch = type === \"input\" || type === \"option\";\n    {\n      if (dirs) {\n        invokeDirectiveHook(vnode, null, parentComponent, \"created\");\n      }\n      let needCallTransitionHooks = false;\n      if (isTemplateNode(el)) {\n        needCallTransitionHooks = needTransition(parentSuspense, transition) && parentComponent && parentComponent.vnode.props && parentComponent.vnode.props.appear;\n        const content = el.content.firstChild;\n        if (needCallTransitionHooks) {\n          transition.beforeEnter(content);\n        }\n        replaceNode(content, el, parentComponent);\n        vnode.el = el = content;\n      }\n      if (shapeFlag & 16 && // skip if element has innerHTML / textContent\n      !(props && (props.innerHTML || props.textContent))) {\n        let next = hydrateChildren(\n          el.firstChild,\n          vnode,\n          el,\n          parentComponent,\n          parentSuspense,\n          slotScopeIds,\n          optimized\n        );\n        let hasWarned = false;\n        while (next) {\n          if (!hasWarned) {\n            warn$1(\n              `Hydration children mismatch on`,\n              el,\n              `\nServer rendered element contains more child nodes than client vdom.`\n            );\n            hasWarned = true;\n          }\n          logMismatchError();\n          const cur = next;\n          next = next.nextSibling;\n          remove(cur);\n        }\n      } else if (shapeFlag & 8) {\n        if (el.textContent !== vnode.children) {\n          warn$1(\n            `Hydration text content mismatch on`,\n            el,\n            `\n  - rendered on server: ${el.textContent}\n  - expected on client: ${vnode.children}`\n          );\n          logMismatchError();\n          el.textContent = vnode.children;\n        }\n      }\n      if (props) {\n        {\n          for (const key in props) {\n            if (// #11189 skip if this node has directives that have created hooks\n            // as it could have mutated the DOM in any possible way\n            !(dirs && dirs.some((d) => d.dir.created)) && propHasMismatch(el, key, props[key], vnode, parentComponent)) {\n              logMismatchError();\n            }\n            if (forcePatch && (key.endsWith(\"value\") || key === \"indeterminate\") || isOn(key) && !isReservedProp(key) || // force hydrate v-bind with .prop modifiers\n            key[0] === \".\") {\n              patchProp(el, key, null, props[key], void 0, parentComponent);\n            }\n          }\n        }\n      }\n      let vnodeHooks;\n      if (vnodeHooks = props && props.onVnodeBeforeMount) {\n        invokeVNodeHook(vnodeHooks, parentComponent, vnode);\n      }\n      if (dirs) {\n        invokeDirectiveHook(vnode, null, parentComponent, \"beforeMount\");\n      }\n      if ((vnodeHooks = props && props.onVnodeMounted) || dirs || needCallTransitionHooks) {\n        queueEffectWithSuspense(() => {\n          vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode);\n          needCallTransitionHooks && transition.enter(el);\n          dirs && invokeDirectiveHook(vnode, null, parentComponent, \"mounted\");\n        }, parentSuspense);\n      }\n    }\n    return el.nextSibling;\n  };\n  const hydrateChildren = (node, parentVNode, container, parentComponent, parentSuspense, slotScopeIds, optimized) => {\n    optimized = optimized || !!parentVNode.dynamicChildren;\n    const children = parentVNode.children;\n    const l = children.length;\n    let hasWarned = false;\n    for (let i = 0; i < l; i++) {\n      const vnode = optimized ? children[i] : children[i] = normalizeVNode(children[i]);\n      const isText = vnode.type === Text;\n      if (node) {\n        if (isText && !optimized) {\n          let next = children[i + 1];\n          if (next && (next = normalizeVNode(next)).type === Text) {\n            insert(\n              createText(\n                node.data.slice(vnode.children.length)\n              ),\n              container,\n              nextSibling(node)\n            );\n            node.data = vnode.children;\n          }\n        }\n        node = hydrateNode(\n          node,\n          vnode,\n          parentComponent,\n          parentSuspense,\n          slotScopeIds,\n          optimized\n        );\n      } else if (isText && !vnode.children) {\n        insert(vnode.el = createText(\"\"), container);\n      } else {\n        if (!hasWarned) {\n          warn$1(\n            `Hydration children mismatch on`,\n            container,\n            `\nServer rendered element contains fewer child nodes than client vdom.`\n          );\n          hasWarned = true;\n        }\n        logMismatchError();\n        patch(\n          null,\n          vnode,\n          container,\n          null,\n          parentComponent,\n          parentSuspense,\n          getContainerType(container),\n          slotScopeIds\n        );\n      }\n    }\n    return node;\n  };\n  const hydrateFragment = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => {\n    const { slotScopeIds: fragmentSlotScopeIds } = vnode;\n    if (fragmentSlotScopeIds) {\n      slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds;\n    }\n    const container = parentNode(node);\n    const next = hydrateChildren(\n      nextSibling(node),\n      vnode,\n      container,\n      parentComponent,\n      parentSuspense,\n      slotScopeIds,\n      optimized\n    );\n    if (next && isComment(next) && next.data === \"]\") {\n      return nextSibling(vnode.anchor = next);\n    } else {\n      logMismatchError();\n      insert(vnode.anchor = createComment(`]`), container, next);\n      return next;\n    }\n  };\n  const handleMismatch = (node, vnode, parentComponent, parentSuspense, slotScopeIds, isFragment) => {\n    warn$1(\n      `Hydration node mismatch:\n- rendered on server:`,\n      node,\n      node.nodeType === 3 /* TEXT */ ? `(text)` : isComment(node) && node.data === \"[\" ? `(start of fragment)` : ``,\n      `\n- expected on client:`,\n      vnode.type\n    );\n    logMismatchError();\n    vnode.el = null;\n    if (isFragment) {\n      const end = locateClosingAnchor(node);\n      while (true) {\n        const next2 = nextSibling(node);\n        if (next2 && next2 !== end) {\n          remove(next2);\n        } else {\n          break;\n        }\n      }\n    }\n    const next = nextSibling(node);\n    const container = parentNode(node);\n    remove(node);\n    patch(\n      null,\n      vnode,\n      container,\n      next,\n      parentComponent,\n      parentSuspense,\n      getContainerType(container),\n      slotScopeIds\n    );\n    return next;\n  };\n  const locateClosingAnchor = (node, open = \"[\", close = \"]\") => {\n    let match = 0;\n    while (node) {\n      node = nextSibling(node);\n      if (node && isComment(node)) {\n        if (node.data === open) match++;\n        if (node.data === close) {\n          if (match === 0) {\n            return nextSibling(node);\n          } else {\n            match--;\n          }\n        }\n      }\n    }\n    return node;\n  };\n  const replaceNode = (newNode, oldNode, parentComponent) => {\n    const parentNode2 = oldNode.parentNode;\n    if (parentNode2) {\n      parentNode2.replaceChild(newNode, oldNode);\n    }\n    let parent = parentComponent;\n    while (parent) {\n      if (parent.vnode.el === oldNode) {\n        parent.vnode.el = parent.subTree.el = newNode;\n      }\n      parent = parent.parent;\n    }\n  };\n  const isTemplateNode = (node) => {\n    return node.nodeType === 1 /* ELEMENT */ && node.tagName.toLowerCase() === \"template\";\n  };\n  return [hydrate, hydrateNode];\n}\nfunction propHasMismatch(el, key, clientValue, vnode, instance) {\n  let mismatchType;\n  let mismatchKey;\n  let actual;\n  let expected;\n  if (key === \"class\") {\n    actual = el.getAttribute(\"class\");\n    expected = normalizeClass(clientValue);\n    if (!isSetEqual(toClassSet(actual || \"\"), toClassSet(expected))) {\n      mismatchType = mismatchKey = `class`;\n    }\n  } else if (key === \"style\") {\n    actual = el.getAttribute(\"style\") || \"\";\n    expected = isString(clientValue) ? clientValue : stringifyStyle(normalizeStyle(clientValue));\n    const actualMap = toStyleMap(actual);\n    const expectedMap = toStyleMap(expected);\n    if (vnode.dirs) {\n      for (const { dir, value } of vnode.dirs) {\n        if (dir.name === \"show\" && !value) {\n          expectedMap.set(\"display\", \"none\");\n        }\n      }\n    }\n    if (instance) {\n      resolveCssVars(instance, vnode, expectedMap);\n    }\n    if (!isMapEqual(actualMap, expectedMap)) {\n      mismatchType = mismatchKey = \"style\";\n    }\n  } else if (el instanceof SVGElement && isKnownSvgAttr(key) || el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key))) {\n    if (isBooleanAttr(key)) {\n      actual = el.hasAttribute(key);\n      expected = includeBooleanAttr(clientValue);\n    } else if (clientValue == null) {\n      actual = el.hasAttribute(key);\n      expected = false;\n    } else {\n      if (el.hasAttribute(key)) {\n        actual = el.getAttribute(key);\n      } else if (key === \"value\" && el.tagName === \"TEXTAREA\") {\n        actual = el.value;\n      } else {\n        actual = false;\n      }\n      expected = isRenderableAttrValue(clientValue) ? String(clientValue) : false;\n    }\n    if (actual !== expected) {\n      mismatchType = `attribute`;\n      mismatchKey = key;\n    }\n  }\n  if (mismatchType) {\n    const format = (v) => v === false ? `(not rendered)` : `${mismatchKey}=\"${v}\"`;\n    const preSegment = `Hydration ${mismatchType} mismatch on`;\n    const postSegment = `\n  - rendered on server: ${format(actual)}\n  - expected on client: ${format(expected)}\n  Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead.\n  You should fix the source of the mismatch.`;\n    {\n      warn$1(preSegment, el, postSegment);\n    }\n    return true;\n  }\n  return false;\n}\nfunction toClassSet(str) {\n  return new Set(str.trim().split(/\\s+/));\n}\nfunction isSetEqual(a, b) {\n  if (a.size !== b.size) {\n    return false;\n  }\n  for (const s of a) {\n    if (!b.has(s)) {\n      return false;\n    }\n  }\n  return true;\n}\nfunction toStyleMap(str) {\n  const styleMap = /* @__PURE__ */ new Map();\n  for (const item of str.split(\";\")) {\n    let [key, value] = item.split(\":\");\n    key = key.trim();\n    value = value && value.trim();\n    if (key && value) {\n      styleMap.set(key, value);\n    }\n  }\n  return styleMap;\n}\nfunction isMapEqual(a, b) {\n  if (a.size !== b.size) {\n    return false;\n  }\n  for (const [key, value] of a) {\n    if (value !== b.get(key)) {\n      return false;\n    }\n  }\n  return true;\n}\nfunction resolveCssVars(instance, vnode, expectedMap) {\n  const root = instance.subTree;\n  if (instance.getCssVars && (vnode === root || root && root.type === Fragment && root.children.includes(vnode))) {\n    const cssVars = instance.getCssVars();\n    for (const key in cssVars) {\n      expectedMap.set(`--${key}`, String(cssVars[key]));\n    }\n  }\n  if (vnode === root && instance.parent) {\n    resolveCssVars(instance.parent, instance.vnode, expectedMap);\n  }\n}\n\nlet supported;\nlet perf;\nfunction startMeasure(instance, type) {\n  if (instance.appContext.config.performance && isSupported()) {\n    perf.mark(`vue-${type}-${instance.uid}`);\n  }\n  {\n    devtoolsPerfStart(instance, type, isSupported() ? perf.now() : Date.now());\n  }\n}\nfunction endMeasure(instance, type) {\n  if (instance.appContext.config.performance && isSupported()) {\n    const startTag = `vue-${type}-${instance.uid}`;\n    const endTag = startTag + `:end`;\n    perf.mark(endTag);\n    perf.measure(\n      `<${formatComponentName(instance, instance.type)}> ${type}`,\n      startTag,\n      endTag\n    );\n    perf.clearMarks(startTag);\n    perf.clearMarks(endTag);\n  }\n  {\n    devtoolsPerfEnd(instance, type, isSupported() ? perf.now() : Date.now());\n  }\n}\nfunction isSupported() {\n  if (supported !== void 0) {\n    return supported;\n  }\n  if (typeof window !== \"undefined\" && window.performance) {\n    supported = true;\n    perf = window.performance;\n  } else {\n    supported = false;\n  }\n  return supported;\n}\n\nconst queuePostRenderEffect = queueEffectWithSuspense ;\nfunction createRenderer(options) {\n  return baseCreateRenderer(options);\n}\nfunction createHydrationRenderer(options) {\n  return baseCreateRenderer(options, createHydrationFunctions);\n}\nfunction baseCreateRenderer(options, createHydrationFns) {\n  const target = getGlobalThis();\n  target.__VUE__ = true;\n  {\n    setDevtoolsHook$1(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target);\n  }\n  const {\n    insert: hostInsert,\n    remove: hostRemove,\n    patchProp: hostPatchProp,\n    createElement: hostCreateElement,\n    createText: hostCreateText,\n    createComment: hostCreateComment,\n    setText: hostSetText,\n    setElementText: hostSetElementText,\n    parentNode: hostParentNode,\n    nextSibling: hostNextSibling,\n    setScopeId: hostSetScopeId = NOOP,\n    insertStaticContent: hostInsertStaticContent\n  } = options;\n  const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, namespace = void 0, slotScopeIds = null, optimized = isHmrUpdating ? false : !!n2.dynamicChildren) => {\n    if (n1 === n2) {\n      return;\n    }\n    if (n1 && !isSameVNodeType(n1, n2)) {\n      anchor = getNextHostNode(n1);\n      unmount(n1, parentComponent, parentSuspense, true);\n      n1 = null;\n    }\n    if (n2.patchFlag === -2) {\n      optimized = false;\n      n2.dynamicChildren = null;\n    }\n    const { type, ref, shapeFlag } = n2;\n    switch (type) {\n      case Text:\n        processText(n1, n2, container, anchor);\n        break;\n      case Comment:\n        processCommentNode(n1, n2, container, anchor);\n        break;\n      case Static:\n        if (n1 == null) {\n          mountStaticNode(n2, container, anchor, namespace);\n        } else {\n          patchStaticNode(n1, n2, container, namespace);\n        }\n        break;\n      case Fragment:\n        processFragment(\n          n1,\n          n2,\n          container,\n          anchor,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n        break;\n      default:\n        if (shapeFlag & 1) {\n          processElement(\n            n1,\n            n2,\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n        } else if (shapeFlag & 6) {\n          processComponent(\n            n1,\n            n2,\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n        } else if (shapeFlag & 64) {\n          type.process(\n            n1,\n            n2,\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized,\n            internals\n          );\n        } else if (shapeFlag & 128) {\n          type.process(\n            n1,\n            n2,\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized,\n            internals\n          );\n        } else {\n          warn$1(\"Invalid VNode type:\", type, `(${typeof type})`);\n        }\n    }\n    if (ref != null && parentComponent) {\n      setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2);\n    }\n  };\n  const processText = (n1, n2, container, anchor) => {\n    if (n1 == null) {\n      hostInsert(\n        n2.el = hostCreateText(n2.children),\n        container,\n        anchor\n      );\n    } else {\n      const el = n2.el = n1.el;\n      if (n2.children !== n1.children) {\n        hostSetText(el, n2.children);\n      }\n    }\n  };\n  const processCommentNode = (n1, n2, container, anchor) => {\n    if (n1 == null) {\n      hostInsert(\n        n2.el = hostCreateComment(n2.children || \"\"),\n        container,\n        anchor\n      );\n    } else {\n      n2.el = n1.el;\n    }\n  };\n  const mountStaticNode = (n2, container, anchor, namespace) => {\n    [n2.el, n2.anchor] = hostInsertStaticContent(\n      n2.children,\n      container,\n      anchor,\n      namespace,\n      n2.el,\n      n2.anchor\n    );\n  };\n  const patchStaticNode = (n1, n2, container, namespace) => {\n    if (n2.children !== n1.children) {\n      const anchor = hostNextSibling(n1.anchor);\n      removeStaticNode(n1);\n      [n2.el, n2.anchor] = hostInsertStaticContent(\n        n2.children,\n        container,\n        anchor,\n        namespace\n      );\n    } else {\n      n2.el = n1.el;\n      n2.anchor = n1.anchor;\n    }\n  };\n  const moveStaticNode = ({ el, anchor }, container, nextSibling) => {\n    let next;\n    while (el && el !== anchor) {\n      next = hostNextSibling(el);\n      hostInsert(el, container, nextSibling);\n      el = next;\n    }\n    hostInsert(anchor, container, nextSibling);\n  };\n  const removeStaticNode = ({ el, anchor }) => {\n    let next;\n    while (el && el !== anchor) {\n      next = hostNextSibling(el);\n      hostRemove(el);\n      el = next;\n    }\n    hostRemove(anchor);\n  };\n  const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {\n    if (n2.type === \"svg\") {\n      namespace = \"svg\";\n    } else if (n2.type === \"math\") {\n      namespace = \"mathml\";\n    }\n    if (n1 == null) {\n      mountElement(\n        n2,\n        container,\n        anchor,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n    } else {\n      patchElement(\n        n1,\n        n2,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n    }\n  };\n  const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {\n    let el;\n    let vnodeHook;\n    const { props, shapeFlag, transition, dirs } = vnode;\n    el = vnode.el = hostCreateElement(\n      vnode.type,\n      namespace,\n      props && props.is,\n      props\n    );\n    if (shapeFlag & 8) {\n      hostSetElementText(el, vnode.children);\n    } else if (shapeFlag & 16) {\n      mountChildren(\n        vnode.children,\n        el,\n        null,\n        parentComponent,\n        parentSuspense,\n        resolveChildrenNamespace(vnode, namespace),\n        slotScopeIds,\n        optimized\n      );\n    }\n    if (dirs) {\n      invokeDirectiveHook(vnode, null, parentComponent, \"created\");\n    }\n    setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent);\n    if (props) {\n      for (const key in props) {\n        if (key !== \"value\" && !isReservedProp(key)) {\n          hostPatchProp(el, key, null, props[key], namespace, parentComponent);\n        }\n      }\n      if (\"value\" in props) {\n        hostPatchProp(el, \"value\", null, props.value, namespace);\n      }\n      if (vnodeHook = props.onVnodeBeforeMount) {\n        invokeVNodeHook(vnodeHook, parentComponent, vnode);\n      }\n    }\n    {\n      def(el, \"__vnode\", vnode, true);\n      def(el, \"__vueParentComponent\", parentComponent, true);\n    }\n    if (dirs) {\n      invokeDirectiveHook(vnode, null, parentComponent, \"beforeMount\");\n    }\n    const needCallTransitionHooks = needTransition(parentSuspense, transition);\n    if (needCallTransitionHooks) {\n      transition.beforeEnter(el);\n    }\n    hostInsert(el, container, anchor);\n    if ((vnodeHook = props && props.onVnodeMounted) || needCallTransitionHooks || dirs) {\n      queuePostRenderEffect(() => {\n        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);\n        needCallTransitionHooks && transition.enter(el);\n        dirs && invokeDirectiveHook(vnode, null, parentComponent, \"mounted\");\n      }, parentSuspense);\n    }\n  };\n  const setScopeId = (el, vnode, scopeId, slotScopeIds, parentComponent) => {\n    if (scopeId) {\n      hostSetScopeId(el, scopeId);\n    }\n    if (slotScopeIds) {\n      for (let i = 0; i < slotScopeIds.length; i++) {\n        hostSetScopeId(el, slotScopeIds[i]);\n      }\n    }\n    if (parentComponent) {\n      let subTree = parentComponent.subTree;\n      if (subTree.patchFlag > 0 && subTree.patchFlag & 2048) {\n        subTree = filterSingleRoot(subTree.children) || subTree;\n      }\n      if (vnode === subTree) {\n        const parentVNode = parentComponent.vnode;\n        setScopeId(\n          el,\n          parentVNode,\n          parentVNode.scopeId,\n          parentVNode.slotScopeIds,\n          parentComponent.parent\n        );\n      }\n    }\n  };\n  const mountChildren = (children, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, start = 0) => {\n    for (let i = start; i < children.length; i++) {\n      const child = children[i] = optimized ? cloneIfMounted(children[i]) : normalizeVNode(children[i]);\n      patch(\n        null,\n        child,\n        container,\n        anchor,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n    }\n  };\n  const patchElement = (n1, n2, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {\n    const el = n2.el = n1.el;\n    {\n      el.__vnode = n2;\n    }\n    let { patchFlag, dynamicChildren, dirs } = n2;\n    patchFlag |= n1.patchFlag & 16;\n    const oldProps = n1.props || EMPTY_OBJ;\n    const newProps = n2.props || EMPTY_OBJ;\n    let vnodeHook;\n    parentComponent && toggleRecurse(parentComponent, false);\n    if (vnodeHook = newProps.onVnodeBeforeUpdate) {\n      invokeVNodeHook(vnodeHook, parentComponent, n2, n1);\n    }\n    if (dirs) {\n      invokeDirectiveHook(n2, n1, parentComponent, \"beforeUpdate\");\n    }\n    parentComponent && toggleRecurse(parentComponent, true);\n    if (isHmrUpdating) {\n      patchFlag = 0;\n      optimized = false;\n      dynamicChildren = null;\n    }\n    if (oldProps.innerHTML && newProps.innerHTML == null || oldProps.textContent && newProps.textContent == null) {\n      hostSetElementText(el, \"\");\n    }\n    if (dynamicChildren) {\n      patchBlockChildren(\n        n1.dynamicChildren,\n        dynamicChildren,\n        el,\n        parentComponent,\n        parentSuspense,\n        resolveChildrenNamespace(n2, namespace),\n        slotScopeIds\n      );\n      {\n        traverseStaticChildren(n1, n2);\n      }\n    } else if (!optimized) {\n      patchChildren(\n        n1,\n        n2,\n        el,\n        null,\n        parentComponent,\n        parentSuspense,\n        resolveChildrenNamespace(n2, namespace),\n        slotScopeIds,\n        false\n      );\n    }\n    if (patchFlag > 0) {\n      if (patchFlag & 16) {\n        patchProps(el, oldProps, newProps, parentComponent, namespace);\n      } else {\n        if (patchFlag & 2) {\n          if (oldProps.class !== newProps.class) {\n            hostPatchProp(el, \"class\", null, newProps.class, namespace);\n          }\n        }\n        if (patchFlag & 4) {\n          hostPatchProp(el, \"style\", oldProps.style, newProps.style, namespace);\n        }\n        if (patchFlag & 8) {\n          const propsToUpdate = n2.dynamicProps;\n          for (let i = 0; i < propsToUpdate.length; i++) {\n            const key = propsToUpdate[i];\n            const prev = oldProps[key];\n            const next = newProps[key];\n            if (next !== prev || key === \"value\") {\n              hostPatchProp(el, key, prev, next, namespace, parentComponent);\n            }\n          }\n        }\n      }\n      if (patchFlag & 1) {\n        if (n1.children !== n2.children) {\n          hostSetElementText(el, n2.children);\n        }\n      }\n    } else if (!optimized && dynamicChildren == null) {\n      patchProps(el, oldProps, newProps, parentComponent, namespace);\n    }\n    if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {\n      queuePostRenderEffect(() => {\n        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1);\n        dirs && invokeDirectiveHook(n2, n1, parentComponent, \"updated\");\n      }, parentSuspense);\n    }\n  };\n  const patchBlockChildren = (oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, namespace, slotScopeIds) => {\n    for (let i = 0; i < newChildren.length; i++) {\n      const oldVNode = oldChildren[i];\n      const newVNode = newChildren[i];\n      const container = (\n        // oldVNode may be an errored async setup() component inside Suspense\n        // which will not have a mounted element\n        oldVNode.el && // - In the case of a Fragment, we need to provide the actual parent\n        // of the Fragment itself so it can move its children.\n        (oldVNode.type === Fragment || // - In the case of different nodes, there is going to be a replacement\n        // which also requires the correct parent container\n        !isSameVNodeType(oldVNode, newVNode) || // - In the case of a component, it could contain anything.\n        oldVNode.shapeFlag & (6 | 64)) ? hostParentNode(oldVNode.el) : (\n          // In other cases, the parent container is not actually used so we\n          // just pass the block element here to avoid a DOM parentNode call.\n          fallbackContainer\n        )\n      );\n      patch(\n        oldVNode,\n        newVNode,\n        container,\n        null,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        true\n      );\n    }\n  };\n  const patchProps = (el, oldProps, newProps, parentComponent, namespace) => {\n    if (oldProps !== newProps) {\n      if (oldProps !== EMPTY_OBJ) {\n        for (const key in oldProps) {\n          if (!isReservedProp(key) && !(key in newProps)) {\n            hostPatchProp(\n              el,\n              key,\n              oldProps[key],\n              null,\n              namespace,\n              parentComponent\n            );\n          }\n        }\n      }\n      for (const key in newProps) {\n        if (isReservedProp(key)) continue;\n        const next = newProps[key];\n        const prev = oldProps[key];\n        if (next !== prev && key !== \"value\") {\n          hostPatchProp(el, key, prev, next, namespace, parentComponent);\n        }\n      }\n      if (\"value\" in newProps) {\n        hostPatchProp(el, \"value\", oldProps.value, newProps.value, namespace);\n      }\n    }\n  };\n  const processFragment = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {\n    const fragmentStartAnchor = n2.el = n1 ? n1.el : hostCreateText(\"\");\n    const fragmentEndAnchor = n2.anchor = n1 ? n1.anchor : hostCreateText(\"\");\n    let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2;\n    if (\n      // #5523 dev root fragment may inherit directives\n      isHmrUpdating || patchFlag & 2048\n    ) {\n      patchFlag = 0;\n      optimized = false;\n      dynamicChildren = null;\n    }\n    if (fragmentSlotScopeIds) {\n      slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds;\n    }\n    if (n1 == null) {\n      hostInsert(fragmentStartAnchor, container, anchor);\n      hostInsert(fragmentEndAnchor, container, anchor);\n      mountChildren(\n        // #10007\n        // such fragment like `<></>` will be compiled into\n        // a fragment which doesn't have a children.\n        // In this case fallback to an empty array\n        n2.children || [],\n        container,\n        fragmentEndAnchor,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n    } else {\n      if (patchFlag > 0 && patchFlag & 64 && dynamicChildren && // #2715 the previous fragment could've been a BAILed one as a result\n      // of renderSlot() with no valid children\n      n1.dynamicChildren) {\n        patchBlockChildren(\n          n1.dynamicChildren,\n          dynamicChildren,\n          container,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds\n        );\n        {\n          traverseStaticChildren(n1, n2);\n        }\n      } else {\n        patchChildren(\n          n1,\n          n2,\n          container,\n          fragmentEndAnchor,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n      }\n    }\n  };\n  const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {\n    n2.slotScopeIds = slotScopeIds;\n    if (n1 == null) {\n      if (n2.shapeFlag & 512) {\n        parentComponent.ctx.activate(\n          n2,\n          container,\n          anchor,\n          namespace,\n          optimized\n        );\n      } else {\n        mountComponent(\n          n2,\n          container,\n          anchor,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          optimized\n        );\n      }\n    } else {\n      updateComponent(n1, n2, optimized);\n    }\n  };\n  const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, namespace, optimized) => {\n    const instance = (initialVNode.component = createComponentInstance(\n      initialVNode,\n      parentComponent,\n      parentSuspense\n    ));\n    if (instance.type.__hmrId) {\n      registerHMR(instance);\n    }\n    {\n      pushWarningContext(initialVNode);\n      startMeasure(instance, `mount`);\n    }\n    if (isKeepAlive(initialVNode)) {\n      instance.ctx.renderer = internals;\n    }\n    {\n      {\n        startMeasure(instance, `init`);\n      }\n      setupComponent(instance, false, optimized);\n      {\n        endMeasure(instance, `init`);\n      }\n    }\n    if (instance.asyncDep) {\n      parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect, optimized);\n      if (!initialVNode.el) {\n        const placeholder = instance.subTree = createVNode(Comment);\n        processCommentNode(null, placeholder, container, anchor);\n      }\n    } else {\n      setupRenderEffect(\n        instance,\n        initialVNode,\n        container,\n        anchor,\n        parentSuspense,\n        namespace,\n        optimized\n      );\n    }\n    {\n      popWarningContext();\n      endMeasure(instance, `mount`);\n    }\n  };\n  const updateComponent = (n1, n2, optimized) => {\n    const instance = n2.component = n1.component;\n    if (shouldUpdateComponent(n1, n2, optimized)) {\n      if (instance.asyncDep && !instance.asyncResolved) {\n        {\n          pushWarningContext(n2);\n        }\n        updateComponentPreRender(instance, n2, optimized);\n        {\n          popWarningContext();\n        }\n        return;\n      } else {\n        instance.next = n2;\n        invalidateJob(instance.update);\n        instance.effect.dirty = true;\n        instance.update();\n      }\n    } else {\n      n2.el = n1.el;\n      instance.vnode = n2;\n    }\n  };\n  const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, namespace, optimized) => {\n    const componentUpdateFn = () => {\n      if (!instance.isMounted) {\n        let vnodeHook;\n        const { el, props } = initialVNode;\n        const { bm, m, parent } = instance;\n        const isAsyncWrapperVNode = isAsyncWrapper(initialVNode);\n        toggleRecurse(instance, false);\n        if (bm) {\n          invokeArrayFns(bm);\n        }\n        if (!isAsyncWrapperVNode && (vnodeHook = props && props.onVnodeBeforeMount)) {\n          invokeVNodeHook(vnodeHook, parent, initialVNode);\n        }\n        toggleRecurse(instance, true);\n        if (el && hydrateNode) {\n          const hydrateSubTree = () => {\n            {\n              startMeasure(instance, `render`);\n            }\n            instance.subTree = renderComponentRoot(instance);\n            {\n              endMeasure(instance, `render`);\n            }\n            {\n              startMeasure(instance, `hydrate`);\n            }\n            hydrateNode(\n              el,\n              instance.subTree,\n              instance,\n              parentSuspense,\n              null\n            );\n            {\n              endMeasure(instance, `hydrate`);\n            }\n          };\n          if (isAsyncWrapperVNode) {\n            initialVNode.type.__asyncLoader().then(\n              // note: we are moving the render call into an async callback,\n              // which means it won't track dependencies - but it's ok because\n              // a server-rendered async wrapper is already in resolved state\n              // and it will never need to change.\n              () => !instance.isUnmounted && hydrateSubTree()\n            );\n          } else {\n            hydrateSubTree();\n          }\n        } else {\n          {\n            startMeasure(instance, `render`);\n          }\n          const subTree = instance.subTree = renderComponentRoot(instance);\n          {\n            endMeasure(instance, `render`);\n          }\n          {\n            startMeasure(instance, `patch`);\n          }\n          patch(\n            null,\n            subTree,\n            container,\n            anchor,\n            instance,\n            parentSuspense,\n            namespace\n          );\n          {\n            endMeasure(instance, `patch`);\n          }\n          initialVNode.el = subTree.el;\n        }\n        if (m) {\n          queuePostRenderEffect(m, parentSuspense);\n        }\n        if (!isAsyncWrapperVNode && (vnodeHook = props && props.onVnodeMounted)) {\n          const scopedInitialVNode = initialVNode;\n          queuePostRenderEffect(\n            () => invokeVNodeHook(vnodeHook, parent, scopedInitialVNode),\n            parentSuspense\n          );\n        }\n        if (initialVNode.shapeFlag & 256 || parent && isAsyncWrapper(parent.vnode) && parent.vnode.shapeFlag & 256) {\n          instance.a && queuePostRenderEffect(instance.a, parentSuspense);\n        }\n        instance.isMounted = true;\n        {\n          devtoolsComponentAdded(instance);\n        }\n        initialVNode = container = anchor = null;\n      } else {\n        let { next, bu, u, parent, vnode } = instance;\n        {\n          const nonHydratedAsyncRoot = locateNonHydratedAsyncRoot(instance);\n          if (nonHydratedAsyncRoot) {\n            if (next) {\n              next.el = vnode.el;\n              updateComponentPreRender(instance, next, optimized);\n            }\n            nonHydratedAsyncRoot.asyncDep.then(() => {\n              if (!instance.isUnmounted) {\n                componentUpdateFn();\n              }\n            });\n            return;\n          }\n        }\n        let originNext = next;\n        let vnodeHook;\n        {\n          pushWarningContext(next || instance.vnode);\n        }\n        toggleRecurse(instance, false);\n        if (next) {\n          next.el = vnode.el;\n          updateComponentPreRender(instance, next, optimized);\n        } else {\n          next = vnode;\n        }\n        if (bu) {\n          invokeArrayFns(bu);\n        }\n        if (vnodeHook = next.props && next.props.onVnodeBeforeUpdate) {\n          invokeVNodeHook(vnodeHook, parent, next, vnode);\n        }\n        toggleRecurse(instance, true);\n        {\n          startMeasure(instance, `render`);\n        }\n        const nextTree = renderComponentRoot(instance);\n        {\n          endMeasure(instance, `render`);\n        }\n        const prevTree = instance.subTree;\n        instance.subTree = nextTree;\n        {\n          startMeasure(instance, `patch`);\n        }\n        patch(\n          prevTree,\n          nextTree,\n          // parent may have changed if it's in a teleport\n          hostParentNode(prevTree.el),\n          // anchor may have changed if it's in a fragment\n          getNextHostNode(prevTree),\n          instance,\n          parentSuspense,\n          namespace\n        );\n        {\n          endMeasure(instance, `patch`);\n        }\n        next.el = nextTree.el;\n        if (originNext === null) {\n          updateHOCHostEl(instance, nextTree.el);\n        }\n        if (u) {\n          queuePostRenderEffect(u, parentSuspense);\n        }\n        if (vnodeHook = next.props && next.props.onVnodeUpdated) {\n          queuePostRenderEffect(\n            () => invokeVNodeHook(vnodeHook, parent, next, vnode),\n            parentSuspense\n          );\n        }\n        {\n          devtoolsComponentUpdated(instance);\n        }\n        {\n          popWarningContext();\n        }\n      }\n    };\n    const effect = instance.effect = new ReactiveEffect(\n      componentUpdateFn,\n      NOOP,\n      () => queueJob(update),\n      instance.scope\n      // track it in component's effect scope\n    );\n    const update = instance.update = () => {\n      if (effect.dirty) {\n        effect.run();\n      }\n    };\n    update.i = instance;\n    update.id = instance.uid;\n    toggleRecurse(instance, true);\n    {\n      effect.onTrack = instance.rtc ? (e) => invokeArrayFns(instance.rtc, e) : void 0;\n      effect.onTrigger = instance.rtg ? (e) => invokeArrayFns(instance.rtg, e) : void 0;\n    }\n    update();\n  };\n  const updateComponentPreRender = (instance, nextVNode, optimized) => {\n    nextVNode.component = instance;\n    const prevProps = instance.vnode.props;\n    instance.vnode = nextVNode;\n    instance.next = null;\n    updateProps(instance, nextVNode.props, prevProps, optimized);\n    updateSlots(instance, nextVNode.children, optimized);\n    pauseTracking();\n    flushPreFlushCbs(instance);\n    resetTracking();\n  };\n  const patchChildren = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized = false) => {\n    const c1 = n1 && n1.children;\n    const prevShapeFlag = n1 ? n1.shapeFlag : 0;\n    const c2 = n2.children;\n    const { patchFlag, shapeFlag } = n2;\n    if (patchFlag > 0) {\n      if (patchFlag & 128) {\n        patchKeyedChildren(\n          c1,\n          c2,\n          container,\n          anchor,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n        return;\n      } else if (patchFlag & 256) {\n        patchUnkeyedChildren(\n          c1,\n          c2,\n          container,\n          anchor,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n        return;\n      }\n    }\n    if (shapeFlag & 8) {\n      if (prevShapeFlag & 16) {\n        unmountChildren(c1, parentComponent, parentSuspense);\n      }\n      if (c2 !== c1) {\n        hostSetElementText(container, c2);\n      }\n    } else {\n      if (prevShapeFlag & 16) {\n        if (shapeFlag & 16) {\n          patchKeyedChildren(\n            c1,\n            c2,\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n        } else {\n          unmountChildren(c1, parentComponent, parentSuspense, true);\n        }\n      } else {\n        if (prevShapeFlag & 8) {\n          hostSetElementText(container, \"\");\n        }\n        if (shapeFlag & 16) {\n          mountChildren(\n            c2,\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n        }\n      }\n    }\n  };\n  const patchUnkeyedChildren = (c1, c2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {\n    c1 = c1 || EMPTY_ARR;\n    c2 = c2 || EMPTY_ARR;\n    const oldLength = c1.length;\n    const newLength = c2.length;\n    const commonLength = Math.min(oldLength, newLength);\n    let i;\n    for (i = 0; i < commonLength; i++) {\n      const nextChild = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);\n      patch(\n        c1[i],\n        nextChild,\n        container,\n        null,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n    }\n    if (oldLength > newLength) {\n      unmountChildren(\n        c1,\n        parentComponent,\n        parentSuspense,\n        true,\n        false,\n        commonLength\n      );\n    } else {\n      mountChildren(\n        c2,\n        container,\n        anchor,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        optimized,\n        commonLength\n      );\n    }\n  };\n  const patchKeyedChildren = (c1, c2, container, parentAnchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {\n    let i = 0;\n    const l2 = c2.length;\n    let e1 = c1.length - 1;\n    let e2 = l2 - 1;\n    while (i <= e1 && i <= e2) {\n      const n1 = c1[i];\n      const n2 = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);\n      if (isSameVNodeType(n1, n2)) {\n        patch(\n          n1,\n          n2,\n          container,\n          null,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n      } else {\n        break;\n      }\n      i++;\n    }\n    while (i <= e1 && i <= e2) {\n      const n1 = c1[e1];\n      const n2 = c2[e2] = optimized ? cloneIfMounted(c2[e2]) : normalizeVNode(c2[e2]);\n      if (isSameVNodeType(n1, n2)) {\n        patch(\n          n1,\n          n2,\n          container,\n          null,\n          parentComponent,\n          parentSuspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n      } else {\n        break;\n      }\n      e1--;\n      e2--;\n    }\n    if (i > e1) {\n      if (i <= e2) {\n        const nextPos = e2 + 1;\n        const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;\n        while (i <= e2) {\n          patch(\n            null,\n            c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]),\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n          i++;\n        }\n      }\n    } else if (i > e2) {\n      while (i <= e1) {\n        unmount(c1[i], parentComponent, parentSuspense, true);\n        i++;\n      }\n    } else {\n      const s1 = i;\n      const s2 = i;\n      const keyToNewIndexMap = /* @__PURE__ */ new Map();\n      for (i = s2; i <= e2; i++) {\n        const nextChild = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);\n        if (nextChild.key != null) {\n          if (keyToNewIndexMap.has(nextChild.key)) {\n            warn$1(\n              `Duplicate keys found during update:`,\n              JSON.stringify(nextChild.key),\n              `Make sure keys are unique.`\n            );\n          }\n          keyToNewIndexMap.set(nextChild.key, i);\n        }\n      }\n      let j;\n      let patched = 0;\n      const toBePatched = e2 - s2 + 1;\n      let moved = false;\n      let maxNewIndexSoFar = 0;\n      const newIndexToOldIndexMap = new Array(toBePatched);\n      for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;\n      for (i = s1; i <= e1; i++) {\n        const prevChild = c1[i];\n        if (patched >= toBePatched) {\n          unmount(prevChild, parentComponent, parentSuspense, true);\n          continue;\n        }\n        let newIndex;\n        if (prevChild.key != null) {\n          newIndex = keyToNewIndexMap.get(prevChild.key);\n        } else {\n          for (j = s2; j <= e2; j++) {\n            if (newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j])) {\n              newIndex = j;\n              break;\n            }\n          }\n        }\n        if (newIndex === void 0) {\n          unmount(prevChild, parentComponent, parentSuspense, true);\n        } else {\n          newIndexToOldIndexMap[newIndex - s2] = i + 1;\n          if (newIndex >= maxNewIndexSoFar) {\n            maxNewIndexSoFar = newIndex;\n          } else {\n            moved = true;\n          }\n          patch(\n            prevChild,\n            c2[newIndex],\n            container,\n            null,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n          patched++;\n        }\n      }\n      const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : EMPTY_ARR;\n      j = increasingNewIndexSequence.length - 1;\n      for (i = toBePatched - 1; i >= 0; i--) {\n        const nextIndex = s2 + i;\n        const nextChild = c2[nextIndex];\n        const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor;\n        if (newIndexToOldIndexMap[i] === 0) {\n          patch(\n            null,\n            nextChild,\n            container,\n            anchor,\n            parentComponent,\n            parentSuspense,\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n        } else if (moved) {\n          if (j < 0 || i !== increasingNewIndexSequence[j]) {\n            move(nextChild, container, anchor, 2);\n          } else {\n            j--;\n          }\n        }\n      }\n    }\n  };\n  const move = (vnode, container, anchor, moveType, parentSuspense = null) => {\n    const { el, type, transition, children, shapeFlag } = vnode;\n    if (shapeFlag & 6) {\n      move(vnode.component.subTree, container, anchor, moveType);\n      return;\n    }\n    if (shapeFlag & 128) {\n      vnode.suspense.move(container, anchor, moveType);\n      return;\n    }\n    if (shapeFlag & 64) {\n      type.move(vnode, container, anchor, internals);\n      return;\n    }\n    if (type === Fragment) {\n      hostInsert(el, container, anchor);\n      for (let i = 0; i < children.length; i++) {\n        move(children[i], container, anchor, moveType);\n      }\n      hostInsert(vnode.anchor, container, anchor);\n      return;\n    }\n    if (type === Static) {\n      moveStaticNode(vnode, container, anchor);\n      return;\n    }\n    const needTransition2 = moveType !== 2 && shapeFlag & 1 && transition;\n    if (needTransition2) {\n      if (moveType === 0) {\n        transition.beforeEnter(el);\n        hostInsert(el, container, anchor);\n        queuePostRenderEffect(() => transition.enter(el), parentSuspense);\n      } else {\n        const { leave, delayLeave, afterLeave } = transition;\n        const remove2 = () => hostInsert(el, container, anchor);\n        const performLeave = () => {\n          leave(el, () => {\n            remove2();\n            afterLeave && afterLeave();\n          });\n        };\n        if (delayLeave) {\n          delayLeave(el, remove2, performLeave);\n        } else {\n          performLeave();\n        }\n      }\n    } else {\n      hostInsert(el, container, anchor);\n    }\n  };\n  const unmount = (vnode, parentComponent, parentSuspense, doRemove = false, optimized = false) => {\n    const {\n      type,\n      props,\n      ref,\n      children,\n      dynamicChildren,\n      shapeFlag,\n      patchFlag,\n      dirs,\n      cacheIndex\n    } = vnode;\n    if (patchFlag === -2) {\n      optimized = false;\n    }\n    if (ref != null) {\n      setRef(ref, null, parentSuspense, vnode, true);\n    }\n    if (cacheIndex != null) {\n      parentComponent.renderCache[cacheIndex] = void 0;\n    }\n    if (shapeFlag & 256) {\n      parentComponent.ctx.deactivate(vnode);\n      return;\n    }\n    const shouldInvokeDirs = shapeFlag & 1 && dirs;\n    const shouldInvokeVnodeHook = !isAsyncWrapper(vnode);\n    let vnodeHook;\n    if (shouldInvokeVnodeHook && (vnodeHook = props && props.onVnodeBeforeUnmount)) {\n      invokeVNodeHook(vnodeHook, parentComponent, vnode);\n    }\n    if (shapeFlag & 6) {\n      unmountComponent(vnode.component, parentSuspense, doRemove);\n    } else {\n      if (shapeFlag & 128) {\n        vnode.suspense.unmount(parentSuspense, doRemove);\n        return;\n      }\n      if (shouldInvokeDirs) {\n        invokeDirectiveHook(vnode, null, parentComponent, \"beforeUnmount\");\n      }\n      if (shapeFlag & 64) {\n        vnode.type.remove(\n          vnode,\n          parentComponent,\n          parentSuspense,\n          internals,\n          doRemove\n        );\n      } else if (dynamicChildren && // #5154\n      // when v-once is used inside a block, setBlockTracking(-1) marks the\n      // parent block with hasOnce: true\n      // so that it doesn't take the fast path during unmount - otherwise\n      // components nested in v-once are never unmounted.\n      !dynamicChildren.hasOnce && // #1153: fast path should not be taken for non-stable (v-for) fragments\n      (type !== Fragment || patchFlag > 0 && patchFlag & 64)) {\n        unmountChildren(\n          dynamicChildren,\n          parentComponent,\n          parentSuspense,\n          false,\n          true\n        );\n      } else if (type === Fragment && patchFlag & (128 | 256) || !optimized && shapeFlag & 16) {\n        unmountChildren(children, parentComponent, parentSuspense);\n      }\n      if (doRemove) {\n        remove(vnode);\n      }\n    }\n    if (shouldInvokeVnodeHook && (vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) {\n      queuePostRenderEffect(() => {\n        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);\n        shouldInvokeDirs && invokeDirectiveHook(vnode, null, parentComponent, \"unmounted\");\n      }, parentSuspense);\n    }\n  };\n  const remove = (vnode) => {\n    const { type, el, anchor, transition } = vnode;\n    if (type === Fragment) {\n      if (vnode.patchFlag > 0 && vnode.patchFlag & 2048 && transition && !transition.persisted) {\n        vnode.children.forEach((child) => {\n          if (child.type === Comment) {\n            hostRemove(child.el);\n          } else {\n            remove(child);\n          }\n        });\n      } else {\n        removeFragment(el, anchor);\n      }\n      return;\n    }\n    if (type === Static) {\n      removeStaticNode(vnode);\n      return;\n    }\n    const performRemove = () => {\n      hostRemove(el);\n      if (transition && !transition.persisted && transition.afterLeave) {\n        transition.afterLeave();\n      }\n    };\n    if (vnode.shapeFlag & 1 && transition && !transition.persisted) {\n      const { leave, delayLeave } = transition;\n      const performLeave = () => leave(el, performRemove);\n      if (delayLeave) {\n        delayLeave(vnode.el, performRemove, performLeave);\n      } else {\n        performLeave();\n      }\n    } else {\n      performRemove();\n    }\n  };\n  const removeFragment = (cur, end) => {\n    let next;\n    while (cur !== end) {\n      next = hostNextSibling(cur);\n      hostRemove(cur);\n      cur = next;\n    }\n    hostRemove(end);\n  };\n  const unmountComponent = (instance, parentSuspense, doRemove) => {\n    if (instance.type.__hmrId) {\n      unregisterHMR(instance);\n    }\n    const { bum, scope, update, subTree, um, m, a } = instance;\n    invalidateMount(m);\n    invalidateMount(a);\n    if (bum) {\n      invokeArrayFns(bum);\n    }\n    scope.stop();\n    if (update) {\n      update.active = false;\n      unmount(subTree, instance, parentSuspense, doRemove);\n    }\n    if (um) {\n      queuePostRenderEffect(um, parentSuspense);\n    }\n    queuePostRenderEffect(() => {\n      instance.isUnmounted = true;\n    }, parentSuspense);\n    if (parentSuspense && parentSuspense.pendingBranch && !parentSuspense.isUnmounted && instance.asyncDep && !instance.asyncResolved && instance.suspenseId === parentSuspense.pendingId) {\n      parentSuspense.deps--;\n      if (parentSuspense.deps === 0) {\n        parentSuspense.resolve();\n      }\n    }\n    {\n      devtoolsComponentRemoved(instance);\n    }\n  };\n  const unmountChildren = (children, parentComponent, parentSuspense, doRemove = false, optimized = false, start = 0) => {\n    for (let i = start; i < children.length; i++) {\n      unmount(children[i], parentComponent, parentSuspense, doRemove, optimized);\n    }\n  };\n  const getNextHostNode = (vnode) => {\n    if (vnode.shapeFlag & 6) {\n      return getNextHostNode(vnode.component.subTree);\n    }\n    if (vnode.shapeFlag & 128) {\n      return vnode.suspense.next();\n    }\n    const el = hostNextSibling(vnode.anchor || vnode.el);\n    const teleportEnd = el && el[TeleportEndKey];\n    return teleportEnd ? hostNextSibling(teleportEnd) : el;\n  };\n  let isFlushing = false;\n  const render = (vnode, container, namespace) => {\n    if (vnode == null) {\n      if (container._vnode) {\n        unmount(container._vnode, null, null, true);\n      }\n    } else {\n      patch(\n        container._vnode || null,\n        vnode,\n        container,\n        null,\n        null,\n        null,\n        namespace\n      );\n    }\n    if (!isFlushing) {\n      isFlushing = true;\n      flushPreFlushCbs();\n      flushPostFlushCbs();\n      isFlushing = false;\n    }\n    container._vnode = vnode;\n  };\n  const internals = {\n    p: patch,\n    um: unmount,\n    m: move,\n    r: remove,\n    mt: mountComponent,\n    mc: mountChildren,\n    pc: patchChildren,\n    pbc: patchBlockChildren,\n    n: getNextHostNode,\n    o: options\n  };\n  let hydrate;\n  let hydrateNode;\n  if (createHydrationFns) {\n    [hydrate, hydrateNode] = createHydrationFns(\n      internals\n    );\n  }\n  return {\n    render,\n    hydrate,\n    createApp: createAppAPI(render, hydrate)\n  };\n}\nfunction resolveChildrenNamespace({ type, props }, currentNamespace) {\n  return currentNamespace === \"svg\" && type === \"foreignObject\" || currentNamespace === \"mathml\" && type === \"annotation-xml\" && props && props.encoding && props.encoding.includes(\"html\") ? void 0 : currentNamespace;\n}\nfunction toggleRecurse({ effect, update }, allowed) {\n  effect.allowRecurse = update.allowRecurse = allowed;\n}\nfunction needTransition(parentSuspense, transition) {\n  return (!parentSuspense || parentSuspense && !parentSuspense.pendingBranch) && transition && !transition.persisted;\n}\nfunction traverseStaticChildren(n1, n2, shallow = false) {\n  const ch1 = n1.children;\n  const ch2 = n2.children;\n  if (isArray(ch1) && isArray(ch2)) {\n    for (let i = 0; i < ch1.length; i++) {\n      const c1 = ch1[i];\n      let c2 = ch2[i];\n      if (c2.shapeFlag & 1 && !c2.dynamicChildren) {\n        if (c2.patchFlag <= 0 || c2.patchFlag === 32) {\n          c2 = ch2[i] = cloneIfMounted(ch2[i]);\n          c2.el = c1.el;\n        }\n        if (!shallow && c2.patchFlag !== -2)\n          traverseStaticChildren(c1, c2);\n      }\n      if (c2.type === Text) {\n        c2.el = c1.el;\n      }\n      if (c2.type === Comment && !c2.el) {\n        c2.el = c1.el;\n      }\n    }\n  }\n}\nfunction getSequence(arr) {\n  const p = arr.slice();\n  const result = [0];\n  let i, j, u, v, c;\n  const len = arr.length;\n  for (i = 0; i < len; i++) {\n    const arrI = arr[i];\n    if (arrI !== 0) {\n      j = result[result.length - 1];\n      if (arr[j] < arrI) {\n        p[i] = j;\n        result.push(i);\n        continue;\n      }\n      u = 0;\n      v = result.length - 1;\n      while (u < v) {\n        c = u + v >> 1;\n        if (arr[result[c]] < arrI) {\n          u = c + 1;\n        } else {\n          v = c;\n        }\n      }\n      if (arrI < arr[result[u]]) {\n        if (u > 0) {\n          p[i] = result[u - 1];\n        }\n        result[u] = i;\n      }\n    }\n  }\n  u = result.length;\n  v = result[u - 1];\n  while (u-- > 0) {\n    result[u] = v;\n    v = p[v];\n  }\n  return result;\n}\nfunction locateNonHydratedAsyncRoot(instance) {\n  const subComponent = instance.subTree.component;\n  if (subComponent) {\n    if (subComponent.asyncDep && !subComponent.asyncResolved) {\n      return subComponent;\n    } else {\n      return locateNonHydratedAsyncRoot(subComponent);\n    }\n  }\n}\nfunction invalidateMount(hooks) {\n  if (hooks) {\n    for (let i = 0; i < hooks.length; i++) hooks[i].active = false;\n  }\n}\n\nconst ssrContextKey = Symbol.for(\"v-scx\");\nconst useSSRContext = () => {\n  {\n    const ctx = inject(ssrContextKey);\n    if (!ctx) {\n      warn$1(\n        `Server rendering context not provided. Make sure to only call useSSRContext() conditionally in the server build.`\n      );\n    }\n    return ctx;\n  }\n};\n\nfunction watchEffect(effect, options) {\n  return doWatch(effect, null, options);\n}\nfunction watchPostEffect(effect, options) {\n  return doWatch(\n    effect,\n    null,\n    extend({}, options, { flush: \"post\" }) \n  );\n}\nfunction watchSyncEffect(effect, options) {\n  return doWatch(\n    effect,\n    null,\n    extend({}, options, { flush: \"sync\" }) \n  );\n}\nconst INITIAL_WATCHER_VALUE = {};\nfunction watch(source, cb, options) {\n  if (!isFunction(cb)) {\n    warn$1(\n      `\\`watch(fn, options?)\\` signature has been moved to a separate API. Use \\`watchEffect(fn, options?)\\` instead. \\`watch\\` now only supports \\`watch(source, cb, options?) signature.`\n    );\n  }\n  return doWatch(source, cb, options);\n}\nfunction doWatch(source, cb, {\n  immediate,\n  deep,\n  flush,\n  once,\n  onTrack,\n  onTrigger\n} = EMPTY_OBJ) {\n  if (cb && once) {\n    const _cb = cb;\n    cb = (...args) => {\n      _cb(...args);\n      unwatch();\n    };\n  }\n  if (deep !== void 0 && typeof deep === \"number\") {\n    warn$1(\n      `watch() \"deep\" option with number value will be used as watch depth in future versions. Please use a boolean instead to avoid potential breakage.`\n    );\n  }\n  if (!cb) {\n    if (immediate !== void 0) {\n      warn$1(\n        `watch() \"immediate\" option is only respected when using the watch(source, callback, options?) signature.`\n      );\n    }\n    if (deep !== void 0) {\n      warn$1(\n        `watch() \"deep\" option is only respected when using the watch(source, callback, options?) signature.`\n      );\n    }\n    if (once !== void 0) {\n      warn$1(\n        `watch() \"once\" option is only respected when using the watch(source, callback, options?) signature.`\n      );\n    }\n  }\n  const warnInvalidSource = (s) => {\n    warn$1(\n      `Invalid watch source: `,\n      s,\n      `A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.`\n    );\n  };\n  const instance = currentInstance;\n  const reactiveGetter = (source2) => deep === true ? source2 : (\n    // for deep: false, only traverse root-level properties\n    traverse(source2, deep === false ? 1 : void 0)\n  );\n  let getter;\n  let forceTrigger = false;\n  let isMultiSource = false;\n  if (isRef(source)) {\n    getter = () => source.value;\n    forceTrigger = isShallow(source);\n  } else if (isReactive(source)) {\n    getter = () => reactiveGetter(source);\n    forceTrigger = true;\n  } else if (isArray(source)) {\n    isMultiSource = true;\n    forceTrigger = source.some((s) => isReactive(s) || isShallow(s));\n    getter = () => source.map((s) => {\n      if (isRef(s)) {\n        return s.value;\n      } else if (isReactive(s)) {\n        return reactiveGetter(s);\n      } else if (isFunction(s)) {\n        return callWithErrorHandling(s, instance, 2);\n      } else {\n        warnInvalidSource(s);\n      }\n    });\n  } else if (isFunction(source)) {\n    if (cb) {\n      getter = () => callWithErrorHandling(source, instance, 2);\n    } else {\n      getter = () => {\n        if (cleanup) {\n          cleanup();\n        }\n        return callWithAsyncErrorHandling(\n          source,\n          instance,\n          3,\n          [onCleanup]\n        );\n      };\n    }\n  } else {\n    getter = NOOP;\n    warnInvalidSource(source);\n  }\n  if (cb && deep) {\n    const baseGetter = getter;\n    getter = () => traverse(baseGetter());\n  }\n  let cleanup;\n  let onCleanup = (fn) => {\n    cleanup = effect.onStop = () => {\n      callWithErrorHandling(fn, instance, 4);\n      cleanup = effect.onStop = void 0;\n    };\n  };\n  let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE;\n  const job = () => {\n    if (!effect.active || !effect.dirty) {\n      return;\n    }\n    if (cb) {\n      const newValue = effect.run();\n      if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue)) || false) {\n        if (cleanup) {\n          cleanup();\n        }\n        callWithAsyncErrorHandling(cb, instance, 3, [\n          newValue,\n          // pass undefined as the old value when it's changed for the first time\n          oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue,\n          onCleanup\n        ]);\n        oldValue = newValue;\n      }\n    } else {\n      effect.run();\n    }\n  };\n  job.allowRecurse = !!cb;\n  let scheduler;\n  if (flush === \"sync\") {\n    scheduler = job;\n  } else if (flush === \"post\") {\n    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);\n  } else {\n    job.pre = true;\n    if (instance) job.id = instance.uid;\n    scheduler = () => queueJob(job);\n  }\n  const effect = new ReactiveEffect(getter, NOOP, scheduler);\n  const scope = getCurrentScope();\n  const unwatch = () => {\n    effect.stop();\n    if (scope) {\n      remove(scope.effects, effect);\n    }\n  };\n  {\n    effect.onTrack = onTrack;\n    effect.onTrigger = onTrigger;\n  }\n  if (cb) {\n    if (immediate) {\n      job();\n    } else {\n      oldValue = effect.run();\n    }\n  } else if (flush === \"post\") {\n    queuePostRenderEffect(\n      effect.run.bind(effect),\n      instance && instance.suspense\n    );\n  } else {\n    effect.run();\n  }\n  return unwatch;\n}\nfunction instanceWatch(source, value, options) {\n  const publicThis = this.proxy;\n  const getter = isString(source) ? source.includes(\".\") ? createPathGetter(publicThis, source) : () => publicThis[source] : source.bind(publicThis, publicThis);\n  let cb;\n  if (isFunction(value)) {\n    cb = value;\n  } else {\n    cb = value.handler;\n    options = value;\n  }\n  const reset = setCurrentInstance(this);\n  const res = doWatch(getter, cb.bind(publicThis), options);\n  reset();\n  return res;\n}\nfunction createPathGetter(ctx, path) {\n  const segments = path.split(\".\");\n  return () => {\n    let cur = ctx;\n    for (let i = 0; i < segments.length && cur; i++) {\n      cur = cur[segments[i]];\n    }\n    return cur;\n  };\n}\nfunction traverse(value, depth = Infinity, seen) {\n  if (depth <= 0 || !isObject(value) || value[\"__v_skip\"]) {\n    return value;\n  }\n  seen = seen || /* @__PURE__ */ new Set();\n  if (seen.has(value)) {\n    return value;\n  }\n  seen.add(value);\n  depth--;\n  if (isRef(value)) {\n    traverse(value.value, depth, seen);\n  } else if (isArray(value)) {\n    for (let i = 0; i < value.length; i++) {\n      traverse(value[i], depth, seen);\n    }\n  } else if (isSet(value) || isMap(value)) {\n    value.forEach((v) => {\n      traverse(v, depth, seen);\n    });\n  } else if (isPlainObject(value)) {\n    for (const key in value) {\n      traverse(value[key], depth, seen);\n    }\n    for (const key of Object.getOwnPropertySymbols(value)) {\n      if (Object.prototype.propertyIsEnumerable.call(value, key)) {\n        traverse(value[key], depth, seen);\n      }\n    }\n  }\n  return value;\n}\n\nfunction useModel(props, name, options = EMPTY_OBJ) {\n  const i = getCurrentInstance();\n  if (!i) {\n    warn$1(`useModel() called without active instance.`);\n    return ref();\n  }\n  if (!i.propsOptions[0][name]) {\n    warn$1(`useModel() called with prop \"${name}\" which is not declared.`);\n    return ref();\n  }\n  const camelizedName = camelize(name);\n  const hyphenatedName = hyphenate(name);\n  const modifiers = getModelModifiers(props, name);\n  const res = customRef((track, trigger) => {\n    let localValue;\n    let prevSetValue = EMPTY_OBJ;\n    let prevEmittedValue;\n    watchSyncEffect(() => {\n      const propValue = props[name];\n      if (hasChanged(localValue, propValue)) {\n        localValue = propValue;\n        trigger();\n      }\n    });\n    return {\n      get() {\n        track();\n        return options.get ? options.get(localValue) : localValue;\n      },\n      set(value) {\n        if (!hasChanged(value, localValue) && !(prevSetValue !== EMPTY_OBJ && hasChanged(value, prevSetValue))) {\n          return;\n        }\n        const rawProps = i.vnode.props;\n        if (!(rawProps && // check if parent has passed v-model\n        (name in rawProps || camelizedName in rawProps || hyphenatedName in rawProps) && (`onUpdate:${name}` in rawProps || `onUpdate:${camelizedName}` in rawProps || `onUpdate:${hyphenatedName}` in rawProps))) {\n          localValue = value;\n          trigger();\n        }\n        const emittedValue = options.set ? options.set(value) : value;\n        i.emit(`update:${name}`, emittedValue);\n        if (hasChanged(value, emittedValue) && hasChanged(value, prevSetValue) && !hasChanged(emittedValue, prevEmittedValue)) {\n          trigger();\n        }\n        prevSetValue = value;\n        prevEmittedValue = emittedValue;\n      }\n    };\n  });\n  res[Symbol.iterator] = () => {\n    let i2 = 0;\n    return {\n      next() {\n        if (i2 < 2) {\n          return { value: i2++ ? modifiers || EMPTY_OBJ : res, done: false };\n        } else {\n          return { done: true };\n        }\n      }\n    };\n  };\n  return res;\n}\nconst getModelModifiers = (props, modelName) => {\n  return modelName === \"modelValue\" || modelName === \"model-value\" ? props.modelModifiers : props[`${modelName}Modifiers`] || props[`${camelize(modelName)}Modifiers`] || props[`${hyphenate(modelName)}Modifiers`];\n};\n\nfunction emit(instance, event, ...rawArgs) {\n  if (instance.isUnmounted) return;\n  const props = instance.vnode.props || EMPTY_OBJ;\n  {\n    const {\n      emitsOptions,\n      propsOptions: [propsOptions]\n    } = instance;\n    if (emitsOptions) {\n      if (!(event in emitsOptions) && true) {\n        if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {\n          warn$1(\n            `Component emitted event \"${event}\" but it is neither declared in the emits option nor as an \"${toHandlerKey(event)}\" prop.`\n          );\n        }\n      } else {\n        const validator = emitsOptions[event];\n        if (isFunction(validator)) {\n          const isValid = validator(...rawArgs);\n          if (!isValid) {\n            warn$1(\n              `Invalid event arguments: event validation failed for event \"${event}\".`\n            );\n          }\n        }\n      }\n    }\n  }\n  let args = rawArgs;\n  const isModelListener = event.startsWith(\"update:\");\n  const modifiers = isModelListener && getModelModifiers(props, event.slice(7));\n  if (modifiers) {\n    if (modifiers.trim) {\n      args = rawArgs.map((a) => isString(a) ? a.trim() : a);\n    }\n    if (modifiers.number) {\n      args = rawArgs.map(looseToNumber);\n    }\n  }\n  {\n    devtoolsComponentEmit(instance, event, args);\n  }\n  {\n    const lowerCaseEvent = event.toLowerCase();\n    if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) {\n      warn$1(\n        `Event \"${lowerCaseEvent}\" is emitted in component ${formatComponentName(\n          instance,\n          instance.type\n        )} but the handler is registered for \"${event}\". Note that HTML attributes are case-insensitive and you cannot use v-on to listen to camelCase events when using in-DOM templates. You should probably use \"${hyphenate(\n          event\n        )}\" instead of \"${event}\".`\n      );\n    }\n  }\n  let handlerName;\n  let handler = props[handlerName = toHandlerKey(event)] || // also try camelCase event handler (#2249)\n  props[handlerName = toHandlerKey(camelize(event))];\n  if (!handler && isModelListener) {\n    handler = props[handlerName = toHandlerKey(hyphenate(event))];\n  }\n  if (handler) {\n    callWithAsyncErrorHandling(\n      handler,\n      instance,\n      6,\n      args\n    );\n  }\n  const onceHandler = props[handlerName + `Once`];\n  if (onceHandler) {\n    if (!instance.emitted) {\n      instance.emitted = {};\n    } else if (instance.emitted[handlerName]) {\n      return;\n    }\n    instance.emitted[handlerName] = true;\n    callWithAsyncErrorHandling(\n      onceHandler,\n      instance,\n      6,\n      args\n    );\n  }\n}\nfunction normalizeEmitsOptions(comp, appContext, asMixin = false) {\n  const cache = appContext.emitsCache;\n  const cached = cache.get(comp);\n  if (cached !== void 0) {\n    return cached;\n  }\n  const raw = comp.emits;\n  let normalized = {};\n  let hasExtends = false;\n  if (!isFunction(comp)) {\n    const extendEmits = (raw2) => {\n      const normalizedFromExtend = normalizeEmitsOptions(raw2, appContext, true);\n      if (normalizedFromExtend) {\n        hasExtends = true;\n        extend(normalized, normalizedFromExtend);\n      }\n    };\n    if (!asMixin && appContext.mixins.length) {\n      appContext.mixins.forEach(extendEmits);\n    }\n    if (comp.extends) {\n      extendEmits(comp.extends);\n    }\n    if (comp.mixins) {\n      comp.mixins.forEach(extendEmits);\n    }\n  }\n  if (!raw && !hasExtends) {\n    if (isObject(comp)) {\n      cache.set(comp, null);\n    }\n    return null;\n  }\n  if (isArray(raw)) {\n    raw.forEach((key) => normalized[key] = null);\n  } else {\n    extend(normalized, raw);\n  }\n  if (isObject(comp)) {\n    cache.set(comp, normalized);\n  }\n  return normalized;\n}\nfunction isEmitListener(options, key) {\n  if (!options || !isOn(key)) {\n    return false;\n  }\n  key = key.slice(2).replace(/Once$/, \"\");\n  return hasOwn(options, key[0].toLowerCase() + key.slice(1)) || hasOwn(options, hyphenate(key)) || hasOwn(options, key);\n}\n\nlet accessedAttrs = false;\nfunction markAttrsAccessed() {\n  accessedAttrs = true;\n}\nfunction renderComponentRoot(instance) {\n  const {\n    type: Component,\n    vnode,\n    proxy,\n    withProxy,\n    propsOptions: [propsOptions],\n    slots,\n    attrs,\n    emit,\n    render,\n    renderCache,\n    props,\n    data,\n    setupState,\n    ctx,\n    inheritAttrs\n  } = instance;\n  const prev = setCurrentRenderingInstance(instance);\n  let result;\n  let fallthroughAttrs;\n  {\n    accessedAttrs = false;\n  }\n  try {\n    if (vnode.shapeFlag & 4) {\n      const proxyToUse = withProxy || proxy;\n      const thisProxy = setupState.__isScriptSetup ? new Proxy(proxyToUse, {\n        get(target, key, receiver) {\n          warn$1(\n            `Property '${String(\n              key\n            )}' was accessed via 'this'. Avoid using 'this' in templates.`\n          );\n          return Reflect.get(target, key, receiver);\n        }\n      }) : proxyToUse;\n      result = normalizeVNode(\n        render.call(\n          thisProxy,\n          proxyToUse,\n          renderCache,\n          true ? shallowReadonly(props) : props,\n          setupState,\n          data,\n          ctx\n        )\n      );\n      fallthroughAttrs = attrs;\n    } else {\n      const render2 = Component;\n      if (attrs === props) {\n        markAttrsAccessed();\n      }\n      result = normalizeVNode(\n        render2.length > 1 ? render2(\n          true ? shallowReadonly(props) : props,\n          true ? {\n            get attrs() {\n              markAttrsAccessed();\n              return shallowReadonly(attrs);\n            },\n            slots,\n            emit\n          } : { attrs, slots, emit }\n        ) : render2(\n          true ? shallowReadonly(props) : props,\n          null\n        )\n      );\n      fallthroughAttrs = Component.props ? attrs : getFunctionalFallthrough(attrs);\n    }\n  } catch (err) {\n    blockStack.length = 0;\n    handleError(err, instance, 1);\n    result = createVNode(Comment);\n  }\n  let root = result;\n  let setRoot = void 0;\n  if (result.patchFlag > 0 && result.patchFlag & 2048) {\n    [root, setRoot] = getChildRoot(result);\n  }\n  if (fallthroughAttrs && inheritAttrs !== false) {\n    const keys = Object.keys(fallthroughAttrs);\n    const { shapeFlag } = root;\n    if (keys.length) {\n      if (shapeFlag & (1 | 6)) {\n        if (propsOptions && keys.some(isModelListener)) {\n          fallthroughAttrs = filterModelListeners(\n            fallthroughAttrs,\n            propsOptions\n          );\n        }\n        root = cloneVNode(root, fallthroughAttrs, false, true);\n      } else if (!accessedAttrs && root.type !== Comment) {\n        const allAttrs = Object.keys(attrs);\n        const eventAttrs = [];\n        const extraAttrs = [];\n        for (let i = 0, l = allAttrs.length; i < l; i++) {\n          const key = allAttrs[i];\n          if (isOn(key)) {\n            if (!isModelListener(key)) {\n              eventAttrs.push(key[2].toLowerCase() + key.slice(3));\n            }\n          } else {\n            extraAttrs.push(key);\n          }\n        }\n        if (extraAttrs.length) {\n          warn$1(\n            `Extraneous non-props attributes (${extraAttrs.join(\", \")}) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.`\n          );\n        }\n        if (eventAttrs.length) {\n          warn$1(\n            `Extraneous non-emits event listeners (${eventAttrs.join(\", \")}) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the \"emits\" option.`\n          );\n        }\n      }\n    }\n  }\n  if (vnode.dirs) {\n    if (!isElementRoot(root)) {\n      warn$1(\n        `Runtime directive used on component with non-element root node. The directives will not function as intended.`\n      );\n    }\n    root = cloneVNode(root, null, false, true);\n    root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs;\n  }\n  if (vnode.transition) {\n    if (!isElementRoot(root)) {\n      warn$1(\n        `Component inside <Transition> renders non-element root node that cannot be animated.`\n      );\n    }\n    root.transition = vnode.transition;\n  }\n  if (setRoot) {\n    setRoot(root);\n  } else {\n    result = root;\n  }\n  setCurrentRenderingInstance(prev);\n  return result;\n}\nconst getChildRoot = (vnode) => {\n  const rawChildren = vnode.children;\n  const dynamicChildren = vnode.dynamicChildren;\n  const childRoot = filterSingleRoot(rawChildren, false);\n  if (!childRoot) {\n    return [vnode, void 0];\n  } else if (childRoot.patchFlag > 0 && childRoot.patchFlag & 2048) {\n    return getChildRoot(childRoot);\n  }\n  const index = rawChildren.indexOf(childRoot);\n  const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1;\n  const setRoot = (updatedRoot) => {\n    rawChildren[index] = updatedRoot;\n    if (dynamicChildren) {\n      if (dynamicIndex > -1) {\n        dynamicChildren[dynamicIndex] = updatedRoot;\n      } else if (updatedRoot.patchFlag > 0) {\n        vnode.dynamicChildren = [...dynamicChildren, updatedRoot];\n      }\n    }\n  };\n  return [normalizeVNode(childRoot), setRoot];\n};\nfunction filterSingleRoot(children, recurse = true) {\n  let singleRoot;\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    if (isVNode(child)) {\n      if (child.type !== Comment || child.children === \"v-if\") {\n        if (singleRoot) {\n          return;\n        } else {\n          singleRoot = child;\n          if (recurse && singleRoot.patchFlag > 0 && singleRoot.patchFlag & 2048) {\n            return filterSingleRoot(singleRoot.children);\n          }\n        }\n      }\n    } else {\n      return;\n    }\n  }\n  return singleRoot;\n}\nconst getFunctionalFallthrough = (attrs) => {\n  let res;\n  for (const key in attrs) {\n    if (key === \"class\" || key === \"style\" || isOn(key)) {\n      (res || (res = {}))[key] = attrs[key];\n    }\n  }\n  return res;\n};\nconst filterModelListeners = (attrs, props) => {\n  const res = {};\n  for (const key in attrs) {\n    if (!isModelListener(key) || !(key.slice(9) in props)) {\n      res[key] = attrs[key];\n    }\n  }\n  return res;\n};\nconst isElementRoot = (vnode) => {\n  return vnode.shapeFlag & (6 | 1) || vnode.type === Comment;\n};\nfunction shouldUpdateComponent(prevVNode, nextVNode, optimized) {\n  const { props: prevProps, children: prevChildren, component } = prevVNode;\n  const { props: nextProps, children: nextChildren, patchFlag } = nextVNode;\n  const emits = component.emitsOptions;\n  if ((prevChildren || nextChildren) && isHmrUpdating) {\n    return true;\n  }\n  if (nextVNode.dirs || nextVNode.transition) {\n    return true;\n  }\n  if (optimized && patchFlag >= 0) {\n    if (patchFlag & 1024) {\n      return true;\n    }\n    if (patchFlag & 16) {\n      if (!prevProps) {\n        return !!nextProps;\n      }\n      return hasPropsChanged(prevProps, nextProps, emits);\n    } else if (patchFlag & 8) {\n      const dynamicProps = nextVNode.dynamicProps;\n      for (let i = 0; i < dynamicProps.length; i++) {\n        const key = dynamicProps[i];\n        if (nextProps[key] !== prevProps[key] && !isEmitListener(emits, key)) {\n          return true;\n        }\n      }\n    }\n  } else {\n    if (prevChildren || nextChildren) {\n      if (!nextChildren || !nextChildren.$stable) {\n        return true;\n      }\n    }\n    if (prevProps === nextProps) {\n      return false;\n    }\n    if (!prevProps) {\n      return !!nextProps;\n    }\n    if (!nextProps) {\n      return true;\n    }\n    return hasPropsChanged(prevProps, nextProps, emits);\n  }\n  return false;\n}\nfunction hasPropsChanged(prevProps, nextProps, emitsOptions) {\n  const nextKeys = Object.keys(nextProps);\n  if (nextKeys.length !== Object.keys(prevProps).length) {\n    return true;\n  }\n  for (let i = 0; i < nextKeys.length; i++) {\n    const key = nextKeys[i];\n    if (nextProps[key] !== prevProps[key] && !isEmitListener(emitsOptions, key)) {\n      return true;\n    }\n  }\n  return false;\n}\nfunction updateHOCHostEl({ vnode, parent }, el) {\n  while (parent) {\n    const root = parent.subTree;\n    if (root.suspense && root.suspense.activeBranch === vnode) {\n      root.el = vnode.el;\n    }\n    if (root === vnode) {\n      (vnode = parent.vnode).el = el;\n      parent = parent.parent;\n    } else {\n      break;\n    }\n  }\n}\n\nconst isSuspense = (type) => type.__isSuspense;\nlet suspenseId = 0;\nconst SuspenseImpl = {\n  name: \"Suspense\",\n  // In order to make Suspense tree-shakable, we need to avoid importing it\n  // directly in the renderer. The renderer checks for the __isSuspense flag\n  // on a vnode's type and calls the `process` method, passing in renderer\n  // internals.\n  __isSuspense: true,\n  process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, rendererInternals) {\n    if (n1 == null) {\n      mountSuspense(\n        n2,\n        container,\n        anchor,\n        parentComponent,\n        parentSuspense,\n        namespace,\n        slotScopeIds,\n        optimized,\n        rendererInternals\n      );\n    } else {\n      if (parentSuspense && parentSuspense.deps > 0 && !n1.suspense.isInFallback) {\n        n2.suspense = n1.suspense;\n        n2.suspense.vnode = n2;\n        n2.el = n1.el;\n        return;\n      }\n      patchSuspense(\n        n1,\n        n2,\n        container,\n        anchor,\n        parentComponent,\n        namespace,\n        slotScopeIds,\n        optimized,\n        rendererInternals\n      );\n    }\n  },\n  hydrate: hydrateSuspense,\n  normalize: normalizeSuspenseChildren\n};\nconst Suspense = SuspenseImpl ;\nfunction triggerEvent(vnode, name) {\n  const eventListener = vnode.props && vnode.props[name];\n  if (isFunction(eventListener)) {\n    eventListener();\n  }\n}\nfunction mountSuspense(vnode, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, rendererInternals) {\n  const {\n    p: patch,\n    o: { createElement }\n  } = rendererInternals;\n  const hiddenContainer = createElement(\"div\");\n  const suspense = vnode.suspense = createSuspenseBoundary(\n    vnode,\n    parentSuspense,\n    parentComponent,\n    container,\n    hiddenContainer,\n    anchor,\n    namespace,\n    slotScopeIds,\n    optimized,\n    rendererInternals\n  );\n  patch(\n    null,\n    suspense.pendingBranch = vnode.ssContent,\n    hiddenContainer,\n    null,\n    parentComponent,\n    suspense,\n    namespace,\n    slotScopeIds\n  );\n  if (suspense.deps > 0) {\n    triggerEvent(vnode, \"onPending\");\n    triggerEvent(vnode, \"onFallback\");\n    patch(\n      null,\n      vnode.ssFallback,\n      container,\n      anchor,\n      parentComponent,\n      null,\n      // fallback tree will not have suspense context\n      namespace,\n      slotScopeIds\n    );\n    setActiveBranch(suspense, vnode.ssFallback);\n  } else {\n    suspense.resolve(false, true);\n  }\n}\nfunction patchSuspense(n1, n2, container, anchor, parentComponent, namespace, slotScopeIds, optimized, { p: patch, um: unmount, o: { createElement } }) {\n  const suspense = n2.suspense = n1.suspense;\n  suspense.vnode = n2;\n  n2.el = n1.el;\n  const newBranch = n2.ssContent;\n  const newFallback = n2.ssFallback;\n  const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense;\n  if (pendingBranch) {\n    suspense.pendingBranch = newBranch;\n    if (isSameVNodeType(newBranch, pendingBranch)) {\n      patch(\n        pendingBranch,\n        newBranch,\n        suspense.hiddenContainer,\n        null,\n        parentComponent,\n        suspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n      if (suspense.deps <= 0) {\n        suspense.resolve();\n      } else if (isInFallback) {\n        if (!isHydrating) {\n          patch(\n            activeBranch,\n            newFallback,\n            container,\n            anchor,\n            parentComponent,\n            null,\n            // fallback tree will not have suspense context\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n          setActiveBranch(suspense, newFallback);\n        }\n      }\n    } else {\n      suspense.pendingId = suspenseId++;\n      if (isHydrating) {\n        suspense.isHydrating = false;\n        suspense.activeBranch = pendingBranch;\n      } else {\n        unmount(pendingBranch, parentComponent, suspense);\n      }\n      suspense.deps = 0;\n      suspense.effects.length = 0;\n      suspense.hiddenContainer = createElement(\"div\");\n      if (isInFallback) {\n        patch(\n          null,\n          newBranch,\n          suspense.hiddenContainer,\n          null,\n          parentComponent,\n          suspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n        if (suspense.deps <= 0) {\n          suspense.resolve();\n        } else {\n          patch(\n            activeBranch,\n            newFallback,\n            container,\n            anchor,\n            parentComponent,\n            null,\n            // fallback tree will not have suspense context\n            namespace,\n            slotScopeIds,\n            optimized\n          );\n          setActiveBranch(suspense, newFallback);\n        }\n      } else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {\n        patch(\n          activeBranch,\n          newBranch,\n          container,\n          anchor,\n          parentComponent,\n          suspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n        suspense.resolve(true);\n      } else {\n        patch(\n          null,\n          newBranch,\n          suspense.hiddenContainer,\n          null,\n          parentComponent,\n          suspense,\n          namespace,\n          slotScopeIds,\n          optimized\n        );\n        if (suspense.deps <= 0) {\n          suspense.resolve();\n        }\n      }\n    }\n  } else {\n    if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {\n      patch(\n        activeBranch,\n        newBranch,\n        container,\n        anchor,\n        parentComponent,\n        suspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n      setActiveBranch(suspense, newBranch);\n    } else {\n      triggerEvent(n2, \"onPending\");\n      suspense.pendingBranch = newBranch;\n      if (newBranch.shapeFlag & 512) {\n        suspense.pendingId = newBranch.component.suspenseId;\n      } else {\n        suspense.pendingId = suspenseId++;\n      }\n      patch(\n        null,\n        newBranch,\n        suspense.hiddenContainer,\n        null,\n        parentComponent,\n        suspense,\n        namespace,\n        slotScopeIds,\n        optimized\n      );\n      if (suspense.deps <= 0) {\n        suspense.resolve();\n      } else {\n        const { timeout, pendingId } = suspense;\n        if (timeout > 0) {\n          setTimeout(() => {\n            if (suspense.pendingId === pendingId) {\n              suspense.fallback(newFallback);\n            }\n          }, timeout);\n        } else if (timeout === 0) {\n          suspense.fallback(newFallback);\n        }\n      }\n    }\n  }\n}\nlet hasWarned = false;\nfunction createSuspenseBoundary(vnode, parentSuspense, parentComponent, container, hiddenContainer, anchor, namespace, slotScopeIds, optimized, rendererInternals, isHydrating = false) {\n  if (!hasWarned) {\n    hasWarned = true;\n    console[console.info ? \"info\" : \"log\"](\n      `<Suspense> is an experimental feature and its API will likely change.`\n    );\n  }\n  const {\n    p: patch,\n    m: move,\n    um: unmount,\n    n: next,\n    o: { parentNode, remove }\n  } = rendererInternals;\n  let parentSuspenseId;\n  const isSuspensible = isVNodeSuspensible(vnode);\n  if (isSuspensible) {\n    if (parentSuspense && parentSuspense.pendingBranch) {\n      parentSuspenseId = parentSuspense.pendingId;\n      parentSuspense.deps++;\n    }\n  }\n  const timeout = vnode.props ? toNumber(vnode.props.timeout) : void 0;\n  {\n    assertNumber(timeout, `Suspense timeout`);\n  }\n  const initialAnchor = anchor;\n  const suspense = {\n    vnode,\n    parent: parentSuspense,\n    parentComponent,\n    namespace,\n    container,\n    hiddenContainer,\n    deps: 0,\n    pendingId: suspenseId++,\n    timeout: typeof timeout === \"number\" ? timeout : -1,\n    activeBranch: null,\n    pendingBranch: null,\n    isInFallback: !isHydrating,\n    isHydrating,\n    isUnmounted: false,\n    effects: [],\n    resolve(resume = false, sync = false) {\n      {\n        if (!resume && !suspense.pendingBranch) {\n          throw new Error(\n            `suspense.resolve() is called without a pending branch.`\n          );\n        }\n        if (suspense.isUnmounted) {\n          throw new Error(\n            `suspense.resolve() is called on an already unmounted suspense boundary.`\n          );\n        }\n      }\n      const {\n        vnode: vnode2,\n        activeBranch,\n        pendingBranch,\n        pendingId,\n        effects,\n        parentComponent: parentComponent2,\n        container: container2\n      } = suspense;\n      let delayEnter = false;\n      if (suspense.isHydrating) {\n        suspense.isHydrating = false;\n      } else if (!resume) {\n        delayEnter = activeBranch && pendingBranch.transition && pendingBranch.transition.mode === \"out-in\";\n        if (delayEnter) {\n          activeBranch.transition.afterLeave = () => {\n            if (pendingId === suspense.pendingId) {\n              move(\n                pendingBranch,\n                container2,\n                anchor === initialAnchor ? next(activeBranch) : anchor,\n                0\n              );\n              queuePostFlushCb(effects);\n            }\n          };\n        }\n        if (activeBranch) {\n          if (parentNode(activeBranch.el) !== suspense.hiddenContainer) {\n            anchor = next(activeBranch);\n          }\n          unmount(activeBranch, parentComponent2, suspense, true);\n        }\n        if (!delayEnter) {\n          move(pendingBranch, container2, anchor, 0);\n        }\n      }\n      setActiveBranch(suspense, pendingBranch);\n      suspense.pendingBranch = null;\n      suspense.isInFallback = false;\n      let parent = suspense.parent;\n      let hasUnresolvedAncestor = false;\n      while (parent) {\n        if (parent.pendingBranch) {\n          parent.effects.push(...effects);\n          hasUnresolvedAncestor = true;\n          break;\n        }\n        parent = parent.parent;\n      }\n      if (!hasUnresolvedAncestor && !delayEnter) {\n        queuePostFlushCb(effects);\n      }\n      suspense.effects = [];\n      if (isSuspensible) {\n        if (parentSuspense && parentSuspense.pendingBranch && parentSuspenseId === parentSuspense.pendingId) {\n          parentSuspense.deps--;\n          if (parentSuspense.deps === 0 && !sync) {\n            parentSuspense.resolve();\n          }\n        }\n      }\n      triggerEvent(vnode2, \"onResolve\");\n    },\n    fallback(fallbackVNode) {\n      if (!suspense.pendingBranch) {\n        return;\n      }\n      const { vnode: vnode2, activeBranch, parentComponent: parentComponent2, container: container2, namespace: namespace2 } = suspense;\n      triggerEvent(vnode2, \"onFallback\");\n      const anchor2 = next(activeBranch);\n      const mountFallback = () => {\n        if (!suspense.isInFallback) {\n          return;\n        }\n        patch(\n          null,\n          fallbackVNode,\n          container2,\n          anchor2,\n          parentComponent2,\n          null,\n          // fallback tree will not have suspense context\n          namespace2,\n          slotScopeIds,\n          optimized\n        );\n        setActiveBranch(suspense, fallbackVNode);\n      };\n      const delayEnter = fallbackVNode.transition && fallbackVNode.transition.mode === \"out-in\";\n      if (delayEnter) {\n        activeBranch.transition.afterLeave = mountFallback;\n      }\n      suspense.isInFallback = true;\n      unmount(\n        activeBranch,\n        parentComponent2,\n        null,\n        // no suspense so unmount hooks fire now\n        true\n        // shouldRemove\n      );\n      if (!delayEnter) {\n        mountFallback();\n      }\n    },\n    move(container2, anchor2, type) {\n      suspense.activeBranch && move(suspense.activeBranch, container2, anchor2, type);\n      suspense.container = container2;\n    },\n    next() {\n      return suspense.activeBranch && next(suspense.activeBranch);\n    },\n    registerDep(instance, setupRenderEffect, optimized2) {\n      const isInPendingSuspense = !!suspense.pendingBranch;\n      if (isInPendingSuspense) {\n        suspense.deps++;\n      }\n      const hydratedEl = instance.vnode.el;\n      instance.asyncDep.catch((err) => {\n        handleError(err, instance, 0);\n      }).then((asyncSetupResult) => {\n        if (instance.isUnmounted || suspense.isUnmounted || suspense.pendingId !== instance.suspenseId) {\n          return;\n        }\n        instance.asyncResolved = true;\n        const { vnode: vnode2 } = instance;\n        {\n          pushWarningContext(vnode2);\n        }\n        handleSetupResult(instance, asyncSetupResult, false);\n        if (hydratedEl) {\n          vnode2.el = hydratedEl;\n        }\n        const placeholder = !hydratedEl && instance.subTree.el;\n        setupRenderEffect(\n          instance,\n          vnode2,\n          // component may have been moved before resolve.\n          // if this is not a hydration, instance.subTree will be the comment\n          // placeholder.\n          parentNode(hydratedEl || instance.subTree.el),\n          // anchor will not be used if this is hydration, so only need to\n          // consider the comment placeholder case.\n          hydratedEl ? null : next(instance.subTree),\n          suspense,\n          namespace,\n          optimized2\n        );\n        if (placeholder) {\n          remove(placeholder);\n        }\n        updateHOCHostEl(instance, vnode2.el);\n        {\n          popWarningContext();\n        }\n        if (isInPendingSuspense && --suspense.deps === 0) {\n          suspense.resolve();\n        }\n      });\n    },\n    unmount(parentSuspense2, doRemove) {\n      suspense.isUnmounted = true;\n      if (suspense.activeBranch) {\n        unmount(\n          suspense.activeBranch,\n          parentComponent,\n          parentSuspense2,\n          doRemove\n        );\n      }\n      if (suspense.pendingBranch) {\n        unmount(\n          suspense.pendingBranch,\n          parentComponent,\n          parentSuspense2,\n          doRemove\n        );\n      }\n    }\n  };\n  return suspense;\n}\nfunction hydrateSuspense(node, vnode, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, rendererInternals, hydrateNode) {\n  const suspense = vnode.suspense = createSuspenseBoundary(\n    vnode,\n    parentSuspense,\n    parentComponent,\n    node.parentNode,\n    // eslint-disable-next-line no-restricted-globals\n    document.createElement(\"div\"),\n    null,\n    namespace,\n    slotScopeIds,\n    optimized,\n    rendererInternals,\n    true\n  );\n  const result = hydrateNode(\n    node,\n    suspense.pendingBranch = vnode.ssContent,\n    parentComponent,\n    suspense,\n    slotScopeIds,\n    optimized\n  );\n  if (suspense.deps === 0) {\n    suspense.resolve(false, true);\n  }\n  return result;\n}\nfunction normalizeSuspenseChildren(vnode) {\n  const { shapeFlag, children } = vnode;\n  const isSlotChildren = shapeFlag & 32;\n  vnode.ssContent = normalizeSuspenseSlot(\n    isSlotChildren ? children.default : children\n  );\n  vnode.ssFallback = isSlotChildren ? normalizeSuspenseSlot(children.fallback) : createVNode(Comment);\n}\nfunction normalizeSuspenseSlot(s) {\n  let block;\n  if (isFunction(s)) {\n    const trackBlock = isBlockTreeEnabled && s._c;\n    if (trackBlock) {\n      s._d = false;\n      openBlock();\n    }\n    s = s();\n    if (trackBlock) {\n      s._d = true;\n      block = currentBlock;\n      closeBlock();\n    }\n  }\n  if (isArray(s)) {\n    const singleChild = filterSingleRoot(s);\n    if (!singleChild && s.filter((child) => child !== NULL_DYNAMIC_COMPONENT).length > 0) {\n      warn$1(`<Suspense> slots expect a single root node.`);\n    }\n    s = singleChild;\n  }\n  s = normalizeVNode(s);\n  if (block && !s.dynamicChildren) {\n    s.dynamicChildren = block.filter((c) => c !== s);\n  }\n  return s;\n}\nfunction queueEffectWithSuspense(fn, suspense) {\n  if (suspense && suspense.pendingBranch) {\n    if (isArray(fn)) {\n      suspense.effects.push(...fn);\n    } else {\n      suspense.effects.push(fn);\n    }\n  } else {\n    queuePostFlushCb(fn);\n  }\n}\nfunction setActiveBranch(suspense, branch) {\n  suspense.activeBranch = branch;\n  const { vnode, parentComponent } = suspense;\n  let el = branch.el;\n  while (!el && branch.component) {\n    branch = branch.component.subTree;\n    el = branch.el;\n  }\n  vnode.el = el;\n  if (parentComponent && parentComponent.subTree === vnode) {\n    parentComponent.vnode.el = el;\n    updateHOCHostEl(parentComponent, el);\n  }\n}\nfunction isVNodeSuspensible(vnode) {\n  const suspensible = vnode.props && vnode.props.suspensible;\n  return suspensible != null && suspensible !== false;\n}\n\nconst Fragment = Symbol.for(\"v-fgt\");\nconst Text = Symbol.for(\"v-txt\");\nconst Comment = Symbol.for(\"v-cmt\");\nconst Static = Symbol.for(\"v-stc\");\nconst blockStack = [];\nlet currentBlock = null;\nfunction openBlock(disableTracking = false) {\n  blockStack.push(currentBlock = disableTracking ? null : []);\n}\nfunction closeBlock() {\n  blockStack.pop();\n  currentBlock = blockStack[blockStack.length - 1] || null;\n}\nlet isBlockTreeEnabled = 1;\nfunction setBlockTracking(value) {\n  isBlockTreeEnabled += value;\n  if (value < 0 && currentBlock) {\n    currentBlock.hasOnce = true;\n  }\n}\nfunction setupBlock(vnode) {\n  vnode.dynamicChildren = isBlockTreeEnabled > 0 ? currentBlock || EMPTY_ARR : null;\n  closeBlock();\n  if (isBlockTreeEnabled > 0 && currentBlock) {\n    currentBlock.push(vnode);\n  }\n  return vnode;\n}\nfunction createElementBlock(type, props, children, patchFlag, dynamicProps, shapeFlag) {\n  return setupBlock(\n    createBaseVNode(\n      type,\n      props,\n      children,\n      patchFlag,\n      dynamicProps,\n      shapeFlag,\n      true\n    )\n  );\n}\nfunction createBlock(type, props, children, patchFlag, dynamicProps) {\n  return setupBlock(\n    createVNode(\n      type,\n      props,\n      children,\n      patchFlag,\n      dynamicProps,\n      true\n    )\n  );\n}\nfunction isVNode(value) {\n  return value ? value.__v_isVNode === true : false;\n}\nfunction isSameVNodeType(n1, n2) {\n  if (n2.shapeFlag & 6 && n1.component) {\n    const dirtyInstances = hmrDirtyComponents.get(n2.type);\n    if (dirtyInstances && dirtyInstances.has(n1.component)) {\n      n1.shapeFlag &= ~256;\n      n2.shapeFlag &= ~512;\n      return false;\n    }\n  }\n  return n1.type === n2.type && n1.key === n2.key;\n}\nlet vnodeArgsTransformer;\nfunction transformVNodeArgs(transformer) {\n  vnodeArgsTransformer = transformer;\n}\nconst createVNodeWithArgsTransform = (...args) => {\n  return _createVNode(\n    ...vnodeArgsTransformer ? vnodeArgsTransformer(args, currentRenderingInstance) : args\n  );\n};\nconst normalizeKey = ({ key }) => key != null ? key : null;\nconst normalizeRef = ({\n  ref,\n  ref_key,\n  ref_for\n}) => {\n  if (typeof ref === \"number\") {\n    ref = \"\" + ref;\n  }\n  return ref != null ? isString(ref) || isRef(ref) || isFunction(ref) ? { i: currentRenderingInstance, r: ref, k: ref_key, f: !!ref_for } : ref : null;\n};\nfunction createBaseVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, shapeFlag = type === Fragment ? 0 : 1, isBlockNode = false, needFullChildrenNormalization = false) {\n  const vnode = {\n    __v_isVNode: true,\n    __v_skip: true,\n    type,\n    props,\n    key: props && normalizeKey(props),\n    ref: props && normalizeRef(props),\n    scopeId: currentScopeId,\n    slotScopeIds: null,\n    children,\n    component: null,\n    suspense: null,\n    ssContent: null,\n    ssFallback: null,\n    dirs: null,\n    transition: null,\n    el: null,\n    anchor: null,\n    target: null,\n    targetStart: null,\n    targetAnchor: null,\n    staticCount: 0,\n    shapeFlag,\n    patchFlag,\n    dynamicProps,\n    dynamicChildren: null,\n    appContext: null,\n    ctx: currentRenderingInstance\n  };\n  if (needFullChildrenNormalization) {\n    normalizeChildren(vnode, children);\n    if (shapeFlag & 128) {\n      type.normalize(vnode);\n    }\n  } else if (children) {\n    vnode.shapeFlag |= isString(children) ? 8 : 16;\n  }\n  if (vnode.key !== vnode.key) {\n    warn$1(`VNode created with invalid key (NaN). VNode type:`, vnode.type);\n  }\n  if (isBlockTreeEnabled > 0 && // avoid a block node from tracking itself\n  !isBlockNode && // has current parent block\n  currentBlock && // presence of a patch flag indicates this node needs patching on updates.\n  // component nodes also should always be patched, because even if the\n  // component doesn't need to update, it needs to persist the instance on to\n  // the next vnode so that it can be properly unmounted later.\n  (vnode.patchFlag > 0 || shapeFlag & 6) && // the EVENTS flag is only for hydration and if it is the only flag, the\n  // vnode should not be considered dynamic due to handler caching.\n  vnode.patchFlag !== 32) {\n    currentBlock.push(vnode);\n  }\n  return vnode;\n}\nconst createVNode = createVNodeWithArgsTransform ;\nfunction _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {\n  if (!type || type === NULL_DYNAMIC_COMPONENT) {\n    if (!type) {\n      warn$1(`Invalid vnode type when creating vnode: ${type}.`);\n    }\n    type = Comment;\n  }\n  if (isVNode(type)) {\n    const cloned = cloneVNode(\n      type,\n      props,\n      true\n      /* mergeRef: true */\n    );\n    if (children) {\n      normalizeChildren(cloned, children);\n    }\n    if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {\n      if (cloned.shapeFlag & 6) {\n        currentBlock[currentBlock.indexOf(type)] = cloned;\n      } else {\n        currentBlock.push(cloned);\n      }\n    }\n    cloned.patchFlag = -2;\n    return cloned;\n  }\n  if (isClassComponent(type)) {\n    type = type.__vccOpts;\n  }\n  if (props) {\n    props = guardReactiveProps(props);\n    let { class: klass, style } = props;\n    if (klass && !isString(klass)) {\n      props.class = normalizeClass(klass);\n    }\n    if (isObject(style)) {\n      if (isProxy(style) && !isArray(style)) {\n        style = extend({}, style);\n      }\n      props.style = normalizeStyle(style);\n    }\n  }\n  const shapeFlag = isString(type) ? 1 : isSuspense(type) ? 128 : isTeleport(type) ? 64 : isObject(type) ? 4 : isFunction(type) ? 2 : 0;\n  if (shapeFlag & 4 && isProxy(type)) {\n    type = toRaw(type);\n    warn$1(\n      `Vue received a Component that was made a reactive object. This can lead to unnecessary performance overhead and should be avoided by marking the component with \\`markRaw\\` or using \\`shallowRef\\` instead of \\`ref\\`.`,\n      `\nComponent that was made reactive: `,\n      type\n    );\n  }\n  return createBaseVNode(\n    type,\n    props,\n    children,\n    patchFlag,\n    dynamicProps,\n    shapeFlag,\n    isBlockNode,\n    true\n  );\n}\nfunction guardReactiveProps(props) {\n  if (!props) return null;\n  return isProxy(props) || isInternalObject(props) ? extend({}, props) : props;\n}\nfunction cloneVNode(vnode, extraProps, mergeRef = false, cloneTransition = false) {\n  const { props, ref, patchFlag, children, transition } = vnode;\n  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;\n  const cloned = {\n    __v_isVNode: true,\n    __v_skip: true,\n    type: vnode.type,\n    props: mergedProps,\n    key: mergedProps && normalizeKey(mergedProps),\n    ref: extraProps && extraProps.ref ? (\n      // #2078 in the case of <component :is=\"vnode\" ref=\"extra\"/>\n      // if the vnode itself already has a ref, cloneVNode will need to merge\n      // the refs so the single vnode can be set on multiple refs\n      mergeRef && ref ? isArray(ref) ? ref.concat(normalizeRef(extraProps)) : [ref, normalizeRef(extraProps)] : normalizeRef(extraProps)\n    ) : ref,\n    scopeId: vnode.scopeId,\n    slotScopeIds: vnode.slotScopeIds,\n    children: patchFlag === -1 && isArray(children) ? children.map(deepCloneVNode) : children,\n    target: vnode.target,\n    targetStart: vnode.targetStart,\n    targetAnchor: vnode.targetAnchor,\n    staticCount: vnode.staticCount,\n    shapeFlag: vnode.shapeFlag,\n    // if the vnode is cloned with extra props, we can no longer assume its\n    // existing patch flag to be reliable and need to add the FULL_PROPS flag.\n    // note: preserve flag for fragments since they use the flag for children\n    // fast paths only.\n    patchFlag: extraProps && vnode.type !== Fragment ? patchFlag === -1 ? 16 : patchFlag | 16 : patchFlag,\n    dynamicProps: vnode.dynamicProps,\n    dynamicChildren: vnode.dynamicChildren,\n    appContext: vnode.appContext,\n    dirs: vnode.dirs,\n    transition,\n    // These should technically only be non-null on mounted VNodes. However,\n    // they *should* be copied for kept-alive vnodes. So we just always copy\n    // them since them being non-null during a mount doesn't affect the logic as\n    // they will simply be overwritten.\n    component: vnode.component,\n    suspense: vnode.suspense,\n    ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),\n    ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),\n    el: vnode.el,\n    anchor: vnode.anchor,\n    ctx: vnode.ctx,\n    ce: vnode.ce\n  };\n  if (transition && cloneTransition) {\n    setTransitionHooks(\n      cloned,\n      transition.clone(cloned)\n    );\n  }\n  return cloned;\n}\nfunction deepCloneVNode(vnode) {\n  const cloned = cloneVNode(vnode);\n  if (isArray(vnode.children)) {\n    cloned.children = vnode.children.map(deepCloneVNode);\n  }\n  return cloned;\n}\nfunction createTextVNode(text = \" \", flag = 0) {\n  return createVNode(Text, null, text, flag);\n}\nfunction createStaticVNode(content, numberOfNodes) {\n  const vnode = createVNode(Static, null, content);\n  vnode.staticCount = numberOfNodes;\n  return vnode;\n}\nfunction createCommentVNode(text = \"\", asBlock = false) {\n  return asBlock ? (openBlock(), createBlock(Comment, null, text)) : createVNode(Comment, null, text);\n}\nfunction normalizeVNode(child) {\n  if (child == null || typeof child === \"boolean\") {\n    return createVNode(Comment);\n  } else if (isArray(child)) {\n    return createVNode(\n      Fragment,\n      null,\n      // #3666, avoid reference pollution when reusing vnode\n      child.slice()\n    );\n  } else if (typeof child === \"object\") {\n    return cloneIfMounted(child);\n  } else {\n    return createVNode(Text, null, String(child));\n  }\n}\nfunction cloneIfMounted(child) {\n  return child.el === null && child.patchFlag !== -1 || child.memo ? child : cloneVNode(child);\n}\nfunction normalizeChildren(vnode, children) {\n  let type = 0;\n  const { shapeFlag } = vnode;\n  if (children == null) {\n    children = null;\n  } else if (isArray(children)) {\n    type = 16;\n  } else if (typeof children === \"object\") {\n    if (shapeFlag & (1 | 64)) {\n      const slot = children.default;\n      if (slot) {\n        slot._c && (slot._d = false);\n        normalizeChildren(vnode, slot());\n        slot._c && (slot._d = true);\n      }\n      return;\n    } else {\n      type = 32;\n      const slotFlag = children._;\n      if (!slotFlag && !isInternalObject(children)) {\n        children._ctx = currentRenderingInstance;\n      } else if (slotFlag === 3 && currentRenderingInstance) {\n        if (currentRenderingInstance.slots._ === 1) {\n          children._ = 1;\n        } else {\n          children._ = 2;\n          vnode.patchFlag |= 1024;\n        }\n      }\n    }\n  } else if (isFunction(children)) {\n    children = { default: children, _ctx: currentRenderingInstance };\n    type = 32;\n  } else {\n    children = String(children);\n    if (shapeFlag & 64) {\n      type = 16;\n      children = [createTextVNode(children)];\n    } else {\n      type = 8;\n    }\n  }\n  vnode.children = children;\n  vnode.shapeFlag |= type;\n}\nfunction mergeProps(...args) {\n  const ret = {};\n  for (let i = 0; i < args.length; i++) {\n    const toMerge = args[i];\n    for (const key in toMerge) {\n      if (key === \"class\") {\n        if (ret.class !== toMerge.class) {\n          ret.class = normalizeClass([ret.class, toMerge.class]);\n        }\n      } else if (key === \"style\") {\n        ret.style = normalizeStyle([ret.style, toMerge.style]);\n      } else if (isOn(key)) {\n        const existing = ret[key];\n        const incoming = toMerge[key];\n        if (incoming && existing !== incoming && !(isArray(existing) && existing.includes(incoming))) {\n          ret[key] = existing ? [].concat(existing, incoming) : incoming;\n        }\n      } else if (key !== \"\") {\n        ret[key] = toMerge[key];\n      }\n    }\n  }\n  return ret;\n}\nfunction invokeVNodeHook(hook, instance, vnode, prevVNode = null) {\n  callWithAsyncErrorHandling(hook, instance, 7, [\n    vnode,\n    prevVNode\n  ]);\n}\n\nconst emptyAppContext = createAppContext();\nlet uid = 0;\nfunction createComponentInstance(vnode, parent, suspense) {\n  const type = vnode.type;\n  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;\n  const instance = {\n    uid: uid++,\n    vnode,\n    type,\n    parent,\n    appContext,\n    root: null,\n    // to be immediately set\n    next: null,\n    subTree: null,\n    // will be set synchronously right after creation\n    effect: null,\n    update: null,\n    // will be set synchronously right after creation\n    scope: new EffectScope(\n      true\n      /* detached */\n    ),\n    render: null,\n    proxy: null,\n    exposed: null,\n    exposeProxy: null,\n    withProxy: null,\n    provides: parent ? parent.provides : Object.create(appContext.provides),\n    accessCache: null,\n    renderCache: [],\n    // local resolved assets\n    components: null,\n    directives: null,\n    // resolved props and emits options\n    propsOptions: normalizePropsOptions(type, appContext),\n    emitsOptions: normalizeEmitsOptions(type, appContext),\n    // emit\n    emit: null,\n    // to be set immediately\n    emitted: null,\n    // props default value\n    propsDefaults: EMPTY_OBJ,\n    // inheritAttrs\n    inheritAttrs: type.inheritAttrs,\n    // state\n    ctx: EMPTY_OBJ,\n    data: EMPTY_OBJ,\n    props: EMPTY_OBJ,\n    attrs: EMPTY_OBJ,\n    slots: EMPTY_OBJ,\n    refs: EMPTY_OBJ,\n    setupState: EMPTY_OBJ,\n    setupContext: null,\n    // suspense related\n    suspense,\n    suspenseId: suspense ? suspense.pendingId : 0,\n    asyncDep: null,\n    asyncResolved: false,\n    // lifecycle hooks\n    // not using enums here because it results in computed properties\n    isMounted: false,\n    isUnmounted: false,\n    isDeactivated: false,\n    bc: null,\n    c: null,\n    bm: null,\n    m: null,\n    bu: null,\n    u: null,\n    um: null,\n    bum: null,\n    da: null,\n    a: null,\n    rtg: null,\n    rtc: null,\n    ec: null,\n    sp: null\n  };\n  {\n    instance.ctx = createDevRenderContext(instance);\n  }\n  instance.root = parent ? parent.root : instance;\n  instance.emit = emit.bind(null, instance);\n  if (vnode.ce) {\n    vnode.ce(instance);\n  }\n  return instance;\n}\nlet currentInstance = null;\nconst getCurrentInstance = () => currentInstance || currentRenderingInstance;\nlet internalSetCurrentInstance;\nlet setInSSRSetupState;\n{\n  internalSetCurrentInstance = (i) => {\n    currentInstance = i;\n  };\n  setInSSRSetupState = (v) => {\n    isInSSRComponentSetup = v;\n  };\n}\nconst setCurrentInstance = (instance) => {\n  const prev = currentInstance;\n  internalSetCurrentInstance(instance);\n  instance.scope.on();\n  return () => {\n    instance.scope.off();\n    internalSetCurrentInstance(prev);\n  };\n};\nconst unsetCurrentInstance = () => {\n  currentInstance && currentInstance.scope.off();\n  internalSetCurrentInstance(null);\n};\nconst isBuiltInTag = /* @__PURE__ */ makeMap(\"slot,component\");\nfunction validateComponentName(name, { isNativeTag }) {\n  if (isBuiltInTag(name) || isNativeTag(name)) {\n    warn$1(\n      \"Do not use built-in or reserved HTML elements as component id: \" + name\n    );\n  }\n}\nfunction isStatefulComponent(instance) {\n  return instance.vnode.shapeFlag & 4;\n}\nlet isInSSRComponentSetup = false;\nfunction setupComponent(instance, isSSR = false, optimized = false) {\n  isSSR && setInSSRSetupState(isSSR);\n  const { props, children } = instance.vnode;\n  const isStateful = isStatefulComponent(instance);\n  initProps(instance, props, isStateful, isSSR);\n  initSlots(instance, children, optimized);\n  const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : void 0;\n  isSSR && setInSSRSetupState(false);\n  return setupResult;\n}\nfunction setupStatefulComponent(instance, isSSR) {\n  var _a;\n  const Component = instance.type;\n  {\n    if (Component.name) {\n      validateComponentName(Component.name, instance.appContext.config);\n    }\n    if (Component.components) {\n      const names = Object.keys(Component.components);\n      for (let i = 0; i < names.length; i++) {\n        validateComponentName(names[i], instance.appContext.config);\n      }\n    }\n    if (Component.directives) {\n      const names = Object.keys(Component.directives);\n      for (let i = 0; i < names.length; i++) {\n        validateDirectiveName(names[i]);\n      }\n    }\n    if (Component.compilerOptions && isRuntimeOnly()) {\n      warn$1(\n        `\"compilerOptions\" is only supported when using a build of Vue that includes the runtime compiler. Since you are using a runtime-only build, the options should be passed via your build tool config instead.`\n      );\n    }\n  }\n  instance.accessCache = /* @__PURE__ */ Object.create(null);\n  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);\n  {\n    exposePropsOnRenderContext(instance);\n  }\n  const { setup } = Component;\n  if (setup) {\n    const setupContext = instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null;\n    const reset = setCurrentInstance(instance);\n    pauseTracking();\n    const setupResult = callWithErrorHandling(\n      setup,\n      instance,\n      0,\n      [\n        shallowReadonly(instance.props) ,\n        setupContext\n      ]\n    );\n    resetTracking();\n    reset();\n    if (isPromise(setupResult)) {\n      setupResult.then(unsetCurrentInstance, unsetCurrentInstance);\n      if (isSSR) {\n        return setupResult.then((resolvedResult) => {\n          handleSetupResult(instance, resolvedResult, isSSR);\n        }).catch((e) => {\n          handleError(e, instance, 0);\n        });\n      } else {\n        instance.asyncDep = setupResult;\n        if (!instance.suspense) {\n          const name = (_a = Component.name) != null ? _a : \"Anonymous\";\n          warn$1(\n            `Component <${name}>: setup function returned a promise, but no <Suspense> boundary was found in the parent component tree. A component with async setup() must be nested in a <Suspense> in order to be rendered.`\n          );\n        }\n      }\n    } else {\n      handleSetupResult(instance, setupResult, isSSR);\n    }\n  } else {\n    finishComponentSetup(instance, isSSR);\n  }\n}\nfunction handleSetupResult(instance, setupResult, isSSR) {\n  if (isFunction(setupResult)) {\n    {\n      instance.render = setupResult;\n    }\n  } else if (isObject(setupResult)) {\n    if (isVNode(setupResult)) {\n      warn$1(\n        `setup() should not return VNodes directly - return a render function instead.`\n      );\n    }\n    {\n      instance.devtoolsRawSetupState = setupResult;\n    }\n    instance.setupState = proxyRefs(setupResult);\n    {\n      exposeSetupStateOnRenderContext(instance);\n    }\n  } else if (setupResult !== void 0) {\n    warn$1(\n      `setup() should return an object. Received: ${setupResult === null ? \"null\" : typeof setupResult}`\n    );\n  }\n  finishComponentSetup(instance, isSSR);\n}\nlet compile$1;\nlet installWithProxy;\nfunction registerRuntimeCompiler(_compile) {\n  compile$1 = _compile;\n  installWithProxy = (i) => {\n    if (i.render._rc) {\n      i.withProxy = new Proxy(i.ctx, RuntimeCompiledPublicInstanceProxyHandlers);\n    }\n  };\n}\nconst isRuntimeOnly = () => !compile$1;\nfunction finishComponentSetup(instance, isSSR, skipOptions) {\n  const Component = instance.type;\n  if (!instance.render) {\n    if (!isSSR && compile$1 && !Component.render) {\n      const template = Component.template || resolveMergedOptions(instance).template;\n      if (template) {\n        {\n          startMeasure(instance, `compile`);\n        }\n        const { isCustomElement, compilerOptions } = instance.appContext.config;\n        const { delimiters, compilerOptions: componentCompilerOptions } = Component;\n        const finalCompilerOptions = extend(\n          extend(\n            {\n              isCustomElement,\n              delimiters\n            },\n            compilerOptions\n          ),\n          componentCompilerOptions\n        );\n        Component.render = compile$1(template, finalCompilerOptions);\n        {\n          endMeasure(instance, `compile`);\n        }\n      }\n    }\n    instance.render = Component.render || NOOP;\n    if (installWithProxy) {\n      installWithProxy(instance);\n    }\n  }\n  {\n    const reset = setCurrentInstance(instance);\n    pauseTracking();\n    try {\n      applyOptions(instance);\n    } finally {\n      resetTracking();\n      reset();\n    }\n  }\n  if (!Component.render && instance.render === NOOP && !isSSR) {\n    if (!compile$1 && Component.template) {\n      warn$1(\n        `Component provided template option but runtime compilation is not supported in this build of Vue.` + (` Use \"vue.esm-browser.js\" instead.` )\n      );\n    } else {\n      warn$1(`Component is missing template or render function: `, Component);\n    }\n  }\n}\nconst attrsProxyHandlers = {\n  get(target, key) {\n    markAttrsAccessed();\n    track(target, \"get\", \"\");\n    return target[key];\n  },\n  set() {\n    warn$1(`setupContext.attrs is readonly.`);\n    return false;\n  },\n  deleteProperty() {\n    warn$1(`setupContext.attrs is readonly.`);\n    return false;\n  }\n} ;\nfunction getSlotsProxy(instance) {\n  return new Proxy(instance.slots, {\n    get(target, key) {\n      track(instance, \"get\", \"$slots\");\n      return target[key];\n    }\n  });\n}\nfunction createSetupContext(instance) {\n  const expose = (exposed) => {\n    {\n      if (instance.exposed) {\n        warn$1(`expose() should be called only once per setup().`);\n      }\n      if (exposed != null) {\n        let exposedType = typeof exposed;\n        if (exposedType === \"object\") {\n          if (isArray(exposed)) {\n            exposedType = \"array\";\n          } else if (isRef(exposed)) {\n            exposedType = \"ref\";\n          }\n        }\n        if (exposedType !== \"object\") {\n          warn$1(\n            `expose() should be passed a plain object, received ${exposedType}.`\n          );\n        }\n      }\n    }\n    instance.exposed = exposed || {};\n  };\n  {\n    let attrsProxy;\n    let slotsProxy;\n    return Object.freeze({\n      get attrs() {\n        return attrsProxy || (attrsProxy = new Proxy(instance.attrs, attrsProxyHandlers));\n      },\n      get slots() {\n        return slotsProxy || (slotsProxy = getSlotsProxy(instance));\n      },\n      get emit() {\n        return (event, ...args) => instance.emit(event, ...args);\n      },\n      expose\n    });\n  }\n}\nfunction getComponentPublicInstance(instance) {\n  if (instance.exposed) {\n    return instance.exposeProxy || (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {\n      get(target, key) {\n        if (key in target) {\n          return target[key];\n        } else if (key in publicPropertiesMap) {\n          return publicPropertiesMap[key](instance);\n        }\n      },\n      has(target, key) {\n        return key in target || key in publicPropertiesMap;\n      }\n    }));\n  } else {\n    return instance.proxy;\n  }\n}\nconst classifyRE = /(?:^|[-_])(\\w)/g;\nconst classify = (str) => str.replace(classifyRE, (c) => c.toUpperCase()).replace(/[-_]/g, \"\");\nfunction getComponentName(Component, includeInferred = true) {\n  return isFunction(Component) ? Component.displayName || Component.name : Component.name || includeInferred && Component.__name;\n}\nfunction formatComponentName(instance, Component, isRoot = false) {\n  let name = getComponentName(Component);\n  if (!name && Component.__file) {\n    const match = Component.__file.match(/([^/\\\\]+)\\.\\w+$/);\n    if (match) {\n      name = match[1];\n    }\n  }\n  if (!name && instance && instance.parent) {\n    const inferFromRegistry = (registry) => {\n      for (const key in registry) {\n        if (registry[key] === Component) {\n          return key;\n        }\n      }\n    };\n    name = inferFromRegistry(\n      instance.components || instance.parent.type.components\n    ) || inferFromRegistry(instance.appContext.components);\n  }\n  return name ? classify(name) : isRoot ? `App` : `Anonymous`;\n}\nfunction isClassComponent(value) {\n  return isFunction(value) && \"__vccOpts\" in value;\n}\n\nconst computed = (getterOrOptions, debugOptions) => {\n  const c = computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);\n  {\n    const i = getCurrentInstance();\n    if (i && i.appContext.config.warnRecursiveComputed) {\n      c._warnRecursive = true;\n    }\n  }\n  return c;\n};\n\nfunction h(type, propsOrChildren, children) {\n  const l = arguments.length;\n  if (l === 2) {\n    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {\n      if (isVNode(propsOrChildren)) {\n        return createVNode(type, null, [propsOrChildren]);\n      }\n      return createVNode(type, propsOrChildren);\n    } else {\n      return createVNode(type, null, propsOrChildren);\n    }\n  } else {\n    if (l > 3) {\n      children = Array.prototype.slice.call(arguments, 2);\n    } else if (l === 3 && isVNode(children)) {\n      children = [children];\n    }\n    return createVNode(type, propsOrChildren, children);\n  }\n}\n\nfunction initCustomFormatter() {\n  if (typeof window === \"undefined\") {\n    return;\n  }\n  const vueStyle = { style: \"color:#3ba776\" };\n  const numberStyle = { style: \"color:#1677ff\" };\n  const stringStyle = { style: \"color:#f5222d\" };\n  const keywordStyle = { style: \"color:#eb2f96\" };\n  const formatter = {\n    __vue_custom_formatter: true,\n    header(obj) {\n      if (!isObject(obj)) {\n        return null;\n      }\n      if (obj.__isVue) {\n        return [\"div\", vueStyle, `VueInstance`];\n      } else if (isRef(obj)) {\n        return [\n          \"div\",\n          {},\n          [\"span\", vueStyle, genRefFlag(obj)],\n          \"<\",\n          formatValue(obj.value),\n          `>`\n        ];\n      } else if (isReactive(obj)) {\n        return [\n          \"div\",\n          {},\n          [\"span\", vueStyle, isShallow(obj) ? \"ShallowReactive\" : \"Reactive\"],\n          \"<\",\n          formatValue(obj),\n          `>${isReadonly(obj) ? ` (readonly)` : ``}`\n        ];\n      } else if (isReadonly(obj)) {\n        return [\n          \"div\",\n          {},\n          [\"span\", vueStyle, isShallow(obj) ? \"ShallowReadonly\" : \"Readonly\"],\n          \"<\",\n          formatValue(obj),\n          \">\"\n        ];\n      }\n      return null;\n    },\n    hasBody(obj) {\n      return obj && obj.__isVue;\n    },\n    body(obj) {\n      if (obj && obj.__isVue) {\n        return [\n          \"div\",\n          {},\n          ...formatInstance(obj.$)\n        ];\n      }\n    }\n  };\n  function formatInstance(instance) {\n    const blocks = [];\n    if (instance.type.props && instance.props) {\n      blocks.push(createInstanceBlock(\"props\", toRaw(instance.props)));\n    }\n    if (instance.setupState !== EMPTY_OBJ) {\n      blocks.push(createInstanceBlock(\"setup\", instance.setupState));\n    }\n    if (instance.data !== EMPTY_OBJ) {\n      blocks.push(createInstanceBlock(\"data\", toRaw(instance.data)));\n    }\n    const computed = extractKeys(instance, \"computed\");\n    if (computed) {\n      blocks.push(createInstanceBlock(\"computed\", computed));\n    }\n    const injected = extractKeys(instance, \"inject\");\n    if (injected) {\n      blocks.push(createInstanceBlock(\"injected\", injected));\n    }\n    blocks.push([\n      \"div\",\n      {},\n      [\n        \"span\",\n        {\n          style: keywordStyle.style + \";opacity:0.66\"\n        },\n        \"$ (internal): \"\n      ],\n      [\"object\", { object: instance }]\n    ]);\n    return blocks;\n  }\n  function createInstanceBlock(type, target) {\n    target = extend({}, target);\n    if (!Object.keys(target).length) {\n      return [\"span\", {}];\n    }\n    return [\n      \"div\",\n      { style: \"line-height:1.25em;margin-bottom:0.6em\" },\n      [\n        \"div\",\n        {\n          style: \"color:#476582\"\n        },\n        type\n      ],\n      [\n        \"div\",\n        {\n          style: \"padding-left:1.25em\"\n        },\n        ...Object.keys(target).map((key) => {\n          return [\n            \"div\",\n            {},\n            [\"span\", keywordStyle, key + \": \"],\n            formatValue(target[key], false)\n          ];\n        })\n      ]\n    ];\n  }\n  function formatValue(v, asRaw = true) {\n    if (typeof v === \"number\") {\n      return [\"span\", numberStyle, v];\n    } else if (typeof v === \"string\") {\n      return [\"span\", stringStyle, JSON.stringify(v)];\n    } else if (typeof v === \"boolean\") {\n      return [\"span\", keywordStyle, v];\n    } else if (isObject(v)) {\n      return [\"object\", { object: asRaw ? toRaw(v) : v }];\n    } else {\n      return [\"span\", stringStyle, String(v)];\n    }\n  }\n  function extractKeys(instance, type) {\n    const Comp = instance.type;\n    if (isFunction(Comp)) {\n      return;\n    }\n    const extracted = {};\n    for (const key in instance.ctx) {\n      if (isKeyOfType(Comp, key, type)) {\n        extracted[key] = instance.ctx[key];\n      }\n    }\n    return extracted;\n  }\n  function isKeyOfType(Comp, key, type) {\n    const opts = Comp[type];\n    if (isArray(opts) && opts.includes(key) || isObject(opts) && key in opts) {\n      return true;\n    }\n    if (Comp.extends && isKeyOfType(Comp.extends, key, type)) {\n      return true;\n    }\n    if (Comp.mixins && Comp.mixins.some((m) => isKeyOfType(m, key, type))) {\n      return true;\n    }\n  }\n  function genRefFlag(v) {\n    if (isShallow(v)) {\n      return `ShallowRef`;\n    }\n    if (v.effect) {\n      return `ComputedRef`;\n    }\n    return `Ref`;\n  }\n  if (window.devtoolsFormatters) {\n    window.devtoolsFormatters.push(formatter);\n  } else {\n    window.devtoolsFormatters = [formatter];\n  }\n}\n\nfunction withMemo(memo, render, cache, index) {\n  const cached = cache[index];\n  if (cached && isMemoSame(cached, memo)) {\n    return cached;\n  }\n  const ret = render();\n  ret.memo = memo.slice();\n  ret.cacheIndex = index;\n  return cache[index] = ret;\n}\nfunction isMemoSame(cached, memo) {\n  const prev = cached.memo;\n  if (prev.length != memo.length) {\n    return false;\n  }\n  for (let i = 0; i < prev.length; i++) {\n    if (hasChanged(prev[i], memo[i])) {\n      return false;\n    }\n  }\n  if (isBlockTreeEnabled > 0 && currentBlock) {\n    currentBlock.push(cached);\n  }\n  return true;\n}\n\nconst version = \"3.4.35\";\nconst warn = warn$1 ;\nconst ErrorTypeStrings = ErrorTypeStrings$1 ;\nconst devtools = devtools$1 ;\nconst setDevtoolsHook = setDevtoolsHook$1 ;\nconst ssrUtils = null;\nconst resolveFilter = null;\nconst compatUtils = null;\nconst DeprecationTypes = null;\n\nconst svgNS = \"http://www.w3.org/2000/svg\";\nconst mathmlNS = \"http://www.w3.org/1998/Math/MathML\";\nconst doc = typeof document !== \"undefined\" ? document : null;\nconst templateContainer = doc && /* @__PURE__ */ doc.createElement(\"template\");\nconst nodeOps = {\n  insert: (child, parent, anchor) => {\n    parent.insertBefore(child, anchor || null);\n  },\n  remove: (child) => {\n    const parent = child.parentNode;\n    if (parent) {\n      parent.removeChild(child);\n    }\n  },\n  createElement: (tag, namespace, is, props) => {\n    const el = namespace === \"svg\" ? doc.createElementNS(svgNS, tag) : namespace === \"mathml\" ? doc.createElementNS(mathmlNS, tag) : is ? doc.createElement(tag, { is }) : doc.createElement(tag);\n    if (tag === \"select\" && props && props.multiple != null) {\n      el.setAttribute(\"multiple\", props.multiple);\n    }\n    return el;\n  },\n  createText: (text) => doc.createTextNode(text),\n  createComment: (text) => doc.createComment(text),\n  setText: (node, text) => {\n    node.nodeValue = text;\n  },\n  setElementText: (el, text) => {\n    el.textContent = text;\n  },\n  parentNode: (node) => node.parentNode,\n  nextSibling: (node) => node.nextSibling,\n  querySelector: (selector) => doc.querySelector(selector),\n  setScopeId(el, id) {\n    el.setAttribute(id, \"\");\n  },\n  // __UNSAFE__\n  // Reason: innerHTML.\n  // Static content here can only come from compiled templates.\n  // As long as the user only uses trusted templates, this is safe.\n  insertStaticContent(content, parent, anchor, namespace, start, end) {\n    const before = anchor ? anchor.previousSibling : parent.lastChild;\n    if (start && (start === end || start.nextSibling)) {\n      while (true) {\n        parent.insertBefore(start.cloneNode(true), anchor);\n        if (start === end || !(start = start.nextSibling)) break;\n      }\n    } else {\n      templateContainer.innerHTML = namespace === \"svg\" ? `<svg>${content}</svg>` : namespace === \"mathml\" ? `<math>${content}</math>` : content;\n      const template = templateContainer.content;\n      if (namespace === \"svg\" || namespace === \"mathml\") {\n        const wrapper = template.firstChild;\n        while (wrapper.firstChild) {\n          template.appendChild(wrapper.firstChild);\n        }\n        template.removeChild(wrapper);\n      }\n      parent.insertBefore(template, anchor);\n    }\n    return [\n      // first\n      before ? before.nextSibling : parent.firstChild,\n      // last\n      anchor ? anchor.previousSibling : parent.lastChild\n    ];\n  }\n};\n\nconst TRANSITION$1 = \"transition\";\nconst ANIMATION = \"animation\";\nconst vtcKey = Symbol(\"_vtc\");\nconst Transition = (props, { slots }) => h(BaseTransition, resolveTransitionProps(props), slots);\nTransition.displayName = \"Transition\";\nconst DOMTransitionPropsValidators = {\n  name: String,\n  type: String,\n  css: {\n    type: Boolean,\n    default: true\n  },\n  duration: [String, Number, Object],\n  enterFromClass: String,\n  enterActiveClass: String,\n  enterToClass: String,\n  appearFromClass: String,\n  appearActiveClass: String,\n  appearToClass: String,\n  leaveFromClass: String,\n  leaveActiveClass: String,\n  leaveToClass: String\n};\nconst TransitionPropsValidators = Transition.props = /* @__PURE__ */ extend(\n  {},\n  BaseTransitionPropsValidators,\n  DOMTransitionPropsValidators\n);\nconst callHook = (hook, args = []) => {\n  if (isArray(hook)) {\n    hook.forEach((h2) => h2(...args));\n  } else if (hook) {\n    hook(...args);\n  }\n};\nconst hasExplicitCallback = (hook) => {\n  return hook ? isArray(hook) ? hook.some((h2) => h2.length > 1) : hook.length > 1 : false;\n};\nfunction resolveTransitionProps(rawProps) {\n  const baseProps = {};\n  for (const key in rawProps) {\n    if (!(key in DOMTransitionPropsValidators)) {\n      baseProps[key] = rawProps[key];\n    }\n  }\n  if (rawProps.css === false) {\n    return baseProps;\n  }\n  const {\n    name = \"v\",\n    type,\n    duration,\n    enterFromClass = `${name}-enter-from`,\n    enterActiveClass = `${name}-enter-active`,\n    enterToClass = `${name}-enter-to`,\n    appearFromClass = enterFromClass,\n    appearActiveClass = enterActiveClass,\n    appearToClass = enterToClass,\n    leaveFromClass = `${name}-leave-from`,\n    leaveActiveClass = `${name}-leave-active`,\n    leaveToClass = `${name}-leave-to`\n  } = rawProps;\n  const durations = normalizeDuration(duration);\n  const enterDuration = durations && durations[0];\n  const leaveDuration = durations && durations[1];\n  const {\n    onBeforeEnter,\n    onEnter,\n    onEnterCancelled,\n    onLeave,\n    onLeaveCancelled,\n    onBeforeAppear = onBeforeEnter,\n    onAppear = onEnter,\n    onAppearCancelled = onEnterCancelled\n  } = baseProps;\n  const finishEnter = (el, isAppear, done) => {\n    removeTransitionClass(el, isAppear ? appearToClass : enterToClass);\n    removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass);\n    done && done();\n  };\n  const finishLeave = (el, done) => {\n    el._isLeaving = false;\n    removeTransitionClass(el, leaveFromClass);\n    removeTransitionClass(el, leaveToClass);\n    removeTransitionClass(el, leaveActiveClass);\n    done && done();\n  };\n  const makeEnterHook = (isAppear) => {\n    return (el, done) => {\n      const hook = isAppear ? onAppear : onEnter;\n      const resolve = () => finishEnter(el, isAppear, done);\n      callHook(hook, [el, resolve]);\n      nextFrame(() => {\n        removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass);\n        addTransitionClass(el, isAppear ? appearToClass : enterToClass);\n        if (!hasExplicitCallback(hook)) {\n          whenTransitionEnds(el, type, enterDuration, resolve);\n        }\n      });\n    };\n  };\n  return extend(baseProps, {\n    onBeforeEnter(el) {\n      callHook(onBeforeEnter, [el]);\n      addTransitionClass(el, enterFromClass);\n      addTransitionClass(el, enterActiveClass);\n    },\n    onBeforeAppear(el) {\n      callHook(onBeforeAppear, [el]);\n      addTransitionClass(el, appearFromClass);\n      addTransitionClass(el, appearActiveClass);\n    },\n    onEnter: makeEnterHook(false),\n    onAppear: makeEnterHook(true),\n    onLeave(el, done) {\n      el._isLeaving = true;\n      const resolve = () => finishLeave(el, done);\n      addTransitionClass(el, leaveFromClass);\n      addTransitionClass(el, leaveActiveClass);\n      forceReflow();\n      nextFrame(() => {\n        if (!el._isLeaving) {\n          return;\n        }\n        removeTransitionClass(el, leaveFromClass);\n        addTransitionClass(el, leaveToClass);\n        if (!hasExplicitCallback(onLeave)) {\n          whenTransitionEnds(el, type, leaveDuration, resolve);\n        }\n      });\n      callHook(onLeave, [el, resolve]);\n    },\n    onEnterCancelled(el) {\n      finishEnter(el, false);\n      callHook(onEnterCancelled, [el]);\n    },\n    onAppearCancelled(el) {\n      finishEnter(el, true);\n      callHook(onAppearCancelled, [el]);\n    },\n    onLeaveCancelled(el) {\n      finishLeave(el);\n      callHook(onLeaveCancelled, [el]);\n    }\n  });\n}\nfunction normalizeDuration(duration) {\n  if (duration == null) {\n    return null;\n  } else if (isObject(duration)) {\n    return [NumberOf(duration.enter), NumberOf(duration.leave)];\n  } else {\n    const n = NumberOf(duration);\n    return [n, n];\n  }\n}\nfunction NumberOf(val) {\n  const res = toNumber(val);\n  {\n    assertNumber(res, \"<transition> explicit duration\");\n  }\n  return res;\n}\nfunction addTransitionClass(el, cls) {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  (el[vtcKey] || (el[vtcKey] = /* @__PURE__ */ new Set())).add(cls);\n}\nfunction removeTransitionClass(el, cls) {\n  cls.split(/\\s+/).forEach((c) => c && el.classList.remove(c));\n  const _vtc = el[vtcKey];\n  if (_vtc) {\n    _vtc.delete(cls);\n    if (!_vtc.size) {\n      el[vtcKey] = void 0;\n    }\n  }\n}\nfunction nextFrame(cb) {\n  requestAnimationFrame(() => {\n    requestAnimationFrame(cb);\n  });\n}\nlet endId = 0;\nfunction whenTransitionEnds(el, expectedType, explicitTimeout, resolve) {\n  const id = el._endId = ++endId;\n  const resolveIfNotStale = () => {\n    if (id === el._endId) {\n      resolve();\n    }\n  };\n  if (explicitTimeout) {\n    return setTimeout(resolveIfNotStale, explicitTimeout);\n  }\n  const { type, timeout, propCount } = getTransitionInfo(el, expectedType);\n  if (!type) {\n    return resolve();\n  }\n  const endEvent = type + \"end\";\n  let ended = 0;\n  const end = () => {\n    el.removeEventListener(endEvent, onEnd);\n    resolveIfNotStale();\n  };\n  const onEnd = (e) => {\n    if (e.target === el && ++ended >= propCount) {\n      end();\n    }\n  };\n  setTimeout(() => {\n    if (ended < propCount) {\n      end();\n    }\n  }, timeout + 1);\n  el.addEventListener(endEvent, onEnd);\n}\nfunction getTransitionInfo(el, expectedType) {\n  const styles = window.getComputedStyle(el);\n  const getStyleProperties = (key) => (styles[key] || \"\").split(\", \");\n  const transitionDelays = getStyleProperties(`${TRANSITION$1}Delay`);\n  const transitionDurations = getStyleProperties(`${TRANSITION$1}Duration`);\n  const transitionTimeout = getTimeout(transitionDelays, transitionDurations);\n  const animationDelays = getStyleProperties(`${ANIMATION}Delay`);\n  const animationDurations = getStyleProperties(`${ANIMATION}Duration`);\n  const animationTimeout = getTimeout(animationDelays, animationDurations);\n  let type = null;\n  let timeout = 0;\n  let propCount = 0;\n  if (expectedType === TRANSITION$1) {\n    if (transitionTimeout > 0) {\n      type = TRANSITION$1;\n      timeout = transitionTimeout;\n      propCount = transitionDurations.length;\n    }\n  } else if (expectedType === ANIMATION) {\n    if (animationTimeout > 0) {\n      type = ANIMATION;\n      timeout = animationTimeout;\n      propCount = animationDurations.length;\n    }\n  } else {\n    timeout = Math.max(transitionTimeout, animationTimeout);\n    type = timeout > 0 ? transitionTimeout > animationTimeout ? TRANSITION$1 : ANIMATION : null;\n    propCount = type ? type === TRANSITION$1 ? transitionDurations.length : animationDurations.length : 0;\n  }\n  const hasTransform = type === TRANSITION$1 && /\\b(transform|all)(,|$)/.test(\n    getStyleProperties(`${TRANSITION$1}Property`).toString()\n  );\n  return {\n    type,\n    timeout,\n    propCount,\n    hasTransform\n  };\n}\nfunction getTimeout(delays, durations) {\n  while (delays.length < durations.length) {\n    delays = delays.concat(delays);\n  }\n  return Math.max(...durations.map((d, i) => toMs(d) + toMs(delays[i])));\n}\nfunction toMs(s) {\n  if (s === \"auto\") return 0;\n  return Number(s.slice(0, -1).replace(\",\", \".\")) * 1e3;\n}\nfunction forceReflow() {\n  return document.body.offsetHeight;\n}\n\nfunction patchClass(el, value, isSVG) {\n  const transitionClasses = el[vtcKey];\n  if (transitionClasses) {\n    value = (value ? [value, ...transitionClasses] : [...transitionClasses]).join(\" \");\n  }\n  if (value == null) {\n    el.removeAttribute(\"class\");\n  } else if (isSVG) {\n    el.setAttribute(\"class\", value);\n  } else {\n    el.className = value;\n  }\n}\n\nconst vShowOriginalDisplay = Symbol(\"_vod\");\nconst vShowHidden = Symbol(\"_vsh\");\nconst vShow = {\n  beforeMount(el, { value }, { transition }) {\n    el[vShowOriginalDisplay] = el.style.display === \"none\" ? \"\" : el.style.display;\n    if (transition && value) {\n      transition.beforeEnter(el);\n    } else {\n      setDisplay(el, value);\n    }\n  },\n  mounted(el, { value }, { transition }) {\n    if (transition && value) {\n      transition.enter(el);\n    }\n  },\n  updated(el, { value, oldValue }, { transition }) {\n    if (!value === !oldValue) return;\n    if (transition) {\n      if (value) {\n        transition.beforeEnter(el);\n        setDisplay(el, true);\n        transition.enter(el);\n      } else {\n        transition.leave(el, () => {\n          setDisplay(el, false);\n        });\n      }\n    } else {\n      setDisplay(el, value);\n    }\n  },\n  beforeUnmount(el, { value }) {\n    setDisplay(el, value);\n  }\n};\n{\n  vShow.name = \"show\";\n}\nfunction setDisplay(el, value) {\n  el.style.display = value ? el[vShowOriginalDisplay] : \"none\";\n  el[vShowHidden] = !value;\n}\n\nconst CSS_VAR_TEXT = Symbol(\"CSS_VAR_TEXT\" );\nfunction useCssVars(getter) {\n  const instance = getCurrentInstance();\n  if (!instance) {\n    warn(`useCssVars is called without current active component instance.`);\n    return;\n  }\n  const updateTeleports = instance.ut = (vars = getter(instance.proxy)) => {\n    Array.from(\n      document.querySelectorAll(`[data-v-owner=\"${instance.uid}\"]`)\n    ).forEach((node) => setVarsOnNode(node, vars));\n  };\n  {\n    instance.getCssVars = () => getter(instance.proxy);\n  }\n  const setVars = () => {\n    const vars = getter(instance.proxy);\n    setVarsOnVNode(instance.subTree, vars);\n    updateTeleports(vars);\n  };\n  onMounted(() => {\n    watchPostEffect(setVars);\n    const ob = new MutationObserver(setVars);\n    ob.observe(instance.subTree.el.parentNode, { childList: true });\n    onUnmounted(() => ob.disconnect());\n  });\n}\nfunction setVarsOnVNode(vnode, vars) {\n  if (vnode.shapeFlag & 128) {\n    const suspense = vnode.suspense;\n    vnode = suspense.activeBranch;\n    if (suspense.pendingBranch && !suspense.isHydrating) {\n      suspense.effects.push(() => {\n        setVarsOnVNode(suspense.activeBranch, vars);\n      });\n    }\n  }\n  while (vnode.component) {\n    vnode = vnode.component.subTree;\n  }\n  if (vnode.shapeFlag & 1 && vnode.el) {\n    setVarsOnNode(vnode.el, vars);\n  } else if (vnode.type === Fragment) {\n    vnode.children.forEach((c) => setVarsOnVNode(c, vars));\n  } else if (vnode.type === Static) {\n    let { el, anchor } = vnode;\n    while (el) {\n      setVarsOnNode(el, vars);\n      if (el === anchor) break;\n      el = el.nextSibling;\n    }\n  }\n}\nfunction setVarsOnNode(el, vars) {\n  if (el.nodeType === 1) {\n    const style = el.style;\n    let cssText = \"\";\n    for (const key in vars) {\n      style.setProperty(`--${key}`, vars[key]);\n      cssText += `--${key}: ${vars[key]};`;\n    }\n    style[CSS_VAR_TEXT] = cssText;\n  }\n}\n\nconst displayRE = /(^|;)\\s*display\\s*:/;\nfunction patchStyle(el, prev, next) {\n  const style = el.style;\n  const isCssString = isString(next);\n  let hasControlledDisplay = false;\n  if (next && !isCssString) {\n    if (prev) {\n      if (!isString(prev)) {\n        for (const key in prev) {\n          if (next[key] == null) {\n            setStyle(style, key, \"\");\n          }\n        }\n      } else {\n        for (const prevStyle of prev.split(\";\")) {\n          const key = prevStyle.slice(0, prevStyle.indexOf(\":\")).trim();\n          if (next[key] == null) {\n            setStyle(style, key, \"\");\n          }\n        }\n      }\n    }\n    for (const key in next) {\n      if (key === \"display\") {\n        hasControlledDisplay = true;\n      }\n      setStyle(style, key, next[key]);\n    }\n  } else {\n    if (isCssString) {\n      if (prev !== next) {\n        const cssVarText = style[CSS_VAR_TEXT];\n        if (cssVarText) {\n          next += \";\" + cssVarText;\n        }\n        style.cssText = next;\n        hasControlledDisplay = displayRE.test(next);\n      }\n    } else if (prev) {\n      el.removeAttribute(\"style\");\n    }\n  }\n  if (vShowOriginalDisplay in el) {\n    el[vShowOriginalDisplay] = hasControlledDisplay ? style.display : \"\";\n    if (el[vShowHidden]) {\n      style.display = \"none\";\n    }\n  }\n}\nconst semicolonRE = /[^\\\\];\\s*$/;\nconst importantRE = /\\s*!important$/;\nfunction setStyle(style, name, val) {\n  if (isArray(val)) {\n    val.forEach((v) => setStyle(style, name, v));\n  } else {\n    if (val == null) val = \"\";\n    {\n      if (semicolonRE.test(val)) {\n        warn(\n          `Unexpected semicolon at the end of '${name}' style value: '${val}'`\n        );\n      }\n    }\n    if (name.startsWith(\"--\")) {\n      style.setProperty(name, val);\n    } else {\n      const prefixed = autoPrefix(style, name);\n      if (importantRE.test(val)) {\n        style.setProperty(\n          hyphenate(prefixed),\n          val.replace(importantRE, \"\"),\n          \"important\"\n        );\n      } else {\n        style[prefixed] = val;\n      }\n    }\n  }\n}\nconst prefixes = [\"Webkit\", \"Moz\", \"ms\"];\nconst prefixCache = {};\nfunction autoPrefix(style, rawName) {\n  const cached = prefixCache[rawName];\n  if (cached) {\n    return cached;\n  }\n  let name = camelize(rawName);\n  if (name !== \"filter\" && name in style) {\n    return prefixCache[rawName] = name;\n  }\n  name = capitalize(name);\n  for (let i = 0; i < prefixes.length; i++) {\n    const prefixed = prefixes[i] + name;\n    if (prefixed in style) {\n      return prefixCache[rawName] = prefixed;\n    }\n  }\n  return rawName;\n}\n\nconst xlinkNS = \"http://www.w3.org/1999/xlink\";\nfunction patchAttr(el, key, value, isSVG, instance, isBoolean = isSpecialBooleanAttr(key)) {\n  if (isSVG && key.startsWith(\"xlink:\")) {\n    if (value == null) {\n      el.removeAttributeNS(xlinkNS, key.slice(6, key.length));\n    } else {\n      el.setAttributeNS(xlinkNS, key, value);\n    }\n  } else {\n    if (value == null || isBoolean && !includeBooleanAttr(value)) {\n      el.removeAttribute(key);\n    } else {\n      el.setAttribute(\n        key,\n        isBoolean ? \"\" : isSymbol(value) ? String(value) : value\n      );\n    }\n  }\n}\n\nfunction patchDOMProp(el, key, value, parentComponent) {\n  if (key === \"innerHTML\" || key === \"textContent\") {\n    if (value == null) return;\n    el[key] = value;\n    return;\n  }\n  const tag = el.tagName;\n  if (key === \"value\" && tag !== \"PROGRESS\" && // custom elements may use _value internally\n  !tag.includes(\"-\")) {\n    const oldValue = tag === \"OPTION\" ? el.getAttribute(\"value\") || \"\" : el.value;\n    const newValue = value == null ? \"\" : String(value);\n    if (oldValue !== newValue || !(\"_value\" in el)) {\n      el.value = newValue;\n    }\n    if (value == null) {\n      el.removeAttribute(key);\n    }\n    el._value = value;\n    return;\n  }\n  let needRemove = false;\n  if (value === \"\" || value == null) {\n    const type = typeof el[key];\n    if (type === \"boolean\") {\n      value = includeBooleanAttr(value);\n    } else if (value == null && type === \"string\") {\n      value = \"\";\n      needRemove = true;\n    } else if (type === \"number\") {\n      value = 0;\n      needRemove = true;\n    }\n  }\n  try {\n    el[key] = value;\n  } catch (e) {\n    if (!needRemove) {\n      warn(\n        `Failed setting prop \"${key}\" on <${tag.toLowerCase()}>: value ${value} is invalid.`,\n        e\n      );\n    }\n  }\n  needRemove && el.removeAttribute(key);\n}\n\nfunction addEventListener(el, event, handler, options) {\n  el.addEventListener(event, handler, options);\n}\nfunction removeEventListener(el, event, handler, options) {\n  el.removeEventListener(event, handler, options);\n}\nconst veiKey = Symbol(\"_vei\");\nfunction patchEvent(el, rawName, prevValue, nextValue, instance = null) {\n  const invokers = el[veiKey] || (el[veiKey] = {});\n  const existingInvoker = invokers[rawName];\n  if (nextValue && existingInvoker) {\n    existingInvoker.value = sanitizeEventValue(nextValue, rawName) ;\n  } else {\n    const [name, options] = parseName(rawName);\n    if (nextValue) {\n      const invoker = invokers[rawName] = createInvoker(\n        sanitizeEventValue(nextValue, rawName) ,\n        instance\n      );\n      addEventListener(el, name, invoker, options);\n    } else if (existingInvoker) {\n      removeEventListener(el, name, existingInvoker, options);\n      invokers[rawName] = void 0;\n    }\n  }\n}\nconst optionsModifierRE = /(?:Once|Passive|Capture)$/;\nfunction parseName(name) {\n  let options;\n  if (optionsModifierRE.test(name)) {\n    options = {};\n    let m;\n    while (m = name.match(optionsModifierRE)) {\n      name = name.slice(0, name.length - m[0].length);\n      options[m[0].toLowerCase()] = true;\n    }\n  }\n  const event = name[2] === \":\" ? name.slice(3) : hyphenate(name.slice(2));\n  return [event, options];\n}\nlet cachedNow = 0;\nconst p = /* @__PURE__ */ Promise.resolve();\nconst getNow = () => cachedNow || (p.then(() => cachedNow = 0), cachedNow = Date.now());\nfunction createInvoker(initialValue, instance) {\n  const invoker = (e) => {\n    if (!e._vts) {\n      e._vts = Date.now();\n    } else if (e._vts <= invoker.attached) {\n      return;\n    }\n    callWithAsyncErrorHandling(\n      patchStopImmediatePropagation(e, invoker.value),\n      instance,\n      5,\n      [e]\n    );\n  };\n  invoker.value = initialValue;\n  invoker.attached = getNow();\n  return invoker;\n}\nfunction sanitizeEventValue(value, propName) {\n  if (isFunction(value) || isArray(value)) {\n    return value;\n  }\n  warn(\n    `Wrong type passed as event handler to ${propName} - did you forget @ or : in front of your prop?\nExpected function or array of functions, received type ${typeof value}.`\n  );\n  return NOOP;\n}\nfunction patchStopImmediatePropagation(e, value) {\n  if (isArray(value)) {\n    const originalStop = e.stopImmediatePropagation;\n    e.stopImmediatePropagation = () => {\n      originalStop.call(e);\n      e._stopped = true;\n    };\n    return value.map(\n      (fn) => (e2) => !e2._stopped && fn && fn(e2)\n    );\n  } else {\n    return value;\n  }\n}\n\nconst isNativeOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // lowercase letter\nkey.charCodeAt(2) > 96 && key.charCodeAt(2) < 123;\nconst patchProp = (el, key, prevValue, nextValue, namespace, parentComponent) => {\n  const isSVG = namespace === \"svg\";\n  if (key === \"class\") {\n    patchClass(el, nextValue, isSVG);\n  } else if (key === \"style\") {\n    patchStyle(el, prevValue, nextValue);\n  } else if (isOn(key)) {\n    if (!isModelListener(key)) {\n      patchEvent(el, key, prevValue, nextValue, parentComponent);\n    }\n  } else if (key[0] === \".\" ? (key = key.slice(1), true) : key[0] === \"^\" ? (key = key.slice(1), false) : shouldSetAsProp(el, key, nextValue, isSVG)) {\n    patchDOMProp(el, key, nextValue);\n    if (!el.tagName.includes(\"-\") && (key === \"value\" || key === \"checked\" || key === \"selected\")) {\n      patchAttr(el, key, nextValue, isSVG, parentComponent, key !== \"value\");\n    }\n  } else {\n    if (key === \"true-value\") {\n      el._trueValue = nextValue;\n    } else if (key === \"false-value\") {\n      el._falseValue = nextValue;\n    }\n    patchAttr(el, key, nextValue, isSVG);\n  }\n};\nfunction shouldSetAsProp(el, key, value, isSVG) {\n  if (isSVG) {\n    if (key === \"innerHTML\" || key === \"textContent\") {\n      return true;\n    }\n    if (key in el && isNativeOn(key) && isFunction(value)) {\n      return true;\n    }\n    return false;\n  }\n  if (key === \"spellcheck\" || key === \"draggable\" || key === \"translate\") {\n    return false;\n  }\n  if (key === \"form\") {\n    return false;\n  }\n  if (key === \"list\" && el.tagName === \"INPUT\") {\n    return false;\n  }\n  if (key === \"type\" && el.tagName === \"TEXTAREA\") {\n    return false;\n  }\n  if (key === \"width\" || key === \"height\") {\n    const tag = el.tagName;\n    if (tag === \"IMG\" || tag === \"VIDEO\" || tag === \"CANVAS\" || tag === \"SOURCE\") {\n      return false;\n    }\n  }\n  if (isNativeOn(key) && isString(value)) {\n    return false;\n  }\n  return key in el;\n}\n\n/*! #__NO_SIDE_EFFECTS__ */\n// @__NO_SIDE_EFFECTS__\nfunction defineCustomElement(options, extraOptions, hydrate2) {\n  const Comp = defineComponent(options, extraOptions);\n  class VueCustomElement extends VueElement {\n    constructor(initialProps) {\n      super(Comp, initialProps, hydrate2);\n    }\n  }\n  VueCustomElement.def = Comp;\n  return VueCustomElement;\n}\n/*! #__NO_SIDE_EFFECTS__ */\nconst defineSSRCustomElement = /* @__NO_SIDE_EFFECTS__ */ (options, extraOptions) => {\n  return /* @__PURE__ */ defineCustomElement(options, extraOptions, hydrate);\n};\nconst BaseClass = typeof HTMLElement !== \"undefined\" ? HTMLElement : class {\n};\nclass VueElement extends BaseClass {\n  constructor(_def, _props = {}, hydrate2) {\n    super();\n    this._def = _def;\n    this._props = _props;\n    /**\n     * @internal\n     */\n    this._instance = null;\n    this._connected = false;\n    this._resolved = false;\n    this._numberProps = null;\n    this._ob = null;\n    if (this.shadowRoot && hydrate2) {\n      hydrate2(this._createVNode(), this.shadowRoot);\n    } else {\n      if (this.shadowRoot) {\n        warn(\n          `Custom element has pre-rendered declarative shadow root but is not defined as hydratable. Use \\`defineSSRCustomElement\\`.`\n        );\n      }\n      this.attachShadow({ mode: \"open\" });\n      if (!this._def.__asyncLoader) {\n        this._resolveProps(this._def);\n      }\n    }\n  }\n  connectedCallback() {\n    this._connected = true;\n    if (!this._instance) {\n      if (this._resolved) {\n        this._update();\n      } else {\n        this._resolveDef();\n      }\n    }\n  }\n  disconnectedCallback() {\n    this._connected = false;\n    nextTick(() => {\n      if (!this._connected) {\n        if (this._ob) {\n          this._ob.disconnect();\n          this._ob = null;\n        }\n        render(null, this.shadowRoot);\n        this._instance = null;\n      }\n    });\n  }\n  /**\n   * resolve inner component definition (handle possible async component)\n   */\n  _resolveDef() {\n    this._resolved = true;\n    for (let i = 0; i < this.attributes.length; i++) {\n      this._setAttr(this.attributes[i].name);\n    }\n    this._ob = new MutationObserver((mutations) => {\n      for (const m of mutations) {\n        this._setAttr(m.attributeName);\n      }\n    });\n    this._ob.observe(this, { attributes: true });\n    const resolve = (def, isAsync = false) => {\n      const { props, styles } = def;\n      let numberProps;\n      if (props && !isArray(props)) {\n        for (const key in props) {\n          const opt = props[key];\n          if (opt === Number || opt && opt.type === Number) {\n            if (key in this._props) {\n              this._props[key] = toNumber(this._props[key]);\n            }\n            (numberProps || (numberProps = /* @__PURE__ */ Object.create(null)))[camelize(key)] = true;\n          }\n        }\n      }\n      this._numberProps = numberProps;\n      if (isAsync) {\n        this._resolveProps(def);\n      }\n      this._applyStyles(styles);\n      this._update();\n    };\n    const asyncDef = this._def.__asyncLoader;\n    if (asyncDef) {\n      asyncDef().then((def) => resolve(def, true));\n    } else {\n      resolve(this._def);\n    }\n  }\n  _resolveProps(def) {\n    const { props } = def;\n    const declaredPropKeys = isArray(props) ? props : Object.keys(props || {});\n    for (const key of Object.keys(this)) {\n      if (key[0] !== \"_\" && declaredPropKeys.includes(key)) {\n        this._setProp(key, this[key], true, false);\n      }\n    }\n    for (const key of declaredPropKeys.map(camelize)) {\n      Object.defineProperty(this, key, {\n        get() {\n          return this._getProp(key);\n        },\n        set(val) {\n          this._setProp(key, val);\n        }\n      });\n    }\n  }\n  _setAttr(key) {\n    let value = this.hasAttribute(key) ? this.getAttribute(key) : void 0;\n    const camelKey = camelize(key);\n    if (this._numberProps && this._numberProps[camelKey]) {\n      value = toNumber(value);\n    }\n    this._setProp(camelKey, value, false);\n  }\n  /**\n   * @internal\n   */\n  _getProp(key) {\n    return this._props[key];\n  }\n  /**\n   * @internal\n   */\n  _setProp(key, val, shouldReflect = true, shouldUpdate = true) {\n    if (val !== this._props[key]) {\n      this._props[key] = val;\n      if (shouldUpdate && this._instance) {\n        this._update();\n      }\n      if (shouldReflect) {\n        if (val === true) {\n          this.setAttribute(hyphenate(key), \"\");\n        } else if (typeof val === \"string\" || typeof val === \"number\") {\n          this.setAttribute(hyphenate(key), val + \"\");\n        } else if (!val) {\n          this.removeAttribute(hyphenate(key));\n        }\n      }\n    }\n  }\n  _update() {\n    render(this._createVNode(), this.shadowRoot);\n  }\n  _createVNode() {\n    const vnode = createVNode(this._def, extend({}, this._props));\n    if (!this._instance) {\n      vnode.ce = (instance) => {\n        this._instance = instance;\n        instance.isCE = true;\n        {\n          instance.ceReload = (newStyles) => {\n            if (this._styles) {\n              this._styles.forEach((s) => this.shadowRoot.removeChild(s));\n              this._styles.length = 0;\n            }\n            this._applyStyles(newStyles);\n            this._instance = null;\n            this._update();\n          };\n        }\n        const dispatch = (event, args) => {\n          this.dispatchEvent(\n            new CustomEvent(event, {\n              detail: args\n            })\n          );\n        };\n        instance.emit = (event, ...args) => {\n          dispatch(event, args);\n          if (hyphenate(event) !== event) {\n            dispatch(hyphenate(event), args);\n          }\n        };\n        let parent = this;\n        while (parent = parent && (parent.parentNode || parent.host)) {\n          if (parent instanceof VueElement) {\n            instance.parent = parent._instance;\n            instance.provides = parent._instance.provides;\n            break;\n          }\n        }\n      };\n    }\n    return vnode;\n  }\n  _applyStyles(styles) {\n    if (styles) {\n      styles.forEach((css) => {\n        const s = document.createElement(\"style\");\n        s.textContent = css;\n        this.shadowRoot.appendChild(s);\n        {\n          (this._styles || (this._styles = [])).push(s);\n        }\n      });\n    }\n  }\n}\n\nfunction useCssModule(name = \"$style\") {\n  {\n    const instance = getCurrentInstance();\n    if (!instance) {\n      warn(`useCssModule must be called inside setup()`);\n      return EMPTY_OBJ;\n    }\n    const modules = instance.type.__cssModules;\n    if (!modules) {\n      warn(`Current instance does not have CSS modules injected.`);\n      return EMPTY_OBJ;\n    }\n    const mod = modules[name];\n    if (!mod) {\n      warn(`Current instance does not have CSS module named \"${name}\".`);\n      return EMPTY_OBJ;\n    }\n    return mod;\n  }\n}\n\nconst positionMap = /* @__PURE__ */ new WeakMap();\nconst newPositionMap = /* @__PURE__ */ new WeakMap();\nconst moveCbKey = Symbol(\"_moveCb\");\nconst enterCbKey = Symbol(\"_enterCb\");\nconst TransitionGroupImpl = {\n  name: \"TransitionGroup\",\n  props: /* @__PURE__ */ extend({}, TransitionPropsValidators, {\n    tag: String,\n    moveClass: String\n  }),\n  setup(props, { slots }) {\n    const instance = getCurrentInstance();\n    const state = useTransitionState();\n    let prevChildren;\n    let children;\n    onUpdated(() => {\n      if (!prevChildren.length) {\n        return;\n      }\n      const moveClass = props.moveClass || `${props.name || \"v\"}-move`;\n      if (!hasCSSTransform(\n        prevChildren[0].el,\n        instance.vnode.el,\n        moveClass\n      )) {\n        return;\n      }\n      prevChildren.forEach(callPendingCbs);\n      prevChildren.forEach(recordPosition);\n      const movedChildren = prevChildren.filter(applyTranslation);\n      forceReflow();\n      movedChildren.forEach((c) => {\n        const el = c.el;\n        const style = el.style;\n        addTransitionClass(el, moveClass);\n        style.transform = style.webkitTransform = style.transitionDuration = \"\";\n        const cb = el[moveCbKey] = (e) => {\n          if (e && e.target !== el) {\n            return;\n          }\n          if (!e || /transform$/.test(e.propertyName)) {\n            el.removeEventListener(\"transitionend\", cb);\n            el[moveCbKey] = null;\n            removeTransitionClass(el, moveClass);\n          }\n        };\n        el.addEventListener(\"transitionend\", cb);\n      });\n    });\n    return () => {\n      const rawProps = toRaw(props);\n      const cssTransitionProps = resolveTransitionProps(rawProps);\n      let tag = rawProps.tag || Fragment;\n      prevChildren = [];\n      if (children) {\n        for (let i = 0; i < children.length; i++) {\n          const child = children[i];\n          if (child.el && child.el instanceof Element) {\n            prevChildren.push(child);\n            setTransitionHooks(\n              child,\n              resolveTransitionHooks(\n                child,\n                cssTransitionProps,\n                state,\n                instance\n              )\n            );\n            positionMap.set(\n              child,\n              child.el.getBoundingClientRect()\n            );\n          }\n        }\n      }\n      children = slots.default ? getTransitionRawChildren(slots.default()) : [];\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i];\n        if (child.key != null) {\n          setTransitionHooks(\n            child,\n            resolveTransitionHooks(child, cssTransitionProps, state, instance)\n          );\n        } else {\n          warn(`<TransitionGroup> children must be keyed.`);\n        }\n      }\n      return createVNode(tag, null, children);\n    };\n  }\n};\nconst removeMode = (props) => delete props.mode;\n/* @__PURE__ */ removeMode(TransitionGroupImpl.props);\nconst TransitionGroup = TransitionGroupImpl;\nfunction callPendingCbs(c) {\n  const el = c.el;\n  if (el[moveCbKey]) {\n    el[moveCbKey]();\n  }\n  if (el[enterCbKey]) {\n    el[enterCbKey]();\n  }\n}\nfunction recordPosition(c) {\n  newPositionMap.set(c, c.el.getBoundingClientRect());\n}\nfunction applyTranslation(c) {\n  const oldPos = positionMap.get(c);\n  const newPos = newPositionMap.get(c);\n  const dx = oldPos.left - newPos.left;\n  const dy = oldPos.top - newPos.top;\n  if (dx || dy) {\n    const s = c.el.style;\n    s.transform = s.webkitTransform = `translate(${dx}px,${dy}px)`;\n    s.transitionDuration = \"0s\";\n    return c;\n  }\n}\nfunction hasCSSTransform(el, root, moveClass) {\n  const clone = el.cloneNode();\n  const _vtc = el[vtcKey];\n  if (_vtc) {\n    _vtc.forEach((cls) => {\n      cls.split(/\\s+/).forEach((c) => c && clone.classList.remove(c));\n    });\n  }\n  moveClass.split(/\\s+/).forEach((c) => c && clone.classList.add(c));\n  clone.style.display = \"none\";\n  const container = root.nodeType === 1 ? root : root.parentNode;\n  container.appendChild(clone);\n  const { hasTransform } = getTransitionInfo(clone);\n  container.removeChild(clone);\n  return hasTransform;\n}\n\nconst getModelAssigner = (vnode) => {\n  const fn = vnode.props[\"onUpdate:modelValue\"] || false;\n  return isArray(fn) ? (value) => invokeArrayFns(fn, value) : fn;\n};\nfunction onCompositionStart(e) {\n  e.target.composing = true;\n}\nfunction onCompositionEnd(e) {\n  const target = e.target;\n  if (target.composing) {\n    target.composing = false;\n    target.dispatchEvent(new Event(\"input\"));\n  }\n}\nconst assignKey = Symbol(\"_assign\");\nconst vModelText = {\n  created(el, { modifiers: { lazy, trim, number } }, vnode) {\n    el[assignKey] = getModelAssigner(vnode);\n    const castToNumber = number || vnode.props && vnode.props.type === \"number\";\n    addEventListener(el, lazy ? \"change\" : \"input\", (e) => {\n      if (e.target.composing) return;\n      let domValue = el.value;\n      if (trim) {\n        domValue = domValue.trim();\n      }\n      if (castToNumber) {\n        domValue = looseToNumber(domValue);\n      }\n      el[assignKey](domValue);\n    });\n    if (trim) {\n      addEventListener(el, \"change\", () => {\n        el.value = el.value.trim();\n      });\n    }\n    if (!lazy) {\n      addEventListener(el, \"compositionstart\", onCompositionStart);\n      addEventListener(el, \"compositionend\", onCompositionEnd);\n      addEventListener(el, \"change\", onCompositionEnd);\n    }\n  },\n  // set value on mounted so it's after min/max for type=\"range\"\n  mounted(el, { value }) {\n    el.value = value == null ? \"\" : value;\n  },\n  beforeUpdate(el, { value, oldValue, modifiers: { lazy, trim, number } }, vnode) {\n    el[assignKey] = getModelAssigner(vnode);\n    if (el.composing) return;\n    const elValue = (number || el.type === \"number\") && !/^0\\d/.test(el.value) ? looseToNumber(el.value) : el.value;\n    const newValue = value == null ? \"\" : value;\n    if (elValue === newValue) {\n      return;\n    }\n    if (document.activeElement === el && el.type !== \"range\") {\n      if (lazy && value === oldValue) {\n        return;\n      }\n      if (trim && el.value.trim() === newValue) {\n        return;\n      }\n    }\n    el.value = newValue;\n  }\n};\nconst vModelCheckbox = {\n  // #4096 array checkboxes need to be deep traversed\n  deep: true,\n  created(el, _, vnode) {\n    el[assignKey] = getModelAssigner(vnode);\n    addEventListener(el, \"change\", () => {\n      const modelValue = el._modelValue;\n      const elementValue = getValue(el);\n      const checked = el.checked;\n      const assign = el[assignKey];\n      if (isArray(modelValue)) {\n        const index = looseIndexOf(modelValue, elementValue);\n        const found = index !== -1;\n        if (checked && !found) {\n          assign(modelValue.concat(elementValue));\n        } else if (!checked && found) {\n          const filtered = [...modelValue];\n          filtered.splice(index, 1);\n          assign(filtered);\n        }\n      } else if (isSet(modelValue)) {\n        const cloned = new Set(modelValue);\n        if (checked) {\n          cloned.add(elementValue);\n        } else {\n          cloned.delete(elementValue);\n        }\n        assign(cloned);\n      } else {\n        assign(getCheckboxValue(el, checked));\n      }\n    });\n  },\n  // set initial checked on mount to wait for true-value/false-value\n  mounted: setChecked,\n  beforeUpdate(el, binding, vnode) {\n    el[assignKey] = getModelAssigner(vnode);\n    setChecked(el, binding, vnode);\n  }\n};\nfunction setChecked(el, { value, oldValue }, vnode) {\n  el._modelValue = value;\n  if (isArray(value)) {\n    el.checked = looseIndexOf(value, vnode.props.value) > -1;\n  } else if (isSet(value)) {\n    el.checked = value.has(vnode.props.value);\n  } else if (value !== oldValue) {\n    el.checked = looseEqual(value, getCheckboxValue(el, true));\n  }\n}\nconst vModelRadio = {\n  created(el, { value }, vnode) {\n    el.checked = looseEqual(value, vnode.props.value);\n    el[assignKey] = getModelAssigner(vnode);\n    addEventListener(el, \"change\", () => {\n      el[assignKey](getValue(el));\n    });\n  },\n  beforeUpdate(el, { value, oldValue }, vnode) {\n    el[assignKey] = getModelAssigner(vnode);\n    if (value !== oldValue) {\n      el.checked = looseEqual(value, vnode.props.value);\n    }\n  }\n};\nconst vModelSelect = {\n  // <select multiple> value need to be deep traversed\n  deep: true,\n  created(el, { value, modifiers: { number } }, vnode) {\n    const isSetModel = isSet(value);\n    addEventListener(el, \"change\", () => {\n      const selectedVal = Array.prototype.filter.call(el.options, (o) => o.selected).map(\n        (o) => number ? looseToNumber(getValue(o)) : getValue(o)\n      );\n      el[assignKey](\n        el.multiple ? isSetModel ? new Set(selectedVal) : selectedVal : selectedVal[0]\n      );\n      el._assigning = true;\n      nextTick(() => {\n        el._assigning = false;\n      });\n    });\n    el[assignKey] = getModelAssigner(vnode);\n  },\n  // set value in mounted & updated because <select> relies on its children\n  // <option>s.\n  mounted(el, { value, modifiers: { number } }) {\n    setSelected(el, value);\n  },\n  beforeUpdate(el, _binding, vnode) {\n    el[assignKey] = getModelAssigner(vnode);\n  },\n  updated(el, { value, modifiers: { number } }) {\n    if (!el._assigning) {\n      setSelected(el, value);\n    }\n  }\n};\nfunction setSelected(el, value, number) {\n  const isMultiple = el.multiple;\n  const isArrayValue = isArray(value);\n  if (isMultiple && !isArrayValue && !isSet(value)) {\n    warn(\n      `<select multiple v-model> expects an Array or Set value for its binding, but got ${Object.prototype.toString.call(value).slice(8, -1)}.`\n    );\n    return;\n  }\n  for (let i = 0, l = el.options.length; i < l; i++) {\n    const option = el.options[i];\n    const optionValue = getValue(option);\n    if (isMultiple) {\n      if (isArrayValue) {\n        const optionType = typeof optionValue;\n        if (optionType === \"string\" || optionType === \"number\") {\n          option.selected = value.some((v) => String(v) === String(optionValue));\n        } else {\n          option.selected = looseIndexOf(value, optionValue) > -1;\n        }\n      } else {\n        option.selected = value.has(optionValue);\n      }\n    } else if (looseEqual(getValue(option), value)) {\n      if (el.selectedIndex !== i) el.selectedIndex = i;\n      return;\n    }\n  }\n  if (!isMultiple && el.selectedIndex !== -1) {\n    el.selectedIndex = -1;\n  }\n}\nfunction getValue(el) {\n  return \"_value\" in el ? el._value : el.value;\n}\nfunction getCheckboxValue(el, checked) {\n  const key = checked ? \"_trueValue\" : \"_falseValue\";\n  return key in el ? el[key] : checked;\n}\nconst vModelDynamic = {\n  created(el, binding, vnode) {\n    callModelHook(el, binding, vnode, null, \"created\");\n  },\n  mounted(el, binding, vnode) {\n    callModelHook(el, binding, vnode, null, \"mounted\");\n  },\n  beforeUpdate(el, binding, vnode, prevVNode) {\n    callModelHook(el, binding, vnode, prevVNode, \"beforeUpdate\");\n  },\n  updated(el, binding, vnode, prevVNode) {\n    callModelHook(el, binding, vnode, prevVNode, \"updated\");\n  }\n};\nfunction resolveDynamicModel(tagName, type) {\n  switch (tagName) {\n    case \"SELECT\":\n      return vModelSelect;\n    case \"TEXTAREA\":\n      return vModelText;\n    default:\n      switch (type) {\n        case \"checkbox\":\n          return vModelCheckbox;\n        case \"radio\":\n          return vModelRadio;\n        default:\n          return vModelText;\n      }\n  }\n}\nfunction callModelHook(el, binding, vnode, prevVNode, hook) {\n  const modelToUse = resolveDynamicModel(\n    el.tagName,\n    vnode.props && vnode.props.type\n  );\n  const fn = modelToUse[hook];\n  fn && fn(el, binding, vnode, prevVNode);\n}\n\nconst systemModifiers = [\"ctrl\", \"shift\", \"alt\", \"meta\"];\nconst modifierGuards = {\n  stop: (e) => e.stopPropagation(),\n  prevent: (e) => e.preventDefault(),\n  self: (e) => e.target !== e.currentTarget,\n  ctrl: (e) => !e.ctrlKey,\n  shift: (e) => !e.shiftKey,\n  alt: (e) => !e.altKey,\n  meta: (e) => !e.metaKey,\n  left: (e) => \"button\" in e && e.button !== 0,\n  middle: (e) => \"button\" in e && e.button !== 1,\n  right: (e) => \"button\" in e && e.button !== 2,\n  exact: (e, modifiers) => systemModifiers.some((m) => e[`${m}Key`] && !modifiers.includes(m))\n};\nconst withModifiers = (fn, modifiers) => {\n  const cache = fn._withMods || (fn._withMods = {});\n  const cacheKey = modifiers.join(\".\");\n  return cache[cacheKey] || (cache[cacheKey] = (event, ...args) => {\n    for (let i = 0; i < modifiers.length; i++) {\n      const guard = modifierGuards[modifiers[i]];\n      if (guard && guard(event, modifiers)) return;\n    }\n    return fn(event, ...args);\n  });\n};\nconst keyNames = {\n  esc: \"escape\",\n  space: \" \",\n  up: \"arrow-up\",\n  left: \"arrow-left\",\n  right: \"arrow-right\",\n  down: \"arrow-down\",\n  delete: \"backspace\"\n};\nconst withKeys = (fn, modifiers) => {\n  const cache = fn._withKeys || (fn._withKeys = {});\n  const cacheKey = modifiers.join(\".\");\n  return cache[cacheKey] || (cache[cacheKey] = (event) => {\n    if (!(\"key\" in event)) {\n      return;\n    }\n    const eventKey = hyphenate(event.key);\n    if (modifiers.some((k) => k === eventKey || keyNames[k] === eventKey)) {\n      return fn(event);\n    }\n  });\n};\n\nconst rendererOptions = /* @__PURE__ */ extend({ patchProp }, nodeOps);\nlet renderer;\nlet enabledHydration = false;\nfunction ensureRenderer() {\n  return renderer || (renderer = createRenderer(rendererOptions));\n}\nfunction ensureHydrationRenderer() {\n  renderer = enabledHydration ? renderer : createHydrationRenderer(rendererOptions);\n  enabledHydration = true;\n  return renderer;\n}\nconst render = (...args) => {\n  ensureRenderer().render(...args);\n};\nconst hydrate = (...args) => {\n  ensureHydrationRenderer().hydrate(...args);\n};\nconst createApp = (...args) => {\n  const app = ensureRenderer().createApp(...args);\n  {\n    injectNativeTagCheck(app);\n    injectCompilerOptionsCheck(app);\n  }\n  const { mount } = app;\n  app.mount = (containerOrSelector) => {\n    const container = normalizeContainer(containerOrSelector);\n    if (!container) return;\n    const component = app._component;\n    if (!isFunction(component) && !component.render && !component.template) {\n      component.template = container.innerHTML;\n    }\n    container.innerHTML = \"\";\n    const proxy = mount(container, false, resolveRootNamespace(container));\n    if (container instanceof Element) {\n      container.removeAttribute(\"v-cloak\");\n      container.setAttribute(\"data-v-app\", \"\");\n    }\n    return proxy;\n  };\n  return app;\n};\nconst createSSRApp = (...args) => {\n  const app = ensureHydrationRenderer().createApp(...args);\n  {\n    injectNativeTagCheck(app);\n    injectCompilerOptionsCheck(app);\n  }\n  const { mount } = app;\n  app.mount = (containerOrSelector) => {\n    const container = normalizeContainer(containerOrSelector);\n    if (container) {\n      return mount(container, true, resolveRootNamespace(container));\n    }\n  };\n  return app;\n};\nfunction resolveRootNamespace(container) {\n  if (container instanceof SVGElement) {\n    return \"svg\";\n  }\n  if (typeof MathMLElement === \"function\" && container instanceof MathMLElement) {\n    return \"mathml\";\n  }\n}\nfunction injectNativeTagCheck(app) {\n  Object.defineProperty(app.config, \"isNativeTag\", {\n    value: (tag) => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),\n    writable: false\n  });\n}\nfunction injectCompilerOptionsCheck(app) {\n  if (isRuntimeOnly()) {\n    const isCustomElement = app.config.isCustomElement;\n    Object.defineProperty(app.config, \"isCustomElement\", {\n      get() {\n        return isCustomElement;\n      },\n      set() {\n        warn(\n          `The \\`isCustomElement\\` config option is deprecated. Use \\`compilerOptions.isCustomElement\\` instead.`\n        );\n      }\n    });\n    const compilerOptions = app.config.compilerOptions;\n    const msg = `The \\`compilerOptions\\` config option is only respected when using a build of Vue.js that includes the runtime compiler (aka \"full build\"). Since you are using the runtime-only build, \\`compilerOptions\\` must be passed to \\`@vue/compiler-dom\\` in the build setup instead.\n- For vue-loader: pass it via vue-loader's \\`compilerOptions\\` loader option.\n- For vue-cli: see https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-loader\n- For vite: pass it via @vitejs/plugin-vue options. See https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue#example-for-passing-options-to-vuecompiler-sfc`;\n    Object.defineProperty(app.config, \"compilerOptions\", {\n      get() {\n        warn(msg);\n        return compilerOptions;\n      },\n      set() {\n        warn(msg);\n      }\n    });\n  }\n}\nfunction normalizeContainer(container) {\n  if (isString(container)) {\n    const res = document.querySelector(container);\n    if (!res) {\n      warn(\n        `Failed to mount app: mount target selector \"${container}\" returned null.`\n      );\n    }\n    return res;\n  }\n  if (window.ShadowRoot && container instanceof window.ShadowRoot && container.mode === \"closed\") {\n    warn(\n      `mounting on a ShadowRoot with \\`{mode: \"closed\"}\\` may lead to unpredictable bugs`\n    );\n  }\n  return container;\n}\nconst initDirectivesForSSR = NOOP;\n\nvar runtimeDom = /*#__PURE__*/Object.freeze({\n  __proto__: null,\n  BaseTransition: BaseTransition,\n  BaseTransitionPropsValidators: BaseTransitionPropsValidators,\n  Comment: Comment,\n  DeprecationTypes: DeprecationTypes,\n  EffectScope: EffectScope,\n  ErrorCodes: ErrorCodes,\n  ErrorTypeStrings: ErrorTypeStrings,\n  Fragment: Fragment,\n  KeepAlive: KeepAlive,\n  ReactiveEffect: ReactiveEffect,\n  Static: Static,\n  Suspense: Suspense,\n  Teleport: Teleport,\n  Text: Text,\n  TrackOpTypes: TrackOpTypes,\n  Transition: Transition,\n  TransitionGroup: TransitionGroup,\n  TriggerOpTypes: TriggerOpTypes,\n  VueElement: VueElement,\n  assertNumber: assertNumber,\n  callWithAsyncErrorHandling: callWithAsyncErrorHandling,\n  callWithErrorHandling: callWithErrorHandling,\n  camelize: camelize,\n  capitalize: capitalize,\n  cloneVNode: cloneVNode,\n  compatUtils: compatUtils,\n  computed: computed,\n  createApp: createApp,\n  createBlock: createBlock,\n  createCommentVNode: createCommentVNode,\n  createElementBlock: createElementBlock,\n  createElementVNode: createBaseVNode,\n  createHydrationRenderer: createHydrationRenderer,\n  createPropsRestProxy: createPropsRestProxy,\n  createRenderer: createRenderer,\n  createSSRApp: createSSRApp,\n  createSlots: createSlots,\n  createStaticVNode: createStaticVNode,\n  createTextVNode: createTextVNode,\n  createVNode: createVNode,\n  customRef: customRef,\n  defineAsyncComponent: defineAsyncComponent,\n  defineComponent: defineComponent,\n  defineCustomElement: defineCustomElement,\n  defineEmits: defineEmits,\n  defineExpose: defineExpose,\n  defineModel: defineModel,\n  defineOptions: defineOptions,\n  defineProps: defineProps,\n  defineSSRCustomElement: defineSSRCustomElement,\n  defineSlots: defineSlots,\n  devtools: devtools,\n  effect: effect,\n  effectScope: effectScope,\n  getCurrentInstance: getCurrentInstance,\n  getCurrentScope: getCurrentScope,\n  getTransitionRawChildren: getTransitionRawChildren,\n  guardReactiveProps: guardReactiveProps,\n  h: h,\n  handleError: handleError,\n  hasInjectionContext: hasInjectionContext,\n  hydrate: hydrate,\n  initCustomFormatter: initCustomFormatter,\n  initDirectivesForSSR: initDirectivesForSSR,\n  inject: inject,\n  isMemoSame: isMemoSame,\n  isProxy: isProxy,\n  isReactive: isReactive,\n  isReadonly: isReadonly,\n  isRef: isRef,\n  isRuntimeOnly: isRuntimeOnly,\n  isShallow: isShallow,\n  isVNode: isVNode,\n  markRaw: markRaw,\n  mergeDefaults: mergeDefaults,\n  mergeModels: mergeModels,\n  mergeProps: mergeProps,\n  nextTick: nextTick,\n  normalizeClass: normalizeClass,\n  normalizeProps: normalizeProps,\n  normalizeStyle: normalizeStyle,\n  onActivated: onActivated,\n  onBeforeMount: onBeforeMount,\n  onBeforeUnmount: onBeforeUnmount,\n  onBeforeUpdate: onBeforeUpdate,\n  onDeactivated: onDeactivated,\n  onErrorCaptured: onErrorCaptured,\n  onMounted: onMounted,\n  onRenderTracked: onRenderTracked,\n  onRenderTriggered: onRenderTriggered,\n  onScopeDispose: onScopeDispose,\n  onServerPrefetch: onServerPrefetch,\n  onUnmounted: onUnmounted,\n  onUpdated: onUpdated,\n  openBlock: openBlock,\n  popScopeId: popScopeId,\n  provide: provide,\n  proxyRefs: proxyRefs,\n  pushScopeId: pushScopeId,\n  queuePostFlushCb: queuePostFlushCb,\n  reactive: reactive,\n  readonly: readonly,\n  ref: ref,\n  registerRuntimeCompiler: registerRuntimeCompiler,\n  render: render,\n  renderList: renderList,\n  renderSlot: renderSlot,\n  resolveComponent: resolveComponent,\n  resolveDirective: resolveDirective,\n  resolveDynamicComponent: resolveDynamicComponent,\n  resolveFilter: resolveFilter,\n  resolveTransitionHooks: resolveTransitionHooks,\n  setBlockTracking: setBlockTracking,\n  setDevtoolsHook: setDevtoolsHook,\n  setTransitionHooks: setTransitionHooks,\n  shallowReactive: shallowReactive,\n  shallowReadonly: shallowReadonly,\n  shallowRef: shallowRef,\n  ssrContextKey: ssrContextKey,\n  ssrUtils: ssrUtils,\n  stop: stop,\n  toDisplayString: toDisplayString,\n  toHandlerKey: toHandlerKey,\n  toHandlers: toHandlers,\n  toRaw: toRaw,\n  toRef: toRef,\n  toRefs: toRefs,\n  toValue: toValue,\n  transformVNodeArgs: transformVNodeArgs,\n  triggerRef: triggerRef,\n  unref: unref,\n  useAttrs: useAttrs,\n  useCssModule: useCssModule,\n  useCssVars: useCssVars,\n  useModel: useModel,\n  useSSRContext: useSSRContext,\n  useSlots: useSlots,\n  useTransitionState: useTransitionState,\n  vModelCheckbox: vModelCheckbox,\n  vModelDynamic: vModelDynamic,\n  vModelRadio: vModelRadio,\n  vModelSelect: vModelSelect,\n  vModelText: vModelText,\n  vShow: vShow,\n  version: version,\n  warn: warn,\n  watch: watch,\n  watchEffect: watchEffect,\n  watchPostEffect: watchPostEffect,\n  watchSyncEffect: watchSyncEffect,\n  withAsyncContext: withAsyncContext,\n  withCtx: withCtx,\n  withDefaults: withDefaults,\n  withDirectives: withDirectives,\n  withKeys: withKeys,\n  withMemo: withMemo,\n  withModifiers: withModifiers,\n  withScopeId: withScopeId\n});\n\nfunction initDev() {\n  {\n    {\n      console.info(\n        `You are running a development build of Vue.\nMake sure to use the production build (*.prod.js) when deploying for production.`\n      );\n    }\n    initCustomFormatter();\n  }\n}\n\nconst FRAGMENT = Symbol(`Fragment` );\nconst TELEPORT = Symbol(`Teleport` );\nconst SUSPENSE = Symbol(`Suspense` );\nconst KEEP_ALIVE = Symbol(`KeepAlive` );\nconst BASE_TRANSITION = Symbol(`BaseTransition` );\nconst OPEN_BLOCK = Symbol(`openBlock` );\nconst CREATE_BLOCK = Symbol(`createBlock` );\nconst CREATE_ELEMENT_BLOCK = Symbol(`createElementBlock` );\nconst CREATE_VNODE = Symbol(`createVNode` );\nconst CREATE_ELEMENT_VNODE = Symbol(`createElementVNode` );\nconst CREATE_COMMENT = Symbol(`createCommentVNode` );\nconst CREATE_TEXT = Symbol(`createTextVNode` );\nconst CREATE_STATIC = Symbol(`createStaticVNode` );\nconst RESOLVE_COMPONENT = Symbol(`resolveComponent` );\nconst RESOLVE_DYNAMIC_COMPONENT = Symbol(\n  `resolveDynamicComponent` \n);\nconst RESOLVE_DIRECTIVE = Symbol(`resolveDirective` );\nconst RESOLVE_FILTER = Symbol(`resolveFilter` );\nconst WITH_DIRECTIVES = Symbol(`withDirectives` );\nconst RENDER_LIST = Symbol(`renderList` );\nconst RENDER_SLOT = Symbol(`renderSlot` );\nconst CREATE_SLOTS = Symbol(`createSlots` );\nconst TO_DISPLAY_STRING = Symbol(`toDisplayString` );\nconst MERGE_PROPS = Symbol(`mergeProps` );\nconst NORMALIZE_CLASS = Symbol(`normalizeClass` );\nconst NORMALIZE_STYLE = Symbol(`normalizeStyle` );\nconst NORMALIZE_PROPS = Symbol(`normalizeProps` );\nconst GUARD_REACTIVE_PROPS = Symbol(`guardReactiveProps` );\nconst TO_HANDLERS = Symbol(`toHandlers` );\nconst CAMELIZE = Symbol(`camelize` );\nconst CAPITALIZE = Symbol(`capitalize` );\nconst TO_HANDLER_KEY = Symbol(`toHandlerKey` );\nconst SET_BLOCK_TRACKING = Symbol(`setBlockTracking` );\nconst PUSH_SCOPE_ID = Symbol(`pushScopeId` );\nconst POP_SCOPE_ID = Symbol(`popScopeId` );\nconst WITH_CTX = Symbol(`withCtx` );\nconst UNREF = Symbol(`unref` );\nconst IS_REF = Symbol(`isRef` );\nconst WITH_MEMO = Symbol(`withMemo` );\nconst IS_MEMO_SAME = Symbol(`isMemoSame` );\nconst helperNameMap = {\n  [FRAGMENT]: `Fragment`,\n  [TELEPORT]: `Teleport`,\n  [SUSPENSE]: `Suspense`,\n  [KEEP_ALIVE]: `KeepAlive`,\n  [BASE_TRANSITION]: `BaseTransition`,\n  [OPEN_BLOCK]: `openBlock`,\n  [CREATE_BLOCK]: `createBlock`,\n  [CREATE_ELEMENT_BLOCK]: `createElementBlock`,\n  [CREATE_VNODE]: `createVNode`,\n  [CREATE_ELEMENT_VNODE]: `createElementVNode`,\n  [CREATE_COMMENT]: `createCommentVNode`,\n  [CREATE_TEXT]: `createTextVNode`,\n  [CREATE_STATIC]: `createStaticVNode`,\n  [RESOLVE_COMPONENT]: `resolveComponent`,\n  [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,\n  [RESOLVE_DIRECTIVE]: `resolveDirective`,\n  [RESOLVE_FILTER]: `resolveFilter`,\n  [WITH_DIRECTIVES]: `withDirectives`,\n  [RENDER_LIST]: `renderList`,\n  [RENDER_SLOT]: `renderSlot`,\n  [CREATE_SLOTS]: `createSlots`,\n  [TO_DISPLAY_STRING]: `toDisplayString`,\n  [MERGE_PROPS]: `mergeProps`,\n  [NORMALIZE_CLASS]: `normalizeClass`,\n  [NORMALIZE_STYLE]: `normalizeStyle`,\n  [NORMALIZE_PROPS]: `normalizeProps`,\n  [GUARD_REACTIVE_PROPS]: `guardReactiveProps`,\n  [TO_HANDLERS]: `toHandlers`,\n  [CAMELIZE]: `camelize`,\n  [CAPITALIZE]: `capitalize`,\n  [TO_HANDLER_KEY]: `toHandlerKey`,\n  [SET_BLOCK_TRACKING]: `setBlockTracking`,\n  [PUSH_SCOPE_ID]: `pushScopeId`,\n  [POP_SCOPE_ID]: `popScopeId`,\n  [WITH_CTX]: `withCtx`,\n  [UNREF]: `unref`,\n  [IS_REF]: `isRef`,\n  [WITH_MEMO]: `withMemo`,\n  [IS_MEMO_SAME]: `isMemoSame`\n};\nfunction registerRuntimeHelpers(helpers) {\n  Object.getOwnPropertySymbols(helpers).forEach((s) => {\n    helperNameMap[s] = helpers[s];\n  });\n}\n\nconst locStub = {\n  start: { line: 1, column: 1, offset: 0 },\n  end: { line: 1, column: 1, offset: 0 },\n  source: \"\"\n};\nfunction createRoot(children, source = \"\") {\n  return {\n    type: 0,\n    source,\n    children,\n    helpers: /* @__PURE__ */ new Set(),\n    components: [],\n    directives: [],\n    hoists: [],\n    imports: [],\n    cached: 0,\n    temps: 0,\n    codegenNode: void 0,\n    loc: locStub\n  };\n}\nfunction createVNodeCall(context, tag, props, children, patchFlag, dynamicProps, directives, isBlock = false, disableTracking = false, isComponent = false, loc = locStub) {\n  if (context) {\n    if (isBlock) {\n      context.helper(OPEN_BLOCK);\n      context.helper(getVNodeBlockHelper(context.inSSR, isComponent));\n    } else {\n      context.helper(getVNodeHelper(context.inSSR, isComponent));\n    }\n    if (directives) {\n      context.helper(WITH_DIRECTIVES);\n    }\n  }\n  return {\n    type: 13,\n    tag,\n    props,\n    children,\n    patchFlag,\n    dynamicProps,\n    directives,\n    isBlock,\n    disableTracking,\n    isComponent,\n    loc\n  };\n}\nfunction createArrayExpression(elements, loc = locStub) {\n  return {\n    type: 17,\n    loc,\n    elements\n  };\n}\nfunction createObjectExpression(properties, loc = locStub) {\n  return {\n    type: 15,\n    loc,\n    properties\n  };\n}\nfunction createObjectProperty(key, value) {\n  return {\n    type: 16,\n    loc: locStub,\n    key: isString(key) ? createSimpleExpression(key, true) : key,\n    value\n  };\n}\nfunction createSimpleExpression(content, isStatic = false, loc = locStub, constType = 0) {\n  return {\n    type: 4,\n    loc,\n    content,\n    isStatic,\n    constType: isStatic ? 3 : constType\n  };\n}\nfunction createCompoundExpression(children, loc = locStub) {\n  return {\n    type: 8,\n    loc,\n    children\n  };\n}\nfunction createCallExpression(callee, args = [], loc = locStub) {\n  return {\n    type: 14,\n    loc,\n    callee,\n    arguments: args\n  };\n}\nfunction createFunctionExpression(params, returns = void 0, newline = false, isSlot = false, loc = locStub) {\n  return {\n    type: 18,\n    params,\n    returns,\n    newline,\n    isSlot,\n    loc\n  };\n}\nfunction createConditionalExpression(test, consequent, alternate, newline = true) {\n  return {\n    type: 19,\n    test,\n    consequent,\n    alternate,\n    newline,\n    loc: locStub\n  };\n}\nfunction createCacheExpression(index, value, isVOnce = false) {\n  return {\n    type: 20,\n    index,\n    value,\n    isVOnce,\n    loc: locStub\n  };\n}\nfunction createBlockStatement(body) {\n  return {\n    type: 21,\n    body,\n    loc: locStub\n  };\n}\nfunction getVNodeHelper(ssr, isComponent) {\n  return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE;\n}\nfunction getVNodeBlockHelper(ssr, isComponent) {\n  return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK;\n}\nfunction convertToBlock(node, { helper, removeHelper, inSSR }) {\n  if (!node.isBlock) {\n    node.isBlock = true;\n    removeHelper(getVNodeHelper(inSSR, node.isComponent));\n    helper(OPEN_BLOCK);\n    helper(getVNodeBlockHelper(inSSR, node.isComponent));\n  }\n}\n\nconst defaultDelimitersOpen = new Uint8Array([123, 123]);\nconst defaultDelimitersClose = new Uint8Array([125, 125]);\nfunction isTagStartChar(c) {\n  return c >= 97 && c <= 122 || c >= 65 && c <= 90;\n}\nfunction isWhitespace(c) {\n  return c === 32 || c === 10 || c === 9 || c === 12 || c === 13;\n}\nfunction isEndOfTagSection(c) {\n  return c === 47 || c === 62 || isWhitespace(c);\n}\nfunction toCharCodes(str) {\n  const ret = new Uint8Array(str.length);\n  for (let i = 0; i < str.length; i++) {\n    ret[i] = str.charCodeAt(i);\n  }\n  return ret;\n}\nconst Sequences = {\n  Cdata: new Uint8Array([67, 68, 65, 84, 65, 91]),\n  // CDATA[\n  CdataEnd: new Uint8Array([93, 93, 62]),\n  // ]]>\n  CommentEnd: new Uint8Array([45, 45, 62]),\n  // `-->`\n  ScriptEnd: new Uint8Array([60, 47, 115, 99, 114, 105, 112, 116]),\n  // `<\\/script`\n  StyleEnd: new Uint8Array([60, 47, 115, 116, 121, 108, 101]),\n  // `</style`\n  TitleEnd: new Uint8Array([60, 47, 116, 105, 116, 108, 101]),\n  // `</title`\n  TextareaEnd: new Uint8Array([\n    60,\n    47,\n    116,\n    101,\n    120,\n    116,\n    97,\n    114,\n    101,\n    97\n  ])\n  // `</textarea\n};\nclass Tokenizer {\n  constructor(stack, cbs) {\n    this.stack = stack;\n    this.cbs = cbs;\n    /** The current state the tokenizer is in. */\n    this.state = 1;\n    /** The read buffer. */\n    this.buffer = \"\";\n    /** The beginning of the section that is currently being read. */\n    this.sectionStart = 0;\n    /** The index within the buffer that we are currently looking at. */\n    this.index = 0;\n    /** The start of the last entity. */\n    this.entityStart = 0;\n    /** Some behavior, eg. when decoding entities, is done while we are in another state. This keeps track of the other state type. */\n    this.baseState = 1;\n    /** For special parsing behavior inside of script and style tags. */\n    this.inRCDATA = false;\n    /** For disabling RCDATA tags handling */\n    this.inXML = false;\n    /** For disabling interpolation parsing in v-pre */\n    this.inVPre = false;\n    /** Record newline positions for fast line / column calculation */\n    this.newlines = [];\n    this.mode = 0;\n    this.delimiterOpen = defaultDelimitersOpen;\n    this.delimiterClose = defaultDelimitersClose;\n    this.delimiterIndex = -1;\n    this.currentSequence = void 0;\n    this.sequenceIndex = 0;\n  }\n  get inSFCRoot() {\n    return this.mode === 2 && this.stack.length === 0;\n  }\n  reset() {\n    this.state = 1;\n    this.mode = 0;\n    this.buffer = \"\";\n    this.sectionStart = 0;\n    this.index = 0;\n    this.baseState = 1;\n    this.inRCDATA = false;\n    this.currentSequence = void 0;\n    this.newlines.length = 0;\n    this.delimiterOpen = defaultDelimitersOpen;\n    this.delimiterClose = defaultDelimitersClose;\n  }\n  /**\n   * Generate Position object with line / column information using recorded\n   * newline positions. We know the index is always going to be an already\n   * processed index, so all the newlines up to this index should have been\n   * recorded.\n   */\n  getPos(index) {\n    let line = 1;\n    let column = index + 1;\n    for (let i = this.newlines.length - 1; i >= 0; i--) {\n      const newlineIndex = this.newlines[i];\n      if (index > newlineIndex) {\n        line = i + 2;\n        column = index - newlineIndex;\n        break;\n      }\n    }\n    return {\n      column,\n      line,\n      offset: index\n    };\n  }\n  peek() {\n    return this.buffer.charCodeAt(this.index + 1);\n  }\n  stateText(c) {\n    if (c === 60) {\n      if (this.index > this.sectionStart) {\n        this.cbs.ontext(this.sectionStart, this.index);\n      }\n      this.state = 5;\n      this.sectionStart = this.index;\n    } else if (!this.inVPre && c === this.delimiterOpen[0]) {\n      this.state = 2;\n      this.delimiterIndex = 0;\n      this.stateInterpolationOpen(c);\n    }\n  }\n  stateInterpolationOpen(c) {\n    if (c === this.delimiterOpen[this.delimiterIndex]) {\n      if (this.delimiterIndex === this.delimiterOpen.length - 1) {\n        const start = this.index + 1 - this.delimiterOpen.length;\n        if (start > this.sectionStart) {\n          this.cbs.ontext(this.sectionStart, start);\n        }\n        this.state = 3;\n        this.sectionStart = start;\n      } else {\n        this.delimiterIndex++;\n      }\n    } else if (this.inRCDATA) {\n      this.state = 32;\n      this.stateInRCDATA(c);\n    } else {\n      this.state = 1;\n      this.stateText(c);\n    }\n  }\n  stateInterpolation(c) {\n    if (c === this.delimiterClose[0]) {\n      this.state = 4;\n      this.delimiterIndex = 0;\n      this.stateInterpolationClose(c);\n    }\n  }\n  stateInterpolationClose(c) {\n    if (c === this.delimiterClose[this.delimiterIndex]) {\n      if (this.delimiterIndex === this.delimiterClose.length - 1) {\n        this.cbs.oninterpolation(this.sectionStart, this.index + 1);\n        if (this.inRCDATA) {\n          this.state = 32;\n        } else {\n          this.state = 1;\n        }\n        this.sectionStart = this.index + 1;\n      } else {\n        this.delimiterIndex++;\n      }\n    } else {\n      this.state = 3;\n      this.stateInterpolation(c);\n    }\n  }\n  stateSpecialStartSequence(c) {\n    const isEnd = this.sequenceIndex === this.currentSequence.length;\n    const isMatch = isEnd ? (\n      // If we are at the end of the sequence, make sure the tag name has ended\n      isEndOfTagSection(c)\n    ) : (\n      // Otherwise, do a case-insensitive comparison\n      (c | 32) === this.currentSequence[this.sequenceIndex]\n    );\n    if (!isMatch) {\n      this.inRCDATA = false;\n    } else if (!isEnd) {\n      this.sequenceIndex++;\n      return;\n    }\n    this.sequenceIndex = 0;\n    this.state = 6;\n    this.stateInTagName(c);\n  }\n  /** Look for an end tag. For <title> and <textarea>, also decode entities. */\n  stateInRCDATA(c) {\n    if (this.sequenceIndex === this.currentSequence.length) {\n      if (c === 62 || isWhitespace(c)) {\n        const endOfText = this.index - this.currentSequence.length;\n        if (this.sectionStart < endOfText) {\n          const actualIndex = this.index;\n          this.index = endOfText;\n          this.cbs.ontext(this.sectionStart, endOfText);\n          this.index = actualIndex;\n        }\n        this.sectionStart = endOfText + 2;\n        this.stateInClosingTagName(c);\n        this.inRCDATA = false;\n        return;\n      }\n      this.sequenceIndex = 0;\n    }\n    if ((c | 32) === this.currentSequence[this.sequenceIndex]) {\n      this.sequenceIndex += 1;\n    } else if (this.sequenceIndex === 0) {\n      if (this.currentSequence === Sequences.TitleEnd || this.currentSequence === Sequences.TextareaEnd && !this.inSFCRoot) {\n        if (c === this.delimiterOpen[0]) {\n          this.state = 2;\n          this.delimiterIndex = 0;\n          this.stateInterpolationOpen(c);\n        }\n      } else if (this.fastForwardTo(60)) {\n        this.sequenceIndex = 1;\n      }\n    } else {\n      this.sequenceIndex = Number(c === 60);\n    }\n  }\n  stateCDATASequence(c) {\n    if (c === Sequences.Cdata[this.sequenceIndex]) {\n      if (++this.sequenceIndex === Sequences.Cdata.length) {\n        this.state = 28;\n        this.currentSequence = Sequences.CdataEnd;\n        this.sequenceIndex = 0;\n        this.sectionStart = this.index + 1;\n      }\n    } else {\n      this.sequenceIndex = 0;\n      this.state = 23;\n      this.stateInDeclaration(c);\n    }\n  }\n  /**\n   * When we wait for one specific character, we can speed things up\n   * by skipping through the buffer until we find it.\n   *\n   * @returns Whether the character was found.\n   */\n  fastForwardTo(c) {\n    while (++this.index < this.buffer.length) {\n      const cc = this.buffer.charCodeAt(this.index);\n      if (cc === 10) {\n        this.newlines.push(this.index);\n      }\n      if (cc === c) {\n        return true;\n      }\n    }\n    this.index = this.buffer.length - 1;\n    return false;\n  }\n  /**\n   * Comments and CDATA end with `-->` and `]]>`.\n   *\n   * Their common qualities are:\n   * - Their end sequences have a distinct character they start with.\n   * - That character is then repeated, so we have to check multiple repeats.\n   * - All characters but the start character of the sequence can be skipped.\n   */\n  stateInCommentLike(c) {\n    if (c === this.currentSequence[this.sequenceIndex]) {\n      if (++this.sequenceIndex === this.currentSequence.length) {\n        if (this.currentSequence === Sequences.CdataEnd) {\n          this.cbs.oncdata(this.sectionStart, this.index - 2);\n        } else {\n          this.cbs.oncomment(this.sectionStart, this.index - 2);\n        }\n        this.sequenceIndex = 0;\n        this.sectionStart = this.index + 1;\n        this.state = 1;\n      }\n    } else if (this.sequenceIndex === 0) {\n      if (this.fastForwardTo(this.currentSequence[0])) {\n        this.sequenceIndex = 1;\n      }\n    } else if (c !== this.currentSequence[this.sequenceIndex - 1]) {\n      this.sequenceIndex = 0;\n    }\n  }\n  startSpecial(sequence, offset) {\n    this.enterRCDATA(sequence, offset);\n    this.state = 31;\n  }\n  enterRCDATA(sequence, offset) {\n    this.inRCDATA = true;\n    this.currentSequence = sequence;\n    this.sequenceIndex = offset;\n  }\n  stateBeforeTagName(c) {\n    if (c === 33) {\n      this.state = 22;\n      this.sectionStart = this.index + 1;\n    } else if (c === 63) {\n      this.state = 24;\n      this.sectionStart = this.index + 1;\n    } else if (isTagStartChar(c)) {\n      this.sectionStart = this.index;\n      if (this.mode === 0) {\n        this.state = 6;\n      } else if (this.inSFCRoot) {\n        this.state = 34;\n      } else if (!this.inXML) {\n        if (c === 116) {\n          this.state = 30;\n        } else {\n          this.state = c === 115 ? 29 : 6;\n        }\n      } else {\n        this.state = 6;\n      }\n    } else if (c === 47) {\n      this.state = 8;\n    } else {\n      this.state = 1;\n      this.stateText(c);\n    }\n  }\n  stateInTagName(c) {\n    if (isEndOfTagSection(c)) {\n      this.handleTagName(c);\n    }\n  }\n  stateInSFCRootTagName(c) {\n    if (isEndOfTagSection(c)) {\n      const tag = this.buffer.slice(this.sectionStart, this.index);\n      if (tag !== \"template\") {\n        this.enterRCDATA(toCharCodes(`</` + tag), 0);\n      }\n      this.handleTagName(c);\n    }\n  }\n  handleTagName(c) {\n    this.cbs.onopentagname(this.sectionStart, this.index);\n    this.sectionStart = -1;\n    this.state = 11;\n    this.stateBeforeAttrName(c);\n  }\n  stateBeforeClosingTagName(c) {\n    if (isWhitespace(c)) ; else if (c === 62) {\n      {\n        this.cbs.onerr(14, this.index);\n      }\n      this.state = 1;\n      this.sectionStart = this.index + 1;\n    } else {\n      this.state = isTagStartChar(c) ? 9 : 27;\n      this.sectionStart = this.index;\n    }\n  }\n  stateInClosingTagName(c) {\n    if (c === 62 || isWhitespace(c)) {\n      this.cbs.onclosetag(this.sectionStart, this.index);\n      this.sectionStart = -1;\n      this.state = 10;\n      this.stateAfterClosingTagName(c);\n    }\n  }\n  stateAfterClosingTagName(c) {\n    if (c === 62) {\n      this.state = 1;\n      this.sectionStart = this.index + 1;\n    }\n  }\n  stateBeforeAttrName(c) {\n    if (c === 62) {\n      this.cbs.onopentagend(this.index);\n      if (this.inRCDATA) {\n        this.state = 32;\n      } else {\n        this.state = 1;\n      }\n      this.sectionStart = this.index + 1;\n    } else if (c === 47) {\n      this.state = 7;\n      if (this.peek() !== 62) {\n        this.cbs.onerr(22, this.index);\n      }\n    } else if (c === 60 && this.peek() === 47) {\n      this.cbs.onopentagend(this.index);\n      this.state = 5;\n      this.sectionStart = this.index;\n    } else if (!isWhitespace(c)) {\n      if (c === 61) {\n        this.cbs.onerr(\n          19,\n          this.index\n        );\n      }\n      this.handleAttrStart(c);\n    }\n  }\n  handleAttrStart(c) {\n    if (c === 118 && this.peek() === 45) {\n      this.state = 13;\n      this.sectionStart = this.index;\n    } else if (c === 46 || c === 58 || c === 64 || c === 35) {\n      this.cbs.ondirname(this.index, this.index + 1);\n      this.state = 14;\n      this.sectionStart = this.index + 1;\n    } else {\n      this.state = 12;\n      this.sectionStart = this.index;\n    }\n  }\n  stateInSelfClosingTag(c) {\n    if (c === 62) {\n      this.cbs.onselfclosingtag(this.index);\n      this.state = 1;\n      this.sectionStart = this.index + 1;\n      this.inRCDATA = false;\n    } else if (!isWhitespace(c)) {\n      this.state = 11;\n      this.stateBeforeAttrName(c);\n    }\n  }\n  stateInAttrName(c) {\n    if (c === 61 || isEndOfTagSection(c)) {\n      this.cbs.onattribname(this.sectionStart, this.index);\n      this.handleAttrNameEnd(c);\n    } else if (c === 34 || c === 39 || c === 60) {\n      this.cbs.onerr(\n        17,\n        this.index\n      );\n    }\n  }\n  stateInDirName(c) {\n    if (c === 61 || isEndOfTagSection(c)) {\n      this.cbs.ondirname(this.sectionStart, this.index);\n      this.handleAttrNameEnd(c);\n    } else if (c === 58) {\n      this.cbs.ondirname(this.sectionStart, this.index);\n      this.state = 14;\n      this.sectionStart = this.index + 1;\n    } else if (c === 46) {\n      this.cbs.ondirname(this.sectionStart, this.index);\n      this.state = 16;\n      this.sectionStart = this.index + 1;\n    }\n  }\n  stateInDirArg(c) {\n    if (c === 61 || isEndOfTagSection(c)) {\n      this.cbs.ondirarg(this.sectionStart, this.index);\n      this.handleAttrNameEnd(c);\n    } else if (c === 91) {\n      this.state = 15;\n    } else if (c === 46) {\n      this.cbs.ondirarg(this.sectionStart, this.index);\n      this.state = 16;\n      this.sectionStart = this.index + 1;\n    }\n  }\n  stateInDynamicDirArg(c) {\n    if (c === 93) {\n      this.state = 14;\n    } else if (c === 61 || isEndOfTagSection(c)) {\n      this.cbs.ondirarg(this.sectionStart, this.index + 1);\n      this.handleAttrNameEnd(c);\n      {\n        this.cbs.onerr(\n          27,\n          this.index\n        );\n      }\n    }\n  }\n  stateInDirModifier(c) {\n    if (c === 61 || isEndOfTagSection(c)) {\n      this.cbs.ondirmodifier(this.sectionStart, this.index);\n      this.handleAttrNameEnd(c);\n    } else if (c === 46) {\n      this.cbs.ondirmodifier(this.sectionStart, this.index);\n      this.sectionStart = this.index + 1;\n    }\n  }\n  handleAttrNameEnd(c) {\n    this.sectionStart = this.index;\n    this.state = 17;\n    this.cbs.onattribnameend(this.index);\n    this.stateAfterAttrName(c);\n  }\n  stateAfterAttrName(c) {\n    if (c === 61) {\n      this.state = 18;\n    } else if (c === 47 || c === 62) {\n      this.cbs.onattribend(0, this.sectionStart);\n      this.sectionStart = -1;\n      this.state = 11;\n      this.stateBeforeAttrName(c);\n    } else if (!isWhitespace(c)) {\n      this.cbs.onattribend(0, this.sectionStart);\n      this.handleAttrStart(c);\n    }\n  }\n  stateBeforeAttrValue(c) {\n    if (c === 34) {\n      this.state = 19;\n      this.sectionStart = this.index + 1;\n    } else if (c === 39) {\n      this.state = 20;\n      this.sectionStart = this.index + 1;\n    } else if (!isWhitespace(c)) {\n      this.sectionStart = this.index;\n      this.state = 21;\n      this.stateInAttrValueNoQuotes(c);\n    }\n  }\n  handleInAttrValue(c, quote) {\n    if (c === quote || this.fastForwardTo(quote)) {\n      this.cbs.onattribdata(this.sectionStart, this.index);\n      this.sectionStart = -1;\n      this.cbs.onattribend(\n        quote === 34 ? 3 : 2,\n        this.index + 1\n      );\n      this.state = 11;\n    }\n  }\n  stateInAttrValueDoubleQuotes(c) {\n    this.handleInAttrValue(c, 34);\n  }\n  stateInAttrValueSingleQuotes(c) {\n    this.handleInAttrValue(c, 39);\n  }\n  stateInAttrValueNoQuotes(c) {\n    if (isWhitespace(c) || c === 62) {\n      this.cbs.onattribdata(this.sectionStart, this.index);\n      this.sectionStart = -1;\n      this.cbs.onattribend(1, this.index);\n      this.state = 11;\n      this.stateBeforeAttrName(c);\n    } else if (c === 34 || c === 39 || c === 60 || c === 61 || c === 96) {\n      this.cbs.onerr(\n        18,\n        this.index\n      );\n    } else ;\n  }\n  stateBeforeDeclaration(c) {\n    if (c === 91) {\n      this.state = 26;\n      this.sequenceIndex = 0;\n    } else {\n      this.state = c === 45 ? 25 : 23;\n    }\n  }\n  stateInDeclaration(c) {\n    if (c === 62 || this.fastForwardTo(62)) {\n      this.state = 1;\n      this.sectionStart = this.index + 1;\n    }\n  }\n  stateInProcessingInstruction(c) {\n    if (c === 62 || this.fastForwardTo(62)) {\n      this.cbs.onprocessinginstruction(this.sectionStart, this.index);\n      this.state = 1;\n      this.sectionStart = this.index + 1;\n    }\n  }\n  stateBeforeComment(c) {\n    if (c === 45) {\n      this.state = 28;\n      this.currentSequence = Sequences.CommentEnd;\n      this.sequenceIndex = 2;\n      this.sectionStart = this.index + 1;\n    } else {\n      this.state = 23;\n    }\n  }\n  stateInSpecialComment(c) {\n    if (c === 62 || this.fastForwardTo(62)) {\n      this.cbs.oncomment(this.sectionStart, this.index);\n      this.state = 1;\n      this.sectionStart = this.index + 1;\n    }\n  }\n  stateBeforeSpecialS(c) {\n    if (c === Sequences.ScriptEnd[3]) {\n      this.startSpecial(Sequences.ScriptEnd, 4);\n    } else if (c === Sequences.StyleEnd[3]) {\n      this.startSpecial(Sequences.StyleEnd, 4);\n    } else {\n      this.state = 6;\n      this.stateInTagName(c);\n    }\n  }\n  stateBeforeSpecialT(c) {\n    if (c === Sequences.TitleEnd[3]) {\n      this.startSpecial(Sequences.TitleEnd, 4);\n    } else if (c === Sequences.TextareaEnd[3]) {\n      this.startSpecial(Sequences.TextareaEnd, 4);\n    } else {\n      this.state = 6;\n      this.stateInTagName(c);\n    }\n  }\n  startEntity() {\n  }\n  stateInEntity() {\n  }\n  /**\n   * Iterates through the buffer, calling the function corresponding to the current state.\n   *\n   * States that are more likely to be hit are higher up, as a performance improvement.\n   */\n  parse(input) {\n    this.buffer = input;\n    while (this.index < this.buffer.length) {\n      const c = this.buffer.charCodeAt(this.index);\n      if (c === 10) {\n        this.newlines.push(this.index);\n      }\n      switch (this.state) {\n        case 1: {\n          this.stateText(c);\n          break;\n        }\n        case 2: {\n          this.stateInterpolationOpen(c);\n          break;\n        }\n        case 3: {\n          this.stateInterpolation(c);\n          break;\n        }\n        case 4: {\n          this.stateInterpolationClose(c);\n          break;\n        }\n        case 31: {\n          this.stateSpecialStartSequence(c);\n          break;\n        }\n        case 32: {\n          this.stateInRCDATA(c);\n          break;\n        }\n        case 26: {\n          this.stateCDATASequence(c);\n          break;\n        }\n        case 19: {\n          this.stateInAttrValueDoubleQuotes(c);\n          break;\n        }\n        case 12: {\n          this.stateInAttrName(c);\n          break;\n        }\n        case 13: {\n          this.stateInDirName(c);\n          break;\n        }\n        case 14: {\n          this.stateInDirArg(c);\n          break;\n        }\n        case 15: {\n          this.stateInDynamicDirArg(c);\n          break;\n        }\n        case 16: {\n          this.stateInDirModifier(c);\n          break;\n        }\n        case 28: {\n          this.stateInCommentLike(c);\n          break;\n        }\n        case 27: {\n          this.stateInSpecialComment(c);\n          break;\n        }\n        case 11: {\n          this.stateBeforeAttrName(c);\n          break;\n        }\n        case 6: {\n          this.stateInTagName(c);\n          break;\n        }\n        case 34: {\n          this.stateInSFCRootTagName(c);\n          break;\n        }\n        case 9: {\n          this.stateInClosingTagName(c);\n          break;\n        }\n        case 5: {\n          this.stateBeforeTagName(c);\n          break;\n        }\n        case 17: {\n          this.stateAfterAttrName(c);\n          break;\n        }\n        case 20: {\n          this.stateInAttrValueSingleQuotes(c);\n          break;\n        }\n        case 18: {\n          this.stateBeforeAttrValue(c);\n          break;\n        }\n        case 8: {\n          this.stateBeforeClosingTagName(c);\n          break;\n        }\n        case 10: {\n          this.stateAfterClosingTagName(c);\n          break;\n        }\n        case 29: {\n          this.stateBeforeSpecialS(c);\n          break;\n        }\n        case 30: {\n          this.stateBeforeSpecialT(c);\n          break;\n        }\n        case 21: {\n          this.stateInAttrValueNoQuotes(c);\n          break;\n        }\n        case 7: {\n          this.stateInSelfClosingTag(c);\n          break;\n        }\n        case 23: {\n          this.stateInDeclaration(c);\n          break;\n        }\n        case 22: {\n          this.stateBeforeDeclaration(c);\n          break;\n        }\n        case 25: {\n          this.stateBeforeComment(c);\n          break;\n        }\n        case 24: {\n          this.stateInProcessingInstruction(c);\n          break;\n        }\n        case 33: {\n          this.stateInEntity();\n          break;\n        }\n      }\n      this.index++;\n    }\n    this.cleanup();\n    this.finish();\n  }\n  /**\n   * Remove data that has already been consumed from the buffer.\n   */\n  cleanup() {\n    if (this.sectionStart !== this.index) {\n      if (this.state === 1 || this.state === 32 && this.sequenceIndex === 0) {\n        this.cbs.ontext(this.sectionStart, this.index);\n        this.sectionStart = this.index;\n      } else if (this.state === 19 || this.state === 20 || this.state === 21) {\n        this.cbs.onattribdata(this.sectionStart, this.index);\n        this.sectionStart = this.index;\n      }\n    }\n  }\n  finish() {\n    this.handleTrailingData();\n    this.cbs.onend();\n  }\n  /** Handle any trailing data. */\n  handleTrailingData() {\n    const endIndex = this.buffer.length;\n    if (this.sectionStart >= endIndex) {\n      return;\n    }\n    if (this.state === 28) {\n      if (this.currentSequence === Sequences.CdataEnd) {\n        this.cbs.oncdata(this.sectionStart, endIndex);\n      } else {\n        this.cbs.oncomment(this.sectionStart, endIndex);\n      }\n    } else if (this.state === 6 || this.state === 11 || this.state === 18 || this.state === 17 || this.state === 12 || this.state === 13 || this.state === 14 || this.state === 15 || this.state === 16 || this.state === 20 || this.state === 19 || this.state === 21 || this.state === 9) ; else {\n      this.cbs.ontext(this.sectionStart, endIndex);\n    }\n  }\n  emitCodePoint(cp, consumed) {\n  }\n}\n\nfunction defaultOnError(error) {\n  throw error;\n}\nfunction defaultOnWarn(msg) {\n  console.warn(`[Vue warn] ${msg.message}`);\n}\nfunction createCompilerError(code, loc, messages, additionalMessage) {\n  const msg = (messages || errorMessages)[code] + (additionalMessage || ``) ;\n  const error = new SyntaxError(String(msg));\n  error.code = code;\n  error.loc = loc;\n  return error;\n}\nconst errorMessages = {\n  // parse errors\n  [0]: \"Illegal comment.\",\n  [1]: \"CDATA section is allowed only in XML context.\",\n  [2]: \"Duplicate attribute.\",\n  [3]: \"End tag cannot have attributes.\",\n  [4]: \"Illegal '/' in tags.\",\n  [5]: \"Unexpected EOF in tag.\",\n  [6]: \"Unexpected EOF in CDATA section.\",\n  [7]: \"Unexpected EOF in comment.\",\n  [8]: \"Unexpected EOF in script.\",\n  [9]: \"Unexpected EOF in tag.\",\n  [10]: \"Incorrectly closed comment.\",\n  [11]: \"Incorrectly opened comment.\",\n  [12]: \"Illegal tag name. Use '&lt;' to print '<'.\",\n  [13]: \"Attribute value was expected.\",\n  [14]: \"End tag name was expected.\",\n  [15]: \"Whitespace was expected.\",\n  [16]: \"Unexpected '<!--' in comment.\",\n  [17]: `Attribute name cannot contain U+0022 (\"), U+0027 ('), and U+003C (<).`,\n  [18]: \"Unquoted attribute value cannot contain U+0022 (\\\"), U+0027 ('), U+003C (<), U+003D (=), and U+0060 (`).\",\n  [19]: \"Attribute name cannot start with '='.\",\n  [21]: \"'<?' is allowed only in XML context.\",\n  [20]: `Unexpected null character.`,\n  [22]: \"Illegal '/' in tags.\",\n  // Vue-specific parse errors\n  [23]: \"Invalid end tag.\",\n  [24]: \"Element is missing end tag.\",\n  [25]: \"Interpolation end sign was not found.\",\n  [27]: \"End bracket for dynamic directive argument was not found. Note that dynamic directive argument cannot contain spaces.\",\n  [26]: \"Legal directive name was expected.\",\n  // transform errors\n  [28]: `v-if/v-else-if is missing expression.`,\n  [29]: `v-if/else branches must use unique keys.`,\n  [30]: `v-else/v-else-if has no adjacent v-if or v-else-if.`,\n  [31]: `v-for is missing expression.`,\n  [32]: `v-for has invalid expression.`,\n  [33]: `<template v-for> key should be placed on the <template> tag.`,\n  [34]: `v-bind is missing expression.`,\n  [52]: `v-bind with same-name shorthand only allows static argument.`,\n  [35]: `v-on is missing expression.`,\n  [36]: `Unexpected custom directive on <slot> outlet.`,\n  [37]: `Mixed v-slot usage on both the component and nested <template>. When there are multiple named slots, all slots should use <template> syntax to avoid scope ambiguity.`,\n  [38]: `Duplicate slot names found. `,\n  [39]: `Extraneous children found when component already has explicitly named default slot. These children will be ignored.`,\n  [40]: `v-slot can only be used on components or <template> tags.`,\n  [41]: `v-model is missing expression.`,\n  [42]: `v-model value must be a valid JavaScript member expression.`,\n  [43]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,\n  [44]: `v-model cannot be used on a prop, because local prop bindings are not writable.\nUse a v-bind binding combined with a v-on listener that emits update:x event instead.`,\n  [45]: `Error parsing JavaScript expression: `,\n  [46]: `<KeepAlive> expects exactly one child component.`,\n  [51]: `@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,\n  // generic errors\n  [47]: `\"prefixIdentifiers\" option is not supported in this build of compiler.`,\n  [48]: `ES module mode is not supported in this build of compiler.`,\n  [49]: `\"cacheHandlers\" option is only supported when the \"prefixIdentifiers\" option is enabled.`,\n  [50]: `\"scopeId\" option is only supported in module mode.`,\n  // just to fulfill types\n  [53]: ``\n};\n\nconst isStaticExp = (p) => p.type === 4 && p.isStatic;\nfunction isCoreComponent(tag) {\n  switch (tag) {\n    case \"Teleport\":\n    case \"teleport\":\n      return TELEPORT;\n    case \"Suspense\":\n    case \"suspense\":\n      return SUSPENSE;\n    case \"KeepAlive\":\n    case \"keep-alive\":\n      return KEEP_ALIVE;\n    case \"BaseTransition\":\n    case \"base-transition\":\n      return BASE_TRANSITION;\n  }\n}\nconst nonIdentifierRE = /^\\d|[^\\$\\w\\xA0-\\uFFFF]/;\nconst isSimpleIdentifier = (name) => !nonIdentifierRE.test(name);\nconst validFirstIdentCharRE = /[A-Za-z_$\\xA0-\\uFFFF]/;\nconst validIdentCharRE = /[\\.\\?\\w$\\xA0-\\uFFFF]/;\nconst whitespaceRE = /\\s+[.[]\\s*|\\s*[.[]\\s+/g;\nconst isMemberExpressionBrowser = (path) => {\n  path = path.trim().replace(whitespaceRE, (s) => s.trim());\n  let state = 0 /* inMemberExp */;\n  let stateStack = [];\n  let currentOpenBracketCount = 0;\n  let currentOpenParensCount = 0;\n  let currentStringType = null;\n  for (let i = 0; i < path.length; i++) {\n    const char = path.charAt(i);\n    switch (state) {\n      case 0 /* inMemberExp */:\n        if (char === \"[\") {\n          stateStack.push(state);\n          state = 1 /* inBrackets */;\n          currentOpenBracketCount++;\n        } else if (char === \"(\") {\n          stateStack.push(state);\n          state = 2 /* inParens */;\n          currentOpenParensCount++;\n        } else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {\n          return false;\n        }\n        break;\n      case 1 /* inBrackets */:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = 3 /* inString */;\n          currentStringType = char;\n        } else if (char === `[`) {\n          currentOpenBracketCount++;\n        } else if (char === `]`) {\n          if (!--currentOpenBracketCount) {\n            state = stateStack.pop();\n          }\n        }\n        break;\n      case 2 /* inParens */:\n        if (char === `'` || char === `\"` || char === \"`\") {\n          stateStack.push(state);\n          state = 3 /* inString */;\n          currentStringType = char;\n        } else if (char === `(`) {\n          currentOpenParensCount++;\n        } else if (char === `)`) {\n          if (i === path.length - 1) {\n            return false;\n          }\n          if (!--currentOpenParensCount) {\n            state = stateStack.pop();\n          }\n        }\n        break;\n      case 3 /* inString */:\n        if (char === currentStringType) {\n          state = stateStack.pop();\n          currentStringType = null;\n        }\n        break;\n    }\n  }\n  return !currentOpenBracketCount && !currentOpenParensCount;\n};\nconst isMemberExpression = isMemberExpressionBrowser ;\nfunction assert(condition, msg) {\n  if (!condition) {\n    throw new Error(msg || `unexpected compiler condition`);\n  }\n}\nfunction findDir(node, name, allowEmpty = false) {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === 7 && (allowEmpty || p.exp) && (isString(name) ? p.name === name : name.test(p.name))) {\n      return p;\n    }\n  }\n}\nfunction findProp(node, name, dynamicOnly = false, allowEmpty = false) {\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === 6) {\n      if (dynamicOnly) continue;\n      if (p.name === name && (p.value || allowEmpty)) {\n        return p;\n      }\n    } else if (p.name === \"bind\" && (p.exp || allowEmpty) && isStaticArgOf(p.arg, name)) {\n      return p;\n    }\n  }\n}\nfunction isStaticArgOf(arg, name) {\n  return !!(arg && isStaticExp(arg) && arg.content === name);\n}\nfunction hasDynamicKeyVBind(node) {\n  return node.props.some(\n    (p) => p.type === 7 && p.name === \"bind\" && (!p.arg || // v-bind=\"obj\"\n    p.arg.type !== 4 || // v-bind:[_ctx.foo]\n    !p.arg.isStatic)\n    // v-bind:[foo]\n  );\n}\nfunction isText$1(node) {\n  return node.type === 5 || node.type === 2;\n}\nfunction isVSlot(p) {\n  return p.type === 7 && p.name === \"slot\";\n}\nfunction isTemplateNode(node) {\n  return node.type === 1 && node.tagType === 3;\n}\nfunction isSlotOutlet(node) {\n  return node.type === 1 && node.tagType === 2;\n}\nconst propsHelperSet = /* @__PURE__ */ new Set([NORMALIZE_PROPS, GUARD_REACTIVE_PROPS]);\nfunction getUnnormalizedProps(props, callPath = []) {\n  if (props && !isString(props) && props.type === 14) {\n    const callee = props.callee;\n    if (!isString(callee) && propsHelperSet.has(callee)) {\n      return getUnnormalizedProps(\n        props.arguments[0],\n        callPath.concat(props)\n      );\n    }\n  }\n  return [props, callPath];\n}\nfunction injectProp(node, prop, context) {\n  let propsWithInjection;\n  let props = node.type === 13 ? node.props : node.arguments[2];\n  let callPath = [];\n  let parentCall;\n  if (props && !isString(props) && props.type === 14) {\n    const ret = getUnnormalizedProps(props);\n    props = ret[0];\n    callPath = ret[1];\n    parentCall = callPath[callPath.length - 1];\n  }\n  if (props == null || isString(props)) {\n    propsWithInjection = createObjectExpression([prop]);\n  } else if (props.type === 14) {\n    const first = props.arguments[0];\n    if (!isString(first) && first.type === 15) {\n      if (!hasProp(prop, first)) {\n        first.properties.unshift(prop);\n      }\n    } else {\n      if (props.callee === TO_HANDLERS) {\n        propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [\n          createObjectExpression([prop]),\n          props\n        ]);\n      } else {\n        props.arguments.unshift(createObjectExpression([prop]));\n      }\n    }\n    !propsWithInjection && (propsWithInjection = props);\n  } else if (props.type === 15) {\n    if (!hasProp(prop, props)) {\n      props.properties.unshift(prop);\n    }\n    propsWithInjection = props;\n  } else {\n    propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [\n      createObjectExpression([prop]),\n      props\n    ]);\n    if (parentCall && parentCall.callee === GUARD_REACTIVE_PROPS) {\n      parentCall = callPath[callPath.length - 2];\n    }\n  }\n  if (node.type === 13) {\n    if (parentCall) {\n      parentCall.arguments[0] = propsWithInjection;\n    } else {\n      node.props = propsWithInjection;\n    }\n  } else {\n    if (parentCall) {\n      parentCall.arguments[0] = propsWithInjection;\n    } else {\n      node.arguments[2] = propsWithInjection;\n    }\n  }\n}\nfunction hasProp(prop, props) {\n  let result = false;\n  if (prop.key.type === 4) {\n    const propKeyName = prop.key.content;\n    result = props.properties.some(\n      (p) => p.key.type === 4 && p.key.content === propKeyName\n    );\n  }\n  return result;\n}\nfunction toValidAssetId(name, type) {\n  return `_${type}_${name.replace(/[^\\w]/g, (searchValue, replaceValue) => {\n    return searchValue === \"-\" ? \"_\" : name.charCodeAt(replaceValue).toString();\n  })}`;\n}\nfunction getMemoedVNodeCall(node) {\n  if (node.type === 14 && node.callee === WITH_MEMO) {\n    return node.arguments[1].returns;\n  } else {\n    return node;\n  }\n}\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+(\\S[\\s\\S]*)/;\n\nconst defaultParserOptions = {\n  parseMode: \"base\",\n  ns: 0,\n  delimiters: [`{{`, `}}`],\n  getNamespace: () => 0,\n  isVoidTag: NO,\n  isPreTag: NO,\n  isCustomElement: NO,\n  onError: defaultOnError,\n  onWarn: defaultOnWarn,\n  comments: true,\n  prefixIdentifiers: false\n};\nlet currentOptions = defaultParserOptions;\nlet currentRoot = null;\nlet currentInput = \"\";\nlet currentOpenTag = null;\nlet currentProp = null;\nlet currentAttrValue = \"\";\nlet currentAttrStartIndex = -1;\nlet currentAttrEndIndex = -1;\nlet inPre = 0;\nlet inVPre = false;\nlet currentVPreBoundary = null;\nconst stack = [];\nconst tokenizer = new Tokenizer(stack, {\n  onerr: emitError,\n  ontext(start, end) {\n    onText(getSlice(start, end), start, end);\n  },\n  ontextentity(char, start, end) {\n    onText(char, start, end);\n  },\n  oninterpolation(start, end) {\n    if (inVPre) {\n      return onText(getSlice(start, end), start, end);\n    }\n    let innerStart = start + tokenizer.delimiterOpen.length;\n    let innerEnd = end - tokenizer.delimiterClose.length;\n    while (isWhitespace(currentInput.charCodeAt(innerStart))) {\n      innerStart++;\n    }\n    while (isWhitespace(currentInput.charCodeAt(innerEnd - 1))) {\n      innerEnd--;\n    }\n    let exp = getSlice(innerStart, innerEnd);\n    if (exp.includes(\"&\")) {\n      {\n        exp = currentOptions.decodeEntities(exp, false);\n      }\n    }\n    addNode({\n      type: 5,\n      content: createExp(exp, false, getLoc(innerStart, innerEnd)),\n      loc: getLoc(start, end)\n    });\n  },\n  onopentagname(start, end) {\n    const name = getSlice(start, end);\n    currentOpenTag = {\n      type: 1,\n      tag: name,\n      ns: currentOptions.getNamespace(name, stack[0], currentOptions.ns),\n      tagType: 0,\n      // will be refined on tag close\n      props: [],\n      children: [],\n      loc: getLoc(start - 1, end),\n      codegenNode: void 0\n    };\n  },\n  onopentagend(end) {\n    endOpenTag(end);\n  },\n  onclosetag(start, end) {\n    const name = getSlice(start, end);\n    if (!currentOptions.isVoidTag(name)) {\n      let found = false;\n      for (let i = 0; i < stack.length; i++) {\n        const e = stack[i];\n        if (e.tag.toLowerCase() === name.toLowerCase()) {\n          found = true;\n          if (i > 0) {\n            emitError(24, stack[0].loc.start.offset);\n          }\n          for (let j = 0; j <= i; j++) {\n            const el = stack.shift();\n            onCloseTag(el, end, j < i);\n          }\n          break;\n        }\n      }\n      if (!found) {\n        emitError(23, backTrack(start, 60));\n      }\n    }\n  },\n  onselfclosingtag(end) {\n    const name = currentOpenTag.tag;\n    currentOpenTag.isSelfClosing = true;\n    endOpenTag(end);\n    if (stack[0] && stack[0].tag === name) {\n      onCloseTag(stack.shift(), end);\n    }\n  },\n  onattribname(start, end) {\n    currentProp = {\n      type: 6,\n      name: getSlice(start, end),\n      nameLoc: getLoc(start, end),\n      value: void 0,\n      loc: getLoc(start)\n    };\n  },\n  ondirname(start, end) {\n    const raw = getSlice(start, end);\n    const name = raw === \".\" || raw === \":\" ? \"bind\" : raw === \"@\" ? \"on\" : raw === \"#\" ? \"slot\" : raw.slice(2);\n    if (!inVPre && name === \"\") {\n      emitError(26, start);\n    }\n    if (inVPre || name === \"\") {\n      currentProp = {\n        type: 6,\n        name: raw,\n        nameLoc: getLoc(start, end),\n        value: void 0,\n        loc: getLoc(start)\n      };\n    } else {\n      currentProp = {\n        type: 7,\n        name,\n        rawName: raw,\n        exp: void 0,\n        arg: void 0,\n        modifiers: raw === \".\" ? [\"prop\"] : [],\n        loc: getLoc(start)\n      };\n      if (name === \"pre\") {\n        inVPre = tokenizer.inVPre = true;\n        currentVPreBoundary = currentOpenTag;\n        const props = currentOpenTag.props;\n        for (let i = 0; i < props.length; i++) {\n          if (props[i].type === 7) {\n            props[i] = dirToAttr(props[i]);\n          }\n        }\n      }\n    }\n  },\n  ondirarg(start, end) {\n    if (start === end) return;\n    const arg = getSlice(start, end);\n    if (inVPre) {\n      currentProp.name += arg;\n      setLocEnd(currentProp.nameLoc, end);\n    } else {\n      const isStatic = arg[0] !== `[`;\n      currentProp.arg = createExp(\n        isStatic ? arg : arg.slice(1, -1),\n        isStatic,\n        getLoc(start, end),\n        isStatic ? 3 : 0\n      );\n    }\n  },\n  ondirmodifier(start, end) {\n    const mod = getSlice(start, end);\n    if (inVPre) {\n      currentProp.name += \".\" + mod;\n      setLocEnd(currentProp.nameLoc, end);\n    } else if (currentProp.name === \"slot\") {\n      const arg = currentProp.arg;\n      if (arg) {\n        arg.content += \".\" + mod;\n        setLocEnd(arg.loc, end);\n      }\n    } else {\n      currentProp.modifiers.push(mod);\n    }\n  },\n  onattribdata(start, end) {\n    currentAttrValue += getSlice(start, end);\n    if (currentAttrStartIndex < 0) currentAttrStartIndex = start;\n    currentAttrEndIndex = end;\n  },\n  onattribentity(char, start, end) {\n    currentAttrValue += char;\n    if (currentAttrStartIndex < 0) currentAttrStartIndex = start;\n    currentAttrEndIndex = end;\n  },\n  onattribnameend(end) {\n    const start = currentProp.loc.start.offset;\n    const name = getSlice(start, end);\n    if (currentProp.type === 7) {\n      currentProp.rawName = name;\n    }\n    if (currentOpenTag.props.some(\n      (p) => (p.type === 7 ? p.rawName : p.name) === name\n    )) {\n      emitError(2, start);\n    }\n  },\n  onattribend(quote, end) {\n    if (currentOpenTag && currentProp) {\n      setLocEnd(currentProp.loc, end);\n      if (quote !== 0) {\n        if (currentAttrValue.includes(\"&\")) {\n          currentAttrValue = currentOptions.decodeEntities(\n            currentAttrValue,\n            true\n          );\n        }\n        if (currentProp.type === 6) {\n          if (currentProp.name === \"class\") {\n            currentAttrValue = condense(currentAttrValue).trim();\n          }\n          if (quote === 1 && !currentAttrValue) {\n            emitError(13, end);\n          }\n          currentProp.value = {\n            type: 2,\n            content: currentAttrValue,\n            loc: quote === 1 ? getLoc(currentAttrStartIndex, currentAttrEndIndex) : getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1)\n          };\n          if (tokenizer.inSFCRoot && currentOpenTag.tag === \"template\" && currentProp.name === \"lang\" && currentAttrValue && currentAttrValue !== \"html\") {\n            tokenizer.enterRCDATA(toCharCodes(`</template`), 0);\n          }\n        } else {\n          let expParseMode = 0 /* Normal */;\n          currentProp.exp = createExp(\n            currentAttrValue,\n            false,\n            getLoc(currentAttrStartIndex, currentAttrEndIndex),\n            0,\n            expParseMode\n          );\n          if (currentProp.name === \"for\") {\n            currentProp.forParseResult = parseForExpression(currentProp.exp);\n          }\n        }\n      }\n      if (currentProp.type !== 7 || currentProp.name !== \"pre\") {\n        currentOpenTag.props.push(currentProp);\n      }\n    }\n    currentAttrValue = \"\";\n    currentAttrStartIndex = currentAttrEndIndex = -1;\n  },\n  oncomment(start, end) {\n    if (currentOptions.comments) {\n      addNode({\n        type: 3,\n        content: getSlice(start, end),\n        loc: getLoc(start - 4, end + 3)\n      });\n    }\n  },\n  onend() {\n    const end = currentInput.length;\n    if (tokenizer.state !== 1) {\n      switch (tokenizer.state) {\n        case 5:\n        case 8:\n          emitError(5, end);\n          break;\n        case 3:\n        case 4:\n          emitError(\n            25,\n            tokenizer.sectionStart\n          );\n          break;\n        case 28:\n          if (tokenizer.currentSequence === Sequences.CdataEnd) {\n            emitError(6, end);\n          } else {\n            emitError(7, end);\n          }\n          break;\n        case 6:\n        case 7:\n        case 9:\n        case 11:\n        case 12:\n        case 13:\n        case 14:\n        case 15:\n        case 16:\n        case 17:\n        case 18:\n        case 19:\n        case 20:\n        case 21:\n          emitError(9, end);\n          break;\n      }\n    }\n    for (let index = 0; index < stack.length; index++) {\n      onCloseTag(stack[index], end - 1);\n      emitError(24, stack[index].loc.start.offset);\n    }\n  },\n  oncdata(start, end) {\n    if (stack[0].ns !== 0) {\n      onText(getSlice(start, end), start, end);\n    } else {\n      emitError(1, start - 9);\n    }\n  },\n  onprocessinginstruction(start) {\n    if ((stack[0] ? stack[0].ns : currentOptions.ns) === 0) {\n      emitError(\n        21,\n        start - 1\n      );\n    }\n  }\n});\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\nfunction parseForExpression(input) {\n  const loc = input.loc;\n  const exp = input.content;\n  const inMatch = exp.match(forAliasRE);\n  if (!inMatch) return;\n  const [, LHS, RHS] = inMatch;\n  const createAliasExpression = (content, offset, asParam = false) => {\n    const start = loc.start.offset + offset;\n    const end = start + content.length;\n    return createExp(\n      content,\n      false,\n      getLoc(start, end),\n      0,\n      asParam ? 1 /* Params */ : 0 /* Normal */\n    );\n  };\n  const result = {\n    source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),\n    value: void 0,\n    key: void 0,\n    index: void 0,\n    finalized: false\n  };\n  let valueContent = LHS.trim().replace(stripParensRE, \"\").trim();\n  const trimmedOffset = LHS.indexOf(valueContent);\n  const iteratorMatch = valueContent.match(forIteratorRE);\n  if (iteratorMatch) {\n    valueContent = valueContent.replace(forIteratorRE, \"\").trim();\n    const keyContent = iteratorMatch[1].trim();\n    let keyOffset;\n    if (keyContent) {\n      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);\n      result.key = createAliasExpression(keyContent, keyOffset, true);\n    }\n    if (iteratorMatch[2]) {\n      const indexContent = iteratorMatch[2].trim();\n      if (indexContent) {\n        result.index = createAliasExpression(\n          indexContent,\n          exp.indexOf(\n            indexContent,\n            result.key ? keyOffset + keyContent.length : trimmedOffset + valueContent.length\n          ),\n          true\n        );\n      }\n    }\n  }\n  if (valueContent) {\n    result.value = createAliasExpression(valueContent, trimmedOffset, true);\n  }\n  return result;\n}\nfunction getSlice(start, end) {\n  return currentInput.slice(start, end);\n}\nfunction endOpenTag(end) {\n  if (tokenizer.inSFCRoot) {\n    currentOpenTag.innerLoc = getLoc(end + 1, end + 1);\n  }\n  addNode(currentOpenTag);\n  const { tag, ns } = currentOpenTag;\n  if (ns === 0 && currentOptions.isPreTag(tag)) {\n    inPre++;\n  }\n  if (currentOptions.isVoidTag(tag)) {\n    onCloseTag(currentOpenTag, end);\n  } else {\n    stack.unshift(currentOpenTag);\n    if (ns === 1 || ns === 2) {\n      tokenizer.inXML = true;\n    }\n  }\n  currentOpenTag = null;\n}\nfunction onText(content, start, end) {\n  {\n    const tag = stack[0] && stack[0].tag;\n    if (tag !== \"script\" && tag !== \"style\" && content.includes(\"&\")) {\n      content = currentOptions.decodeEntities(content, false);\n    }\n  }\n  const parent = stack[0] || currentRoot;\n  const lastNode = parent.children[parent.children.length - 1];\n  if (lastNode && lastNode.type === 2) {\n    lastNode.content += content;\n    setLocEnd(lastNode.loc, end);\n  } else {\n    parent.children.push({\n      type: 2,\n      content,\n      loc: getLoc(start, end)\n    });\n  }\n}\nfunction onCloseTag(el, end, isImplied = false) {\n  if (isImplied) {\n    setLocEnd(el.loc, backTrack(end, 60));\n  } else {\n    setLocEnd(el.loc, lookAhead(end, 62) + 1);\n  }\n  if (tokenizer.inSFCRoot) {\n    if (el.children.length) {\n      el.innerLoc.end = extend({}, el.children[el.children.length - 1].loc.end);\n    } else {\n      el.innerLoc.end = extend({}, el.innerLoc.start);\n    }\n    el.innerLoc.source = getSlice(\n      el.innerLoc.start.offset,\n      el.innerLoc.end.offset\n    );\n  }\n  const { tag, ns } = el;\n  if (!inVPre) {\n    if (tag === \"slot\") {\n      el.tagType = 2;\n    } else if (isFragmentTemplate(el)) {\n      el.tagType = 3;\n    } else if (isComponent(el)) {\n      el.tagType = 1;\n    }\n  }\n  if (!tokenizer.inRCDATA) {\n    el.children = condenseWhitespace(el.children, el.tag);\n  }\n  if (ns === 0 && currentOptions.isPreTag(tag)) {\n    inPre--;\n  }\n  if (currentVPreBoundary === el) {\n    inVPre = tokenizer.inVPre = false;\n    currentVPreBoundary = null;\n  }\n  if (tokenizer.inXML && (stack[0] ? stack[0].ns : currentOptions.ns) === 0) {\n    tokenizer.inXML = false;\n  }\n}\nfunction lookAhead(index, c) {\n  let i = index;\n  while (currentInput.charCodeAt(i) !== c && i < currentInput.length - 1) i++;\n  return i;\n}\nfunction backTrack(index, c) {\n  let i = index;\n  while (currentInput.charCodeAt(i) !== c && i >= 0) i--;\n  return i;\n}\nconst specialTemplateDir = /* @__PURE__ */ new Set([\"if\", \"else\", \"else-if\", \"for\", \"slot\"]);\nfunction isFragmentTemplate({ tag, props }) {\n  if (tag === \"template\") {\n    for (let i = 0; i < props.length; i++) {\n      if (props[i].type === 7 && specialTemplateDir.has(props[i].name)) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\nfunction isComponent({ tag, props }) {\n  if (currentOptions.isCustomElement(tag)) {\n    return false;\n  }\n  if (tag === \"component\" || isUpperCase(tag.charCodeAt(0)) || isCoreComponent(tag) || currentOptions.isBuiltInComponent && currentOptions.isBuiltInComponent(tag) || currentOptions.isNativeTag && !currentOptions.isNativeTag(tag)) {\n    return true;\n  }\n  for (let i = 0; i < props.length; i++) {\n    const p = props[i];\n    if (p.type === 6) {\n      if (p.name === \"is\" && p.value) {\n        if (p.value.content.startsWith(\"vue:\")) {\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\nfunction isUpperCase(c) {\n  return c > 64 && c < 91;\n}\nconst windowsNewlineRE = /\\r\\n/g;\nfunction condenseWhitespace(nodes, tag) {\n  const shouldCondense = currentOptions.whitespace !== \"preserve\";\n  let removedWhitespace = false;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (node.type === 2) {\n      if (!inPre) {\n        if (isAllWhitespace(node.content)) {\n          const prev = nodes[i - 1] && nodes[i - 1].type;\n          const next = nodes[i + 1] && nodes[i + 1].type;\n          if (!prev || !next || shouldCondense && (prev === 3 && (next === 3 || next === 1) || prev === 1 && (next === 3 || next === 1 && hasNewlineChar(node.content)))) {\n            removedWhitespace = true;\n            nodes[i] = null;\n          } else {\n            node.content = \" \";\n          }\n        } else if (shouldCondense) {\n          node.content = condense(node.content);\n        }\n      } else {\n        node.content = node.content.replace(windowsNewlineRE, \"\\n\");\n      }\n    }\n  }\n  if (inPre && tag && currentOptions.isPreTag(tag)) {\n    const first = nodes[0];\n    if (first && first.type === 2) {\n      first.content = first.content.replace(/^\\r?\\n/, \"\");\n    }\n  }\n  return removedWhitespace ? nodes.filter(Boolean) : nodes;\n}\nfunction isAllWhitespace(str) {\n  for (let i = 0; i < str.length; i++) {\n    if (!isWhitespace(str.charCodeAt(i))) {\n      return false;\n    }\n  }\n  return true;\n}\nfunction hasNewlineChar(str) {\n  for (let i = 0; i < str.length; i++) {\n    const c = str.charCodeAt(i);\n    if (c === 10 || c === 13) {\n      return true;\n    }\n  }\n  return false;\n}\nfunction condense(str) {\n  let ret = \"\";\n  let prevCharIsWhitespace = false;\n  for (let i = 0; i < str.length; i++) {\n    if (isWhitespace(str.charCodeAt(i))) {\n      if (!prevCharIsWhitespace) {\n        ret += \" \";\n        prevCharIsWhitespace = true;\n      }\n    } else {\n      ret += str[i];\n      prevCharIsWhitespace = false;\n    }\n  }\n  return ret;\n}\nfunction addNode(node) {\n  (stack[0] || currentRoot).children.push(node);\n}\nfunction getLoc(start, end) {\n  return {\n    start: tokenizer.getPos(start),\n    // @ts-expect-error allow late attachment\n    end: end == null ? end : tokenizer.getPos(end),\n    // @ts-expect-error allow late attachment\n    source: end == null ? end : getSlice(start, end)\n  };\n}\nfunction setLocEnd(loc, end) {\n  loc.end = tokenizer.getPos(end);\n  loc.source = getSlice(loc.start.offset, end);\n}\nfunction dirToAttr(dir) {\n  const attr = {\n    type: 6,\n    name: dir.rawName,\n    nameLoc: getLoc(\n      dir.loc.start.offset,\n      dir.loc.start.offset + dir.rawName.length\n    ),\n    value: void 0,\n    loc: dir.loc\n  };\n  if (dir.exp) {\n    const loc = dir.exp.loc;\n    if (loc.end.offset < dir.loc.end.offset) {\n      loc.start.offset--;\n      loc.start.column--;\n      loc.end.offset++;\n      loc.end.column++;\n    }\n    attr.value = {\n      type: 2,\n      content: dir.exp.content,\n      loc\n    };\n  }\n  return attr;\n}\nfunction createExp(content, isStatic = false, loc, constType = 0, parseMode = 0 /* Normal */) {\n  const exp = createSimpleExpression(content, isStatic, loc, constType);\n  return exp;\n}\nfunction emitError(code, index, message) {\n  currentOptions.onError(\n    createCompilerError(code, getLoc(index, index), void 0, message)\n  );\n}\nfunction reset() {\n  tokenizer.reset();\n  currentOpenTag = null;\n  currentProp = null;\n  currentAttrValue = \"\";\n  currentAttrStartIndex = -1;\n  currentAttrEndIndex = -1;\n  stack.length = 0;\n}\nfunction baseParse(input, options) {\n  reset();\n  currentInput = input;\n  currentOptions = extend({}, defaultParserOptions);\n  if (options) {\n    let key;\n    for (key in options) {\n      if (options[key] != null) {\n        currentOptions[key] = options[key];\n      }\n    }\n  }\n  {\n    if (!currentOptions.decodeEntities) {\n      throw new Error(\n        `[@vue/compiler-core] decodeEntities option is required in browser builds.`\n      );\n    }\n  }\n  tokenizer.mode = currentOptions.parseMode === \"html\" ? 1 : currentOptions.parseMode === \"sfc\" ? 2 : 0;\n  tokenizer.inXML = currentOptions.ns === 1 || currentOptions.ns === 2;\n  const delimiters = options && options.delimiters;\n  if (delimiters) {\n    tokenizer.delimiterOpen = toCharCodes(delimiters[0]);\n    tokenizer.delimiterClose = toCharCodes(delimiters[1]);\n  }\n  const root = currentRoot = createRoot([], input);\n  tokenizer.parse(currentInput);\n  root.loc = getLoc(0, input.length);\n  root.children = condenseWhitespace(root.children);\n  currentRoot = null;\n  return root;\n}\n\nfunction hoistStatic(root, context) {\n  walk(\n    root,\n    context,\n    // Root node is unfortunately non-hoistable due to potential parent\n    // fallthrough attributes.\n    isSingleElementRoot(root, root.children[0])\n  );\n}\nfunction isSingleElementRoot(root, child) {\n  const { children } = root;\n  return children.length === 1 && child.type === 1 && !isSlotOutlet(child);\n}\nfunction walk(node, context, doNotHoistNode = false) {\n  const { children } = node;\n  const originalCount = children.length;\n  let hoistedCount = 0;\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    if (child.type === 1 && child.tagType === 0) {\n      const constantType = doNotHoistNode ? 0 : getConstantType(child, context);\n      if (constantType > 0) {\n        if (constantType >= 2) {\n          child.codegenNode.patchFlag = -1;\n          child.codegenNode = context.hoist(child.codegenNode);\n          hoistedCount++;\n          continue;\n        }\n      } else {\n        const codegenNode = child.codegenNode;\n        if (codegenNode.type === 13) {\n          const flag = codegenNode.patchFlag;\n          if ((flag === void 0 || flag === 512 || flag === 1) && getGeneratedPropsConstantType(child, context) >= 2) {\n            const props = getNodeProps(child);\n            if (props) {\n              codegenNode.props = context.hoist(props);\n            }\n          }\n          if (codegenNode.dynamicProps) {\n            codegenNode.dynamicProps = context.hoist(codegenNode.dynamicProps);\n          }\n        }\n      }\n    }\n    if (child.type === 1) {\n      const isComponent = child.tagType === 1;\n      if (isComponent) {\n        context.scopes.vSlot++;\n      }\n      walk(child, context);\n      if (isComponent) {\n        context.scopes.vSlot--;\n      }\n    } else if (child.type === 11) {\n      walk(child, context, child.children.length === 1);\n    } else if (child.type === 9) {\n      for (let i2 = 0; i2 < child.branches.length; i2++) {\n        walk(\n          child.branches[i2],\n          context,\n          child.branches[i2].children.length === 1\n        );\n      }\n    }\n  }\n  if (hoistedCount && context.transformHoist) {\n    context.transformHoist(children, context, node);\n  }\n  if (hoistedCount && hoistedCount === originalCount && node.type === 1 && node.tagType === 0 && node.codegenNode && node.codegenNode.type === 13 && isArray(node.codegenNode.children)) {\n    const hoisted = context.hoist(\n      createArrayExpression(node.codegenNode.children)\n    );\n    if (context.hmr) {\n      hoisted.content = `[...${hoisted.content}]`;\n    }\n    node.codegenNode.children = hoisted;\n  }\n}\nfunction getConstantType(node, context) {\n  const { constantCache } = context;\n  switch (node.type) {\n    case 1:\n      if (node.tagType !== 0) {\n        return 0;\n      }\n      const cached = constantCache.get(node);\n      if (cached !== void 0) {\n        return cached;\n      }\n      const codegenNode = node.codegenNode;\n      if (codegenNode.type !== 13) {\n        return 0;\n      }\n      if (codegenNode.isBlock && node.tag !== \"svg\" && node.tag !== \"foreignObject\" && node.tag !== \"math\") {\n        return 0;\n      }\n      if (codegenNode.patchFlag === void 0) {\n        let returnType2 = 3;\n        const generatedPropsType = getGeneratedPropsConstantType(node, context);\n        if (generatedPropsType === 0) {\n          constantCache.set(node, 0);\n          return 0;\n        }\n        if (generatedPropsType < returnType2) {\n          returnType2 = generatedPropsType;\n        }\n        for (let i = 0; i < node.children.length; i++) {\n          const childType = getConstantType(node.children[i], context);\n          if (childType === 0) {\n            constantCache.set(node, 0);\n            return 0;\n          }\n          if (childType < returnType2) {\n            returnType2 = childType;\n          }\n        }\n        if (returnType2 > 1) {\n          for (let i = 0; i < node.props.length; i++) {\n            const p = node.props[i];\n            if (p.type === 7 && p.name === \"bind\" && p.exp) {\n              const expType = getConstantType(p.exp, context);\n              if (expType === 0) {\n                constantCache.set(node, 0);\n                return 0;\n              }\n              if (expType < returnType2) {\n                returnType2 = expType;\n              }\n            }\n          }\n        }\n        if (codegenNode.isBlock) {\n          for (let i = 0; i < node.props.length; i++) {\n            const p = node.props[i];\n            if (p.type === 7) {\n              constantCache.set(node, 0);\n              return 0;\n            }\n          }\n          context.removeHelper(OPEN_BLOCK);\n          context.removeHelper(\n            getVNodeBlockHelper(context.inSSR, codegenNode.isComponent)\n          );\n          codegenNode.isBlock = false;\n          context.helper(getVNodeHelper(context.inSSR, codegenNode.isComponent));\n        }\n        constantCache.set(node, returnType2);\n        return returnType2;\n      } else {\n        constantCache.set(node, 0);\n        return 0;\n      }\n    case 2:\n    case 3:\n      return 3;\n    case 9:\n    case 11:\n    case 10:\n      return 0;\n    case 5:\n    case 12:\n      return getConstantType(node.content, context);\n    case 4:\n      return node.constType;\n    case 8:\n      let returnType = 3;\n      for (let i = 0; i < node.children.length; i++) {\n        const child = node.children[i];\n        if (isString(child) || isSymbol(child)) {\n          continue;\n        }\n        const childType = getConstantType(child, context);\n        if (childType === 0) {\n          return 0;\n        } else if (childType < returnType) {\n          returnType = childType;\n        }\n      }\n      return returnType;\n    default:\n      return 0;\n  }\n}\nconst allowHoistedHelperSet = /* @__PURE__ */ new Set([\n  NORMALIZE_CLASS,\n  NORMALIZE_STYLE,\n  NORMALIZE_PROPS,\n  GUARD_REACTIVE_PROPS\n]);\nfunction getConstantTypeOfHelperCall(value, context) {\n  if (value.type === 14 && !isString(value.callee) && allowHoistedHelperSet.has(value.callee)) {\n    const arg = value.arguments[0];\n    if (arg.type === 4) {\n      return getConstantType(arg, context);\n    } else if (arg.type === 14) {\n      return getConstantTypeOfHelperCall(arg, context);\n    }\n  }\n  return 0;\n}\nfunction getGeneratedPropsConstantType(node, context) {\n  let returnType = 3;\n  const props = getNodeProps(node);\n  if (props && props.type === 15) {\n    const { properties } = props;\n    for (let i = 0; i < properties.length; i++) {\n      const { key, value } = properties[i];\n      const keyType = getConstantType(key, context);\n      if (keyType === 0) {\n        return keyType;\n      }\n      if (keyType < returnType) {\n        returnType = keyType;\n      }\n      let valueType;\n      if (value.type === 4) {\n        valueType = getConstantType(value, context);\n      } else if (value.type === 14) {\n        valueType = getConstantTypeOfHelperCall(value, context);\n      } else {\n        valueType = 0;\n      }\n      if (valueType === 0) {\n        return valueType;\n      }\n      if (valueType < returnType) {\n        returnType = valueType;\n      }\n    }\n  }\n  return returnType;\n}\nfunction getNodeProps(node) {\n  const codegenNode = node.codegenNode;\n  if (codegenNode.type === 13) {\n    return codegenNode.props;\n  }\n}\n\nfunction createTransformContext(root, {\n  filename = \"\",\n  prefixIdentifiers = false,\n  hoistStatic: hoistStatic2 = false,\n  hmr = false,\n  cacheHandlers = false,\n  nodeTransforms = [],\n  directiveTransforms = {},\n  transformHoist = null,\n  isBuiltInComponent = NOOP,\n  isCustomElement = NOOP,\n  expressionPlugins = [],\n  scopeId = null,\n  slotted = true,\n  ssr = false,\n  inSSR = false,\n  ssrCssVars = ``,\n  bindingMetadata = EMPTY_OBJ,\n  inline = false,\n  isTS = false,\n  onError = defaultOnError,\n  onWarn = defaultOnWarn,\n  compatConfig\n}) {\n  const nameMatch = filename.replace(/\\?.*$/, \"\").match(/([^/\\\\]+)\\.\\w+$/);\n  const context = {\n    // options\n    filename,\n    selfName: nameMatch && capitalize(camelize(nameMatch[1])),\n    prefixIdentifiers,\n    hoistStatic: hoistStatic2,\n    hmr,\n    cacheHandlers,\n    nodeTransforms,\n    directiveTransforms,\n    transformHoist,\n    isBuiltInComponent,\n    isCustomElement,\n    expressionPlugins,\n    scopeId,\n    slotted,\n    ssr,\n    inSSR,\n    ssrCssVars,\n    bindingMetadata,\n    inline,\n    isTS,\n    onError,\n    onWarn,\n    compatConfig,\n    // state\n    root,\n    helpers: /* @__PURE__ */ new Map(),\n    components: /* @__PURE__ */ new Set(),\n    directives: /* @__PURE__ */ new Set(),\n    hoists: [],\n    imports: [],\n    constantCache: /* @__PURE__ */ new WeakMap(),\n    temps: 0,\n    cached: 0,\n    identifiers: /* @__PURE__ */ Object.create(null),\n    scopes: {\n      vFor: 0,\n      vSlot: 0,\n      vPre: 0,\n      vOnce: 0\n    },\n    parent: null,\n    grandParent: null,\n    currentNode: root,\n    childIndex: 0,\n    inVOnce: false,\n    // methods\n    helper(name) {\n      const count = context.helpers.get(name) || 0;\n      context.helpers.set(name, count + 1);\n      return name;\n    },\n    removeHelper(name) {\n      const count = context.helpers.get(name);\n      if (count) {\n        const currentCount = count - 1;\n        if (!currentCount) {\n          context.helpers.delete(name);\n        } else {\n          context.helpers.set(name, currentCount);\n        }\n      }\n    },\n    helperString(name) {\n      return `_${helperNameMap[context.helper(name)]}`;\n    },\n    replaceNode(node) {\n      {\n        if (!context.currentNode) {\n          throw new Error(`Node being replaced is already removed.`);\n        }\n        if (!context.parent) {\n          throw new Error(`Cannot replace root node.`);\n        }\n      }\n      context.parent.children[context.childIndex] = context.currentNode = node;\n    },\n    removeNode(node) {\n      if (!context.parent) {\n        throw new Error(`Cannot remove root node.`);\n      }\n      const list = context.parent.children;\n      const removalIndex = node ? list.indexOf(node) : context.currentNode ? context.childIndex : -1;\n      if (removalIndex < 0) {\n        throw new Error(`node being removed is not a child of current parent`);\n      }\n      if (!node || node === context.currentNode) {\n        context.currentNode = null;\n        context.onNodeRemoved();\n      } else {\n        if (context.childIndex > removalIndex) {\n          context.childIndex--;\n          context.onNodeRemoved();\n        }\n      }\n      context.parent.children.splice(removalIndex, 1);\n    },\n    onNodeRemoved: NOOP,\n    addIdentifiers(exp) {\n    },\n    removeIdentifiers(exp) {\n    },\n    hoist(exp) {\n      if (isString(exp)) exp = createSimpleExpression(exp);\n      context.hoists.push(exp);\n      const identifier = createSimpleExpression(\n        `_hoisted_${context.hoists.length}`,\n        false,\n        exp.loc,\n        2\n      );\n      identifier.hoisted = exp;\n      return identifier;\n    },\n    cache(exp, isVNode = false) {\n      return createCacheExpression(context.cached++, exp, isVNode);\n    }\n  };\n  return context;\n}\nfunction transform(root, options) {\n  const context = createTransformContext(root, options);\n  traverseNode(root, context);\n  if (options.hoistStatic) {\n    hoistStatic(root, context);\n  }\n  if (!options.ssr) {\n    createRootCodegen(root, context);\n  }\n  root.helpers = /* @__PURE__ */ new Set([...context.helpers.keys()]);\n  root.components = [...context.components];\n  root.directives = [...context.directives];\n  root.imports = context.imports;\n  root.hoists = context.hoists;\n  root.temps = context.temps;\n  root.cached = context.cached;\n  root.transformed = true;\n}\nfunction createRootCodegen(root, context) {\n  const { helper } = context;\n  const { children } = root;\n  if (children.length === 1) {\n    const child = children[0];\n    if (isSingleElementRoot(root, child) && child.codegenNode) {\n      const codegenNode = child.codegenNode;\n      if (codegenNode.type === 13) {\n        convertToBlock(codegenNode, context);\n      }\n      root.codegenNode = codegenNode;\n    } else {\n      root.codegenNode = child;\n    }\n  } else if (children.length > 1) {\n    let patchFlag = 64;\n    let patchFlagText = PatchFlagNames[64];\n    if (children.filter((c) => c.type !== 3).length === 1) {\n      patchFlag |= 2048;\n      patchFlagText += `, ${PatchFlagNames[2048]}`;\n    }\n    root.codegenNode = createVNodeCall(\n      context,\n      helper(FRAGMENT),\n      void 0,\n      root.children,\n      patchFlag,\n      void 0,\n      void 0,\n      true,\n      void 0,\n      false\n    );\n  } else ;\n}\nfunction traverseChildren(parent, context) {\n  let i = 0;\n  const nodeRemoved = () => {\n    i--;\n  };\n  for (; i < parent.children.length; i++) {\n    const child = parent.children[i];\n    if (isString(child)) continue;\n    context.grandParent = context.parent;\n    context.parent = parent;\n    context.childIndex = i;\n    context.onNodeRemoved = nodeRemoved;\n    traverseNode(child, context);\n  }\n}\nfunction traverseNode(node, context) {\n  context.currentNode = node;\n  const { nodeTransforms } = context;\n  const exitFns = [];\n  for (let i2 = 0; i2 < nodeTransforms.length; i2++) {\n    const onExit = nodeTransforms[i2](node, context);\n    if (onExit) {\n      if (isArray(onExit)) {\n        exitFns.push(...onExit);\n      } else {\n        exitFns.push(onExit);\n      }\n    }\n    if (!context.currentNode) {\n      return;\n    } else {\n      node = context.currentNode;\n    }\n  }\n  switch (node.type) {\n    case 3:\n      if (!context.ssr) {\n        context.helper(CREATE_COMMENT);\n      }\n      break;\n    case 5:\n      if (!context.ssr) {\n        context.helper(TO_DISPLAY_STRING);\n      }\n      break;\n    case 9:\n      for (let i2 = 0; i2 < node.branches.length; i2++) {\n        traverseNode(node.branches[i2], context);\n      }\n      break;\n    case 10:\n    case 11:\n    case 1:\n    case 0:\n      traverseChildren(node, context);\n      break;\n  }\n  context.currentNode = node;\n  let i = exitFns.length;\n  while (i--) {\n    exitFns[i]();\n  }\n}\nfunction createStructuralDirectiveTransform(name, fn) {\n  const matches = isString(name) ? (n) => n === name : (n) => name.test(n);\n  return (node, context) => {\n    if (node.type === 1) {\n      const { props } = node;\n      if (node.tagType === 3 && props.some(isVSlot)) {\n        return;\n      }\n      const exitFns = [];\n      for (let i = 0; i < props.length; i++) {\n        const prop = props[i];\n        if (prop.type === 7 && matches(prop.name)) {\n          props.splice(i, 1);\n          i--;\n          const onExit = fn(node, prop, context);\n          if (onExit) exitFns.push(onExit);\n        }\n      }\n      return exitFns;\n    }\n  };\n}\n\nconst PURE_ANNOTATION = `/*#__PURE__*/`;\nconst aliasHelper = (s) => `${helperNameMap[s]}: _${helperNameMap[s]}`;\nfunction createCodegenContext(ast, {\n  mode = \"function\",\n  prefixIdentifiers = mode === \"module\",\n  sourceMap = false,\n  filename = `template.vue.html`,\n  scopeId = null,\n  optimizeImports = false,\n  runtimeGlobalName = `Vue`,\n  runtimeModuleName = `vue`,\n  ssrRuntimeModuleName = \"vue/server-renderer\",\n  ssr = false,\n  isTS = false,\n  inSSR = false\n}) {\n  const context = {\n    mode,\n    prefixIdentifiers,\n    sourceMap,\n    filename,\n    scopeId,\n    optimizeImports,\n    runtimeGlobalName,\n    runtimeModuleName,\n    ssrRuntimeModuleName,\n    ssr,\n    isTS,\n    inSSR,\n    source: ast.source,\n    code: ``,\n    column: 1,\n    line: 1,\n    offset: 0,\n    indentLevel: 0,\n    pure: false,\n    map: void 0,\n    helper(key) {\n      return `_${helperNameMap[key]}`;\n    },\n    push(code, newlineIndex = -2 /* None */, node) {\n      context.code += code;\n    },\n    indent() {\n      newline(++context.indentLevel);\n    },\n    deindent(withoutNewLine = false) {\n      if (withoutNewLine) {\n        --context.indentLevel;\n      } else {\n        newline(--context.indentLevel);\n      }\n    },\n    newline() {\n      newline(context.indentLevel);\n    }\n  };\n  function newline(n) {\n    context.push(\"\\n\" + `  `.repeat(n), 0 /* Start */);\n  }\n  return context;\n}\nfunction generate(ast, options = {}) {\n  const context = createCodegenContext(ast, options);\n  if (options.onContextCreated) options.onContextCreated(context);\n  const {\n    mode,\n    push,\n    prefixIdentifiers,\n    indent,\n    deindent,\n    newline,\n    scopeId,\n    ssr\n  } = context;\n  const helpers = Array.from(ast.helpers);\n  const hasHelpers = helpers.length > 0;\n  const useWithBlock = !prefixIdentifiers && mode !== \"module\";\n  const preambleContext = context;\n  {\n    genFunctionPreamble(ast, preambleContext);\n  }\n  const functionName = ssr ? `ssrRender` : `render`;\n  const args = ssr ? [\"_ctx\", \"_push\", \"_parent\", \"_attrs\"] : [\"_ctx\", \"_cache\"];\n  const signature = args.join(\", \");\n  {\n    push(`function ${functionName}(${signature}) {`);\n  }\n  indent();\n  if (useWithBlock) {\n    push(`with (_ctx) {`);\n    indent();\n    if (hasHelpers) {\n      push(\n        `const { ${helpers.map(aliasHelper).join(\", \")} } = _Vue\n`,\n        -1 /* End */\n      );\n      newline();\n    }\n  }\n  if (ast.components.length) {\n    genAssets(ast.components, \"component\", context);\n    if (ast.directives.length || ast.temps > 0) {\n      newline();\n    }\n  }\n  if (ast.directives.length) {\n    genAssets(ast.directives, \"directive\", context);\n    if (ast.temps > 0) {\n      newline();\n    }\n  }\n  if (ast.temps > 0) {\n    push(`let `);\n    for (let i = 0; i < ast.temps; i++) {\n      push(`${i > 0 ? `, ` : ``}_temp${i}`);\n    }\n  }\n  if (ast.components.length || ast.directives.length || ast.temps) {\n    push(`\n`, 0 /* Start */);\n    newline();\n  }\n  if (!ssr) {\n    push(`return `);\n  }\n  if (ast.codegenNode) {\n    genNode(ast.codegenNode, context);\n  } else {\n    push(`null`);\n  }\n  if (useWithBlock) {\n    deindent();\n    push(`}`);\n  }\n  deindent();\n  push(`}`);\n  return {\n    ast,\n    code: context.code,\n    preamble: ``,\n    map: context.map ? context.map.toJSON() : void 0\n  };\n}\nfunction genFunctionPreamble(ast, context) {\n  const {\n    ssr,\n    prefixIdentifiers,\n    push,\n    newline,\n    runtimeModuleName,\n    runtimeGlobalName,\n    ssrRuntimeModuleName\n  } = context;\n  const VueBinding = runtimeGlobalName;\n  const helpers = Array.from(ast.helpers);\n  if (helpers.length > 0) {\n    {\n      push(`const _Vue = ${VueBinding}\n`, -1 /* End */);\n      if (ast.hoists.length) {\n        const staticHelpers = [\n          CREATE_VNODE,\n          CREATE_ELEMENT_VNODE,\n          CREATE_COMMENT,\n          CREATE_TEXT,\n          CREATE_STATIC\n        ].filter((helper) => helpers.includes(helper)).map(aliasHelper).join(\", \");\n        push(`const { ${staticHelpers} } = _Vue\n`, -1 /* End */);\n      }\n    }\n  }\n  genHoists(ast.hoists, context);\n  newline();\n  push(`return `);\n}\nfunction genAssets(assets, type, { helper, push, newline, isTS }) {\n  const resolver = helper(\n    type === \"component\" ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE\n  );\n  for (let i = 0; i < assets.length; i++) {\n    let id = assets[i];\n    const maybeSelfReference = id.endsWith(\"__self\");\n    if (maybeSelfReference) {\n      id = id.slice(0, -6);\n    }\n    push(\n      `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${maybeSelfReference ? `, true` : ``})${isTS ? `!` : ``}`\n    );\n    if (i < assets.length - 1) {\n      newline();\n    }\n  }\n}\nfunction genHoists(hoists, context) {\n  if (!hoists.length) {\n    return;\n  }\n  context.pure = true;\n  const { push, newline, helper, scopeId, mode } = context;\n  newline();\n  for (let i = 0; i < hoists.length; i++) {\n    const exp = hoists[i];\n    if (exp) {\n      push(\n        `const _hoisted_${i + 1} = ${``}`\n      );\n      genNode(exp, context);\n      newline();\n    }\n  }\n  context.pure = false;\n}\nfunction isText(n) {\n  return isString(n) || n.type === 4 || n.type === 2 || n.type === 5 || n.type === 8;\n}\nfunction genNodeListAsArray(nodes, context) {\n  const multilines = nodes.length > 3 || nodes.some((n) => isArray(n) || !isText(n));\n  context.push(`[`);\n  multilines && context.indent();\n  genNodeList(nodes, context, multilines);\n  multilines && context.deindent();\n  context.push(`]`);\n}\nfunction genNodeList(nodes, context, multilines = false, comma = true) {\n  const { push, newline } = context;\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    if (isString(node)) {\n      push(node, -3 /* Unknown */);\n    } else if (isArray(node)) {\n      genNodeListAsArray(node, context);\n    } else {\n      genNode(node, context);\n    }\n    if (i < nodes.length - 1) {\n      if (multilines) {\n        comma && push(\",\");\n        newline();\n      } else {\n        comma && push(\", \");\n      }\n    }\n  }\n}\nfunction genNode(node, context) {\n  if (isString(node)) {\n    context.push(node, -3 /* Unknown */);\n    return;\n  }\n  if (isSymbol(node)) {\n    context.push(context.helper(node));\n    return;\n  }\n  switch (node.type) {\n    case 1:\n    case 9:\n    case 11:\n      assert(\n        node.codegenNode != null,\n        `Codegen node is missing for element/if/for node. Apply appropriate transforms first.`\n      );\n      genNode(node.codegenNode, context);\n      break;\n    case 2:\n      genText(node, context);\n      break;\n    case 4:\n      genExpression(node, context);\n      break;\n    case 5:\n      genInterpolation(node, context);\n      break;\n    case 12:\n      genNode(node.codegenNode, context);\n      break;\n    case 8:\n      genCompoundExpression(node, context);\n      break;\n    case 3:\n      genComment(node, context);\n      break;\n    case 13:\n      genVNodeCall(node, context);\n      break;\n    case 14:\n      genCallExpression(node, context);\n      break;\n    case 15:\n      genObjectExpression(node, context);\n      break;\n    case 17:\n      genArrayExpression(node, context);\n      break;\n    case 18:\n      genFunctionExpression(node, context);\n      break;\n    case 19:\n      genConditionalExpression(node, context);\n      break;\n    case 20:\n      genCacheExpression(node, context);\n      break;\n    case 21:\n      genNodeList(node.body, context, true, false);\n      break;\n    case 22:\n      break;\n    case 23:\n      break;\n    case 24:\n      break;\n    case 25:\n      break;\n    case 26:\n      break;\n    case 10:\n      break;\n    default:\n      {\n        assert(false, `unhandled codegen node type: ${node.type}`);\n        const exhaustiveCheck = node;\n        return exhaustiveCheck;\n      }\n  }\n}\nfunction genText(node, context) {\n  context.push(JSON.stringify(node.content), -3 /* Unknown */, node);\n}\nfunction genExpression(node, context) {\n  const { content, isStatic } = node;\n  context.push(\n    isStatic ? JSON.stringify(content) : content,\n    -3 /* Unknown */,\n    node\n  );\n}\nfunction genInterpolation(node, context) {\n  const { push, helper, pure } = context;\n  if (pure) push(PURE_ANNOTATION);\n  push(`${helper(TO_DISPLAY_STRING)}(`);\n  genNode(node.content, context);\n  push(`)`);\n}\nfunction genCompoundExpression(node, context) {\n  for (let i = 0; i < node.children.length; i++) {\n    const child = node.children[i];\n    if (isString(child)) {\n      context.push(child, -3 /* Unknown */);\n    } else {\n      genNode(child, context);\n    }\n  }\n}\nfunction genExpressionAsPropertyKey(node, context) {\n  const { push } = context;\n  if (node.type === 8) {\n    push(`[`);\n    genCompoundExpression(node, context);\n    push(`]`);\n  } else if (node.isStatic) {\n    const text = isSimpleIdentifier(node.content) ? node.content : JSON.stringify(node.content);\n    push(text, -2 /* None */, node);\n  } else {\n    push(`[${node.content}]`, -3 /* Unknown */, node);\n  }\n}\nfunction genComment(node, context) {\n  const { push, helper, pure } = context;\n  if (pure) {\n    push(PURE_ANNOTATION);\n  }\n  push(\n    `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,\n    -3 /* Unknown */,\n    node\n  );\n}\nfunction genVNodeCall(node, context) {\n  const { push, helper, pure } = context;\n  const {\n    tag,\n    props,\n    children,\n    patchFlag,\n    dynamicProps,\n    directives,\n    isBlock,\n    disableTracking,\n    isComponent\n  } = node;\n  let patchFlagString;\n  if (patchFlag) {\n    {\n      if (patchFlag < 0) {\n        patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`;\n      } else {\n        const flagNames = Object.keys(PatchFlagNames).map(Number).filter((n) => n > 0 && patchFlag & n).map((n) => PatchFlagNames[n]).join(`, `);\n        patchFlagString = patchFlag + ` /* ${flagNames} */`;\n      }\n    }\n  }\n  if (directives) {\n    push(helper(WITH_DIRECTIVES) + `(`);\n  }\n  if (isBlock) {\n    push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `);\n  }\n  if (pure) {\n    push(PURE_ANNOTATION);\n  }\n  const callHelper = isBlock ? getVNodeBlockHelper(context.inSSR, isComponent) : getVNodeHelper(context.inSSR, isComponent);\n  push(helper(callHelper) + `(`, -2 /* None */, node);\n  genNodeList(\n    genNullableArgs([tag, props, children, patchFlagString, dynamicProps]),\n    context\n  );\n  push(`)`);\n  if (isBlock) {\n    push(`)`);\n  }\n  if (directives) {\n    push(`, `);\n    genNode(directives, context);\n    push(`)`);\n  }\n}\nfunction genNullableArgs(args) {\n  let i = args.length;\n  while (i--) {\n    if (args[i] != null) break;\n  }\n  return args.slice(0, i + 1).map((arg) => arg || `null`);\n}\nfunction genCallExpression(node, context) {\n  const { push, helper, pure } = context;\n  const callee = isString(node.callee) ? node.callee : helper(node.callee);\n  if (pure) {\n    push(PURE_ANNOTATION);\n  }\n  push(callee + `(`, -2 /* None */, node);\n  genNodeList(node.arguments, context);\n  push(`)`);\n}\nfunction genObjectExpression(node, context) {\n  const { push, indent, deindent, newline } = context;\n  const { properties } = node;\n  if (!properties.length) {\n    push(`{}`, -2 /* None */, node);\n    return;\n  }\n  const multilines = properties.length > 1 || properties.some((p) => p.value.type !== 4);\n  push(multilines ? `{` : `{ `);\n  multilines && indent();\n  for (let i = 0; i < properties.length; i++) {\n    const { key, value } = properties[i];\n    genExpressionAsPropertyKey(key, context);\n    push(`: `);\n    genNode(value, context);\n    if (i < properties.length - 1) {\n      push(`,`);\n      newline();\n    }\n  }\n  multilines && deindent();\n  push(multilines ? `}` : ` }`);\n}\nfunction genArrayExpression(node, context) {\n  genNodeListAsArray(node.elements, context);\n}\nfunction genFunctionExpression(node, context) {\n  const { push, indent, deindent } = context;\n  const { params, returns, body, newline, isSlot } = node;\n  if (isSlot) {\n    push(`_${helperNameMap[WITH_CTX]}(`);\n  }\n  push(`(`, -2 /* None */, node);\n  if (isArray(params)) {\n    genNodeList(params, context);\n  } else if (params) {\n    genNode(params, context);\n  }\n  push(`) => `);\n  if (newline || body) {\n    push(`{`);\n    indent();\n  }\n  if (returns) {\n    if (newline) {\n      push(`return `);\n    }\n    if (isArray(returns)) {\n      genNodeListAsArray(returns, context);\n    } else {\n      genNode(returns, context);\n    }\n  } else if (body) {\n    genNode(body, context);\n  }\n  if (newline || body) {\n    deindent();\n    push(`}`);\n  }\n  if (isSlot) {\n    push(`)`);\n  }\n}\nfunction genConditionalExpression(node, context) {\n  const { test, consequent, alternate, newline: needNewline } = node;\n  const { push, indent, deindent, newline } = context;\n  if (test.type === 4) {\n    const needsParens = !isSimpleIdentifier(test.content);\n    needsParens && push(`(`);\n    genExpression(test, context);\n    needsParens && push(`)`);\n  } else {\n    push(`(`);\n    genNode(test, context);\n    push(`)`);\n  }\n  needNewline && indent();\n  context.indentLevel++;\n  needNewline || push(` `);\n  push(`? `);\n  genNode(consequent, context);\n  context.indentLevel--;\n  needNewline && newline();\n  needNewline || push(` `);\n  push(`: `);\n  const isNested = alternate.type === 19;\n  if (!isNested) {\n    context.indentLevel++;\n  }\n  genNode(alternate, context);\n  if (!isNested) {\n    context.indentLevel--;\n  }\n  needNewline && deindent(\n    true\n    /* without newline */\n  );\n}\nfunction genCacheExpression(node, context) {\n  const { push, helper, indent, deindent, newline } = context;\n  push(`_cache[${node.index}] || (`);\n  if (node.isVOnce) {\n    indent();\n    push(`${helper(SET_BLOCK_TRACKING)}(-1),`);\n    newline();\n    push(`(`);\n  }\n  push(`_cache[${node.index}] = `);\n  genNode(node.value, context);\n  if (node.isVOnce) {\n    push(`).cacheIndex = ${node.index},`);\n    newline();\n    push(`${helper(SET_BLOCK_TRACKING)}(1),`);\n    newline();\n    push(`_cache[${node.index}]`);\n    deindent();\n  }\n  push(`)`);\n}\n\nconst prohibitedKeywordRE = new RegExp(\n  \"\\\\b\" + \"arguments,await,break,case,catch,class,const,continue,debugger,default,delete,do,else,export,extends,finally,for,function,if,import,let,new,return,super,switch,throw,try,var,void,while,with,yield\".split(\",\").join(\"\\\\b|\\\\b\") + \"\\\\b\"\n);\nconst stripStringRE = /'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*\\$\\{|\\}(?:[^`\\\\]|\\\\.)*`|`(?:[^`\\\\]|\\\\.)*`/g;\nfunction validateBrowserExpression(node, context, asParams = false, asRawStatements = false) {\n  const exp = node.content;\n  if (!exp.trim()) {\n    return;\n  }\n  try {\n    new Function(\n      asRawStatements ? ` ${exp} ` : `return ${asParams ? `(${exp}) => {}` : `(${exp})`}`\n    );\n  } catch (e) {\n    let message = e.message;\n    const keywordMatch = exp.replace(stripStringRE, \"\").match(prohibitedKeywordRE);\n    if (keywordMatch) {\n      message = `avoid using JavaScript keyword as property name: \"${keywordMatch[0]}\"`;\n    }\n    context.onError(\n      createCompilerError(\n        45,\n        node.loc,\n        void 0,\n        message\n      )\n    );\n  }\n}\n\nconst transformExpression = (node, context) => {\n  if (node.type === 5) {\n    node.content = processExpression(\n      node.content,\n      context\n    );\n  } else if (node.type === 1) {\n    for (let i = 0; i < node.props.length; i++) {\n      const dir = node.props[i];\n      if (dir.type === 7 && dir.name !== \"for\") {\n        const exp = dir.exp;\n        const arg = dir.arg;\n        if (exp && exp.type === 4 && !(dir.name === \"on\" && arg)) {\n          dir.exp = processExpression(\n            exp,\n            context,\n            // slot args must be processed as function params\n            dir.name === \"slot\"\n          );\n        }\n        if (arg && arg.type === 4 && !arg.isStatic) {\n          dir.arg = processExpression(arg, context);\n        }\n      }\n    }\n  }\n};\nfunction processExpression(node, context, asParams = false, asRawStatements = false, localVars = Object.create(context.identifiers)) {\n  {\n    {\n      validateBrowserExpression(node, context, asParams, asRawStatements);\n    }\n    return node;\n  }\n}\n\nconst transformIf = createStructuralDirectiveTransform(\n  /^(if|else|else-if)$/,\n  (node, dir, context) => {\n    return processIf(node, dir, context, (ifNode, branch, isRoot) => {\n      const siblings = context.parent.children;\n      let i = siblings.indexOf(ifNode);\n      let key = 0;\n      while (i-- >= 0) {\n        const sibling = siblings[i];\n        if (sibling && sibling.type === 9) {\n          key += sibling.branches.length;\n        }\n      }\n      return () => {\n        if (isRoot) {\n          ifNode.codegenNode = createCodegenNodeForBranch(\n            branch,\n            key,\n            context\n          );\n        } else {\n          const parentCondition = getParentCondition(ifNode.codegenNode);\n          parentCondition.alternate = createCodegenNodeForBranch(\n            branch,\n            key + ifNode.branches.length - 1,\n            context\n          );\n        }\n      };\n    });\n  }\n);\nfunction processIf(node, dir, context, processCodegen) {\n  if (dir.name !== \"else\" && (!dir.exp || !dir.exp.content.trim())) {\n    const loc = dir.exp ? dir.exp.loc : node.loc;\n    context.onError(\n      createCompilerError(28, dir.loc)\n    );\n    dir.exp = createSimpleExpression(`true`, false, loc);\n  }\n  if (dir.exp) {\n    validateBrowserExpression(dir.exp, context);\n  }\n  if (dir.name === \"if\") {\n    const branch = createIfBranch(node, dir);\n    const ifNode = {\n      type: 9,\n      loc: node.loc,\n      branches: [branch]\n    };\n    context.replaceNode(ifNode);\n    if (processCodegen) {\n      return processCodegen(ifNode, branch, true);\n    }\n  } else {\n    const siblings = context.parent.children;\n    const comments = [];\n    let i = siblings.indexOf(node);\n    while (i-- >= -1) {\n      const sibling = siblings[i];\n      if (sibling && sibling.type === 3) {\n        context.removeNode(sibling);\n        comments.unshift(sibling);\n        continue;\n      }\n      if (sibling && sibling.type === 2 && !sibling.content.trim().length) {\n        context.removeNode(sibling);\n        continue;\n      }\n      if (sibling && sibling.type === 9) {\n        if (dir.name === \"else-if\" && sibling.branches[sibling.branches.length - 1].condition === void 0) {\n          context.onError(\n            createCompilerError(30, node.loc)\n          );\n        }\n        context.removeNode();\n        const branch = createIfBranch(node, dir);\n        if (comments.length && // #3619 ignore comments if the v-if is direct child of <transition>\n        !(context.parent && context.parent.type === 1 && (context.parent.tag === \"transition\" || context.parent.tag === \"Transition\"))) {\n          branch.children = [...comments, ...branch.children];\n        }\n        {\n          const key = branch.userKey;\n          if (key) {\n            sibling.branches.forEach(({ userKey }) => {\n              if (isSameKey(userKey, key)) {\n                context.onError(\n                  createCompilerError(\n                    29,\n                    branch.userKey.loc\n                  )\n                );\n              }\n            });\n          }\n        }\n        sibling.branches.push(branch);\n        const onExit = processCodegen && processCodegen(sibling, branch, false);\n        traverseNode(branch, context);\n        if (onExit) onExit();\n        context.currentNode = null;\n      } else {\n        context.onError(\n          createCompilerError(30, node.loc)\n        );\n      }\n      break;\n    }\n  }\n}\nfunction createIfBranch(node, dir) {\n  const isTemplateIf = node.tagType === 3;\n  return {\n    type: 10,\n    loc: node.loc,\n    condition: dir.name === \"else\" ? void 0 : dir.exp,\n    children: isTemplateIf && !findDir(node, \"for\") ? node.children : [node],\n    userKey: findProp(node, `key`),\n    isTemplateIf\n  };\n}\nfunction createCodegenNodeForBranch(branch, keyIndex, context) {\n  if (branch.condition) {\n    return createConditionalExpression(\n      branch.condition,\n      createChildrenCodegenNode(branch, keyIndex, context),\n      // make sure to pass in asBlock: true so that the comment node call\n      // closes the current block.\n      createCallExpression(context.helper(CREATE_COMMENT), [\n        '\"v-if\"' ,\n        \"true\"\n      ])\n    );\n  } else {\n    return createChildrenCodegenNode(branch, keyIndex, context);\n  }\n}\nfunction createChildrenCodegenNode(branch, keyIndex, context) {\n  const { helper } = context;\n  const keyProperty = createObjectProperty(\n    `key`,\n    createSimpleExpression(\n      `${keyIndex}`,\n      false,\n      locStub,\n      2\n    )\n  );\n  const { children } = branch;\n  const firstChild = children[0];\n  const needFragmentWrapper = children.length !== 1 || firstChild.type !== 1;\n  if (needFragmentWrapper) {\n    if (children.length === 1 && firstChild.type === 11) {\n      const vnodeCall = firstChild.codegenNode;\n      injectProp(vnodeCall, keyProperty, context);\n      return vnodeCall;\n    } else {\n      let patchFlag = 64;\n      let patchFlagText = PatchFlagNames[64];\n      if (!branch.isTemplateIf && children.filter((c) => c.type !== 3).length === 1) {\n        patchFlag |= 2048;\n        patchFlagText += `, ${PatchFlagNames[2048]}`;\n      }\n      return createVNodeCall(\n        context,\n        helper(FRAGMENT),\n        createObjectExpression([keyProperty]),\n        children,\n        patchFlag,\n        void 0,\n        void 0,\n        true,\n        false,\n        false,\n        branch.loc\n      );\n    }\n  } else {\n    const ret = firstChild.codegenNode;\n    const vnodeCall = getMemoedVNodeCall(ret);\n    if (vnodeCall.type === 13) {\n      convertToBlock(vnodeCall, context);\n    }\n    injectProp(vnodeCall, keyProperty, context);\n    return ret;\n  }\n}\nfunction isSameKey(a, b) {\n  if (!a || a.type !== b.type) {\n    return false;\n  }\n  if (a.type === 6) {\n    if (a.value.content !== b.value.content) {\n      return false;\n    }\n  } else {\n    const exp = a.exp;\n    const branchExp = b.exp;\n    if (exp.type !== branchExp.type) {\n      return false;\n    }\n    if (exp.type !== 4 || exp.isStatic !== branchExp.isStatic || exp.content !== branchExp.content) {\n      return false;\n    }\n  }\n  return true;\n}\nfunction getParentCondition(node) {\n  while (true) {\n    if (node.type === 19) {\n      if (node.alternate.type === 19) {\n        node = node.alternate;\n      } else {\n        return node;\n      }\n    } else if (node.type === 20) {\n      node = node.value;\n    }\n  }\n}\n\nconst transformBind = (dir, _node, context) => {\n  const { modifiers, loc } = dir;\n  const arg = dir.arg;\n  let { exp } = dir;\n  if (exp && exp.type === 4 && !exp.content.trim()) {\n    {\n      exp = void 0;\n    }\n  }\n  if (!exp) {\n    if (arg.type !== 4 || !arg.isStatic) {\n      context.onError(\n        createCompilerError(\n          52,\n          arg.loc\n        )\n      );\n      return {\n        props: [\n          createObjectProperty(arg, createSimpleExpression(\"\", true, loc))\n        ]\n      };\n    }\n    transformBindShorthand(dir);\n    exp = dir.exp;\n  }\n  if (arg.type !== 4) {\n    arg.children.unshift(`(`);\n    arg.children.push(`) || \"\"`);\n  } else if (!arg.isStatic) {\n    arg.content = `${arg.content} || \"\"`;\n  }\n  if (modifiers.includes(\"camel\")) {\n    if (arg.type === 4) {\n      if (arg.isStatic) {\n        arg.content = camelize(arg.content);\n      } else {\n        arg.content = `${context.helperString(CAMELIZE)}(${arg.content})`;\n      }\n    } else {\n      arg.children.unshift(`${context.helperString(CAMELIZE)}(`);\n      arg.children.push(`)`);\n    }\n  }\n  if (!context.inSSR) {\n    if (modifiers.includes(\"prop\")) {\n      injectPrefix(arg, \".\");\n    }\n    if (modifiers.includes(\"attr\")) {\n      injectPrefix(arg, \"^\");\n    }\n  }\n  return {\n    props: [createObjectProperty(arg, exp)]\n  };\n};\nconst transformBindShorthand = (dir, context) => {\n  const arg = dir.arg;\n  const propName = camelize(arg.content);\n  dir.exp = createSimpleExpression(propName, false, arg.loc);\n};\nconst injectPrefix = (arg, prefix) => {\n  if (arg.type === 4) {\n    if (arg.isStatic) {\n      arg.content = prefix + arg.content;\n    } else {\n      arg.content = `\\`${prefix}\\${${arg.content}}\\``;\n    }\n  } else {\n    arg.children.unshift(`'${prefix}' + (`);\n    arg.children.push(`)`);\n  }\n};\n\nconst transformFor = createStructuralDirectiveTransform(\n  \"for\",\n  (node, dir, context) => {\n    const { helper, removeHelper } = context;\n    return processFor(node, dir, context, (forNode) => {\n      const renderExp = createCallExpression(helper(RENDER_LIST), [\n        forNode.source\n      ]);\n      const isTemplate = isTemplateNode(node);\n      const memo = findDir(node, \"memo\");\n      const keyProp = findProp(node, `key`, false, true);\n      if (keyProp && keyProp.type === 7 && !keyProp.exp) {\n        transformBindShorthand(keyProp);\n      }\n      const keyExp = keyProp && (keyProp.type === 6 ? keyProp.value ? createSimpleExpression(keyProp.value.content, true) : void 0 : keyProp.exp);\n      const keyProperty = keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null;\n      const isStableFragment = forNode.source.type === 4 && forNode.source.constType > 0;\n      const fragmentFlag = isStableFragment ? 64 : keyProp ? 128 : 256;\n      forNode.codegenNode = createVNodeCall(\n        context,\n        helper(FRAGMENT),\n        void 0,\n        renderExp,\n        fragmentFlag,\n        void 0,\n        void 0,\n        true,\n        !isStableFragment,\n        false,\n        node.loc\n      );\n      return () => {\n        let childBlock;\n        const { children } = forNode;\n        if (isTemplate) {\n          node.children.some((c) => {\n            if (c.type === 1) {\n              const key = findProp(c, \"key\");\n              if (key) {\n                context.onError(\n                  createCompilerError(\n                    33,\n                    key.loc\n                  )\n                );\n                return true;\n              }\n            }\n          });\n        }\n        const needFragmentWrapper = children.length !== 1 || children[0].type !== 1;\n        const slotOutlet = isSlotOutlet(node) ? node : isTemplate && node.children.length === 1 && isSlotOutlet(node.children[0]) ? node.children[0] : null;\n        if (slotOutlet) {\n          childBlock = slotOutlet.codegenNode;\n          if (isTemplate && keyProperty) {\n            injectProp(childBlock, keyProperty, context);\n          }\n        } else if (needFragmentWrapper) {\n          childBlock = createVNodeCall(\n            context,\n            helper(FRAGMENT),\n            keyProperty ? createObjectExpression([keyProperty]) : void 0,\n            node.children,\n            64,\n            void 0,\n            void 0,\n            true,\n            void 0,\n            false\n          );\n        } else {\n          childBlock = children[0].codegenNode;\n          if (isTemplate && keyProperty) {\n            injectProp(childBlock, keyProperty, context);\n          }\n          if (childBlock.isBlock !== !isStableFragment) {\n            if (childBlock.isBlock) {\n              removeHelper(OPEN_BLOCK);\n              removeHelper(\n                getVNodeBlockHelper(context.inSSR, childBlock.isComponent)\n              );\n            } else {\n              removeHelper(\n                getVNodeHelper(context.inSSR, childBlock.isComponent)\n              );\n            }\n          }\n          childBlock.isBlock = !isStableFragment;\n          if (childBlock.isBlock) {\n            helper(OPEN_BLOCK);\n            helper(getVNodeBlockHelper(context.inSSR, childBlock.isComponent));\n          } else {\n            helper(getVNodeHelper(context.inSSR, childBlock.isComponent));\n          }\n        }\n        if (memo) {\n          const loop = createFunctionExpression(\n            createForLoopParams(forNode.parseResult, [\n              createSimpleExpression(`_cached`)\n            ])\n          );\n          loop.body = createBlockStatement([\n            createCompoundExpression([`const _memo = (`, memo.exp, `)`]),\n            createCompoundExpression([\n              `if (_cached`,\n              ...keyExp ? [` && _cached.key === `, keyExp] : [],\n              ` && ${context.helperString(\n                IS_MEMO_SAME\n              )}(_cached, _memo)) return _cached`\n            ]),\n            createCompoundExpression([`const _item = `, childBlock]),\n            createSimpleExpression(`_item.memo = _memo`),\n            createSimpleExpression(`return _item`)\n          ]);\n          renderExp.arguments.push(\n            loop,\n            createSimpleExpression(`_cache`),\n            createSimpleExpression(String(context.cached++))\n          );\n        } else {\n          renderExp.arguments.push(\n            createFunctionExpression(\n              createForLoopParams(forNode.parseResult),\n              childBlock,\n              true\n            )\n          );\n        }\n      };\n    });\n  }\n);\nfunction processFor(node, dir, context, processCodegen) {\n  if (!dir.exp) {\n    context.onError(\n      createCompilerError(31, dir.loc)\n    );\n    return;\n  }\n  const parseResult = dir.forParseResult;\n  if (!parseResult) {\n    context.onError(\n      createCompilerError(32, dir.loc)\n    );\n    return;\n  }\n  finalizeForParseResult(parseResult, context);\n  const { addIdentifiers, removeIdentifiers, scopes } = context;\n  const { source, value, key, index } = parseResult;\n  const forNode = {\n    type: 11,\n    loc: dir.loc,\n    source,\n    valueAlias: value,\n    keyAlias: key,\n    objectIndexAlias: index,\n    parseResult,\n    children: isTemplateNode(node) ? node.children : [node]\n  };\n  context.replaceNode(forNode);\n  scopes.vFor++;\n  const onExit = processCodegen && processCodegen(forNode);\n  return () => {\n    scopes.vFor--;\n    if (onExit) onExit();\n  };\n}\nfunction finalizeForParseResult(result, context) {\n  if (result.finalized) return;\n  {\n    validateBrowserExpression(result.source, context);\n    if (result.key) {\n      validateBrowserExpression(\n        result.key,\n        context,\n        true\n      );\n    }\n    if (result.index) {\n      validateBrowserExpression(\n        result.index,\n        context,\n        true\n      );\n    }\n    if (result.value) {\n      validateBrowserExpression(\n        result.value,\n        context,\n        true\n      );\n    }\n  }\n  result.finalized = true;\n}\nfunction createForLoopParams({ value, key, index }, memoArgs = []) {\n  return createParamsList([value, key, index, ...memoArgs]);\n}\nfunction createParamsList(args) {\n  let i = args.length;\n  while (i--) {\n    if (args[i]) break;\n  }\n  return args.slice(0, i + 1).map((arg, i2) => arg || createSimpleExpression(`_`.repeat(i2 + 1), false));\n}\n\nconst defaultFallback = createSimpleExpression(`undefined`, false);\nconst trackSlotScopes = (node, context) => {\n  if (node.type === 1 && (node.tagType === 1 || node.tagType === 3)) {\n    const vSlot = findDir(node, \"slot\");\n    if (vSlot) {\n      vSlot.exp;\n      context.scopes.vSlot++;\n      return () => {\n        context.scopes.vSlot--;\n      };\n    }\n  }\n};\nconst buildClientSlotFn = (props, _vForExp, children, loc) => createFunctionExpression(\n  props,\n  children,\n  false,\n  true,\n  children.length ? children[0].loc : loc\n);\nfunction buildSlots(node, context, buildSlotFn = buildClientSlotFn) {\n  context.helper(WITH_CTX);\n  const { children, loc } = node;\n  const slotsProperties = [];\n  const dynamicSlots = [];\n  let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0;\n  const onComponentSlot = findDir(node, \"slot\", true);\n  if (onComponentSlot) {\n    const { arg, exp } = onComponentSlot;\n    if (arg && !isStaticExp(arg)) {\n      hasDynamicSlots = true;\n    }\n    slotsProperties.push(\n      createObjectProperty(\n        arg || createSimpleExpression(\"default\", true),\n        buildSlotFn(exp, void 0, children, loc)\n      )\n    );\n  }\n  let hasTemplateSlots = false;\n  let hasNamedDefaultSlot = false;\n  const implicitDefaultChildren = [];\n  const seenSlotNames = /* @__PURE__ */ new Set();\n  let conditionalBranchIndex = 0;\n  for (let i = 0; i < children.length; i++) {\n    const slotElement = children[i];\n    let slotDir;\n    if (!isTemplateNode(slotElement) || !(slotDir = findDir(slotElement, \"slot\", true))) {\n      if (slotElement.type !== 3) {\n        implicitDefaultChildren.push(slotElement);\n      }\n      continue;\n    }\n    if (onComponentSlot) {\n      context.onError(\n        createCompilerError(37, slotDir.loc)\n      );\n      break;\n    }\n    hasTemplateSlots = true;\n    const { children: slotChildren, loc: slotLoc } = slotElement;\n    const {\n      arg: slotName = createSimpleExpression(`default`, true),\n      exp: slotProps,\n      loc: dirLoc\n    } = slotDir;\n    let staticSlotName;\n    if (isStaticExp(slotName)) {\n      staticSlotName = slotName ? slotName.content : `default`;\n    } else {\n      hasDynamicSlots = true;\n    }\n    const vFor = findDir(slotElement, \"for\");\n    const slotFunction = buildSlotFn(slotProps, vFor, slotChildren, slotLoc);\n    let vIf;\n    let vElse;\n    if (vIf = findDir(slotElement, \"if\")) {\n      hasDynamicSlots = true;\n      dynamicSlots.push(\n        createConditionalExpression(\n          vIf.exp,\n          buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),\n          defaultFallback\n        )\n      );\n    } else if (vElse = findDir(\n      slotElement,\n      /^else(-if)?$/,\n      true\n      /* allowEmpty */\n    )) {\n      let j = i;\n      let prev;\n      while (j--) {\n        prev = children[j];\n        if (prev.type !== 3) {\n          break;\n        }\n      }\n      if (prev && isTemplateNode(prev) && findDir(prev, /^(else-)?if$/)) {\n        let conditional = dynamicSlots[dynamicSlots.length - 1];\n        while (conditional.alternate.type === 19) {\n          conditional = conditional.alternate;\n        }\n        conditional.alternate = vElse.exp ? createConditionalExpression(\n          vElse.exp,\n          buildDynamicSlot(\n            slotName,\n            slotFunction,\n            conditionalBranchIndex++\n          ),\n          defaultFallback\n        ) : buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++);\n      } else {\n        context.onError(\n          createCompilerError(30, vElse.loc)\n        );\n      }\n    } else if (vFor) {\n      hasDynamicSlots = true;\n      const parseResult = vFor.forParseResult;\n      if (parseResult) {\n        finalizeForParseResult(parseResult, context);\n        dynamicSlots.push(\n          createCallExpression(context.helper(RENDER_LIST), [\n            parseResult.source,\n            createFunctionExpression(\n              createForLoopParams(parseResult),\n              buildDynamicSlot(slotName, slotFunction),\n              true\n            )\n          ])\n        );\n      } else {\n        context.onError(\n          createCompilerError(\n            32,\n            vFor.loc\n          )\n        );\n      }\n    } else {\n      if (staticSlotName) {\n        if (seenSlotNames.has(staticSlotName)) {\n          context.onError(\n            createCompilerError(\n              38,\n              dirLoc\n            )\n          );\n          continue;\n        }\n        seenSlotNames.add(staticSlotName);\n        if (staticSlotName === \"default\") {\n          hasNamedDefaultSlot = true;\n        }\n      }\n      slotsProperties.push(createObjectProperty(slotName, slotFunction));\n    }\n  }\n  if (!onComponentSlot) {\n    const buildDefaultSlotProperty = (props, children2) => {\n      const fn = buildSlotFn(props, void 0, children2, loc);\n      return createObjectProperty(`default`, fn);\n    };\n    if (!hasTemplateSlots) {\n      slotsProperties.push(buildDefaultSlotProperty(void 0, children));\n    } else if (implicitDefaultChildren.length && // #3766\n    // with whitespace: 'preserve', whitespaces between slots will end up in\n    // implicitDefaultChildren. Ignore if all implicit children are whitespaces.\n    implicitDefaultChildren.some((node2) => isNonWhitespaceContent(node2))) {\n      if (hasNamedDefaultSlot) {\n        context.onError(\n          createCompilerError(\n            39,\n            implicitDefaultChildren[0].loc\n          )\n        );\n      } else {\n        slotsProperties.push(\n          buildDefaultSlotProperty(void 0, implicitDefaultChildren)\n        );\n      }\n    }\n  }\n  const slotFlag = hasDynamicSlots ? 2 : hasForwardedSlots(node.children) ? 3 : 1;\n  let slots = createObjectExpression(\n    slotsProperties.concat(\n      createObjectProperty(\n        `_`,\n        // 2 = compiled but dynamic = can skip normalization, but must run diff\n        // 1 = compiled and static = can skip normalization AND diff as optimized\n        createSimpleExpression(\n          slotFlag + (` /* ${slotFlagsText[slotFlag]} */` ),\n          false\n        )\n      )\n    ),\n    loc\n  );\n  if (dynamicSlots.length) {\n    slots = createCallExpression(context.helper(CREATE_SLOTS), [\n      slots,\n      createArrayExpression(dynamicSlots)\n    ]);\n  }\n  return {\n    slots,\n    hasDynamicSlots\n  };\n}\nfunction buildDynamicSlot(name, fn, index) {\n  const props = [\n    createObjectProperty(`name`, name),\n    createObjectProperty(`fn`, fn)\n  ];\n  if (index != null) {\n    props.push(\n      createObjectProperty(`key`, createSimpleExpression(String(index), true))\n    );\n  }\n  return createObjectExpression(props);\n}\nfunction hasForwardedSlots(children) {\n  for (let i = 0; i < children.length; i++) {\n    const child = children[i];\n    switch (child.type) {\n      case 1:\n        if (child.tagType === 2 || hasForwardedSlots(child.children)) {\n          return true;\n        }\n        break;\n      case 9:\n        if (hasForwardedSlots(child.branches)) return true;\n        break;\n      case 10:\n      case 11:\n        if (hasForwardedSlots(child.children)) return true;\n        break;\n    }\n  }\n  return false;\n}\nfunction isNonWhitespaceContent(node) {\n  if (node.type !== 2 && node.type !== 12)\n    return true;\n  return node.type === 2 ? !!node.content.trim() : isNonWhitespaceContent(node.content);\n}\n\nconst directiveImportMap = /* @__PURE__ */ new WeakMap();\nconst transformElement = (node, context) => {\n  return function postTransformElement() {\n    node = context.currentNode;\n    if (!(node.type === 1 && (node.tagType === 0 || node.tagType === 1))) {\n      return;\n    }\n    const { tag, props } = node;\n    const isComponent = node.tagType === 1;\n    let vnodeTag = isComponent ? resolveComponentType(node, context) : `\"${tag}\"`;\n    const isDynamicComponent = isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT;\n    let vnodeProps;\n    let vnodeChildren;\n    let patchFlag = 0;\n    let vnodeDynamicProps;\n    let dynamicPropNames;\n    let vnodeDirectives;\n    let shouldUseBlock = (\n      // dynamic component may resolve to plain elements\n      isDynamicComponent || vnodeTag === TELEPORT || vnodeTag === SUSPENSE || !isComponent && // <svg> and <foreignObject> must be forced into blocks so that block\n      // updates inside get proper isSVG flag at runtime. (#639, #643)\n      // This is technically web-specific, but splitting the logic out of core\n      // leads to too much unnecessary complexity.\n      (tag === \"svg\" || tag === \"foreignObject\" || tag === \"math\")\n    );\n    if (props.length > 0) {\n      const propsBuildResult = buildProps(\n        node,\n        context,\n        void 0,\n        isComponent,\n        isDynamicComponent\n      );\n      vnodeProps = propsBuildResult.props;\n      patchFlag = propsBuildResult.patchFlag;\n      dynamicPropNames = propsBuildResult.dynamicPropNames;\n      const directives = propsBuildResult.directives;\n      vnodeDirectives = directives && directives.length ? createArrayExpression(\n        directives.map((dir) => buildDirectiveArgs(dir, context))\n      ) : void 0;\n      if (propsBuildResult.shouldUseBlock) {\n        shouldUseBlock = true;\n      }\n    }\n    if (node.children.length > 0) {\n      if (vnodeTag === KEEP_ALIVE) {\n        shouldUseBlock = true;\n        patchFlag |= 1024;\n        if (node.children.length > 1) {\n          context.onError(\n            createCompilerError(46, {\n              start: node.children[0].loc.start,\n              end: node.children[node.children.length - 1].loc.end,\n              source: \"\"\n            })\n          );\n        }\n      }\n      const shouldBuildAsSlots = isComponent && // Teleport is not a real component and has dedicated runtime handling\n      vnodeTag !== TELEPORT && // explained above.\n      vnodeTag !== KEEP_ALIVE;\n      if (shouldBuildAsSlots) {\n        const { slots, hasDynamicSlots } = buildSlots(node, context);\n        vnodeChildren = slots;\n        if (hasDynamicSlots) {\n          patchFlag |= 1024;\n        }\n      } else if (node.children.length === 1 && vnodeTag !== TELEPORT) {\n        const child = node.children[0];\n        const type = child.type;\n        const hasDynamicTextChild = type === 5 || type === 8;\n        if (hasDynamicTextChild && getConstantType(child, context) === 0) {\n          patchFlag |= 1;\n        }\n        if (hasDynamicTextChild || type === 2) {\n          vnodeChildren = child;\n        } else {\n          vnodeChildren = node.children;\n        }\n      } else {\n        vnodeChildren = node.children;\n      }\n    }\n    if (dynamicPropNames && dynamicPropNames.length) {\n      vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames);\n    }\n    node.codegenNode = createVNodeCall(\n      context,\n      vnodeTag,\n      vnodeProps,\n      vnodeChildren,\n      patchFlag === 0 ? void 0 : patchFlag,\n      vnodeDynamicProps,\n      vnodeDirectives,\n      !!shouldUseBlock,\n      false,\n      isComponent,\n      node.loc\n    );\n  };\n};\nfunction resolveComponentType(node, context, ssr = false) {\n  let { tag } = node;\n  const isExplicitDynamic = isComponentTag(tag);\n  const isProp = findProp(\n    node,\n    \"is\",\n    false,\n    true\n    /* allow empty */\n  );\n  if (isProp) {\n    if (isExplicitDynamic || false) {\n      let exp;\n      if (isProp.type === 6) {\n        exp = isProp.value && createSimpleExpression(isProp.value.content, true);\n      } else {\n        exp = isProp.exp;\n        if (!exp) {\n          exp = createSimpleExpression(`is`, false, isProp.loc);\n        }\n      }\n      if (exp) {\n        return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [\n          exp\n        ]);\n      }\n    } else if (isProp.type === 6 && isProp.value.content.startsWith(\"vue:\")) {\n      tag = isProp.value.content.slice(4);\n    }\n  }\n  const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag);\n  if (builtIn) {\n    if (!ssr) context.helper(builtIn);\n    return builtIn;\n  }\n  context.helper(RESOLVE_COMPONENT);\n  context.components.add(tag);\n  return toValidAssetId(tag, `component`);\n}\nfunction buildProps(node, context, props = node.props, isComponent, isDynamicComponent, ssr = false) {\n  const { tag, loc: elementLoc, children } = node;\n  let properties = [];\n  const mergeArgs = [];\n  const runtimeDirectives = [];\n  const hasChildren = children.length > 0;\n  let shouldUseBlock = false;\n  let patchFlag = 0;\n  let hasRef = false;\n  let hasClassBinding = false;\n  let hasStyleBinding = false;\n  let hasHydrationEventBinding = false;\n  let hasDynamicKeys = false;\n  let hasVnodeHook = false;\n  const dynamicPropNames = [];\n  const pushMergeArg = (arg) => {\n    if (properties.length) {\n      mergeArgs.push(\n        createObjectExpression(dedupeProperties(properties), elementLoc)\n      );\n      properties = [];\n    }\n    if (arg) mergeArgs.push(arg);\n  };\n  const pushRefVForMarker = () => {\n    if (context.scopes.vFor > 0) {\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(\"ref_for\", true),\n          createSimpleExpression(\"true\")\n        )\n      );\n    }\n  };\n  const analyzePatchFlag = ({ key, value }) => {\n    if (isStaticExp(key)) {\n      const name = key.content;\n      const isEventHandler = isOn(name);\n      if (isEventHandler && (!isComponent || isDynamicComponent) && // omit the flag for click handlers because hydration gives click\n      // dedicated fast path.\n      name.toLowerCase() !== \"onclick\" && // omit v-model handlers\n      name !== \"onUpdate:modelValue\" && // omit onVnodeXXX hooks\n      !isReservedProp(name)) {\n        hasHydrationEventBinding = true;\n      }\n      if (isEventHandler && isReservedProp(name)) {\n        hasVnodeHook = true;\n      }\n      if (isEventHandler && value.type === 14) {\n        value = value.arguments[0];\n      }\n      if (value.type === 20 || (value.type === 4 || value.type === 8) && getConstantType(value, context) > 0) {\n        return;\n      }\n      if (name === \"ref\") {\n        hasRef = true;\n      } else if (name === \"class\") {\n        hasClassBinding = true;\n      } else if (name === \"style\") {\n        hasStyleBinding = true;\n      } else if (name !== \"key\" && !dynamicPropNames.includes(name)) {\n        dynamicPropNames.push(name);\n      }\n      if (isComponent && (name === \"class\" || name === \"style\") && !dynamicPropNames.includes(name)) {\n        dynamicPropNames.push(name);\n      }\n    } else {\n      hasDynamicKeys = true;\n    }\n  };\n  for (let i = 0; i < props.length; i++) {\n    const prop = props[i];\n    if (prop.type === 6) {\n      const { loc, name, nameLoc, value } = prop;\n      let isStatic = true;\n      if (name === \"ref\") {\n        hasRef = true;\n        pushRefVForMarker();\n      }\n      if (name === \"is\" && (isComponentTag(tag) || value && value.content.startsWith(\"vue:\") || false)) {\n        continue;\n      }\n      properties.push(\n        createObjectProperty(\n          createSimpleExpression(name, true, nameLoc),\n          createSimpleExpression(\n            value ? value.content : \"\",\n            isStatic,\n            value ? value.loc : loc\n          )\n        )\n      );\n    } else {\n      const { name, arg, exp, loc, modifiers } = prop;\n      const isVBind = name === \"bind\";\n      const isVOn = name === \"on\";\n      if (name === \"slot\") {\n        if (!isComponent) {\n          context.onError(\n            createCompilerError(40, loc)\n          );\n        }\n        continue;\n      }\n      if (name === \"once\" || name === \"memo\") {\n        continue;\n      }\n      if (name === \"is\" || isVBind && isStaticArgOf(arg, \"is\") && (isComponentTag(tag) || false)) {\n        continue;\n      }\n      if (isVOn && ssr) {\n        continue;\n      }\n      if (\n        // #938: elements with dynamic keys should be forced into blocks\n        isVBind && isStaticArgOf(arg, \"key\") || // inline before-update hooks need to force block so that it is invoked\n        // before children\n        isVOn && hasChildren && isStaticArgOf(arg, \"vue:before-update\")\n      ) {\n        shouldUseBlock = true;\n      }\n      if (isVBind && isStaticArgOf(arg, \"ref\")) {\n        pushRefVForMarker();\n      }\n      if (!arg && (isVBind || isVOn)) {\n        hasDynamicKeys = true;\n        if (exp) {\n          if (isVBind) {\n            pushRefVForMarker();\n            pushMergeArg();\n            mergeArgs.push(exp);\n          } else {\n            pushMergeArg({\n              type: 14,\n              loc,\n              callee: context.helper(TO_HANDLERS),\n              arguments: isComponent ? [exp] : [exp, `true`]\n            });\n          }\n        } else {\n          context.onError(\n            createCompilerError(\n              isVBind ? 34 : 35,\n              loc\n            )\n          );\n        }\n        continue;\n      }\n      if (isVBind && modifiers.includes(\"prop\")) {\n        patchFlag |= 32;\n      }\n      const directiveTransform = context.directiveTransforms[name];\n      if (directiveTransform) {\n        const { props: props2, needRuntime } = directiveTransform(prop, node, context);\n        !ssr && props2.forEach(analyzePatchFlag);\n        if (isVOn && arg && !isStaticExp(arg)) {\n          pushMergeArg(createObjectExpression(props2, elementLoc));\n        } else {\n          properties.push(...props2);\n        }\n        if (needRuntime) {\n          runtimeDirectives.push(prop);\n          if (isSymbol(needRuntime)) {\n            directiveImportMap.set(prop, needRuntime);\n          }\n        }\n      } else if (!isBuiltInDirective(name)) {\n        runtimeDirectives.push(prop);\n        if (hasChildren) {\n          shouldUseBlock = true;\n        }\n      }\n    }\n  }\n  let propsExpression = void 0;\n  if (mergeArgs.length) {\n    pushMergeArg();\n    if (mergeArgs.length > 1) {\n      propsExpression = createCallExpression(\n        context.helper(MERGE_PROPS),\n        mergeArgs,\n        elementLoc\n      );\n    } else {\n      propsExpression = mergeArgs[0];\n    }\n  } else if (properties.length) {\n    propsExpression = createObjectExpression(\n      dedupeProperties(properties),\n      elementLoc\n    );\n  }\n  if (hasDynamicKeys) {\n    patchFlag |= 16;\n  } else {\n    if (hasClassBinding && !isComponent) {\n      patchFlag |= 2;\n    }\n    if (hasStyleBinding && !isComponent) {\n      patchFlag |= 4;\n    }\n    if (dynamicPropNames.length) {\n      patchFlag |= 8;\n    }\n    if (hasHydrationEventBinding) {\n      patchFlag |= 32;\n    }\n  }\n  if (!shouldUseBlock && (patchFlag === 0 || patchFlag === 32) && (hasRef || hasVnodeHook || runtimeDirectives.length > 0)) {\n    patchFlag |= 512;\n  }\n  if (!context.inSSR && propsExpression) {\n    switch (propsExpression.type) {\n      case 15:\n        let classKeyIndex = -1;\n        let styleKeyIndex = -1;\n        let hasDynamicKey = false;\n        for (let i = 0; i < propsExpression.properties.length; i++) {\n          const key = propsExpression.properties[i].key;\n          if (isStaticExp(key)) {\n            if (key.content === \"class\") {\n              classKeyIndex = i;\n            } else if (key.content === \"style\") {\n              styleKeyIndex = i;\n            }\n          } else if (!key.isHandlerKey) {\n            hasDynamicKey = true;\n          }\n        }\n        const classProp = propsExpression.properties[classKeyIndex];\n        const styleProp = propsExpression.properties[styleKeyIndex];\n        if (!hasDynamicKey) {\n          if (classProp && !isStaticExp(classProp.value)) {\n            classProp.value = createCallExpression(\n              context.helper(NORMALIZE_CLASS),\n              [classProp.value]\n            );\n          }\n          if (styleProp && // the static style is compiled into an object,\n          // so use `hasStyleBinding` to ensure that it is a dynamic style binding\n          (hasStyleBinding || styleProp.value.type === 4 && styleProp.value.content.trim()[0] === `[` || // v-bind:style and style both exist,\n          // v-bind:style with static literal object\n          styleProp.value.type === 17)) {\n            styleProp.value = createCallExpression(\n              context.helper(NORMALIZE_STYLE),\n              [styleProp.value]\n            );\n          }\n        } else {\n          propsExpression = createCallExpression(\n            context.helper(NORMALIZE_PROPS),\n            [propsExpression]\n          );\n        }\n        break;\n      case 14:\n        break;\n      default:\n        propsExpression = createCallExpression(\n          context.helper(NORMALIZE_PROPS),\n          [\n            createCallExpression(context.helper(GUARD_REACTIVE_PROPS), [\n              propsExpression\n            ])\n          ]\n        );\n        break;\n    }\n  }\n  return {\n    props: propsExpression,\n    directives: runtimeDirectives,\n    patchFlag,\n    dynamicPropNames,\n    shouldUseBlock\n  };\n}\nfunction dedupeProperties(properties) {\n  const knownProps = /* @__PURE__ */ new Map();\n  const deduped = [];\n  for (let i = 0; i < properties.length; i++) {\n    const prop = properties[i];\n    if (prop.key.type === 8 || !prop.key.isStatic) {\n      deduped.push(prop);\n      continue;\n    }\n    const name = prop.key.content;\n    const existing = knownProps.get(name);\n    if (existing) {\n      if (name === \"style\" || name === \"class\" || isOn(name)) {\n        mergeAsArray(existing, prop);\n      }\n    } else {\n      knownProps.set(name, prop);\n      deduped.push(prop);\n    }\n  }\n  return deduped;\n}\nfunction mergeAsArray(existing, incoming) {\n  if (existing.value.type === 17) {\n    existing.value.elements.push(incoming.value);\n  } else {\n    existing.value = createArrayExpression(\n      [existing.value, incoming.value],\n      existing.loc\n    );\n  }\n}\nfunction buildDirectiveArgs(dir, context) {\n  const dirArgs = [];\n  const runtime = directiveImportMap.get(dir);\n  if (runtime) {\n    dirArgs.push(context.helperString(runtime));\n  } else {\n    {\n      context.helper(RESOLVE_DIRECTIVE);\n      context.directives.add(dir.name);\n      dirArgs.push(toValidAssetId(dir.name, `directive`));\n    }\n  }\n  const { loc } = dir;\n  if (dir.exp) dirArgs.push(dir.exp);\n  if (dir.arg) {\n    if (!dir.exp) {\n      dirArgs.push(`void 0`);\n    }\n    dirArgs.push(dir.arg);\n  }\n  if (Object.keys(dir.modifiers).length) {\n    if (!dir.arg) {\n      if (!dir.exp) {\n        dirArgs.push(`void 0`);\n      }\n      dirArgs.push(`void 0`);\n    }\n    const trueExpression = createSimpleExpression(`true`, false, loc);\n    dirArgs.push(\n      createObjectExpression(\n        dir.modifiers.map(\n          (modifier) => createObjectProperty(modifier, trueExpression)\n        ),\n        loc\n      )\n    );\n  }\n  return createArrayExpression(dirArgs, dir.loc);\n}\nfunction stringifyDynamicPropNames(props) {\n  let propsNamesString = `[`;\n  for (let i = 0, l = props.length; i < l; i++) {\n    propsNamesString += JSON.stringify(props[i]);\n    if (i < l - 1) propsNamesString += \", \";\n  }\n  return propsNamesString + `]`;\n}\nfunction isComponentTag(tag) {\n  return tag === \"component\" || tag === \"Component\";\n}\n\nconst transformSlotOutlet = (node, context) => {\n  if (isSlotOutlet(node)) {\n    const { children, loc } = node;\n    const { slotName, slotProps } = processSlotOutlet(node, context);\n    const slotArgs = [\n      context.prefixIdentifiers ? `_ctx.$slots` : `$slots`,\n      slotName,\n      \"{}\",\n      \"undefined\",\n      \"true\"\n    ];\n    let expectedLen = 2;\n    if (slotProps) {\n      slotArgs[2] = slotProps;\n      expectedLen = 3;\n    }\n    if (children.length) {\n      slotArgs[3] = createFunctionExpression([], children, false, false, loc);\n      expectedLen = 4;\n    }\n    if (context.scopeId && !context.slotted) {\n      expectedLen = 5;\n    }\n    slotArgs.splice(expectedLen);\n    node.codegenNode = createCallExpression(\n      context.helper(RENDER_SLOT),\n      slotArgs,\n      loc\n    );\n  }\n};\nfunction processSlotOutlet(node, context) {\n  let slotName = `\"default\"`;\n  let slotProps = void 0;\n  const nonNameProps = [];\n  for (let i = 0; i < node.props.length; i++) {\n    const p = node.props[i];\n    if (p.type === 6) {\n      if (p.value) {\n        if (p.name === \"name\") {\n          slotName = JSON.stringify(p.value.content);\n        } else {\n          p.name = camelize(p.name);\n          nonNameProps.push(p);\n        }\n      }\n    } else {\n      if (p.name === \"bind\" && isStaticArgOf(p.arg, \"name\")) {\n        if (p.exp) {\n          slotName = p.exp;\n        } else if (p.arg && p.arg.type === 4) {\n          const name = camelize(p.arg.content);\n          slotName = p.exp = createSimpleExpression(name, false, p.arg.loc);\n        }\n      } else {\n        if (p.name === \"bind\" && p.arg && isStaticExp(p.arg)) {\n          p.arg.content = camelize(p.arg.content);\n        }\n        nonNameProps.push(p);\n      }\n    }\n  }\n  if (nonNameProps.length > 0) {\n    const { props, directives } = buildProps(\n      node,\n      context,\n      nonNameProps,\n      false,\n      false\n    );\n    slotProps = props;\n    if (directives.length) {\n      context.onError(\n        createCompilerError(\n          36,\n          directives[0].loc\n        )\n      );\n    }\n  }\n  return {\n    slotName,\n    slotProps\n  };\n}\n\nconst fnExpRE = /^\\s*(async\\s*)?(\\([^)]*?\\)|[\\w$_]+)\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/;\nconst transformOn$1 = (dir, node, context, augmentor) => {\n  const { loc, modifiers, arg } = dir;\n  if (!dir.exp && !modifiers.length) {\n    context.onError(createCompilerError(35, loc));\n  }\n  let eventName;\n  if (arg.type === 4) {\n    if (arg.isStatic) {\n      let rawName = arg.content;\n      if (rawName.startsWith(\"vnode\")) {\n        context.onError(createCompilerError(51, arg.loc));\n      }\n      if (rawName.startsWith(\"vue:\")) {\n        rawName = `vnode-${rawName.slice(4)}`;\n      }\n      const eventString = node.tagType !== 0 || rawName.startsWith(\"vnode\") || !/[A-Z]/.test(rawName) ? (\n        // for non-element and vnode lifecycle event listeners, auto convert\n        // it to camelCase. See issue #2249\n        toHandlerKey(camelize(rawName))\n      ) : (\n        // preserve case for plain element listeners that have uppercase\n        // letters, as these may be custom elements' custom events\n        `on:${rawName}`\n      );\n      eventName = createSimpleExpression(eventString, true, arg.loc);\n    } else {\n      eventName = createCompoundExpression([\n        `${context.helperString(TO_HANDLER_KEY)}(`,\n        arg,\n        `)`\n      ]);\n    }\n  } else {\n    eventName = arg;\n    eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);\n    eventName.children.push(`)`);\n  }\n  let exp = dir.exp;\n  if (exp && !exp.content.trim()) {\n    exp = void 0;\n  }\n  let shouldCache = context.cacheHandlers && !exp && !context.inVOnce;\n  if (exp) {\n    const isMemberExp = isMemberExpression(exp.content);\n    const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));\n    const hasMultipleStatements = exp.content.includes(`;`);\n    {\n      validateBrowserExpression(\n        exp,\n        context,\n        false,\n        hasMultipleStatements\n      );\n    }\n    if (isInlineStatement || shouldCache && isMemberExp) {\n      exp = createCompoundExpression([\n        `${isInlineStatement ? `$event` : `${``}(...args)`} => ${hasMultipleStatements ? `{` : `(`}`,\n        exp,\n        hasMultipleStatements ? `}` : `)`\n      ]);\n    }\n  }\n  let ret = {\n    props: [\n      createObjectProperty(\n        eventName,\n        exp || createSimpleExpression(`() => {}`, false, loc)\n      )\n    ]\n  };\n  if (augmentor) {\n    ret = augmentor(ret);\n  }\n  if (shouldCache) {\n    ret.props[0].value = context.cache(ret.props[0].value);\n  }\n  ret.props.forEach((p) => p.key.isHandlerKey = true);\n  return ret;\n};\n\nconst transformText = (node, context) => {\n  if (node.type === 0 || node.type === 1 || node.type === 11 || node.type === 10) {\n    return () => {\n      const children = node.children;\n      let currentContainer = void 0;\n      let hasText = false;\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i];\n        if (isText$1(child)) {\n          hasText = true;\n          for (let j = i + 1; j < children.length; j++) {\n            const next = children[j];\n            if (isText$1(next)) {\n              if (!currentContainer) {\n                currentContainer = children[i] = createCompoundExpression(\n                  [child],\n                  child.loc\n                );\n              }\n              currentContainer.children.push(` + `, next);\n              children.splice(j, 1);\n              j--;\n            } else {\n              currentContainer = void 0;\n              break;\n            }\n          }\n        }\n      }\n      if (!hasText || // if this is a plain element with a single text child, leave it\n      // as-is since the runtime has dedicated fast path for this by directly\n      // setting textContent of the element.\n      // for component root it's always normalized anyway.\n      children.length === 1 && (node.type === 0 || node.type === 1 && node.tagType === 0 && // #3756\n      // custom directives can potentially add DOM elements arbitrarily,\n      // we need to avoid setting textContent of the element at runtime\n      // to avoid accidentally overwriting the DOM elements added\n      // by the user through custom directives.\n      !node.props.find(\n        (p) => p.type === 7 && !context.directiveTransforms[p.name]\n      ) && // in compat mode, <template> tags with no special directives\n      // will be rendered as a fragment so its children must be\n      // converted into vnodes.\n      true)) {\n        return;\n      }\n      for (let i = 0; i < children.length; i++) {\n        const child = children[i];\n        if (isText$1(child) || child.type === 8) {\n          const callArgs = [];\n          if (child.type !== 2 || child.content !== \" \") {\n            callArgs.push(child);\n          }\n          if (!context.ssr && getConstantType(child, context) === 0) {\n            callArgs.push(\n              1 + (` /* ${PatchFlagNames[1]} */` )\n            );\n          }\n          children[i] = {\n            type: 12,\n            content: child,\n            loc: child.loc,\n            codegenNode: createCallExpression(\n              context.helper(CREATE_TEXT),\n              callArgs\n            )\n          };\n        }\n      }\n    };\n  }\n};\n\nconst seen$1 = /* @__PURE__ */ new WeakSet();\nconst transformOnce = (node, context) => {\n  if (node.type === 1 && findDir(node, \"once\", true)) {\n    if (seen$1.has(node) || context.inVOnce || context.inSSR) {\n      return;\n    }\n    seen$1.add(node);\n    context.inVOnce = true;\n    context.helper(SET_BLOCK_TRACKING);\n    return () => {\n      context.inVOnce = false;\n      const cur = context.currentNode;\n      if (cur.codegenNode) {\n        cur.codegenNode = context.cache(\n          cur.codegenNode,\n          true\n          /* isVNode */\n        );\n      }\n    };\n  }\n};\n\nconst transformModel$1 = (dir, node, context) => {\n  const { exp, arg } = dir;\n  if (!exp) {\n    context.onError(\n      createCompilerError(41, dir.loc)\n    );\n    return createTransformProps();\n  }\n  const rawExp = exp.loc.source;\n  const expString = exp.type === 4 ? exp.content : rawExp;\n  const bindingType = context.bindingMetadata[rawExp];\n  if (bindingType === \"props\" || bindingType === \"props-aliased\") {\n    context.onError(createCompilerError(44, exp.loc));\n    return createTransformProps();\n  }\n  const maybeRef = false;\n  if (!expString.trim() || !isMemberExpression(expString) && !maybeRef) {\n    context.onError(\n      createCompilerError(42, exp.loc)\n    );\n    return createTransformProps();\n  }\n  const propName = arg ? arg : createSimpleExpression(\"modelValue\", true);\n  const eventName = arg ? isStaticExp(arg) ? `onUpdate:${camelize(arg.content)}` : createCompoundExpression(['\"onUpdate:\" + ', arg]) : `onUpdate:modelValue`;\n  let assignmentExp;\n  const eventArg = context.isTS ? `($event: any)` : `$event`;\n  {\n    assignmentExp = createCompoundExpression([\n      `${eventArg} => ((`,\n      exp,\n      `) = $event)`\n    ]);\n  }\n  const props = [\n    // modelValue: foo\n    createObjectProperty(propName, dir.exp),\n    // \"onUpdate:modelValue\": $event => (foo = $event)\n    createObjectProperty(eventName, assignmentExp)\n  ];\n  if (dir.modifiers.length && node.tagType === 1) {\n    const modifiers = dir.modifiers.map((m) => (isSimpleIdentifier(m) ? m : JSON.stringify(m)) + `: true`).join(`, `);\n    const modifiersKey = arg ? isStaticExp(arg) ? `${arg.content}Modifiers` : createCompoundExpression([arg, ' + \"Modifiers\"']) : `modelModifiers`;\n    props.push(\n      createObjectProperty(\n        modifiersKey,\n        createSimpleExpression(\n          `{ ${modifiers} }`,\n          false,\n          dir.loc,\n          2\n        )\n      )\n    );\n  }\n  return createTransformProps(props);\n};\nfunction createTransformProps(props = []) {\n  return { props };\n}\n\nconst seen = /* @__PURE__ */ new WeakSet();\nconst transformMemo = (node, context) => {\n  if (node.type === 1) {\n    const dir = findDir(node, \"memo\");\n    if (!dir || seen.has(node)) {\n      return;\n    }\n    seen.add(node);\n    return () => {\n      const codegenNode = node.codegenNode || context.currentNode.codegenNode;\n      if (codegenNode && codegenNode.type === 13) {\n        if (node.tagType !== 1) {\n          convertToBlock(codegenNode, context);\n        }\n        node.codegenNode = createCallExpression(context.helper(WITH_MEMO), [\n          dir.exp,\n          createFunctionExpression(void 0, codegenNode),\n          `_cache`,\n          String(context.cached++)\n        ]);\n      }\n    };\n  }\n};\n\nfunction getBaseTransformPreset(prefixIdentifiers) {\n  return [\n    [\n      transformOnce,\n      transformIf,\n      transformMemo,\n      transformFor,\n      ...[],\n      ...[transformExpression] ,\n      transformSlotOutlet,\n      transformElement,\n      trackSlotScopes,\n      transformText\n    ],\n    {\n      on: transformOn$1,\n      bind: transformBind,\n      model: transformModel$1\n    }\n  ];\n}\nfunction baseCompile(source, options = {}) {\n  const onError = options.onError || defaultOnError;\n  const isModuleMode = options.mode === \"module\";\n  {\n    if (options.prefixIdentifiers === true) {\n      onError(createCompilerError(47));\n    } else if (isModuleMode) {\n      onError(createCompilerError(48));\n    }\n  }\n  const prefixIdentifiers = false;\n  if (options.cacheHandlers) {\n    onError(createCompilerError(49));\n  }\n  if (options.scopeId && !isModuleMode) {\n    onError(createCompilerError(50));\n  }\n  const resolvedOptions = extend({}, options, {\n    prefixIdentifiers\n  });\n  const ast = isString(source) ? baseParse(source, resolvedOptions) : source;\n  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();\n  transform(\n    ast,\n    extend({}, resolvedOptions, {\n      nodeTransforms: [\n        ...nodeTransforms,\n        ...options.nodeTransforms || []\n        // user transforms\n      ],\n      directiveTransforms: extend(\n        {},\n        directiveTransforms,\n        options.directiveTransforms || {}\n        // user transforms\n      )\n    })\n  );\n  return generate(ast, resolvedOptions);\n}\n\nconst noopDirectiveTransform = () => ({ props: [] });\n\nconst V_MODEL_RADIO = Symbol(`vModelRadio` );\nconst V_MODEL_CHECKBOX = Symbol(`vModelCheckbox` );\nconst V_MODEL_TEXT = Symbol(`vModelText` );\nconst V_MODEL_SELECT = Symbol(`vModelSelect` );\nconst V_MODEL_DYNAMIC = Symbol(`vModelDynamic` );\nconst V_ON_WITH_MODIFIERS = Symbol(`vOnModifiersGuard` );\nconst V_ON_WITH_KEYS = Symbol(`vOnKeysGuard` );\nconst V_SHOW = Symbol(`vShow` );\nconst TRANSITION = Symbol(`Transition` );\nconst TRANSITION_GROUP = Symbol(`TransitionGroup` );\nregisterRuntimeHelpers({\n  [V_MODEL_RADIO]: `vModelRadio`,\n  [V_MODEL_CHECKBOX]: `vModelCheckbox`,\n  [V_MODEL_TEXT]: `vModelText`,\n  [V_MODEL_SELECT]: `vModelSelect`,\n  [V_MODEL_DYNAMIC]: `vModelDynamic`,\n  [V_ON_WITH_MODIFIERS]: `withModifiers`,\n  [V_ON_WITH_KEYS]: `withKeys`,\n  [V_SHOW]: `vShow`,\n  [TRANSITION]: `Transition`,\n  [TRANSITION_GROUP]: `TransitionGroup`\n});\n\nlet decoder;\nfunction decodeHtmlBrowser(raw, asAttr = false) {\n  if (!decoder) {\n    decoder = document.createElement(\"div\");\n  }\n  if (asAttr) {\n    decoder.innerHTML = `<div foo=\"${raw.replace(/\"/g, \"&quot;\")}\">`;\n    return decoder.children[0].getAttribute(\"foo\");\n  } else {\n    decoder.innerHTML = raw;\n    return decoder.textContent;\n  }\n}\n\nconst parserOptions = {\n  parseMode: \"html\",\n  isVoidTag,\n  isNativeTag: (tag) => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),\n  isPreTag: (tag) => tag === \"pre\",\n  decodeEntities: decodeHtmlBrowser ,\n  isBuiltInComponent: (tag) => {\n    if (tag === \"Transition\" || tag === \"transition\") {\n      return TRANSITION;\n    } else if (tag === \"TransitionGroup\" || tag === \"transition-group\") {\n      return TRANSITION_GROUP;\n    }\n  },\n  // https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher\n  getNamespace(tag, parent, rootNamespace) {\n    let ns = parent ? parent.ns : rootNamespace;\n    if (parent && ns === 2) {\n      if (parent.tag === \"annotation-xml\") {\n        if (tag === \"svg\") {\n          return 1;\n        }\n        if (parent.props.some(\n          (a) => a.type === 6 && a.name === \"encoding\" && a.value != null && (a.value.content === \"text/html\" || a.value.content === \"application/xhtml+xml\")\n        )) {\n          ns = 0;\n        }\n      } else if (/^m(?:[ions]|text)$/.test(parent.tag) && tag !== \"mglyph\" && tag !== \"malignmark\") {\n        ns = 0;\n      }\n    } else if (parent && ns === 1) {\n      if (parent.tag === \"foreignObject\" || parent.tag === \"desc\" || parent.tag === \"title\") {\n        ns = 0;\n      }\n    }\n    if (ns === 0) {\n      if (tag === \"svg\") {\n        return 1;\n      }\n      if (tag === \"math\") {\n        return 2;\n      }\n    }\n    return ns;\n  }\n};\n\nconst transformStyle = (node) => {\n  if (node.type === 1) {\n    node.props.forEach((p, i) => {\n      if (p.type === 6 && p.name === \"style\" && p.value) {\n        node.props[i] = {\n          type: 7,\n          name: `bind`,\n          arg: createSimpleExpression(`style`, true, p.loc),\n          exp: parseInlineCSS(p.value.content, p.loc),\n          modifiers: [],\n          loc: p.loc\n        };\n      }\n    });\n  }\n};\nconst parseInlineCSS = (cssText, loc) => {\n  const normalized = parseStringStyle(cssText);\n  return createSimpleExpression(\n    JSON.stringify(normalized),\n    false,\n    loc,\n    3\n  );\n};\n\nfunction createDOMCompilerError(code, loc) {\n  return createCompilerError(\n    code,\n    loc,\n    DOMErrorMessages \n  );\n}\nconst DOMErrorMessages = {\n  [53]: `v-html is missing expression.`,\n  [54]: `v-html will override element children.`,\n  [55]: `v-text is missing expression.`,\n  [56]: `v-text will override element children.`,\n  [57]: `v-model can only be used on <input>, <textarea> and <select> elements.`,\n  [58]: `v-model argument is not supported on plain elements.`,\n  [59]: `v-model cannot be used on file inputs since they are read-only. Use a v-on:change listener instead.`,\n  [60]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,\n  [61]: `v-show is missing expression.`,\n  [62]: `<Transition> expects exactly one child element or component.`,\n  [63]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`\n};\n\nconst transformVHtml = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    context.onError(\n      createDOMCompilerError(53, loc)\n    );\n  }\n  if (node.children.length) {\n    context.onError(\n      createDOMCompilerError(54, loc)\n    );\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`innerHTML`, true, loc),\n        exp || createSimpleExpression(\"\", true)\n      )\n    ]\n  };\n};\n\nconst transformVText = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    context.onError(\n      createDOMCompilerError(55, loc)\n    );\n  }\n  if (node.children.length) {\n    context.onError(\n      createDOMCompilerError(56, loc)\n    );\n    node.children.length = 0;\n  }\n  return {\n    props: [\n      createObjectProperty(\n        createSimpleExpression(`textContent`, true),\n        exp ? getConstantType(exp, context) > 0 ? exp : createCallExpression(\n          context.helperString(TO_DISPLAY_STRING),\n          [exp],\n          loc\n        ) : createSimpleExpression(\"\", true)\n      )\n    ]\n  };\n};\n\nconst transformModel = (dir, node, context) => {\n  const baseResult = transformModel$1(dir, node, context);\n  if (!baseResult.props.length || node.tagType === 1) {\n    return baseResult;\n  }\n  if (dir.arg) {\n    context.onError(\n      createDOMCompilerError(\n        58,\n        dir.arg.loc\n      )\n    );\n  }\n  function checkDuplicatedValue() {\n    const value = findDir(node, \"bind\");\n    if (value && isStaticArgOf(value.arg, \"value\")) {\n      context.onError(\n        createDOMCompilerError(\n          60,\n          value.loc\n        )\n      );\n    }\n  }\n  const { tag } = node;\n  const isCustomElement = context.isCustomElement(tag);\n  if (tag === \"input\" || tag === \"textarea\" || tag === \"select\" || isCustomElement) {\n    let directiveToUse = V_MODEL_TEXT;\n    let isInvalidType = false;\n    if (tag === \"input\" || isCustomElement) {\n      const type = findProp(node, `type`);\n      if (type) {\n        if (type.type === 7) {\n          directiveToUse = V_MODEL_DYNAMIC;\n        } else if (type.value) {\n          switch (type.value.content) {\n            case \"radio\":\n              directiveToUse = V_MODEL_RADIO;\n              break;\n            case \"checkbox\":\n              directiveToUse = V_MODEL_CHECKBOX;\n              break;\n            case \"file\":\n              isInvalidType = true;\n              context.onError(\n                createDOMCompilerError(\n                  59,\n                  dir.loc\n                )\n              );\n              break;\n            default:\n              checkDuplicatedValue();\n              break;\n          }\n        }\n      } else if (hasDynamicKeyVBind(node)) {\n        directiveToUse = V_MODEL_DYNAMIC;\n      } else {\n        checkDuplicatedValue();\n      }\n    } else if (tag === \"select\") {\n      directiveToUse = V_MODEL_SELECT;\n    } else {\n      checkDuplicatedValue();\n    }\n    if (!isInvalidType) {\n      baseResult.needRuntime = context.helper(directiveToUse);\n    }\n  } else {\n    context.onError(\n      createDOMCompilerError(\n        57,\n        dir.loc\n      )\n    );\n  }\n  baseResult.props = baseResult.props.filter(\n    (p) => !(p.key.type === 4 && p.key.content === \"modelValue\")\n  );\n  return baseResult;\n};\n\nconst isEventOptionModifier = /* @__PURE__ */ makeMap(`passive,once,capture`);\nconst isNonKeyModifier = /* @__PURE__ */ makeMap(\n  // event propagation management\n  `stop,prevent,self,ctrl,shift,alt,meta,exact,middle`\n);\nconst maybeKeyModifier = /* @__PURE__ */ makeMap(\"left,right\");\nconst isKeyboardEvent = /* @__PURE__ */ makeMap(\n  `onkeyup,onkeydown,onkeypress`,\n  true\n);\nconst resolveModifiers = (key, modifiers, context, loc) => {\n  const keyModifiers = [];\n  const nonKeyModifiers = [];\n  const eventOptionModifiers = [];\n  for (let i = 0; i < modifiers.length; i++) {\n    const modifier = modifiers[i];\n    if (isEventOptionModifier(modifier)) {\n      eventOptionModifiers.push(modifier);\n    } else {\n      if (maybeKeyModifier(modifier)) {\n        if (isStaticExp(key)) {\n          if (isKeyboardEvent(key.content)) {\n            keyModifiers.push(modifier);\n          } else {\n            nonKeyModifiers.push(modifier);\n          }\n        } else {\n          keyModifiers.push(modifier);\n          nonKeyModifiers.push(modifier);\n        }\n      } else {\n        if (isNonKeyModifier(modifier)) {\n          nonKeyModifiers.push(modifier);\n        } else {\n          keyModifiers.push(modifier);\n        }\n      }\n    }\n  }\n  return {\n    keyModifiers,\n    nonKeyModifiers,\n    eventOptionModifiers\n  };\n};\nconst transformClick = (key, event) => {\n  const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === \"onclick\";\n  return isStaticClick ? createSimpleExpression(event, true) : key.type !== 4 ? createCompoundExpression([\n    `(`,\n    key,\n    `) === \"onClick\" ? \"${event}\" : (`,\n    key,\n    `)`\n  ]) : key;\n};\nconst transformOn = (dir, node, context) => {\n  return transformOn$1(dir, node, context, (baseResult) => {\n    const { modifiers } = dir;\n    if (!modifiers.length) return baseResult;\n    let { key, value: handlerExp } = baseResult.props[0];\n    const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(key, modifiers, context, dir.loc);\n    if (nonKeyModifiers.includes(\"right\")) {\n      key = transformClick(key, `onContextmenu`);\n    }\n    if (nonKeyModifiers.includes(\"middle\")) {\n      key = transformClick(key, `onMouseup`);\n    }\n    if (nonKeyModifiers.length) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [\n        handlerExp,\n        JSON.stringify(nonKeyModifiers)\n      ]);\n    }\n    if (keyModifiers.length && // if event name is dynamic, always wrap with keys guard\n    (!isStaticExp(key) || isKeyboardEvent(key.content))) {\n      handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [\n        handlerExp,\n        JSON.stringify(keyModifiers)\n      ]);\n    }\n    if (eventOptionModifiers.length) {\n      const modifierPostfix = eventOptionModifiers.map(capitalize).join(\"\");\n      key = isStaticExp(key) ? createSimpleExpression(`${key.content}${modifierPostfix}`, true) : createCompoundExpression([`(`, key, `) + \"${modifierPostfix}\"`]);\n    }\n    return {\n      props: [createObjectProperty(key, handlerExp)]\n    };\n  });\n};\n\nconst transformShow = (dir, node, context) => {\n  const { exp, loc } = dir;\n  if (!exp) {\n    context.onError(\n      createDOMCompilerError(61, loc)\n    );\n  }\n  return {\n    props: [],\n    needRuntime: context.helper(V_SHOW)\n  };\n};\n\nconst transformTransition = (node, context) => {\n  if (node.type === 1 && node.tagType === 1) {\n    const component = context.isBuiltInComponent(node.tag);\n    if (component === TRANSITION) {\n      return () => {\n        if (!node.children.length) {\n          return;\n        }\n        if (hasMultipleChildren(node)) {\n          context.onError(\n            createDOMCompilerError(\n              62,\n              {\n                start: node.children[0].loc.start,\n                end: node.children[node.children.length - 1].loc.end,\n                source: \"\"\n              }\n            )\n          );\n        }\n        const child = node.children[0];\n        if (child.type === 1) {\n          for (const p of child.props) {\n            if (p.type === 7 && p.name === \"show\") {\n              node.props.push({\n                type: 6,\n                name: \"persisted\",\n                nameLoc: node.loc,\n                value: void 0,\n                loc: node.loc\n              });\n            }\n          }\n        }\n      };\n    }\n  }\n};\nfunction hasMultipleChildren(node) {\n  const children = node.children = node.children.filter(\n    (c) => c.type !== 3 && !(c.type === 2 && !c.content.trim())\n  );\n  const child = children[0];\n  return children.length !== 1 || child.type === 11 || child.type === 9 && child.branches.some(hasMultipleChildren);\n}\n\nconst ignoreSideEffectTags = (node, context) => {\n  if (node.type === 1 && node.tagType === 0 && (node.tag === \"script\" || node.tag === \"style\")) {\n    context.onError(\n      createDOMCompilerError(\n        63,\n        node.loc\n      )\n    );\n    context.removeNode();\n  }\n};\n\nconst DOMNodeTransforms = [\n  transformStyle,\n  ...[transformTransition] \n];\nconst DOMDirectiveTransforms = {\n  cloak: noopDirectiveTransform,\n  html: transformVHtml,\n  text: transformVText,\n  model: transformModel,\n  // override compiler-core\n  on: transformOn,\n  // override compiler-core\n  show: transformShow\n};\nfunction compile(src, options = {}) {\n  return baseCompile(\n    src,\n    extend({}, parserOptions, options, {\n      nodeTransforms: [\n        // ignore <script> and <tag>\n        // this is not put inside DOMNodeTransforms because that list is used\n        // by compiler-ssr to generate vnode fallback branches\n        ignoreSideEffectTags,\n        ...DOMNodeTransforms,\n        ...options.nodeTransforms || []\n      ],\n      directiveTransforms: extend(\n        {},\n        DOMDirectiveTransforms,\n        options.directiveTransforms || {}\n      ),\n      transformHoist: null \n    })\n  );\n}\n\n{\n  initDev();\n}\nconst compileCache = /* @__PURE__ */ new WeakMap();\nfunction getCache(options) {\n  let c = compileCache.get(options != null ? options : EMPTY_OBJ);\n  if (!c) {\n    c = /* @__PURE__ */ Object.create(null);\n    compileCache.set(options != null ? options : EMPTY_OBJ, c);\n  }\n  return c;\n}\nfunction compileToFunction(template, options) {\n  if (!isString(template)) {\n    if (template.nodeType) {\n      template = template.innerHTML;\n    } else {\n      warn(`invalid template option: `, template);\n      return NOOP;\n    }\n  }\n  const key = template;\n  const cache = getCache(options);\n  const cached = cache[key];\n  if (cached) {\n    return cached;\n  }\n  if (template[0] === \"#\") {\n    const el = document.querySelector(template);\n    if (!el) {\n      warn(`Template element not found or is empty: ${template}`);\n    }\n    template = el ? el.innerHTML : ``;\n  }\n  const opts = extend(\n    {\n      hoistStatic: true,\n      onError: onError ,\n      onWarn: (e) => onError(e, true) \n    },\n    options\n  );\n  if (!opts.isCustomElement && typeof customElements !== \"undefined\") {\n    opts.isCustomElement = (tag) => !!customElements.get(tag);\n  }\n  const { code } = compile(template, opts);\n  function onError(err, asWarning = false) {\n    const message = asWarning ? err.message : `Template compilation error: ${err.message}`;\n    const codeFrame = err.loc && generateCodeFrame(\n      template,\n      err.loc.start.offset,\n      err.loc.end.offset\n    );\n    warn(codeFrame ? `${message}\n${codeFrame}` : message);\n  }\n  const render = new Function(\"Vue\", code)(runtimeDom);\n  render._rc = true;\n  return cache[key] = render;\n}\nregisterRuntimeCompiler(compileToFunction);\n\nexport { BaseTransition, BaseTransitionPropsValidators, Comment, DeprecationTypes, EffectScope, ErrorCodes, ErrorTypeStrings, Fragment, KeepAlive, ReactiveEffect, Static, Suspense, Teleport, Text, TrackOpTypes, Transition, TransitionGroup, TriggerOpTypes, VueElement, assertNumber, callWithAsyncErrorHandling, callWithErrorHandling, camelize, capitalize, cloneVNode, compatUtils, compileToFunction as compile, computed, createApp, createBlock, createCommentVNode, createElementBlock, createBaseVNode as createElementVNode, createHydrationRenderer, createPropsRestProxy, createRenderer, createSSRApp, createSlots, createStaticVNode, createTextVNode, createVNode, customRef, defineAsyncComponent, defineComponent, defineCustomElement, defineEmits, defineExpose, defineModel, defineOptions, defineProps, defineSSRCustomElement, defineSlots, devtools, effect, effectScope, getCurrentInstance, getCurrentScope, getTransitionRawChildren, guardReactiveProps, h, handleError, hasInjectionContext, hydrate, initCustomFormatter, initDirectivesForSSR, inject, isMemoSame, isProxy, isReactive, isReadonly, isRef, isRuntimeOnly, isShallow, isVNode, markRaw, mergeDefaults, mergeModels, mergeProps, nextTick, normalizeClass, normalizeProps, normalizeStyle, onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onDeactivated, onErrorCaptured, onMounted, onRenderTracked, onRenderTriggered, onScopeDispose, onServerPrefetch, onUnmounted, onUpdated, openBlock, popScopeId, provide, proxyRefs, pushScopeId, queuePostFlushCb, reactive, readonly, ref, registerRuntimeCompiler, render, renderList, renderSlot, resolveComponent, resolveDirective, resolveDynamicComponent, resolveFilter, resolveTransitionHooks, setBlockTracking, setDevtoolsHook, setTransitionHooks, shallowReactive, shallowReadonly, shallowRef, ssrContextKey, ssrUtils, stop, toDisplayString, toHandlerKey, toHandlers, toRaw, toRef, toRefs, toValue, transformVNodeArgs, triggerRef, unref, useAttrs, useCssModule, useCssVars, useModel, useSSRContext, useSlots, useTransitionState, vModelCheckbox, vModelDynamic, vModelRadio, vModelSelect, vModelText, vShow, version, warn, watch, watchEffect, watchPostEffect, watchSyncEffect, withAsyncContext, withCtx, withDefaults, withDirectives, withKeys, withMemo, withModifiers, withScopeId };\n"
  },
  {
    "path": "src/crusader-lib/assets/vue.prod.js",
    "content": "/**\n* vue v3.4.35\n* (c) 2018-present Yuxi (Evan) You and Vue contributors\n* @license MIT\n**//*! #__NO_SIDE_EFFECTS__ */let e,t,n,r,i,l,s,o,a;function c(e,t){let n=new Set(e.split(\",\"));return t?e=>n.has(e.toLowerCase()):e=>n.has(e)}let u={},d=[],p=()=>{},h=()=>!1,f=e=>111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&(e.charCodeAt(2)>122||97>e.charCodeAt(2)),m=e=>e.startsWith(\"onUpdate:\"),g=Object.assign,y=(e,t)=>{let n=e.indexOf(t);n>-1&&e.splice(n,1)},b=Object.prototype.hasOwnProperty,_=(e,t)=>b.call(e,t),S=Array.isArray,x=e=>\"[object Map]\"===O(e),C=e=>\"[object Set]\"===O(e),T=e=>\"[object Date]\"===O(e),k=e=>\"[object RegExp]\"===O(e),w=e=>\"function\"==typeof e,E=e=>\"string\"==typeof e,A=e=>\"symbol\"==typeof e,N=e=>null!==e&&\"object\"==typeof e,I=e=>(N(e)||w(e))&&w(e.then)&&w(e.catch),R=Object.prototype.toString,O=e=>R.call(e),M=e=>O(e).slice(8,-1),L=e=>\"[object Object]\"===O(e),P=e=>E(e)&&\"NaN\"!==e&&\"-\"!==e[0]&&\"\"+parseInt(e,10)===e,$=c(\",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted\"),F=c(\"bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo\"),V=e=>{let t=Object.create(null);return n=>t[n]||(t[n]=e(n))},D=/-(\\w)/g,B=V(e=>e.replace(D,(e,t)=>t?t.toUpperCase():\"\")),U=/\\B([A-Z])/g,j=V(e=>e.replace(U,\"-$1\").toLowerCase()),H=V(e=>e.charAt(0).toUpperCase()+e.slice(1)),q=V(e=>e?`on${H(e)}`:\"\"),W=(e,t)=>!Object.is(e,t),K=(e,...t)=>{for(let n=0;n<e.length;n++)e[n](...t)},z=(e,t,n,r=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},G=e=>{let t=parseFloat(e);return isNaN(t)?e:t},J=e=>{let t=E(e)?Number(e):NaN;return isNaN(t)?e:t},X=()=>e||(e=\"undefined\"!=typeof globalThis?globalThis:\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:{}),Q=c(\"Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error\");function Z(e){if(S(e)){let t={};for(let n=0;n<e.length;n++){let r=e[n],i=E(r)?en(r):Z(r);if(i)for(let e in i)t[e]=i[e]}return t}if(E(e)||N(e))return e}let Y=/;(?![^(]*\\))/g,ee=/:([^]+)/,et=/\\/\\*[^]*?\\*\\//g;function en(e){let t={};return e.replace(et,\"\").split(Y).forEach(e=>{if(e){let n=e.split(ee);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function er(e){let t=\"\";if(E(e))t=e;else if(S(e))for(let n=0;n<e.length;n++){let r=er(e[n]);r&&(t+=r+\" \")}else if(N(e))for(let n in e)e[n]&&(t+=n+\" \");return t.trim()}function ei(e){if(!e)return null;let{class:t,style:n}=e;return t&&!E(t)&&(e.class=er(t)),n&&(e.style=Z(n)),e}let el=c(\"html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot\"),es=c(\"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\"),eo=c(\"annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics\"),ea=c(\"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\"),ec=c(\"itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly\");function eu(e,t){if(e===t)return!0;let n=T(e),r=T(t);if(n||r)return!!n&&!!r&&e.getTime()===t.getTime();if(n=A(e),r=A(t),n||r)return e===t;if(n=S(e),r=S(t),n||r)return!!n&&!!r&&function(e,t){if(e.length!==t.length)return!1;let n=!0;for(let r=0;n&&r<e.length;r++)n=eu(e[r],t[r]);return n}(e,t);if(n=N(e),r=N(t),n||r){if(!n||!r||Object.keys(e).length!==Object.keys(t).length)return!1;for(let n in e){let r=e.hasOwnProperty(n),i=t.hasOwnProperty(n);if(r&&!i||!r&&i||!eu(e[n],t[n]))return!1}}return String(e)===String(t)}function ed(e,t){return e.findIndex(e=>eu(e,t))}let ep=e=>!!(e&&!0===e.__v_isRef),eh=e=>E(e)?e:null==e?\"\":S(e)||N(e)&&(e.toString===R||!w(e.toString))?ep(e)?eh(e.value):JSON.stringify(e,ef,2):String(e),ef=(e,t)=>ep(t)?ef(e,t.value):x(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((e,[t,n],r)=>(e[em(t,r)+\" =>\"]=n,e),{})}:C(t)?{[`Set(${t.size})`]:[...t.values()].map(e=>em(e))}:A(t)?em(t):!N(t)||S(t)||L(t)?t:String(t),em=(e,t=\"\")=>{var n;return A(e)?`Symbol(${null!=(n=e.description)?n:t})`:e};class eg{constructor(e=!1){this.detached=e,this._active=!0,this.effects=[],this.cleanups=[],this.parent=t,!e&&t&&(this.index=(t.scopes||(t.scopes=[])).push(this)-1)}get active(){return this._active}run(e){if(this._active){let n=t;try{return t=this,e()}finally{t=n}}}on(){t=this}off(){t=this.parent}stop(e){if(this._active){let t,n;for(t=0,n=this.effects.length;t<n;t++)this.effects[t].stop();for(t=0,n=this.cleanups.length;t<n;t++)this.cleanups[t]();if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].stop(!0);if(!this.detached&&this.parent&&!e){let e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.parent=void 0,this._active=!1}}}function ey(e){return new eg(e)}function ev(e,n=t){n&&n.active&&n.effects.push(e)}function eb(){return t}function e_(e){t&&t.cleanups.push(e)}class eS{constructor(e,t,n,r){this.fn=e,this.trigger=t,this.scheduler=n,this.active=!0,this.deps=[],this._dirtyLevel=4,this._trackId=0,this._runnings=0,this._shouldSchedule=!1,this._depsLength=0,ev(this,r)}get dirty(){if(2===this._dirtyLevel||3===this._dirtyLevel){this._dirtyLevel=1,eI();for(let e=0;e<this._depsLength;e++){let t=this.deps[e];if(t.computed&&(t.computed.value,this._dirtyLevel>=4))break}1===this._dirtyLevel&&(this._dirtyLevel=0),eR()}return this._dirtyLevel>=4}set dirty(e){this._dirtyLevel=e?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let e=eE,t=n;try{return eE=!0,n=this,this._runnings++,ex(this),this.fn()}finally{eC(this),this._runnings--,n=t,eE=e}}stop(){this.active&&(ex(this),eC(this),this.onStop&&this.onStop(),this.active=!1)}}function ex(e){e._trackId++,e._depsLength=0}function eC(e){if(e.deps.length>e._depsLength){for(let t=e._depsLength;t<e.deps.length;t++)eT(e.deps[t],e);e.deps.length=e._depsLength}}function eT(e,t){let n=e.get(t);void 0!==n&&t._trackId!==n&&(e.delete(t),0===e.size&&e.cleanup())}function ek(e,t){e.effect instanceof eS&&(e=e.effect.fn);let n=new eS(e,p,()=>{n.dirty&&n.run()});t&&(g(n,t),t.scope&&ev(n,t.scope)),t&&t.lazy||n.run();let r=n.run.bind(n);return r.effect=n,r}function ew(e){e.effect.stop()}let eE=!0,eA=0,eN=[];function eI(){eN.push(eE),eE=!1}function eR(){let e=eN.pop();eE=void 0===e||e}function eO(){for(eA--;!eA&&eL.length;)eL.shift()()}function eM(e,t,n){if(t.get(e)!==e._trackId){t.set(e,e._trackId);let n=e.deps[e._depsLength];n!==t?(n&&eT(n,e),e.deps[e._depsLength++]=t):e._depsLength++}}let eL=[];function eP(e,t,n){for(let n of(eA++,e.keys())){let r;n._dirtyLevel<t&&(null!=r?r:r=e.get(n)===n._trackId)&&(n._shouldSchedule||(n._shouldSchedule=0===n._dirtyLevel),n._dirtyLevel=t),n._shouldSchedule&&(null!=r?r:r=e.get(n)===n._trackId)&&(n.trigger(),(!n._runnings||n.allowRecurse)&&2!==n._dirtyLevel&&(n._shouldSchedule=!1,n.scheduler&&eL.push(n.scheduler)))}eO()}let e$=(e,t)=>{let n=new Map;return n.cleanup=e,n.computed=t,n},eF=new WeakMap,eV=Symbol(\"\"),eD=Symbol(\"\");function eB(e,t,r){if(eE&&n){let t=eF.get(e);t||eF.set(e,t=new Map);let i=t.get(r);i||t.set(r,i=e$(()=>t.delete(r))),eM(n,i)}}function eU(e,t,n,r,i,l){let s=eF.get(e);if(!s)return;let o=[];if(\"clear\"===t)o=[...s.values()];else if(\"length\"===n&&S(e)){let e=Number(r);s.forEach((t,n)=>{(\"length\"===n||!A(n)&&n>=e)&&o.push(t)})}else switch(void 0!==n&&o.push(s.get(n)),t){case\"add\":S(e)?P(n)&&o.push(s.get(\"length\")):(o.push(s.get(eV)),x(e)&&o.push(s.get(eD)));break;case\"delete\":!S(e)&&(o.push(s.get(eV)),x(e)&&o.push(s.get(eD)));break;case\"set\":x(e)&&o.push(s.get(eV))}for(let e of(eA++,o))e&&eP(e,4);eO()}let ej=c(\"__proto__,__v_isRef,__isVue\"),eH=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>\"arguments\"!==e&&\"caller\"!==e).map(e=>Symbol[e]).filter(A)),eq=function(){let e={};return[\"includes\",\"indexOf\",\"lastIndexOf\"].forEach(t=>{e[t]=function(...e){let n=tC(this);for(let e=0,t=this.length;e<t;e++)eB(n,\"get\",e+\"\");let r=n[t](...e);return -1===r||!1===r?n[t](...e.map(tC)):r}}),[\"push\",\"pop\",\"shift\",\"unshift\",\"splice\"].forEach(t=>{e[t]=function(...e){eI(),eA++;let n=tC(this)[t].apply(this,e);return eO(),eR(),n}}),e}();function eW(e){A(e)||(e=String(e));let t=tC(this);return eB(t,\"has\",e),t.hasOwnProperty(e)}class eK{constructor(e=!1,t=!1){this._isReadonly=e,this._isShallow=t}get(e,t,n){let r=this._isReadonly,i=this._isShallow;if(\"__v_isReactive\"===t)return!r;if(\"__v_isReadonly\"===t)return r;if(\"__v_isShallow\"===t)return i;if(\"__v_raw\"===t)return n===(r?i?th:tp:i?td:tu).get(e)||Object.getPrototypeOf(e)===Object.getPrototypeOf(n)?e:void 0;let l=S(e);if(!r){if(l&&_(eq,t))return Reflect.get(eq,t,n);if(\"hasOwnProperty\"===t)return eW}let s=Reflect.get(e,t,n);return(A(t)?eH.has(t):ej(t))?s:(r||eB(e,\"get\",t),i)?s:tI(s)?l&&P(t)?s:s.value:N(s)?r?tg(s):tf(s):s}}class ez extends eK{constructor(e=!1){super(!1,e)}set(e,t,n,r){let i=e[t];if(!this._isShallow){let t=t_(i);if(tS(n)||t_(n)||(i=tC(i),n=tC(n)),!S(e)&&tI(i)&&!tI(n))return!t&&(i.value=n,!0)}let l=S(e)&&P(t)?Number(t)<e.length:_(e,t),s=Reflect.set(e,t,n,r);return e===tC(r)&&(l?W(n,i)&&eU(e,\"set\",t,n):eU(e,\"add\",t,n)),s}deleteProperty(e,t){let n=_(e,t);e[t];let r=Reflect.deleteProperty(e,t);return r&&n&&eU(e,\"delete\",t,void 0),r}has(e,t){let n=Reflect.has(e,t);return A(t)&&eH.has(t)||eB(e,\"has\",t),n}ownKeys(e){return eB(e,\"iterate\",S(e)?\"length\":eV),Reflect.ownKeys(e)}}class eG extends eK{constructor(e=!1){super(!0,e)}set(e,t){return!0}deleteProperty(e,t){return!0}}let eJ=new ez,eX=new eG,eQ=new ez(!0),eZ=new eG(!0),eY=e=>e,e0=e=>Reflect.getPrototypeOf(e);function e1(e,t,n=!1,r=!1){let i=tC(e=e.__v_raw),l=tC(t);n||(W(t,l)&&eB(i,\"get\",t),eB(i,\"get\",l));let{has:s}=e0(i),o=r?eY:n?tw:tk;return s.call(i,t)?o(e.get(t)):s.call(i,l)?o(e.get(l)):void(e!==i&&e.get(t))}function e2(e,t=!1){let n=this.__v_raw,r=tC(n),i=tC(e);return t||(W(e,i)&&eB(r,\"has\",e),eB(r,\"has\",i)),e===i?n.has(e):n.has(e)||n.has(i)}function e3(e,t=!1){return e=e.__v_raw,t||eB(tC(e),\"iterate\",eV),Reflect.get(e,\"size\",e)}function e6(e,t=!1){t||tS(e)||t_(e)||(e=tC(e));let n=tC(this);return e0(n).has.call(n,e)||(n.add(e),eU(n,\"add\",e,e)),this}function e4(e,t,n=!1){n||tS(t)||t_(t)||(t=tC(t));let r=tC(this),{has:i,get:l}=e0(r),s=i.call(r,e);s||(e=tC(e),s=i.call(r,e));let o=l.call(r,e);return r.set(e,t),s?W(t,o)&&eU(r,\"set\",e,t):eU(r,\"add\",e,t),this}function e8(e){let t=tC(this),{has:n,get:r}=e0(t),i=n.call(t,e);i||(e=tC(e),i=n.call(t,e)),r&&r.call(t,e);let l=t.delete(e);return i&&eU(t,\"delete\",e,void 0),l}function e5(){let e=tC(this),t=0!==e.size,n=e.clear();return t&&eU(e,\"clear\",void 0,void 0),n}function e9(e,t){return function(n,r){let i=this,l=i.__v_raw,s=tC(l),o=t?eY:e?tw:tk;return e||eB(s,\"iterate\",eV),l.forEach((e,t)=>n.call(r,o(e),o(t),i))}}function e7(e,t,n){return function(...r){let i=this.__v_raw,l=tC(i),s=x(l),o=\"entries\"===e||e===Symbol.iterator&&s,a=i[e](...r),c=n?eY:t?tw:tk;return t||eB(l,\"iterate\",\"keys\"===e&&s?eD:eV),{next(){let{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:o?[c(e[0]),c(e[1])]:c(e),done:t}},[Symbol.iterator](){return this}}}}function te(e){return function(...t){return\"delete\"!==e&&(\"clear\"===e?void 0:this)}}let[tt,tn,tr,ti]=function(){let e={get(e){return e1(this,e)},get size(){return e3(this)},has:e2,add:e6,set:e4,delete:e8,clear:e5,forEach:e9(!1,!1)},t={get(e){return e1(this,e,!1,!0)},get size(){return e3(this)},has:e2,add(e){return e6.call(this,e,!0)},set(e,t){return e4.call(this,e,t,!0)},delete:e8,clear:e5,forEach:e9(!1,!0)},n={get(e){return e1(this,e,!0)},get size(){return e3(this,!0)},has(e){return e2.call(this,e,!0)},add:te(\"add\"),set:te(\"set\"),delete:te(\"delete\"),clear:te(\"clear\"),forEach:e9(!0,!1)},r={get(e){return e1(this,e,!0,!0)},get size(){return e3(this,!0)},has(e){return e2.call(this,e,!0)},add:te(\"add\"),set:te(\"set\"),delete:te(\"delete\"),clear:te(\"clear\"),forEach:e9(!0,!0)};return[\"keys\",\"values\",\"entries\",Symbol.iterator].forEach(i=>{e[i]=e7(i,!1,!1),n[i]=e7(i,!0,!1),t[i]=e7(i,!1,!0),r[i]=e7(i,!0,!0)}),[e,n,t,r]}();function tl(e,t){let n=t?e?ti:tr:e?tn:tt;return(t,r,i)=>\"__v_isReactive\"===r?!e:\"__v_isReadonly\"===r?e:\"__v_raw\"===r?t:Reflect.get(_(n,r)&&r in t?n:t,r,i)}let ts={get:tl(!1,!1)},to={get:tl(!1,!0)},ta={get:tl(!0,!1)},tc={get:tl(!0,!0)},tu=new WeakMap,td=new WeakMap,tp=new WeakMap,th=new WeakMap;function tf(e){return t_(e)?e:tv(e,!1,eJ,ts,tu)}function tm(e){return tv(e,!1,eQ,to,td)}function tg(e){return tv(e,!0,eX,ta,tp)}function ty(e){return tv(e,!0,eZ,tc,th)}function tv(e,t,n,r,i){if(!N(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;let l=i.get(e);if(l)return l;let s=e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case\"Object\":case\"Array\":return 1;case\"Map\":case\"Set\":case\"WeakMap\":case\"WeakSet\":return 2;default:return 0}}(M(e));if(0===s)return e;let o=new Proxy(e,2===s?r:n);return i.set(e,o),o}function tb(e){return t_(e)?tb(e.__v_raw):!!(e&&e.__v_isReactive)}function t_(e){return!!(e&&e.__v_isReadonly)}function tS(e){return!!(e&&e.__v_isShallow)}function tx(e){return!!e&&!!e.__v_raw}function tC(e){let t=e&&e.__v_raw;return t?tC(t):e}function tT(e){return Object.isExtensible(e)&&z(e,\"__v_skip\",!0),e}let tk=e=>N(e)?tf(e):e,tw=e=>N(e)?tg(e):e;class tE{constructor(e,t,n,r){this.getter=e,this._setter=t,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new eS(()=>e(this._value),()=>tN(this,2===this.effect._dirtyLevel?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!r,this.__v_isReadonly=n}get value(){let e=tC(this);return(!e._cacheable||e.effect.dirty)&&W(e._value,e._value=e.effect.run())&&tN(e,4),tA(e),e.effect._dirtyLevel>=2&&tN(e,2),e._value}set value(e){this._setter(e)}get _dirty(){return this.effect.dirty}set _dirty(e){this.effect.dirty=e}}function tA(e){var t;eE&&n&&(e=tC(e),eM(n,null!=(t=e.dep)?t:e.dep=e$(()=>e.dep=void 0,e instanceof tE?e:void 0)))}function tN(e,t=4,n,r){let i=(e=tC(e)).dep;i&&eP(i,t)}function tI(e){return!!(e&&!0===e.__v_isRef)}function tR(e){return tM(e,!1)}function tO(e){return tM(e,!0)}function tM(e,t){return tI(e)?e:new tL(e,t)}class tL{constructor(e,t){this.__v_isShallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?e:tC(e),this._value=t?e:tk(e)}get value(){return tA(this),this._value}set value(e){let t=this.__v_isShallow||tS(e)||t_(e);W(e=t?e:tC(e),this._rawValue)&&(this._rawValue,this._rawValue=e,this._value=t?e:tk(e),tN(this,4))}}function tP(e){tN(e,4)}function t$(e){return tI(e)?e.value:e}function tF(e){return w(e)?e():t$(e)}let tV={get:(e,t,n)=>t$(Reflect.get(e,t,n)),set:(e,t,n,r)=>{let i=e[t];return tI(i)&&!tI(n)?(i.value=n,!0):Reflect.set(e,t,n,r)}};function tD(e){return tb(e)?e:new Proxy(e,tV)}class tB{constructor(e){this.dep=void 0,this.__v_isRef=!0;let{get:t,set:n}=e(()=>tA(this),()=>tN(this));this._get=t,this._set=n}get value(){return this._get()}set value(e){this._set(e)}}function tU(e){return new tB(e)}function tj(e){let t=S(e)?Array(e.length):{};for(let n in e)t[n]=tK(e,n);return t}class tH{constructor(e,t,n){this._object=e,this._key=t,this._defaultValue=n,this.__v_isRef=!0}get value(){let e=this._object[this._key];return void 0===e?this._defaultValue:e}set value(e){this._object[this._key]=e}get dep(){return function(e,t){let n=eF.get(e);return n&&n.get(t)}(tC(this._object),this._key)}}class tq{constructor(e){this._getter=e,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function tW(e,t,n){return tI(e)?e:w(e)?new tq(e):N(e)&&arguments.length>1?tK(e,t,n):tR(e)}function tK(e,t,n){let r=e[t];return tI(r)?r:new tH(e,t,n)}let tz={GET:\"get\",HAS:\"has\",ITERATE:\"iterate\"},tG={SET:\"set\",ADD:\"add\",DELETE:\"delete\",CLEAR:\"clear\"};function tJ(e,t){}let tX={SETUP_FUNCTION:0,0:\"SETUP_FUNCTION\",RENDER_FUNCTION:1,1:\"RENDER_FUNCTION\",WATCH_GETTER:2,2:\"WATCH_GETTER\",WATCH_CALLBACK:3,3:\"WATCH_CALLBACK\",WATCH_CLEANUP:4,4:\"WATCH_CLEANUP\",NATIVE_EVENT_HANDLER:5,5:\"NATIVE_EVENT_HANDLER\",COMPONENT_EVENT_HANDLER:6,6:\"COMPONENT_EVENT_HANDLER\",VNODE_HOOK:7,7:\"VNODE_HOOK\",DIRECTIVE_HOOK:8,8:\"DIRECTIVE_HOOK\",TRANSITION_HOOK:9,9:\"TRANSITION_HOOK\",APP_ERROR_HANDLER:10,10:\"APP_ERROR_HANDLER\",APP_WARN_HANDLER:11,11:\"APP_WARN_HANDLER\",FUNCTION_REF:12,12:\"FUNCTION_REF\",ASYNC_COMPONENT_LOADER:13,13:\"ASYNC_COMPONENT_LOADER\",SCHEDULER:14,14:\"SCHEDULER\",COMPONENT_UPDATE:15,15:\"COMPONENT_UPDATE\"};function tQ(e,t,n,r){try{return r?e(...r):e()}catch(e){tY(e,t,n)}}function tZ(e,t,n,r){if(w(e)){let i=tQ(e,t,n,r);return i&&I(i)&&i.catch(e=>{tY(e,t,n)}),i}if(S(e)){let i=[];for(let l=0;l<e.length;l++)i.push(tZ(e[l],t,n,r));return i}}function tY(e,t,n,r=!0){if(t&&t.vnode,t){let r=t.parent,i=t.proxy,l=`https://vuejs.org/error-reference/#runtime-${n}`;for(;r;){let t=r.ec;if(t){for(let n=0;n<t.length;n++)if(!1===t[n](e,i,l))return}r=r.parent}let s=t.appContext.config.errorHandler;if(s){eI(),tQ(s,null,10,[e,i,l]),eR();return}}!function(e,t,n,r=!0){console.error(e)}(e,0,0,r)}let t0=!1,t1=!1,t2=[],t3=0,t6=[],t4=null,t8=0,t5=Promise.resolve(),t9=null;function t7(e){let t=t9||t5;return e?t.then(this?e.bind(this):e):t}function ne(e){t2.length&&t2.includes(e,t0&&e.allowRecurse?t3+1:t3)||(null==e.id?t2.push(e):t2.splice(function(e){let t=t3+1,n=t2.length;for(;t<n;){let r=t+n>>>1,i=t2[r],l=nl(i);l<e||l===e&&i.pre?t=r+1:n=r}return t}(e.id),0,e),nt())}function nt(){t0||t1||(t1=!0,t9=t5.then(function e(t){t1=!1,t0=!0,t2.sort(ns);try{for(t3=0;t3<t2.length;t3++){let e=t2[t3];e&&!1!==e.active&&tQ(e,e.i,e.i?15:14)}}finally{t3=0,t2.length=0,ni(),t0=!1,t9=null,(t2.length||t6.length)&&e()}}))}function nn(e){S(e)?t6.push(...e):t4&&t4.includes(e,e.allowRecurse?t8+1:t8)||t6.push(e),nt()}function nr(e,t,n=t0?t3+1:0){for(;n<t2.length;n++){let t=t2[n];if(t&&t.pre){if(e&&t.id!==e.uid)continue;t2.splice(n,1),n--,t()}}}function ni(e){if(t6.length){let e=[...new Set(t6)].sort((e,t)=>nl(e)-nl(t));if(t6.length=0,t4){t4.push(...e);return}for(t8=0,t4=e;t8<t4.length;t8++){let e=t4[t8];!1!==e.active&&e()}t4=null,t8=0}}let nl=e=>null==e.id?1/0:e.id,ns=(e,t)=>{let n=nl(e)-nl(t);if(0===n){if(e.pre&&!t.pre)return -1;if(t.pre&&!e.pre)return 1}return n},no=null,na=null;function nc(e){let t=no;return no=e,na=e&&e.type.__scopeId||null,t}function nu(e){na=e}function nd(){na=null}let np=e=>nh;function nh(e,t=no,n){if(!t||e._n)return e;let r=(...n)=>{let i;r._d&&iY(-1);let l=nc(t);try{i=e(...n)}finally{nc(l),r._d&&iY(1)}return i};return r._n=!0,r._c=!0,r._d=!0,r}function nf(e,t){if(null===no)return e;let n=lT(no),r=e.dirs||(e.dirs=[]);for(let e=0;e<t.length;e++){let[i,l,s,o=u]=t[e];i&&(w(i)&&(i={mounted:i,updated:i}),i.deep&&iw(l),r.push({dir:i,instance:n,value:l,oldValue:void 0,arg:s,modifiers:o}))}return e}function nm(e,t,n,r){let i=e.dirs,l=t&&t.dirs;for(let s=0;s<i.length;s++){let o=i[s];l&&(o.oldValue=l[s].value);let a=o.dir[r];a&&(eI(),tZ(a,n,8,[e.el,o,e,t]),eR())}}let ng=Symbol(\"_leaveCb\"),ny=Symbol(\"_enterCb\");function nv(){let e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return nq(()=>{e.isMounted=!0}),nz(()=>{e.isUnmounting=!0}),e}let nb=[Function,Array],n_={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:nb,onEnter:nb,onAfterEnter:nb,onEnterCancelled:nb,onBeforeLeave:nb,onLeave:nb,onAfterLeave:nb,onLeaveCancelled:nb,onBeforeAppear:nb,onAppear:nb,onAfterAppear:nb,onAppearCancelled:nb},nS=e=>{let t=e.subTree;return t.component?nS(t.component):t},nx={name:\"BaseTransition\",props:n_,setup(e,{slots:t}){let n=lh(),r=nv();return()=>{let i=t.default&&nA(t.default(),!0);if(!i||!i.length)return;let l=i[0];if(i.length>1){for(let e of i)if(e.type!==iK){l=e;break}}let s=tC(e),{mode:o}=s;if(r.isLeaving)return nk(l);let a=nw(l);if(!a)return nk(l);let c=nT(a,s,r,n,e=>c=e);nE(a,c);let u=n.subTree,d=u&&nw(u);if(d&&d.type!==iK&&!i6(a,d)&&nS(n).type!==iK){let e=nT(d,s,r,n);if(nE(d,e),\"out-in\"===o&&a.type!==iK)return r.isLeaving=!0,e.afterLeave=()=>{r.isLeaving=!1,!1!==n.update.active&&(n.effect.dirty=!0,n.update())},nk(l);\"in-out\"===o&&a.type!==iK&&(e.delayLeave=(e,t,n)=>{nC(r,d)[String(d.key)]=d,e[ng]=()=>{t(),e[ng]=void 0,delete c.delayedLeave},c.delayedLeave=n})}return l}}};function nC(e,t){let{leavingVNodes:n}=e,r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function nT(e,t,n,r,i){let{appear:l,mode:s,persisted:o=!1,onBeforeEnter:a,onEnter:c,onAfterEnter:u,onEnterCancelled:d,onBeforeLeave:p,onLeave:h,onAfterLeave:f,onLeaveCancelled:m,onBeforeAppear:g,onAppear:y,onAfterAppear:b,onAppearCancelled:_}=t,x=String(e.key),C=nC(n,e),T=(e,t)=>{e&&tZ(e,r,9,t)},k=(e,t)=>{let n=t[1];T(e,t),S(e)?e.every(e=>e.length<=1)&&n():e.length<=1&&n()},w={mode:s,persisted:o,beforeEnter(t){let r=a;if(!n.isMounted){if(!l)return;r=g||a}t[ng]&&t[ng](!0);let i=C[x];i&&i6(e,i)&&i.el[ng]&&i.el[ng](),T(r,[t])},enter(e){let t=c,r=u,i=d;if(!n.isMounted){if(!l)return;t=y||c,r=b||u,i=_||d}let s=!1,o=e[ny]=t=>{s||(s=!0,t?T(i,[e]):T(r,[e]),w.delayedLeave&&w.delayedLeave(),e[ny]=void 0)};t?k(t,[e,o]):o()},leave(t,r){let i=String(e.key);if(t[ny]&&t[ny](!0),n.isUnmounting)return r();T(p,[t]);let l=!1,s=t[ng]=n=>{l||(l=!0,r(),n?T(m,[t]):T(f,[t]),t[ng]=void 0,C[i]!==e||delete C[i])};C[i]=e,h?k(h,[t,s]):s()},clone(e){let l=nT(e,t,n,r,i);return i&&i(l),l}};return w}function nk(e){if(nM(e))return(e=lt(e)).children=null,e}function nw(e){if(!nM(e))return e;let{shapeFlag:t,children:n}=e;if(n){if(16&t)return n[0];if(32&t&&w(n.default))return n.default()}}function nE(e,t){6&e.shapeFlag&&e.component?nE(e.component.subTree,t):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function nA(e,t=!1,n){let r=[],i=0;for(let l=0;l<e.length;l++){let s=e[l],o=null==n?s.key:String(n)+String(null!=s.key?s.key:l);s.type===iq?(128&s.patchFlag&&i++,r=r.concat(nA(s.children,t,o))):(t||s.type!==iK)&&r.push(null!=o?lt(s,{key:o}):s)}if(i>1)for(let e=0;e<r.length;e++)r[e].patchFlag=-2;return r}/*! #__NO_SIDE_EFFECTS__ */function nN(e,t){return w(e)?g({name:e.name},t,{setup:e}):e}let nI=e=>!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function nR(e){let t;w(e)&&(e={loader:e});let{loader:n,loadingComponent:r,errorComponent:i,delay:l=200,timeout:s,suspensible:o=!0,onError:a}=e,c=null,u=0,d=()=>(u++,c=null,p()),p=()=>{let e;return c||(e=c=n().catch(e=>{if(e=e instanceof Error?e:Error(String(e)),a)return new Promise((t,n)=>{a(e,()=>t(d()),()=>n(e),u+1)});throw e}).then(n=>e!==c&&c?c:(n&&(n.__esModule||\"Module\"===n[Symbol.toStringTag])&&(n=n.default),t=n,n)))};return nN({name:\"AsyncComponentWrapper\",__asyncLoader:p,get __asyncResolved(){return t},setup(){let e=lp;if(t)return()=>nO(t,e);let n=t=>{c=null,tY(t,e,13,!i)};if(o&&e.suspense)return p().then(t=>()=>nO(t,e)).catch(e=>(n(e),()=>i?i7(i,{error:e}):null));let a=tR(!1),u=tR(),d=tR(!!l);return l&&setTimeout(()=>{d.value=!1},l),null!=s&&setTimeout(()=>{if(!a.value&&!u.value){let e=Error(`Async component timed out after ${s}ms.`);n(e),u.value=e}},s),p().then(()=>{a.value=!0,e.parent&&nM(e.parent.vnode)&&(e.parent.effect.dirty=!0,ne(e.parent.update))}).catch(e=>{n(e),u.value=e}),()=>a.value&&t?nO(t,e):u.value&&i?i7(i,{error:u.value}):r&&!d.value?i7(r):void 0}})}function nO(e,t){let{ref:n,props:r,children:i,ce:l}=t.vnode,s=i7(e,r,i);return s.ref=n,s.ce=l,delete t.vnode.ce,s}let nM=e=>e.type.__isKeepAlive,nL={name:\"KeepAlive\",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){let n=lh(),r=n.ctx,i=new Map,l=new Set,s=null,o=n.suspense,{renderer:{p:a,m:c,um:u,o:{createElement:d}}}=r,p=d(\"div\");function h(e){nD(e),u(e,n,o,!0)}function f(e){i.forEach((t,n)=>{let r=lk(t.type);!r||e&&e(r)||m(n)})}function m(e){let t=i.get(e);s&&i6(t,s)?s&&nD(s):h(t),i.delete(e),l.delete(e)}r.activate=(e,t,n,r,i)=>{let l=e.component;c(e,t,n,0,o),a(l.vnode,e,t,n,l,o,r,e.slotScopeIds,i),is(()=>{l.isDeactivated=!1,l.a&&K(l.a);let t=e.props&&e.props.onVnodeMounted;t&&lc(t,l.parent,e)},o)},r.deactivate=e=>{let t=e.component;im(t.m),im(t.a),c(e,p,null,1,o),is(()=>{t.da&&K(t.da);let n=e.props&&e.props.onVnodeUnmounted;n&&lc(n,t.parent,e),t.isDeactivated=!0},o)},ix(()=>[e.include,e.exclude],([e,t])=>{e&&f(t=>nP(e,t)),t&&f(e=>!nP(t,e))},{flush:\"post\",deep:!0});let g=null,y=()=>{null!=g&&(i$(n.subTree.type)?is(()=>{i.set(g,nB(n.subTree))},n.subTree.suspense):i.set(g,nB(n.subTree)))};return nq(y),nK(y),nz(()=>{i.forEach(e=>{let{subTree:t,suspense:r}=n,i=nB(t);if(e.type===i.type&&e.key===i.key){nD(i);let e=i.component.da;e&&is(e,r);return}h(e)})}),()=>{if(g=null,!t.default)return null;let n=t.default(),r=n[0];if(n.length>1)return s=null,n;if(!i3(r)||!(4&r.shapeFlag)&&!(128&r.shapeFlag))return s=null,r;let o=nB(r),a=o.type,c=lk(nI(o)?o.type.__asyncResolved||{}:a),{include:u,exclude:d,max:p}=e;if(u&&(!c||!nP(u,c))||d&&c&&nP(d,c))return s=o,r;let h=null==o.key?a:o.key,f=i.get(h);return o.el&&(o=lt(o),128&r.shapeFlag&&(r.ssContent=o)),g=h,f?(o.el=f.el,o.component=f.component,o.transition&&nE(o,o.transition),o.shapeFlag|=512,l.delete(h),l.add(h)):(l.add(h),p&&l.size>parseInt(p,10)&&m(l.values().next().value)),o.shapeFlag|=256,s=o,i$(r.type)?r:o}}};function nP(e,t){return S(e)?e.some(e=>nP(e,t)):E(e)?e.split(\",\").includes(t):!!k(e)&&e.test(t)}function n$(e,t){nV(e,\"a\",t)}function nF(e,t){nV(e,\"da\",t)}function nV(e,t,n=lp){let r=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}return e()});if(nU(t,r,n),n){let e=n.parent;for(;e&&e.parent;)nM(e.parent.vnode)&&function(e,t,n,r){let i=nU(t,e,r,!0);nG(()=>{y(r[t],i)},n)}(r,t,n,e),e=e.parent}}function nD(e){e.shapeFlag&=-257,e.shapeFlag&=-513}function nB(e){return 128&e.shapeFlag?e.ssContent:e}function nU(e,t,n=lp,r=!1){if(n){let i=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...r)=>{eI();let i=lf(n),l=tZ(t,n,e,r);return i(),eR(),l});return r?i.unshift(l):i.push(l),l}}let nj=e=>(t,n=lp)=>{ly&&\"sp\"!==e||nU(e,(...e)=>t(...e),n)},nH=nj(\"bm\"),nq=nj(\"m\"),nW=nj(\"bu\"),nK=nj(\"u\"),nz=nj(\"bum\"),nG=nj(\"um\"),nJ=nj(\"sp\"),nX=nj(\"rtg\"),nQ=nj(\"rtc\");function nZ(e,t=lp){nU(\"ec\",e,t)}let nY=\"components\";function n0(e,t){return n6(nY,e,!0,t)||e}let n1=Symbol.for(\"v-ndc\");function n2(e){return E(e)?n6(nY,e,!1)||e:e||n1}function n3(e){return n6(\"directives\",e)}function n6(e,t,n=!0,r=!1){let i=no||lp;if(i){let n=i.type;if(e===nY){let e=lk(n,!1);if(e&&(e===t||e===B(t)||e===H(B(t))))return n}let l=n4(i[e]||n[e],t)||n4(i.appContext[e],t);return!l&&r?n:l}}function n4(e,t){return e&&(e[t]||e[B(t)]||e[H(B(t))])}function n8(e,t,n,r){let i;let l=n&&n[r];if(S(e)||E(e)){i=Array(e.length);for(let n=0,r=e.length;n<r;n++)i[n]=t(e[n],n,void 0,l&&l[n])}else if(\"number\"==typeof e){i=Array(e);for(let n=0;n<e;n++)i[n]=t(n+1,n,void 0,l&&l[n])}else if(N(e)){if(e[Symbol.iterator])i=Array.from(e,(e,n)=>t(e,n,void 0,l&&l[n]));else{let n=Object.keys(e);i=Array(n.length);for(let r=0,s=n.length;r<s;r++){let s=n[r];i[r]=t(e[s],s,r,l&&l[r])}}}else i=[];return n&&(n[r]=i),i}function n5(e,t){for(let n=0;n<t.length;n++){let r=t[n];if(S(r))for(let t=0;t<r.length;t++)e[r[t].name]=r[t].fn;else r&&(e[r.name]=r.key?(...e)=>{let t=r.fn(...e);return t&&(t.key=r.key),t}:r.fn)}return e}function n9(e,t,n={},r,i){if(no.isCE||no.parent&&nI(no.parent)&&no.parent.isCE)return\"default\"!==t&&(n.name=t),i7(\"slot\",n,r&&r());let l=e[t];l&&l._c&&(l._d=!1),iX();let s=l&&function e(t){return t.some(t=>!i3(t)||!!(t.type!==iK&&(t.type!==iq||e(t.children))))?t:null}(l(n)),o=i2(iq,{key:(n.key||s&&s.key||`_${t}`)+(!s&&r?\"_fb\":\"\")},s||(r?r():[]),s&&1===e._?64:-2);return!i&&o.scopeId&&(o.slotScopeIds=[o.scopeId+\"-s\"]),l&&l._c&&(l._d=!0),o}function n7(e,t){let n={};for(let r in e)n[t&&/[A-Z]/.test(r)?`on:${r}`:q(r)]=e[r];return n}let re=e=>e?lg(e)?lT(e):re(e.parent):null,rt=g(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>re(e.parent),$root:e=>re(e.root),$emit:e=>e.emit,$options:e=>rx(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,ne(e.update)}),$nextTick:e=>e.n||(e.n=t7.bind(e.proxy)),$watch:e=>iT.bind(e)}),rn=(e,t)=>e!==u&&!e.__isScriptSetup&&_(e,t),rr={get({_:e},t){let n,r,i;if(\"__v_skip\"===t)return!0;let{ctx:l,setupState:s,data:o,props:a,accessCache:c,type:d,appContext:p}=e;if(\"$\"!==t[0]){let r=c[t];if(void 0!==r)switch(r){case 1:return s[t];case 2:return o[t];case 4:return l[t];case 3:return a[t]}else{if(rn(s,t))return c[t]=1,s[t];if(o!==u&&_(o,t))return c[t]=2,o[t];if((n=e.propsOptions[0])&&_(n,t))return c[t]=3,a[t];if(l!==u&&_(l,t))return c[t]=4,l[t];r_&&(c[t]=0)}}let h=rt[t];return h?(\"$attrs\"===t&&eB(e.attrs,\"get\",\"\"),h(e)):(r=d.__cssModules)&&(r=r[t])?r:l!==u&&_(l,t)?(c[t]=4,l[t]):_(i=p.config.globalProperties,t)?i[t]:void 0},set({_:e},t,n){let{data:r,setupState:i,ctx:l}=e;return rn(i,t)?(i[t]=n,!0):r!==u&&_(r,t)?(r[t]=n,!0):!_(e.props,t)&&!(\"$\"===t[0]&&t.slice(1) in e)&&(l[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:i,propsOptions:l}},s){let o;return!!n[s]||e!==u&&_(e,s)||rn(t,s)||(o=l[0])&&_(o,s)||_(r,s)||_(rt,s)||_(i.config.globalProperties,s)},defineProperty(e,t,n){return null!=n.get?e._.accessCache[t]=0:_(n,\"value\")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},ri=g({},rr,{get(e,t){if(t!==Symbol.unscopables)return rr.get(e,t,e)},has:(e,t)=>\"_\"!==t[0]&&!Q(t)});function rl(){return null}function rs(){return null}function ro(e){}function ra(e){}function rc(){return null}function ru(){}function rd(e,t){return null}function rp(){return rf().slots}function rh(){return rf().attrs}function rf(){let e=lh();return e.setupContext||(e.setupContext=lC(e))}function rm(e){return S(e)?e.reduce((e,t)=>(e[t]=null,e),{}):e}function rg(e,t){let n=rm(e);for(let e in t){if(e.startsWith(\"__skip\"))continue;let r=n[e];r?S(r)||w(r)?r=n[e]={type:r,default:t[e]}:r.default=t[e]:null===r&&(r=n[e]={default:t[e]}),r&&t[`__skip_${e}`]&&(r.skipFactory=!0)}return n}function ry(e,t){return e&&t?S(e)&&S(t)?e.concat(t):g({},rm(e),rm(t)):e||t}function rv(e,t){let n={};for(let r in e)t.includes(r)||Object.defineProperty(n,r,{enumerable:!0,get:()=>e[r]});return n}function rb(e){let t=lh(),n=e();return lm(),I(n)&&(n=n.catch(e=>{throw lf(t),e})),[n,()=>lf(t)]}let r_=!0;function rS(e,t,n){tZ(S(e)?e.map(e=>e.bind(t.proxy)):e.bind(t.proxy),t,n)}function rx(e){let t;let n=e.type,{mixins:r,extends:i}=n,{mixins:l,optionsCache:s,config:{optionMergeStrategies:o}}=e.appContext,a=s.get(n);return a?t=a:l.length||r||i?(t={},l.length&&l.forEach(e=>rC(t,e,o,!0)),rC(t,n,o)):t=n,N(n)&&s.set(n,t),t}function rC(e,t,n,r=!1){let{mixins:i,extends:l}=t;for(let s in l&&rC(e,l,n,!0),i&&i.forEach(t=>rC(e,t,n,!0)),t)if(r&&\"expose\"===s);else{let r=rT[s]||n&&n[s];e[s]=r?r(e[s],t[s]):t[s]}return e}let rT={data:rk,props:rN,emits:rN,methods:rA,computed:rA,beforeCreate:rE,created:rE,beforeMount:rE,mounted:rE,beforeUpdate:rE,updated:rE,beforeDestroy:rE,beforeUnmount:rE,destroyed:rE,unmounted:rE,activated:rE,deactivated:rE,errorCaptured:rE,serverPrefetch:rE,components:rA,directives:rA,watch:function(e,t){if(!e)return t;if(!t)return e;let n=g(Object.create(null),e);for(let r in t)n[r]=rE(e[r],t[r]);return n},provide:rk,inject:function(e,t){return rA(rw(e),rw(t))}};function rk(e,t){return t?e?function(){return g(w(e)?e.call(this,this):e,w(t)?t.call(this,this):t)}:t:e}function rw(e){if(S(e)){let t={};for(let n=0;n<e.length;n++)t[e[n]]=e[n];return t}return e}function rE(e,t){return e?[...new Set([].concat(e,t))]:t}function rA(e,t){return e?g(Object.create(null),e,t):t}function rN(e,t){return e?S(e)&&S(t)?[...new Set([...e,...t])]:g(Object.create(null),rm(e),rm(null!=t?t:{})):t}function rI(){return{app:null,config:{isNativeTag:h,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let rR=0,rO=null;function rM(e,t){if(lp){let n=lp.provides,r=lp.parent&&lp.parent.provides;r===n&&(n=lp.provides=Object.create(r)),n[e]=t}}function rL(e,t,n=!1){let r=lp||no;if(r||rO){let i=r?null==r.parent?r.vnode.appContext&&r.vnode.appContext.provides:r.parent.provides:rO._context.provides;if(i&&e in i)return i[e];if(arguments.length>1)return n&&w(t)?t.call(r&&r.proxy):t}}function rP(){return!!(lp||no||rO)}let r$={},rF=()=>Object.create(r$),rV=e=>Object.getPrototypeOf(e)===r$;function rD(e,t,n,r){let i;let[l,s]=e.propsOptions,o=!1;if(t)for(let a in t){let c;if($(a))continue;let u=t[a];l&&_(l,c=B(a))?s&&s.includes(c)?(i||(i={}))[c]=u:n[c]=u:iI(e.emitsOptions,a)||a in r&&u===r[a]||(r[a]=u,o=!0)}if(s){let t=tC(n),r=i||u;for(let i=0;i<s.length;i++){let o=s[i];n[o]=rB(l,t,o,r[o],e,!_(r,o))}}return o}function rB(e,t,n,r,i,l){let s=e[n];if(null!=s){let e=_(s,\"default\");if(e&&void 0===r){let e=s.default;if(s.type!==Function&&!s.skipFactory&&w(e)){let{propsDefaults:l}=i;if(n in l)r=l[n];else{let s=lf(i);r=l[n]=e.call(null,t),s()}}else r=e}s[0]&&(l&&!e?r=!1:s[1]&&(\"\"===r||r===j(n))&&(r=!0))}return r}let rU=new WeakMap;function rj(e){return!(\"$\"===e[0]||$(e))}let rH=e=>\"_\"===e[0]||\"$stable\"===e,rq=e=>S(e)?e.map(ll):[ll(e)],rW=(e,t,n)=>{if(t._n)return t;let r=nh((...e)=>rq(t(...e)),n);return r._c=!1,r},rK=(e,t,n)=>{let r=e._ctx;for(let n in e){if(rH(n))continue;let i=e[n];if(w(i))t[n]=rW(n,i,r);else if(null!=i){let e=rq(i);t[n]=()=>e}}},rz=(e,t)=>{let n=rq(t);e.slots.default=()=>n},rG=(e,t,n)=>{for(let r in t)(n||\"_\"!==r)&&(e[r]=t[r])},rJ=(e,t,n)=>{let r=e.slots=rF();if(32&e.vnode.shapeFlag){let e=t._;e?(rG(r,t,n),n&&z(r,\"_\",e,!0)):rK(t,r)}else t&&rz(e,t)},rX=(e,t,n)=>{let{vnode:r,slots:i}=e,l=!0,s=u;if(32&r.shapeFlag){let e=t._;e?n&&1===e?l=!1:rG(i,t,n):(l=!t.$stable,rK(t,i)),s=t}else t&&(rz(e,t),s={default:1});if(l)for(let e in i)rH(e)||null!=s[e]||delete i[e]};function rQ(e,t,n,r,i=!1){if(S(e)){e.forEach((e,l)=>rQ(e,t&&(S(t)?t[l]:t),n,r,i));return}if(nI(r)&&!i)return;let l=4&r.shapeFlag?lT(r.component):r.el,s=i?null:l,{i:o,r:a}=e,c=t&&t.r,d=o.refs===u?o.refs={}:o.refs,p=o.setupState;if(null!=c&&c!==a&&(E(c)?(d[c]=null,_(p,c)&&(p[c]=null)):tI(c)&&(c.value=null)),w(a))tQ(a,o,12,[s,d]);else{let t=E(a),r=tI(a);if(t||r){let o=()=>{if(e.f){let n=t?_(p,a)?p[a]:d[a]:a.value;i?S(n)&&y(n,l):S(n)?n.includes(l)||n.push(l):t?(d[a]=[l],_(p,a)&&(p[a]=d[a])):(a.value=[l],e.k&&(d[e.k]=a.value))}else t?(d[a]=s,_(p,a)&&(p[a]=s)):r&&(a.value=s,e.k&&(d[e.k]=s))};s?(o.id=-1,is(o,n)):o()}}}let rZ=Symbol(\"_vte\"),rY=e=>e.__isTeleport,r0=e=>e&&(e.disabled||\"\"===e.disabled),r1=e=>\"undefined\"!=typeof SVGElement&&e instanceof SVGElement,r2=e=>\"function\"==typeof MathMLElement&&e instanceof MathMLElement,r3=(e,t)=>{let n=e&&e.to;return E(n)?t?t(n):null:n};function r6(e,t,n,{o:{insert:r},m:i},l=2){0===l&&r(e.targetAnchor,t,n);let{el:s,anchor:o,shapeFlag:a,children:c,props:u}=e,d=2===l;if(d&&r(s,t,n),(!d||r0(u))&&16&a)for(let e=0;e<c.length;e++)i(c[e],t,n,2);d&&r(o,t,n)}let r4={name:\"Teleport\",__isTeleport:!0,process(e,t,n,r,i,l,s,o,a,c){let{mc:u,pc:d,pbc:p,o:{insert:h,querySelector:f,createText:m,createComment:g}}=c,y=r0(t.props),{shapeFlag:b,children:_,dynamicChildren:S}=t;if(null==e){let e=t.el=m(\"\"),c=t.anchor=m(\"\");h(e,n,r),h(c,n,r);let d=t.target=r3(t.props,f),p=r5(d,t,m,h);d&&(\"svg\"===s||r1(d)?s=\"svg\":(\"mathml\"===s||r2(d))&&(s=\"mathml\"));let g=(e,t)=>{16&b&&u(_,e,t,i,l,s,o,a)};y?g(n,c):d&&g(d,p)}else{t.el=e.el,t.targetStart=e.targetStart;let r=t.anchor=e.anchor,u=t.target=e.target,h=t.targetAnchor=e.targetAnchor,m=r0(e.props),g=m?n:u;if(\"svg\"===s||r1(u)?s=\"svg\":(\"mathml\"===s||r2(u))&&(s=\"mathml\"),S?(p(e.dynamicChildren,S,g,i,l,s,o),ih(e,t,!0)):a||d(e,t,g,m?r:h,i,l,s,o,!1),y)m?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):r6(t,n,r,c,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){let e=t.target=r3(t.props,f);e&&r6(t,e,null,c,0)}else m&&r6(t,u,h,c,1)}r8(t)},remove(e,t,n,{um:r,o:{remove:i}},l){let{shapeFlag:s,children:o,anchor:a,targetStart:c,targetAnchor:u,target:d,props:p}=e;if(d&&(i(c),i(u)),l&&i(a),16&s){let e=l||!r0(p);for(let i=0;i<o.length;i++){let l=o[i];r(l,t,n,e,!!l.dynamicChildren)}}},move:r6,hydrate:function(e,t,n,r,i,l,{o:{nextSibling:s,parentNode:o,querySelector:a,insert:c,createText:u}},d){let p=t.target=r3(t.props,a);if(p){let a=p._lpa||p.firstChild;if(16&t.shapeFlag){if(r0(t.props))t.anchor=d(s(e),t,o(e),n,r,i,l),t.targetStart=a,t.targetAnchor=a&&s(a);else{t.anchor=s(e);let o=a;for(;o;){if(o&&8===o.nodeType){if(\"teleport start anchor\"===o.data)t.targetStart=o;else if(\"teleport anchor\"===o.data){t.targetAnchor=o,p._lpa=t.targetAnchor&&s(t.targetAnchor);break}}o=s(o)}t.targetAnchor||r5(p,t,u,c),d(a&&s(a),t,p,n,r,i,l)}}r8(t)}return t.anchor&&s(t.anchor)}};function r8(e){let t=e.ctx;if(t&&t.ut){let n=e.children[0].el;for(;n&&n!==e.targetAnchor;)1===n.nodeType&&n.setAttribute(\"data-v-owner\",t.uid),n=n.nextSibling;t.ut()}}function r5(e,t,n,r){let i=t.targetStart=n(\"\"),l=t.targetAnchor=n(\"\");return i[rZ]=l,e&&(r(i,e),r(l,e)),l}let r9=!1,r7=()=>{r9||(console.error(\"Hydration completed but contains mismatches.\"),r9=!0)},ie=e=>e.namespaceURI.includes(\"svg\")&&\"foreignObject\"!==e.tagName,it=e=>e.namespaceURI.includes(\"MathML\"),ir=e=>ie(e)?\"svg\":it(e)?\"mathml\":void 0,ii=e=>8===e.nodeType;function il(e){let{mt:t,p:n,o:{patchProp:r,createText:i,nextSibling:l,parentNode:s,remove:o,insert:a,createComment:c}}=e,u=(n,r,o,c,f,_=!1)=>{_=_||!!r.dynamicChildren;let S=ii(n)&&\"[\"===n.data,x=()=>m(n,r,o,c,f,S),{type:C,ref:T,shapeFlag:k,patchFlag:w}=r,E=n.nodeType;r.el=n,-2===w&&(_=!1,r.dynamicChildren=null);let A=null;switch(C){case iW:3!==E?\"\"===r.children?(a(r.el=i(\"\"),s(n),n),A=n):A=x():(n.data!==r.children&&(r7(),n.data=r.children),A=l(n));break;case iK:b(n)?(A=l(n),y(r.el=n.content.firstChild,n,o)):A=8!==E||S?x():l(n);break;case iz:if(S&&(E=(n=l(n)).nodeType),1===E||3===E){A=n;let e=!r.children.length;for(let t=0;t<r.staticCount;t++)e&&(r.children+=1===A.nodeType?A.outerHTML:A.data),t===r.staticCount-1&&(r.anchor=A),A=l(A);return S?l(A):A}x();break;case iq:A=S?h(n,r,o,c,f,_):x();break;default:if(1&k)A=1===E&&r.type.toLowerCase()===n.tagName.toLowerCase()||b(n)?d(n,r,o,c,f,_):x();else if(6&k){r.slotScopeIds=f;let e=s(n);if(A=S?g(n):ii(n)&&\"teleport start\"===n.data?g(n,n.data,\"teleport end\"):l(n),t(r,e,null,o,c,ir(e),_),nI(r)){let t;S?(t=i7(iq)).anchor=A?A.previousSibling:e.lastChild:t=3===n.nodeType?ln(\"\"):i7(\"div\"),t.el=n,r.component.subTree=t}}else 64&k?A=8!==E?x():r.type.hydrate(n,r,o,c,f,_,e,p):128&k&&(A=r.type.hydrate(n,r,o,c,ir(s(n)),f,_,e,u))}return null!=T&&rQ(T,null,c,r),A},d=(e,t,n,i,l,s)=>{s=s||!!t.dynamicChildren;let{type:a,props:c,patchFlag:u,shapeFlag:d,dirs:h,transition:m}=t,g=\"input\"===a||\"option\"===a;if(g||-1!==u){let a;h&&nm(t,null,n,\"created\");let _=!1;if(b(e)){_=ip(i,m)&&n&&n.vnode.props&&n.vnode.props.appear;let r=e.content.firstChild;_&&m.beforeEnter(r),y(r,e,n),t.el=e=r}if(16&d&&!(c&&(c.innerHTML||c.textContent))){let r=p(e.firstChild,t,e,n,i,l,s);for(;r;){r7();let e=r;r=r.nextSibling,o(e)}}else 8&d&&e.textContent!==t.children&&(r7(),e.textContent=t.children);if(c){if(g||!s||48&u)for(let t in c)(g&&(t.endsWith(\"value\")||\"indeterminate\"===t)||f(t)&&!$(t)||\".\"===t[0])&&r(e,t,null,c[t],void 0,n);else if(c.onClick)r(e,\"onClick\",null,c.onClick,void 0,n);else if(4&u&&tb(c.style))for(let e in c.style)c.style[e]}(a=c&&c.onVnodeBeforeMount)&&lc(a,n,t),h&&nm(t,null,n,\"beforeMount\"),((a=c&&c.onVnodeMounted)||h||_)&&ij(()=>{a&&lc(a,n,t),_&&m.enter(e),h&&nm(t,null,n,\"mounted\")},i)}return e.nextSibling},p=(e,t,r,s,o,c,d)=>{d=d||!!t.dynamicChildren;let p=t.children,h=p.length;for(let t=0;t<h;t++){let h=d?p[t]:p[t]=ll(p[t]),f=h.type===iW;if(e){if(f&&!d){let n=p[t+1];n&&(n=ll(n)).type===iW&&(a(i(e.data.slice(h.children.length)),r,l(e)),e.data=h.children)}e=u(e,h,s,o,c,d)}else f&&!h.children?a(h.el=i(\"\"),r):(r7(),n(null,h,r,null,s,o,ir(r),c))}return e},h=(e,t,n,r,i,o)=>{let{slotScopeIds:u}=t;u&&(i=i?i.concat(u):u);let d=s(e),h=p(l(e),t,d,n,r,i,o);return h&&ii(h)&&\"]\"===h.data?l(t.anchor=h):(r7(),a(t.anchor=c(\"]\"),d,h),h)},m=(e,t,r,i,a,c)=>{if(r7(),t.el=null,c){let t=g(e);for(;;){let n=l(e);if(n&&n!==t)o(n);else break}}let u=l(e),d=s(e);return o(e),n(null,t,d,u,r,i,ir(d),a),u},g=(e,t=\"[\",n=\"]\")=>{let r=0;for(;e;)if((e=l(e))&&ii(e)&&(e.data===t&&r++,e.data===n)){if(0===r)return l(e);r--}return e},y=(e,t,n)=>{let r=t.parentNode;r&&r.replaceChild(e,t);let i=n;for(;i;)i.vnode.el===t&&(i.vnode.el=i.subTree.el=e),i=i.parent},b=e=>1===e.nodeType&&\"template\"===e.tagName.toLowerCase();return[(e,t)=>{if(!t.hasChildNodes()){n(null,e,t),ni(),t._vnode=e;return}u(t.firstChild,e,null,null,null),ni(),t._vnode=e},u]}let is=ij;function io(e){return ic(e)}function ia(e){return ic(e,il)}function ic(e,t){var n;let r,l;X().__VUE__=!0;let{insert:s,remove:o,patchProp:a,createElement:c,createText:h,createComment:f,setText:m,setElementText:y,parentNode:b,nextSibling:x,setScopeId:C=p,insertStaticContent:T}=e,k=(e,t,n,r=null,i=null,l=null,s,o=null,a=!!t.dynamicChildren)=>{if(e===t)return;e&&!i6(e,t)&&(r=eo(e),en(e,i,l,!0),e=null),-2===t.patchFlag&&(a=!1,t.dynamicChildren=null);let{type:c,ref:u,shapeFlag:d}=t;switch(c){case iW:E(e,t,n,r);break;case iK:A(e,t,n,r);break;case iz:null==e&&R(t,n,r,s);break;case iq:q(e,t,n,r,i,l,s,o,a);break;default:1&d?L(e,t,n,r,i,l,s,o,a):6&d?W(e,t,n,r,i,l,s,o,a):64&d?c.process(e,t,n,r,i,l,s,o,a,eu):128&d&&c.process(e,t,n,r,i,l,s,o,a,eu)}null!=u&&i&&rQ(u,e&&e.ref,l,t||e,!t)},E=(e,t,n,r)=>{if(null==e)s(t.el=h(t.children),n,r);else{let n=t.el=e.el;t.children!==e.children&&m(n,t.children)}},A=(e,t,n,r)=>{null==e?s(t.el=f(t.children||\"\"),n,r):t.el=e.el},R=(e,t,n,r)=>{[e.el,e.anchor]=T(e.children,t,n,r,e.el,e.anchor)},O=({el:e,anchor:t},n,r)=>{let i;for(;e&&e!==t;)i=x(e),s(e,n,r),e=i;s(t,n,r)},M=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=x(e),o(e),e=n;o(t)},L=(e,t,n,r,i,l,s,o,a)=>{\"svg\"===t.type?s=\"svg\":\"math\"===t.type&&(s=\"mathml\"),null==e?P(t,n,r,i,l,s,o,a):D(e,t,i,l,s,o,a)},P=(e,t,n,r,i,l,o,u)=>{let d,p;let{props:h,shapeFlag:f,transition:m,dirs:g}=e;if(d=e.el=c(e.type,l,h&&h.is,h),8&f?y(d,e.children):16&f&&V(e.children,d,null,r,i,iu(e,l),o,u),g&&nm(e,null,r,\"created\"),F(d,e,e.scopeId,o,r),h){for(let e in h)\"value\"===e||$(e)||a(d,e,null,h[e],l,r);\"value\"in h&&a(d,\"value\",null,h.value,l),(p=h.onVnodeBeforeMount)&&lc(p,r,e)}g&&nm(e,null,r,\"beforeMount\");let b=ip(i,m);b&&m.beforeEnter(d),s(d,t,n),((p=h&&h.onVnodeMounted)||b||g)&&is(()=>{p&&lc(p,r,e),b&&m.enter(d),g&&nm(e,null,r,\"mounted\")},i)},F=(e,t,n,r,i)=>{if(n&&C(e,n),r)for(let t=0;t<r.length;t++)C(e,r[t]);if(i&&t===i.subTree){let t=i.vnode;F(e,t,t.scopeId,t.slotScopeIds,i.parent)}},V=(e,t,n,r,i,l,s,o,a=0)=>{for(let c=a;c<e.length;c++)k(null,e[c]=o?ls(e[c]):ll(e[c]),t,n,r,i,l,s,o)},D=(e,t,n,r,i,l,s)=>{let o;let c=t.el=e.el,{patchFlag:d,dynamicChildren:p,dirs:h}=t;d|=16&e.patchFlag;let f=e.props||u,m=t.props||u;if(n&&id(n,!1),(o=m.onVnodeBeforeUpdate)&&lc(o,n,t,e),h&&nm(t,e,n,\"beforeUpdate\"),n&&id(n,!0),(f.innerHTML&&null==m.innerHTML||f.textContent&&null==m.textContent)&&y(c,\"\"),p?U(e.dynamicChildren,p,c,n,r,iu(t,i),l):s||Z(e,t,c,null,n,r,iu(t,i),l,!1),d>0){if(16&d)H(c,f,m,n,i);else if(2&d&&f.class!==m.class&&a(c,\"class\",null,m.class,i),4&d&&a(c,\"style\",f.style,m.style,i),8&d){let e=t.dynamicProps;for(let t=0;t<e.length;t++){let r=e[t],l=f[r],s=m[r];(s!==l||\"value\"===r)&&a(c,r,l,s,i,n)}}1&d&&e.children!==t.children&&y(c,t.children)}else s||null!=p||H(c,f,m,n,i);((o=m.onVnodeUpdated)||h)&&is(()=>{o&&lc(o,n,t,e),h&&nm(t,e,n,\"updated\")},r)},U=(e,t,n,r,i,l,s)=>{for(let o=0;o<t.length;o++){let a=e[o],c=t[o],u=a.el&&(a.type===iq||!i6(a,c)||70&a.shapeFlag)?b(a.el):n;k(a,c,u,null,r,i,l,s,!0)}},H=(e,t,n,r,i)=>{if(t!==n){if(t!==u)for(let l in t)$(l)||l in n||a(e,l,t[l],null,i,r);for(let l in n){if($(l))continue;let s=n[l],o=t[l];s!==o&&\"value\"!==l&&a(e,l,o,s,i,r)}\"value\"in n&&a(e,\"value\",t.value,n.value,i)}},q=(e,t,n,r,i,l,o,a,c)=>{let u=t.el=e?e.el:h(\"\"),d=t.anchor=e?e.anchor:h(\"\"),{patchFlag:p,dynamicChildren:f,slotScopeIds:m}=t;m&&(a=a?a.concat(m):m),null==e?(s(u,n,r),s(d,n,r),V(t.children||[],n,d,i,l,o,a,c)):p>0&&64&p&&f&&e.dynamicChildren?(U(e.dynamicChildren,f,n,i,l,o,a),(null!=t.key||i&&t===i.subTree)&&ih(e,t,!0)):Z(e,t,n,d,i,l,o,a,c)},W=(e,t,n,r,i,l,s,o,a)=>{t.slotScopeIds=o,null==e?512&t.shapeFlag?i.ctx.activate(t,n,r,s,a):z(t,n,r,i,l,s,a):G(e,t,a)},z=(e,t,n,r,l,s,o)=>{let a=e.component=function(e,t,n){let r=e.type,i=(t?t.appContext:e.appContext)||lu,l={uid:ld++,vnode:e,type:r,parent:t,appContext:i,root:null,next:null,subTree:null,effect:null,update:null,scope:new eg(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(i.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:function e(t,n,r=!1){let i=r?rU:n.propsCache,l=i.get(t);if(l)return l;let s=t.props,o={},a=[],c=!1;if(!w(t)){let i=t=>{c=!0;let[r,i]=e(t,n,!0);g(o,r),i&&a.push(...i)};!r&&n.mixins.length&&n.mixins.forEach(i),t.extends&&i(t.extends),t.mixins&&t.mixins.forEach(i)}if(!s&&!c)return N(t)&&i.set(t,d),d;if(S(s))for(let e=0;e<s.length;e++){let t=B(s[e]);rj(t)&&(o[t]=u)}else if(s)for(let e in s){let t=B(e);if(rj(t)){let n=s[e],r=o[t]=S(n)||w(n)?{type:n}:g({},n),i=r.type,l=!1,c=!0;if(S(i))for(let e=0;e<i.length;++e){let t=i[e],n=w(t)&&t.name;if(\"Boolean\"===n){l=!0;break}\"String\"===n&&(c=!1)}else l=w(i)&&\"Boolean\"===i.name;r[0]=l,r[1]=c,(l||_(r,\"default\"))&&a.push(t)}}let p=[o,a];return N(t)&&i.set(t,p),p}(r,i),emitsOptions:function e(t,n,r=!1){let i=n.emitsCache,l=i.get(t);if(void 0!==l)return l;let s=t.emits,o={},a=!1;if(!w(t)){let i=t=>{let r=e(t,n,!0);r&&(a=!0,g(o,r))};!r&&n.mixins.length&&n.mixins.forEach(i),t.extends&&i(t.extends),t.mixins&&t.mixins.forEach(i)}return s||a?(S(s)?s.forEach(e=>o[e]=null):g(o,s),N(t)&&i.set(t,o),o):(N(t)&&i.set(t,null),null)}(r,i),emit:null,emitted:null,propsDefaults:u,inheritAttrs:r.inheritAttrs,ctx:u,data:u,props:u,attrs:u,slots:u,refs:u,setupState:u,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return l.ctx={_:l},l.root=t?t.root:l,l.emit=iN.bind(null,l),e.ce&&e.ce(l),l}(e,r,l);nM(e)&&(a.ctx.renderer=eu),function(e,t=!1,n=!1){t&&i(t);let{props:r,children:l}=e.vnode,s=lg(e);(function(e,t,n,r=!1){let i={},l=rF();for(let n in e.propsDefaults=Object.create(null),rD(e,t,i,l),e.propsOptions[0])n in i||(i[n]=void 0);n?e.props=r?i:tm(i):e.type.props?e.props=i:e.props=l,e.attrs=l})(e,r,s,t),rJ(e,l,n),s&&function(e,t){let n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,rr);let{setup:r}=n;if(r){let n=e.setupContext=r.length>1?lC(e):null,i=lf(e);eI();let l=tQ(r,e,0,[e.props,n]);if(eR(),i(),I(l)){if(l.then(lm,lm),t)return l.then(n=>{lv(e,n,t)}).catch(t=>{tY(t,e,0)});e.asyncDep=l}else lv(e,l,t)}else lS(e,t)}(e,t),t&&i(!1)}(a,!1,o),a.asyncDep?(l&&l.registerDep(a,J,o),e.el||A(null,a.subTree=i7(iK),t,n)):J(a,e,t,n,l,s,o)},G=(e,t,n)=>{let r=t.component=e.component;if(function(e,t,n){let{props:r,children:i,component:l}=e,{props:s,children:o,patchFlag:a}=t,c=l.emitsOptions;if(t.dirs||t.transition)return!0;if(!n||!(a>=0))return(!!i||!!o)&&(!o||!o.$stable)||r!==s&&(r?!s||iL(r,s,c):!!s);if(1024&a)return!0;if(16&a)return r?iL(r,s,c):!!s;if(8&a){let e=t.dynamicProps;for(let t=0;t<e.length;t++){let n=e[t];if(s[n]!==r[n]&&!iI(c,n))return!0}}return!1}(e,t,n)){if(r.asyncDep&&!r.asyncResolved){Q(r,t,n);return}r.next=t,function(e){let t=t2.indexOf(e);t>t3&&t2.splice(t,1)}(r.update),r.effect.dirty=!0,r.update()}else t.el=e.el,r.vnode=t},J=(e,t,n,r,i,s,o)=>{let a=()=>{if(e.isMounted){let t,{next:n,bu:r,u:l,parent:c,vnode:u}=e;{let t=function e(t){let n=t.subTree.component;if(n)return n.asyncDep&&!n.asyncResolved?n:e(n)}(e);if(t){n&&(n.el=u.el,Q(e,n,o)),t.asyncDep.then(()=>{e.isUnmounted||a()});return}}let d=n;id(e,!1),n?(n.el=u.el,Q(e,n,o)):n=u,r&&K(r),(t=n.props&&n.props.onVnodeBeforeUpdate)&&lc(t,c,n,u),id(e,!0);let p=iR(e),h=e.subTree;e.subTree=p,k(h,p,b(h.el),eo(h),e,i,s),n.el=p.el,null===d&&iP(e,p.el),l&&is(l,i),(t=n.props&&n.props.onVnodeUpdated)&&is(()=>lc(t,c,n,u),i)}else{let o;let{el:a,props:c}=t,{bm:u,m:d,parent:p}=e,h=nI(t);if(id(e,!1),u&&K(u),!h&&(o=c&&c.onVnodeBeforeMount)&&lc(o,p,t),id(e,!0),a&&l){let n=()=>{e.subTree=iR(e),l(a,e.subTree,e,i,null)};h?t.type.__asyncLoader().then(()=>!e.isUnmounted&&n()):n()}else{let l=e.subTree=iR(e);k(null,l,n,r,e,i,s),t.el=l.el}if(d&&is(d,i),!h&&(o=c&&c.onVnodeMounted)){let e=t;is(()=>lc(o,p,e),i)}(256&t.shapeFlag||p&&nI(p.vnode)&&256&p.vnode.shapeFlag)&&e.a&&is(e.a,i),e.isMounted=!0,t=n=r=null}},c=e.effect=new eS(a,p,()=>ne(u),e.scope),u=e.update=()=>{c.dirty&&c.run()};u.i=e,u.id=e.uid,id(e,!0),u()},Q=(e,t,n)=>{t.component=e;let r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,r){let{props:i,attrs:l,vnode:{patchFlag:s}}=e,o=tC(i),[a]=e.propsOptions,c=!1;if((r||s>0)&&!(16&s)){if(8&s){let n=e.vnode.dynamicProps;for(let r=0;r<n.length;r++){let s=n[r];if(iI(e.emitsOptions,s))continue;let u=t[s];if(a){if(_(l,s))u!==l[s]&&(l[s]=u,c=!0);else{let t=B(s);i[t]=rB(a,o,t,u,e,!1)}}else u!==l[s]&&(l[s]=u,c=!0)}}}else{let r;for(let s in rD(e,t,i,l)&&(c=!0),o)t&&(_(t,s)||(r=j(s))!==s&&_(t,r))||(a?n&&(void 0!==n[s]||void 0!==n[r])&&(i[s]=rB(a,o,s,void 0,e,!0)):delete i[s]);if(l!==o)for(let e in l)t&&_(t,e)||(delete l[e],c=!0)}c&&eU(e.attrs,\"set\",\"\")}(e,t.props,r,n),rX(e,t.children,n),eI(),nr(e),eR()},Z=(e,t,n,r,i,l,s,o,a=!1)=>{let c=e&&e.children,u=e?e.shapeFlag:0,d=t.children,{patchFlag:p,shapeFlag:h}=t;if(p>0){if(128&p){ee(c,d,n,r,i,l,s,o,a);return}if(256&p){Y(c,d,n,r,i,l,s,o,a);return}}8&h?(16&u&&es(c,i,l),d!==c&&y(n,d)):16&u?16&h?ee(c,d,n,r,i,l,s,o,a):es(c,i,l,!0):(8&u&&y(n,\"\"),16&h&&V(d,n,r,i,l,s,o,a))},Y=(e,t,n,r,i,l,s,o,a)=>{let c;e=e||d,t=t||d;let u=e.length,p=t.length,h=Math.min(u,p);for(c=0;c<h;c++){let r=t[c]=a?ls(t[c]):ll(t[c]);k(e[c],r,n,null,i,l,s,o,a)}u>p?es(e,i,l,!0,!1,h):V(t,n,r,i,l,s,o,a,h)},ee=(e,t,n,r,i,l,s,o,a)=>{let c=0,u=t.length,p=e.length-1,h=u-1;for(;c<=p&&c<=h;){let r=e[c],u=t[c]=a?ls(t[c]):ll(t[c]);if(i6(r,u))k(r,u,n,null,i,l,s,o,a);else break;c++}for(;c<=p&&c<=h;){let r=e[p],c=t[h]=a?ls(t[h]):ll(t[h]);if(i6(r,c))k(r,c,n,null,i,l,s,o,a);else break;p--,h--}if(c>p){if(c<=h){let e=h+1,d=e<u?t[e].el:r;for(;c<=h;)k(null,t[c]=a?ls(t[c]):ll(t[c]),n,d,i,l,s,o,a),c++}}else if(c>h)for(;c<=p;)en(e[c],i,l,!0),c++;else{let f;let m=c,g=c,y=new Map;for(c=g;c<=h;c++){let e=t[c]=a?ls(t[c]):ll(t[c]);null!=e.key&&y.set(e.key,c)}let b=0,_=h-g+1,S=!1,x=0,C=Array(_);for(c=0;c<_;c++)C[c]=0;for(c=m;c<=p;c++){let r;let u=e[c];if(b>=_){en(u,i,l,!0);continue}if(null!=u.key)r=y.get(u.key);else for(f=g;f<=h;f++)if(0===C[f-g]&&i6(u,t[f])){r=f;break}void 0===r?en(u,i,l,!0):(C[r-g]=c+1,r>=x?x=r:S=!0,k(u,t[r],n,null,i,l,s,o,a),b++)}let T=S?function(e){let t,n,r,i,l;let s=e.slice(),o=[0],a=e.length;for(t=0;t<a;t++){let a=e[t];if(0!==a){if(e[n=o[o.length-1]]<a){s[t]=n,o.push(t);continue}for(r=0,i=o.length-1;r<i;)e[o[l=r+i>>1]]<a?r=l+1:i=l;a<e[o[r]]&&(r>0&&(s[t]=o[r-1]),o[r]=t)}}for(r=o.length,i=o[r-1];r-- >0;)o[r]=i,i=s[i];return o}(C):d;for(f=T.length-1,c=_-1;c>=0;c--){let e=g+c,d=t[e],p=e+1<u?t[e+1].el:r;0===C[c]?k(null,d,n,p,i,l,s,o,a):S&&(f<0||c!==T[f]?et(d,n,p,2):f--)}}},et=(e,t,n,r,i=null)=>{let{el:l,type:o,transition:a,children:c,shapeFlag:u}=e;if(6&u){et(e.component.subTree,t,n,r);return}if(128&u){e.suspense.move(t,n,r);return}if(64&u){o.move(e,t,n,eu);return}if(o===iq){s(l,t,n);for(let e=0;e<c.length;e++)et(c[e],t,n,r);s(e.anchor,t,n);return}if(o===iz){O(e,t,n);return}if(2!==r&&1&u&&a){if(0===r)a.beforeEnter(l),s(l,t,n),is(()=>a.enter(l),i);else{let{leave:e,delayLeave:r,afterLeave:i}=a,o=()=>s(l,t,n),c=()=>{e(l,()=>{o(),i&&i()})};r?r(l,o,c):c()}}else s(l,t,n)},en=(e,t,n,r=!1,i=!1)=>{let l;let{type:s,props:o,ref:a,children:c,dynamicChildren:u,shapeFlag:d,patchFlag:p,dirs:h,cacheIndex:f}=e;if(-2===p&&(i=!1),null!=a&&rQ(a,null,n,e,!0),null!=f&&(t.renderCache[f]=void 0),256&d){t.ctx.deactivate(e);return}let m=1&d&&h,g=!nI(e);if(g&&(l=o&&o.onVnodeBeforeUnmount)&&lc(l,t,e),6&d)el(e.component,n,r);else{if(128&d){e.suspense.unmount(n,r);return}m&&nm(e,null,t,\"beforeUnmount\"),64&d?e.type.remove(e,t,n,eu,r):u&&!u.hasOnce&&(s!==iq||p>0&&64&p)?es(u,t,n,!1,!0):(s===iq&&384&p||!i&&16&d)&&es(c,t,n),r&&er(e)}(g&&(l=o&&o.onVnodeUnmounted)||m)&&is(()=>{l&&lc(l,t,e),m&&nm(e,null,t,\"unmounted\")},n)},er=e=>{let{type:t,el:n,anchor:r,transition:i}=e;if(t===iq){ei(n,r);return}if(t===iz){M(e);return}let l=()=>{o(n),i&&!i.persisted&&i.afterLeave&&i.afterLeave()};if(1&e.shapeFlag&&i&&!i.persisted){let{leave:t,delayLeave:r}=i,s=()=>t(n,l);r?r(e.el,l,s):s()}else l()},ei=(e,t)=>{let n;for(;e!==t;)n=x(e),o(e),e=n;o(t)},el=(e,t,n)=>{let{bum:r,scope:i,update:l,subTree:s,um:o,m:a,a:c}=e;im(a),im(c),r&&K(r),i.stop(),l&&(l.active=!1,en(s,e,t,n)),o&&is(o,t),is(()=>{e.isUnmounted=!0},t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},es=(e,t,n,r=!1,i=!1,l=0)=>{for(let s=l;s<e.length;s++)en(e[s],t,n,r,i)},eo=e=>{if(6&e.shapeFlag)return eo(e.component.subTree);if(128&e.shapeFlag)return e.suspense.next();let t=x(e.anchor||e.el),n=t&&t[rZ];return n?x(n):t},ea=!1,ec=(e,t,n)=>{null==e?t._vnode&&en(t._vnode,null,null,!0):k(t._vnode||null,e,t,null,null,null,n),ea||(ea=!0,nr(),ni(),ea=!1),t._vnode=e},eu={p:k,um:en,m:et,r:er,mt:z,mc:V,pc:Z,pbc:U,n:eo,o:e};return t&&([r,l]=t(eu)),{render:ec,hydrate:r,createApp:(n=r,function(e,t=null){w(e)||(e=g({},e)),null==t||N(t)||(t=null);let r=rI(),i=new WeakSet,l=!1,s=r.app={_uid:rR++,_component:e,_props:t,_container:null,_context:r,_instance:null,version:lR,get config(){return r.config},set config(v){},use:(e,...t)=>(i.has(e)||(e&&w(e.install)?(i.add(e),e.install(s,...t)):w(e)&&(i.add(e),e(s,...t))),s),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),s),component:(e,t)=>t?(r.components[e]=t,s):r.components[e],directive:(e,t)=>t?(r.directives[e]=t,s):r.directives[e],mount(i,o,a){if(!l){let c=i7(e,t);return c.appContext=r,!0===a?a=\"svg\":!1===a&&(a=void 0),o&&n?n(c,i):ec(c,i,a),l=!0,s._container=i,i.__vue_app__=s,lT(c.component)}},unmount(){l&&(ec(null,s._container),delete s._container.__vue_app__)},provide:(e,t)=>(r.provides[e]=t,s),runWithContext(e){let t=rO;rO=s;try{return e()}finally{rO=t}}};return s})}}function iu({type:e,props:t},n){return\"svg\"===n&&\"foreignObject\"===e||\"mathml\"===n&&\"annotation-xml\"===e&&t&&t.encoding&&t.encoding.includes(\"html\")?void 0:n}function id({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function ip(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function ih(e,t,n=!1){let r=e.children,i=t.children;if(S(r)&&S(i))for(let e=0;e<r.length;e++){let t=r[e],l=i[e];!(1&l.shapeFlag)||l.dynamicChildren||((l.patchFlag<=0||32===l.patchFlag)&&((l=i[e]=ls(i[e])).el=t.el),n||-2===l.patchFlag||ih(t,l)),l.type===iW&&(l.el=t.el)}}function im(e){if(e)for(let t=0;t<e.length;t++)e[t].active=!1}let ig=Symbol.for(\"v-scx\"),iy=()=>rL(ig);function iv(e,t){return iC(e,null,t)}function ib(e,t){return iC(e,null,{flush:\"post\"})}function i_(e,t){return iC(e,null,{flush:\"sync\"})}let iS={};function ix(e,t,n){return iC(e,t,n)}function iC(e,t,{immediate:n,deep:r,flush:i,once:l,onTrack:s,onTrigger:o}=u){let a,c,d;if(t&&l){let e=t;t=(...t)=>{e(...t),k()}}let h=lp,f=e=>!0===r?e:iw(e,!1===r?1:void 0),m=!1,g=!1;if(tI(e)?(a=()=>e.value,m=tS(e)):tb(e)?(a=()=>f(e),m=!0):S(e)?(g=!0,m=e.some(e=>tb(e)||tS(e)),a=()=>e.map(e=>tI(e)?e.value:tb(e)?f(e):w(e)?tQ(e,h,2):void 0)):a=w(e)?t?()=>tQ(e,h,2):()=>(c&&c(),tZ(e,h,3,[b])):p,t&&r){let e=a;a=()=>iw(e())}let b=e=>{c=C.onStop=()=>{tQ(e,h,4),c=C.onStop=void 0}},_=g?Array(e.length).fill(iS):iS,x=()=>{if(C.active&&C.dirty){if(t){let e=C.run();(r||m||(g?e.some((e,t)=>W(e,_[t])):W(e,_)))&&(c&&c(),tZ(t,h,3,[e,_===iS?void 0:g&&_[0]===iS?[]:_,b]),_=e)}else C.run()}};x.allowRecurse=!!t,\"sync\"===i?d=x:\"post\"===i?d=()=>is(x,h&&h.suspense):(x.pre=!0,h&&(x.id=h.uid),d=()=>ne(x));let C=new eS(a,p,d),T=eb(),k=()=>{C.stop(),T&&y(T.effects,C)};return t?n?x():_=C.run():\"post\"===i?is(C.run.bind(C),h&&h.suspense):C.run(),k}function iT(e,t,n){let r;let i=this.proxy,l=E(e)?e.includes(\".\")?ik(i,e):()=>i[e]:e.bind(i,i);w(t)?r=t:(r=t.handler,n=t);let s=lf(this),o=iC(l,r.bind(i),n);return s(),o}function ik(e,t){let n=t.split(\".\");return()=>{let t=e;for(let e=0;e<n.length&&t;e++)t=t[n[e]];return t}}function iw(e,t=1/0,n){if(t<=0||!N(e)||e.__v_skip||(n=n||new Set).has(e))return e;if(n.add(e),t--,tI(e))iw(e.value,t,n);else if(S(e))for(let r=0;r<e.length;r++)iw(e[r],t,n);else if(C(e)||x(e))e.forEach(e=>{iw(e,t,n)});else if(L(e)){for(let r in e)iw(e[r],t,n);for(let r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&iw(e[r],t,n)}return e}function iE(e,t,n=u){let r=lh(),i=B(t),l=j(t),s=iA(e,t),o=tU((s,o)=>{let a,c;let d=u;return i_(()=>{let n=e[t];W(a,n)&&(a=n,o())}),{get:()=>(s(),n.get?n.get(a):a),set(e){if(!W(e,a)&&!(d!==u&&W(e,d)))return;let s=r.vnode.props;s&&(t in s||i in s||l in s)&&(`onUpdate:${t}` in s||`onUpdate:${i}` in s||`onUpdate:${l}` in s)||(a=e,o());let p=n.set?n.set(e):e;r.emit(`update:${t}`,p),W(e,p)&&W(e,d)&&!W(p,c)&&o(),d=e,c=p}}});return o[Symbol.iterator]=()=>{let e=0;return{next:()=>e<2?{value:e++?s||u:o,done:!1}:{done:!0}}},o}let iA=(e,t)=>\"modelValue\"===t||\"model-value\"===t?e.modelModifiers:e[`${t}Modifiers`]||e[`${B(t)}Modifiers`]||e[`${j(t)}Modifiers`];function iN(e,t,...n){let r;if(e.isUnmounted)return;let i=e.vnode.props||u,l=n,s=t.startsWith(\"update:\"),o=s&&iA(i,t.slice(7));o&&(o.trim&&(l=n.map(e=>E(e)?e.trim():e)),o.number&&(l=n.map(G)));let a=i[r=q(t)]||i[r=q(B(t))];!a&&s&&(a=i[r=q(j(t))]),a&&tZ(a,e,6,l);let c=i[r+\"Once\"];if(c){if(e.emitted){if(e.emitted[r])return}else e.emitted={};e.emitted[r]=!0,tZ(c,e,6,l)}}function iI(e,t){return!!(e&&f(t))&&(_(e,(t=t.slice(2).replace(/Once$/,\"\"))[0].toLowerCase()+t.slice(1))||_(e,j(t))||_(e,t))}function iR(e){let t,n;let{type:r,vnode:i,proxy:l,withProxy:s,propsOptions:[o],slots:a,attrs:c,emit:u,render:d,renderCache:p,props:h,data:f,setupState:g,ctx:y,inheritAttrs:b}=e,_=nc(e);try{if(4&i.shapeFlag){let e=s||l;t=ll(d.call(e,e,p,h,g,f,y)),n=c}else t=ll(r.length>1?r(h,{attrs:c,slots:a,emit:u}):r(h,null)),n=r.props?c:iO(c)}catch(n){iG.length=0,tY(n,e,1),t=i7(iK)}let S=t;if(n&&!1!==b){let e=Object.keys(n),{shapeFlag:t}=S;e.length&&7&t&&(o&&e.some(m)&&(n=iM(n,o)),S=lt(S,n,!1,!0))}return i.dirs&&((S=lt(S,null,!1,!0)).dirs=S.dirs?S.dirs.concat(i.dirs):i.dirs),i.transition&&(S.transition=i.transition),t=S,nc(_),t}let iO=e=>{let t;for(let n in e)(\"class\"===n||\"style\"===n||f(n))&&((t||(t={}))[n]=e[n]);return t},iM=(e,t)=>{let n={};for(let r in e)m(r)&&r.slice(9) in t||(n[r]=e[r]);return n};function iL(e,t,n){let r=Object.keys(t);if(r.length!==Object.keys(e).length)return!0;for(let i=0;i<r.length;i++){let l=r[i];if(t[l]!==e[l]&&!iI(n,l))return!0}return!1}function iP({vnode:e,parent:t},n){for(;t;){let r=t.subTree;if(r.suspense&&r.suspense.activeBranch===e&&(r.el=e.el),r===e)(e=t.vnode).el=n,t=t.parent;else break}}let i$=e=>e.__isSuspense,iF=0,iV={name:\"Suspense\",__isSuspense:!0,process(e,t,n,r,i,l,s,o,a,c){if(null==e)!function(e,t,n,r,i,l,s,o,a){let{p:c,o:{createElement:u}}=a,d=u(\"div\"),p=e.suspense=iB(e,i,r,t,d,n,l,s,o,a);c(null,p.pendingBranch=e.ssContent,d,null,r,p,l,s),p.deps>0?(iD(e,\"onPending\"),iD(e,\"onFallback\"),c(null,e.ssFallback,t,n,r,null,l,s),iH(p,e.ssFallback)):p.resolve(!1,!0)}(t,n,r,i,l,s,o,a,c);else{if(l&&l.deps>0&&!e.suspense.isInFallback){t.suspense=e.suspense,t.suspense.vnode=t,t.el=e.el;return}!function(e,t,n,r,i,l,s,o,{p:a,um:c,o:{createElement:u}}){let d=t.suspense=e.suspense;d.vnode=t,t.el=e.el;let p=t.ssContent,h=t.ssFallback,{activeBranch:f,pendingBranch:m,isInFallback:g,isHydrating:y}=d;if(m)d.pendingBranch=p,i6(p,m)?(a(m,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0?d.resolve():g&&!y&&(a(f,h,n,r,i,null,l,s,o),iH(d,h))):(d.pendingId=iF++,y?(d.isHydrating=!1,d.activeBranch=m):c(m,i,d),d.deps=0,d.effects.length=0,d.hiddenContainer=u(\"div\"),g?(a(null,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0?d.resolve():(a(f,h,n,r,i,null,l,s,o),iH(d,h))):f&&i6(p,f)?(a(f,p,n,r,i,d,l,s,o),d.resolve(!0)):(a(null,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0&&d.resolve()));else if(f&&i6(p,f))a(f,p,n,r,i,d,l,s,o),iH(d,p);else if(iD(t,\"onPending\"),d.pendingBranch=p,512&p.shapeFlag?d.pendingId=p.component.suspenseId:d.pendingId=iF++,a(null,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0)d.resolve();else{let{timeout:e,pendingId:t}=d;e>0?setTimeout(()=>{d.pendingId===t&&d.fallback(h)},e):0===e&&d.fallback(h)}}(e,t,n,r,i,s,o,a,c)}},hydrate:function(e,t,n,r,i,l,s,o,a){let c=t.suspense=iB(t,r,n,e.parentNode,document.createElement(\"div\"),null,i,l,s,o,!0),u=a(e,c.pendingBranch=t.ssContent,n,c,l,s);return 0===c.deps&&c.resolve(!1,!0),u},normalize:function(e){let{shapeFlag:t,children:n}=e,r=32&t;e.ssContent=iU(r?n.default:n),e.ssFallback=r?iU(n.fallback):i7(iK)}};function iD(e,t){let n=e.props&&e.props[t];w(n)&&n()}function iB(e,t,n,r,i,l,s,o,a,c,u=!1){let d;let{p:p,m:h,um:f,n:m,o:{parentNode:g,remove:y}}=c,b=function(e){let t=e.props&&e.props.suspensible;return null!=t&&!1!==t}(e);b&&t&&t.pendingBranch&&(d=t.pendingId,t.deps++);let _=e.props?J(e.props.timeout):void 0,S=l,x={vnode:e,parent:t,parentComponent:n,namespace:s,container:r,hiddenContainer:i,deps:0,pendingId:iF++,timeout:\"number\"==typeof _?_:-1,activeBranch:null,pendingBranch:null,isInFallback:!u,isHydrating:u,isUnmounted:!1,effects:[],resolve(e=!1,n=!1){let{vnode:r,activeBranch:i,pendingBranch:s,pendingId:o,effects:a,parentComponent:c,container:u}=x,p=!1;x.isHydrating?x.isHydrating=!1:e||((p=i&&s.transition&&\"out-in\"===s.transition.mode)&&(i.transition.afterLeave=()=>{o===x.pendingId&&(h(s,u,l===S?m(i):l,0),nn(a))}),i&&(g(i.el)!==x.hiddenContainer&&(l=m(i)),f(i,c,x,!0)),p||h(s,u,l,0)),iH(x,s),x.pendingBranch=null,x.isInFallback=!1;let y=x.parent,_=!1;for(;y;){if(y.pendingBranch){y.effects.push(...a),_=!0;break}y=y.parent}_||p||nn(a),x.effects=[],b&&t&&t.pendingBranch&&d===t.pendingId&&(t.deps--,0!==t.deps||n||t.resolve()),iD(r,\"onResolve\")},fallback(e){if(!x.pendingBranch)return;let{vnode:t,activeBranch:n,parentComponent:r,container:i,namespace:l}=x;iD(t,\"onFallback\");let s=m(n),c=()=>{x.isInFallback&&(p(null,e,i,s,r,null,l,o,a),iH(x,e))},u=e.transition&&\"out-in\"===e.transition.mode;u&&(n.transition.afterLeave=c),x.isInFallback=!0,f(n,r,null,!0),u||c()},move(e,t,n){x.activeBranch&&h(x.activeBranch,e,t,n),x.container=e},next:()=>x.activeBranch&&m(x.activeBranch),registerDep(e,t,n){let r=!!x.pendingBranch;r&&x.deps++;let i=e.vnode.el;e.asyncDep.catch(t=>{tY(t,e,0)}).then(l=>{if(e.isUnmounted||x.isUnmounted||x.pendingId!==e.suspenseId)return;e.asyncResolved=!0;let{vnode:o}=e;lv(e,l,!1),i&&(o.el=i);let a=!i&&e.subTree.el;t(e,o,g(i||e.subTree.el),i?null:m(e.subTree),x,s,n),a&&y(a),iP(e,o.el),r&&0==--x.deps&&x.resolve()})},unmount(e,t){x.isUnmounted=!0,x.activeBranch&&f(x.activeBranch,n,e,t),x.pendingBranch&&f(x.pendingBranch,n,e,t)}};return x}function iU(e){let t;if(w(e)){let n=iZ&&e._c;n&&(e._d=!1,iX()),e=e(),n&&(e._d=!0,t=iJ,iQ())}return S(e)&&(e=function(e,t=!0){let n;for(let t=0;t<e.length;t++){let r=e[t];if(!i3(r))return;if(r.type!==iK||\"v-if\"===r.children){if(n)return;n=r}}return n}(e)),e=ll(e),t&&!e.dynamicChildren&&(e.dynamicChildren=t.filter(t=>t!==e)),e}function ij(e,t){t&&t.pendingBranch?S(e)?t.effects.push(...e):t.effects.push(e):nn(e)}function iH(e,t){e.activeBranch=t;let{vnode:n,parentComponent:r}=e,i=t.el;for(;!i&&t.component;)i=(t=t.component.subTree).el;n.el=i,r&&r.subTree===n&&(r.vnode.el=i,iP(r,i))}let iq=Symbol.for(\"v-fgt\"),iW=Symbol.for(\"v-txt\"),iK=Symbol.for(\"v-cmt\"),iz=Symbol.for(\"v-stc\"),iG=[],iJ=null;function iX(e=!1){iG.push(iJ=e?null:[])}function iQ(){iG.pop(),iJ=iG[iG.length-1]||null}let iZ=1;function iY(e){iZ+=e,e<0&&iJ&&(iJ.hasOnce=!0)}function i0(e){return e.dynamicChildren=iZ>0?iJ||d:null,iQ(),iZ>0&&iJ&&iJ.push(e),e}function i1(e,t,n,r,i,l){return i0(i9(e,t,n,r,i,l,!0))}function i2(e,t,n,r,i){return i0(i7(e,t,n,r,i,!0))}function i3(e){return!!e&&!0===e.__v_isVNode}function i6(e,t){return e.type===t.type&&e.key===t.key}function i4(e){}let i8=({key:e})=>null!=e?e:null,i5=({ref:e,ref_key:t,ref_for:n})=>(\"number\"==typeof e&&(e=\"\"+e),null!=e?E(e)||tI(e)||w(e)?{i:no,r:e,k:t,f:!!n}:e:null);function i9(e,t=null,n=null,r=0,i=null,l=e===iq?0:1,s=!1,o=!1){let a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&i8(t),ref:t&&i5(t),scopeId:na,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:r,dynamicProps:i,dynamicChildren:null,appContext:null,ctx:no};return o?(lo(a,n),128&l&&e.normalize(a)):n&&(a.shapeFlag|=E(n)?8:16),iZ>0&&!s&&iJ&&(a.patchFlag>0||6&l)&&32!==a.patchFlag&&iJ.push(a),a}let i7=function(e,t=null,n=null,r=0,i=null,l=!1){var s;if(e&&e!==n1||(e=iK),i3(e)){let r=lt(e,t,!0);return n&&lo(r,n),iZ>0&&!l&&iJ&&(6&r.shapeFlag?iJ[iJ.indexOf(e)]=r:iJ.push(r)),r.patchFlag=-2,r}if(w(s=e)&&\"__vccOpts\"in s&&(e=e.__vccOpts),t){let{class:e,style:n}=t=le(t);e&&!E(e)&&(t.class=er(e)),N(n)&&(tx(n)&&!S(n)&&(n=g({},n)),t.style=Z(n))}let o=E(e)?1:i$(e)?128:rY(e)?64:N(e)?4:w(e)?2:0;return i9(e,t,n,r,i,o,l,!0)};function le(e){return e?tx(e)||rV(e)?g({},e):e:null}function lt(e,t,n=!1,r=!1){let{props:i,ref:l,patchFlag:s,children:o,transition:a}=e,c=t?la(i||{},t):i,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&i8(c),ref:t&&t.ref?n&&l?S(l)?l.concat(i5(t)):[l,i5(t)]:i5(t):l,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:o,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==iq?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:a,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&lt(e.ssContent),ssFallback:e.ssFallback&&lt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return a&&r&&nE(u,a.clone(u)),u}function ln(e=\" \",t=0){return i7(iW,null,e,t)}function lr(e,t){let n=i7(iz,null,e);return n.staticCount=t,n}function li(e=\"\",t=!1){return t?(iX(),i2(iK,null,e)):i7(iK,null,e)}function ll(e){return null==e||\"boolean\"==typeof e?i7(iK):S(e)?i7(iq,null,e.slice()):\"object\"==typeof e?ls(e):i7(iW,null,String(e))}function ls(e){return null===e.el&&-1!==e.patchFlag||e.memo?e:lt(e)}function lo(e,t){let n=0,{shapeFlag:r}=e;if(null==t)t=null;else if(S(t))n=16;else if(\"object\"==typeof t){if(65&r){let n=t.default;n&&(n._c&&(n._d=!1),lo(e,n()),n._c&&(n._d=!0));return}{n=32;let r=t._;r||rV(t)?3===r&&no&&(1===no.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=no}}else w(t)?(t={default:t,_ctx:no},n=32):(t=String(t),64&r?(n=16,t=[ln(t)]):n=8);e.children=t,e.shapeFlag|=n}function la(...e){let t={};for(let n=0;n<e.length;n++){let r=e[n];for(let e in r)if(\"class\"===e)t.class!==r.class&&(t.class=er([t.class,r.class]));else if(\"style\"===e)t.style=Z([t.style,r.style]);else if(f(e)){let n=t[e],i=r[e];i&&n!==i&&!(S(n)&&n.includes(i))&&(t[e]=n?[].concat(n,i):i)}else\"\"!==e&&(t[e]=r[e])}return t}function lc(e,t,n,r=null){tZ(e,t,7,[n,r])}let lu=rI(),ld=0,lp=null,lh=()=>lp||no;r=e=>{lp=e},i=e=>{ly=e};let lf=e=>{let t=lp;return r(e),e.scope.on(),()=>{e.scope.off(),r(t)}},lm=()=>{lp&&lp.scope.off(),r(null)};function lg(e){return 4&e.vnode.shapeFlag}let ly=!1;function lv(e,t,n){w(t)?e.render=t:N(t)&&(e.setupState=tD(t)),lS(e,n)}function lb(e){l=e,s=e=>{e.render._rc&&(e.withProxy=new Proxy(e.ctx,ri))}}let l_=()=>!l;function lS(e,t,n){let r=e.type;if(!e.render){if(!t&&l&&!r.render){let t=r.template||rx(e).template;if(t){let{isCustomElement:n,compilerOptions:i}=e.appContext.config,{delimiters:s,compilerOptions:o}=r,a=g(g({isCustomElement:n,delimiters:s},i),o);r.render=l(t,a)}}e.render=r.render||p,s&&s(e)}{let t=lf(e);eI();try{!function(e){let t=rx(e),n=e.proxy,r=e.ctx;r_=!1,t.beforeCreate&&rS(t.beforeCreate,e,\"bc\");let{data:i,computed:l,methods:s,watch:o,provide:a,inject:c,created:u,beforeMount:d,mounted:h,beforeUpdate:f,updated:m,activated:g,deactivated:y,beforeDestroy:b,beforeUnmount:_,destroyed:x,unmounted:C,render:T,renderTracked:k,renderTriggered:A,errorCaptured:I,serverPrefetch:R,expose:O,inheritAttrs:M,components:L,directives:P,filters:$}=t;if(c&&function(e,t,n=p){for(let n in S(e)&&(e=rw(e)),e){let r;let i=e[n];tI(r=N(i)?\"default\"in i?rL(i.from||n,i.default,!0):rL(i.from||n):rL(i))?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>r.value,set:e=>r.value=e}):t[n]=r}}(c,r,null),s)for(let e in s){let t=s[e];w(t)&&(r[e]=t.bind(n))}if(i){let t=i.call(n,n);N(t)&&(e.data=tf(t))}if(r_=!0,l)for(let e in l){let t=l[e],i=w(t)?t.bind(n,n):w(t.get)?t.get.bind(n,n):p,s=lw({get:i,set:!w(t)&&w(t.set)?t.set.bind(n):p});Object.defineProperty(r,e,{enumerable:!0,configurable:!0,get:()=>s.value,set:e=>s.value=e})}if(o)for(let e in o)!function e(t,n,r,i){let l=i.includes(\".\")?ik(r,i):()=>r[i];if(E(t)){let e=n[t];w(e)&&ix(l,e)}else if(w(t))ix(l,t.bind(r));else if(N(t)){if(S(t))t.forEach(t=>e(t,n,r,i));else{let e=w(t.handler)?t.handler.bind(r):n[t.handler];w(e)&&ix(l,e,t)}}}(o[e],r,n,e);if(a){let e=w(a)?a.call(n):a;Reflect.ownKeys(e).forEach(t=>{rM(t,e[t])})}function F(e,t){S(t)?t.forEach(t=>e(t.bind(n))):t&&e(t.bind(n))}if(u&&rS(u,e,\"c\"),F(nH,d),F(nq,h),F(nW,f),F(nK,m),F(n$,g),F(nF,y),F(nZ,I),F(nQ,k),F(nX,A),F(nz,_),F(nG,C),F(nJ,R),S(O)){if(O.length){let t=e.exposed||(e.exposed={});O.forEach(e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t})})}else e.exposed||(e.exposed={})}T&&e.render===p&&(e.render=T),null!=M&&(e.inheritAttrs=M),L&&(e.components=L),P&&(e.directives=P)}(e)}finally{eR(),t()}}}let lx={get:(e,t)=>(eB(e,\"get\",\"\"),e[t])};function lC(e){return{attrs:new Proxy(e.attrs,lx),slots:e.slots,emit:e.emit,expose:t=>{e.exposed=t||{}}}}function lT(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(tD(tT(e.exposed)),{get:(t,n)=>n in t?t[n]:n in rt?rt[n](e):void 0,has:(e,t)=>t in e||t in rt})):e.proxy}function lk(e,t=!0){return w(e)?e.displayName||e.name:e.name||t&&e.__name}let lw=(e,t)=>(function(e,t,n=!1){let r,i;let l=w(e);return l?(r=e,i=p):(r=e.get,i=e.set),new tE(r,i,l||!i,n)})(e,0,ly);function lE(e,t,n){let r=arguments.length;return 2!==r?(r>3?n=Array.prototype.slice.call(arguments,2):3===r&&i3(n)&&(n=[n]),i7(e,t,n)):!N(t)||S(t)?i7(e,null,t):i3(t)?i7(e,null,[t]):i7(e,t)}function lA(){}function lN(e,t,n,r){let i=n[r];if(i&&lI(i,e))return i;let l=t();return l.memo=e.slice(),l.cacheIndex=r,n[r]=l}function lI(e,t){let n=e.memo;if(n.length!=t.length)return!1;for(let e=0;e<n.length;e++)if(W(n[e],t[e]))return!1;return iZ>0&&iJ&&iJ.push(e),!0}let lR=\"3.4.35\",lO=p,lM=null,lL=void 0,lP=p,l$=null,lF=null,lV=null,lD=null,lB=\"undefined\"!=typeof document?document:null,lU=lB&&lB.createElement(\"template\"),lj=\"transition\",lH=\"animation\",lq=Symbol(\"_vtc\"),lW=(e,{slots:t})=>lE(nx,lX(e),t);lW.displayName=\"Transition\";let lK={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},lz=lW.props=g({},n_,lK),lG=(e,t=[])=>{S(e)?e.forEach(e=>e(...t)):e&&e(...t)},lJ=e=>!!e&&(S(e)?e.some(e=>e.length>1):e.length>1);function lX(e){let t={};for(let n in e)n in lK||(t[n]=e[n]);if(!1===e.css)return t;let{name:n=\"v\",type:r,duration:i,enterFromClass:l=`${n}-enter-from`,enterActiveClass:s=`${n}-enter-active`,enterToClass:o=`${n}-enter-to`,appearFromClass:a=l,appearActiveClass:c=s,appearToClass:u=o,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:h=`${n}-leave-to`}=e,f=function(e){if(null==e)return null;if(N(e))return[J(e.enter),J(e.leave)];{let t=J(e);return[t,t]}}(i),m=f&&f[0],y=f&&f[1],{onBeforeEnter:b,onEnter:_,onEnterCancelled:S,onLeave:x,onLeaveCancelled:C,onBeforeAppear:T=b,onAppear:k=_,onAppearCancelled:w=S}=t,E=(e,t,n)=>{lZ(e,t?u:o),lZ(e,t?c:s),n&&n()},A=(e,t)=>{e._isLeaving=!1,lZ(e,d),lZ(e,h),lZ(e,p),t&&t()},I=e=>(t,n)=>{let i=e?k:_,s=()=>E(t,e,n);lG(i,[t,s]),lY(()=>{lZ(t,e?a:l),lQ(t,e?u:o),lJ(i)||l1(t,r,m,s)})};return g(t,{onBeforeEnter(e){lG(b,[e]),lQ(e,l),lQ(e,s)},onBeforeAppear(e){lG(T,[e]),lQ(e,a),lQ(e,c)},onEnter:I(!1),onAppear:I(!0),onLeave(e,t){e._isLeaving=!0;let n=()=>A(e,t);lQ(e,d),lQ(e,p),l4(),lY(()=>{e._isLeaving&&(lZ(e,d),lQ(e,h),lJ(x)||l1(e,r,y,n))}),lG(x,[e,n])},onEnterCancelled(e){E(e,!1),lG(S,[e])},onAppearCancelled(e){E(e,!0),lG(w,[e])},onLeaveCancelled(e){A(e),lG(C,[e])}})}function lQ(e,t){t.split(/\\s+/).forEach(t=>t&&e.classList.add(t)),(e[lq]||(e[lq]=new Set)).add(t)}function lZ(e,t){t.split(/\\s+/).forEach(t=>t&&e.classList.remove(t));let n=e[lq];n&&(n.delete(t),n.size||(e[lq]=void 0))}function lY(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let l0=0;function l1(e,t,n,r){let i=e._endId=++l0,l=()=>{i===e._endId&&r()};if(n)return setTimeout(l,n);let{type:s,timeout:o,propCount:a}=l2(e,t);if(!s)return r();let c=s+\"end\",u=0,d=()=>{e.removeEventListener(c,p),l()},p=t=>{t.target===e&&++u>=a&&d()};setTimeout(()=>{u<a&&d()},o+1),e.addEventListener(c,p)}function l2(e,t){let n=window.getComputedStyle(e),r=e=>(n[e]||\"\").split(\", \"),i=r(`${lj}Delay`),l=r(`${lj}Duration`),s=l3(i,l),o=r(`${lH}Delay`),a=r(`${lH}Duration`),c=l3(o,a),u=null,d=0,p=0;t===lj?s>0&&(u=lj,d=s,p=l.length):t===lH?c>0&&(u=lH,d=c,p=a.length):p=(u=(d=Math.max(s,c))>0?s>c?lj:lH:null)?u===lj?l.length:a.length:0;let h=u===lj&&/\\b(transform|all)(,|$)/.test(r(`${lj}Property`).toString());return{type:u,timeout:d,propCount:p,hasTransform:h}}function l3(e,t){for(;e.length<t.length;)e=e.concat(e);return Math.max(...t.map((t,n)=>l6(t)+l6(e[n])))}function l6(e){return\"auto\"===e?0:1e3*Number(e.slice(0,-1).replace(\",\",\".\"))}function l4(){return document.body.offsetHeight}let l8=Symbol(\"_vod\"),l5=Symbol(\"_vsh\"),l9={beforeMount(e,{value:t},{transition:n}){e[l8]=\"none\"===e.style.display?\"\":e.style.display,n&&t?n.beforeEnter(e):l7(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),l7(e,!0),r.enter(e)):r.leave(e,()=>{l7(e,!1)}):l7(e,t))},beforeUnmount(e,{value:t}){l7(e,t)}};function l7(e,t){e.style.display=t?e[l8]:\"none\",e[l5]=!t}let se=Symbol(\"\");function st(e){let t=lh();if(!t)return;let n=t.ut=(n=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner=\"${t.uid}\"]`)).forEach(e=>sn(e,n))},r=()=>{let r=e(t.proxy);(function e(t,n){if(128&t.shapeFlag){let r=t.suspense;t=r.activeBranch,r.pendingBranch&&!r.isHydrating&&r.effects.push(()=>{e(r.activeBranch,n)})}for(;t.component;)t=t.component.subTree;if(1&t.shapeFlag&&t.el)sn(t.el,n);else if(t.type===iq)t.children.forEach(t=>e(t,n));else if(t.type===iz){let{el:e,anchor:r}=t;for(;e&&(sn(e,n),e!==r);)e=e.nextSibling}})(t.subTree,r),n(r)};nq(()=>{ib(r);let e=new MutationObserver(r);e.observe(t.subTree.el.parentNode,{childList:!0}),nG(()=>e.disconnect())})}function sn(e,t){if(1===e.nodeType){let n=e.style,r=\"\";for(let e in t)n.setProperty(`--${e}`,t[e]),r+=`--${e}: ${t[e]};`;n[se]=r}}let sr=/(^|;)\\s*display\\s*:/,si=/\\s*!important$/;function sl(e,t,n){if(S(n))n.forEach(n=>sl(e,t,n));else if(null==n&&(n=\"\"),t.startsWith(\"--\"))e.setProperty(t,n);else{let r=function(e,t){let n=so[t];if(n)return n;let r=B(t);if(\"filter\"!==r&&r in e)return so[t]=r;r=H(r);for(let n=0;n<ss.length;n++){let i=ss[n]+r;if(i in e)return so[t]=i}return t}(e,t);si.test(n)?e.setProperty(j(r),n.replace(si,\"\"),\"important\"):e[r]=n}}let ss=[\"Webkit\",\"Moz\",\"ms\"],so={},sa=\"http://www.w3.org/1999/xlink\";function sc(e,t,n,r,i,l=ec(t)){r&&t.startsWith(\"xlink:\")?null==n?e.removeAttributeNS(sa,t.slice(6,t.length)):e.setAttributeNS(sa,t,n):null==n||l&&!(n||\"\"===n)?e.removeAttribute(t):e.setAttribute(t,l?\"\":A(n)?String(n):n)}function su(e,t,n,r){e.addEventListener(t,n,r)}let sd=Symbol(\"_vei\"),sp=/(?:Once|Passive|Capture)$/,sh=0,sf=Promise.resolve(),sm=()=>sh||(sf.then(()=>sh=0),sh=Date.now()),sg=e=>111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&e.charCodeAt(2)>96&&123>e.charCodeAt(2);/*! #__NO_SIDE_EFFECTS__ */function sy(e,t,n){let r=nN(e,t);class i extends s_{constructor(e){super(r,e,n)}}return i.def=r,i}/*! #__NO_SIDE_EFFECTS__ */let sv=(e,t)=>sy(e,t,s0),sb=\"undefined\"!=typeof HTMLElement?HTMLElement:class{};class s_ extends sb{constructor(e,t={},n){super(),this._def=e,this._props=t,this._instance=null,this._connected=!1,this._resolved=!1,this._numberProps=null,this._ob=null,this.shadowRoot&&n?n(this._createVNode(),this.shadowRoot):(this.attachShadow({mode:\"open\"}),this._def.__asyncLoader||this._resolveProps(this._def))}connectedCallback(){this._connected=!0,this._instance||(this._resolved?this._update():this._resolveDef())}disconnectedCallback(){this._connected=!1,t7(()=>{this._connected||(this._ob&&(this._ob.disconnect(),this._ob=null),sY(null,this.shadowRoot),this._instance=null)})}_resolveDef(){this._resolved=!0;for(let e=0;e<this.attributes.length;e++)this._setAttr(this.attributes[e].name);this._ob=new MutationObserver(e=>{for(let t of e)this._setAttr(t.attributeName)}),this._ob.observe(this,{attributes:!0});let e=(e,t=!1)=>{let n;let{props:r,styles:i}=e;if(r&&!S(r))for(let e in r){let t=r[e];(t===Number||t&&t.type===Number)&&(e in this._props&&(this._props[e]=J(this._props[e])),(n||(n=Object.create(null)))[B(e)]=!0)}this._numberProps=n,t&&this._resolveProps(e),this._applyStyles(i),this._update()},t=this._def.__asyncLoader;t?t().then(t=>e(t,!0)):e(this._def)}_resolveProps(e){let{props:t}=e,n=S(t)?t:Object.keys(t||{});for(let e of Object.keys(this))\"_\"!==e[0]&&n.includes(e)&&this._setProp(e,this[e],!0,!1);for(let e of n.map(B))Object.defineProperty(this,e,{get(){return this._getProp(e)},set(t){this._setProp(e,t)}})}_setAttr(e){let t=this.hasAttribute(e)?this.getAttribute(e):void 0,n=B(e);this._numberProps&&this._numberProps[n]&&(t=J(t)),this._setProp(n,t,!1)}_getProp(e){return this._props[e]}_setProp(e,t,n=!0,r=!0){t!==this._props[e]&&(this._props[e]=t,r&&this._instance&&this._update(),n&&(!0===t?this.setAttribute(j(e),\"\"):\"string\"==typeof t||\"number\"==typeof t?this.setAttribute(j(e),t+\"\"):t||this.removeAttribute(j(e))))}_update(){sY(this._createVNode(),this.shadowRoot)}_createVNode(){let e=i7(this._def,g({},this._props));return this._instance||(e.ce=e=>{this._instance=e,e.isCE=!0;let t=(e,t)=>{this.dispatchEvent(new CustomEvent(e,{detail:t}))};e.emit=(e,...n)=>{t(e,n),j(e)!==e&&t(j(e),n)};let n=this;for(;n=n&&(n.parentNode||n.host);)if(n instanceof s_){e.parent=n._instance,e.provides=n._instance.provides;break}}),e}_applyStyles(e){e&&e.forEach(e=>{let t=document.createElement(\"style\");t.textContent=e,this.shadowRoot.appendChild(t)})}}function sS(e=\"$style\"){{let t=lh();if(!t)return u;let n=t.type.__cssModules;return n&&n[e]||u}}let sx=new WeakMap,sC=new WeakMap,sT=Symbol(\"_moveCb\"),sk=Symbol(\"_enterCb\"),sw={name:\"TransitionGroup\",props:g({},lz,{tag:String,moveClass:String}),setup(e,{slots:t}){let n,r;let i=lh(),l=nv();return nK(()=>{if(!n.length)return;let t=e.moveClass||`${e.name||\"v\"}-move`;if(!function(e,t,n){let r=e.cloneNode(),i=e[lq];i&&i.forEach(e=>{e.split(/\\s+/).forEach(e=>e&&r.classList.remove(e))}),n.split(/\\s+/).forEach(e=>e&&r.classList.add(e)),r.style.display=\"none\";let l=1===t.nodeType?t:t.parentNode;l.appendChild(r);let{hasTransform:s}=l2(r);return l.removeChild(r),s}(n[0].el,i.vnode.el,t))return;n.forEach(sA),n.forEach(sN);let r=n.filter(sI);l4(),r.forEach(e=>{let n=e.el,r=n.style;lQ(n,t),r.transform=r.webkitTransform=r.transitionDuration=\"\";let i=n[sT]=e=>{(!e||e.target===n)&&(!e||/transform$/.test(e.propertyName))&&(n.removeEventListener(\"transitionend\",i),n[sT]=null,lZ(n,t))};n.addEventListener(\"transitionend\",i)})}),()=>{let s=tC(e),o=lX(s),a=s.tag||iq;if(n=[],r)for(let e=0;e<r.length;e++){let t=r[e];t.el&&t.el instanceof Element&&(n.push(t),nE(t,nT(t,o,l,i)),sx.set(t,t.el.getBoundingClientRect()))}r=t.default?nA(t.default()):[];for(let e=0;e<r.length;e++){let t=r[e];null!=t.key&&nE(t,nT(t,o,l,i))}return i7(a,null,r)}}};sw.props;let sE=sw;function sA(e){let t=e.el;t[sT]&&t[sT](),t[sk]&&t[sk]()}function sN(e){sC.set(e,e.el.getBoundingClientRect())}function sI(e){let t=sx.get(e),n=sC.get(e),r=t.left-n.left,i=t.top-n.top;if(r||i){let t=e.el.style;return t.transform=t.webkitTransform=`translate(${r}px,${i}px)`,t.transitionDuration=\"0s\",e}}let sR=e=>{let t=e.props[\"onUpdate:modelValue\"]||!1;return S(t)?e=>K(t,e):t};function sO(e){e.target.composing=!0}function sM(e){let t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event(\"input\")))}let sL=Symbol(\"_assign\"),sP={created(e,{modifiers:{lazy:t,trim:n,number:r}},i){e[sL]=sR(i);let l=r||i.props&&\"number\"===i.props.type;su(e,t?\"change\":\"input\",t=>{if(t.target.composing)return;let r=e.value;n&&(r=r.trim()),l&&(r=G(r)),e[sL](r)}),n&&su(e,\"change\",()=>{e.value=e.value.trim()}),t||(su(e,\"compositionstart\",sO),su(e,\"compositionend\",sM),su(e,\"change\",sM))},mounted(e,{value:t}){e.value=null==t?\"\":t},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:i,number:l}},s){if(e[sL]=sR(s),e.composing)return;let o=(l||\"number\"===e.type)&&!/^0\\d/.test(e.value)?G(e.value):e.value,a=null==t?\"\":t;o===a||document.activeElement===e&&\"range\"!==e.type&&(r&&t===n||i&&e.value.trim()===a)||(e.value=a)}},s$={deep:!0,created(e,t,n){e[sL]=sR(n),su(e,\"change\",()=>{let t=e._modelValue,n=sU(e),r=e.checked,i=e[sL];if(S(t)){let e=ed(t,n),l=-1!==e;if(r&&!l)i(t.concat(n));else if(!r&&l){let n=[...t];n.splice(e,1),i(n)}}else if(C(t)){let e=new Set(t);r?e.add(n):e.delete(n),i(e)}else i(sj(e,r))})},mounted:sF,beforeUpdate(e,t,n){e[sL]=sR(n),sF(e,t,n)}};function sF(e,{value:t,oldValue:n},r){e._modelValue=t,S(t)?e.checked=ed(t,r.props.value)>-1:C(t)?e.checked=t.has(r.props.value):t!==n&&(e.checked=eu(t,sj(e,!0)))}let sV={created(e,{value:t},n){e.checked=eu(t,n.props.value),e[sL]=sR(n),su(e,\"change\",()=>{e[sL](sU(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[sL]=sR(r),t!==n&&(e.checked=eu(t,r.props.value))}},sD={deep:!0,created(e,{value:t,modifiers:{number:n}},r){let i=C(t);su(e,\"change\",()=>{let t=Array.prototype.filter.call(e.options,e=>e.selected).map(e=>n?G(sU(e)):sU(e));e[sL](e.multiple?i?new Set(t):t:t[0]),e._assigning=!0,t7(()=>{e._assigning=!1})}),e[sL]=sR(r)},mounted(e,{value:t}){sB(e,t)},beforeUpdate(e,t,n){e[sL]=sR(n)},updated(e,{value:t}){e._assigning||sB(e,t)}};function sB(e,t,n){let r=e.multiple,i=S(t);if(!r||i||C(t)){for(let n=0,l=e.options.length;n<l;n++){let l=e.options[n],s=sU(l);if(r){if(i){let e=typeof s;\"string\"===e||\"number\"===e?l.selected=t.some(e=>String(e)===String(s)):l.selected=ed(t,s)>-1}else l.selected=t.has(s)}else if(eu(sU(l),t)){e.selectedIndex!==n&&(e.selectedIndex=n);return}}r||-1===e.selectedIndex||(e.selectedIndex=-1)}}function sU(e){return\"_value\"in e?e._value:e.value}function sj(e,t){let n=t?\"_trueValue\":\"_falseValue\";return n in e?e[n]:t}let sH={created(e,t,n){sq(e,t,n,null,\"created\")},mounted(e,t,n){sq(e,t,n,null,\"mounted\")},beforeUpdate(e,t,n,r){sq(e,t,n,r,\"beforeUpdate\")},updated(e,t,n,r){sq(e,t,n,r,\"updated\")}};function sq(e,t,n,r,i){let l=function(e,t){switch(e){case\"SELECT\":return sD;case\"TEXTAREA\":return sP;default:switch(t){case\"checkbox\":return s$;case\"radio\":return sV;default:return sP}}}(e.tagName,n.props&&n.props.type)[i];l&&l(e,t,n,r)}let sW=[\"ctrl\",\"shift\",\"alt\",\"meta\"],sK={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>\"button\"in e&&0!==e.button,middle:e=>\"button\"in e&&1!==e.button,right:e=>\"button\"in e&&2!==e.button,exact:(e,t)=>sW.some(n=>e[`${n}Key`]&&!t.includes(n))},sz=(e,t)=>{let n=e._withMods||(e._withMods={}),r=t.join(\".\");return n[r]||(n[r]=(n,...r)=>{for(let e=0;e<t.length;e++){let r=sK[t[e]];if(r&&r(n,t))return}return e(n,...r)})},sG={esc:\"escape\",space:\" \",up:\"arrow-up\",left:\"arrow-left\",right:\"arrow-right\",down:\"arrow-down\",delete:\"backspace\"},sJ=(e,t)=>{let n=e._withKeys||(e._withKeys={}),r=t.join(\".\");return n[r]||(n[r]=n=>{if(!(\"key\"in n))return;let r=j(n.key);if(t.some(e=>e===r||sG[e]===r))return e(n)})},sX=g({patchProp:(e,t,n,r,i,l)=>{let s=\"svg\"===i;\"class\"===t?function(e,t,n){let r=e[lq];r&&(t=(t?[t,...r]:[...r]).join(\" \")),null==t?e.removeAttribute(\"class\"):n?e.setAttribute(\"class\",t):e.className=t}(e,r,s):\"style\"===t?function(e,t,n){let r=e.style,i=E(n),l=!1;if(n&&!i){if(t){if(E(t))for(let e of t.split(\";\")){let t=e.slice(0,e.indexOf(\":\")).trim();null==n[t]&&sl(r,t,\"\")}else for(let e in t)null==n[e]&&sl(r,e,\"\")}for(let e in n)\"display\"===e&&(l=!0),sl(r,e,n[e])}else if(i){if(t!==n){let e=r[se];e&&(n+=\";\"+e),r.cssText=n,l=sr.test(n)}}else t&&e.removeAttribute(\"style\");l8 in e&&(e[l8]=l?r.display:\"\",e[l5]&&(r.display=\"none\"))}(e,n,r):f(t)?m(t)||function(e,t,n,r,i=null){let l=e[sd]||(e[sd]={}),s=l[t];if(r&&s)s.value=r;else{let[n,o]=function(e){let t;if(sp.test(e)){let n;for(t={};n=e.match(sp);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[\":\"===e[2]?e.slice(3):j(e.slice(2)),t]}(t);r?su(e,n,l[t]=function(e,t){let n=e=>{if(e._vts){if(e._vts<=n.attached)return}else e._vts=Date.now();tZ(function(e,t){if(!S(t))return t;{let n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(e=>t=>!t._stopped&&e&&e(t))}}(e,n.value),t,5,[e])};return n.value=e,n.attached=sm(),n}(r,i),o):s&&(!function(e,t,n,r){e.removeEventListener(t,n,r)}(e,n,s,o),l[t]=void 0)}}(e,t,0,r,l):(\".\"===t[0]?(t=t.slice(1),0):\"^\"===t[0]?(t=t.slice(1),1):!function(e,t,n,r){if(r)return!!(\"innerHTML\"===t||\"textContent\"===t||t in e&&sg(t)&&w(n));if(\"spellcheck\"===t||\"draggable\"===t||\"translate\"===t||\"form\"===t||\"list\"===t&&\"INPUT\"===e.tagName||\"type\"===t&&\"TEXTAREA\"===e.tagName)return!1;if(\"width\"===t||\"height\"===t){let t=e.tagName;if(\"IMG\"===t||\"VIDEO\"===t||\"CANVAS\"===t||\"SOURCE\"===t)return!1}return!(sg(t)&&E(n))&&t in e}(e,t,r,s))?(\"true-value\"===t?e._trueValue=r:\"false-value\"===t&&(e._falseValue=r),sc(e,t,r,s)):(!function(e,t,n,r){if(\"innerHTML\"===t||\"textContent\"===t){if(null==n)return;e[t]=n;return}let i=e.tagName;if(\"value\"===t&&\"PROGRESS\"!==i&&!i.includes(\"-\")){let r=\"OPTION\"===i?e.getAttribute(\"value\")||\"\":e.value,l=null==n?\"\":String(n);r===l&&\"_value\"in e||(e.value=l),null==n&&e.removeAttribute(t),e._value=n;return}let l=!1;if(\"\"===n||null==n){let r=typeof e[t];if(\"boolean\"===r){var s;n=!!(s=n)||\"\"===s}else null==n&&\"string\"===r?(n=\"\",l=!0):\"number\"===r&&(n=0,l=!0)}try{e[t]=n}catch(e){}l&&e.removeAttribute(t)}(e,t,r),e.tagName.includes(\"-\")||\"value\"!==t&&\"checked\"!==t&&\"selected\"!==t||sc(e,t,r,s,l,\"value\"!==t))}},{insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{let t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{let i=\"svg\"===t?lB.createElementNS(\"http://www.w3.org/2000/svg\",e):\"mathml\"===t?lB.createElementNS(\"http://www.w3.org/1998/Math/MathML\",e):n?lB.createElement(e,{is:n}):lB.createElement(e);return\"select\"===e&&r&&null!=r.multiple&&i.setAttribute(\"multiple\",r.multiple),i},createText:e=>lB.createTextNode(e),createComment:e=>lB.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>lB.querySelector(e),setScopeId(e,t){e.setAttribute(t,\"\")},insertStaticContent(e,t,n,r,i,l){let s=n?n.previousSibling:t.lastChild;if(i&&(i===l||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),i!==l&&(i=i.nextSibling););else{lU.innerHTML=\"svg\"===r?`<svg>${e}</svg>`:\"mathml\"===r?`<math>${e}</math>`:e;let i=lU.content;if(\"svg\"===r||\"mathml\"===r){let e=i.firstChild;for(;e.firstChild;)i.appendChild(e.firstChild);i.removeChild(e)}t.insertBefore(i,n)}return[s?s.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}}),sQ=!1;function sZ(){return o=sQ?o:ia(sX),sQ=!0,o}let sY=(...e)=>{(o||(o=io(sX))).render(...e)},s0=(...e)=>{sZ().hydrate(...e)},s1=(...e)=>{let t=(o||(o=io(sX))).createApp(...e),{mount:n}=t;return t.mount=e=>{let r=s6(e);if(!r)return;let i=t._component;w(i)||i.render||i.template||(i.template=r.innerHTML),r.innerHTML=\"\";let l=n(r,!1,s3(r));return r instanceof Element&&(r.removeAttribute(\"v-cloak\"),r.setAttribute(\"data-v-app\",\"\")),l},t},s2=(...e)=>{let t=sZ().createApp(...e),{mount:n}=t;return t.mount=e=>{let t=s6(e);if(t)return n(t,!0,s3(t))},t};function s3(e){return e instanceof SVGElement?\"svg\":\"function\"==typeof MathMLElement&&e instanceof MathMLElement?\"mathml\":void 0}function s6(e){return E(e)?document.querySelector(e):e}let s4=p;var s8=Object.freeze({__proto__:null,BaseTransition:nx,BaseTransitionPropsValidators:n_,Comment:iK,DeprecationTypes:lD,EffectScope:eg,ErrorCodes:tX,ErrorTypeStrings:lM,Fragment:iq,KeepAlive:nL,ReactiveEffect:eS,Static:iz,Suspense:iV,Teleport:r4,Text:iW,TrackOpTypes:tz,Transition:lW,TransitionGroup:sE,TriggerOpTypes:tG,VueElement:s_,assertNumber:tJ,callWithAsyncErrorHandling:tZ,callWithErrorHandling:tQ,camelize:B,capitalize:H,cloneVNode:lt,compatUtils:lV,computed:lw,createApp:s1,createBlock:i2,createCommentVNode:li,createElementBlock:i1,createElementVNode:i9,createHydrationRenderer:ia,createPropsRestProxy:rv,createRenderer:io,createSSRApp:s2,createSlots:n5,createStaticVNode:lr,createTextVNode:ln,createVNode:i7,customRef:tU,defineAsyncComponent:nR,defineComponent:nN,defineCustomElement:sy,defineEmits:rs,defineExpose:ro,defineModel:ru,defineOptions:ra,defineProps:rl,defineSSRCustomElement:sv,defineSlots:rc,devtools:lL,effect:ek,effectScope:ey,getCurrentInstance:lh,getCurrentScope:eb,getTransitionRawChildren:nA,guardReactiveProps:le,h:lE,handleError:tY,hasInjectionContext:rP,hydrate:s0,initCustomFormatter:lA,initDirectivesForSSR:s4,inject:rL,isMemoSame:lI,isProxy:tx,isReactive:tb,isReadonly:t_,isRef:tI,isRuntimeOnly:l_,isShallow:tS,isVNode:i3,markRaw:tT,mergeDefaults:rg,mergeModels:ry,mergeProps:la,nextTick:t7,normalizeClass:er,normalizeProps:ei,normalizeStyle:Z,onActivated:n$,onBeforeMount:nH,onBeforeUnmount:nz,onBeforeUpdate:nW,onDeactivated:nF,onErrorCaptured:nZ,onMounted:nq,onRenderTracked:nQ,onRenderTriggered:nX,onScopeDispose:e_,onServerPrefetch:nJ,onUnmounted:nG,onUpdated:nK,openBlock:iX,popScopeId:nd,provide:rM,proxyRefs:tD,pushScopeId:nu,queuePostFlushCb:nn,reactive:tf,readonly:tg,ref:tR,registerRuntimeCompiler:lb,render:sY,renderList:n8,renderSlot:n9,resolveComponent:n0,resolveDirective:n3,resolveDynamicComponent:n2,resolveFilter:lF,resolveTransitionHooks:nT,setBlockTracking:iY,setDevtoolsHook:lP,setTransitionHooks:nE,shallowReactive:tm,shallowReadonly:ty,shallowRef:tO,ssrContextKey:ig,ssrUtils:l$,stop:ew,toDisplayString:eh,toHandlerKey:q,toHandlers:n7,toRaw:tC,toRef:tW,toRefs:tj,toValue:tF,transformVNodeArgs:i4,triggerRef:tP,unref:t$,useAttrs:rh,useCssModule:sS,useCssVars:st,useModel:iE,useSSRContext:iy,useSlots:rp,useTransitionState:nv,vModelCheckbox:s$,vModelDynamic:sH,vModelRadio:sV,vModelSelect:sD,vModelText:sP,vShow:l9,version:lR,warn:lO,watch:ix,watchEffect:iv,watchPostEffect:ib,watchSyncEffect:i_,withAsyncContext:rb,withCtx:nh,withDefaults:rd,withDirectives:nf,withKeys:sJ,withMemo:lN,withModifiers:sz,withScopeId:np});let s5=Symbol(\"\"),s9=Symbol(\"\"),s7=Symbol(\"\"),oe=Symbol(\"\"),ot=Symbol(\"\"),on=Symbol(\"\"),or=Symbol(\"\"),oi=Symbol(\"\"),ol=Symbol(\"\"),os=Symbol(\"\"),oo=Symbol(\"\"),oa=Symbol(\"\"),oc=Symbol(\"\"),ou=Symbol(\"\"),od=Symbol(\"\"),op=Symbol(\"\"),oh=Symbol(\"\"),of=Symbol(\"\"),om=Symbol(\"\"),og=Symbol(\"\"),oy=Symbol(\"\"),ov=Symbol(\"\"),ob=Symbol(\"\"),o_=Symbol(\"\"),oS=Symbol(\"\"),ox=Symbol(\"\"),oC=Symbol(\"\"),oT=Symbol(\"\"),ok=Symbol(\"\"),ow=Symbol(\"\"),oE=Symbol(\"\"),oA=Symbol(\"\"),oN=Symbol(\"\"),oI=Symbol(\"\"),oR=Symbol(\"\"),oO=Symbol(\"\"),oM=Symbol(\"\"),oL=Symbol(\"\"),oP=Symbol(\"\"),o$={[s5]:\"Fragment\",[s9]:\"Teleport\",[s7]:\"Suspense\",[oe]:\"KeepAlive\",[ot]:\"BaseTransition\",[on]:\"openBlock\",[or]:\"createBlock\",[oi]:\"createElementBlock\",[ol]:\"createVNode\",[os]:\"createElementVNode\",[oo]:\"createCommentVNode\",[oa]:\"createTextVNode\",[oc]:\"createStaticVNode\",[ou]:\"resolveComponent\",[od]:\"resolveDynamicComponent\",[op]:\"resolveDirective\",[oh]:\"resolveFilter\",[of]:\"withDirectives\",[om]:\"renderList\",[og]:\"renderSlot\",[oy]:\"createSlots\",[ov]:\"toDisplayString\",[ob]:\"mergeProps\",[o_]:\"normalizeClass\",[oS]:\"normalizeStyle\",[ox]:\"normalizeProps\",[oC]:\"guardReactiveProps\",[oT]:\"toHandlers\",[ok]:\"camelize\",[ow]:\"capitalize\",[oE]:\"toHandlerKey\",[oA]:\"setBlockTracking\",[oN]:\"pushScopeId\",[oI]:\"popScopeId\",[oR]:\"withCtx\",[oO]:\"unref\",[oM]:\"isRef\",[oL]:\"withMemo\",[oP]:\"isMemoSame\"},oF={start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0},source:\"\"};function oV(e,t,n,r,i,l,s,o=!1,a=!1,c=!1,u=oF){return e&&(o?(e.helper(on),e.helper(e.inSSR||c?or:oi)):e.helper(e.inSSR||c?ol:os),s&&e.helper(of)),{type:13,tag:t,props:n,children:r,patchFlag:i,dynamicProps:l,directives:s,isBlock:o,disableTracking:a,isComponent:c,loc:u}}function oD(e,t=oF){return{type:17,loc:t,elements:e}}function oB(e,t=oF){return{type:15,loc:t,properties:e}}function oU(e,t){return{type:16,loc:oF,key:E(e)?oj(e,!0):e,value:t}}function oj(e,t=!1,n=oF,r=0){return{type:4,loc:n,content:e,isStatic:t,constType:t?3:r}}function oH(e,t=oF){return{type:8,loc:t,children:e}}function oq(e,t=[],n=oF){return{type:14,loc:n,callee:e,arguments:t}}function oW(e,t,n=!1,r=!1,i=oF){return{type:18,params:e,returns:t,newline:n,isSlot:r,loc:i}}function oK(e,t,n,r=!0){return{type:19,test:e,consequent:t,alternate:n,newline:r,loc:oF}}function oz(e,{helper:t,removeHelper:n,inSSR:r}){if(!e.isBlock){var i,l;e.isBlock=!0,n((i=e.isComponent,r||i?ol:os)),t(on),t((l=e.isComponent,r||l?or:oi))}}let oG=new Uint8Array([123,123]),oJ=new Uint8Array([125,125]);function oX(e){return e>=97&&e<=122||e>=65&&e<=90}function oQ(e){return 32===e||10===e||9===e||12===e||13===e}function oZ(e){return 47===e||62===e||oQ(e)}function oY(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);return t}let o0={Cdata:new Uint8Array([67,68,65,84,65,91]),CdataEnd:new Uint8Array([93,93,62]),CommentEnd:new Uint8Array([45,45,62]),ScriptEnd:new Uint8Array([60,47,115,99,114,105,112,116]),StyleEnd:new Uint8Array([60,47,115,116,121,108,101]),TitleEnd:new Uint8Array([60,47,116,105,116,108,101]),TextareaEnd:new Uint8Array([60,47,116,101,120,116,97,114,101,97])};function o1(e){throw e}function o2(e){}function o3(e,t,n,r){let i=SyntaxError(String(`https://vuejs.org/error-reference/#compiler-${e}`));return i.code=e,i.loc=t,i}let o6=e=>4===e.type&&e.isStatic;function o4(e){switch(e){case\"Teleport\":case\"teleport\":return s9;case\"Suspense\":case\"suspense\":return s7;case\"KeepAlive\":case\"keep-alive\":return oe;case\"BaseTransition\":case\"base-transition\":return ot}}let o8=/^\\d|[^\\$\\w\\xA0-\\uFFFF]/,o5=e=>!o8.test(e),o9=/[A-Za-z_$\\xA0-\\uFFFF]/,o7=/[\\.\\?\\w$\\xA0-\\uFFFF]/,ae=/\\s+[.[]\\s*|\\s*[.[]\\s+/g,at=e=>{e=e.trim().replace(ae,e=>e.trim());let t=0,n=[],r=0,i=0,l=null;for(let s=0;s<e.length;s++){let o=e.charAt(s);switch(t){case 0:if(\"[\"===o)n.push(t),t=1,r++;else if(\"(\"===o)n.push(t),t=2,i++;else if(!(0===s?o9:o7).test(o))return!1;break;case 1:\"'\"===o||'\"'===o||\"`\"===o?(n.push(t),t=3,l=o):\"[\"===o?r++:\"]\"!==o||--r||(t=n.pop());break;case 2:if(\"'\"===o||'\"'===o||\"`\"===o)n.push(t),t=3,l=o;else if(\"(\"===o)i++;else if(\")\"===o){if(s===e.length-1)return!1;--i||(t=n.pop())}break;case 3:o===l&&(t=n.pop(),l=null)}}return!r&&!i};function an(e,t,n=!1){for(let r=0;r<e.props.length;r++){let i=e.props[r];if(7===i.type&&(n||i.exp)&&(E(t)?i.name===t:t.test(i.name)))return i}}function ar(e,t,n=!1,r=!1){for(let i=0;i<e.props.length;i++){let l=e.props[i];if(6===l.type){if(n)continue;if(l.name===t&&(l.value||r))return l}else if(\"bind\"===l.name&&(l.exp||r)&&ai(l.arg,t))return l}}function ai(e,t){return!!(e&&o6(e)&&e.content===t)}function al(e){return 5===e.type||2===e.type}function as(e){return 7===e.type&&\"slot\"===e.name}function ao(e){return 1===e.type&&3===e.tagType}function aa(e){return 1===e.type&&2===e.tagType}let ac=new Set([ox,oC]);function au(e,t,n){let r,i;let l=13===e.type?e.props:e.arguments[2],s=[];if(l&&!E(l)&&14===l.type){let e=function e(t,n=[]){if(t&&!E(t)&&14===t.type){let r=t.callee;if(!E(r)&&ac.has(r))return e(t.arguments[0],n.concat(t))}return[t,n]}(l);l=e[0],i=(s=e[1])[s.length-1]}if(null==l||E(l))r=oB([t]);else if(14===l.type){let e=l.arguments[0];E(e)||15!==e.type?l.callee===oT?r=oq(n.helper(ob),[oB([t]),l]):l.arguments.unshift(oB([t])):ad(t,e)||e.properties.unshift(t),r||(r=l)}else 15===l.type?(ad(t,l)||l.properties.unshift(t),r=l):(r=oq(n.helper(ob),[oB([t]),l]),i&&i.callee===oC&&(i=s[s.length-2]));13===e.type?i?i.arguments[0]=r:e.props=r:i?i.arguments[0]=r:e.arguments[2]=r}function ad(e,t){let n=!1;if(4===e.key.type){let r=e.key.content;n=t.properties.some(e=>4===e.key.type&&e.key.content===r)}return n}function ap(e,t){return`_${t}_${e.replace(/[^\\w]/g,(t,n)=>\"-\"===t?\"_\":e.charCodeAt(n).toString())}`}let ah=/([\\s\\S]*?)\\s+(?:in|of)\\s+(\\S[\\s\\S]*)/,af={parseMode:\"base\",ns:0,delimiters:[\"{{\",\"}}\"],getNamespace:()=>0,isVoidTag:h,isPreTag:h,isCustomElement:h,onError:o1,onWarn:o2,comments:!1,prefixIdentifiers:!1},am=af,ag=null,ay=\"\",av=null,ab=null,a_=\"\",aS=-1,ax=-1,aC=0,aT=!1,ak=null,aw=[],aE=new class{constructor(e,t){this.stack=e,this.cbs=t,this.state=1,this.buffer=\"\",this.sectionStart=0,this.index=0,this.entityStart=0,this.baseState=1,this.inRCDATA=!1,this.inXML=!1,this.inVPre=!1,this.newlines=[],this.mode=0,this.delimiterOpen=oG,this.delimiterClose=oJ,this.delimiterIndex=-1,this.currentSequence=void 0,this.sequenceIndex=0}get inSFCRoot(){return 2===this.mode&&0===this.stack.length}reset(){this.state=1,this.mode=0,this.buffer=\"\",this.sectionStart=0,this.index=0,this.baseState=1,this.inRCDATA=!1,this.currentSequence=void 0,this.newlines.length=0,this.delimiterOpen=oG,this.delimiterClose=oJ}getPos(e){let t=1,n=e+1;for(let r=this.newlines.length-1;r>=0;r--){let i=this.newlines[r];if(e>i){t=r+2,n=e-i;break}}return{column:n,line:t,offset:e}}peek(){return this.buffer.charCodeAt(this.index+1)}stateText(e){60===e?(this.index>this.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=5,this.sectionStart=this.index):this.inVPre||e!==this.delimiterOpen[0]||(this.state=2,this.delimiterIndex=0,this.stateInterpolationOpen(e))}stateInterpolationOpen(e){if(e===this.delimiterOpen[this.delimiterIndex]){if(this.delimiterIndex===this.delimiterOpen.length-1){let e=this.index+1-this.delimiterOpen.length;e>this.sectionStart&&this.cbs.ontext(this.sectionStart,e),this.state=3,this.sectionStart=e}else this.delimiterIndex++}else this.inRCDATA?(this.state=32,this.stateInRCDATA(e)):(this.state=1,this.stateText(e))}stateInterpolation(e){e===this.delimiterClose[0]&&(this.state=4,this.delimiterIndex=0,this.stateInterpolationClose(e))}stateInterpolationClose(e){e===this.delimiterClose[this.delimiterIndex]?this.delimiterIndex===this.delimiterClose.length-1?(this.cbs.oninterpolation(this.sectionStart,this.index+1),this.inRCDATA?this.state=32:this.state=1,this.sectionStart=this.index+1):this.delimiterIndex++:(this.state=3,this.stateInterpolation(e))}stateSpecialStartSequence(e){let t=this.sequenceIndex===this.currentSequence.length;if(t?oZ(e):(32|e)===this.currentSequence[this.sequenceIndex]){if(!t){this.sequenceIndex++;return}}else this.inRCDATA=!1;this.sequenceIndex=0,this.state=6,this.stateInTagName(e)}stateInRCDATA(e){if(this.sequenceIndex===this.currentSequence.length){if(62===e||oQ(e)){let t=this.index-this.currentSequence.length;if(this.sectionStart<t){let e=this.index;this.index=t,this.cbs.ontext(this.sectionStart,t),this.index=e}this.sectionStart=t+2,this.stateInClosingTagName(e),this.inRCDATA=!1;return}this.sequenceIndex=0}(32|e)===this.currentSequence[this.sequenceIndex]?this.sequenceIndex+=1:0===this.sequenceIndex?this.currentSequence!==o0.TitleEnd&&(this.currentSequence!==o0.TextareaEnd||this.inSFCRoot)?this.fastForwardTo(60)&&(this.sequenceIndex=1):e===this.delimiterOpen[0]&&(this.state=2,this.delimiterIndex=0,this.stateInterpolationOpen(e)):this.sequenceIndex=Number(60===e)}stateCDATASequence(e){e===o0.Cdata[this.sequenceIndex]?++this.sequenceIndex===o0.Cdata.length&&(this.state=28,this.currentSequence=o0.CdataEnd,this.sequenceIndex=0,this.sectionStart=this.index+1):(this.sequenceIndex=0,this.state=23,this.stateInDeclaration(e))}fastForwardTo(e){for(;++this.index<this.buffer.length;){let t=this.buffer.charCodeAt(this.index);if(10===t&&this.newlines.push(this.index),t===e)return!0}return this.index=this.buffer.length-1,!1}stateInCommentLike(e){e===this.currentSequence[this.sequenceIndex]?++this.sequenceIndex===this.currentSequence.length&&(this.currentSequence===o0.CdataEnd?this.cbs.oncdata(this.sectionStart,this.index-2):this.cbs.oncomment(this.sectionStart,this.index-2),this.sequenceIndex=0,this.sectionStart=this.index+1,this.state=1):0===this.sequenceIndex?this.fastForwardTo(this.currentSequence[0])&&(this.sequenceIndex=1):e!==this.currentSequence[this.sequenceIndex-1]&&(this.sequenceIndex=0)}startSpecial(e,t){this.enterRCDATA(e,t),this.state=31}enterRCDATA(e,t){this.inRCDATA=!0,this.currentSequence=e,this.sequenceIndex=t}stateBeforeTagName(e){33===e?(this.state=22,this.sectionStart=this.index+1):63===e?(this.state=24,this.sectionStart=this.index+1):oX(e)?(this.sectionStart=this.index,0===this.mode?this.state=6:this.inSFCRoot?this.state=34:this.inXML?this.state=6:116===e?this.state=30:this.state=115===e?29:6):47===e?this.state=8:(this.state=1,this.stateText(e))}stateInTagName(e){oZ(e)&&this.handleTagName(e)}stateInSFCRootTagName(e){if(oZ(e)){let t=this.buffer.slice(this.sectionStart,this.index);\"template\"!==t&&this.enterRCDATA(oY(\"</\"+t),0),this.handleTagName(e)}}handleTagName(e){this.cbs.onopentagname(this.sectionStart,this.index),this.sectionStart=-1,this.state=11,this.stateBeforeAttrName(e)}stateBeforeClosingTagName(e){oQ(e)||(62===e?(this.state=1,this.sectionStart=this.index+1):(this.state=oX(e)?9:27,this.sectionStart=this.index))}stateInClosingTagName(e){(62===e||oQ(e))&&(this.cbs.onclosetag(this.sectionStart,this.index),this.sectionStart=-1,this.state=10,this.stateAfterClosingTagName(e))}stateAfterClosingTagName(e){62===e&&(this.state=1,this.sectionStart=this.index+1)}stateBeforeAttrName(e){62===e?(this.cbs.onopentagend(this.index),this.inRCDATA?this.state=32:this.state=1,this.sectionStart=this.index+1):47===e?this.state=7:60===e&&47===this.peek()?(this.cbs.onopentagend(this.index),this.state=5,this.sectionStart=this.index):oQ(e)||this.handleAttrStart(e)}handleAttrStart(e){118===e&&45===this.peek()?(this.state=13,this.sectionStart=this.index):46===e||58===e||64===e||35===e?(this.cbs.ondirname(this.index,this.index+1),this.state=14,this.sectionStart=this.index+1):(this.state=12,this.sectionStart=this.index)}stateInSelfClosingTag(e){62===e?(this.cbs.onselfclosingtag(this.index),this.state=1,this.sectionStart=this.index+1,this.inRCDATA=!1):oQ(e)||(this.state=11,this.stateBeforeAttrName(e))}stateInAttrName(e){(61===e||oZ(e))&&(this.cbs.onattribname(this.sectionStart,this.index),this.handleAttrNameEnd(e))}stateInDirName(e){61===e||oZ(e)?(this.cbs.ondirname(this.sectionStart,this.index),this.handleAttrNameEnd(e)):58===e?(this.cbs.ondirname(this.sectionStart,this.index),this.state=14,this.sectionStart=this.index+1):46===e&&(this.cbs.ondirname(this.sectionStart,this.index),this.state=16,this.sectionStart=this.index+1)}stateInDirArg(e){61===e||oZ(e)?(this.cbs.ondirarg(this.sectionStart,this.index),this.handleAttrNameEnd(e)):91===e?this.state=15:46===e&&(this.cbs.ondirarg(this.sectionStart,this.index),this.state=16,this.sectionStart=this.index+1)}stateInDynamicDirArg(e){93===e?this.state=14:(61===e||oZ(e))&&(this.cbs.ondirarg(this.sectionStart,this.index+1),this.handleAttrNameEnd(e))}stateInDirModifier(e){61===e||oZ(e)?(this.cbs.ondirmodifier(this.sectionStart,this.index),this.handleAttrNameEnd(e)):46===e&&(this.cbs.ondirmodifier(this.sectionStart,this.index),this.sectionStart=this.index+1)}handleAttrNameEnd(e){this.sectionStart=this.index,this.state=17,this.cbs.onattribnameend(this.index),this.stateAfterAttrName(e)}stateAfterAttrName(e){61===e?this.state=18:47===e||62===e?(this.cbs.onattribend(0,this.sectionStart),this.sectionStart=-1,this.state=11,this.stateBeforeAttrName(e)):oQ(e)||(this.cbs.onattribend(0,this.sectionStart),this.handleAttrStart(e))}stateBeforeAttrValue(e){34===e?(this.state=19,this.sectionStart=this.index+1):39===e?(this.state=20,this.sectionStart=this.index+1):oQ(e)||(this.sectionStart=this.index,this.state=21,this.stateInAttrValueNoQuotes(e))}handleInAttrValue(e,t){(e===t||this.fastForwardTo(t))&&(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(34===t?3:2,this.index+1),this.state=11)}stateInAttrValueDoubleQuotes(e){this.handleInAttrValue(e,34)}stateInAttrValueSingleQuotes(e){this.handleInAttrValue(e,39)}stateInAttrValueNoQuotes(e){oQ(e)||62===e?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(1,this.index),this.state=11,this.stateBeforeAttrName(e)):(39===e||60===e||61===e||96===e)&&this.cbs.onerr(18,this.index)}stateBeforeDeclaration(e){91===e?(this.state=26,this.sequenceIndex=0):this.state=45===e?25:23}stateInDeclaration(e){(62===e||this.fastForwardTo(62))&&(this.state=1,this.sectionStart=this.index+1)}stateInProcessingInstruction(e){(62===e||this.fastForwardTo(62))&&(this.cbs.onprocessinginstruction(this.sectionStart,this.index),this.state=1,this.sectionStart=this.index+1)}stateBeforeComment(e){45===e?(this.state=28,this.currentSequence=o0.CommentEnd,this.sequenceIndex=2,this.sectionStart=this.index+1):this.state=23}stateInSpecialComment(e){(62===e||this.fastForwardTo(62))&&(this.cbs.oncomment(this.sectionStart,this.index),this.state=1,this.sectionStart=this.index+1)}stateBeforeSpecialS(e){e===o0.ScriptEnd[3]?this.startSpecial(o0.ScriptEnd,4):e===o0.StyleEnd[3]?this.startSpecial(o0.StyleEnd,4):(this.state=6,this.stateInTagName(e))}stateBeforeSpecialT(e){e===o0.TitleEnd[3]?this.startSpecial(o0.TitleEnd,4):e===o0.TextareaEnd[3]?this.startSpecial(o0.TextareaEnd,4):(this.state=6,this.stateInTagName(e))}startEntity(){}stateInEntity(){}parse(e){for(this.buffer=e;this.index<this.buffer.length;){let e=this.buffer.charCodeAt(this.index);switch(10===e&&this.newlines.push(this.index),this.state){case 1:this.stateText(e);break;case 2:this.stateInterpolationOpen(e);break;case 3:this.stateInterpolation(e);break;case 4:this.stateInterpolationClose(e);break;case 31:this.stateSpecialStartSequence(e);break;case 32:this.stateInRCDATA(e);break;case 26:this.stateCDATASequence(e);break;case 19:this.stateInAttrValueDoubleQuotes(e);break;case 12:this.stateInAttrName(e);break;case 13:this.stateInDirName(e);break;case 14:this.stateInDirArg(e);break;case 15:this.stateInDynamicDirArg(e);break;case 16:this.stateInDirModifier(e);break;case 28:this.stateInCommentLike(e);break;case 27:this.stateInSpecialComment(e);break;case 11:this.stateBeforeAttrName(e);break;case 6:this.stateInTagName(e);break;case 34:this.stateInSFCRootTagName(e);break;case 9:this.stateInClosingTagName(e);break;case 5:this.stateBeforeTagName(e);break;case 17:this.stateAfterAttrName(e);break;case 20:this.stateInAttrValueSingleQuotes(e);break;case 18:this.stateBeforeAttrValue(e);break;case 8:this.stateBeforeClosingTagName(e);break;case 10:this.stateAfterClosingTagName(e);break;case 29:this.stateBeforeSpecialS(e);break;case 30:this.stateBeforeSpecialT(e);break;case 21:this.stateInAttrValueNoQuotes(e);break;case 7:this.stateInSelfClosingTag(e);break;case 23:this.stateInDeclaration(e);break;case 22:this.stateBeforeDeclaration(e);break;case 25:this.stateBeforeComment(e);break;case 24:this.stateInProcessingInstruction(e);break;case 33:this.stateInEntity()}this.index++}this.cleanup(),this.finish()}cleanup(){this.sectionStart!==this.index&&(1===this.state||32===this.state&&0===this.sequenceIndex?(this.cbs.ontext(this.sectionStart,this.index),this.sectionStart=this.index):(19===this.state||20===this.state||21===this.state)&&(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=this.index))}finish(){this.handleTrailingData(),this.cbs.onend()}handleTrailingData(){let e=this.buffer.length;this.sectionStart>=e||(28===this.state?this.currentSequence===o0.CdataEnd?this.cbs.oncdata(this.sectionStart,e):this.cbs.oncomment(this.sectionStart,e):6===this.state||11===this.state||18===this.state||17===this.state||12===this.state||13===this.state||14===this.state||15===this.state||16===this.state||20===this.state||19===this.state||21===this.state||9===this.state||this.cbs.ontext(this.sectionStart,e))}emitCodePoint(e,t){}}(aw,{onerr:aH,ontext(e,t){aO(aI(e,t),e,t)},ontextentity(e,t,n){aO(e,t,n)},oninterpolation(e,t){if(aT)return aO(aI(e,t),e,t);let n=e+aE.delimiterOpen.length,r=t-aE.delimiterClose.length;for(;oQ(ay.charCodeAt(n));)n++;for(;oQ(ay.charCodeAt(r-1));)r--;let i=aI(n,r);i.includes(\"&\")&&(i=am.decodeEntities(i,!1)),aD({type:5,content:aj(i,!1,aB(n,r)),loc:aB(e,t)})},onopentagname(e,t){let n=aI(e,t);av={type:1,tag:n,ns:am.getNamespace(n,aw[0],am.ns),tagType:0,props:[],children:[],loc:aB(e-1,t),codegenNode:void 0}},onopentagend(e){aR(e)},onclosetag(e,t){let n=aI(e,t);if(!am.isVoidTag(n)){let r=!1;for(let e=0;e<aw.length;e++)if(aw[e].tag.toLowerCase()===n.toLowerCase()){r=!0,e>0&&aw[0].loc.start.offset;for(let n=0;n<=e;n++)aM(aw.shift(),t,n<e);break}r||aL(e,60)}},onselfclosingtag(e){let t=av.tag;av.isSelfClosing=!0,aR(e),aw[0]&&aw[0].tag===t&&aM(aw.shift(),e)},onattribname(e,t){ab={type:6,name:aI(e,t),nameLoc:aB(e,t),value:void 0,loc:aB(e)}},ondirname(e,t){let n=aI(e,t),r=\".\"===n||\":\"===n?\"bind\":\"@\"===n?\"on\":\"#\"===n?\"slot\":n.slice(2);if(aT||\"\"===r)ab={type:6,name:n,nameLoc:aB(e,t),value:void 0,loc:aB(e)};else if(ab={type:7,name:r,rawName:n,exp:void 0,arg:void 0,modifiers:\".\"===n?[\"prop\"]:[],loc:aB(e)},\"pre\"===r){aT=aE.inVPre=!0,ak=av;let e=av.props;for(let t=0;t<e.length;t++)7===e[t].type&&(e[t]=function(e){let t={type:6,name:e.rawName,nameLoc:aB(e.loc.start.offset,e.loc.start.offset+e.rawName.length),value:void 0,loc:e.loc};if(e.exp){let n=e.exp.loc;n.end.offset<e.loc.end.offset&&(n.start.offset--,n.start.column--,n.end.offset++,n.end.column++),t.value={type:2,content:e.exp.content,loc:n}}return t}(e[t]))}},ondirarg(e,t){if(e===t)return;let n=aI(e,t);if(aT)ab.name+=n,aU(ab.nameLoc,t);else{let r=\"[\"!==n[0];ab.arg=aj(r?n:n.slice(1,-1),r,aB(e,t),r?3:0)}},ondirmodifier(e,t){let n=aI(e,t);if(aT)ab.name+=\".\"+n,aU(ab.nameLoc,t);else if(\"slot\"===ab.name){let e=ab.arg;e&&(e.content+=\".\"+n,aU(e.loc,t))}else ab.modifiers.push(n)},onattribdata(e,t){a_+=aI(e,t),aS<0&&(aS=e),ax=t},onattribentity(e,t,n){a_+=e,aS<0&&(aS=t),ax=n},onattribnameend(e){let t=aI(ab.loc.start.offset,e);7===ab.type&&(ab.rawName=t),av.props.some(e=>(7===e.type?e.rawName:e.name)===t)},onattribend(e,t){av&&ab&&(aU(ab.loc,t),0!==e&&(a_.includes(\"&\")&&(a_=am.decodeEntities(a_,!0)),6===ab.type?(\"class\"===ab.name&&(a_=aV(a_).trim()),ab.value={type:2,content:a_,loc:1===e?aB(aS,ax):aB(aS-1,ax+1)},aE.inSFCRoot&&\"template\"===av.tag&&\"lang\"===ab.name&&a_&&\"html\"!==a_&&aE.enterRCDATA(oY(\"</template\"),0)):(ab.exp=aj(a_,!1,aB(aS,ax),0,0),\"for\"===ab.name&&(ab.forParseResult=function(e){let t=e.loc,n=e.content,r=n.match(ah);if(!r)return;let[,i,l]=r,s=(e,n,r=!1)=>{let i=t.start.offset+n,l=i+e.length;return aj(e,!1,aB(i,l),0,r?1:0)},o={source:s(l.trim(),n.indexOf(l,i.length)),value:void 0,key:void 0,index:void 0,finalized:!1},a=i.trim().replace(aN,\"\").trim(),c=i.indexOf(a),u=a.match(aA);if(u){let e;a=a.replace(aA,\"\").trim();let t=u[1].trim();if(t&&(e=n.indexOf(t,c+a.length),o.key=s(t,e,!0)),u[2]){let r=u[2].trim();r&&(o.index=s(r,n.indexOf(r,o.key?e+t.length:c+a.length),!0))}}return a&&(o.value=s(a,c,!0)),o}(ab.exp)))),(7!==ab.type||\"pre\"!==ab.name)&&av.props.push(ab)),a_=\"\",aS=ax=-1},oncomment(e,t){am.comments&&aD({type:3,content:aI(e,t),loc:aB(e-4,t+3)})},onend(){let e=ay.length;for(let t=0;t<aw.length;t++)aM(aw[t],e-1),aw[t].loc.start.offset},oncdata(e,t){0!==aw[0].ns&&aO(aI(e,t),e,t)},onprocessinginstruction(e){(aw[0]?aw[0].ns:am.ns)===0&&aH(21,e-1)}}),aA=/,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/,aN=/^\\(|\\)$/g;function aI(e,t){return ay.slice(e,t)}function aR(e){aE.inSFCRoot&&(av.innerLoc=aB(e+1,e+1)),aD(av);let{tag:t,ns:n}=av;0===n&&am.isPreTag(t)&&aC++,am.isVoidTag(t)?aM(av,e):(aw.unshift(av),(1===n||2===n)&&(aE.inXML=!0)),av=null}function aO(e,t,n){{let t=aw[0]&&aw[0].tag;\"script\"!==t&&\"style\"!==t&&e.includes(\"&\")&&(e=am.decodeEntities(e,!1))}let r=aw[0]||ag,i=r.children[r.children.length-1];i&&2===i.type?(i.content+=e,aU(i.loc,n)):r.children.push({type:2,content:e,loc:aB(t,n)})}function aM(e,t,n=!1){n?aU(e.loc,aL(t,60)):aU(e.loc,function(e,t){let n=e;for(;62!==ay.charCodeAt(n)&&n<ay.length-1;)n++;return n}(t,0)+1),aE.inSFCRoot&&(e.children.length?e.innerLoc.end=g({},e.children[e.children.length-1].loc.end):e.innerLoc.end=g({},e.innerLoc.start),e.innerLoc.source=aI(e.innerLoc.start.offset,e.innerLoc.end.offset));let{tag:r,ns:i}=e;!aT&&(\"slot\"===r?e.tagType=2:function({tag:e,props:t}){if(\"template\"===e){for(let e=0;e<t.length;e++)if(7===t[e].type&&aP.has(t[e].name))return!0}return!1}(e)?e.tagType=3:function({tag:e,props:t}){var n;if(am.isCustomElement(e))return!1;if(\"component\"===e||(n=e.charCodeAt(0))>64&&n<91||o4(e)||am.isBuiltInComponent&&am.isBuiltInComponent(e)||am.isNativeTag&&!am.isNativeTag(e))return!0;for(let e=0;e<t.length;e++){let n=t[e];if(6===n.type&&\"is\"===n.name&&n.value&&n.value.content.startsWith(\"vue:\"))return!0}return!1}(e)&&(e.tagType=1)),aE.inRCDATA||(e.children=aF(e.children,e.tag)),0===i&&am.isPreTag(r)&&aC--,ak===e&&(aT=aE.inVPre=!1,ak=null),aE.inXML&&(aw[0]?aw[0].ns:am.ns)===0&&(aE.inXML=!1)}function aL(e,t){let n=e;for(;ay.charCodeAt(n)!==t&&n>=0;)n--;return n}let aP=new Set([\"if\",\"else\",\"else-if\",\"for\",\"slot\"]),a$=/\\r\\n/g;function aF(e,t){let n=\"preserve\"!==am.whitespace,r=!1;for(let t=0;t<e.length;t++){let i=e[t];if(2===i.type){if(aC)i.content=i.content.replace(a$,\"\\n\");else if(function(e){for(let t=0;t<e.length;t++)if(!oQ(e.charCodeAt(t)))return!1;return!0}(i.content)){let l=e[t-1]&&e[t-1].type,s=e[t+1]&&e[t+1].type;!l||!s||n&&(3===l&&(3===s||1===s)||1===l&&(3===s||1===s&&function(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(10===n||13===n)return!0}return!1}(i.content)))?(r=!0,e[t]=null):i.content=\" \"}else n&&(i.content=aV(i.content))}}if(aC&&t&&am.isPreTag(t)){let t=e[0];t&&2===t.type&&(t.content=t.content.replace(/^\\r?\\n/,\"\"))}return r?e.filter(Boolean):e}function aV(e){let t=\"\",n=!1;for(let r=0;r<e.length;r++)oQ(e.charCodeAt(r))?n||(t+=\" \",n=!0):(t+=e[r],n=!1);return t}function aD(e){(aw[0]||ag).children.push(e)}function aB(e,t){return{start:aE.getPos(e),end:null==t?t:aE.getPos(t),source:null==t?t:aI(e,t)}}function aU(e,t){e.end=aE.getPos(t),e.source=aI(e.start.offset,t)}function aj(e,t=!1,n,r=0,i=0){return oj(e,t,n,r)}function aH(e,t,n){am.onError(o3(e,aB(t,t)))}function aq(e,t){let{children:n}=e;return 1===n.length&&1===t.type&&!aa(t)}function aW(e,t){let{constantCache:n}=t;switch(e.type){case 1:if(0!==e.tagType)return 0;let r=n.get(e);if(void 0!==r)return r;let i=e.codegenNode;if(13!==i.type||i.isBlock&&\"svg\"!==e.tag&&\"foreignObject\"!==e.tag&&\"math\"!==e.tag)return 0;if(void 0!==i.patchFlag)return n.set(e,0),0;{let r=3,c=az(e,t);if(0===c)return n.set(e,0),0;c<r&&(r=c);for(let i=0;i<e.children.length;i++){let l=aW(e.children[i],t);if(0===l)return n.set(e,0),0;l<r&&(r=l)}if(r>1)for(let i=0;i<e.props.length;i++){let l=e.props[i];if(7===l.type&&\"bind\"===l.name&&l.exp){let i=aW(l.exp,t);if(0===i)return n.set(e,0),0;i<r&&(r=i)}}if(i.isBlock){var l,s,o,a;for(let t=0;t<e.props.length;t++)if(7===e.props[t].type)return n.set(e,0),0;t.removeHelper(on),t.removeHelper((l=t.inSSR,s=i.isComponent,l||s?or:oi)),i.isBlock=!1,t.helper((o=t.inSSR,a=i.isComponent,o||a?ol:os))}return n.set(e,r),r}case 2:case 3:return 3;case 9:case 11:case 10:default:return 0;case 5:case 12:return aW(e.content,t);case 4:return e.constType;case 8:let c=3;for(let n=0;n<e.children.length;n++){let r=e.children[n];if(E(r)||A(r))continue;let i=aW(r,t);if(0===i)return 0;i<c&&(c=i)}return c}}let aK=new Set([o_,oS,ox,oC]);function az(e,t){let n=3,r=aG(e);if(r&&15===r.type){let{properties:e}=r;for(let r=0;r<e.length;r++){let i;let{key:l,value:s}=e[r],o=aW(l,t);if(0===o)return o;if(o<n&&(n=o),0===(i=4===s.type?aW(s,t):14===s.type?function e(t,n){if(14===t.type&&!E(t.callee)&&aK.has(t.callee)){let r=t.arguments[0];if(4===r.type)return aW(r,n);if(14===r.type)return e(r,n)}return 0}(s,t):0))return i;i<n&&(n=i)}}return n}function aG(e){let t=e.codegenNode;if(13===t.type)return t.props}function aJ(e,t){t.currentNode=e;let{nodeTransforms:n}=t,r=[];for(let i=0;i<n.length;i++){let l=n[i](e,t);if(l&&(S(l)?r.push(...l):r.push(l)),!t.currentNode)return;e=t.currentNode}switch(e.type){case 3:t.ssr||t.helper(oo);break;case 5:t.ssr||t.helper(ov);break;case 9:for(let n=0;n<e.branches.length;n++)aJ(e.branches[n],t);break;case 10:case 11:case 1:case 0:!function(e,t){let n=0,r=()=>{n--};for(;n<e.children.length;n++){let i=e.children[n];E(i)||(t.grandParent=t.parent,t.parent=e,t.childIndex=n,t.onNodeRemoved=r,aJ(i,t))}}(e,t)}t.currentNode=e;let i=r.length;for(;i--;)r[i]()}function aX(e,t){let n=E(e)?t=>t===e:t=>e.test(t);return(e,r)=>{if(1===e.type){let{props:i}=e;if(3===e.tagType&&i.some(as))return;let l=[];for(let s=0;s<i.length;s++){let o=i[s];if(7===o.type&&n(o.name)){i.splice(s,1),s--;let n=t(e,o,r);n&&l.push(n)}}return l}}}let aQ=\"/*#__PURE__*/\",aZ=e=>`${o$[e]}: _${o$[e]}`;function aY(e,t,{helper:n,push:r,newline:i,isTS:l}){let s=n(\"component\"===t?ou:op);for(let n=0;n<e.length;n++){let o=e[n],a=o.endsWith(\"__self\");a&&(o=o.slice(0,-6)),r(`const ${ap(o,t)} = ${s}(${JSON.stringify(o)}${a?\", true\":\"\"})${l?\"!\":\"\"}`),n<e.length-1&&i()}}function a0(e,t){let n=e.length>3;t.push(\"[\"),n&&t.indent(),a1(e,t,n),n&&t.deindent(),t.push(\"]\")}function a1(e,t,n=!1,r=!0){let{push:i,newline:l}=t;for(let s=0;s<e.length;s++){let o=e[s];E(o)?i(o,-3):S(o)?a0(o,t):a2(o,t),s<e.length-1&&(n?(r&&i(\",\"),l()):r&&i(\", \"))}}function a2(e,t){if(E(e)){t.push(e,-3);return}if(A(e)){t.push(t.helper(e));return}switch(e.type){case 1:case 9:case 11:case 12:a2(e.codegenNode,t);break;case 2:!function(e,t){t.push(JSON.stringify(e.content),-3,e)}(e,t);break;case 4:a3(e,t);break;case 5:!function(e,t){let{push:n,helper:r,pure:i}=t;i&&n(aQ),n(`${r(ov)}(`),a2(e.content,t),n(\")\")}(e,t);break;case 8:a6(e,t);break;case 3:!function(e,t){let{push:n,helper:r,pure:i}=t;i&&n(aQ),n(`${r(oo)}(${JSON.stringify(e.content)})`,-3,e)}(e,t);break;case 13:!function(e,t){let n;let{push:r,helper:i,pure:l}=t,{tag:s,props:o,children:a,patchFlag:c,dynamicProps:u,directives:d,isBlock:p,disableTracking:h,isComponent:f}=e;c&&(n=String(c)),d&&r(i(of)+\"(\"),p&&r(`(${i(on)}(${h?\"true\":\"\"}), `),l&&r(aQ),r(i(p?t.inSSR||f?or:oi:t.inSSR||f?ol:os)+\"(\",-2,e),a1(function(e){let t=e.length;for(;t--&&null==e[t];);return e.slice(0,t+1).map(e=>e||\"null\")}([s,o,a,n,u]),t),r(\")\"),p&&r(\")\"),d&&(r(\", \"),a2(d,t),r(\")\"))}(e,t);break;case 14:!function(e,t){let{push:n,helper:r,pure:i}=t,l=E(e.callee)?e.callee:r(e.callee);i&&n(aQ),n(l+\"(\",-2,e),a1(e.arguments,t),n(\")\")}(e,t);break;case 15:!function(e,t){let{push:n,indent:r,deindent:i,newline:l}=t,{properties:s}=e;if(!s.length){n(\"{}\",-2,e);return}let o=s.length>1;n(o?\"{\":\"{ \"),o&&r();for(let e=0;e<s.length;e++){let{key:r,value:i}=s[e];!function(e,t){let{push:n}=t;8===e.type?(n(\"[\"),a6(e,t),n(\"]\")):e.isStatic?n(o5(e.content)?e.content:JSON.stringify(e.content),-2,e):n(`[${e.content}]`,-3,e)}(r,t),n(\": \"),a2(i,t),e<s.length-1&&(n(\",\"),l())}o&&i(),n(o?\"}\":\" }\")}(e,t);break;case 17:a0(e.elements,t);break;case 18:!function(e,t){let{push:n,indent:r,deindent:i}=t,{params:l,returns:s,body:o,newline:a,isSlot:c}=e;c&&n(`_${o$[oR]}(`),n(\"(\",-2,e),S(l)?a1(l,t):l&&a2(l,t),n(\") => \"),(a||o)&&(n(\"{\"),r()),s?(a&&n(\"return \"),S(s)?a0(s,t):a2(s,t)):o&&a2(o,t),(a||o)&&(i(),n(\"}\")),c&&n(\")\")}(e,t);break;case 19:!function(e,t){let{test:n,consequent:r,alternate:i,newline:l}=e,{push:s,indent:o,deindent:a,newline:c}=t;if(4===n.type){let e=!o5(n.content);e&&s(\"(\"),a3(n,t),e&&s(\")\")}else s(\"(\"),a2(n,t),s(\")\");l&&o(),t.indentLevel++,l||s(\" \"),s(\"? \"),a2(r,t),t.indentLevel--,l&&c(),l||s(\" \"),s(\": \");let u=19===i.type;!u&&t.indentLevel++,a2(i,t),!u&&t.indentLevel--,l&&a(!0)}(e,t);break;case 20:!function(e,t){let{push:n,helper:r,indent:i,deindent:l,newline:s}=t;n(`_cache[${e.index}] || (`),e.isVOnce&&(i(),n(`${r(oA)}(-1),`),s(),n(\"(\")),n(`_cache[${e.index}] = `),a2(e.value,t),e.isVOnce&&(n(`).cacheIndex = ${e.index},`),s(),n(`${r(oA)}(1),`),s(),n(`_cache[${e.index}]`),l()),n(\")\")}(e,t);break;case 21:a1(e.body,t,!0,!1)}}function a3(e,t){let{content:n,isStatic:r}=e;t.push(r?JSON.stringify(n):n,-3,e)}function a6(e,t){for(let n=0;n<e.children.length;n++){let r=e.children[n];E(r)?t.push(r,-3):a2(r,t)}}let a4=aX(/^(if|else|else-if)$/,(e,t,n)=>(function(e,t,n,r){if(\"else\"!==t.name&&(!t.exp||!t.exp.content.trim())){let r=t.exp?t.exp.loc:e.loc;n.onError(o3(28,t.loc)),t.exp=oj(\"true\",!1,r)}if(\"if\"===t.name){let i=a8(e,t),l={type:9,loc:e.loc,branches:[i]};if(n.replaceNode(l),r)return r(l,i,!0)}else{let i=n.parent.children,l=i.indexOf(e);for(;l-- >=-1;){let s=i[l];if(s&&3===s.type||s&&2===s.type&&!s.content.trim().length){n.removeNode(s);continue}if(s&&9===s.type){\"else-if\"===t.name&&void 0===s.branches[s.branches.length-1].condition&&n.onError(o3(30,e.loc)),n.removeNode();let i=a8(e,t);s.branches.push(i);let l=r&&r(s,i,!1);aJ(i,n),l&&l(),n.currentNode=null}else n.onError(o3(30,e.loc));break}}})(e,t,n,(e,t,r)=>{let i=n.parent.children,l=i.indexOf(e),s=0;for(;l-- >=0;){let e=i[l];e&&9===e.type&&(s+=e.branches.length)}return()=>{r?e.codegenNode=a5(t,s,n):function(e){for(;;)if(19===e.type){if(19!==e.alternate.type)return e;e=e.alternate}else 20===e.type&&(e=e.value)}(e.codegenNode).alternate=a5(t,s+e.branches.length-1,n)}}));function a8(e,t){let n=3===e.tagType;return{type:10,loc:e.loc,condition:\"else\"===t.name?void 0:t.exp,children:n&&!an(e,\"for\")?e.children:[e],userKey:ar(e,\"key\"),isTemplateIf:n}}function a5(e,t,n){return e.condition?oK(e.condition,a9(e,t,n),oq(n.helper(oo),['\"\"',\"true\"])):a9(e,t,n)}function a9(e,t,n){let{helper:r}=n,i=oU(\"key\",oj(`${t}`,!1,oF,2)),{children:l}=e,s=l[0];if(1!==l.length||1!==s.type){if(1!==l.length||11!==s.type)return oV(n,r(s5),oB([i]),l,64,void 0,void 0,!0,!1,!1,e.loc);{let e=s.codegenNode;return au(e,i,n),e}}{let e=s.codegenNode,t=14===e.type&&e.callee===oL?e.arguments[1].returns:e;return 13===t.type&&oz(t,n),au(t,i,n),e}}let a7=(e,t,n)=>{let{modifiers:r,loc:i}=e,l=e.arg,{exp:s}=e;if(s&&4===s.type&&!s.content.trim()&&(s=void 0),!s){if(4!==l.type||!l.isStatic)return n.onError(o3(52,l.loc)),{props:[oU(l,oj(\"\",!0,i))]};ce(e),s=e.exp}return 4!==l.type?(l.children.unshift(\"(\"),l.children.push(') || \"\"')):l.isStatic||(l.content=`${l.content} || \"\"`),r.includes(\"camel\")&&(4===l.type?l.isStatic?l.content=B(l.content):l.content=`${n.helperString(ok)}(${l.content})`:(l.children.unshift(`${n.helperString(ok)}(`),l.children.push(\")\"))),!n.inSSR&&(r.includes(\"prop\")&&ct(l,\".\"),r.includes(\"attr\")&&ct(l,\"^\")),{props:[oU(l,s)]}},ce=(e,t)=>{let n=e.arg,r=B(n.content);e.exp=oj(r,!1,n.loc)},ct=(e,t)=>{4===e.type?e.isStatic?e.content=t+e.content:e.content=`\\`${t}\\${${e.content}}\\``:(e.children.unshift(`'${t}' + (`),e.children.push(\")\"))},cn=aX(\"for\",(e,t,n)=>{let{helper:r,removeHelper:i}=n;return function(e,t,n,r){if(!t.exp){n.onError(o3(31,t.loc));return}let i=t.forParseResult;if(!i){n.onError(o3(32,t.loc));return}cr(i);let{addIdentifiers:l,removeIdentifiers:s,scopes:o}=n,{source:a,value:c,key:u,index:d}=i,p={type:11,loc:t.loc,source:a,valueAlias:c,keyAlias:u,objectIndexAlias:d,parseResult:i,children:ao(e)?e.children:[e]};n.replaceNode(p),o.vFor++;let h=r&&r(p);return()=>{o.vFor--,h&&h()}}(e,t,n,t=>{let l=oq(r(om),[t.source]),s=ao(e),o=an(e,\"memo\"),a=ar(e,\"key\",!1,!0);a&&7===a.type&&!a.exp&&ce(a);let c=a&&(6===a.type?a.value?oj(a.value.content,!0):void 0:a.exp),u=a&&c?oU(\"key\",c):null,d=4===t.source.type&&t.source.constType>0,p=d?64:a?128:256;return t.codegenNode=oV(n,r(s5),void 0,l,p,void 0,void 0,!0,!d,!1,e.loc),()=>{let a;let{children:p}=t,h=1!==p.length||1!==p[0].type,f=aa(e)?e:s&&1===e.children.length&&aa(e.children[0])?e.children[0]:null;if(f)a=f.codegenNode,s&&u&&au(a,u,n);else if(h)a=oV(n,r(s5),u?oB([u]):void 0,e.children,64,void 0,void 0,!0,void 0,!1);else{var m,g,y,b,_,S,x,C;a=p[0].codegenNode,s&&u&&au(a,u,n),!d!==a.isBlock&&(a.isBlock?(i(on),i((m=n.inSSR,g=a.isComponent,m||g?or:oi))):i((y=n.inSSR,b=a.isComponent,y||b?ol:os))),(a.isBlock=!d,a.isBlock)?(r(on),r((_=n.inSSR,S=a.isComponent,_||S?or:oi))):r((x=n.inSSR,C=a.isComponent,x||C?ol:os))}if(o){let e=oW(ci(t.parseResult,[oj(\"_cached\")]));e.body={type:21,body:[oH([\"const _memo = (\",o.exp,\")\"]),oH([\"if (_cached\",...c?[\" && _cached.key === \",c]:[],` && ${n.helperString(oP)}(_cached, _memo)) return _cached`]),oH([\"const _item = \",a]),oj(\"_item.memo = _memo\"),oj(\"return _item\")],loc:oF},l.arguments.push(e,oj(\"_cache\"),oj(String(n.cached++)))}else l.arguments.push(oW(ci(t.parseResult),a,!0))}})});function cr(e,t){e.finalized||(e.finalized=!0)}function ci({value:e,key:t,index:n},r=[]){return function(e){let t=e.length;for(;t--&&!e[t];);return e.slice(0,t+1).map((e,t)=>e||oj(\"_\".repeat(t+1),!1))}([e,t,n,...r])}let cl=oj(\"undefined\",!1),cs=(e,t)=>{if(1===e.type&&(1===e.tagType||3===e.tagType)){let n=an(e,\"slot\");if(n)return n.exp,t.scopes.vSlot++,()=>{t.scopes.vSlot--}}},co=(e,t,n,r)=>oW(e,n,!1,!0,n.length?n[0].loc:r);function ca(e,t,n){let r=[oU(\"name\",e),oU(\"fn\",t)];return null!=n&&r.push(oU(\"key\",oj(String(n),!0))),oB(r)}let cc=new WeakMap,cu=(e,t)=>function(){let n,r,i,l,s;if(!(1===(e=t.currentNode).type&&(0===e.tagType||1===e.tagType)))return;let{tag:o,props:a}=e,c=1===e.tagType,u=c?function(e,t,n=!1){let{tag:r}=e,i=ch(r),l=ar(e,\"is\",!1,!0);if(l){if(i){let e;if(6===l.type?e=l.value&&oj(l.value.content,!0):(e=l.exp)||(e=oj(\"is\",!1,l.loc)),e)return oq(t.helper(od),[e])}else 6===l.type&&l.value.content.startsWith(\"vue:\")&&(r=l.value.content.slice(4))}let s=o4(r)||t.isBuiltInComponent(r);return s?(n||t.helper(s),s):(t.helper(ou),t.components.add(r),ap(r,\"component\"))}(e,t):`\"${o}\"`,d=N(u)&&u.callee===od,p=0,h=d||u===s9||u===s7||!c&&(\"svg\"===o||\"foreignObject\"===o||\"math\"===o);if(a.length>0){let r=cd(e,t,void 0,c,d);n=r.props,p=r.patchFlag,l=r.dynamicPropNames;let i=r.directives;s=i&&i.length?oD(i.map(e=>(function(e,t){let n=[],r=cc.get(e);r?n.push(t.helperString(r)):(t.helper(op),t.directives.add(e.name),n.push(ap(e.name,\"directive\")));let{loc:i}=e;if(e.exp&&n.push(e.exp),e.arg&&(e.exp||n.push(\"void 0\"),n.push(e.arg)),Object.keys(e.modifiers).length){e.arg||(e.exp||n.push(\"void 0\"),n.push(\"void 0\"));let t=oj(\"true\",!1,i);n.push(oB(e.modifiers.map(e=>oU(e,t)),i))}return oD(n,e.loc)})(e,t))):void 0,r.shouldUseBlock&&(h=!0)}if(e.children.length>0){if(u===oe&&(h=!0,p|=1024),c&&u!==s9&&u!==oe){let{slots:n,hasDynamicSlots:i}=function(e,t,n=co){t.helper(oR);let{children:r,loc:i}=e,l=[],s=[],o=t.scopes.vSlot>0||t.scopes.vFor>0,a=an(e,\"slot\",!0);if(a){let{arg:e,exp:t}=a;e&&!o6(e)&&(o=!0),l.push(oU(e||oj(\"default\",!0),n(t,void 0,r,i)))}let c=!1,u=!1,d=[],p=new Set,h=0;for(let e=0;e<r.length;e++){let i,f,m,g;let y=r[e];if(!ao(y)||!(i=an(y,\"slot\",!0))){3!==y.type&&d.push(y);continue}if(a){t.onError(o3(37,i.loc));break}c=!0;let{children:b,loc:_}=y,{arg:S=oj(\"default\",!0),exp:x,loc:C}=i;o6(S)?f=S?S.content:\"default\":o=!0;let T=an(y,\"for\"),k=n(x,T,b,_);if(m=an(y,\"if\"))o=!0,s.push(oK(m.exp,ca(S,k,h++),cl));else if(g=an(y,/^else(-if)?$/,!0)){let n,i=e;for(;i--&&3===(n=r[i]).type;);if(n&&ao(n)&&an(n,/^(else-)?if$/)){let e=s[s.length-1];for(;19===e.alternate.type;)e=e.alternate;e.alternate=g.exp?oK(g.exp,ca(S,k,h++),cl):ca(S,k,h++)}else t.onError(o3(30,g.loc))}else if(T){o=!0;let e=T.forParseResult;e?(cr(e),s.push(oq(t.helper(om),[e.source,oW(ci(e),ca(S,k),!0)]))):t.onError(o3(32,T.loc))}else{if(f){if(p.has(f)){t.onError(o3(38,C));continue}p.add(f),\"default\"===f&&(u=!0)}l.push(oU(S,k))}}if(!a){let e=(e,t)=>oU(\"default\",n(e,void 0,t,i));c?d.length&&d.some(e=>(function e(t){return 2!==t.type&&12!==t.type||(2===t.type?!!t.content.trim():e(t.content))})(e))&&(u?t.onError(o3(39,d[0].loc)):l.push(e(void 0,d))):l.push(e(void 0,r))}let f=o?2:!function e(t){for(let n=0;n<t.length;n++){let r=t[n];switch(r.type){case 1:if(2===r.tagType||e(r.children))return!0;break;case 9:if(e(r.branches))return!0;break;case 10:case 11:if(e(r.children))return!0}}return!1}(e.children)?1:3,m=oB(l.concat(oU(\"_\",oj(f+\"\",!1))),i);return s.length&&(m=oq(t.helper(oy),[m,oD(s)])),{slots:m,hasDynamicSlots:o}}(e,t);r=n,i&&(p|=1024)}else if(1===e.children.length&&u!==s9){let n=e.children[0],i=n.type,l=5===i||8===i;l&&0===aW(n,t)&&(p|=1),r=l||2===i?n:e.children}else r=e.children}l&&l.length&&(i=function(e){let t=\"[\";for(let n=0,r=e.length;n<r;n++)t+=JSON.stringify(e[n]),n<r-1&&(t+=\", \");return t+\"]\"}(l)),e.codegenNode=oV(t,u,n,r,0===p?void 0:p,i,s,!!h,!1,c,e.loc)};function cd(e,t,n=e.props,r,i,l=!1){let s;let{tag:o,loc:a,children:c}=e,u=[],d=[],p=[],h=c.length>0,m=!1,g=0,y=!1,b=!1,_=!1,S=!1,x=!1,C=!1,T=[],k=e=>{u.length&&(d.push(oB(cp(u),a)),u=[]),e&&d.push(e)},w=()=>{t.scopes.vFor>0&&u.push(oU(oj(\"ref_for\",!0),oj(\"true\")))},E=({key:e,value:n})=>{if(o6(e)){let l=e.content,s=f(l);s&&(!r||i)&&\"onclick\"!==l.toLowerCase()&&\"onUpdate:modelValue\"!==l&&!$(l)&&(S=!0),s&&$(l)&&(C=!0),s&&14===n.type&&(n=n.arguments[0]),20===n.type||(4===n.type||8===n.type)&&aW(n,t)>0||(\"ref\"===l?y=!0:\"class\"===l?b=!0:\"style\"===l?_=!0:\"key\"===l||T.includes(l)||T.push(l),r&&(\"class\"===l||\"style\"===l)&&!T.includes(l)&&T.push(l))}else x=!0};for(let i=0;i<n.length;i++){let s=n[i];if(6===s.type){let{loc:e,name:t,nameLoc:n,value:r}=s;if(\"ref\"===t&&(y=!0,w()),\"is\"===t&&(ch(o)||r&&r.content.startsWith(\"vue:\")))continue;u.push(oU(oj(t,!0,n),oj(r?r.content:\"\",!0,r?r.loc:e)))}else{let{name:n,arg:i,exp:c,loc:f,modifiers:y}=s,b=\"bind\"===n,_=\"on\"===n;if(\"slot\"===n){r||t.onError(o3(40,f));continue}if(\"once\"===n||\"memo\"===n||\"is\"===n||b&&ai(i,\"is\")&&ch(o)||_&&l)continue;if((b&&ai(i,\"key\")||_&&h&&ai(i,\"vue:before-update\"))&&(m=!0),b&&ai(i,\"ref\")&&w(),!i&&(b||_)){x=!0,c?b?(w(),k(),d.push(c)):k({type:14,loc:f,callee:t.helper(oT),arguments:r?[c]:[c,\"true\"]}):t.onError(o3(b?34:35,f));continue}b&&y.includes(\"prop\")&&(g|=32);let S=t.directiveTransforms[n];if(S){let{props:n,needRuntime:r}=S(s,e,t);l||n.forEach(E),_&&i&&!o6(i)?k(oB(n,a)):u.push(...n),r&&(p.push(s),A(r)&&cc.set(s,r))}else!F(n)&&(p.push(s),h&&(m=!0))}}if(d.length?(k(),s=d.length>1?oq(t.helper(ob),d,a):d[0]):u.length&&(s=oB(cp(u),a)),x?g|=16:(b&&!r&&(g|=2),_&&!r&&(g|=4),T.length&&(g|=8),S&&(g|=32)),!m&&(0===g||32===g)&&(y||C||p.length>0)&&(g|=512),!t.inSSR&&s)switch(s.type){case 15:let N=-1,I=-1,R=!1;for(let e=0;e<s.properties.length;e++){let t=s.properties[e].key;o6(t)?\"class\"===t.content?N=e:\"style\"===t.content&&(I=e):t.isHandlerKey||(R=!0)}let O=s.properties[N],M=s.properties[I];R?s=oq(t.helper(ox),[s]):(O&&!o6(O.value)&&(O.value=oq(t.helper(o_),[O.value])),M&&(_||4===M.value.type&&\"[\"===M.value.content.trim()[0]||17===M.value.type)&&(M.value=oq(t.helper(oS),[M.value])));break;case 14:break;default:s=oq(t.helper(ox),[oq(t.helper(oC),[s])])}return{props:s,directives:p,patchFlag:g,dynamicPropNames:T,shouldUseBlock:m}}function cp(e){let t=new Map,n=[];for(let r=0;r<e.length;r++){let i=e[r];if(8===i.key.type||!i.key.isStatic){n.push(i);continue}let l=i.key.content,s=t.get(l);s?(\"style\"===l||\"class\"===l||f(l))&&(17===s.value.type?s.value.elements.push(i.value):s.value=oD([s.value,i.value],s.loc)):(t.set(l,i),n.push(i))}return n}function ch(e){return\"component\"===e||\"Component\"===e}let cf=(e,t)=>{if(aa(e)){let{children:n,loc:r}=e,{slotName:i,slotProps:l}=function(e,t){let n,r='\"default\"',i=[];for(let t=0;t<e.props.length;t++){let n=e.props[t];if(6===n.type)n.value&&(\"name\"===n.name?r=JSON.stringify(n.value.content):(n.name=B(n.name),i.push(n)));else if(\"bind\"===n.name&&ai(n.arg,\"name\")){if(n.exp)r=n.exp;else if(n.arg&&4===n.arg.type){let e=B(n.arg.content);r=n.exp=oj(e,!1,n.arg.loc)}}else\"bind\"===n.name&&n.arg&&o6(n.arg)&&(n.arg.content=B(n.arg.content)),i.push(n)}if(i.length>0){let{props:r,directives:l}=cd(e,t,i,!1,!1);n=r,l.length&&t.onError(o3(36,l[0].loc))}return{slotName:r,slotProps:n}}(e,t),s=[t.prefixIdentifiers?\"_ctx.$slots\":\"$slots\",i,\"{}\",\"undefined\",\"true\"],o=2;l&&(s[2]=l,o=3),n.length&&(s[3]=oW([],n,!1,!1,r),o=4),t.scopeId&&!t.slotted&&(o=5),s.splice(o),e.codegenNode=oq(t.helper(og),s,r)}},cm=/^\\s*(async\\s*)?(\\([^)]*?\\)|[\\w$_]+)\\s*(:[^=]+)?=>|^\\s*(async\\s+)?function(?:\\s+[\\w$]+)?\\s*\\(/,cg=(e,t,n,r)=>{let i;let{loc:l,modifiers:s,arg:o}=e;if(e.exp||s.length,4===o.type){if(o.isStatic){let e=o.content;e.startsWith(\"vue:\")&&(e=`vnode-${e.slice(4)}`),i=oj(0!==t.tagType||e.startsWith(\"vnode\")||!/[A-Z]/.test(e)?q(B(e)):`on:${e}`,!0,o.loc)}else i=oH([`${n.helperString(oE)}(`,o,\")\"])}else(i=o).children.unshift(`${n.helperString(oE)}(`),i.children.push(\")\");let a=e.exp;a&&!a.content.trim()&&(a=void 0);let c=n.cacheHandlers&&!a&&!n.inVOnce;if(a){let e=at(a.content),t=!(e||cm.test(a.content)),n=a.content.includes(\";\");(t||c&&e)&&(a=oH([`${t?\"$event\":\"(...args)\"} => ${n?\"{\":\"(\"}`,a,n?\"}\":\")\"]))}let u={props:[oU(i,a||oj(\"() => {}\",!1,l))]};return r&&(u=r(u)),c&&(u.props[0].value=n.cache(u.props[0].value)),u.props.forEach(e=>e.key.isHandlerKey=!0),u},cy=(e,t)=>{if(0===e.type||1===e.type||11===e.type||10===e.type)return()=>{let n;let r=e.children,i=!1;for(let e=0;e<r.length;e++){let t=r[e];if(al(t)){i=!0;for(let i=e+1;i<r.length;i++){let l=r[i];if(al(l))n||(n=r[e]=oH([t],t.loc)),n.children.push(\" + \",l),r.splice(i,1),i--;else{n=void 0;break}}}}if(i&&(1!==r.length||0!==e.type&&(1!==e.type||0!==e.tagType||e.props.find(e=>7===e.type&&!t.directiveTransforms[e.name]))))for(let e=0;e<r.length;e++){let n=r[e];if(al(n)||8===n.type){let i=[];(2!==n.type||\" \"!==n.content)&&i.push(n),t.ssr||0!==aW(n,t)||i.push(\"1\"),r[e]={type:12,content:n,loc:n.loc,codegenNode:oq(t.helper(oa),i)}}}}},cv=new WeakSet,cb=(e,t)=>{if(1===e.type&&an(e,\"once\",!0)&&!cv.has(e)&&!t.inVOnce&&!t.inSSR)return cv.add(e),t.inVOnce=!0,t.helper(oA),()=>{t.inVOnce=!1;let e=t.currentNode;e.codegenNode&&(e.codegenNode=t.cache(e.codegenNode,!0))}},c_=(e,t,n)=>{let r;let{exp:i,arg:l}=e;if(!i)return n.onError(o3(41,e.loc)),cS();let s=i.loc.source,o=4===i.type?i.content:s,a=n.bindingMetadata[s];if(\"props\"===a||\"props-aliased\"===a)return i.loc,cS();if(!o.trim()||!at(o))return n.onError(o3(42,i.loc)),cS();let c=l||oj(\"modelValue\",!0),u=l?o6(l)?`onUpdate:${B(l.content)}`:oH(['\"onUpdate:\" + ',l]):\"onUpdate:modelValue\",d=n.isTS?\"($event: any)\":\"$event\";r=oH([`${d} => ((`,i,\") = $event)\"]);let p=[oU(c,e.exp),oU(u,r)];if(e.modifiers.length&&1===t.tagType){let t=e.modifiers.map(e=>(o5(e)?e:JSON.stringify(e))+\": true\").join(\", \"),n=l?o6(l)?`${l.content}Modifiers`:oH([l,' + \"Modifiers\"']):\"modelModifiers\";p.push(oU(n,oj(`{ ${t} }`,!1,e.loc,2)))}return cS(p)};function cS(e=[]){return{props:e}}let cx=new WeakSet,cC=(e,t)=>{if(1===e.type){let n=an(e,\"memo\");if(!(!n||cx.has(e)))return cx.add(e),()=>{let r=e.codegenNode||t.currentNode.codegenNode;r&&13===r.type&&(1!==e.tagType&&oz(r,t),e.codegenNode=oq(t.helper(oL),[n.exp,oW(void 0,r),\"_cache\",String(t.cached++)]))}}},cT=Symbol(\"\"),ck=Symbol(\"\"),cw=Symbol(\"\"),cE=Symbol(\"\"),cA=Symbol(\"\"),cN=Symbol(\"\"),cI=Symbol(\"\"),cR=Symbol(\"\"),cO=Symbol(\"\"),cM=Symbol(\"\");!function(e){Object.getOwnPropertySymbols(e).forEach(t=>{o$[t]=e[t]})}({[cT]:\"vModelRadio\",[ck]:\"vModelCheckbox\",[cw]:\"vModelText\",[cE]:\"vModelSelect\",[cA]:\"vModelDynamic\",[cN]:\"withModifiers\",[cI]:\"withKeys\",[cR]:\"vShow\",[cO]:\"Transition\",[cM]:\"TransitionGroup\"});let cL={parseMode:\"html\",isVoidTag:ea,isNativeTag:e=>el(e)||es(e)||eo(e),isPreTag:e=>\"pre\"===e,decodeEntities:function(e,t=!1){return(a||(a=document.createElement(\"div\")),t)?(a.innerHTML=`<div foo=\"${e.replace(/\"/g,\"&quot;\")}\">`,a.children[0].getAttribute(\"foo\")):(a.innerHTML=e,a.textContent)},isBuiltInComponent:e=>\"Transition\"===e||\"transition\"===e?cO:\"TransitionGroup\"===e||\"transition-group\"===e?cM:void 0,getNamespace(e,t,n){let r=t?t.ns:n;if(t&&2===r){if(\"annotation-xml\"===t.tag){if(\"svg\"===e)return 1;t.props.some(e=>6===e.type&&\"encoding\"===e.name&&null!=e.value&&(\"text/html\"===e.value.content||\"application/xhtml+xml\"===e.value.content))&&(r=0)}else/^m(?:[ions]|text)$/.test(t.tag)&&\"mglyph\"!==e&&\"malignmark\"!==e&&(r=0)}else t&&1===r&&(\"foreignObject\"===t.tag||\"desc\"===t.tag||\"title\"===t.tag)&&(r=0);if(0===r){if(\"svg\"===e)return 1;if(\"math\"===e)return 2}return r}},cP=(e,t)=>oj(JSON.stringify(en(e)),!1,t,3),c$=c(\"passive,once,capture\"),cF=c(\"stop,prevent,self,ctrl,shift,alt,meta,exact,middle\"),cV=c(\"left,right\"),cD=c(\"onkeyup,onkeydown,onkeypress\",!0),cB=(e,t,n,r)=>{let i=[],l=[],s=[];for(let n=0;n<t.length;n++){let r=t[n];c$(r)?s.push(r):cV(r)?o6(e)?cD(e.content)?i.push(r):l.push(r):(i.push(r),l.push(r)):cF(r)?l.push(r):i.push(r)}return{keyModifiers:i,nonKeyModifiers:l,eventOptionModifiers:s}},cU=(e,t)=>o6(e)&&\"onclick\"===e.content.toLowerCase()?oj(t,!0):4!==e.type?oH([\"(\",e,`) === \"onClick\" ? \"${t}\" : (`,e,\")\"]):e,cj=(e,t)=>{1===e.type&&0===e.tagType&&(\"script\"===e.tag||\"style\"===e.tag)&&t.removeNode()},cH=[e=>{1===e.type&&e.props.forEach((t,n)=>{6===t.type&&\"style\"===t.name&&t.value&&(e.props[n]={type:7,name:\"bind\",arg:oj(\"style\",!0,t.loc),exp:cP(t.value.content,t.loc),modifiers:[],loc:t.loc})})}],cq={cloak:()=>({props:[]}),html:(e,t,n)=>{let{exp:r,loc:i}=e;return r||n.onError(o3(53,i)),t.children.length&&(n.onError(o3(54,i)),t.children.length=0),{props:[oU(oj(\"innerHTML\",!0,i),r||oj(\"\",!0))]}},text:(e,t,n)=>{let{exp:r,loc:i}=e;return r||n.onError(o3(55,i)),t.children.length&&(n.onError(o3(56,i)),t.children.length=0),{props:[oU(oj(\"textContent\",!0),r?aW(r,n)>0?r:oq(n.helperString(ov),[r],i):oj(\"\",!0))]}},model:(e,t,n)=>{let r=c_(e,t,n);if(!r.props.length||1===t.tagType)return r;e.arg&&n.onError(o3(58,e.arg.loc));let{tag:i}=t,l=n.isCustomElement(i);if(\"input\"===i||\"textarea\"===i||\"select\"===i||l){let s=cw,o=!1;if(\"input\"===i||l){let r=ar(t,\"type\");if(r){if(7===r.type)s=cA;else if(r.value)switch(r.value.content){case\"radio\":s=cT;break;case\"checkbox\":s=ck;break;case\"file\":o=!0,n.onError(o3(59,e.loc))}}else t.props.some(e=>7===e.type&&\"bind\"===e.name&&(!e.arg||4!==e.arg.type||!e.arg.isStatic))&&(s=cA)}else\"select\"===i&&(s=cE);o||(r.needRuntime=n.helper(s))}else n.onError(o3(57,e.loc));return r.props=r.props.filter(e=>!(4===e.key.type&&\"modelValue\"===e.key.content)),r},on:(e,t,n)=>cg(e,t,n,t=>{let{modifiers:r}=e;if(!r.length)return t;let{key:i,value:l}=t.props[0],{keyModifiers:s,nonKeyModifiers:o,eventOptionModifiers:a}=cB(i,r,n,e.loc);if(o.includes(\"right\")&&(i=cU(i,\"onContextmenu\")),o.includes(\"middle\")&&(i=cU(i,\"onMouseup\")),o.length&&(l=oq(n.helper(cN),[l,JSON.stringify(o)])),s.length&&(!o6(i)||cD(i.content))&&(l=oq(n.helper(cI),[l,JSON.stringify(s)])),a.length){let e=a.map(H).join(\"\");i=o6(i)?oj(`${i.content}${e}`,!0):oH([\"(\",i,`) + \"${e}\"`])}return{props:[oU(i,l)]}}),show:(e,t,n)=>{let{exp:r,loc:i}=e;return!r&&n.onError(o3(61,i)),{props:[],needRuntime:n.helper(cR)}}},cW=new WeakMap;function cK(e,t){let n;if(!E(e)){if(!e.nodeType)return p;e=e.innerHTML}let r=e,i=((n=cW.get(null!=t?t:u))||(n=Object.create(null),cW.set(null!=t?t:u,n)),n),l=i[r];if(l)return l;if(\"#\"===e[0]){let t=document.querySelector(e);e=t?t.innerHTML:\"\"}let s=g({hoistStatic:!0,onError:void 0,onWarn:p},t);s.isCustomElement||\"undefined\"==typeof customElements||(s.isCustomElement=e=>!!customElements.get(e));let{code:o}=function(e,t={}){return function(e,t={}){let n=t.onError||o1,r=\"module\"===t.mode;!0===t.prefixIdentifiers?n(o3(47)):r&&n(o3(48)),t.cacheHandlers&&n(o3(49)),t.scopeId&&!r&&n(o3(50));let i=g({},t,{prefixIdentifiers:!1}),l=E(e)?function(e,t){if(aE.reset(),av=null,ab=null,a_=\"\",aS=-1,ax=-1,aw.length=0,ay=e,am=g({},af),t){let e;for(e in t)null!=t[e]&&(am[e]=t[e])}aE.mode=\"html\"===am.parseMode?1:\"sfc\"===am.parseMode?2:0,aE.inXML=1===am.ns||2===am.ns;let n=t&&t.delimiters;n&&(aE.delimiterOpen=oY(n[0]),aE.delimiterClose=oY(n[1]));let r=ag=function(e,t=\"\"){return{type:0,source:t,children:e,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:0,temps:0,codegenNode:void 0,loc:oF}}([],e);return aE.parse(ay),r.loc=aB(0,e.length),r.children=aF(r.children),ag=null,r}(e,i):e,[s,o]=[[cb,a4,cC,cn,cf,cu,cs,cy],{on:cg,bind:a7,model:c_}];return!function(e,t){let n=function(e,{filename:t=\"\",prefixIdentifiers:n=!1,hoistStatic:r=!1,hmr:i=!1,cacheHandlers:l=!1,nodeTransforms:s=[],directiveTransforms:o={},transformHoist:a=null,isBuiltInComponent:c=p,isCustomElement:d=p,expressionPlugins:h=[],scopeId:f=null,slotted:m=!0,ssr:g=!1,inSSR:y=!1,ssrCssVars:b=\"\",bindingMetadata:_=u,inline:S=!1,isTS:x=!1,onError:C=o1,onWarn:T=o2,compatConfig:k}){let w=t.replace(/\\?.*$/,\"\").match(/([^/\\\\]+)\\.\\w+$/),A={filename:t,selfName:w&&H(B(w[1])),prefixIdentifiers:n,hoistStatic:r,hmr:i,cacheHandlers:l,nodeTransforms:s,directiveTransforms:o,transformHoist:a,isBuiltInComponent:c,isCustomElement:d,expressionPlugins:h,scopeId:f,slotted:m,ssr:g,inSSR:y,ssrCssVars:b,bindingMetadata:_,inline:S,isTS:x,onError:C,onWarn:T,compatConfig:k,root:e,helpers:new Map,components:new Set,directives:new Set,hoists:[],imports:[],constantCache:new WeakMap,temps:0,cached:0,identifiers:Object.create(null),scopes:{vFor:0,vSlot:0,vPre:0,vOnce:0},parent:null,grandParent:null,currentNode:e,childIndex:0,inVOnce:!1,helper(e){let t=A.helpers.get(e)||0;return A.helpers.set(e,t+1),e},removeHelper(e){let t=A.helpers.get(e);if(t){let n=t-1;n?A.helpers.set(e,n):A.helpers.delete(e)}},helperString:e=>`_${o$[A.helper(e)]}`,replaceNode(e){A.parent.children[A.childIndex]=A.currentNode=e},removeNode(e){let t=A.parent.children,n=e?t.indexOf(e):A.currentNode?A.childIndex:-1;e&&e!==A.currentNode?A.childIndex>n&&(A.childIndex--,A.onNodeRemoved()):(A.currentNode=null,A.onNodeRemoved()),A.parent.children.splice(n,1)},onNodeRemoved:p,addIdentifiers(e){},removeIdentifiers(e){},hoist(e){E(e)&&(e=oj(e)),A.hoists.push(e);let t=oj(`_hoisted_${A.hoists.length}`,!1,e.loc,2);return t.hoisted=e,t},cache:(e,t=!1)=>(function(e,t,n=!1){return{type:20,index:e,value:t,isVOnce:n,loc:oF}})(A.cached++,e,t)};return A}(e,t);aJ(e,n),t.hoistStatic&&function e(t,n,r=!1){let{children:i}=t,l=i.length,s=0;for(let t=0;t<i.length;t++){let l=i[t];if(1===l.type&&0===l.tagType){let e=r?0:aW(l,n);if(e>0){if(e>=2){l.codegenNode.patchFlag=-1,l.codegenNode=n.hoist(l.codegenNode),s++;continue}}else{let e=l.codegenNode;if(13===e.type){let t=e.patchFlag;if((void 0===t||512===t||1===t)&&az(l,n)>=2){let t=aG(l);t&&(e.props=n.hoist(t))}e.dynamicProps&&(e.dynamicProps=n.hoist(e.dynamicProps))}}}if(1===l.type){let t=1===l.tagType;t&&n.scopes.vSlot++,e(l,n),t&&n.scopes.vSlot--}else if(11===l.type)e(l,n,1===l.children.length);else if(9===l.type)for(let t=0;t<l.branches.length;t++)e(l.branches[t],n,1===l.branches[t].children.length)}if(s&&n.transformHoist&&n.transformHoist(i,n,t),s&&s===l&&1===t.type&&0===t.tagType&&t.codegenNode&&13===t.codegenNode.type&&S(t.codegenNode.children)){let e=n.hoist(oD(t.codegenNode.children));n.hmr&&(e.content=`[...${e.content}]`),t.codegenNode.children=e}}(e,n,aq(e,e.children[0])),t.ssr||function(e,t){let{helper:n}=t,{children:r}=e;if(1===r.length){let n=r[0];if(aq(e,n)&&n.codegenNode){let r=n.codegenNode;13===r.type&&oz(r,t),e.codegenNode=r}else e.codegenNode=n}else r.length>1&&(e.codegenNode=oV(t,n(s5),void 0,e.children,64,void 0,void 0,!0,void 0,!1))}(e,n),e.helpers=new Set([...n.helpers.keys()]),e.components=[...n.components],e.directives=[...n.directives],e.imports=n.imports,e.hoists=n.hoists,e.temps=n.temps,e.cached=n.cached,e.transformed=!0}(l,g({},i,{nodeTransforms:[...s,...t.nodeTransforms||[]],directiveTransforms:g({},o,t.directiveTransforms||{})})),function(e,t={}){let n=function(e,{mode:t=\"function\",prefixIdentifiers:n=\"module\"===t,sourceMap:r=!1,filename:i=\"template.vue.html\",scopeId:l=null,optimizeImports:s=!1,runtimeGlobalName:o=\"Vue\",runtimeModuleName:a=\"vue\",ssrRuntimeModuleName:c=\"vue/server-renderer\",ssr:u=!1,isTS:d=!1,inSSR:p=!1}){let h={mode:t,prefixIdentifiers:n,sourceMap:r,filename:i,scopeId:l,optimizeImports:s,runtimeGlobalName:o,runtimeModuleName:a,ssrRuntimeModuleName:c,ssr:u,isTS:d,inSSR:p,source:e.source,code:\"\",column:1,line:1,offset:0,indentLevel:0,pure:!1,map:void 0,helper:e=>`_${o$[e]}`,push(e,t=-2,n){h.code+=e},indent(){f(++h.indentLevel)},deindent(e=!1){e?--h.indentLevel:f(--h.indentLevel)},newline(){f(h.indentLevel)}};function f(e){h.push(\"\\n\"+\"  \".repeat(e),0)}return h}(e,t);t.onContextCreated&&t.onContextCreated(n);let{mode:r,push:i,prefixIdentifiers:l,indent:s,deindent:o,newline:a,scopeId:c,ssr:u}=n,d=Array.from(e.helpers),p=d.length>0,h=!l&&\"module\"!==r;(function(e,t){let{ssr:n,prefixIdentifiers:r,push:i,newline:l,runtimeModuleName:s,runtimeGlobalName:o,ssrRuntimeModuleName:a}=t,c=Array.from(e.helpers);if(c.length>0&&(i(`const _Vue = ${o}\n`,-1),e.hoists.length)){let e=[ol,os,oo,oa,oc].filter(e=>c.includes(e)).map(aZ).join(\", \");i(`const { ${e} } = _Vue\n`,-1)}(function(e,t){if(!e.length)return;t.pure=!0;let{push:n,newline:r,helper:i,scopeId:l,mode:s}=t;r();for(let i=0;i<e.length;i++){let l=e[i];l&&(n(`const _hoisted_${i+1} = `),a2(l,t),r())}t.pure=!1})(e.hoists,t),l(),i(\"return \")})(e,n);let f=(u?[\"_ctx\",\"_push\",\"_parent\",\"_attrs\"]:[\"_ctx\",\"_cache\"]).join(\", \");if(i(`function ${u?\"ssrRender\":\"render\"}(${f}) {`),s(),h&&(i(\"with (_ctx) {\"),s(),p&&(i(`const { ${d.map(aZ).join(\", \")} } = _Vue\n`,-1),a())),e.components.length&&(aY(e.components,\"component\",n),(e.directives.length||e.temps>0)&&a()),e.directives.length&&(aY(e.directives,\"directive\",n),e.temps>0&&a()),e.temps>0){i(\"let \");for(let t=0;t<e.temps;t++)i(`${t>0?\", \":\"\"}_temp${t}`)}return(e.components.length||e.directives.length||e.temps)&&(i(`\n`,0),a()),u||i(\"return \"),e.codegenNode?a2(e.codegenNode,n):i(\"null\"),h&&(o(),i(\"}\")),o(),i(\"}\"),{ast:e,code:n.code,preamble:\"\",map:n.map?n.map.toJSON():void 0}}(l,i)}(e,g({},cL,t,{nodeTransforms:[cj,...cH,...t.nodeTransforms||[]],directiveTransforms:g({},cq,t.directiveTransforms||{}),transformHoist:null}))}(e,s),a=Function(\"Vue\",o)(s8);return a._rc=!0,i[r]=a}lb(cK);export{nx as BaseTransition,n_ as BaseTransitionPropsValidators,iK as Comment,lD as DeprecationTypes,eg as EffectScope,tX as ErrorCodes,lM as ErrorTypeStrings,iq as Fragment,nL as KeepAlive,eS as ReactiveEffect,iz as Static,iV as Suspense,r4 as Teleport,iW as Text,tz as TrackOpTypes,lW as Transition,sE as TransitionGroup,tG as TriggerOpTypes,s_ as VueElement,tJ as assertNumber,tZ as callWithAsyncErrorHandling,tQ as callWithErrorHandling,B as camelize,H as capitalize,lt as cloneVNode,lV as compatUtils,cK as compile,lw as computed,s1 as createApp,i2 as createBlock,li as createCommentVNode,i1 as createElementBlock,i9 as createElementVNode,ia as createHydrationRenderer,rv as createPropsRestProxy,io as createRenderer,s2 as createSSRApp,n5 as createSlots,lr as createStaticVNode,ln as createTextVNode,i7 as createVNode,tU as customRef,nR as defineAsyncComponent,nN as defineComponent,sy as defineCustomElement,rs as defineEmits,ro as defineExpose,ru as defineModel,ra as defineOptions,rl as defineProps,sv as defineSSRCustomElement,rc as defineSlots,lL as devtools,ek as effect,ey as effectScope,lh as getCurrentInstance,eb as getCurrentScope,nA as getTransitionRawChildren,le as guardReactiveProps,lE as h,tY as handleError,rP as hasInjectionContext,s0 as hydrate,lA as initCustomFormatter,s4 as initDirectivesForSSR,rL as inject,lI as isMemoSame,tx as isProxy,tb as isReactive,t_ as isReadonly,tI as isRef,l_ as isRuntimeOnly,tS as isShallow,i3 as isVNode,tT as markRaw,rg as mergeDefaults,ry as mergeModels,la as mergeProps,t7 as nextTick,er as normalizeClass,ei as normalizeProps,Z as normalizeStyle,n$ as onActivated,nH as onBeforeMount,nz as onBeforeUnmount,nW as onBeforeUpdate,nF as onDeactivated,nZ as onErrorCaptured,nq as onMounted,nQ as onRenderTracked,nX as onRenderTriggered,e_ as onScopeDispose,nJ as onServerPrefetch,nG as onUnmounted,nK as onUpdated,iX as openBlock,nd as popScopeId,rM as provide,tD as proxyRefs,nu as pushScopeId,nn as queuePostFlushCb,tf as reactive,tg as readonly,tR as ref,lb as registerRuntimeCompiler,sY as render,n8 as renderList,n9 as renderSlot,n0 as resolveComponent,n3 as resolveDirective,n2 as resolveDynamicComponent,lF as resolveFilter,nT as resolveTransitionHooks,iY as setBlockTracking,lP as setDevtoolsHook,nE as setTransitionHooks,tm as shallowReactive,ty as shallowReadonly,tO as shallowRef,ig as ssrContextKey,l$ as ssrUtils,ew as stop,eh as toDisplayString,q as toHandlerKey,n7 as toHandlers,tC as toRaw,tW as toRef,tj as toRefs,tF as toValue,i4 as transformVNodeArgs,tP as triggerRef,t$ as unref,rh as useAttrs,sS as useCssModule,st as useCssVars,iE as useModel,iy as useSSRContext,rp as useSlots,nv as useTransitionState,s$ as vModelCheckbox,sH as vModelDynamic,sV as vModelRadio,sD as vModelSelect,sP as vModelText,l9 as vShow,lR as version,lO as warn,ix as watch,iv as watchEffect,ib as watchPostEffect,i_ as watchSyncEffect,rb as withAsyncContext,nh as withCtx,rd as withDefaults,nf as withDirectives,sJ as withKeys,lN as withMemo,sz as withModifiers,np as withScopeId};\n"
  },
  {
    "path": "src/crusader-lib/build.rs",
    "content": "use std::process::Command;\r\n\r\nfn main() {\r\n    if let Some(commit) = Command::new(\"git\")\r\n        .args([\"rev-parse\", \"--short\", \"HEAD\"])\r\n        .output()\r\n        .ok()\r\n        .and_then(|output| String::from_utf8(output.stdout).ok())\r\n    {\r\n        println!(\"cargo:rustc-env=GIT_COMMIT={}\", commit.trim());\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/common.rs",
    "content": "use crate::{\r\n    protocol::{receive, send, ClientMessage, Hello, Ping, ServerMessage},\r\n    serve::OnDrop,\r\n};\r\nuse anyhow::{anyhow, bail, Context};\r\nuse bytes::{Bytes, BytesMut};\r\nuse futures::{pin_mut, select, FutureExt, Sink, Stream};\r\nuse rand::Rng;\r\nuse rand::{rngs::StdRng, SeedableRng};\r\nuse std::{\r\n    error::Error,\r\n    io::Cursor,\r\n    net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},\r\n    sync::{\r\n        atomic::{AtomicBool, AtomicU64, Ordering},\r\n        Arc,\r\n    },\r\n    time::Duration,\r\n};\r\nuse tokio::{\r\n    join,\r\n    net::{\r\n        self,\r\n        tcp::{OwnedReadHalf, OwnedWriteHalf},\r\n        TcpStream, ToSocketAddrs, UdpSocket,\r\n    },\r\n    sync::{\r\n        oneshot,\r\n        watch::{self, error::RecvError},\r\n    },\r\n    task::yield_now,\r\n    time::{self, timeout, Instant},\r\n};\r\nuse tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};\r\n\r\n#[cfg(feature = \"client\")]\r\npub(crate) type Msg = Arc<dyn Fn(&str) + Send + Sync>;\r\n\r\n#[allow(unused)]\r\n#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]\r\npub(crate) enum TestState {\r\n    Setup,\r\n    Grace1,\r\n    LoadFromClient,\r\n    Grace2,\r\n    LoadFromServer,\r\n    Grace3,\r\n    LoadFromBoth,\r\n    Grace4,\r\n    End,\r\n    EndPingRecv,\r\n}\r\n\r\n#[cfg(feature = \"client\")]\r\n#[derive(Copy, Clone, PartialEq)]\r\npub struct Config {\r\n    pub download: bool,\r\n    pub upload: bool,\r\n    pub bidirectional: bool,\r\n    pub port: u16,\r\n    pub load_duration: Duration,\r\n    pub grace_duration: Duration,\r\n    pub streams: u64,\r\n    pub stream_stagger: Duration,\r\n    pub ping_interval: Duration,\r\n    pub throughput_interval: Duration,\r\n}\r\n\r\npub async fn connect<A: ToSocketAddrs>(addr: A, name: &str) -> Result<TcpStream, anyhow::Error> {\r\n    match timeout(Duration::from_secs(8), net::TcpStream::connect(addr)).await {\r\n        Ok(v) => v.with_context(|| format!(\"Failed to connect to {name}\")),\r\n        Err(_) => bail!(\"Timed out trying to connect to {name}. Is the {name} running?\"),\r\n    }\r\n}\r\n\r\npub fn interface_ips() -> Vec<(String, IpAddr)> {\r\n    let mut _result = Vec::new();\r\n\r\n    #[cfg(target_family = \"unix\")]\r\n    {\r\n        use nix::net::if_::InterfaceFlags;\r\n\r\n        if let Ok(interfaces) = nix::ifaddrs::getifaddrs() {\r\n            for interface in interfaces {\r\n                if interface.flags.contains(InterfaceFlags::IFF_LOOPBACK) {\r\n                    continue;\r\n                }\r\n                if !interface.flags.contains(InterfaceFlags::IFF_RUNNING) {\r\n                    continue;\r\n                }\r\n                if let Some(addr) = interface.address.as_ref().and_then(|i| i.as_sockaddr_in()) {\r\n                    _result.push((interface.interface_name.clone(), IpAddr::V4(addr.ip())));\r\n                }\r\n                if let Some(addr) = interface.address.as_ref().and_then(|i| i.as_sockaddr_in6()) {\r\n                    if is_unicast_link_local(addr.ip()) {\r\n                        continue;\r\n                    }\r\n                    _result.push((interface.interface_name.clone(), IpAddr::V6(addr.ip())));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    #[cfg(target_family = \"windows\")]\r\n    {\r\n        if let Ok(adapters) = ipconfig::get_adapters() {\r\n            for adapter in adapters {\r\n                if adapter.oper_status() != ipconfig::OperStatus::IfOperStatusUp {\r\n                    continue;\r\n                }\r\n                for &addr in adapter.ip_addresses() {\r\n                    if let IpAddr::V6(ip) = addr {\r\n                        if is_unicast_link_local(ip) {\r\n                            continue;\r\n                        }\r\n                    }\r\n                    if addr.is_loopback() {\r\n                        continue;\r\n                    }\r\n                    _result.push((adapter.friendly_name().to_owned(), addr));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    _result\r\n}\r\n\r\npub fn is_unicast_link_local(ip: Ipv6Addr) -> bool {\r\n    (ip.segments()[0] & 0xffc0) == 0xfe80\r\n}\r\n\r\npub fn fresh_socket_addr(socket: SocketAddr, port: u16) -> SocketAddr {\r\n    match socket {\r\n        SocketAddr::V4(socket) => SocketAddr::V4(SocketAddrV4::new(*socket.ip(), port)),\r\n        SocketAddr::V6(socket) => {\r\n            if let Some(ip) = socket.ip().to_ipv4_mapped() {\r\n                return SocketAddr::V4(SocketAddrV4::new(ip, port));\r\n            }\r\n            SocketAddr::V6(SocketAddrV6::new(*socket.ip(), port, 0, socket.scope_id()))\r\n        }\r\n    }\r\n}\r\n\r\npub fn inherit_local(socket: SocketAddr, ip: IpAddr, port: u16) -> SocketAddr {\r\n    if let SocketAddr::V6(socket) = socket {\r\n        if let IpAddr::V6(ip) = ip {\r\n            if is_unicast_link_local(ip) {\r\n                return SocketAddr::V6(SocketAddrV6::new(ip, port, 0, socket.scope_id()));\r\n            }\r\n        }\r\n    }\r\n\r\n    SocketAddr::new(ip, port)\r\n}\r\n\r\npub(crate) fn data() -> Vec<u8> {\r\n    let mut vec = Vec::with_capacity(128 * 1024);\r\n    let mut rng = StdRng::from_seed([\r\n        18, 141, 186, 158, 195, 76, 244, 56, 219, 131, 65, 128, 250, 63, 228, 44, 233, 34, 9, 51,\r\n        13, 72, 230, 131, 223, 240, 124, 77, 103, 238, 103, 186,\r\n    ]);\r\n    for _ in 0..vec.capacity() {\r\n        vec.push(rng.gen())\r\n    }\r\n    vec\r\n}\r\n\r\npub(crate) async fn read_data(\r\n    stream: TcpStream,\r\n    buffer: &mut [u8],\r\n    bytes: Arc<AtomicU64>,\r\n    until: Instant,\r\n    writer_done: oneshot::Receiver<()>,\r\n) -> Result<bool, anyhow::Error> {\r\n    stream.set_linger(Some(Duration::from_secs(0))).ok();\r\n\r\n    let reading_done = Arc::new(AtomicBool::new(false));\r\n\r\n    // Set `reading_done` to true 2 minutes after the load should terminate.\r\n    let reading_done_ = reading_done.clone();\r\n    tokio::spawn(async move {\r\n        time::sleep_until(until + Duration::from_secs(120)).await;\r\n        reading_done_.store(true, Ordering::Release);\r\n    });\r\n\r\n    // Set `reading_done` to true after 5 seconds of not receiving data.\r\n    let reading_done_ = reading_done.clone();\r\n    let bytes_ = bytes.clone();\r\n    tokio::spawn(async move {\r\n        writer_done.await.ok();\r\n\r\n        let mut current = bytes_.load(Ordering::Acquire);\r\n        let mut i = 0;\r\n        loop {\r\n            time::sleep(Duration::from_millis(100)).await;\r\n\r\n            if reading_done_.load(Ordering::Acquire) {\r\n                break;\r\n            }\r\n\r\n            let now = bytes_.load(Ordering::Acquire);\r\n\r\n            if now != current {\r\n                i = 0;\r\n                current = now;\r\n            } else {\r\n                i += 1;\r\n\r\n                if i > 50 {\r\n                    reading_done_.store(true, Ordering::Release);\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n    });\r\n\r\n    // Set `reading_done` to true on exit to terminate the spawned task.\r\n    let reading_done_ = reading_done.clone();\r\n    let _on_drop = OnDrop(|| {\r\n        reading_done_.store(true, Ordering::Release);\r\n    });\r\n\r\n    loop {\r\n        if let Ok(Err(err)) = time::timeout(Duration::from_millis(50), stream.readable()).await {\r\n            if err.kind() == std::io::ErrorKind::ConnectionReset\r\n                || err.kind() == std::io::ErrorKind::ConnectionAborted\r\n            {\r\n                return Ok(false);\r\n            } else {\r\n                return Err(err.into());\r\n            }\r\n        }\r\n\r\n        loop {\r\n            if reading_done.load(Ordering::Acquire) {\r\n                return Ok(true);\r\n            }\r\n\r\n            match stream.try_read(buffer) {\r\n                Ok(0) => return Ok(false),\r\n                Ok(n) => {\r\n                    bytes.fetch_add(n as u64, Ordering::Release);\r\n                    yield_now().await;\r\n                }\r\n                Err(err) => {\r\n                    if err.kind() == std::io::ErrorKind::WouldBlock {\r\n                        break;\r\n                    } else if err.kind() == std::io::ErrorKind::ConnectionReset\r\n                        || err.kind() == std::io::ErrorKind::ConnectionAborted\r\n                    {\r\n                        return Ok(false);\r\n                    } else {\r\n                        return Err(err.into());\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\npub(crate) async fn write_data(\r\n    stream: TcpStream,\r\n    data: &[u8],\r\n    until: Instant,\r\n) -> Result<(), anyhow::Error> {\r\n    stream.set_nodelay(false).ok();\r\n    stream.set_linger(Some(Duration::from_secs(0))).ok();\r\n\r\n    let done = Arc::new(AtomicBool::new(false));\r\n    let done_ = done.clone();\r\n\r\n    tokio::spawn(async move {\r\n        time::sleep_until(until).await;\r\n        done.store(true, Ordering::Release);\r\n    });\r\n\r\n    loop {\r\n        if let Ok(Err(err)) = time::timeout(Duration::from_millis(50), stream.writable()).await {\r\n            if err.kind() == std::io::ErrorKind::ConnectionReset\r\n                || err.kind() == std::io::ErrorKind::ConnectionAborted\r\n            {\r\n                break;\r\n            } else {\r\n                return Err(err.into());\r\n            }\r\n        }\r\n\r\n        if done_.load(Ordering::Acquire) {\r\n            break;\r\n        }\r\n        match stream.try_write(data) {\r\n            Ok(_) => (),\r\n            Err(err) => {\r\n                if err.kind() == std::io::ErrorKind::WouldBlock {\r\n                } else if err.kind() == std::io::ErrorKind::ConnectionReset\r\n                    || err.kind() == std::io::ErrorKind::ConnectionAborted\r\n                {\r\n                    break;\r\n                } else {\r\n                    return Err(err.into());\r\n                }\r\n            }\r\n        }\r\n\r\n        yield_now().await;\r\n    }\r\n\r\n    std::mem::drop(stream);\r\n\r\n    Ok(())\r\n}\r\n\r\npub(crate) async fn hello<\r\n    T: Sink<Bytes> + Unpin,\r\n    R: Stream<Item = Result<BytesMut, RE>> + Unpin,\r\n    RE,\r\n>(\r\n    tx: &mut T,\r\n    rx: &mut R,\r\n) -> Result<(), anyhow::Error>\r\nwhere\r\n    T::Error: Error + Send + Sync + 'static,\r\n    RE: Error + Send + Sync + 'static,\r\n{\r\n    let hello = Hello::new();\r\n\r\n    send(tx, &hello).await.context(\"Sending hello\")?;\r\n    let server_hello: Hello = receive(rx).await.context(\"Receiving hello\")?;\r\n\r\n    if hello != server_hello {\r\n        bail!(\r\n            \"Mismatched server hello, got {:?}, expected {:?}\",\r\n            server_hello,\r\n            hello\r\n        );\r\n    }\r\n\r\n    Ok(())\r\n}\r\n\r\npub(crate) fn udp_handle(result: std::io::Result<()>) -> std::io::Result<()> {\r\n    match result {\r\n        Ok(v) => Ok(v),\r\n        Err(e) => {\r\n            if e.raw_os_error() == Some(libc::ENOBUFS) {\r\n                Ok(())\r\n            } else {\r\n                Err(e)\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nasync fn ping_measure_send(\r\n    mut index: u64,\r\n    id: u64,\r\n    setup_start: Instant,\r\n    socket: Arc<UdpSocket>,\r\n    samples: u32,\r\n) -> Result<(Vec<Duration>, u64), anyhow::Error> {\r\n    let mut storage = Vec::with_capacity(samples as usize);\r\n    let mut buf = [0; 64];\r\n\r\n    let mut interval = time::interval(Duration::from_millis(5));\r\n\r\n    for _ in 0..samples {\r\n        interval.tick().await;\r\n\r\n        let current = setup_start.elapsed();\r\n\r\n        let ping = Ping { id, index };\r\n\r\n        index += 1;\r\n\r\n        let mut cursor = Cursor::new(&mut buf[..]);\r\n        bincode::serialize_into(&mut cursor, &ping)?;\r\n        let buf = &cursor.get_ref()[0..(cursor.position() as usize)];\r\n\r\n        socket.send(buf).await?;\r\n\r\n        storage.push(current);\r\n    }\r\n\r\n    Ok((storage, index))\r\n}\r\n\r\nasync fn ping_measure_recv(\r\n    setup_start: Instant,\r\n    socket: Arc<UdpSocket>,\r\n    samples: u32,\r\n) -> Result<Vec<(Ping, Duration)>, anyhow::Error> {\r\n    let mut storage = Vec::with_capacity(samples as usize);\r\n    let mut buf = [0; 64];\r\n\r\n    let end = time::sleep(Duration::from_millis(5) * samples + Duration::from_millis(1000)).fuse();\r\n    pin_mut!(end);\r\n\r\n    loop {\r\n        let result = {\r\n            let packet = socket.recv(&mut buf).fuse();\r\n            pin_mut!(packet);\r\n\r\n            select! {\r\n                result = packet => result,\r\n                _ = end => break,\r\n            }\r\n        };\r\n\r\n        let current = setup_start.elapsed();\r\n        let len = result?;\r\n        let buf = buf\r\n            .get_mut(..len)\r\n            .ok_or_else(|| anyhow!(\"Pong too large\"))?;\r\n        let ping: Ping = bincode::deserialize(buf)?;\r\n\r\n        storage.push((ping, current));\r\n    }\r\n\r\n    Ok(storage)\r\n}\r\n\r\npub struct LatencyResult {\r\n    pub latency: Duration,\r\n    pub threshold: Duration,\r\n    pub server_pong: Duration,\r\n    pub server_offset: u64,\r\n    pub server_time: u64,\r\n    pub control_rx: FramedRead<OwnedReadHalf, LengthDelimitedCodec>,\r\n}\r\n\r\npub(crate) async fn measure_latency(\r\n    id: u64,\r\n    ping_index: &mut u64,\r\n    mut control_tx: &mut FramedWrite<OwnedWriteHalf, LengthDelimitedCodec>,\r\n    mut control_rx: FramedRead<OwnedReadHalf, LengthDelimitedCodec>,\r\n    server: SocketAddr,\r\n    local_udp: SocketAddr,\r\n    setup_start: Instant,\r\n) -> Result<LatencyResult, anyhow::Error> {\r\n    send(&mut control_tx, &ClientMessage::GetMeasurements).await?;\r\n\r\n    let latencies = tokio::spawn(async move {\r\n        let mut latencies = Vec::new();\r\n\r\n        loop {\r\n            let reply: ServerMessage = receive(&mut control_rx).await?;\r\n            match reply {\r\n                ServerMessage::LatencyMeasures(measures) => {\r\n                    latencies.extend(measures.into_iter());\r\n                }\r\n                ServerMessage::MeasurementsDone { .. } => break,\r\n                _ => bail!(\"Unexpected message {:?}\", reply),\r\n            };\r\n        }\r\n\r\n        Ok((latencies, control_rx))\r\n    });\r\n\r\n    let udp_socket = Arc::new(net::UdpSocket::bind(local_udp).await?);\r\n    udp_socket.connect(server).await?;\r\n    let udp_socket2 = udp_socket.clone();\r\n\r\n    let samples = 100;\r\n\r\n    let ping_start_index = *ping_index;\r\n    let ping_send = tokio::spawn(ping_measure_send(\r\n        ping_start_index,\r\n        id,\r\n        setup_start,\r\n        udp_socket,\r\n        samples,\r\n    ));\r\n\r\n    let ping_recv = tokio::spawn(ping_measure_recv(setup_start, udp_socket2, samples));\r\n\r\n    let (sent, recv) = join!(ping_send, ping_recv);\r\n\r\n    send(&mut control_tx, &ClientMessage::StopMeasurements).await?;\r\n\r\n    let (mut latencies, control_rx) = latencies.await??;\r\n\r\n    let (sent, new_ping_index) = sent??;\r\n    *ping_index = new_ping_index;\r\n    let mut recv = recv??;\r\n\r\n    latencies.sort_by_key(|d| d.index);\r\n    recv.sort_by_key(|d| d.0.index);\r\n    let mut pings: Vec<(Duration, Duration, u64)> = sent\r\n        .into_iter()\r\n        .enumerate()\r\n        .filter_map(|(index, sent)| {\r\n            let index = index as u64 + ping_start_index;\r\n            let latency = latencies\r\n                .binary_search_by_key(&index, |e| e.index)\r\n                .ok()\r\n                .map(|ping| latencies[ping].time);\r\n\r\n            latency.and_then(|time| {\r\n                recv.binary_search_by_key(&index, |e| e.0.index)\r\n                    .ok()\r\n                    .map(|ping| (sent, recv[ping].1 - sent, time))\r\n            })\r\n        })\r\n        .collect();\r\n    if pings.is_empty() {\r\n        bail!(\"Unable to measure latency to server\");\r\n    }\r\n    if pings.len() < (samples / 2) as usize {\r\n        bail!(\"Unable get enough latency samples from server\");\r\n    }\r\n\r\n    pings.sort_by_key(|d| d.1);\r\n\r\n    let latency = pings.get(pings.len() / 2).unwrap().1;\r\n\r\n    let threshold = pings.get(pings.len() / 3).unwrap().1;\r\n\r\n    let pings: Vec<_> = pings\r\n        .get(0..=(pings.len() / 3))\r\n        .unwrap()\r\n        .iter()\r\n        .map(|&(sent, latency, server_time)| {\r\n            let server_pong = sent + latency / 2;\r\n\r\n            let server_offset = (server_pong.as_micros() as u64).wrapping_sub(server_time);\r\n\r\n            (server_pong, latency, server_offset, server_time)\r\n        })\r\n        .collect();\r\n\r\n    let server_pong = pings\r\n        .iter()\r\n        .map(|&(server_pong, _, _, _)| server_pong)\r\n        .sum::<Duration>()\r\n        / (pings.len() as u32);\r\n\r\n    let server_offset = pings\r\n        .iter()\r\n        .map(|&(_, _, offset, _)| offset as u128)\r\n        .sum::<u128>()\r\n        / (pings.len() as u128);\r\n\r\n    let server_time = pings\r\n        .iter()\r\n        .map(|&(_, _, _, time)| time as u128)\r\n        .sum::<u128>()\r\n        / (pings.len() as u128);\r\n\r\n    Ok(LatencyResult {\r\n        latency,\r\n        threshold,\r\n        server_pong,\r\n        server_offset: server_offset as u64,\r\n        server_time: server_time as u64,\r\n        control_rx,\r\n    })\r\n}\r\n\r\npub(crate) async fn ping_send(\r\n    mut ping_index: u64,\r\n    id: u64,\r\n    state_rx: watch::Receiver<(TestState, Instant)>,\r\n    setup_start: Instant,\r\n    socket: Arc<UdpSocket>,\r\n    interval: Duration,\r\n    estimated_duration: Duration,\r\n) -> Result<(Vec<Duration>, u64), anyhow::Error> {\r\n    let mut storage = Vec::with_capacity(\r\n        ((estimated_duration.as_secs_f64() + 2.0) * (1000.0 / interval.as_millis() as f64) * 1.5)\r\n            as usize,\r\n    );\r\n    let mut buf = [0; 64];\r\n\r\n    let mut interval = time::interval(interval);\r\n\r\n    loop {\r\n        interval.tick().await;\r\n\r\n        if state_rx.borrow().0 >= TestState::End {\r\n            break;\r\n        }\r\n\r\n        let current = setup_start.elapsed();\r\n\r\n        let ping = Ping {\r\n            id,\r\n            index: ping_index,\r\n        };\r\n\r\n        ping_index += 1;\r\n\r\n        let mut cursor = Cursor::new(&mut buf[..]);\r\n        bincode::serialize_into(&mut cursor, &ping).unwrap();\r\n        let buf = &cursor.get_ref()[0..(cursor.position() as usize)];\r\n\r\n        udp_handle(socket.send(buf).await.map(|_| ())).context(\"Unable to send UDP ping packet\")?;\r\n\r\n        storage.push(current);\r\n    }\r\n\r\n    Ok((storage, ping_index))\r\n}\r\n\r\npub(crate) async fn ping_recv(\r\n    mut state_rx: watch::Receiver<(TestState, Instant)>,\r\n    setup_start: Instant,\r\n    socket: Arc<UdpSocket>,\r\n    interval: Duration,\r\n    estimated_duration: Duration,\r\n) -> Result<Vec<(Ping, Duration)>, anyhow::Error> {\r\n    let mut storage = Vec::with_capacity(\r\n        ((estimated_duration.as_secs_f64() + 2.0) * (1000.0 / interval.as_millis() as f64) * 1.5)\r\n            as usize,\r\n    );\r\n    let mut buf = [0; 64];\r\n\r\n    let end = wait_for_state(&mut state_rx, TestState::EndPingRecv).fuse();\r\n    pin_mut!(end);\r\n\r\n    loop {\r\n        let result = {\r\n            let packet = socket.recv(&mut buf).fuse();\r\n            pin_mut!(packet);\r\n\r\n            select! {\r\n                result = packet => result,\r\n                _ = end => break,\r\n            }\r\n        };\r\n\r\n        let current = setup_start.elapsed();\r\n        let len = result?;\r\n        let buf = buf\r\n            .get_mut(..len)\r\n            .ok_or_else(|| anyhow!(\"Pong too large\"))?;\r\n        let ping: Ping = bincode::deserialize(buf)?;\r\n\r\n        storage.push((ping, current));\r\n    }\r\n\r\n    Ok(storage)\r\n}\r\n\r\npub(crate) async fn wait_for_state(\r\n    state_rx: &mut watch::Receiver<(TestState, Instant)>,\r\n    state: TestState,\r\n) -> Result<Instant, RecvError> {\r\n    loop {\r\n        {\r\n            let current = state_rx.borrow_and_update();\r\n            if current.0 == state {\r\n                return Ok(current.1);\r\n            }\r\n        }\r\n        state_rx.changed().await?;\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/discovery.rs",
    "content": "use crate::{common::is_unicast_link_local, protocol, serve::State, version};\r\n#[cfg(feature = \"client\")]\r\nuse anyhow::anyhow;\r\nuse anyhow::bail;\r\n#[cfg(target_family = \"unix\")]\r\nuse nix::net::if_::InterfaceFlags;\r\nuse serde::{Deserialize, Serialize};\r\nuse socket2::{Domain, Protocol, Socket};\r\nuse std::{\r\n    net::{IpAddr, Ipv6Addr, SocketAddr},\r\n    str::FromStr,\r\n    sync::Arc,\r\n};\r\nuse tokio::net::UdpSocket;\r\n\r\npub const DISCOVER_PORT: u16 = protocol::PORT + 2;\r\npub const DISCOVER_VERSION: u64 = 0;\r\n\r\n#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]\r\nstruct Hello {\r\n    magic: u64,\r\n    pub version: u64,\r\n}\r\n\r\nimpl Hello {\r\n    pub fn new() -> Self {\r\n        Hello {\r\n            magic: protocol::MAGIC,\r\n            version: DISCOVER_VERSION,\r\n        }\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug)]\r\nstruct Data {\r\n    hello: Hello,\r\n    message: Message,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug)]\r\nenum Message {\r\n    Discover {\r\n        peer: bool,\r\n    },\r\n    Server {\r\n        peer: bool,\r\n        port: u16,\r\n        protocol_version: u64,\r\n        software_version: String,\r\n        hostname: Option<String>,\r\n        label: Option<String>,\r\n        ips: Vec<[u8; 16]>,\r\n    },\r\n}\r\n\r\n#[cfg(feature = \"client\")]\r\npub struct Server {\r\n    pub at: String,\r\n    pub socket: SocketAddr,\r\n    pub software_version: String,\r\n}\r\n\r\nfn interfaces() -> Vec<u32> {\r\n    let mut _result = vec![0];\r\n\r\n    #[cfg(target_family = \"unix\")]\r\n    {\r\n        if let Ok(interfaces) = nix::ifaddrs::getifaddrs() {\r\n            for interface in interfaces {\r\n                if interface.flags.contains(InterfaceFlags::IFF_LOOPBACK) {\r\n                    continue;\r\n                }\r\n                if !interface.flags.contains(InterfaceFlags::IFF_MULTICAST) {\r\n                    continue;\r\n                }\r\n                if let Some(addr) = interface.address.as_ref().and_then(|i| i.as_sockaddr_in6()) {\r\n                    if !is_unicast_link_local(addr.ip()) {\r\n                        continue;\r\n                    }\r\n                    _result.push(addr.scope_id());\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    _result\r\n}\r\n\r\n#[cfg(feature = \"client\")]\r\npub async fn locate(peer_server: bool) -> Result<Server, anyhow::Error> {\r\n    use crate::{common::fresh_socket_addr, serve::OnDrop};\r\n    use std::{\r\n        net::SocketAddrV6,\r\n        sync::atomic::{AtomicBool, Ordering},\r\n        time::Duration,\r\n    };\r\n    use tokio::time::{self, timeout};\r\n\r\n    fn handle_packet(\r\n        peer_server: bool,\r\n        packet: &[u8],\r\n        src: SocketAddr,\r\n    ) -> Result<Server, anyhow::Error> {\r\n        let data: Data = bincode::deserialize(packet)?;\r\n        if data.hello != Hello::new() {\r\n            bail!(\"Wrong hello\");\r\n        }\r\n        if let Message::Server {\r\n            peer,\r\n            port,\r\n            protocol_version,\r\n            software_version,\r\n            hostname,\r\n            ips: _,\r\n            label: _,\r\n        } = data.message\r\n        {\r\n            if peer != peer_server {\r\n                bail!(\"Wrong server kind\");\r\n            }\r\n            if protocol_version != protocol::VERSION {\r\n                bail!(\"Wrong protocol\");\r\n            }\r\n            let socket = fresh_socket_addr(src, port);\r\n\r\n            let at = hostname\r\n                .map(|hostname| format!(\"`{hostname}` {socket}\"))\r\n                .unwrap_or(socket.to_string());\r\n\r\n            Ok(Server {\r\n                at,\r\n                socket,\r\n                software_version,\r\n            })\r\n        } else {\r\n            bail!(\"Wrong message\")\r\n        }\r\n    }\r\n\r\n    let socket = Socket::new(Domain::IPV6, socket2::Type::DGRAM, Some(Protocol::UDP))?;\r\n    socket.set_only_v6(true)?;\r\n    socket.bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0).into())?;\r\n    let socket: std::net::UdpSocket = socket.into();\r\n    socket.set_nonblocking(true)?;\r\n    let socket = UdpSocket::from_std(socket)?;\r\n\r\n    socket.set_broadcast(true)?;\r\n\r\n    let socket = Arc::new(socket);\r\n\r\n    let data = Data {\r\n        hello: Hello::new(),\r\n        message: Message::Discover { peer: peer_server },\r\n    };\r\n\r\n    let buf = bincode::serialize(&data)?;\r\n\r\n    let ip = Ipv6Addr::from_str(\"ff02::1\").unwrap();\r\n\r\n    let socket_ = socket.clone();\r\n    let send_packet = move || {\r\n        let socket = socket_.clone();\r\n        let buf = buf.clone();\r\n        async move {\r\n            let mut any = false;\r\n            for interface in interfaces() {\r\n                if socket\r\n                    .send_to(&buf, SocketAddrV6::new(ip, DISCOVER_PORT, 0, interface))\r\n                    .await\r\n                    .is_ok()\r\n                {\r\n                    any = true;\r\n                }\r\n            }\r\n            any\r\n        }\r\n    };\r\n\r\n    if !send_packet().await {\r\n        bail!(\"Failed to send any discovery multicast packets\");\r\n    }\r\n\r\n    let done = Arc::new(AtomicBool::new(false));\r\n    let done_ = done.clone();\r\n    let _on_drop = OnDrop(|| {\r\n        done_.store(true, Ordering::Release);\r\n    });\r\n\r\n    tokio::spawn(async move {\r\n        loop {\r\n            time::sleep(Duration::from_millis(100)).await;\r\n\r\n            if done.load(Ordering::Acquire) {\r\n                break;\r\n            }\r\n\r\n            send_packet().await;\r\n        }\r\n    });\r\n\r\n    let find = async {\r\n        let mut buf = [0; 1500];\r\n        loop {\r\n            if let Ok((len, src)) = socket.recv_from(&mut buf).await {\r\n                if let Ok(server) = handle_packet(peer_server, &buf[..len], src) {\r\n                    return server;\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    timeout(Duration::from_secs(1), find).await.map_err(|_| {\r\n        if peer_server {\r\n            anyhow!(\"Failed to locate local latency peer\")\r\n        } else {\r\n            anyhow!(\"Failed to locate local server\")\r\n        }\r\n    })\r\n}\r\n\r\npub fn serve(state: Arc<State>, port: u16) -> Result<(), anyhow::Error> {\r\n    async fn handle_packet(\r\n        port: u16,\r\n        peer_server: bool,\r\n        hostname: &Option<String>,\r\n        packet: &[u8],\r\n        socket: &UdpSocket,\r\n        src: SocketAddr,\r\n    ) -> Result<(), anyhow::Error> {\r\n        match src {\r\n            SocketAddr::V6(src) if is_unicast_link_local(*src.ip()) => (),\r\n            _ => bail!(\"Unexpected source\"),\r\n        }\r\n        let data: Data = bincode::deserialize(packet)?;\r\n        if data.hello != Hello::new() {\r\n            bail!(\"Wrong hello\");\r\n        }\r\n        if let Message::Discover { peer } = data.message {\r\n            if peer != peer_server {\r\n                return Ok(());\r\n            }\r\n            let data = Data {\r\n                hello: Hello::new(),\r\n                message: Message::Server {\r\n                    peer,\r\n                    port,\r\n                    protocol_version: protocol::VERSION,\r\n                    software_version: version(),\r\n                    hostname: hostname.clone(),\r\n                    label: None,\r\n                    ips: Vec::new(),\r\n                },\r\n            };\r\n            let buf = bincode::serialize(&data)?;\r\n            socket.send_to(&buf, src).await?;\r\n        }\r\n        Ok(())\r\n    }\r\n\r\n    let hostname = hostname::get()\r\n        .ok()\r\n        .and_then(|n| n.into_string().ok())\r\n        .filter(|n| n != \"localhost\");\r\n\r\n    let socket = Socket::new(Domain::IPV6, socket2::Type::DGRAM, Some(Protocol::UDP))?;\r\n    socket.set_only_v6(true)?;\r\n    socket.set_reuse_address(true)?;\r\n    socket.bind(&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), DISCOVER_PORT).into())?;\r\n    let socket: std::net::UdpSocket = socket.into();\r\n    socket.set_nonblocking(true)?;\r\n    let socket = UdpSocket::from_std(socket)?;\r\n\r\n    let ip = Ipv6Addr::from_str(\"ff02::1\").unwrap();\r\n\r\n    let mut any = false;\r\n    for interface in interfaces() {\r\n        if socket.join_multicast_v6(&ip, interface).is_ok() {\r\n            any = true;\r\n        }\r\n    }\r\n    if !any {\r\n        bail!(\"Failed to join any multicast groups\");\r\n    }\r\n\r\n    tokio::spawn(async move {\r\n        let mut buf = [0; 1500];\r\n        loop {\r\n            if let Ok((len, src)) = socket.recv_from(&mut buf).await {\r\n                handle_packet(\r\n                    port,\r\n                    state.peer_server,\r\n                    &hostname,\r\n                    &buf[..len],\r\n                    &socket,\r\n                    src,\r\n                )\r\n                .await\r\n                .map_err(|error| {\r\n                    (state.msg)(&format!(\"Unable to handle discovery packet: {:?}\", error));\r\n                })\r\n                .ok();\r\n            }\r\n        }\r\n    });\r\n\r\n    Ok(())\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/file_format.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse std::fs::File;\r\nuse std::io::BufReader;\r\nuse std::io::BufWriter;\r\nuse std::io::Read;\r\nuse std::io::Write;\r\nuse std::path::Path;\r\nuse std::time::Duration;\r\n\r\nuse crate::protocol;\r\nuse crate::protocol::RawLatency;\r\n\r\n// Note that rmp_serde doesn't not use an enumerator when serializing Option.\r\n// Be careful about which types are inside Option.\r\n\r\n#[derive(Serialize, Deserialize)]\r\n#[serde(transparent)]\r\npub struct Elasped {\r\n    pub microseconds: u64,\r\n}\r\n\r\n// V0 specific\r\n\r\n#[derive(Serialize, Deserialize)]\r\npub struct RawPingV0 {\r\n    pub index: u64,\r\n    pub sent: Duration,\r\n    pub latency: Option<Duration>,\r\n}\r\n\r\nimpl RawPingV0 {\r\n    pub fn to_v1(&self) -> RawPing {\r\n        RawPing {\r\n            index: self.index,\r\n            sent: self.sent,\r\n            latency: self.latency.map(|total| RawLatency {\r\n                total: Some(total),\r\n                up: Duration::from_secs(0),\r\n            }),\r\n        }\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone)]\r\npub struct RawConfigV0 {\r\n    // Seconds\r\n    pub load_duration: u64,\r\n    pub grace_duration: u64,\r\n\r\n    // Milliseconds\r\n    pub ping_interval: u64,\r\n    pub bandwidth_interval: u64,\r\n}\r\n\r\nimpl RawConfigV0 {\r\n    pub fn to_v1(&self) -> RawConfig {\r\n        RawConfig {\r\n            stagger: Duration::from_secs(0),\r\n            load_duration: Duration::from_secs(self.load_duration),\r\n            grace_duration: Duration::from_secs(self.grace_duration),\r\n            ping_interval: Duration::from_millis(self.ping_interval),\r\n            bandwidth_interval: Duration::from_millis(self.bandwidth_interval),\r\n        }\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize)]\r\npub struct RawResultV0 {\r\n    pub config: RawConfigV0,\r\n    pub start: Duration,\r\n    pub duration: Duration,\r\n    pub stream_groups: Vec<RawStreamGroup>,\r\n    pub pings: Vec<RawPingV0>,\r\n}\r\n\r\nimpl RawResultV0 {\r\n    pub fn to_v1(&self) -> RawResult {\r\n        RawResult {\r\n            version: 0,\r\n            generated_by: String::new(),\r\n            config: self.config.to_v1(),\r\n            start: self.start,\r\n            server_latency: Duration::from_secs(0),\r\n            ipv6: false,\r\n            duration: self.duration,\r\n            stream_groups: self.stream_groups.clone(),\r\n            pings: self.pings.iter().map(|ping| ping.to_v1()).collect(),\r\n            server_overload: false,\r\n            load_termination_timeout: false,\r\n            peer_pings: None,\r\n            test_data: Vec::new(),\r\n        }\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Hash, Debug)]\r\npub enum TestKind {\r\n    Download,\r\n    Upload,\r\n    Bidirectional,\r\n}\r\n\r\nimpl TestKind {\r\n    pub fn name(&self) -> &'static str {\r\n        match *self {\r\n            Self::Download => \"Download\",\r\n            Self::Upload => \"Upload\",\r\n            Self::Bidirectional => \"Bidirectional\",\r\n        }\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone)]\r\npub struct TestData {\r\n    pub start: Duration,\r\n    pub end: Duration,\r\n    pub kind: TestKind,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone)]\r\npub struct RawPoint {\r\n    pub time: Duration,\r\n    pub bytes: u64,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone)]\r\npub struct RawStream {\r\n    pub data: Vec<RawPoint>,\r\n}\r\n\r\nimpl RawStream {\r\n    pub(crate) fn to_vec(&self) -> Vec<(u64, u64)> {\r\n        self.data\r\n            .iter()\r\n            .map(|point| (point.time.as_micros() as u64, point.bytes))\r\n            .collect()\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone)]\r\npub struct RawStreamGroup {\r\n    pub download: bool,\r\n    pub both: bool,\r\n    pub streams: Vec<RawStream>,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone, Debug)]\r\npub struct RawPing {\r\n    pub index: u64,\r\n    pub sent: Duration,\r\n    pub latency: Option<RawLatency>,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone)]\r\npub struct RawConfig {\r\n    // Microseconds\r\n    pub stagger: Duration,\r\n    pub load_duration: Duration,\r\n    pub grace_duration: Duration,\r\n    pub ping_interval: Duration,\r\n    pub bandwidth_interval: Duration,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Eq, PartialEq)]\r\npub struct RawHeader {\r\n    pub magic: u64,\r\n    pub version: u64,\r\n}\r\n\r\nimpl Default for RawHeader {\r\n    fn default() -> Self {\r\n        Self {\r\n            magic: protocol::MAGIC,\r\n            version: 2,\r\n        }\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Clone)]\r\npub struct RawResult {\r\n    pub version: u64,\r\n    pub generated_by: String,\r\n    pub config: RawConfig,\r\n    pub ipv6: bool,\r\n    #[serde(default)]\r\n    pub load_termination_timeout: bool, // Added in V2\r\n    #[serde(default)]\r\n    pub server_overload: bool, // Added in V2\r\n    pub server_latency: Duration,\r\n    pub start: Duration,\r\n    pub duration: Duration,\r\n    pub stream_groups: Vec<RawStreamGroup>,\r\n    pub pings: Vec<RawPing>,\r\n    #[serde(default)]\r\n    pub peer_pings: Option<Vec<RawPing>>, // Added in V2\r\n    #[serde(default)] // Added in V2\r\n    pub test_data: Vec<TestData>,\r\n}\r\n\r\nimpl RawResult {\r\n    pub fn streams(&self) -> u64 {\r\n        self.stream_groups\r\n            .first()\r\n            .map(|group| group.streams.len())\r\n            .unwrap_or_default()\r\n            .try_into()\r\n            .unwrap()\r\n    }\r\n\r\n    pub fn download(&self) -> bool {\r\n        self.stream_groups\r\n            .iter()\r\n            .any(|group| group.download && !group.both)\r\n    }\r\n\r\n    pub fn upload(&self) -> bool {\r\n        self.stream_groups\r\n            .iter()\r\n            .any(|group| !group.download && !group.both)\r\n    }\r\n\r\n    pub fn idle(&self) -> bool {\r\n        self.stream_groups.is_empty()\r\n    }\r\n\r\n    pub fn both(&self) -> bool {\r\n        self.stream_groups.iter().any(|group| group.both)\r\n    }\r\n\r\n    pub fn load_from_reader(reader: impl Read) -> Option<Self> {\r\n        let mut file = BufReader::new(reader);\r\n        let header: RawHeader = bincode::deserialize_from(&mut file).ok()?;\r\n        if header.magic != RawHeader::default().magic {\r\n            return None;\r\n        }\r\n        match header.version {\r\n            0 => {\r\n                let result: RawResultV0 = bincode::deserialize_from(file).ok()?;\r\n                Some(result.to_v1())\r\n            }\r\n            1 | 2 => {\r\n                let data = snap::read::FrameDecoder::new(file);\r\n                Some(rmp_serde::decode::from_read(data).ok()?)\r\n            }\r\n            _ => None,\r\n        }\r\n    }\r\n\r\n    pub fn load(path: &Path) -> Option<Self> {\r\n        Self::load_from_reader(File::open(path).ok()?)\r\n    }\r\n\r\n    pub fn save_to_writer(&self, writer: impl Write) -> Result<(), anyhow::Error> {\r\n        let mut file = BufWriter::new(writer);\r\n\r\n        bincode::serialize_into(&mut file, &RawHeader::default())?;\r\n\r\n        let mut compressor = snap::write::FrameEncoder::new(file);\r\n\r\n        self.serialize(&mut rmp_serde::Serializer::new(&mut compressor).with_struct_map())?;\r\n\r\n        compressor.flush()?;\r\n        Ok(())\r\n    }\r\n\r\n    pub fn save(&self, name: &Path) -> Result<(), anyhow::Error> {\r\n        self.save_to_writer(File::create(name)?)\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/latency.rs",
    "content": "use anyhow::{anyhow, bail, Context};\r\nuse futures::future::FutureExt;\r\nuse futures::select;\r\nuse parking_lot::Mutex;\r\nuse std::collections::VecDeque;\r\nuse std::{\r\n    io::Cursor,\r\n    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},\r\n    sync::Arc,\r\n    time::Duration,\r\n};\r\nuse std::{iter, thread};\r\nuse tokio::net::UdpSocket;\r\nuse tokio::sync::mpsc::{channel, Sender};\r\nuse tokio::sync::oneshot;\r\nuse tokio::task;\r\nuse tokio::time::Instant;\r\nuse tokio::{\r\n    net::{self},\r\n    time,\r\n};\r\nuse tokio_util::codec::{FramedRead, FramedWrite};\r\n\r\nuse crate::common::{connect, hello, measure_latency, udp_handle, LatencyResult};\r\nuse crate::discovery;\r\nuse crate::protocol::{codec, receive, send, ClientMessage, Ping, ServerMessage};\r\n\r\ntype UpdateFn = Arc<dyn Fn() + Send + Sync>;\r\n\r\n#[derive(Copy, Clone)]\r\npub struct Config {\r\n    pub port: u16,\r\n    pub ping_interval: Duration,\r\n}\r\n\r\n#[derive(Debug, Copy, Clone)]\r\npub enum EventKind {\r\n    Sent { sent: Duration },\r\n    Timeout,\r\n    AtServer { server_time: u64 },\r\n    Pong { recv: Duration },\r\n}\r\n\r\n#[derive(Debug, Copy, Clone)]\r\npub struct Event {\r\n    pub ping_index: u64,\r\n    pub kind: EventKind,\r\n}\r\n\r\n#[derive(Clone)]\r\npub struct Point {\r\n    pub pending: bool,\r\n    pub index: u64,\r\n    pub sent: Duration,\r\n    pub total: Option<Duration>,\r\n    pub up: Option<Duration>,\r\n    at_server: Option<u64>, // In server time\r\n    recv: Option<Duration>,\r\n}\r\n\r\n#[derive(Debug, Clone)]\r\npub enum State {\r\n    Connecting,\r\n    Syncing,\r\n    Monitoring { at: String },\r\n}\r\n\r\npub struct Data {\r\n    pub state: Mutex<State>,\r\n    pub start: Instant,\r\n    pub limit: usize,\r\n    pub points: tokio::sync::Mutex<VecDeque<Point>>,\r\n    update_fn: UpdateFn,\r\n}\r\n\r\nimpl Data {\r\n    pub fn new(limit: usize, update_fn: UpdateFn) -> Self {\r\n        Self {\r\n            state: Mutex::new(State::Connecting),\r\n            start: Instant::now(),\r\n            limit,\r\n            points: tokio::sync::Mutex::new(VecDeque::new()),\r\n            update_fn,\r\n        }\r\n    }\r\n}\r\n\r\nasync fn test_async(\r\n    config: Config,\r\n    server: Option<&str>,\r\n    data: Arc<Data>,\r\n    stop: oneshot::Receiver<()>,\r\n) -> Result<(), anyhow::Error> {\r\n    let (control, at) = if let Some(server) = server {\r\n        (\r\n            connect((server, config.port), \"server\").await?,\r\n            server.to_owned(),\r\n        )\r\n    } else {\r\n        let server = discovery::locate(false).await?;\r\n        (connect(server.socket, \"server\").await?, server.at)\r\n    };\r\n\r\n    control.set_nodelay(true)?;\r\n\r\n    let server = control.peer_addr()?;\r\n\r\n    *data.state.lock() = State::Syncing;\r\n    (data.update_fn)();\r\n\r\n    let (rx, tx) = control.into_split();\r\n    let mut control_rx = FramedRead::new(rx, codec());\r\n    let mut control_tx = FramedWrite::new(tx, codec());\r\n\r\n    hello(&mut control_tx, &mut control_rx).await?;\r\n\r\n    send(&mut control_tx, &ClientMessage::NewClient).await?;\r\n\r\n    let setup_start = data.start;\r\n\r\n    let reply: ServerMessage = receive(&mut control_rx).await?;\r\n    let id = match reply {\r\n        ServerMessage::NewClient(Some(id)) => id,\r\n        ServerMessage::NewClient(None) => bail!(\"Server was unable to create client\"),\r\n        _ => bail!(\"Unexpected message {:?}\", reply),\r\n    };\r\n\r\n    let local_udp = if server.is_ipv6() {\r\n        SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)\r\n    } else {\r\n        SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)\r\n    };\r\n\r\n    let mut ping_index = 0;\r\n\r\n    let LatencyResult {\r\n        threshold: latency,\r\n        server_offset: mut server_time_offset,\r\n        mut control_rx,\r\n        ..\r\n    } = measure_latency(\r\n        id,\r\n        &mut ping_index,\r\n        &mut control_tx,\r\n        control_rx,\r\n        server,\r\n        local_udp,\r\n        setup_start,\r\n    )\r\n    .await?;\r\n\r\n    let sample_interval = Duration::from_secs(2);\r\n    let sample_count =\r\n        (((sample_interval.as_secs_f64() * 0.6) / config.ping_interval.as_secs_f64()).round()\r\n            as usize)\r\n            .clamp(10, 1000);\r\n\r\n    let latency_filter =\r\n        Duration::from_secs_f64(latency.as_secs_f64() * 1.01) + Duration::from_micros(500);\r\n\r\n    let mut samples: VecDeque<u64> = iter::repeat(server_time_offset)\r\n        .take(sample_count)\r\n        .collect();\r\n\r\n    let udp_socket = Arc::new(net::UdpSocket::bind(local_udp).await?);\r\n    udp_socket.connect(server).await?;\r\n    let udp_socket2 = udp_socket.clone();\r\n\r\n    let ping_interval = config.ping_interval;\r\n\r\n    let (event_tx, mut event_rx) = channel(1000);\r\n\r\n    send(&mut control_tx, &ClientMessage::GetMeasurements).await?;\r\n\r\n    let event_tx_ = event_tx.clone();\r\n    let measures = tokio::spawn(async move {\r\n        let overload_;\r\n\r\n        loop {\r\n            let reply: ServerMessage = receive(&mut control_rx).await?;\r\n            match reply {\r\n                ServerMessage::LatencyMeasures(measures) => {\r\n                    for measure in measures {\r\n                        event_tx_\r\n                            .send(Event {\r\n                                ping_index: measure.index,\r\n                                kind: EventKind::AtServer {\r\n                                    server_time: measure.time,\r\n                                },\r\n                            })\r\n                            .await?;\r\n                    }\r\n                }\r\n                ServerMessage::MeasurementsDone { overload } => {\r\n                    overload_ = overload;\r\n                    break;\r\n                }\r\n                _ => bail!(\"Unexpected message {:?}\", reply),\r\n            };\r\n        }\r\n\r\n        Ok(overload_)\r\n    });\r\n\r\n    let ping_recv = tokio::spawn(ping_recv(\r\n        event_tx.clone(),\r\n        setup_start,\r\n        udp_socket2.clone(),\r\n    ));\r\n\r\n    time::sleep(Duration::from_millis(50)).await;\r\n\r\n    *data.state.lock() = State::Monitoring { at };\r\n    (data.update_fn)();\r\n\r\n    let ping_send = tokio::spawn(ping_send(\r\n        event_tx.clone(),\r\n        ping_index,\r\n        id,\r\n        setup_start,\r\n        udp_socket2.clone(),\r\n        ping_interval,\r\n    ));\r\n\r\n    tokio::spawn(async move {\r\n        let mut sync_time = |server_time_offset: &mut u64, point: &Point| {\r\n            if let Some(at_server) = point.at_server {\r\n                if let Some(recv) = point.recv {\r\n                    let sent = point.sent;\r\n                    let latency = recv.saturating_sub(sent);\r\n\r\n                    if latency > latency_filter {\r\n                        return;\r\n                    }\r\n\r\n                    let server_time = at_server;\r\n                    let server_pong = sent + latency / 2;\r\n\r\n                    let server_offset = (server_pong.as_micros() as u64).wrapping_sub(server_time);\r\n\r\n                    samples.push_front(server_offset);\r\n                    samples.pop_back();\r\n\r\n                    let current = *server_time_offset;\r\n\r\n                    let sum: i64 = samples\r\n                        .iter()\r\n                        .map(|server_offset| server_offset.wrapping_sub(current) as i64)\r\n                        .sum();\r\n\r\n                    let offset = sum / (samples.len() as i64);\r\n\r\n                    *server_time_offset = current.wrapping_add(offset as u64);\r\n                }\r\n            }\r\n        };\r\n\r\n        while let Some(event) = event_rx.recv().await {\r\n            {\r\n                let mut points = data.points.lock().await;\r\n                let i = points\r\n                    .iter()\r\n                    .enumerate()\r\n                    .find(|r| r.1.index == event.ping_index)\r\n                    .map(|r| r.0);\r\n                match event.kind {\r\n                    EventKind::Sent { sent } => {\r\n                        while points.len() > data.limit {\r\n                            points.pop_back();\r\n                        }\r\n                        points.push_front(Point {\r\n                            pending: true,\r\n                            index: event.ping_index,\r\n                            sent,\r\n                            up: None,\r\n                            total: None,\r\n                            at_server: None,\r\n                            recv: None,\r\n                        });\r\n                    }\r\n                    EventKind::AtServer { server_time } => {\r\n                        i.map(|i| {\r\n                            let time =\r\n                                Duration::from_micros(server_time.wrapping_add(server_time_offset));\r\n                            points[i].up = Some(time.saturating_sub(points[i].sent));\r\n                            points[i].at_server = Some(server_time);\r\n                            sync_time(&mut server_time_offset, &points[i]);\r\n                        });\r\n                    }\r\n                    EventKind::Pong { recv } => {\r\n                        i.map(|i| {\r\n                            points[i].pending = false;\r\n                            points[i].recv = Some(recv);\r\n                            points[i].total = Some(recv.saturating_sub(points[i].sent));\r\n                            sync_time(&mut server_time_offset, &points[i]);\r\n                        });\r\n                    }\r\n                    EventKind::Timeout => {\r\n                        i.map(|i| {\r\n                            points[i].pending = false;\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n            (data.update_fn)();\r\n        }\r\n    });\r\n\r\n    select! {\r\n        result = ping_recv.fuse() => {\r\n            result??;\r\n        },\r\n        result = ping_send.fuse() => {\r\n            result??;\r\n        },\r\n        result = stop.fuse() => {\r\n            result?;\r\n        },\r\n    }\r\n\r\n    send(&mut control_tx, &ClientMessage::StopMeasurements).await?;\r\n    send(&mut control_tx, &ClientMessage::Done).await?;\r\n\r\n    let _server_overload = measures.await??;\r\n\r\n    Ok(())\r\n}\r\n\r\nasync fn ping_send(\r\n    event_tx: Sender<Event>,\r\n    mut ping_index: u64,\r\n    id: u64,\r\n    setup_start: Instant,\r\n    socket: Arc<UdpSocket>,\r\n    interval: Duration,\r\n) -> Result<(), anyhow::Error> {\r\n    let mut buf = [0; 64];\r\n\r\n    let mut interval = time::interval(interval);\r\n\r\n    loop {\r\n        interval.tick().await;\r\n\r\n        let current = setup_start.elapsed();\r\n\r\n        let ping = Ping {\r\n            id,\r\n            index: ping_index,\r\n        };\r\n\r\n        let mut cursor = Cursor::new(&mut buf[..]);\r\n        bincode::serialize_into(&mut cursor, &ping).unwrap();\r\n        let buf = &cursor.get_ref()[0..(cursor.position() as usize)];\r\n\r\n        udp_handle(socket.send(buf).await.map(|_| ())).context(\"Unable to send UDP ping packet\")?;\r\n\r\n        event_tx\r\n            .send(Event {\r\n                ping_index,\r\n                kind: EventKind::Sent { sent: current },\r\n            })\r\n            .await?;\r\n\r\n        let event_tx = event_tx.clone();\r\n        tokio::spawn(async move {\r\n            time::sleep(Duration::from_secs(1)).await;\r\n            event_tx\r\n                .send(Event {\r\n                    ping_index,\r\n                    kind: EventKind::Timeout,\r\n                })\r\n                .await\r\n                .ok();\r\n        });\r\n\r\n        ping_index += 1;\r\n    }\r\n}\r\n\r\nasync fn ping_recv(\r\n    event_tx: Sender<Event>,\r\n    setup_start: Instant,\r\n    socket: Arc<UdpSocket>,\r\n) -> Result<Vec<(Ping, Duration)>, anyhow::Error> {\r\n    let mut buf = [0; 64];\r\n\r\n    loop {\r\n        let result = socket.recv(&mut buf).await;\r\n\r\n        let current = setup_start.elapsed();\r\n        let len = result?;\r\n        let buf = buf\r\n            .get_mut(..len)\r\n            .ok_or_else(|| anyhow!(\"Pong too large\"))?;\r\n        let ping: Ping = bincode::deserialize(buf)?;\r\n\r\n        event_tx\r\n            .send(Event {\r\n                ping_index: ping.index,\r\n                kind: EventKind::Pong { recv: current },\r\n            })\r\n            .await?;\r\n    }\r\n}\r\n\r\npub fn test_callback(\r\n    config: Config,\r\n    host: Option<&str>,\r\n    data: Arc<Data>,\r\n    done: Box<dyn FnOnce(Option<Result<(), String>>) + Send>,\r\n) -> oneshot::Sender<()> {\r\n    let (stop_tx, stop_rx) = oneshot::channel();\r\n    let (force_stop_tx, force_stop_rx) = oneshot::channel();\r\n    let host = host.map(|host| host.to_string());\r\n    thread::spawn(move || {\r\n        let rt = tokio::runtime::Runtime::new().unwrap();\r\n\r\n        done(rt.block_on(async move {\r\n            let (tx, rx) = oneshot::channel();\r\n            task::spawn(async move {\r\n                stop_rx.await.ok();\r\n                tx.send(()).ok();\r\n                time::sleep(Duration::from_secs(5)).await;\r\n                force_stop_tx.send(()).ok();\r\n            });\r\n\r\n            let mut result = task::spawn(async move {\r\n                test_async(config, host.as_deref(), data, rx)\r\n                    .await\r\n                    .map_err(|error| format!(\"{:?}\", error))\r\n            })\r\n            .fuse();\r\n\r\n            select! {\r\n                result = result => {\r\n                    Some(result.map_err(|error| error.to_string()).and_then(|result| result))\r\n                },\r\n                result = force_stop_rx.fuse() => {\r\n                    result.ok();\r\n                    None\r\n                },\r\n            }\r\n        }));\r\n    });\r\n    stop_tx\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/lib.rs",
    "content": "#![allow(\n    clippy::new_without_default,\n    clippy::too_many_arguments,\n    clippy::useless_format,\n    clippy::type_complexity,\n    clippy::collapsible_else_if,\n    clippy::option_map_unit_fn\n)]\n\nconst VERSION: &str = \"0.3.3-dev\";\n\npub fn version() -> String {\n    if !VERSION.ends_with(\"-dev\") {\n        VERSION.to_owned()\n    } else {\n        let commit = option_env!(\"GIT_COMMIT\")\n            .map(|commit| format!(\"commit {}\", commit))\n            .unwrap_or(\"unknown commit\".to_owned());\n        format!(\"{} ({})\", VERSION, commit)\n    }\n}\n\npub fn with_time(msg: &str) -> String {\n    let time = chrono::Local::now().format(\"%Y-%m-%d %H:%M:%S\");\n    format!(\"[{}] {}\", time, msg)\n}\n\nmod common;\nmod discovery;\n#[cfg(feature = \"client\")]\npub use common::Config;\n#[cfg(feature = \"client\")]\npub mod file_format;\n#[cfg(feature = \"client\")]\npub mod latency;\nmod peer;\n#[cfg(feature = \"client\")]\npub mod plot;\npub mod protocol;\n#[cfg(feature = \"client\")]\npub mod remote;\npub mod serve;\n#[cfg(feature = \"client\")]\npub mod test;\n"
  },
  {
    "path": "src/crusader-lib/src/peer.rs",
    "content": "use crate::common::{connect, LatencyResult};\r\n#[cfg(feature = \"client\")]\r\nuse crate::common::{Config, Msg};\r\n#[cfg(feature = \"client\")]\r\nuse crate::discovery;\r\nuse crate::protocol::PeerLatency;\r\nuse crate::serve::State;\r\nuse crate::{\r\n    common::{hello, measure_latency, ping_recv, ping_send, TestState},\r\n    protocol::{codec, receive, send, ClientMessage, RawLatency, ServerMessage},\r\n};\r\nuse anyhow::{bail, Context};\r\nuse std::{\r\n    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},\r\n    sync::Arc,\r\n    time::Duration,\r\n};\r\nuse tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};\r\nuse tokio::sync::watch;\r\nuse tokio::time::Instant;\r\nuse tokio::{\r\n    net::{self},\r\n    time,\r\n};\r\nuse tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};\r\n\r\n#[cfg(feature = \"client\")]\r\npub struct Peer {\r\n    msg: Msg,\r\n    tx: FramedWrite<OwnedWriteHalf, LengthDelimitedCodec>,\r\n    rx: FramedRead<OwnedReadHalf, LengthDelimitedCodec>,\r\n}\r\n\r\n#[cfg(feature = \"client\")]\r\nimpl Peer {\r\n    pub async fn start(&mut self) -> Result<(), anyhow::Error> {\r\n        let reply: ServerMessage = receive(&mut self.rx)\r\n            .await\r\n            .context(\"Peer failed to get ready\")?;\r\n        match reply {\r\n            ServerMessage::PeerReady { server_latency } => {\r\n                (self.msg)(&format!(\r\n                    \"Peer idle latency to server {:.2} ms\",\r\n                    Duration::from_nanos(server_latency).as_secs_f64() * 1000.0\r\n                ));\r\n            }\r\n            _ => bail!(\"Unexpected message {:?}\", reply),\r\n        };\r\n        send(&mut self.tx, &ClientMessage::PeerStart).await?;\r\n        let reply: ServerMessage = receive(&mut self.rx).await?;\r\n        match reply {\r\n            ServerMessage::PeerStarted => (),\r\n            _ => bail!(\"Unexpected message {:?}\", reply),\r\n        };\r\n        Ok(())\r\n    }\r\n\r\n    pub async fn stop(&mut self) -> Result<(), anyhow::Error> {\r\n        send(&mut self.tx, &ClientMessage::PeerStop).await?;\r\n        Ok(())\r\n    }\r\n\r\n    pub async fn complete(mut self) -> Result<(bool, Vec<PeerLatency>), anyhow::Error> {\r\n        let reply: ServerMessage = receive(&mut self.rx).await?;\r\n        match reply {\r\n            ServerMessage::PeerDone {\r\n                overload,\r\n                latencies,\r\n            } => Ok((overload, latencies)),\r\n            _ => bail!(\"Unexpected message {:?}\", reply),\r\n        }\r\n    }\r\n}\r\n\r\n#[cfg(feature = \"client\")]\r\npub async fn connect_to_peer(\r\n    config: Config,\r\n    server: SocketAddr,\r\n    latency_peer_server: Option<&str>,\r\n    estimated_duration: Duration,\r\n    msg: Msg,\r\n) -> Result<Peer, anyhow::Error> {\r\n    let control = if let Some(server) = latency_peer_server {\r\n        connect((server, config.port), \"latency peer\").await?\r\n    } else {\r\n        let server = discovery::locate(true).await?;\r\n        msg(&format!(\r\n            \"Found peer at {} running version {}\",\r\n            server.at, server.software_version\r\n        ));\r\n        connect(server.socket, \"latency peer\").await?\r\n    };\r\n    control.set_nodelay(true)?;\r\n\r\n    let peer_server = control.peer_addr()?;\r\n\r\n    msg(&format!(\"Connected to peer {}\", peer_server));\r\n\r\n    let (rx, tx) = control.into_split();\r\n    let mut control_rx = FramedRead::new(rx, codec());\r\n    let mut control_tx = FramedWrite::new(tx, codec());\r\n\r\n    hello(&mut control_tx, &mut control_rx).await?;\r\n\r\n    send(\r\n        &mut control_tx,\r\n        &ClientMessage::NewPeer {\r\n            server: match server.ip() {\r\n                IpAddr::V4(ip) => ip.to_ipv6_mapped(),\r\n                IpAddr::V6(ip) => ip,\r\n            }\r\n            .octets(),\r\n            port: server.port(),\r\n            ping_interval: config.ping_interval.as_millis() as u64,\r\n            estimated_duration: estimated_duration.as_millis(),\r\n        },\r\n    )\r\n    .await?;\r\n\r\n    let reply: ServerMessage = receive(&mut control_rx)\r\n        .await\r\n        .context(\"Failed to create peer\")?;\r\n    match reply {\r\n        ServerMessage::NewPeer => (),\r\n        _ => bail!(\"Unexpected message {:?}\", reply),\r\n    };\r\n\r\n    Ok(Peer {\r\n        msg,\r\n        rx: control_rx,\r\n        tx: control_tx,\r\n    })\r\n}\r\n\r\npub async fn run_peer(\r\n    state: Arc<State>,\r\n    server: SocketAddr,\r\n    ping_interval: Duration,\r\n    estimated_duration: Duration,\r\n    stream_rx: &mut FramedRead<OwnedReadHalf, LengthDelimitedCodec>,\r\n    stream_tx: &mut FramedWrite<OwnedWriteHalf, LengthDelimitedCodec>,\r\n) -> Result<(), anyhow::Error> {\r\n    let control = connect(server, \"server\").await?;\r\n    control.set_nodelay(true)?;\r\n\r\n    let server = control.peer_addr()?;\r\n\r\n    (state.msg)(&format!(\"Peer connected to server {}\", server));\r\n\r\n    let (rx, tx) = control.into_split();\r\n    let mut control_rx = FramedRead::new(rx, codec());\r\n    let mut control_tx = FramedWrite::new(tx, codec());\r\n\r\n    hello(&mut control_tx, &mut control_rx).await?;\r\n\r\n    send(&mut control_tx, &ClientMessage::NewClient).await?;\r\n\r\n    let setup_start = Instant::now();\r\n\r\n    let reply: ServerMessage = receive(&mut control_rx).await?;\r\n    let id = match reply {\r\n        ServerMessage::NewClient(Some(id)) => id,\r\n        ServerMessage::NewClient(None) => bail!(\"Server was unable to create client\"),\r\n        _ => bail!(\"Unexpected message {:?}\", reply),\r\n    };\r\n\r\n    send(stream_tx, &ServerMessage::NewPeer).await?;\r\n\r\n    let local_udp = if server.is_ipv6() {\r\n        SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)\r\n    } else {\r\n        SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)\r\n    };\r\n\r\n    let mut ping_index = 0;\r\n\r\n    let LatencyResult {\r\n        latency,\r\n        server_pong: pre_server_pong,\r\n        server_time: pre_server_time,\r\n        mut control_rx,\r\n        ..\r\n    } = measure_latency(\r\n        id,\r\n        &mut ping_index,\r\n        &mut control_tx,\r\n        control_rx,\r\n        server,\r\n        local_udp,\r\n        setup_start,\r\n    )\r\n    .await?;\r\n\r\n    (state.msg)(&format!(\r\n        \"Peer idle latency to server {:.2} ms\",\r\n        latency.as_secs_f64() * 1000.0\r\n    ));\r\n\r\n    let udp_socket = Arc::new(net::UdpSocket::bind(local_udp).await?);\r\n    udp_socket.connect(server).await?;\r\n    let udp_socket2 = udp_socket.clone();\r\n\r\n    let (state_tx, state_rx) = watch::channel((TestState::Setup, setup_start));\r\n\r\n    send(&mut control_tx, &ClientMessage::GetMeasurements).await?;\r\n\r\n    let measures = tokio::spawn(async move {\r\n        let mut latencies = Vec::new();\r\n        let overload_;\r\n\r\n        loop {\r\n            let reply: ServerMessage = receive(&mut control_rx).await?;\r\n            match reply {\r\n                ServerMessage::LatencyMeasures(measures) => {\r\n                    latencies.extend(measures.into_iter());\r\n                }\r\n                ServerMessage::MeasurementsDone { overload } => {\r\n                    overload_ = overload;\r\n                    break;\r\n                }\r\n                _ => bail!(\"Unexpected message {:?}\", reply),\r\n            };\r\n        }\r\n\r\n        Ok((latencies, overload_, control_rx))\r\n    });\r\n\r\n    send(\r\n        stream_tx,\r\n        &ServerMessage::PeerReady {\r\n            server_latency: latency.as_nanos() as u64,\r\n        },\r\n    )\r\n    .await?;\r\n\r\n    let reply: ClientMessage = receive(stream_rx).await?;\r\n    match reply {\r\n        ClientMessage::PeerStart => (),\r\n        _ => bail!(\"Unexpected message {:?}\", reply),\r\n    };\r\n\r\n    let ping_start_index = ping_index;\r\n    let ping_send = tokio::spawn(ping_send(\r\n        ping_index,\r\n        id,\r\n        state_rx.clone(),\r\n        setup_start,\r\n        udp_socket2.clone(),\r\n        ping_interval,\r\n        estimated_duration,\r\n    ));\r\n\r\n    let ping_recv = tokio::spawn(ping_recv(\r\n        state_rx.clone(),\r\n        setup_start,\r\n        udp_socket2.clone(),\r\n        ping_interval,\r\n        estimated_duration,\r\n    ));\r\n\r\n    send(stream_tx, &ServerMessage::PeerStarted).await?;\r\n\r\n    // Wait for client to complete test\r\n    let reply: ClientMessage = receive(stream_rx).await?;\r\n    match reply {\r\n        ClientMessage::PeerStop => (),\r\n        _ => bail!(\"Unexpected message {:?}\", reply),\r\n    };\r\n\r\n    state_tx.send((TestState::End, Instant::now())).ok();\r\n\r\n    // Wait for pings to return\r\n    time::sleep(Duration::from_millis(500)).await;\r\n\r\n    state_tx.send((TestState::EndPingRecv, Instant::now())).ok();\r\n\r\n    let (pings_sent, mut ping_index) = ping_send.await??;\r\n    let mut pongs = ping_recv.await??;\r\n\r\n    send(&mut control_tx, &ClientMessage::StopMeasurements).await?;\r\n\r\n    let (mut latencies, server_overload, control_rx) = measures.await??;\r\n\r\n    let LatencyResult {\r\n        server_pong: post_server_pong,\r\n        server_time: post_server_time,\r\n        ..\r\n    } = measure_latency(\r\n        id,\r\n        &mut ping_index,\r\n        &mut control_tx,\r\n        control_rx,\r\n        server,\r\n        local_udp,\r\n        setup_start,\r\n    )\r\n    .await?;\r\n\r\n    send(&mut control_tx, &ClientMessage::Done).await?;\r\n\r\n    let server_time = post_server_time.wrapping_sub(pre_server_time);\r\n    let peer_time = post_server_pong.saturating_sub(pre_server_pong);\r\n    let peer_time_micros = peer_time.as_micros() as f64;\r\n    let ratio = peer_time_micros / server_time as f64;\r\n    let inv_ratio = server_time as f64 / peer_time_micros;\r\n\r\n    let to_peer_time = |server_time: u64| -> u64 {\r\n        let time = server_time.wrapping_sub(pre_server_time);\r\n        let time = (time as f64 * ratio) as u64;\r\n        (pre_server_pong.as_micros() as u64).saturating_add(time)\r\n    };\r\n\r\n    let to_server_time = |peer_time: Duration| -> u64 {\r\n        let time = peer_time.saturating_sub(pre_server_pong).as_micros() as u64;\r\n        let time = (time as f64 * inv_ratio) as u64;\r\n        pre_server_time.wrapping_add(time)\r\n    };\r\n\r\n    latencies.sort_by_key(|d| d.index);\r\n    pongs.sort_by_key(|d| d.0.index);\r\n    let pings: Vec<_> = pings_sent\r\n        .into_iter()\r\n        .enumerate()\r\n        .map(|(index, sent)| {\r\n            let index = index as u64 + ping_start_index;\r\n            let mut latency = latencies\r\n                .binary_search_by_key(&index, |e| e.index)\r\n                .ok()\r\n                .map(|ping| RawLatency {\r\n                    total: None,\r\n                    up: Duration::from_micros(to_peer_time(latencies[ping].time))\r\n                        .saturating_sub(sent),\r\n                });\r\n\r\n            latency.as_mut().map(|latency| {\r\n                pongs\r\n                    .binary_search_by_key(&index, |e| e.0.index)\r\n                    .ok()\r\n                    .map(|ping| {\r\n                        latency.total = Some(pongs[ping].1.saturating_sub(sent));\r\n                    });\r\n            });\r\n\r\n            PeerLatency {\r\n                sent: to_server_time(sent),\r\n                latency,\r\n            }\r\n        })\r\n        .collect();\r\n\r\n    send(\r\n        stream_tx,\r\n        &ServerMessage::PeerDone {\r\n            overload: server_overload,\r\n            latencies: pings,\r\n        },\r\n    )\r\n    .await?;\r\n\r\n    Ok(())\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/plot.rs",
    "content": "use anyhow::{anyhow, Context};\r\nuse image::{ImageBuffer, ImageFormat, Rgb};\r\nuse plotters::coord::types::RangedCoordf64;\r\nuse plotters::coord::Shift;\r\nuse plotters::prelude::*;\r\nuse plotters::style::text_anchor::{HPos, Pos, VPos};\r\nuse plotters::style::{register_font, RGBColor};\r\n\r\nuse std::collections::{HashMap, HashSet};\r\nuse std::path::Path;\r\nuse std::time::Duration;\r\nuse std::{cmp, fmt::Write, mem};\r\n\r\nuse crate::file_format::{RawPing, RawResult, TestData, TestKind};\r\nuse crate::protocol::RawLatency;\r\nuse crate::test::{unique, PlotConfig};\r\n\r\nconst UP_COLOR: RGBColor = RGBColor(37, 83, 169);\r\nconst DOWN_COLOR: RGBColor = RGBColor(95, 145, 62);\r\n\r\nfn darken(color: RGBColor, d: f64) -> RGBColor {\r\n    RGBColor(\r\n        (color.0 as f64 * d).round() as u8,\r\n        (color.1 as f64 * d).round() as u8,\r\n        (color.2 as f64 * d).round() as u8,\r\n    )\r\n}\r\n\r\npub fn register_fonts() {\r\n    register_font(\r\n        \"sans-serif\",\r\n        FontStyle::Normal,\r\n        include_bytes!(\"../Ubuntu-Light.ttf\"),\r\n    )\r\n    .map_err(|_| ())\r\n    .unwrap();\r\n}\r\n\r\nimpl RawResult {\r\n    pub fn to_test_result(&self) -> TestResult {\r\n        let throughput_interval = self.config.bandwidth_interval;\r\n\r\n        let stream_groups: Vec<_> = self\r\n            .stream_groups\r\n            .iter()\r\n            .map(|group| TestStreamGroup {\r\n                download: group.download,\r\n                both: group.both,\r\n                streams: (0..(group.streams.len()))\r\n                    .map(|i| {\r\n                        let bytes: Vec<_> = (0..=i)\r\n                            .map(|i| to_float(&group.streams[i].to_vec()))\r\n                            .collect();\r\n                        let bytes: Vec<_> = bytes.iter().map(|stream| stream.as_slice()).collect();\r\n                        TestStream {\r\n                            data: sum_bytes(&bytes, throughput_interval),\r\n                        }\r\n                    })\r\n                    .collect(),\r\n            })\r\n            .collect();\r\n\r\n        let process_bytes = |bytes: Vec<Vec<(u64, u64)>>| -> Vec<(u64, f64)> {\r\n            let bytes: Vec<_> = bytes.iter().map(|stream| to_float(stream)).collect();\r\n            let bytes: Vec<_> = bytes.iter().map(|stream| stream.as_slice()).collect();\r\n            sum_bytes(&bytes, throughput_interval)\r\n        };\r\n\r\n        let groups: Vec<_> = self\r\n            .stream_groups\r\n            .iter()\r\n            .map(|group| {\r\n                let streams: Vec<_> = group.streams.iter().map(|stream| stream.to_vec()).collect();\r\n                let single = process_bytes(streams);\r\n                (group, single)\r\n            })\r\n            .collect();\r\n\r\n        let find = |download, both| {\r\n            groups\r\n                .iter()\r\n                .find(|group| group.0.download == download && group.0.both == both)\r\n                .map(|group| group.1.clone())\r\n        };\r\n\r\n        let download_bytes_sum = find(true, false);\r\n        let both_download_bytes_sum = find(true, true);\r\n\r\n        let combined_download_bytes: Vec<_> = [\r\n            download_bytes_sum.as_deref(),\r\n            both_download_bytes_sum.as_deref(),\r\n        ]\r\n        .into_iter()\r\n        .flatten()\r\n        .collect();\r\n        let combined_download_bytes = sum_bytes(&combined_download_bytes, throughput_interval);\r\n\r\n        let upload_bytes_sum = find(false, false);\r\n\r\n        let both_upload_bytes_sum = find(false, true);\r\n\r\n        let combined_upload_bytes: Vec<_> = [\r\n            upload_bytes_sum.as_deref(),\r\n            both_upload_bytes_sum.as_deref(),\r\n        ]\r\n        .into_iter()\r\n        .flatten()\r\n        .collect();\r\n        let combined_upload_bytes = sum_bytes(&combined_upload_bytes, throughput_interval);\r\n\r\n        let both_bytes = self.both().then(|| {\r\n            sum_bytes(\r\n                &[\r\n                    both_download_bytes_sum.as_deref().unwrap(),\r\n                    both_upload_bytes_sum.as_deref().unwrap(),\r\n                ],\r\n                throughput_interval,\r\n            )\r\n        });\r\n\r\n        let pings = self.pings.clone();\r\n\r\n        let mut throughputs = HashMap::new();\r\n\r\n        let mut add_throughput =\r\n            |stream: &Option<Vec<(u64, f64)>>, kind: TestKind, sub: TestKind| {\r\n                if let Some(stream) = stream {\r\n                    if let Some(t) = throughput(\r\n                        stream,\r\n                        self.test_data.iter().find(|d| d.kind == kind),\r\n                        self.config.load_duration,\r\n                    ) {\r\n                        throughputs.insert((kind, sub), t);\r\n                    }\r\n                }\r\n            };\r\n\r\n        add_throughput(&download_bytes_sum, TestKind::Download, TestKind::Download);\r\n        add_throughput(&upload_bytes_sum, TestKind::Upload, TestKind::Upload);\r\n        add_throughput(\r\n            &both_download_bytes_sum,\r\n            TestKind::Bidirectional,\r\n            TestKind::Download,\r\n        );\r\n        add_throughput(\r\n            &both_upload_bytes_sum,\r\n            TestKind::Bidirectional,\r\n            TestKind::Upload,\r\n        );\r\n        add_throughput(\r\n            &both_bytes,\r\n            TestKind::Bidirectional,\r\n            TestKind::Bidirectional,\r\n        );\r\n\r\n        let add_latency = |map: &mut HashMap<Option<TestKind>, LatencySummary>,\r\n                           loss: &mut HashMap<Option<TestKind>, (f64, f64)>,\r\n                           stream: &Option<Vec<(u64, f64)>>,\r\n                           kind: TestKind,\r\n                           smooth_pings: &[RawPing],\r\n                           pings: &[RawPing]| {\r\n            if let Some(stream) = stream {\r\n                if let Some(t) = ping_peak(\r\n                    stream,\r\n                    self.test_data.iter().find(|d| d.kind == kind),\r\n                    self.config.load_duration,\r\n                    smooth_pings,\r\n                ) {\r\n                    map.insert(Some(kind), t);\r\n                }\r\n                if let Some(t) = ping_loss(\r\n                    stream,\r\n                    self.test_data.iter().find(|d| d.kind == kind),\r\n                    self.config.load_duration,\r\n                    pings,\r\n                ) {\r\n                    loss.insert(Some(kind), t);\r\n                }\r\n            }\r\n        };\r\n\r\n        let latency_map = |pings: &[RawPing]| {\r\n            let mut latencies = HashMap::new();\r\n            let mut loss = HashMap::new();\r\n\r\n            let smooth_pings = smooth_ping(\r\n                pings,\r\n                (self.config.ping_interval * 3).max(Duration::from_millis(200)),\r\n            );\r\n\r\n            add_latency(\r\n                &mut latencies,\r\n                &mut loss,\r\n                &download_bytes_sum,\r\n                TestKind::Download,\r\n                &smooth_pings,\r\n                pings,\r\n            );\r\n            add_latency(\r\n                &mut latencies,\r\n                &mut loss,\r\n                &upload_bytes_sum,\r\n                TestKind::Upload,\r\n                &smooth_pings,\r\n                pings,\r\n            );\r\n            add_latency(\r\n                &mut latencies,\r\n                &mut loss,\r\n                &both_bytes,\r\n                TestKind::Bidirectional,\r\n                &smooth_pings,\r\n                pings,\r\n            );\r\n\r\n            if self.idle() {\r\n                let whole_data = TestData {\r\n                    kind: TestKind::Bidirectional,\r\n                    start: self.start,\r\n                    end: self.start + self.duration,\r\n                };\r\n\r\n                if let Some(t) = ping_peak(&[], Some(&whole_data), self.duration, &smooth_pings) {\r\n                    latencies.insert(None, t);\r\n                }\r\n\r\n                if let Some(t) = ping_loss(&[], Some(&whole_data), self.duration, pings) {\r\n                    loss.insert(None, t);\r\n                }\r\n            }\r\n\r\n            LatencyLossSummary { latencies, loss }\r\n        };\r\n\r\n        let latencies = latency_map(&pings);\r\n        let peer_latencies = self\r\n            .peer_pings\r\n            .as_ref()\r\n            .map(|peer_pings| latency_map(peer_pings))\r\n            .unwrap_or_default();\r\n\r\n        TestResult {\r\n            raw_result: self.clone(),\r\n            start: self.start,\r\n            duration: self.duration,\r\n            pings,\r\n            both_bytes,\r\n            both_download_bytes: both_download_bytes_sum,\r\n            both_upload_bytes: both_upload_bytes_sum,\r\n            download_bytes: download_bytes_sum,\r\n            upload_bytes: upload_bytes_sum,\r\n            combined_download_bytes,\r\n            combined_upload_bytes,\r\n            stream_groups,\r\n            throughputs,\r\n            latencies,\r\n            peer_latencies,\r\n        }\r\n    }\r\n}\r\n\r\npub struct TestStream {\r\n    pub data: Vec<(u64, f64)>,\r\n}\r\n\r\npub struct TestStreamGroup {\r\n    pub download: bool,\r\n    pub both: bool,\r\n    pub streams: Vec<TestStream>,\r\n}\r\n\r\n#[derive(Debug)]\r\npub struct LatencySummary {\r\n    pub total: Duration,\r\n    pub down: Duration,\r\n    pub up: Duration,\r\n}\r\n\r\n#[derive(Default)]\r\npub struct LatencyLossSummary {\r\n    pub latencies: HashMap<Option<TestKind>, LatencySummary>,\r\n    pub loss: HashMap<Option<TestKind>, (f64, f64)>,\r\n}\r\n\r\npub struct TestResult {\r\n    pub raw_result: RawResult,\r\n    pub start: Duration,\r\n    pub duration: Duration,\r\n    pub download_bytes: Option<Vec<(u64, f64)>>,\r\n    pub upload_bytes: Option<Vec<(u64, f64)>>,\r\n    pub combined_download_bytes: Vec<(u64, f64)>,\r\n    pub combined_upload_bytes: Vec<(u64, f64)>,\r\n    pub both_download_bytes: Option<Vec<(u64, f64)>>,\r\n    pub both_upload_bytes: Option<Vec<(u64, f64)>>,\r\n    pub both_bytes: Option<Vec<(u64, f64)>>,\r\n    pub pings: Vec<RawPing>,\r\n    pub stream_groups: Vec<TestStreamGroup>,\r\n    pub throughputs: HashMap<(TestKind, TestKind), f64>,\r\n    pub latencies: LatencyLossSummary,\r\n    pub peer_latencies: LatencyLossSummary,\r\n}\r\n\r\nimpl TestResult {\r\n    pub fn summary(&self) -> Result<String, anyhow::Error> {\r\n        let mut o = String::new();\r\n\r\n        let width = 20;\r\n\r\n        let mut kind = |kind: Option<TestKind>| -> Result<(), anyhow::Error> {\r\n            writeln!(\r\n                &mut o,\r\n                \"-- {} test --\",\r\n                kind.map(|kind| kind.name()).unwrap_or(\"Idle\")\r\n            )?;\r\n\r\n            if let Some(kind) = kind {\r\n                if let Some(throughput) = self.throughputs.get(&(kind, kind)) {\r\n                    write!(\r\n                        &mut o,\r\n                        \"{:>width$}: {:.02} Mbps\",\r\n                        \"Throughput\",\r\n                        throughput,\r\n                        width = width\r\n                    )?;\r\n                    if kind == TestKind::Bidirectional {\r\n                        if let Some(down) = self\r\n                            .throughputs\r\n                            .get(&(TestKind::Bidirectional, TestKind::Download))\r\n                        {\r\n                            if let Some(up) = self\r\n                                .throughputs\r\n                                .get(&(TestKind::Bidirectional, TestKind::Upload))\r\n                            {\r\n                                write!(&mut o, \" ({:.02} Mbps down, {:.02} Mbps up)\", down, up)?;\r\n                            }\r\n                        }\r\n                    }\r\n                    writeln!(&mut o)?;\r\n                }\r\n            }\r\n\r\n            let mut latency =\r\n                |latencies: &LatencyLossSummary, peer: bool| -> Result<(), anyhow::Error> {\r\n                    if let Some(latency) = latencies.latencies.get(&kind) {\r\n                        let label = if peer { \"Peer latency\" } else { \"Latency\" };\r\n                        writeln!(\r\n                            &mut o,\r\n                            \"{:>width$}: {:.01} ms ({:.01} ms down, {:.01} ms up)\",\r\n                            label,\r\n                            latency.total.as_secs_f64() * 1000.0,\r\n                            latency.down.as_secs_f64() * 1000.0,\r\n                            latency.up.as_secs_f64() * 1000.0,\r\n                            width = width\r\n                        )?;\r\n                    }\r\n                    if let Some(&(down, up)) = latencies.loss.get(&kind) {\r\n                        let label = if peer {\r\n                            \"Peer packet loss\"\r\n                        } else {\r\n                            \"Packet loss\"\r\n                        };\r\n                        if down == 0.0 && up == 0.0 {\r\n                            writeln!(&mut o, \"{:>width$}: 0%\", label)?;\r\n                        } else {\r\n                            writeln!(\r\n                                &mut o,\r\n                                \"{:>width$}: {:.*}% down, {:.*}% up\",\r\n                                label,\r\n                                if down == 0.0 { 0 } else { 2 },\r\n                                down * 100.0,\r\n                                if up == 0.0 { 0 } else { 2 },\r\n                                up * 100.0,\r\n                                width = width\r\n                            )?;\r\n                        }\r\n                    }\r\n\r\n                    Ok(())\r\n                };\r\n\r\n            latency(&self.latencies, false)?;\r\n            latency(&self.peer_latencies, true)?;\r\n\r\n            writeln!(&mut o)?;\r\n\r\n            Ok(())\r\n        };\r\n\r\n        if self.raw_result.download() {\r\n            kind(Some(TestKind::Download))?;\r\n        }\r\n\r\n        if self.raw_result.upload() {\r\n            kind(Some(TestKind::Upload))?;\r\n        }\r\n\r\n        if self.raw_result.both() {\r\n            kind(Some(TestKind::Bidirectional))?;\r\n        }\r\n\r\n        if self.raw_result.idle() {\r\n            kind(None)?;\r\n        }\r\n\r\n        Ok(o)\r\n    }\r\n}\r\n\r\npub fn save_graph(\r\n    config: &PlotConfig,\r\n    result: &TestResult,\r\n    name: &str,\r\n    root_path: &Path,\r\n) -> Result<String, anyhow::Error> {\r\n    std::fs::create_dir_all(root_path)?;\r\n    let file = unique(name, \"png\", root_path);\r\n    save_graph_to_path(&root_path.join(&file), config, result)?;\r\n    Ok(file)\r\n}\r\n\r\npub fn save_graph_to_path(\r\n    path: &Path,\r\n    config: &PlotConfig,\r\n    result: &TestResult,\r\n) -> Result<(), anyhow::Error> {\r\n    let img = save_graph_to_mem(config, result).context(\"Unable to plot\")?;\r\n    img.save_with_format(&path, ImageFormat::Png)\r\n        .context(\"Unable to write plot to file\")\r\n}\r\n\r\npub(crate) struct ThroughputPlot<'a> {\r\n    name: &'static str,\r\n    color: RGBColor,\r\n    rates: Vec<(u64, f64)>,\r\n    smooth: Vec<(u64, f64)>,\r\n    bytes: Vec<&'a [(u64, f64)]>,\r\n    rate: Option<f64>,\r\n    phase: Option<TestKind>,\r\n    dual_rates: Option<(f64, f64)>,\r\n}\r\n\r\npub(crate) fn save_graph_to_mem(\r\n    config: &PlotConfig,\r\n    result: &TestResult,\r\n) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, anyhow::Error> {\r\n    let mut throughput = Vec::new();\r\n\r\n    let smooth_interval = cmp::min(\r\n        Duration::from_secs_f64(1.0),\r\n        result.raw_result.config.grace_duration,\r\n    );\r\n    let interval = result.raw_result.config.bandwidth_interval;\r\n\r\n    result.download_bytes.as_ref().map(|bytes| {\r\n        throughput.push(ThroughputPlot {\r\n            name: \"Download\",\r\n            color: DOWN_COLOR,\r\n            rates: to_rates(bytes),\r\n            smooth: smooth(bytes, interval, smooth_interval),\r\n            bytes: vec![bytes.as_slice()],\r\n            rate: result\r\n                .throughputs\r\n                .get(&(TestKind::Download, TestKind::Download))\r\n                .cloned(),\r\n            dual_rates: None,\r\n            phase: Some(TestKind::Download),\r\n        });\r\n    });\r\n\r\n    result.upload_bytes.as_ref().map(|bytes| {\r\n        throughput.push(ThroughputPlot {\r\n            name: \"Upload\",\r\n            color: UP_COLOR,\r\n            rates: to_rates(bytes),\r\n            smooth: smooth(bytes, interval, smooth_interval),\r\n            bytes: vec![bytes.as_slice()],\r\n            rate: result\r\n                .throughputs\r\n                .get(&(TestKind::Upload, TestKind::Upload))\r\n                .cloned(),\r\n            dual_rates: None,\r\n            phase: Some(TestKind::Upload),\r\n        });\r\n    });\r\n\r\n    result.both_download_bytes.as_ref().map(|bytes| {\r\n        throughput.push(ThroughputPlot {\r\n            name: \"Download\",\r\n            color: DOWN_COLOR,\r\n            rates: to_rates(bytes),\r\n            smooth: smooth(bytes, interval, smooth_interval),\r\n            bytes: vec![bytes.as_slice()],\r\n            rate: None,\r\n            dual_rates: None,\r\n            phase: None,\r\n        });\r\n    });\r\n\r\n    result.both_upload_bytes.as_ref().map(|bytes| {\r\n        throughput.push(ThroughputPlot {\r\n            name: \"Upload\",\r\n            color: UP_COLOR,\r\n            rates: to_rates(bytes),\r\n            smooth: smooth(bytes, interval, smooth_interval),\r\n            bytes: vec![bytes.as_slice()],\r\n            rate: None,\r\n            dual_rates: None,\r\n            phase: None,\r\n        });\r\n    });\r\n\r\n    result.both_bytes.as_ref().map(|both_bytes| {\r\n        throughput.push(ThroughputPlot {\r\n            name: \"Aggregate\",\r\n            color: RGBColor(149, 96, 153),\r\n            rates: to_rates(both_bytes),\r\n            smooth: smooth(both_bytes, interval, smooth_interval),\r\n            bytes: vec![both_bytes.as_slice()],\r\n            rate: result\r\n                .throughputs\r\n                .get(&(TestKind::Bidirectional, TestKind::Bidirectional))\r\n                .cloned(),\r\n            dual_rates: result\r\n                .throughputs\r\n                .get(&(TestKind::Bidirectional, TestKind::Download))\r\n                .cloned()\r\n                .and_then(|down| {\r\n                    result\r\n                        .throughputs\r\n                        .get(&(TestKind::Bidirectional, TestKind::Upload))\r\n                        .cloned()\r\n                        .map(|up| (down, up))\r\n                }),\r\n            phase: Some(TestKind::Bidirectional),\r\n        });\r\n    });\r\n\r\n    graph(\r\n        config,\r\n        result,\r\n        &result.pings,\r\n        &throughput,\r\n        result.start.as_secs_f64(),\r\n        result.duration.as_secs_f64(),\r\n    )\r\n}\r\n\r\npub fn float_max(iter: impl Iterator<Item = f64>) -> f64 {\r\n    let mut max = iter.fold(f64::NAN, f64::max);\r\n\r\n    if max.is_nan() {\r\n        max = 100.0;\r\n    }\r\n\r\n    max\r\n}\r\n\r\nfn to_float(stream: &[(u64, u64)]) -> Vec<(u64, f64)> {\r\n    stream.iter().map(|(t, v)| (*t, *v as f64)).collect()\r\n}\r\n\r\npub fn to_rates(stream: &[(u64, f64)]) -> Vec<(u64, f64)> {\r\n    let mut result: Vec<(u64, f64)> = (0..stream.len())\r\n        .map(|i| {\r\n            let rate = if i > 0 {\r\n                let bytes = stream[i].1 - stream[i - 1].1;\r\n                let duration = Duration::from_micros(stream[i].0 - stream[i - 1].0);\r\n                let mbits = (bytes * 8.0) / (1000.0 * 1000.0);\r\n                mbits / duration.as_secs_f64()\r\n            } else {\r\n                0.0\r\n            };\r\n            (stream[i].0, rate)\r\n        })\r\n        .collect();\r\n\r\n    // Insert dummy zero points for nicer graphs\r\n    if !result.is_empty() {\r\n        result.first().unwrap().0.checked_sub(1).map(|first| {\r\n            result.insert(0, (first, 0.0));\r\n        });\r\n        result.push((result.last().unwrap().0 + 1, 0.0));\r\n    }\r\n\r\n    result\r\n}\r\n\r\nfn throughput(\r\n    stream: &[(u64, f64)],\r\n    test_data: Option<&TestData>,\r\n    load_duration: Duration,\r\n) -> Option<f64> {\r\n    if stream.is_empty() {\r\n        return None;\r\n    }\r\n\r\n    let start_offset = (load_duration.as_secs_f64() * 0.2).min(2.0);\r\n    let end_offset = load_duration.as_secs_f64() - (load_duration.as_secs_f64() * 0.1).min(0.5);\r\n\r\n    let test_start = if let Some(test_data) = test_data {\r\n        test_data.start\r\n    } else {\r\n        Duration::from_micros(stream.iter().find(|e| e.1 > 0.0)?.0)\r\n    };\r\n    let start = (test_start + Duration::from_secs_f64(start_offset)).as_micros() as u64;\r\n    let end = (test_start + Duration::from_secs_f64(end_offset)).as_micros() as u64;\r\n    let end = if let Some(test_data) = test_data {\r\n        cmp::min(test_data.end.as_micros() as u64, end)\r\n    } else {\r\n        end\r\n    };\r\n\r\n    if start >= end {\r\n        return None;\r\n    }\r\n\r\n    let lookup = |point: u64| {\r\n        let i = stream.partition_point(|e| e.0 < point);\r\n        if i == stream.len() {\r\n            stream[i - 1]\r\n        } else {\r\n            stream[i]\r\n        }\r\n    };\r\n\r\n    let end = lookup(end);\r\n    let start = lookup(start);\r\n\r\n    let bytes = end.1 - start.1;\r\n    let time = end.0 - start.0;\r\n    let duration = Duration::from_micros(time).as_secs_f64();\r\n    let mbits = (bytes * 8.0) / (1000.0 * 1000.0);\r\n    Some(mbits / duration)\r\n}\r\n\r\nfn ping_peak(\r\n    stream: &[(u64, f64)],\r\n    test_data: Option<&TestData>,\r\n    load_duration: Duration,\r\n    pings: &[RawPing],\r\n) -> Option<LatencySummary> {\r\n    if pings.is_empty() {\r\n        return None;\r\n    }\r\n\r\n    let test_start = if let Some(test_data) = test_data {\r\n        test_data.start\r\n    } else {\r\n        Duration::from_micros(stream.iter().find(|e| e.1 > 0.0)?.0)\r\n    };\r\n    let start = test_start.as_micros() as u64;\r\n    let end = (test_start + load_duration).as_micros() as u64;\r\n    let end = if let Some(test_data) = test_data {\r\n        cmp::min(test_data.end.as_micros() as u64, end)\r\n    } else {\r\n        end\r\n    };\r\n\r\n    if start >= end {\r\n        return None;\r\n    }\r\n\r\n    let start = pings.partition_point(|p| (p.sent.as_micros() as u64) < start);\r\n    let end = pings.partition_point(|p| (p.sent.as_micros() as u64) <= end);\r\n    let values = pings.get(start..end)?;\r\n\r\n    let point = values\r\n        .iter()\r\n        .max_by_key(|v| v.latency.unwrap().total.unwrap())?;\r\n\r\n    Some(LatencySummary {\r\n        total: point.latency.unwrap().total.unwrap(),\r\n        down: point.latency.unwrap().down().unwrap(),\r\n        up: point.latency.unwrap().up,\r\n    })\r\n}\r\n\r\nfn ping_loss(\r\n    stream: &[(u64, f64)],\r\n    test_data: Option<&TestData>,\r\n    load_duration: Duration,\r\n    pings: &[RawPing],\r\n) -> Option<(f64, f64)> {\r\n    if pings.is_empty() {\r\n        return None;\r\n    }\r\n\r\n    let test_start = if let Some(test_data) = test_data {\r\n        test_data.start\r\n    } else {\r\n        Duration::from_micros(stream.iter().find(|e| e.1 > 0.0)?.0)\r\n    };\r\n    let start = test_start.as_micros() as u64;\r\n    let end = (test_start + load_duration).as_micros() as u64;\r\n    let end = if let Some(test_data) = test_data {\r\n        cmp::min(test_data.end.as_micros() as u64, end)\r\n    } else {\r\n        end\r\n    };\r\n\r\n    if start >= end {\r\n        return None;\r\n    }\r\n\r\n    let start = pings.partition_point(|p| (p.sent.as_micros() as u64) < start);\r\n    let end = pings.partition_point(|p| (p.sent.as_micros() as u64) <= end);\r\n    let values = pings.get(start..end)?;\r\n\r\n    let loss_up = values.iter().filter(|v| v.latency.is_none()).count();\r\n\r\n    let loss_down = values\r\n        .iter()\r\n        .filter(|v| v.latency.map(|l| l.total.is_none()).unwrap_or(false))\r\n        .count();\r\n\r\n    let count = values.len() as f64;\r\n\r\n    Some(((loss_down as f64) / count, (loss_up as f64) / count))\r\n}\r\n\r\npub fn smooth(\r\n    stream: &[(u64, f64)],\r\n    interval: Duration,\r\n    smoothing_interval: Duration,\r\n) -> Vec<(u64, f64)> {\r\n    if stream.is_empty() {\r\n        return Vec::new();\r\n    }\r\n\r\n    let interval = interval.as_micros() as u64;\r\n    let smoothing_interval = smoothing_interval.as_micros() as u64;\r\n\r\n    let m = cmp::max(\r\n        1,\r\n        ((smoothing_interval as f64 / 2.0) / (interval as f64)).ceil() as u64,\r\n    ) as i64;\r\n    let smoothing_interval = interval * (m as u64);\r\n\r\n    let min = stream.first().unwrap().0.saturating_sub(smoothing_interval);\r\n    let max = stream.last().unwrap().0 + smoothing_interval;\r\n\r\n    let mut data = Vec::new();\r\n\r\n    let lookup = |point: u64, m| {\r\n        if let Some(point) = point.checked_add_signed(m * interval as i64) {\r\n            match stream.binary_search_by_key(&point, |e| e.0) {\r\n                Ok(i) => stream[i].1,\r\n                Err(0) => 0.0,\r\n                Err(i) if i == stream.len() => stream.last().unwrap().1,\r\n                _ => panic!(\"unexpected index\"),\r\n            }\r\n        } else {\r\n            0.0\r\n        }\r\n    };\r\n\r\n    for point in (min..=max).step_by(interval as usize) {\r\n        let value = (-m..=m).map(|m| lookup(point, m)).sum::<f64>() / ((m as f64) * 2.0 + 1.0);\r\n        data.push((point, value));\r\n    }\r\n\r\n    to_rates(&data)\r\n}\r\n\r\nfn smooth_ping(pings: &[RawPing], interval: Duration) -> Vec<RawPing> {\r\n    if pings.is_empty() {\r\n        return Vec::new();\r\n    }\r\n\r\n    let interval = interval.as_micros() as u64;\r\n    let step = interval / 4;\r\n\r\n    let min = (pings.first().unwrap().sent.as_micros() as u64).saturating_sub(interval);\r\n    let max = (pings.last().unwrap().sent.as_micros() as u64) + interval;\r\n\r\n    let mut data = Vec::new();\r\n\r\n    for (i, point) in (min..=max).step_by(step as usize).enumerate() {\r\n        let start =\r\n            pings.partition_point(|p| (p.sent.as_micros() as u64) < point.saturating_sub(interval));\r\n        let stop = pings.partition_point(|p| (p.sent.as_micros() as u64) <= point + interval);\r\n        let values = pings.get(start..stop);\r\n\r\n        if let Some(points) = values {\r\n            let values: Vec<_> = points\r\n                .iter()\r\n                .filter_map(|v| {\r\n                    v.latency.and_then(|l| {\r\n                        l.total\r\n                            .map(|total| (total.as_secs_f64(), l.up.as_secs_f64()))\r\n                    })\r\n                })\r\n                .collect();\r\n            if values.len() > 2 {\r\n                data.push(RawPing {\r\n                    sent: Duration::from_micros(point),\r\n                    index: i as u64,\r\n                    latency: Some(RawLatency {\r\n                        total: Some(Duration::from_secs_f64(\r\n                            values.iter().map(|v| v.0).sum::<f64>() / (values.len() as f64),\r\n                        )),\r\n                        up: Duration::from_secs_f64(\r\n                            values.iter().map(|v| v.1).sum::<f64>() / (values.len() as f64),\r\n                        ),\r\n                    }),\r\n                });\r\n            }\r\n        }\r\n    }\r\n\r\n    data\r\n}\r\n\r\nfn sum_bytes(input: &[&[(u64, f64)]], interval: Duration) -> Vec<(u64, f64)> {\r\n    let interval = interval.as_micros() as u64;\r\n\r\n    let throughput: Vec<_> = input\r\n        .iter()\r\n        .map(|stream| interpolate(stream, interval))\r\n        .collect();\r\n\r\n    let min = throughput\r\n        .iter()\r\n        .map(|stream| stream.first().map(|e| e.0).unwrap_or(0))\r\n        .min()\r\n        .unwrap_or(0);\r\n\r\n    let max = throughput\r\n        .iter()\r\n        .map(|stream| stream.last().map(|e| e.0).unwrap_or(0))\r\n        .max()\r\n        .unwrap_or(0);\r\n\r\n    let mut data = Vec::new();\r\n\r\n    for point in (min..=max).step_by(interval as usize) {\r\n        let value = throughput\r\n            .iter()\r\n            .map(\r\n                |stream| match stream.binary_search_by_key(&point, |e| e.0) {\r\n                    Ok(i) => stream[i].1,\r\n                    Err(0) => 0.0,\r\n                    Err(i) if i == stream.len() => stream.last().unwrap().1,\r\n                    _ => panic!(\"unexpected index\"),\r\n                },\r\n            )\r\n            .sum();\r\n        data.push((point, value));\r\n    }\r\n\r\n    data\r\n}\r\n\r\nfn interpolate(input: &[(u64, f64)], interval: u64) -> Vec<(u64, f64)> {\r\n    if input.is_empty() {\r\n        return Vec::new();\r\n    }\r\n\r\n    let min = input.first().unwrap().0 / interval * interval;\r\n    let max = input.last().unwrap().0.div_ceil(interval) * interval;\r\n\r\n    let mut data = Vec::new();\r\n\r\n    for point in (min..=max).step_by(interval as usize) {\r\n        let i = input.partition_point(|e| e.0 < point);\r\n        let value = if i == input.len() {\r\n            input.last().unwrap().1\r\n        } else if input[i].0 == point || i == 0 {\r\n            input[i].1\r\n        } else {\r\n            let len = input[i].0 - input[i - 1].0;\r\n            if len == 0 {\r\n                input[i].1\r\n            } else {\r\n                let ratio = (point - input[i - 1].0) as f64 / len as f64;\r\n                let delta = input[i].1 - input[i - 1].1;\r\n                input[i - 1].1 + delta * ratio\r\n            }\r\n        };\r\n        data.push((point, value));\r\n    }\r\n\r\n    data\r\n}\r\n\r\nfn draw_centered(\r\n    x: i32,\r\n    y: i32,\r\n    text: &[(String, RGBColor)],\r\n    area: &DrawingArea<BitMapBackend<'_>, Shift>,\r\n) {\r\n    let small_style: TextStyle = (FontFamily::SansSerif, 14).into();\r\n\r\n    let size: i32 = text\r\n        .iter()\r\n        .map(|t| area.estimate_text_size(&t.0, &small_style).unwrap().0 as i32)\r\n        .sum::<i32>()\r\n        / 2;\r\n\r\n    let mut x = x + -size;\r\n\r\n    for (text, color) in text {\r\n        area.draw_text(\r\n            text,\r\n            &small_style\r\n                .pos(Pos::new(HPos::Left, VPos::Center))\r\n                .color(&color),\r\n            (x, y),\r\n        )\r\n        .unwrap();\r\n\r\n        x += area.estimate_text_size(text, &small_style).unwrap().0 as i32;\r\n    }\r\n}\r\n\r\nfn new_chart<'a, 'c>(\r\n    duration: f64,\r\n    padding_bottom: Option<i32>,\r\n    max: f64,\r\n    label: &str,\r\n    x_labels: bool,\r\n    area: &'a DrawingArea<BitMapBackend<'c>, Shift>,\r\n) -> ChartContext<'a, BitMapBackend<'c>, Cartesian2d<RangedCoordf64, RangedCoordf64>> {\r\n    let font = (FontFamily::SansSerif, 16);\r\n\r\n    let mut chart = ChartBuilder::on(area)\r\n        .margin(6)\r\n        .set_label_area_size(LabelAreaPosition::Left, 100)\r\n        .set_label_area_size(LabelAreaPosition::Right, 100)\r\n        .set_label_area_size(LabelAreaPosition::Bottom, padding_bottom.unwrap_or(20))\r\n        .build_cartesian_2d(0.0..duration, 0.0..max)\r\n        .unwrap();\r\n\r\n    chart\r\n        .plotting_area()\r\n        .fill(&RGBColor(248, 248, 248))\r\n        .unwrap();\r\n\r\n    let mut mesh = chart.configure_mesh();\r\n\r\n    mesh.disable_x_mesh().disable_y_mesh();\r\n\r\n    if x_labels {\r\n        mesh.x_labels(20).y_labels(10);\r\n    } else {\r\n        mesh.x_labels(0).y_labels(0);\r\n    }\r\n\r\n    mesh.x_label_style(font).y_label_style(font).y_desc(label);\r\n\r\n    mesh.draw().unwrap();\r\n\r\n    chart\r\n}\r\n\r\nfn legends<'a, 'b: 'a>(\r\n    chart: &mut ChartContext<'a, BitMapBackend<'b>, Cartesian2d<RangedCoordf64, RangedCoordf64>>,\r\n) {\r\n    let font = (FontFamily::SansSerif, 16);\r\n\r\n    chart\r\n        .configure_series_labels()\r\n        .background_style(WHITE.mix(0.8))\r\n        .label_font(font)\r\n        .border_style(BLACK)\r\n        .draw()\r\n        .unwrap();\r\n}\r\n\r\nconst PACKET_LOSS_AREA_SIZE: f64 = 70.0;\r\n\r\nfn latency<'a>(\r\n    config: &PlotConfig,\r\n    result: &TestResult,\r\n    pings: &[RawPing],\r\n    throughputs: &[ThroughputPlot],\r\n    summary: &LatencyLossSummary,\r\n    start: f64,\r\n    duration: f64,\r\n    area: &DrawingArea<BitMapBackend<'a>, Shift>,\r\n    packet_loss_area: Option<&DrawingArea<BitMapBackend<'a>, Shift>>,\r\n    peer: bool,\r\n) {\r\n    let new_area;\r\n    let new_packet_loss_area;\r\n    let (packet_loss_area, area) = if let Some(packet_loss_area) = packet_loss_area {\r\n        (packet_loss_area, area)\r\n    } else {\r\n        (new_area, new_packet_loss_area) =\r\n            area.split_vertically(area.relative_to_height(1.0) - PACKET_LOSS_AREA_SIZE);\r\n        (&new_packet_loss_area, &new_area)\r\n    };\r\n\r\n    // Draw latency summaries\r\n\r\n    let small_style: TextStyle = (FontFamily::SansSerif, 14).into();\r\n\r\n    let text_height = area.estimate_text_size(\"Wg\", &small_style).unwrap().1 as i32 + 5;\r\n\r\n    let center = text_height / 2 + 5;\r\n\r\n    let side = 107;\r\n\r\n    struct Summary {\r\n        phase: Option<TestKind>,\r\n        color: RGBColor,\r\n    }\r\n\r\n    let summaries: Vec<_> = throughputs\r\n        .iter()\r\n        .filter(|t| t.phase.is_some())\r\n        .map(|throughput| Summary {\r\n            phase: throughput.phase,\r\n            color: throughput.color,\r\n        })\r\n        .chain(result.raw_result.idle().then_some(Summary {\r\n            phase: None,\r\n            color: RGBColor(0, 0, 0),\r\n        }))\r\n        .collect();\r\n\r\n    let width =\r\n        (area.dim_in_pixel().0.saturating_sub(side * 2) as f64 / 1.14) / (summaries.len() as f64);\r\n\r\n    let (area, textarea) = area.split_vertically(area.dim_in_pixel().1 - (text_height as u32 + 10));\r\n\r\n    for (i, current_summary) in summaries.iter().enumerate() {\r\n        if let Some(latency) = summary.latencies.get(&current_summary.phase) {\r\n            let mut text = Vec::new();\r\n\r\n            text.push((\r\n                format!(\r\n                    \"{}\",\r\n                    current_summary\r\n                        .phase\r\n                        .map(|phase| phase.name())\r\n                        .unwrap_or(\"Latency\")\r\n                ),\r\n                darken(current_summary.color, 0.5),\r\n            ));\r\n            text.push((\r\n                format!(\": {:.01} ms\", latency.total.as_secs_f64() * 1000.0),\r\n                RGBColor(0, 0, 0),\r\n            ));\r\n\r\n            text.push((\r\n                format!(\"  ({:.01} \", latency.down.as_secs_f64() * 1000.0),\r\n                RGBColor(0, 0, 0),\r\n            ));\r\n            text.push((\"down\".to_owned(), darken(DOWN_COLOR, 0.5)));\r\n            text.push((\r\n                format!(\", {:.01} \", latency.up.as_secs_f64() * 1000.0),\r\n                RGBColor(0, 0, 0),\r\n            ));\r\n            text.push((\"up\".to_owned(), darken(UP_COLOR, 0.5)));\r\n            text.push((\")\".to_owned(), RGBColor(0, 0, 0)));\r\n\r\n            let x = side as f64 + width * (i as f64) + width / 2.0;\r\n\r\n            draw_centered(x.round() as i32, center, &text, &textarea);\r\n        }\r\n    }\r\n\r\n    // Draw packet loss summaries\r\n\r\n    let (packet_loss_area, textarea) =\r\n        packet_loss_area.split_vertically(packet_loss_area.dim_in_pixel().1);\r\n\r\n    for (i, current_summary) in summaries.iter().enumerate() {\r\n        if let Some(&(down, up)) = summary.loss.get(&current_summary.phase) {\r\n            let mut text = Vec::new();\r\n\r\n            text.push((\r\n                format!(\r\n                    \"{}\",\r\n                    current_summary\r\n                        .phase\r\n                        .map(|phase| phase.name())\r\n                        .unwrap_or(\"Packet loss\")\r\n                ),\r\n                darken(current_summary.color, 0.5),\r\n            ));\r\n            if down == 0.0 && up == 0.0 {\r\n                text.push((\": 0%\".to_owned(), RGBColor(0, 0, 0)));\r\n            } else {\r\n                text.push((\r\n                    format!(\": {:.1$}% \", down * 100.0, if down == 0.0 { 0 } else { 2 }),\r\n                    RGBColor(0, 0, 0),\r\n                ));\r\n                text.push((\"down\".to_owned(), darken(DOWN_COLOR, 0.5)));\r\n                text.push((\r\n                    format!(\", {:.1$}% \", up * 100.0, if up == 0.0 { 0 } else { 2 }),\r\n                    RGBColor(0, 0, 0),\r\n                ));\r\n                text.push((\"up\".to_owned(), darken(UP_COLOR, 0.5)));\r\n            }\r\n\r\n            let x = side as f64 + width * (i as f64) + width / 2.0;\r\n\r\n            draw_centered(x.round() as i32, -16, &text, &textarea);\r\n        }\r\n    }\r\n\r\n    // Draw latency plot\r\n\r\n    let max_latency = pings\r\n        .iter()\r\n        .filter_map(|d| d.latency)\r\n        .filter_map(|latency| latency.total)\r\n        .max()\r\n        .unwrap_or(Duration::from_millis(100))\r\n        .as_secs_f64()\r\n        * 1000.0;\r\n\r\n    let mut max_latency = max_latency * 1.05;\r\n\r\n    if let Some(max) = config.max_latency.map(|l| l as f64) {\r\n        if max > max_latency {\r\n            max_latency = max;\r\n        }\r\n    }\r\n\r\n    let mut chart = new_chart(\r\n        duration,\r\n        None,\r\n        max_latency,\r\n        if peer {\r\n            \"Peer latency (ms)\"\r\n        } else {\r\n            \"Latency (ms)\"\r\n        },\r\n        true,\r\n        &area,\r\n    );\r\n\r\n    let mut draw_latency =\r\n        |color: RGBColor, name: &str, get_latency: fn(&RawLatency) -> Option<Duration>| {\r\n            let mut data = Vec::new();\r\n\r\n            let flush = |data: &mut Vec<_>| {\r\n                let data = mem::take(data);\r\n\r\n                if data.len() == 1 {\r\n                    chart\r\n                        .plotting_area()\r\n                        .draw(&Circle::new(data[0], 1, color.filled()))\r\n                        .unwrap();\r\n                } else {\r\n                    chart\r\n                        .plotting_area()\r\n                        .draw(&PathElement::new(data, color))\r\n                        .unwrap();\r\n                }\r\n            };\r\n\r\n            for ping in pings {\r\n                match &ping.latency {\r\n                    Some(latency) => match get_latency(latency) {\r\n                        Some(latency) => {\r\n                            let x = ping.sent.as_secs_f64() - start;\r\n                            let y = latency.as_secs_f64() * 1000.0;\r\n\r\n                            data.push((x, y));\r\n                        }\r\n                        None => {\r\n                            flush(&mut data);\r\n                        }\r\n                    },\r\n                    None => {\r\n                        flush(&mut data);\r\n                    }\r\n                }\r\n            }\r\n\r\n            flush(&mut data);\r\n\r\n            chart\r\n                .draw_series(LineSeries::new(std::iter::empty(), color))\r\n                .unwrap()\r\n                .label(name)\r\n                .legend(move |(x, y)| {\r\n                    Rectangle::new([(x, y - 5), (x + 18, y + 3)], color.filled())\r\n                });\r\n        };\r\n\r\n    draw_latency(UP_COLOR, \"Up\", |latency| Some(latency.up));\r\n\r\n    draw_latency(DOWN_COLOR, \"Down\", |latency| latency.down());\r\n\r\n    draw_latency(RGBColor(50, 50, 50), \"Round-trip\", |latency| latency.total);\r\n\r\n    legends(&mut chart);\r\n\r\n    // Packet loss\r\n\r\n    let chart = new_chart(\r\n        duration,\r\n        Some(30),\r\n        1.0,\r\n        if peer { \"Peer loss\" } else { \"Packet loss\" },\r\n        false,\r\n        &packet_loss_area,\r\n    );\r\n\r\n    for ping in pings {\r\n        let x = ping.sent.as_secs_f64() - start;\r\n        if ping.latency.and_then(|latency| latency.total).is_none() {\r\n            let bold_size = 0.1111;\r\n            let (color, s, e, bold) = if result.raw_result.version >= 2 {\r\n                if ping.latency.is_none() {\r\n                    (UP_COLOR, 0.0, 0.5, Some(0.0 + bold_size))\r\n                } else {\r\n                    (DOWN_COLOR, 1.0, 0.5, Some(1.0 - bold_size))\r\n                }\r\n            } else {\r\n                (RGBColor(193, 85, 85), 0.0, 1.0, None)\r\n            };\r\n            chart\r\n                .plotting_area()\r\n                .draw(&PathElement::new(vec![(x, s), (x, e)], color))\r\n                .unwrap();\r\n            bold.map(|bold| {\r\n                chart\r\n                    .plotting_area()\r\n                    .draw(&PathElement::new(\r\n                        vec![(x, s), (x, bold)],\r\n                        color.stroke_width(2),\r\n                    ))\r\n                    .unwrap();\r\n            });\r\n        }\r\n    }\r\n\r\n    chart\r\n        .plotting_area()\r\n        .draw(&PathElement::new(vec![(0.0, 1.0), (duration, 1.0)], BLACK))\r\n        .unwrap();\r\n}\r\n\r\nfn plot_split_throughput(\r\n    config: &PlotConfig,\r\n    download: bool,\r\n    result: &TestResult,\r\n    start: f64,\r\n    duration: f64,\r\n    area: &DrawingArea<BitMapBackend, Shift>,\r\n) {\r\n    let groups: Vec<_> = result\r\n        .stream_groups\r\n        .iter()\r\n        .filter(|group| group.download == download)\r\n        .map(|group| TestStreamGroup {\r\n            download,\r\n            both: group.both,\r\n            streams: group\r\n                .streams\r\n                .iter()\r\n                .map(|stream| TestStream {\r\n                    data: to_rates(&stream.data),\r\n                })\r\n                .collect(),\r\n        })\r\n        .collect();\r\n\r\n    let max_throughput = float_max(\r\n        groups\r\n            .iter()\r\n            .flat_map(|group| group.streams.last().unwrap().data.iter())\r\n            .map(|e| e.1),\r\n    );\r\n\r\n    let mut max_throughput = max_throughput * 1.05;\r\n\r\n    if let Some(max) = config.max_throughput.map(|l| l as f64 / (1000.0 * 1000.0)) {\r\n        if max > max_throughput {\r\n            max_throughput = max;\r\n        }\r\n    }\r\n\r\n    let mut chart = new_chart(\r\n        duration,\r\n        None,\r\n        max_throughput,\r\n        if download {\r\n            \"Download (Mbps)\"\r\n        } else {\r\n            \"Upload (Mbps)\"\r\n        },\r\n        true,\r\n        area,\r\n    );\r\n\r\n    for group in groups {\r\n        for i in 0..(group.streams.len()) {\r\n            let main = i == group.streams.len() - 1;\r\n            let color = if download {\r\n                if main {\r\n                    DOWN_COLOR\r\n                } else {\r\n                    if i & 1 == 0 {\r\n                        RGBColor(188, 203, 177)\r\n                    } else {\r\n                        RGBColor(215, 223, 208)\r\n                    }\r\n                }\r\n            } else {\r\n                if main {\r\n                    UP_COLOR\r\n                } else {\r\n                    if i & 1 == 0 {\r\n                        RGBColor(159, 172, 202)\r\n                    } else {\r\n                        RGBColor(211, 217, 231)\r\n                    }\r\n                }\r\n            };\r\n            chart\r\n                .draw_series(LineSeries::new(\r\n                    group.streams[i].data.iter().map(|(time, rate)| {\r\n                        (Duration::from_micros(*time).as_secs_f64() - start, *rate)\r\n                    }),\r\n                    color,\r\n                ))\r\n                .unwrap();\r\n        }\r\n    }\r\n}\r\n\r\nfn plot_throughput(\r\n    config: &PlotConfig,\r\n    throughputs: &[ThroughputPlot],\r\n    start: f64,\r\n    duration: f64,\r\n    area: &DrawingArea<BitMapBackend<'_>, Shift>,\r\n) {\r\n    let max_throughput = float_max(\r\n        throughputs\r\n            .iter()\r\n            .flat_map(|list| list.rates.iter())\r\n            .map(|e| e.1),\r\n    );\r\n\r\n    let mut max_throughput = max_throughput * 1.05;\r\n\r\n    if let Some(max) = config.max_throughput.map(|l| l as f64 / (1000.0 * 1000.0)) {\r\n        if max > max_throughput {\r\n            max_throughput = max;\r\n        }\r\n    }\r\n\r\n    let small_style: TextStyle = (FontFamily::SansSerif, 14).into();\r\n\r\n    let text_height = area.estimate_text_size(\"Wg\", &small_style).unwrap().1 as i32 + 5;\r\n\r\n    let center = text_height / 2 + 5;\r\n\r\n    let side = 107;\r\n    let width = (area.dim_in_pixel().0.saturating_sub(side * 2) as f64 / 1.14)\r\n        / (throughputs.iter().filter(|t| t.phase.is_some()).count() as f64);\r\n\r\n    let (area, textarea) = area.split_vertically(area.dim_in_pixel().1 - (text_height as u32 + 10));\r\n\r\n    for (i, throughput) in throughputs.iter().filter(|t| t.phase.is_some()).enumerate() {\r\n        if let Some(rate) = throughput.rate {\r\n            let mut text = Vec::new();\r\n\r\n            text.push((\r\n                format!(\"{}\", throughput.phase.unwrap().name()),\r\n                darken(throughput.color, 0.5),\r\n            ));\r\n            text.push((format!(\": {:.02} Mbps\", rate), RGBColor(0, 0, 0)));\r\n\r\n            if let Some((down, up)) = throughput.dual_rates {\r\n                text.push((format!(\"  ({:.02} \", down), RGBColor(0, 0, 0)));\r\n                text.push((\"down\".to_owned(), darken(DOWN_COLOR, 0.5)));\r\n                text.push((format!(\", {:.02} \", up), RGBColor(0, 0, 0)));\r\n                text.push((\"up\".to_owned(), darken(UP_COLOR, 0.5)));\r\n                text.push((\")\".to_owned(), RGBColor(0, 0, 0)));\r\n            }\r\n\r\n            let x = side as f64 + width * (i as f64) + width / 2.0;\r\n\r\n            draw_centered(x.round() as i32, center, &text, &textarea);\r\n        }\r\n    }\r\n\r\n    let mut chart = new_chart(\r\n        duration,\r\n        None,\r\n        max_throughput,\r\n        \"Throughput (Mbps)\",\r\n        true,\r\n        &area,\r\n    );\r\n\r\n    let mut seen = HashSet::new();\r\n    for throughput in throughputs {\r\n        let series = chart\r\n            .draw_series(LineSeries::new(\r\n                throughput.rates.iter().map(|(time, rate)| {\r\n                    (Duration::from_micros(*time).as_secs_f64() - start, *rate)\r\n                }),\r\n                throughput.color,\r\n            ))\r\n            .unwrap();\r\n        if seen.insert(throughput.name.to_owned()) {\r\n            series.label(throughput.name).legend(move |(x, y)| {\r\n                Rectangle::new([(x, y - 5), (x + 18, y + 3)], throughput.color.filled())\r\n            });\r\n        }\r\n    }\r\n\r\n    for throughput in throughputs {\r\n        chart\r\n            .draw_series(LineSeries::new(\r\n                throughput.smooth.iter().map(|(time, rate)| {\r\n                    (Duration::from_micros(*time).as_secs_f64() - start, *rate)\r\n                }),\r\n                ShapeStyle {\r\n                    color: darken(throughput.color, 0.5).mix(0.5),\r\n                    filled: true,\r\n                    stroke_width: 2,\r\n                },\r\n            ))\r\n            .unwrap();\r\n    }\r\n\r\n    legends(&mut chart);\r\n}\r\n\r\npub(crate) fn bytes_transferred(\r\n    throughputs: &[ThroughputPlot],\r\n    start: f64,\r\n    duration: f64,\r\n    area: &DrawingArea<BitMapBackend, Shift>,\r\n) {\r\n    let max_bytes = float_max(\r\n        throughputs\r\n            .iter()\r\n            .flat_map(|list| list.bytes.iter())\r\n            .flat_map(|list| list.iter())\r\n            .map(|e| e.1),\r\n    );\r\n\r\n    let max_bytes = max_bytes / (1024.0 * 1024.0 * 1024.0);\r\n\r\n    let max_bytes = max_bytes * 1.05;\r\n\r\n    let mut chart = new_chart(\r\n        duration,\r\n        Some(50),\r\n        max_bytes,\r\n        \"Data transferred (GiB)\",\r\n        true,\r\n        area,\r\n    );\r\n\r\n    let mut seen = HashSet::new();\r\n    for throughput in throughputs {\r\n        for (i, bytes) in throughput.bytes.iter().enumerate() {\r\n            let series = chart\r\n                .draw_series(LineSeries::new(\r\n                    bytes.iter().map(|(time, bytes)| {\r\n                        (\r\n                            Duration::from_micros(*time).as_secs_f64() - start,\r\n                            *bytes / (1024.0 * 1024.0 * 1024.0),\r\n                        )\r\n                    }),\r\n                    &throughput.color,\r\n                ))\r\n                .unwrap();\r\n\r\n            if seen.insert(throughput.name.to_owned()) && i == 0 {\r\n                series.label(throughput.name).legend(move |(x, y)| {\r\n                    Rectangle::new([(x, y - 5), (x + 18, y + 3)], throughput.color.filled())\r\n                });\r\n            }\r\n        }\r\n    }\r\n\r\n    legends(&mut chart);\r\n}\r\n\r\npub(crate) fn graph(\r\n    config: &PlotConfig,\r\n    result: &TestResult,\r\n    pings: &[RawPing],\r\n    throughput: &[ThroughputPlot],\r\n    start: f64,\r\n    duration: f64,\r\n) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, anyhow::Error> {\r\n    let width = config.width.unwrap_or(1280) as u32;\r\n\r\n    let peer_latency = result.raw_result.peer_pings.is_some();\r\n\r\n    let mut def_height = 720;\r\n\r\n    if peer_latency {\r\n        def_height += 380;\r\n    }\r\n\r\n    if config.transferred {\r\n        def_height += 320;\r\n    }\r\n\r\n    let height = config.height.unwrap_or(def_height) as u32;\r\n\r\n    let mut data = vec![0; 3 * (width as usize * height as usize)];\r\n\r\n    let idle = result.raw_result.idle();\r\n\r\n    let title = config.title.as_deref().unwrap_or(if idle {\r\n        \"Latency\"\r\n    } else {\r\n        \"Latency under load\"\r\n    });\r\n\r\n    {\r\n        let root = BitMapBackend::with_buffer(&mut data, (width, height)).into_drawing_area();\r\n\r\n        root.fill(&WHITE).unwrap();\r\n\r\n        let style: TextStyle = (FontFamily::SansSerif, 26).into();\r\n\r\n        let medium_style: TextStyle = (FontFamily::SansSerif, 16).into();\r\n\r\n        let small_style: TextStyle = (FontFamily::SansSerif, 14).into();\r\n\r\n        let lines = 2;\r\n\r\n        let text_height =\r\n            (root.estimate_text_size(\"Wg\", &small_style).unwrap().1 as i32 + 5) * lines;\r\n\r\n        let center = text_height / 2 + 10;\r\n\r\n        root.draw_text(\r\n            title,\r\n            &style.pos(Pos::new(HPos::Center, VPos::Center)),\r\n            (width as i32 / 2, center),\r\n        )\r\n        .unwrap();\r\n\r\n        if result.raw_result.version >= 1 {\r\n            let top_margin = 10;\r\n            root.draw_text(\r\n                &format!(\r\n                    \"Connections: {} over IPv{}\",\r\n                    result.raw_result.streams(),\r\n                    if result.raw_result.ipv6 { 6 } else { 4 },\r\n                ),\r\n                &small_style.pos(Pos::new(HPos::Left, VPos::Top)),\r\n                (100, top_margin + text_height / lines),\r\n            )\r\n            .unwrap();\r\n\r\n            root.draw_text(\r\n                &format!(\r\n                    \"Stagger: {} s\",\r\n                    result.raw_result.config.stagger.as_secs_f64(),\r\n                ),\r\n                &small_style.pos(Pos::new(HPos::Left, VPos::Top)),\r\n                (100 + 180, top_margin + text_height / lines),\r\n            )\r\n            .unwrap();\r\n\r\n            root.draw_text(\r\n                &if idle {\r\n                    format!(\r\n                        \"Grace duration: {:.2} s\",\r\n                        result.raw_result.config.grace_duration.as_secs_f64(),\r\n                    )\r\n                } else {\r\n                    format!(\r\n                        \"Load duration: {:.2} s\",\r\n                        result.raw_result.config.load_duration.as_secs_f64(),\r\n                    )\r\n                },\r\n                &small_style.pos(Pos::new(HPos::Left, VPos::Top)),\r\n                (100, top_margin),\r\n            )\r\n            .unwrap();\r\n\r\n            root.draw_text(\r\n                &format!(\r\n                    \"Server latency: {:.2} ms\",\r\n                    result.raw_result.server_latency.as_secs_f64() * 1000.0,\r\n                ),\r\n                &small_style.pos(Pos::new(HPos::Left, VPos::Top)),\r\n                (100 + 180, top_margin),\r\n            )\r\n            .unwrap();\r\n\r\n            root.draw_text(\r\n                &result.raw_result.generated_by,\r\n                &small_style.pos(Pos::new(HPos::Right, VPos::Center)),\r\n                (width as i32 - 100, center),\r\n            )\r\n            .unwrap();\r\n        }\r\n\r\n        let (root, textarea) = root.split_vertically(root.dim_in_pixel().1 - 24);\r\n\r\n        textarea\r\n            .draw_text(\r\n                \"Elapsed time (seconds)\",\r\n                &medium_style.pos(Pos::new(HPos::Center, VPos::Center)),\r\n                ((width as i32) / 2, 12),\r\n            )\r\n            .unwrap();\r\n\r\n        let mut root = root.split_vertically(text_height + 10).1;\r\n\r\n        let loss = if !peer_latency {\r\n            let loss;\r\n            (root, loss) =\r\n                root.split_vertically(root.relative_to_height(1.0) - PACKET_LOSS_AREA_SIZE);\r\n            Some(loss)\r\n        } else {\r\n            None\r\n        };\r\n\r\n        let mut charts = 1;\r\n\r\n        if peer_latency {\r\n            charts += 1;\r\n        }\r\n\r\n        if result.raw_result.streams() > 0 {\r\n            if config.split_throughput {\r\n                if result.raw_result.download() || result.raw_result.both() {\r\n                    charts += 1\r\n                }\r\n                if result.raw_result.upload() || result.raw_result.both() {\r\n                    charts += 1\r\n                }\r\n            } else {\r\n                charts += 1\r\n            }\r\n            if config.transferred {\r\n                charts += 1\r\n            }\r\n        }\r\n\r\n        let areas = root.split_evenly((charts, 1));\r\n\r\n        // Scale to fit the legend\r\n        let duration = duration * 1.12;\r\n\r\n        let mut chart_index = 0;\r\n\r\n        if result.raw_result.streams() > 0 {\r\n            if config.split_throughput {\r\n                if result.raw_result.download() || result.raw_result.both() {\r\n                    plot_split_throughput(\r\n                        config,\r\n                        true,\r\n                        result,\r\n                        start,\r\n                        duration,\r\n                        &areas[chart_index],\r\n                    );\r\n                    chart_index += 1;\r\n                }\r\n                if result.raw_result.upload() || result.raw_result.both() {\r\n                    plot_split_throughput(\r\n                        config,\r\n                        false,\r\n                        result,\r\n                        start,\r\n                        duration,\r\n                        &areas[chart_index],\r\n                    );\r\n                    chart_index += 1;\r\n                }\r\n            } else {\r\n                plot_throughput(config, throughput, start, duration, &areas[chart_index]);\r\n                chart_index += 1;\r\n            }\r\n        }\r\n\r\n        latency(\r\n            config,\r\n            result,\r\n            pings,\r\n            throughput,\r\n            &result.latencies,\r\n            start,\r\n            duration,\r\n            &areas[chart_index],\r\n            loss.as_ref(),\r\n            false,\r\n        );\r\n        chart_index += 1;\r\n\r\n        if let Some(peer_pings) = result.raw_result.peer_pings.as_ref() {\r\n            latency(\r\n                config,\r\n                result,\r\n                peer_pings,\r\n                throughput,\r\n                &result.peer_latencies,\r\n                start,\r\n                duration,\r\n                &areas[chart_index],\r\n                None,\r\n                true,\r\n            );\r\n            chart_index += 1;\r\n        }\r\n\r\n        if result.raw_result.streams() > 0 && config.transferred {\r\n            bytes_transferred(throughput, start, duration, &areas[chart_index]);\r\n            #[allow(unused_assignments)]\r\n            {\r\n                chart_index += 1;\r\n            }\r\n        }\r\n\r\n        root.present().map_err(|_| anyhow!(\"Unable to plot\"))?;\r\n    }\r\n\r\n    ImageBuffer::from_raw(width, height, data).ok_or(anyhow!(\"Failed to create image\"))\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/protocol.rs",
    "content": "use anyhow::Context;\r\nuse bytes::{Bytes, BytesMut};\r\nuse futures::{Sink, SinkExt, Stream, StreamExt};\r\nuse serde::{Deserialize, Serialize};\r\nuse std::{error::Error, time::Duration};\r\nuse tokio_util::codec::{length_delimited, LengthDelimitedCodec};\r\n\r\n#[derive(Serialize, Deserialize, Copy, Clone, Debug)]\r\npub struct RawLatency {\r\n    // File format: Changed from Duration to Option<Duration> in v2.\r\n    pub total: Option<Duration>,\r\n    pub up: Duration,\r\n}\r\n\r\nimpl RawLatency {\r\n    pub fn down(&self) -> Option<Duration> {\r\n        self.total.map(|total| total.saturating_sub(self.up))\r\n    }\r\n}\r\n\r\npub const PORT: u16 = 35481;\r\n\r\npub const MAGIC: u64 = 0x5372ab82ae7c59cb;\r\npub const VERSION: u64 = 3;\r\n\r\n#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]\r\npub struct Hello {\r\n    magic: u64,\r\n    pub version: u64,\r\n}\r\n\r\nimpl Hello {\r\n    pub fn new() -> Self {\r\n        Hello {\r\n            magic: MAGIC,\r\n            version: VERSION,\r\n        }\r\n    }\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]\r\npub struct TestStream {\r\n    pub group: u32,\r\n    pub id: u32,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug)]\r\npub struct LatencyMeasure {\r\n    pub time: u64, // In microseconds and in server time\r\n    pub index: u64,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug)]\r\npub struct PeerLatency {\r\n    pub sent: u64, // In microseconds and in server time\r\n    pub latency: Option<RawLatency>,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug)]\r\npub enum ServerMessage {\r\n    NewClient(Option<u64>),\r\n    LatencyMeasures(Vec<LatencyMeasure>),\r\n    Measure {\r\n        stream: TestStream,\r\n        time: u64,\r\n        bytes: u64,\r\n    },\r\n    MeasureStreamDone {\r\n        stream: TestStream,\r\n        timeout: bool,\r\n    },\r\n    MeasurementsDone {\r\n        overload: bool,\r\n    },\r\n    LoadComplete {\r\n        stream: TestStream,\r\n    },\r\n    ScheduledLoads {\r\n        groups: Vec<u32>,\r\n        time: u64,\r\n    },\r\n    WaitingForLoad,\r\n    WaitingForByte,\r\n    NewPeer,\r\n    PeerReady {\r\n        server_latency: u64,\r\n    },\r\n    PeerStarted,\r\n    PeerDone {\r\n        overload: bool,\r\n        latencies: Vec<PeerLatency>,\r\n    },\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug)]\r\npub enum ClientMessage {\r\n    NewClient,\r\n    Associate(u64),\r\n    Done,\r\n    ScheduleLoads {\r\n        groups: Vec<u32>,\r\n        delay: u64,\r\n    },\r\n    LoadFromClient {\r\n        stream: TestStream,\r\n        duration: u64,\r\n        delay: u64,\r\n        throughput_interval: u64,\r\n    },\r\n    LoadFromServer {\r\n        stream: TestStream,\r\n        duration: u64,\r\n        delay: u64,\r\n    },\r\n    LoadComplete {\r\n        stream: TestStream,\r\n    },\r\n    SendByte,\r\n    GetMeasurements,\r\n    StopMeasurements,\r\n    NewPeer {\r\n        server: [u8; 16],\r\n        port: u16,\r\n        ping_interval: u64,\r\n        estimated_duration: u128,\r\n    },\r\n    PeerStart,\r\n    PeerStop,\r\n}\r\n\r\n#[derive(Serialize, Deserialize, Debug)]\r\npub struct Ping {\r\n    pub id: u64,\r\n    pub index: u64,\r\n}\r\n\r\npub fn codec() -> LengthDelimitedCodec {\r\n    length_delimited::Builder::new()\r\n        .little_endian()\r\n        .length_field_type::<u64>()\r\n        .new_codec()\r\n}\r\n\r\npub async fn send<S: Sink<Bytes> + Unpin>(\r\n    sink: &mut S,\r\n    value: &impl Serialize,\r\n) -> Result<(), anyhow::Error>\r\nwhere\r\n    S::Error: Error + Send + Sync + 'static,\r\n{\r\n    Ok(sink.send(bincode::serialize(value)?.into()).await?)\r\n}\r\n\r\npub async fn receive<S: Stream<Item = Result<BytesMut, E>> + Unpin, T: for<'a> Deserialize<'a>, E>(\r\n    stream: &mut S,\r\n) -> Result<T, anyhow::Error>\r\nwhere\r\n    E: Error + Send + Sync + 'static,\r\n{\r\n    let bytes = stream\r\n        .next()\r\n        .await\r\n        .context(\"Expected protocol message, but stream closed\")?\r\n        .context(\"Failed to receive protocol message\")?;\r\n    Ok(bincode::deserialize(&bytes)?)\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/remote.html",
    "content": "<!doctype html>\r\n<html>\r\n\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>Crusader Remote Client</title>\r\n</head>\r\n<style>\r\n    body {\r\n        padding: 0;\r\n        margin: 0;\r\n        height: 100%;\r\n        font-family: Arial, Helvetica, sans-serif;\r\n        font-size: 13px;\r\n        background: #f5f5f5;\r\n        color: rgb(87, 87, 87);\r\n    }\r\n\r\n    h1 {\r\n        font-weight: normal;\r\n        font-size: 1.6em;\r\n        padding: 0;\r\n        margin: 0;\r\n    }\r\n\r\n    .flex {\r\n        display: flex;\r\n        justify-content: center;\r\n        flex-wrap: wrap;\r\n        align-items: flex-start;\r\n    }\r\n\r\n    .box {\r\n        padding: 1.5em;\r\n        margin: 1.5em;\r\n        background: #f7f5f5;\r\n        box-shadow: 0.1em 0.1em 1.7em rgba(0, 0, 0, 0.08);\r\n        border-radius: 5px;\r\n        border: 1px solid rgb(218, 218, 218);\r\n        width: fit-content;\r\n    }\r\n\r\n    input {\r\n        border: 1px solid rgb(190, 190, 190);\r\n        border-radius: 5px;\r\n        padding: 5px;\r\n    }\r\n\r\n    input[type=\"number\"] {\r\n        width: 50px;\r\n    }\r\n\r\n    .box img {\r\n        border-radius: 20px;\r\n        padding: 5px;\r\n        border: 1px solid rgb(235, 235, 235);\r\n        background: white;\r\n    }\r\n\r\n    .tspacer {\r\n        padding-right: 20px;\r\n    }\r\n\r\n    a {\r\n        text-decoration: none;\r\n        padding: 5px;\r\n        margin: 5px;\r\n        margin-top: 0;\r\n        margin-bottom: 0;\r\n        color: rgb(87, 87, 87);\r\n        border: 1px solid rgb(190, 190, 190);\r\n        border-radius: 5px;\r\n        background-color: #e6e6e6;\r\n    }\r\n\r\n    a:hover {\r\n        background-color: #dadada;\r\n    }\r\n</style>\r\n<script>\r\n</script>\r\n\r\n<body>\r\n    <div id=\"app\" style=\"visibility: hidden;\">\r\n        <div class=\"flex\">\r\n            <div class=\"box\">\r\n                <h1>Crusader Remote Client</h1>\r\n                <p>Server: <input type=\"text\" v-model=\"config.server\" placeholder=\"(Locate local server)\"></p>\r\n                <hr>\r\n                <p>\r\n                <table>\r\n                    <tr>\r\n                        <td class=\"tspacer\"><input type=\"checkbox\" v-model=\"config.download\" id=\"download\"><label\r\n                                for=\"download\">Download</label></td>\r\n                        <td>Streams:</td>\r\n                        <td class=\"tspacer\"><input type=\"number\" v-model=\"config.streams\"></td>\r\n                        <td>Stream stagger:</td>\r\n                        <td><input type=\"number\" v-model=\"config.stream_stagger\"> seconds</td>\r\n                    </tr>\r\n                    <tr>\r\n                        <td class=\"tspacer\"><input type=\"checkbox\" v-model=\"config.upload\" id=\"upload\"><label\r\n                                for=\"upload\">Upload</label></td>\r\n                        <td>Load duration:</td>\r\n                        <td class=\"tspacer\"><input type=\"number\" v-model=\"config.load_duration\"> seconds</td>\r\n                        <td>Latency sample interval:</td>\r\n                        <td><input type=\"number\" v-model=\"config.latency_sample_interval\"> milliseconds\r\n                        </td>\r\n                    </tr>\r\n                    <tr>\r\n                        <td class=\"tspacer\"><input type=\"checkbox\" v-model=\"config.bidirectional\"\r\n                                id=\"bidirectional\"><label for=\"bidirectional\">Bidirectional</label></td>\r\n                        <td>Grace duration:</td>\r\n                        <td class=\"tspacer\"><input type=\"number\" v-model=\"config.grace_duration\"> seconds</td>\r\n                        <td>Throughput sample interval:</td>\r\n                        <td><input type=\"number\" v-model=\"config.throughput_sample_interval\">\r\n                            milliseconds</td>\r\n                    </tr>\r\n                </table>\r\n                <hr>\r\n                <p><input type=\"checkbox\" v-model=\"config.latency_peer\" id=\"latency_peer\"><label\r\n                        for=\"latency_peer\">Latency peer:</label> <input type=\"text\" :disabled=\"!config.latency_peer\"\r\n                        v-model=\"config.latency_peer_server\" placeholder=\"(Locate local peer)\">\r\n                </p>\r\n                <hr>\r\n                <p><input type=\"button\" value=\"Run test\" :disabled=\"running\" @click=\"run\"></p>\r\n                <p v-for=\"msg in log\">\r\n                    {{ msg }}\r\n                </p>\r\n            </div>\r\n            <div v-if=\"plot\" class=\"box\">\r\n                <div style=\"display: flex; margin-bottom: 10px;\">\r\n                    <h1 style=\"margin-top: auto; margin-bottom: auto;\">Result {{ time.replaceAll(\".\", \":\") }}</h1>\r\n                    <span style=\"flex-grow: 1;\"></span>\r\n                    <a :href=\"plot\" :download=\"`test ${time}.png`\">⬇ Download plot</a></p>\r\n                    <a v-if=\"raw_result\" :href=\"raw_result\" :download=\"`test ${time}.crr`\">⬇ Download raw\r\n                        result</a>\r\n                </div>\r\n                <img :src=\"plot\">\r\n            </div>\r\n        </div>\r\n    </div>\r\n</body>\r\n<script type=\"module\">\r\n    import { createApp, onMounted, ref, toRaw } from './assets/vue.js'\r\n\r\n    createApp({\r\n        setup() {\r\n            let config = ref({\r\n                server: \"\",\r\n                download: true,\r\n                upload: true,\r\n                bidirectional: true,\r\n                port: 35481,\r\n                streams: 8,\r\n                stream_stagger: 0,\r\n                load_duration: 10,\r\n                grace_duration: 2,\r\n                latency_sample_interval: 5,\r\n                throughput_sample_interval: 60,\r\n                latency_peer: false,\r\n                latency_peer_server: \"\",\r\n            });\r\n            let log = ref([]);\r\n            let plot = ref(null);\r\n            let raw_result = ref(null);\r\n            let time = ref(null);\r\n\r\n            onMounted(() => {\r\n                Object.assign(config.value, JSON.parse(localStorage.getItem(\"config\")));\r\n                document.getElementById(\"app\").style = \"\";\r\n            });\r\n\r\n            let running = ref(false);\r\n\r\n            function run() {\r\n                running.value = true;\r\n                log.value = [];\r\n                plot.value = null;\r\n                raw_result.value = null;\r\n                time.value = null;\r\n\r\n                localStorage.setItem(\"config\", JSON.stringify(config.value));\r\n\r\n                let request = structuredClone(toRaw(config.value));\r\n\r\n                if (!request.latency_peer_server) {\r\n                    request.latency_peer_server = null;\r\n                }\r\n                if (!request.server) {\r\n                    request.server = null;\r\n                }\r\n\r\n                let binary_index = 0;\r\n                let open = false;\r\n\r\n                let ws = new WebSocket(`ws://${window.location.host}/api/client`)\r\n                ws.onmessage = event => {\r\n                    if (event.data instanceof Blob) {\r\n                        if (binary_index == 0) {\r\n                            plot.value = URL.createObjectURL(event.data);\r\n                        }\r\n                        if (binary_index == 1) {\r\n                            raw_result.value = URL.createObjectURL(event.data);\r\n                        }\r\n                        binary_index += 1;\r\n                    } else {\r\n                        let data = JSON.parse(event.data);\r\n                        if (data.type == \"log\") {\r\n                            log.value.push(data.message);\r\n                        }\r\n                        if (data.type == \"result\") {\r\n                            time.value = data.time;\r\n                            log.value.push(`[${data.time.trim().replaceAll(\".\", \":\")}] Test completed`);\r\n                        }\r\n                    }\r\n                };\r\n                ws.onopen = (event) => {\r\n                    open = true;\r\n                    ws.send(JSON.stringify(request));\r\n                };\r\n                ws.onclose = (event) => {\r\n                    if (!raw_result.value) {\r\n                        if (open) {\r\n                            log.value.push(\"Connection lost.\");\r\n                        } else {\r\n                            log.value.push(\"Unable to connect to the client.\");\r\n                        }\r\n                    }\r\n                    running.value = false;\r\n                };\r\n            }\r\n\r\n            return {\r\n                config, running, run, log, plot, raw_result, time\r\n            };\r\n        }\r\n    }).mount('#app')\r\n</script>\r\n\r\n</html>"
  },
  {
    "path": "src/crusader-lib/src/remote.rs",
    "content": "use crate::common::{interface_ips, Config};\r\nuse crate::plot::save_graph_to_mem;\r\nuse crate::test::{test_async, timed, PlotConfig};\r\nuse crate::{version, with_time};\r\nuse anyhow::anyhow;\r\nuse anyhow::bail;\r\nuse anyhow::Error;\r\nuse axum::body::Body;\r\nuse axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};\r\nuse axum::http::{header, HeaderValue, Response};\r\nuse axum::{\r\n    extract::{ConnectInfo, State},\r\n    response::{Html, IntoResponse},\r\n    routing::get,\r\n    Router,\r\n};\r\nuse image::ImageFormat;\r\nuse serde::Deserialize;\r\nuse serde_json::json;\r\nuse socket2::{Domain, Protocol, Socket};\r\nuse std::io::{Cursor, ErrorKind};\r\nuse std::net::{IpAddr, Ipv6Addr};\r\nuse std::thread;\r\nuse std::time::Duration;\r\nuse std::{\r\n    net::{Ipv4Addr, SocketAddr},\r\n    sync::Arc,\r\n};\r\nuse tokio::net::TcpSocket;\r\nuse tokio::sync::mpsc::unbounded_channel;\r\nuse tokio::sync::oneshot;\r\nuse tokio::{net::TcpListener, signal, task};\r\n\r\nstruct Env {\r\n    live_reload: bool,\r\n    msg: Box<dyn Fn(&str) + Send + Sync>,\r\n}\r\n\r\nasync fn ws_client(\r\n    State(state): State<Arc<Env>>,\r\n    ws: WebSocketUpgrade,\r\n    ConnectInfo(addr): ConnectInfo<SocketAddr>,\r\n) -> impl IntoResponse {\r\n    ws.on_upgrade(move |socket| async move {\r\n        handle_client(state, socket, addr).await.ok();\r\n    })\r\n}\r\n\r\n#[derive(Deserialize, Debug)]\r\nstruct TestArgs {\r\n    server: Option<String>,\r\n    download: bool,\r\n    upload: bool,\r\n    bidirectional: bool,\r\n    port: u16,\r\n\r\n    streams: u64,\r\n\r\n    stream_stagger: f64,\r\n\r\n    load_duration: f64,\r\n\r\n    grace_duration: f64,\r\n    latency_sample_interval: u64,\r\n    throughput_sample_interval: u64,\r\n    latency_peer: bool,\r\n    latency_peer_server: Option<String>,\r\n}\r\n\r\nasync fn handle_client(\r\n    state: Arc<Env>,\r\n    mut socket: WebSocket,\r\n    who: SocketAddr,\r\n) -> Result<(), Error> {\r\n    let args: TestArgs = match socket.recv().await.ok_or(anyhow!(\"No request\"))?? {\r\n        Message::Text(request) => serde_json::from_str(&request)?,\r\n        _ => bail!(\"unexpected message\"),\r\n    };\r\n    let config = Config {\r\n        port: args.port,\r\n        streams: args.streams,\r\n        stream_stagger: Duration::from_secs_f64(args.stream_stagger),\r\n        grace_duration: Duration::from_secs_f64(args.grace_duration),\r\n        load_duration: Duration::from_secs_f64(args.load_duration),\r\n        download: args.download,\r\n        upload: args.upload,\r\n        bidirectional: args.bidirectional,\r\n        ping_interval: Duration::from_millis(args.latency_sample_interval),\r\n        throughput_interval: Duration::from_millis(args.throughput_sample_interval),\r\n    };\r\n\r\n    (state.msg)(&format!(\"Remote client ({}) test started\", who.ip()));\r\n\r\n    let (msg_tx, mut msg_rx) = unbounded_channel();\r\n\r\n    let tester = tokio::spawn(async move {\r\n        let msg = Arc::new(move |msg: &str| {\r\n            let msg = with_time(msg);\r\n            msg_tx.send(msg.clone()).ok();\r\n            task::spawn_blocking(move || println!(\"{}\", msg));\r\n        });\r\n        let result = test_async(\r\n            config,\r\n            args.server.as_deref(),\r\n            args.latency_peer\r\n                .then_some(args.latency_peer_server.as_deref()),\r\n            msg.clone(),\r\n        )\r\n        .await\r\n        .map_err(|err| {\r\n            msg(&format!(\"Client failed: {}\", err));\r\n            anyhow!(\"Client failed\")\r\n        });\r\n        (result, timed(\"\"))\r\n    });\r\n\r\n    while let Some(msg) = msg_rx.recv().await {\r\n        socket\r\n            .send(Message::Text(\r\n                json!({\r\n                    \"type\": \"log\",\r\n                    \"message\": msg,\r\n                })\r\n                .to_string(),\r\n            ))\r\n            .await?;\r\n    }\r\n\r\n    let (result, time) = tester.await?;\r\n    let result = result?;\r\n\r\n    socket\r\n        .send(Message::Text(\r\n            json!({\r\n                \"type\": \"result\",\r\n                \"time\": time,\r\n            })\r\n            .to_string(),\r\n        ))\r\n        .await?;\r\n\r\n    let (result, plot) = task::spawn_blocking(move || -> Result<_, anyhow::Error> {\r\n        let mut data = Cursor::new(Vec::new());\r\n\r\n        let plot = save_graph_to_mem(&PlotConfig::default(), &result.to_test_result())?;\r\n        plot.write_to(&mut data, ImageFormat::Png)?;\r\n        Ok((result, data.into_inner()))\r\n    })\r\n    .await??;\r\n\r\n    socket.send(Message::Binary(plot)).await?;\r\n\r\n    let data = task::spawn_blocking(move || {\r\n        let mut data = Vec::new();\r\n\r\n        result.save_to_writer(&mut data)?;\r\n        Ok::<_, anyhow::Error>(data)\r\n    })\r\n    .await??;\r\n    socket.send(Message::Binary(data)).await?;\r\n\r\n    (state.msg)(&format!(\"Remote client ({}) test complete\", who.ip()));\r\n    Ok(())\r\n}\r\n\r\nasync fn listen(state: Arc<Env>, listener: TcpListener) {\r\n    async fn root(State(state): State<Arc<Env>>) -> Html<String> {\r\n        if state.live_reload {\r\n            if let Ok(data) = std::fs::read_to_string(\"crusader-lib/src/remote.html\") {\r\n                return Html(data);\r\n            }\r\n        }\r\n\r\n        Html(include_str!(\"remote.html\").to_string())\r\n    }\r\n\r\n    async fn vue() -> Response<Body> {\r\n        #[cfg(debug_assertions)]\r\n        let body: Body = include_str!(\"../assets/vue.js\").into();\r\n        #[cfg(not(debug_assertions))]\r\n        let body: Body = include_str!(\"../assets/vue.prod.js\").into();\r\n        (\r\n            [(\r\n                header::CONTENT_TYPE,\r\n                HeaderValue::from_static(\"text/javascript\"),\r\n            )],\r\n            body,\r\n        )\r\n            .into_response()\r\n    }\r\n\r\n    let app = Router::new()\r\n        .route(\"/\", get(root))\r\n        .route(\"/assets/vue.js\", get(vue))\r\n        .route(\"/api/client\", get(ws_client))\r\n        .with_state(state);\r\n\r\n    axum::serve(\r\n        listener,\r\n        app.into_make_service_with_connect_info::<SocketAddr>(),\r\n    )\r\n    .await\r\n    .unwrap();\r\n}\r\n\r\nasync fn serve_async(port: u16, msg: Box<dyn Fn(&str) + Send + Sync>) -> Result<(), Error> {\r\n    let live_reload = cfg!(debug_assertions)\r\n        && std::fs::read_to_string(\"crusader-lib/src/remote.html\")\r\n            .map(|file| *file == *include_str!(\"remote.html\"))\r\n            .unwrap_or_default();\r\n\r\n    if live_reload {\r\n        (msg)(&format!(\r\n            \"Live reload of crusader-lib/src/remote.html enabled\",\r\n        ));\r\n    }\r\n\r\n    let state = Arc::new(Env { live_reload, msg });\r\n\r\n    let v6 = Socket::new(Domain::IPV6, socket2::Type::STREAM, Some(Protocol::TCP))?;\r\n    v6.set_only_v6(true)?;\r\n    let v6: std::net::TcpStream = v6.into();\r\n    v6.set_nonblocking(true)?;\r\n    let v6 = TcpSocket::from_std_stream(v6);\r\n    v6.bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port))\r\n        .map_err(|error| {\r\n            if let ErrorKind::AddrInUse = error.kind() {\r\n                anyhow!(\r\n                    \"Failed to bind TCP port, maybe another Crusader instance is already running\"\r\n                )\r\n            } else {\r\n                error.into()\r\n            }\r\n        })?;\r\n    let v6 = v6.listen(1024)?;\r\n\r\n    let v4 = TcpListener::bind((Ipv4Addr::UNSPECIFIED, port)).await?;\r\n\r\n    task::spawn(listen(state.clone(), v6));\r\n    task::spawn(listen(state.clone(), v4));\r\n\r\n    (state.msg)(&format!(\r\n        \"Remote{} version {} running...\",\r\n        if cfg!(debug_assertions) {\r\n            \" (debugging enabled)\"\r\n        } else {\r\n            \"\"\r\n        },\r\n        version()\r\n    ));\r\n\r\n    for (name, ip) in interface_ips() {\r\n        let addr = match ip {\r\n            IpAddr::V6(ip) => format!(\"[{ip}]\"),\r\n            IpAddr::V4(ip) => ip.to_string(),\r\n        };\r\n        (state.msg)(&format!(\"Address on `{name}`: http://{addr}:{port}\"));\r\n    }\r\n\r\n    Ok(())\r\n}\r\n\r\npub fn serve_until(\r\n    port: u16,\r\n    msg: Box<dyn Fn(&str) + Send + Sync>,\r\n    started: Box<dyn FnOnce(Result<(), String>) + Send>,\r\n    done: Box<dyn FnOnce() + Send>,\r\n) -> Result<oneshot::Sender<()>, anyhow::Error> {\r\n    let (tx, rx) = oneshot::channel();\r\n\r\n    let rt = tokio::runtime::Runtime::new()?;\r\n\r\n    thread::spawn(move || {\r\n        rt.block_on(async move {\r\n            match serve_async(port, msg).await {\r\n                Ok(()) => {\r\n                    started(Ok(()));\r\n                    rx.await.ok();\r\n                }\r\n                Err(error) => started(Err(error.to_string())),\r\n            }\r\n        });\r\n\r\n        done();\r\n    });\r\n\r\n    Ok(tx)\r\n}\r\n\r\npub fn run(port: u16) -> Result<(), anyhow::Error> {\r\n    let rt = tokio::runtime::Runtime::new()?;\r\n    rt.block_on(async move {\r\n        serve_async(\r\n            port,\r\n            Box::new(|msg: &str| {\r\n                let msg = msg.to_owned();\r\n                task::spawn_blocking(move || println!(\"{}\", with_time(&msg)));\r\n            }),\r\n        )\r\n        .await?;\r\n        signal::ctrl_c().await?;\r\n        println!(\"{}\", with_time(\"Remote server aborting...\"));\r\n        Ok(())\r\n    })\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/serve.rs",
    "content": "use anyhow::{anyhow, bail, Context};\r\nuse futures::{pin_mut, select, FutureExt};\r\nuse parking_lot::Mutex;\r\nuse socket2::{Domain, Protocol, Socket};\r\nuse std::collections::HashMap;\r\nuse std::io::ErrorKind;\r\nuse std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};\r\nuse std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\r\nuse std::sync::Arc;\r\nuse std::time::Duration;\r\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\r\nuse tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket};\r\nuse tokio::sync::mpsc::{\r\n    channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender,\r\n};\r\nuse tokio::sync::{oneshot, watch};\r\nuse tokio::task::{self};\r\nuse tokio::{signal, time, time::Instant};\r\nuse tokio_util::codec::{FramedRead, FramedWrite};\r\n\r\nuse crate::common::{fresh_socket_addr, inherit_local, interface_ips, read_data, write_data};\r\nuse crate::peer::run_peer;\r\nuse crate::protocol::{\r\n    self, codec, receive, send, ClientMessage, LatencyMeasure, ServerMessage, TestStream,\r\n};\r\nuse crate::{discovery, version, with_time};\r\n\r\nuse std::thread;\r\n\r\n#[derive(Debug)]\r\nstruct SlotUpdate {\r\n    slot: u64,\r\n    client: Option<Arc<Client>>,\r\n    reply: Option<oneshot::Sender<()>>,\r\n}\r\n\r\n#[derive(Debug)]\r\nstruct Client {\r\n    ip: Ipv6Addr,\r\n    tx_message: UnboundedSender<ServerMessage>,\r\n    tx_latency: Sender<LatencyMeasure>,\r\n    rx_latency: Mutex<Receiver<LatencyMeasure>>,\r\n    overload: AtomicBool,\r\n    loads: Mutex<HashMap<u32, watch::Sender<Option<Instant>>>>,\r\n    uploads: Mutex<HashMap<TestStream, oneshot::Sender<()>>>,\r\n}\r\n\r\nimpl Client {\r\n    fn forward_latency_msgs(&self) {\r\n        let mut rx = self.rx_latency.lock();\r\n\r\n        let mut measures = Vec::new();\r\n\r\n        while let Ok(measure) = rx.try_recv() {\r\n            measures.push(measure);\r\n        }\r\n\r\n        self.tx_message\r\n            .send(ServerMessage::LatencyMeasures(measures))\r\n            .ok();\r\n    }\r\n\r\n    fn load_waiter(&self, group: u32) -> watch::Receiver<Option<Instant>> {\r\n        self.loads\r\n            .lock()\r\n            .entry(group)\r\n            .or_insert_with(|| watch::channel(None).0)\r\n            .subscribe()\r\n    }\r\n\r\n    async fn schedule_loads(\r\n        &self,\r\n        state: &State,\r\n        groups: Vec<u32>,\r\n        delay: u64,\r\n    ) -> Result<ServerMessage, anyhow::Error> {\r\n        let time = Instant::now() + Duration::from_micros(delay);\r\n        {\r\n            let loads = self.loads.lock();\r\n            for group in &groups {\r\n                loads\r\n                    .get(group)\r\n                    .ok_or(anyhow!(\"Unknown group\"))?\r\n                    .send(Some(time))?;\r\n            }\r\n        }\r\n\r\n        Ok(ServerMessage::ScheduledLoads {\r\n            groups,\r\n            time: time.saturating_duration_since(state.started).as_micros() as u64,\r\n        })\r\n    }\r\n}\r\n\r\nstruct Pong {\r\n    updates: UnboundedSender<SlotUpdate>,\r\n}\r\n\r\npub(crate) struct State {\r\n    port: u16,\r\n    started: Instant,\r\n    dummy_data: Vec<u8>,\r\n    clients: Mutex<Vec<Option<Arc<Client>>>>,\r\n    pong_servers: Mutex<HashMap<SocketAddr, Arc<Pong>>>,\r\n    pub(crate) msg: Box<dyn Fn(&str) + Send + Sync>,\r\n    pub(crate) peer_server: bool,\r\n}\r\n\r\nfn ip_to_ipv6_mapped(ip: IpAddr) -> Ipv6Addr {\r\n    match ip {\r\n        IpAddr::V4(ip) => ip.to_ipv6_mapped(),\r\n        IpAddr::V6(ip) => ip,\r\n    }\r\n}\r\n\r\npub struct OnDrop<F: Fn()>(pub F);\r\n\r\nimpl<F: Fn()> Drop for OnDrop<F> {\r\n    fn drop(&mut self) {\r\n        (self.0)();\r\n    }\r\n}\r\n\r\nasync fn client(state: Arc<State>, stream: TcpStream) -> Result<(), anyhow::Error> {\r\n    stream.set_nodelay(true)?;\r\n\r\n    let addr = stream.peer_addr()?;\r\n    let local_addr = fresh_socket_addr(stream.local_addr()?, state.port);\r\n\r\n    let (rx, tx) = stream.into_split();\r\n    let mut stream_rx = FramedRead::new(rx, codec());\r\n    let mut stream_tx = FramedWrite::new(tx, codec());\r\n\r\n    let hello = protocol::Hello::new();\r\n\r\n    let client_hello: protocol::Hello = receive(&mut stream_rx).await?;\r\n\r\n    send(&mut stream_tx, &hello).await?;\r\n\r\n    if hello != client_hello {\r\n        (state.msg)(&format!(\r\n            \"Client {} had invalid hello {:?}, expected {:?}\",\r\n            addr, client_hello, hello\r\n        ));\r\n        return Ok(());\r\n    }\r\n\r\n    let mut buffer = Vec::with_capacity(512 * 1024);\r\n    buffer.extend((0..buffer.capacity()).map(|_| 0));\r\n\r\n    let mut client = None;\r\n    let mut receiver = None;\r\n    let mut _client_dropper = None;\r\n\r\n    loop {\r\n        let request: ClientMessage = receive(&mut stream_rx).await?;\r\n        match request {\r\n            ClientMessage::NewPeer {\r\n                server,\r\n                port,\r\n                ping_interval,\r\n                estimated_duration,\r\n            } => {\r\n                if !state.peer_server {\r\n                    bail!(\"Server not accepting peers\")\r\n                }\r\n                (state.msg)(&format!(\r\n                    \"Serving as peer for {}, version {}\",\r\n                    addr, hello.version\r\n                ));\r\n                let ip = Ipv6Addr::from(server).to_canonical();\r\n                (state.msg)(&format!(\"Server for peer is {ip}:{port}\",));\r\n                run_peer(\r\n                    state,\r\n                    inherit_local(local_addr, ip, port),\r\n                    Duration::from_millis(ping_interval),\r\n                    Duration::from_millis(estimated_duration as u64),\r\n                    &mut stream_rx,\r\n                    &mut stream_tx,\r\n                )\r\n                .await\r\n                .context(\"Failed to run peer\")?;\r\n                return Ok(());\r\n            }\r\n            ClientMessage::NewClient => {\r\n                (state.msg)(&format!(\"Serving {}, version {}\", addr, hello.version));\r\n\r\n                let pong = start_pong_server(&state, local_addr)\r\n                    .await\r\n                    .context(\"Failed to start pong server\")?;\r\n\r\n                let client = {\r\n                    let client = {\r\n                        let mut clients = state.clients.lock();\r\n                        let free_slot =\r\n                            clients.iter_mut().enumerate().find(|slot| slot.1.is_none());\r\n\r\n                        free_slot.map(|(slot, data)| {\r\n                            let (tx_message, rx_message) = unbounded_channel();\r\n\r\n                            let (tx_latency, rx_latency) = channel(200);\r\n                            let slot = slot as u64;\r\n                            let new_client = Arc::new(Client {\r\n                                ip: ip_to_ipv6_mapped(addr.ip()),\r\n                                tx_message,\r\n                                tx_latency,\r\n                                rx_latency: Mutex::new(rx_latency),\r\n                                overload: AtomicBool::new(false),\r\n                                loads: Mutex::new(HashMap::new()),\r\n                                uploads: Mutex::new(HashMap::new()),\r\n                            });\r\n                            *data = Some(new_client.clone());\r\n\r\n                            receiver = Some(rx_message);\r\n                            client = Some(new_client.clone());\r\n                            (slot, new_client)\r\n                        })\r\n                    };\r\n\r\n                    if let Some((slot, client)) = client {\r\n                        // Update pong server slot\r\n                        let (rx, tx) = oneshot::channel();\r\n                        pong.updates.send(SlotUpdate {\r\n                            slot,\r\n                            client: Some(client.clone()),\r\n                            reply: Some(rx),\r\n                        })?;\r\n                        tx.await.ok();\r\n\r\n                        _client_dropper = Some(move || {\r\n                            pong.updates\r\n                                .send(SlotUpdate {\r\n                                    slot,\r\n                                    client: None,\r\n                                    reply: None,\r\n                                })\r\n                                .ok();\r\n                        });\r\n\r\n                        Some(slot)\r\n                    } else {\r\n                        None\r\n                    }\r\n                };\r\n\r\n                send(&mut stream_tx, &ServerMessage::NewClient(client)).await?;\r\n            }\r\n            ClientMessage::Associate(id) => {\r\n                client = Some(\r\n                    state\r\n                        .clients\r\n                        .lock()\r\n                        .get(id as usize)\r\n                        .and_then(|client| client.as_ref())\r\n                        .cloned()\r\n                        .and_then(|client| {\r\n                            (client.ip == ip_to_ipv6_mapped(addr.ip())).then_some(client)\r\n                        })\r\n                        .ok_or(anyhow!(\"Unable to assoicate client\"))?,\r\n                );\r\n            }\r\n            ClientMessage::GetMeasurements => {\r\n                let receiver = receiver.as_mut().ok_or(anyhow!(\"Not the main client\"))?;\r\n\r\n                let client = client.clone().ok_or(anyhow!(\"Not the main client\"))?;\r\n                let client_ = client.clone();\r\n\r\n                let done = Arc::new(AtomicBool::new(false));\r\n                let done_ = done.clone();\r\n\r\n                let get_pings = tokio::spawn(async move {\r\n                    let mut interval = time::interval(Duration::from_millis(20));\r\n                    loop {\r\n                        interval.tick().await;\r\n\r\n                        client.forward_latency_msgs();\r\n\r\n                        if done_.load(Ordering::Acquire) {\r\n                            return client.overload.load(Ordering::SeqCst);\r\n                        }\r\n                    }\r\n                });\r\n\r\n                loop {\r\n                    let message = {\r\n                        let request = receive::<_, ClientMessage, _>(&mut stream_rx).fuse();\r\n                        pin_mut!(request);\r\n\r\n                        let message = receiver.recv().fuse();\r\n                        pin_mut!(message);\r\n\r\n                        select! {\r\n                            request = request => Err(request?),\r\n                            message = message => Ok(message),\r\n                        }\r\n                    };\r\n\r\n                    match message {\r\n                        Ok(Some(message)) => {\r\n                            send(&mut stream_tx, &message).await?;\r\n                        }\r\n                        Ok(None) | Err(ClientMessage::StopMeasurements) => {\r\n                            done.store(true, Ordering::Release);\r\n                            let overload = get_pings.await?;\r\n\r\n                            // Send pending messages\r\n                            while let Ok(message) = receiver.try_recv() {\r\n                                send(&mut stream_tx, &message).await?;\r\n                            }\r\n\r\n                            send(\r\n                                &mut stream_tx,\r\n                                &ServerMessage::MeasurementsDone { overload },\r\n                            )\r\n                            .await?;\r\n                            break;\r\n                        }\r\n                        Err(ClientMessage::LoadComplete { stream }) => {\r\n                            client_\r\n                                .uploads\r\n                                .lock()\r\n                                .remove(&stream)\r\n                                .ok_or(anyhow!(\"Expected upload stream\"))?\r\n                                .send(())\r\n                                .map_err(|_| {\r\n                                    anyhow!(\"Unable to notify reader of writer completion\")\r\n                                })?;\r\n                        }\r\n                        Err(ClientMessage::ScheduleLoads { groups, delay }) => {\r\n                            let reply = client_.schedule_loads(&state, groups, delay).await?;\r\n                            send(&mut stream_tx, &reply).await?;\r\n                        }\r\n                        Err(msg) => {\r\n                            bail!(\"Unexpected message during measurement {:?}\", msg)\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n\r\n            ClientMessage::LoadFromServer {\r\n                stream: test_stream,\r\n                duration,\r\n                delay,\r\n            } => {\r\n                let client = client.ok_or(anyhow!(\"No associated client\"))?;\r\n\r\n                let mut stream_rx = stream_rx.into_inner();\r\n\r\n                send(&mut stream_tx, &ServerMessage::WaitingForByte).await?;\r\n\r\n                // Wait for a pending read byte\r\n                loop {\r\n                    let _ = stream_rx.read(&mut []).await?;\r\n                    match time::timeout(Duration::from_millis(10), stream_rx.peek(&mut [0])).await {\r\n                        Ok(Ok(1)) => break,\r\n                        Err(_) | Ok(Ok(_)) => (),\r\n                        Ok(Err(err)) => return Err(err.into()),\r\n                    }\r\n                }\r\n\r\n                let mut waiter = client.load_waiter(test_stream.group);\r\n\r\n                send(&mut stream_tx, &ServerMessage::WaitingForLoad).await?;\r\n\r\n                let stream = stream_tx.into_inner().reunite(stream_rx).unwrap();\r\n\r\n                waiter.changed().await?;\r\n                let start =\r\n                    waiter.borrow().ok_or(anyhow!(\"Expected time\"))? + Duration::from_micros(delay);\r\n\r\n                time::sleep_until(start).await;\r\n\r\n                write_data(\r\n                    stream,\r\n                    state.dummy_data.as_ref(),\r\n                    start + Duration::from_micros(duration),\r\n                )\r\n                .await?;\r\n\r\n                client\r\n                    .tx_message\r\n                    .send(ServerMessage::LoadComplete {\r\n                        stream: test_stream,\r\n                    })\r\n                    .ok();\r\n\r\n                return Ok(());\r\n            }\r\n            ClientMessage::LoadFromClient {\r\n                stream: test_stream,\r\n                duration,\r\n                delay,\r\n                throughput_interval,\r\n            } => {\r\n                let client = client.ok_or(anyhow!(\"No associated client\"))?;\r\n\r\n                send(&mut stream_tx, &ServerMessage::WaitingForLoad).await?;\r\n\r\n                let reply: ClientMessage = receive(&mut stream_rx).await.unwrap();\r\n                match reply {\r\n                    ClientMessage::SendByte => (),\r\n                    _ => bail!(\"Unexpected message {:?}\", reply),\r\n                };\r\n\r\n                let mut stream = stream_rx\r\n                    .into_inner()\r\n                    .reunite(stream_tx.into_inner())\r\n                    .unwrap();\r\n\r\n                stream.write_u8(1).await.unwrap();\r\n\r\n                let (reading_done_tx, reading_done_rx) = oneshot::channel();\r\n\r\n                client.uploads.lock().insert(test_stream, reading_done_tx);\r\n\r\n                let bytes = Arc::new(AtomicU64::new(0));\r\n                let bytes_ = bytes.clone();\r\n                let (done_tx, mut done_rx) = oneshot::channel();\r\n\r\n                let mut waiter = client.load_waiter(test_stream.group);\r\n                waiter.changed().await?;\r\n                let start =\r\n                    waiter.borrow().ok_or(anyhow!(\"Expected time\"))? + Duration::from_micros(delay);\r\n\r\n                time::sleep_until(start).await;\r\n\r\n                tokio::spawn(async move {\r\n                    let mut interval = time::interval(Duration::from_micros(throughput_interval));\r\n                    loop {\r\n                        interval.tick().await;\r\n\r\n                        let current_time = Instant::now();\r\n                        let current_bytes = bytes_.load(Ordering::Acquire);\r\n\r\n                        client\r\n                            .tx_message\r\n                            .send(ServerMessage::Measure {\r\n                                stream: test_stream,\r\n                                time: current_time\r\n                                    .saturating_duration_since(state.started)\r\n                                    .as_micros() as u64,\r\n                                bytes: current_bytes,\r\n                            })\r\n                            .ok();\r\n\r\n                        if let Ok(timeout) = done_rx.try_recv() {\r\n                            client\r\n                                .tx_message\r\n                                .send(ServerMessage::MeasureStreamDone {\r\n                                    stream: test_stream,\r\n                                    timeout,\r\n                                })\r\n                                .ok();\r\n                            break;\r\n                        }\r\n                    }\r\n                });\r\n\r\n                let timeout = read_data(\r\n                    stream,\r\n                    &mut buffer,\r\n                    bytes,\r\n                    start + Duration::from_micros(duration),\r\n                    reading_done_rx,\r\n                )\r\n                .await?;\r\n\r\n                done_tx\r\n                    .send(timeout)\r\n                    .map_err(|_| anyhow!(\"Unable to signal reading completion\"))?;\r\n\r\n                return Ok(());\r\n            }\r\n            ClientMessage::Done => {\r\n                (state.msg)(&format!(\"Serving complete for {}\", addr));\r\n\r\n                return Ok(());\r\n            }\r\n            msg @ (ClientMessage::StopMeasurements\r\n            | ClientMessage::ScheduleLoads { .. }\r\n            | ClientMessage::LoadComplete { .. }\r\n            | ClientMessage::SendByte\r\n            | ClientMessage::PeerStart\r\n            | ClientMessage::PeerStop) => {\r\n                bail!(\"Unexpected message {:?}\", msg);\r\n            }\r\n        };\r\n    }\r\n}\r\n\r\nasync fn listen(state: Arc<State>, listener: TcpListener) {\r\n    loop {\r\n        match listener.accept().await {\r\n            Ok((socket, addr)) => {\r\n                let state = state.clone();\r\n                tokio::spawn(async move {\r\n                    client(state.clone(), socket).await.map_err(|error| {\r\n                        (state.msg)(&format!(\"Error serving client {}: {:?}\", addr, error));\r\n                    })\r\n                });\r\n            }\r\n            Err(error) => {\r\n                (state.msg)(&format!(\"Error accepting client: {}\", error));\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nasync fn handle_ping(\r\n    state: &State,\r\n    slots: &[Option<Arc<Client>>],\r\n    packet: &[u8],\r\n    src: SocketAddr,\r\n    socket: &UdpSocket,\r\n) {\r\n    let valid_ping = bincode::deserialize(packet)\r\n        .ok()\r\n        .and_then(|ping: protocol::Ping| {\r\n            slots\r\n                .get(ping.id as usize)\r\n                .and_then(|client| client.as_ref())\r\n                .and_then(|client| {\r\n                    (ip_to_ipv6_mapped(src.ip()) == client.ip).then_some((client, ping))\r\n                })\r\n        });\r\n\r\n    if let Some((client, ping)) = valid_ping {\r\n        let time = Instant::now()\r\n            .saturating_duration_since(state.started)\r\n            .as_micros() as u64;\r\n\r\n        let measure = LatencyMeasure {\r\n            time,\r\n            index: ping.index,\r\n        };\r\n\r\n        if client.tx_latency.try_send(measure).is_err() {\r\n            client.overload.store(true, Ordering::SeqCst);\r\n        }\r\n\r\n        socket\r\n            .send_to(packet, &src)\r\n            .await\r\n            .map_err(|error| {\r\n                (state.msg)(&format!(\"Unable to send UDP pong packet: {:?}\", error));\r\n            })\r\n            .ok();\r\n    }\r\n}\r\n\r\nasync fn pong(\r\n    socket: UdpSocket,\r\n    addr: SocketAddr,\r\n    state: Arc<State>,\r\n    mut rx: UnboundedReceiver<SlotUpdate>,\r\n) {\r\n    (state.msg)(&format!(\"Starting UDP server ({})\", addr));\r\n\r\n    let mut slots: Vec<_> = (0..SLOTS).map(|_| None).collect();\r\n    let mut buf = [0; 128];\r\n\r\n    loop {\r\n        let packet = {\r\n            let socket_packet = socket.recv_from(&mut buf).fuse();\r\n            pin_mut!(socket_packet);\r\n\r\n            let message = rx.recv().fuse();\r\n            pin_mut!(message);\r\n\r\n            select! {\r\n                result = socket_packet => {\r\n                    match result {\r\n                        Ok((len, src)) => {\r\n                            Some((len, src))\r\n                        }\r\n                        Err(error) => {\r\n                            (state.msg)(&format!(\"Unable to read from UDP socket ({}): {}\", addr, error));\r\n                            state.pong_servers.lock().remove(&addr);\r\n                            return\r\n                        }\r\n                    }\r\n                },\r\n                slot_update = message => {\r\n                    slot_update.map(|slot_update| {\r\n                        slots[slot_update.slot as usize] = slot_update.client;\r\n                        slot_update.reply.map(|reply| reply.send(()).ok());\r\n                    });\r\n                    None\r\n                },\r\n            }\r\n        };\r\n\r\n        if let Some((len, src)) = packet {\r\n            let packet = &mut buf[..len];\r\n            handle_ping(&state, slots.as_slice(), packet, src, &socket).await;\r\n        }\r\n    }\r\n}\r\n\r\nconst SLOTS: usize = 1000;\r\n\r\nasync fn start_pong_server(\r\n    state: &Arc<State>,\r\n    addr: SocketAddr,\r\n) -> Result<Arc<Pong>, anyhow::Error> {\r\n    if let Some(pong) = state.pong_servers.lock().get(&addr) {\r\n        return Ok(pong.clone());\r\n    }\r\n\r\n    let socket = UdpSocket::bind(addr).await?;\r\n\r\n    Ok((*state\r\n        .pong_servers\r\n        .lock()\r\n        .entry(addr)\r\n        .or_insert_with(move || {\r\n            let (tx, rx) = unbounded_channel();\r\n\r\n            tokio::spawn(pong(socket, addr, state.clone(), rx));\r\n\r\n            Arc::new(Pong { updates: tx })\r\n        }))\r\n    .clone())\r\n}\r\n\r\nasync fn serve_async(\r\n    port: u16,\r\n    peer_server: bool,\r\n    msg: Box<dyn Fn(&str) + Send + Sync>,\r\n) -> Result<(), anyhow::Error> {\r\n    let state = Arc::new(State {\r\n        port,\r\n        started: Instant::now(),\r\n        dummy_data: crate::common::data(),\r\n        clients: Mutex::new((0..SLOTS).map(|_| None).collect()),\r\n        pong_servers: Default::default(),\r\n        msg,\r\n        peer_server,\r\n    });\r\n\r\n    let v6 = Socket::new(Domain::IPV6, socket2::Type::STREAM, Some(Protocol::TCP))?;\r\n    v6.set_only_v6(true)?;\r\n    let v6: std::net::TcpStream = v6.into();\r\n    v6.set_nonblocking(true)?;\r\n    let v6 = TcpSocket::from_std_stream(v6);\r\n    v6.bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port))\r\n        .map_err(|error| {\r\n            if let ErrorKind::AddrInUse = error.kind() {\r\n                anyhow!(\r\n                    \"Failed to bind TCP port, maybe another Crusader instance is already running\"\r\n                )\r\n            } else {\r\n                error.into()\r\n            }\r\n        })?;\r\n    let v6 = v6.listen(1024)?;\r\n\r\n    let v4 = TcpListener::bind((Ipv4Addr::UNSPECIFIED, port)).await?;\r\n\r\n    task::spawn(listen(state.clone(), v6));\r\n    task::spawn(listen(state.clone(), v4));\r\n\r\n    if let Err(error) = discovery::serve(state.clone(), port) {\r\n        (state.msg)(&format!(\"Failed to run discovery: {:?}\", error));\r\n    }\r\n\r\n    (state.msg)(&format!(\"Server version {} running...\", version()));\r\n\r\n    for (name, ip) in interface_ips() {\r\n        (state.msg)(&format!(\"Address on `{name}`: {ip}\"));\r\n    }\r\n\r\n    if peer_server {\r\n        (state.msg)(\"Server is in peer mode\");\r\n    }\r\n\r\n    Ok(())\r\n}\r\n\r\npub fn serve_until(\r\n    port: u16,\r\n    peer_server: bool,\r\n    msg: Box<dyn Fn(&str) + Send + Sync>,\r\n    started: Box<dyn FnOnce(Result<(), String>) + Send>,\r\n    done: Box<dyn FnOnce() + Send>,\r\n) -> Result<oneshot::Sender<()>, anyhow::Error> {\r\n    let (tx, rx) = oneshot::channel();\r\n\r\n    let rt = tokio::runtime::Runtime::new()?;\r\n\r\n    thread::spawn(move || {\r\n        rt.block_on(async move {\r\n            match serve_async(port, peer_server, msg).await {\r\n                Ok(()) => {\r\n                    started(Ok(()));\r\n                    rx.await.ok();\r\n                }\r\n                Err(error) => started(Err(error.to_string())),\r\n            }\r\n        });\r\n\r\n        done();\r\n    });\r\n\r\n    Ok(tx)\r\n}\r\n\r\npub fn serve(port: u16, peer_server: bool) -> Result<(), anyhow::Error> {\r\n    let rt = tokio::runtime::Runtime::new()?;\r\n    rt.block_on(async move {\r\n        serve_async(\r\n            port,\r\n            peer_server,\r\n            Box::new(|msg: &str| {\r\n                let msg = msg.to_owned();\r\n                task::spawn_blocking(move || println!(\"{}\", with_time(&msg)));\r\n            }),\r\n        )\r\n        .await?;\r\n        signal::ctrl_c().await?;\r\n        println!(\"{}\", with_time(\"Server aborting...\"));\r\n        Ok(())\r\n    })\r\n}\r\n"
  },
  {
    "path": "src/crusader-lib/src/test.rs",
    "content": "use crate::common::{\r\n    connect, data, fresh_socket_addr, hello, measure_latency, ping_recv, ping_send, read_data,\r\n    wait_for_state, write_data, Config, LatencyResult, Msg, TestState,\r\n};\r\nuse crate::file_format::{\r\n    RawConfig, RawHeader, RawPing, RawPoint, RawResult, RawStream, RawStreamGroup, TestData,\r\n    TestKind,\r\n};\r\nuse crate::peer::connect_to_peer;\r\nuse crate::plot::save_graph;\r\nuse crate::protocol::{\r\n    codec, receive, send, ClientMessage, Hello, RawLatency, ServerMessage, TestStream,\r\n};\r\nuse crate::{discovery, version, with_time};\r\nuse anyhow::{anyhow, bail, Context};\r\nuse bytes::{Bytes, BytesMut};\r\nuse futures::future::FutureExt;\r\nuse futures::{select, Sink, Stream};\r\nuse futures::{stream, StreamExt};\r\nuse parking_lot::Mutex;\r\nuse std::collections::HashMap;\r\nuse std::path::{Path, PathBuf};\r\nuse std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\r\nuse std::thread;\r\nuse std::{\r\n    error::Error,\r\n    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},\r\n    sync::Arc,\r\n    time::Duration,\r\n};\r\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\r\nuse tokio::net::TcpStream;\r\nuse tokio::sync::mpsc::{channel, Sender};\r\nuse tokio::sync::{oneshot, watch, Semaphore};\r\nuse tokio::task::{self, JoinHandle};\r\nuse tokio::time::Instant;\r\nuse tokio::{\r\n    net::{self},\r\n    time,\r\n};\r\nuse tokio_util::codec::{Framed, FramedRead, FramedWrite, LengthDelimitedCodec};\r\n\r\nconst MEASURE_DELAY: Duration = Duration::from_millis(50);\r\n\r\n#[derive(Debug)]\r\nstruct ScheduledLoads;\r\n\r\nstruct State {\r\n    downloads: Mutex<HashMap<TestStream, oneshot::Sender<()>>>,\r\n    timeout: AtomicBool,\r\n}\r\n\r\nasync fn hello_combined<S: Sink<Bytes> + Stream<Item = Result<BytesMut, S::Error>> + Unpin>(\r\n    stream: &mut S,\r\n) -> Result<(), anyhow::Error>\r\nwhere\r\n    S::Error: Error + Send + Sync + 'static,\r\n{\r\n    let hello = Hello::new();\r\n\r\n    send(stream, &hello).await?;\r\n    let server_hello: Hello = receive(stream).await?;\r\n\r\n    if hello != server_hello {\r\n        panic!(\r\n            \"Mismatched server hello, got {:?}, expected {:?}\",\r\n            server_hello, hello\r\n        );\r\n    }\r\n\r\n    Ok(())\r\n}\r\n\r\n#[derive(Default)]\r\npub struct PlotConfig {\r\n    pub split_throughput: bool,\r\n    pub transferred: bool,\r\n    pub max_throughput: Option<u64>,\r\n    pub max_latency: Option<u64>,\r\n    pub width: Option<u64>,\r\n    pub height: Option<u64>,\r\n    pub title: Option<String>,\r\n}\r\n\r\npub(crate) async fn test_async(\r\n    config: Config,\r\n    server: Option<&str>,\r\n    latency_peer_server: Option<Option<&str>>,\r\n    msg: Msg,\r\n) -> Result<RawResult, anyhow::Error> {\r\n    msg(&format!(\"Client version {} running\", version()));\r\n\r\n    let control = if let Some(server) = server {\r\n        connect((server, config.port), \"server\").await?\r\n    } else {\r\n        let server = discovery::locate(false).await?;\r\n        msg(&format!(\r\n            \"Found server at {} running version {}\",\r\n            server.at, server.software_version\r\n        ));\r\n        connect(server.socket, \"server\").await?\r\n    };\r\n\r\n    control.set_nodelay(true)?;\r\n\r\n    let server = control.peer_addr()?;\r\n    let server = fresh_socket_addr(server, server.port());\r\n\r\n    msg(&format!(\"Connected to server {}\", server));\r\n\r\n    let (rx, tx) = control.into_split();\r\n    let mut control_rx = FramedRead::new(rx, codec());\r\n    let mut control_tx = FramedWrite::new(tx, codec());\r\n\r\n    hello(&mut control_tx, &mut control_rx)\r\n        .await\r\n        .context(\"Failed protocol handshake\")?;\r\n\r\n    send(&mut control_tx, &ClientMessage::NewClient).await?;\r\n\r\n    let setup_start = Instant::now();\r\n\r\n    let reply: ServerMessage = receive(&mut control_rx)\r\n        .await\r\n        .context(\"Failed to create a new client id\")?;\r\n    let id = match reply {\r\n        ServerMessage::NewClient(Some(id)) => id,\r\n        ServerMessage::NewClient(None) => bail!(\"Server was unable to create client\"),\r\n        _ => bail!(\"Unexpected message {:?}\", reply),\r\n    };\r\n\r\n    let loading_streams: u32 = config.streams.try_into()?;\r\n\r\n    let grace = config.grace_duration;\r\n    let load_duration = config.load_duration;\r\n    let ping_interval = config.ping_interval;\r\n\r\n    let loads = config.bidirectional as u32 + config.download as u32 + config.upload as u32;\r\n\r\n    let estimated_duration = load_duration * loads + grace * 2;\r\n\r\n    let mut peer = if let Some(peer) = latency_peer_server {\r\n        Some(connect_to_peer(config, server, peer, estimated_duration, msg.clone()).await?)\r\n    } else {\r\n        None\r\n    };\r\n\r\n    let local_udp = if server.is_ipv6() {\r\n        SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)\r\n    } else {\r\n        SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)\r\n    };\r\n\r\n    let mut ping_index = 0;\r\n\r\n    let LatencyResult {\r\n        latency,\r\n        server_pong: pre_server_pong,\r\n        server_time: pre_server_time,\r\n        mut control_rx,\r\n        ..\r\n    } = measure_latency(\r\n        id,\r\n        &mut ping_index,\r\n        &mut control_tx,\r\n        control_rx,\r\n        server,\r\n        local_udp,\r\n        setup_start,\r\n    )\r\n    .await?;\r\n\r\n    msg(&format!(\r\n        \"Idle latency to server {:.2} ms\",\r\n        latency.as_secs_f64() * 1000.0\r\n    ));\r\n\r\n    let udp_socket = Arc::new(net::UdpSocket::bind(local_udp).await?);\r\n    udp_socket.connect(server).await?;\r\n    let udp_socket2 = udp_socket.clone();\r\n\r\n    let data = Arc::new(data());\r\n\r\n    let state = Arc::new(State {\r\n        downloads: Mutex::new(HashMap::new()),\r\n        timeout: AtomicBool::new(false),\r\n    });\r\n\r\n    let (state_tx, state_rx) = watch::channel((TestState::Setup, setup_start));\r\n\r\n    let all_loaders = Arc::new(Semaphore::new(0));\r\n    let mut loader_count = 0;\r\n\r\n    let (upload_done_tx, mut upload_done_rx) = channel(config.streams as usize);\r\n\r\n    if config.upload {\r\n        loader_count += config.streams;\r\n        upload_loaders(\r\n            all_loaders.clone(),\r\n            id,\r\n            server,\r\n            0,\r\n            config,\r\n            Duration::ZERO,\r\n            data.clone(),\r\n            state_rx.clone(),\r\n            TestState::LoadFromClient,\r\n            upload_done_tx.clone(),\r\n        );\r\n    }\r\n\r\n    if config.bidirectional {\r\n        loader_count += config.streams;\r\n        upload_loaders(\r\n            all_loaders.clone(),\r\n            id,\r\n            server,\r\n            1,\r\n            config,\r\n            config.stream_stagger / 2,\r\n            data.clone(),\r\n            state_rx.clone(),\r\n            TestState::LoadFromBoth,\r\n            upload_done_tx.clone(),\r\n        );\r\n    }\r\n\r\n    let download = config.download.then(|| {\r\n        loader_count += config.streams;\r\n        download_loaders(\r\n            state.clone(),\r\n            all_loaders.clone(),\r\n            id,\r\n            server,\r\n            2,\r\n            config,\r\n            setup_start,\r\n            state_rx.clone(),\r\n            TestState::LoadFromServer,\r\n        )\r\n    });\r\n\r\n    let both_download = config.bidirectional.then(|| {\r\n        loader_count += config.streams;\r\n        download_loaders(\r\n            state.clone(),\r\n            all_loaders.clone(),\r\n            id,\r\n            server,\r\n            3,\r\n            config,\r\n            setup_start,\r\n            state_rx.clone(),\r\n            TestState::LoadFromBoth,\r\n        )\r\n    });\r\n\r\n    send(&mut control_tx, &ClientMessage::GetMeasurements).await?;\r\n\r\n    // Wait for all loaders to setup\r\n    let _ = all_loaders.acquire_many(loader_count as u32).await?;\r\n\r\n    let upload_semaphore = Arc::new(Semaphore::new(0));\r\n    let upload_semaphore_ = upload_semaphore.clone();\r\n    let both_upload_semaphore = Arc::new(Semaphore::new(0));\r\n    let both_upload_semaphore_ = both_upload_semaphore.clone();\r\n\r\n    let (scheduled_load_tx, mut scheduled_load_rx) = channel(4);\r\n\r\n    let state_ = state.clone();\r\n    let measures = tokio::spawn(async move {\r\n        let mut throughput = Vec::new();\r\n        let mut latencies = Vec::new();\r\n        let overload_;\r\n\r\n        loop {\r\n            let reply: ServerMessage = receive(&mut control_rx).await?;\r\n            match reply {\r\n                ServerMessage::MeasureStreamDone { stream, timeout } => {\r\n                    if timeout {\r\n                        state_.timeout.store(true, Ordering::SeqCst);\r\n                    }\r\n\r\n                    if stream.group == 0 {\r\n                        upload_semaphore_.add_permits(1);\r\n                    } else if stream.group == 1 {\r\n                        both_upload_semaphore_.add_permits(1);\r\n                    }\r\n                }\r\n                ServerMessage::Measure {\r\n                    stream,\r\n                    time,\r\n                    bytes,\r\n                } => {\r\n                    throughput.push((stream, time, bytes));\r\n                }\r\n                ServerMessage::LatencyMeasures(measures) => {\r\n                    latencies.extend(measures.into_iter());\r\n                }\r\n                ServerMessage::MeasurementsDone { overload } => {\r\n                    overload_ = overload;\r\n                    break;\r\n                }\r\n                ServerMessage::LoadComplete { stream } => {\r\n                    state_\r\n                        .downloads\r\n                        .lock()\r\n                        .remove(&stream)\r\n                        .ok_or(anyhow!(\"Failed to find stream\"))?\r\n                        .send(())\r\n                        .map_err(|_| anyhow!(\"Failed to notify downloader\"))?;\r\n                }\r\n                ServerMessage::ScheduledLoads { groups: _, time: _ } => {\r\n                    scheduled_load_tx.send(ScheduledLoads).await?\r\n                }\r\n                _ => bail!(\"Unexpected message {:?}\", reply),\r\n            };\r\n        }\r\n\r\n        Ok((latencies, throughput, overload_, control_rx))\r\n    });\r\n\r\n    if let Some(peer) = peer.as_mut() {\r\n        peer.start().await?;\r\n    }\r\n\r\n    let ping_start_index = ping_index;\r\n    let ping_send = tokio::spawn(ping_send(\r\n        ping_index,\r\n        id,\r\n        state_rx.clone(),\r\n        setup_start,\r\n        udp_socket2.clone(),\r\n        ping_interval,\r\n        estimated_duration,\r\n    ));\r\n\r\n    let ping_recv = tokio::spawn(ping_recv(\r\n        state_rx.clone(),\r\n        setup_start,\r\n        udp_socket2.clone(),\r\n        ping_interval,\r\n        estimated_duration,\r\n    ));\r\n\r\n    time::sleep(Duration::from_millis(50)).await;\r\n\r\n    let start = Instant::now();\r\n\r\n    state_tx.send((TestState::Grace1, start))?;\r\n    time::sleep(grace).await;\r\n\r\n    let load_delay_pure = Duration::from_millis(50);\r\n    let load_delay = (load_delay_pure + latency / 2).as_micros() as u64;\r\n\r\n    let start_time = || -> Result<Instant, anyhow::Error> {\r\n        Instant::now()\r\n            .checked_add(load_delay_pure)\r\n            .ok_or(anyhow!(\"Time overflow\"))?\r\n            .checked_sub(latency / 2)\r\n            .ok_or(anyhow!(\"Time overflow\"))\r\n    };\r\n\r\n    let mut test_data = Vec::new();\r\n\r\n    if let Some((semaphore, _)) = download.as_ref() {\r\n        send(\r\n            &mut control_tx,\r\n            &ClientMessage::ScheduleLoads {\r\n                groups: vec![2],\r\n                delay: load_delay,\r\n            },\r\n        )\r\n        .await?;\r\n        scheduled_load_rx\r\n            .recv()\r\n            .await\r\n            .ok_or(anyhow!(\"Failed to receive\"))?;\r\n        let start = start_time()?;\r\n        state_tx.send((TestState::LoadFromServer, start))?;\r\n        msg(&format!(\"Testing download...\"));\r\n        let _ = semaphore.acquire_many(loading_streams).await?;\r\n        let end = Instant::now();\r\n        test_data.push(TestData {\r\n            start: start.duration_since(setup_start),\r\n            end: end.duration_since(setup_start),\r\n            kind: TestKind::Download,\r\n        });\r\n        state_tx.send((TestState::Grace2, end))?;\r\n        time::sleep(grace).await;\r\n    }\r\n\r\n    if config.upload {\r\n        send(\r\n            &mut control_tx,\r\n            &ClientMessage::ScheduleLoads {\r\n                groups: vec![0],\r\n                delay: load_delay,\r\n            },\r\n        )\r\n        .await?;\r\n        scheduled_load_rx\r\n            .recv()\r\n            .await\r\n            .ok_or(anyhow!(\"Failed to receive\"))?;\r\n        let start = start_time()?;\r\n        state_tx.send((TestState::LoadFromClient, start))?;\r\n        msg(&format!(\"Testing upload...\"));\r\n\r\n        for _ in 0..config.streams {\r\n            let stream = upload_done_rx\r\n                .recv()\r\n                .await\r\n                .ok_or(anyhow!(\"Expected stream\"))?;\r\n            send(&mut control_tx, &ClientMessage::LoadComplete { stream }).await?;\r\n        }\r\n\r\n        let _ = upload_semaphore.acquire_many(loading_streams).await?;\r\n\r\n        let end = Instant::now();\r\n        test_data.push(TestData {\r\n            start: start.duration_since(setup_start),\r\n            end: end.duration_since(setup_start),\r\n            kind: TestKind::Upload,\r\n        });\r\n\r\n        state_tx.send((TestState::Grace3, end))?;\r\n        time::sleep(grace).await;\r\n    }\r\n\r\n    if let Some((semaphore, _)) = both_download.as_ref() {\r\n        send(\r\n            &mut control_tx,\r\n            &ClientMessage::ScheduleLoads {\r\n                groups: vec![1, 3],\r\n                delay: load_delay,\r\n            },\r\n        )\r\n        .await?;\r\n        scheduled_load_rx\r\n            .recv()\r\n            .await\r\n            .ok_or(anyhow!(\"Failed to receive\"))?;\r\n        let start = start_time()?;\r\n        state_tx.send((TestState::LoadFromBoth, start))?;\r\n        msg(&format!(\"Testing both download and upload...\"));\r\n\r\n        for _ in 0..config.streams {\r\n            let stream = upload_done_rx\r\n                .recv()\r\n                .await\r\n                .ok_or(anyhow!(\"Expected stream\"))?;\r\n            send(&mut control_tx, &ClientMessage::LoadComplete { stream }).await?;\r\n        }\r\n\r\n        let _ = semaphore.acquire_many(loading_streams).await?;\r\n        let _ = both_upload_semaphore.acquire_many(loading_streams).await?;\r\n\r\n        let end = Instant::now();\r\n        test_data.push(TestData {\r\n            start: start.duration_since(setup_start),\r\n            end: end.duration_since(setup_start),\r\n            kind: TestKind::Bidirectional,\r\n        });\r\n\r\n        state_tx.send((TestState::Grace4, end))?;\r\n        time::sleep(grace).await;\r\n    }\r\n\r\n    state_tx.send((TestState::End, Instant::now()))?;\r\n\r\n    if let Some(peer) = peer.as_mut() {\r\n        peer.stop().await?;\r\n    }\r\n\r\n    // Wait for pings to return\r\n    time::sleep(Duration::from_millis(500)).await;\r\n    state_tx.send((TestState::EndPingRecv, Instant::now()))?;\r\n\r\n    let peer = if let Some(peer) = peer {\r\n        Some(\r\n            peer.complete()\r\n                .await\r\n                .context(\"Failed to wait for peer completion\")?,\r\n        )\r\n    } else {\r\n        None\r\n    };\r\n\r\n    let duration = start.elapsed();\r\n\r\n    let (pings_sent, mut ping_index) = ping_send.await??;\r\n    let mut pongs = ping_recv.await??;\r\n\r\n    send(&mut control_tx, &ClientMessage::StopMeasurements).await?;\r\n\r\n    let (mut latencies, throughput, server_overload, control_rx) = measures.await??;\r\n\r\n    let LatencyResult {\r\n        server_pong: post_server_pong,\r\n        server_time: post_server_time,\r\n        ..\r\n    } = measure_latency(\r\n        id,\r\n        &mut ping_index,\r\n        &mut control_tx,\r\n        control_rx,\r\n        server,\r\n        local_udp,\r\n        setup_start,\r\n    )\r\n    .await?;\r\n\r\n    send(&mut control_tx, &ClientMessage::Done).await?;\r\n\r\n    let server_time = post_server_time.wrapping_sub(pre_server_time);\r\n    let client_time = post_server_pong.saturating_sub(pre_server_pong);\r\n    let client_time_micros = client_time.as_micros() as f64;\r\n    let ratio = client_time_micros / server_time as f64;\r\n\r\n    let to_client_time = |server_time: u64| -> u64 {\r\n        let time = server_time.wrapping_sub(pre_server_time);\r\n        let time = (time as f64 * ratio) as u64;\r\n        (pre_server_pong.as_micros() as u64).saturating_add(time)\r\n    };\r\n\r\n    let server_overload = server_overload || peer.as_ref().map(|p| p.0).unwrap_or_default();\r\n\r\n    let peer_latencies = peer.map(|(_, latencies)| {\r\n        latencies\r\n            .into_iter()\r\n            .enumerate()\r\n            .map(|(i, p)| RawPing {\r\n                index: i as u64,\r\n                sent: Duration::from_micros(to_client_time(p.sent)),\r\n                latency: p.latency,\r\n            })\r\n            .collect::<Vec<_>>()\r\n    });\r\n\r\n    let download_bytes = wait_on_download_loaders(download).await?;\r\n    let both_download_bytes = wait_on_download_loaders(both_download).await?;\r\n\r\n    latencies.sort_by_key(|d| d.index);\r\n    pongs.sort_by_key(|d| d.0.index);\r\n    let pings: Vec<_> = pings_sent\r\n        .into_iter()\r\n        .enumerate()\r\n        .map(|(index, sent)| {\r\n            let index = index as u64 + ping_start_index;\r\n            let mut latency = latencies\r\n                .binary_search_by_key(&index, |e| e.index)\r\n                .ok()\r\n                .map(|ping| RawLatency {\r\n                    total: None,\r\n                    up: Duration::from_micros(to_client_time(latencies[ping].time))\r\n                        .saturating_sub(sent),\r\n                });\r\n\r\n            latency.as_mut().map(|latency| {\r\n                pongs\r\n                    .binary_search_by_key(&index, |e| e.0.index)\r\n                    .ok()\r\n                    .map(|ping| {\r\n                        let total = pongs[ping].1.saturating_sub(sent);\r\n                        latency.total = Some(total);\r\n                        // Ensure `up` stays below `total`\r\n                        latency.up = latency.up.min(total);\r\n                    });\r\n            });\r\n\r\n            RawPing {\r\n                index,\r\n                sent,\r\n                latency,\r\n            }\r\n        })\r\n        .collect();\r\n\r\n    let mut raw_streams = Vec::new();\r\n\r\n    let to_raw = |data: &[(u64, u64)]| -> RawStream {\r\n        RawStream {\r\n            data: data\r\n                .iter()\r\n                .map(|&(time, bytes)| RawPoint {\r\n                    time: Duration::from_micros(time),\r\n                    bytes,\r\n                })\r\n                .collect(),\r\n        }\r\n    };\r\n\r\n    let mut add_down = |both, data: &Option<Vec<Vec<(u64, u64)>>>| {\r\n        data.as_ref().map(|download_bytes| {\r\n            raw_streams.push(RawStreamGroup {\r\n                download: true,\r\n                both,\r\n                streams: download_bytes.iter().map(|stream| to_raw(stream)).collect(),\r\n            });\r\n        });\r\n    };\r\n\r\n    add_down(false, &download_bytes);\r\n    add_down(true, &both_download_bytes);\r\n\r\n    let get_stream = |group, id| -> Vec<_> {\r\n        throughput\r\n            .iter()\r\n            .filter(|e| e.0.group == group && e.0.id == id)\r\n            .map(|e| (to_client_time(e.1), e.2))\r\n            .collect()\r\n    };\r\n\r\n    let get_raw_upload_bytes = |group| -> Vec<RawStream> {\r\n        (0..loading_streams)\r\n            .map(|i| to_raw(&get_stream(group, i)))\r\n            .collect()\r\n    };\r\n\r\n    config.upload.then(|| {\r\n        raw_streams.push(RawStreamGroup {\r\n            download: false,\r\n            both: false,\r\n            streams: get_raw_upload_bytes(0),\r\n        })\r\n    });\r\n\r\n    config.bidirectional.then(|| {\r\n        raw_streams.push(RawStreamGroup {\r\n            download: false,\r\n            both: true,\r\n            streams: get_raw_upload_bytes(1),\r\n        })\r\n    });\r\n\r\n    let raw_config = RawConfig {\r\n        stagger: config.stream_stagger,\r\n        load_duration: config.load_duration,\r\n        grace_duration: config.grace_duration,\r\n        ping_interval: config.ping_interval,\r\n        bandwidth_interval: config.throughput_interval,\r\n    };\r\n\r\n    if server_overload {\r\n        msg(&format!(\r\n            \"Warning: Server overload detected during test. Result should be discarded.\"\r\n        ));\r\n    }\r\n\r\n    let load_termination_timeout = state.timeout.load(Ordering::SeqCst);\r\n\r\n    if load_termination_timeout {\r\n        msg(&format!(\r\n            \"Warning: Load termination timed out. There may be residual untracked traffic in the background.\"\r\n        ));\r\n    }\r\n\r\n    let start = start.duration_since(setup_start);\r\n\r\n    let raw_result = RawResult {\r\n        version: RawHeader::default().version,\r\n        generated_by: format!(\"Crusader {}\", version()),\r\n        config: raw_config,\r\n        ipv6: server.is_ipv6(),\r\n        load_termination_timeout,\r\n        server_overload,\r\n        server_latency: latency,\r\n        start,\r\n        duration,\r\n        stream_groups: raw_streams,\r\n        pings,\r\n        peer_pings: peer_latencies,\r\n        test_data,\r\n    };\r\n\r\n    Ok(raw_result)\r\n}\r\n\r\npub fn save_raw(\r\n    result: &RawResult,\r\n    name: &str,\r\n    root_path: &Path,\r\n) -> Result<PathBuf, anyhow::Error> {\r\n    std::fs::create_dir_all(root_path)?;\r\n    let name = unique(name, \"crr\", root_path);\r\n    let path = root_path.join(&name);\r\n    result.save(&path)?;\r\n    Ok(path)\r\n}\r\n\r\nfn setup_loaders(\r\n    id: u64,\r\n    server: SocketAddr,\r\n    count: u64,\r\n) -> Vec<JoinHandle<Result<Framed<TcpStream, LengthDelimitedCodec>, anyhow::Error>>> {\r\n    (0..count)\r\n        .map(|_| {\r\n            tokio::spawn(async move {\r\n                let stream = TcpStream::connect(server)\r\n                    .await\r\n                    .context(\"Failed connect to server for throughput connection\")?;\r\n                stream.set_nodelay(true)?;\r\n                let mut stream = Framed::new(stream, codec());\r\n                hello_combined(&mut stream).await?;\r\n                send(&mut stream, &ClientMessage::Associate(id)).await?;\r\n\r\n                Ok(stream)\r\n            })\r\n        })\r\n        .collect()\r\n}\r\n\r\nfn upload_loaders(\r\n    all_loaders: Arc<Semaphore>,\r\n    id: u64,\r\n    server: SocketAddr,\r\n    group: u32,\r\n    config: Config,\r\n    stagger_offset: Duration,\r\n    data: Arc<Vec<u8>>,\r\n    state_rx: watch::Receiver<(TestState, Instant)>,\r\n    state: TestState,\r\n    done: Sender<TestStream>,\r\n) {\r\n    let loaders = setup_loaders(id, server, config.streams);\r\n\r\n    for (i, loader) in loaders.into_iter().enumerate() {\r\n        let mut state_rx = state_rx.clone();\r\n        let data = data.clone();\r\n        let all_loaders = all_loaders.clone();\r\n        let done = done.clone();\r\n        tokio::spawn(async move {\r\n            let mut stream = loader.await??;\r\n\r\n            let delay = config.stream_stagger * i as u32 + stagger_offset;\r\n\r\n            let test_stream = TestStream {\r\n                group,\r\n                id: i as u32,\r\n            };\r\n\r\n            send(\r\n                &mut stream,\r\n                &ClientMessage::LoadFromClient {\r\n                    stream: test_stream,\r\n                    delay: delay.as_micros() as u64,\r\n                    duration: (config.load_duration + MEASURE_DELAY).as_micros() as u64,\r\n                    throughput_interval: config.throughput_interval.as_micros() as u64,\r\n                },\r\n            )\r\n            .await?;\r\n            let reply: ServerMessage = receive(&mut stream).await?;\r\n            match reply {\r\n                ServerMessage::WaitingForLoad => (),\r\n                _ => panic!(\"Unexpected message {:?}\", reply),\r\n            };\r\n\r\n            send(&mut stream, &ClientMessage::SendByte).await?;\r\n\r\n            // Wait for a pending read byte\r\n            {\r\n                let mut stream_rx = stream.get_mut().split().0;\r\n                loop {\r\n                    let _ = stream_rx.read(&mut []).await?;\r\n                    match time::timeout(Duration::from_millis(10), stream_rx.peek(&mut [0])).await {\r\n                        Ok(Ok(1)) => break,\r\n                        Err(_) | Ok(Ok(_)) => (),\r\n                        Ok(Err(err)) => panic!(\"{:?}\", err),\r\n                    }\r\n                }\r\n            }\r\n\r\n            all_loaders.add_permits(1);\r\n\r\n            let start = wait_for_state(&mut state_rx, state).await? + MEASURE_DELAY + delay;\r\n\r\n            time::sleep_until(start).await;\r\n\r\n            write_data(\r\n                stream.into_inner(),\r\n                data.as_ref(),\r\n                start + config.load_duration,\r\n            )\r\n            .await\r\n            .unwrap();\r\n\r\n            done.send(test_stream).await?;\r\n            Ok::<(), anyhow::Error>(())\r\n        });\r\n    }\r\n}\r\n\r\nasync fn wait_on_download_loaders(\r\n    download: Option<(\r\n        Arc<Semaphore>,\r\n        Vec<JoinHandle<Result<Vec<(u64, u64)>, anyhow::Error>>>,\r\n    )>,\r\n) -> Result<Option<Vec<Vec<(u64, u64)>>>, anyhow::Error> {\r\n    match download {\r\n        Some((_, result)) => {\r\n            let bytes: Vec<_> = stream::iter(result)\r\n                .then(|data| async move { data.await? })\r\n                .collect()\r\n                .await;\r\n            let bytes: Result<Vec<_>, _> = bytes.into_iter().collect();\r\n            Ok(Some(bytes?))\r\n        }\r\n        None => Ok(None),\r\n    }\r\n}\r\n\r\nfn download_loaders(\r\n    state: Arc<State>,\r\n    all_loaders: Arc<Semaphore>,\r\n    id: u64,\r\n    server: SocketAddr,\r\n    group: u32,\r\n    config: Config,\r\n    setup_start: Instant,\r\n    state_rx: watch::Receiver<(TestState, Instant)>,\r\n    test_state: TestState,\r\n) -> (\r\n    Arc<Semaphore>,\r\n    Vec<JoinHandle<Result<Vec<(u64, u64)>, anyhow::Error>>>,\r\n) {\r\n    let semaphore = Arc::new(Semaphore::new(0));\r\n    let loaders = setup_loaders(id, server, config.streams);\r\n\r\n    let loaders = loaders\r\n        .into_iter()\r\n        .enumerate()\r\n        .map(|(i, loader)| {\r\n            let mut state_rx = state_rx.clone();\r\n            let state = state.clone();\r\n            let semaphore = semaphore.clone();\r\n            let all_loaders = all_loaders.clone();\r\n\r\n            tokio::spawn(async move {\r\n                let mut stream = loader.await??;\r\n\r\n                let mut buffer = Vec::with_capacity(512 * 1024);\r\n                buffer.extend((0..buffer.capacity()).map(|_| 0));\r\n\r\n                let delay = config.stream_stagger * i as u32;\r\n\r\n                let test_stream = TestStream {\r\n                    group,\r\n                    id: i as u32,\r\n                };\r\n\r\n                send(\r\n                    &mut stream,\r\n                    &ClientMessage::LoadFromServer {\r\n                        stream: test_stream,\r\n                        duration: config.load_duration.as_micros() as u64,\r\n                        delay: (MEASURE_DELAY + delay).as_micros() as u64,\r\n                    },\r\n                )\r\n                .await?;\r\n\r\n                let reply: ServerMessage = receive(&mut stream).await?;\r\n                match reply {\r\n                    ServerMessage::WaitingForByte => (),\r\n                    _ => panic!(\"Unexpected message {:?}\", reply),\r\n                };\r\n\r\n                stream.get_mut().write_u8(1).await?;\r\n\r\n                let reply: ServerMessage = receive(&mut stream).await?;\r\n                match reply {\r\n                    ServerMessage::WaitingForLoad => (),\r\n                    _ => panic!(\"Unexpected message {:?}\", reply),\r\n                };\r\n\r\n                let stream = stream.into_inner();\r\n\r\n                let (reading_done_tx, reading_done_rx) = oneshot::channel();\r\n\r\n                state.downloads.lock().insert(test_stream, reading_done_tx);\r\n\r\n                let bytes = Arc::new(AtomicU64::new(0));\r\n                let bytes_ = bytes.clone();\r\n\r\n                let done = Arc::new(AtomicBool::new(false));\r\n                let done_ = done.clone();\r\n\r\n                all_loaders.add_permits(1);\r\n\r\n                let start = wait_for_state(&mut state_rx, test_state).await? + delay;\r\n\r\n                time::sleep_until(start).await;\r\n\r\n                let measures = tokio::spawn(async move {\r\n                    let mut measures = Vec::new();\r\n                    let mut interval = time::interval(config.throughput_interval);\r\n                    loop {\r\n                        interval.tick().await;\r\n\r\n                        let current_time = Instant::now();\r\n                        let current_bytes = bytes_.load(Ordering::Acquire);\r\n\r\n                        measures.push((\r\n                            current_time.duration_since(setup_start).as_micros() as u64,\r\n                            current_bytes,\r\n                        ));\r\n\r\n                        if done_.load(Ordering::Acquire) {\r\n                            break;\r\n                        }\r\n                    }\r\n                    measures\r\n                });\r\n\r\n                let timeout = read_data(\r\n                    stream,\r\n                    &mut buffer,\r\n                    bytes,\r\n                    start + MEASURE_DELAY + config.load_duration,\r\n                    reading_done_rx,\r\n                )\r\n                .await?;\r\n\r\n                if timeout {\r\n                    state.timeout.store(true, Ordering::SeqCst);\r\n                }\r\n\r\n                done.store(true, Ordering::Release);\r\n\r\n                semaphore.add_permits(1);\r\n\r\n                Ok::<_, anyhow::Error>(measures.await?)\r\n            })\r\n        })\r\n        .collect();\r\n    (semaphore, loaders)\r\n}\r\n\r\npub fn timed(name: &str) -> String {\r\n    let time = chrono::Local::now().format(\" %Y-%m-%d %H.%M.%S\");\r\n    format!(\"{}{}\", name, time)\r\n}\r\n\r\npub(crate) fn unique(name: &str, ext: &str, root_path: &Path) -> String {\r\n    let stem = name.to_owned();\r\n    let mut i: usize = 0;\r\n    loop {\r\n        let file = if i != 0 {\r\n            format!(\"{} {}\", stem, i)\r\n        } else {\r\n            stem.to_string()\r\n        };\r\n        let file = format!(\"{}.{}\", file, ext);\r\n        if !root_path.join(&file).exists() {\r\n            return file;\r\n        }\r\n        i += 1;\r\n    }\r\n}\r\n\r\npub fn test(\r\n    config: Config,\r\n    plot: PlotConfig,\r\n    host: Option<&str>,\r\n    latency_peer_server: Option<Option<&str>>,\r\n    out_name: &str,\r\n) -> Result<(), anyhow::Error> {\r\n    let rt = tokio::runtime::Runtime::new().unwrap();\r\n    let result = rt.block_on(test_async(\r\n        config,\r\n        host,\r\n        latency_peer_server,\r\n        Arc::new(|msg| println!(\"{}\", with_time(msg))),\r\n    ));\r\n    let result = match result {\r\n        Ok(result) => result,\r\n        Err(error) => {\r\n            println!(\"{}\", with_time(&format!(\"Client failed\")));\r\n            return Err(error);\r\n        }\r\n    };\r\n    let out_name = timed(out_name);\r\n    let test_result = result.to_test_result();\r\n    print!(\"\\n{}\", test_result.summary()?);\r\n    println!(\"{}\", with_time(\"Writing data...\"));\r\n    let path = Path::new(\"crusader-results\");\r\n    let raw = save_raw(&result, &out_name, path)?;\r\n    println!(\r\n        \"{}\",\r\n        with_time(&format!(\"Saved raw data as {}\", raw.display()))\r\n    );\r\n    let plot = save_graph(&plot, &test_result, &out_name, path)?;\r\n    println!(\r\n        \"{}\",\r\n        with_time(&format!(\"Saved plot as {}\", path.join(plot).display()))\r\n    );\r\n    Ok(())\r\n}\r\n\r\npub fn test_callback(\r\n    config: Config,\r\n    host: Option<&str>,\r\n    latency_peer_server: Option<Option<&str>>,\r\n    msg: Arc<dyn Fn(&str) + Send + Sync>,\r\n    done: Box<dyn FnOnce(Option<Result<RawResult, String>>) + Send>,\r\n) -> oneshot::Sender<()> {\r\n    let (tx, rx) = oneshot::channel();\r\n    let host = host.map(|host| host.to_string());\r\n    let latency_peer_server = latency_peer_server.map(|host| host.map(|host| host.to_string()));\r\n    thread::spawn(move || {\r\n        let rt = tokio::runtime::Runtime::new().unwrap();\r\n\r\n        done(rt.block_on(async move {\r\n            let mut result = task::spawn(async move {\r\n                test_async(\r\n                    config,\r\n                    host.as_deref(),\r\n                    latency_peer_server.as_ref().map(|host| host.as_deref()),\r\n                    msg,\r\n                )\r\n                .await\r\n                .map_err(|error| format!(\"{:?}\", error))\r\n            })\r\n            .fuse();\r\n\r\n            select! {\r\n                result = result => {\r\n                    Some(result.map_err(|error| error.to_string()).and_then(|result| result))\r\n                },\r\n                result = rx.fuse() => {\r\n                    result.ok();\r\n                    None\r\n                },\r\n            }\r\n        }));\r\n    });\r\n    tx\r\n}\r\n"
  }
]