[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = tab\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\n"
  },
  {
    "path": ".gitattributes",
    "content": "public/apps/terminal.tapp/scripts/** linguist-vendored\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: NovaAppsInc\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: snoot2204\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug report with a given code from the OS\nabout: This should be used when the OS gives a Error Code and/or Error Message\ntitle: ''\nlabels: bug\nassignees: NovaAppsInc, Notplayingallday383\n---\n\n**Error Code and/or Error Message**\nProvide the Error Code and/or Error Message provided from the OS; it they required field should be copied to you clipboard if consented to it.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**OS Version and platform info**\n- Site you used [e.g. someterbiumrepl.repl.co]\n- Version (The latest version currently is v2.0, If in the About app it doesn't say v2.0 then consider updating your instance.)\n- Browser [e.g. Chrome, Firefox]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ndefault: bug-report.md\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"pnpm\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"saturday\""
  },
  {
    "path": ".github/workflows/biome.yml",
    "content": "name: \"Biome Code Quality Assurance\"\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  quality:\n    runs-on: \"ubuntu-latest\"\n\n    permissions:\n      contents: \"write\"\n\n    steps:\n      - name: \"Checkout code\"\n        uses: \"actions/checkout@v5\"\n        with:\n          token: \"${{ secrets.GITHUB_TOKEN }}\"\n\n      - name: \"Setup Biome\"\n        uses: \"biomejs/setup-biome@v2\"\n        with:\n          version: \"latest\"\n\n      - name: \"Format with Biome\"\n        run: \"biome format --write .\"\n\n      - name: \"Check for Git changes\"\n        id: \"verify-changed-files\"\n        run: |\n          if [ -n \"$(git status --porcelain)\" ]; then\n            echo \"changed=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"changed=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: \"Commit Git changes\"\n        if: steps.verify-changed-files.outputs.changed == 'true' && github.event_name == 'push'\n        run: |\n          git -c user.name=\"github-actions[bot]\" -c user.email=\"41898282+github-actions[bot]@users.noreply.github.com\" \\\n            commit -am \"chore: auto-fix formatting and linting with Biome\"\n\n      - name: \"Push Git changes\"\n        if: steps.verify-changed-files.outputs.changed == 'true' && github.event_name == 'push'\n        uses: \"ad-m/github-push-action@master\"\n        with:\n          github_token: \"${{ secrets.GITHUB_TOKEN }}\"\n          branch: \"${{ github.ref_name }}\"\n\n      - name: \"Fail if formatting was needed\"\n        if: steps.verify-changed-files.outputs.changed == 'true'\n        run: |\n          echo \"Biome formatting changes were needed. Please pull the latest changes.\"\n          exit 1\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Build Check\non:\n    push:\n    pull_request:\n\njobs:\n    build:\n        runs-on: ubuntu-latest\n        steps:\n            - name: Checkout code\n              uses: actions/checkout@v5\n            - name: Set up Node\n              uses: actions/setup-node@v6\n              with:\n                node-version: 'lts/*'\n            - name: Setup pnpm\n              uses: pnpm/action-setup@v4\n              with:\n                version: 10\n            - name: Install dependencies\n              run: pnpm i\n            - name: Build TB React\n              run: pnpm run build-static\n"
  },
  {
    "path": ".github/workflows/upk-build.yml",
    "content": "name: Build UPK for Anura\non:\n  push:\n      branches:\n          - main\n      paths:\n          - 'package.json'\n\njobs:\n    build:\n        runs-on: ubuntu-latest\n        steps:\n            - name: Checkout code\n              uses: actions/checkout@v5\n            - name: Set up Node\n              uses: actions/setup-node@v6\n              with:\n                node-version: 'lts/*'\n            - name: Setup pnpm\n              uses: pnpm/action-setup@v4\n              with:\n                version: 10\n            - name: Setup Python\n              uses: actions/setup-python@v6\n              with:\n                python-version: '3.13'\n            - name: Install dependencies\n              run: pnpm i\n            - name: Install BareMux v1\n              run: pnpm i @mercuryworkshop/bare-mux@^1.1.4\n            - name: Download upk-tools.zip\n              run: curl -L -o upk-tools.zip https://cdn.terbiumon.top/upk-tools.zip\n            - name: Extract upk-tools\n              run: unzip -o upk-tools.zip\n            - name: replace BCC Client v2 with v1\n              run: mv bx1bcc.ts src/sys/liquor/bcc.ts\n            - name: Replace BareMux in codebase\n              run: bash replace.sh\n            - name: Build TB React\n              run: pnpm run build-static\n            - name: \"Run UPK Builder\"\n              run: python3 upk.py\n            - name: Upload to latest release\n              uses: actions/github-script@v8\n              with:\n                github-token: ${{ secrets.GITHUB_TOKEN }}\n                script: |\n                  const fs = require('fs');\n                  const latestRelease = await github.rest.repos.getLatestRelease({\n                    owner: context.repo.owner,\n                    repo: context.repo.repo\n                  });\n                  await github.rest.repos.uploadReleaseAsset({\n                    owner: context.repo.owner,\n                    repo: context.repo.repo,\n                    release_id: latestRelease.data.id,\n                    name: 'terbium-upk.app.zip',\n                    data: fs.readFileSync('terbium-upk.app.zip')\n                  });"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\nvite.config.ts.timestamp-*.mjs\n\nnode_modules\ndist\ndist-ssr\n*.local\n*.tsbuildinfo\n.npmrc\npackage-lock.json\n\n# Editor directories and files\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n.env\n\n# Other\nsrc/apps.json\nsrc/hash.json\nsrc/installer.json\n"
  },
  {
    "path": ".node_version",
    "content": "22.19.0\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n    \"recommendations\": [\"biomejs.biome\", \"prosser.json-schema-2020-validation\", \"andersonbruceb.json-in-html\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n\t\"editor.formatOnSave\": false\n}\n"
  },
  {
    "path": ".zed/settings.json",
    "content": "{\n\t\"format_on_save\": \"on\",\n\t\"disable_ai\": true,\n\t\"languages\": {\n\t\t\"HTML\": {\n\t\t\t\"formatter\": {\n\t\t\t\t\"language_server\": {\n\t\t\t\t\t\"name\": \"biome\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"JavaScript\": {\n\t\t\t\"formatter\": { \"language_server\": { \"name\": \"biome\" } },\n\t\t\t\"code_actions_on_format\": {\n\t\t\t\t\"source.fixAll.biome\": true,\n\t\t\t\t\"source.organizeImports.biome\": true\n\t\t\t}\n\t\t},\n\t\t\"TypeScript\": {\n\t\t\t\"formatter\": { \"language_server\": { \"name\": \"biome\" } },\n\t\t\t\"code_actions_on_format\": {\n\t\t\t\t\"source.fixAll.biome\": true,\n\t\t\t\t\"source.organizeImports.biome\": true\n\t\t\t}\n\t\t},\n\t\t\"TSX\": {\n\t\t\t\"formatter\": { \"language_server\": { \"name\": \"biome\" } },\n\t\t\t\"code_actions_on_format\": {\n\t\t\t\t\"source.fixAll.biome\": true,\n\t\t\t\t\"source.organizeImports.biome\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"lsp\": {\n\t\t\"biome\": {\n\t\t\t\"settings\": {\n\t\t\t\t\"require_config_file\": true\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) 2026 TerbiumOS\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>."
  },
  {
    "path": "README.md",
    "content": "<center>\n    <img src=\"card.png\" style=\"display: block; margin-left: auto; margin-right: auto; width: 300px;\"></img>\n    <h1 style=\"color: #32ae62;\">Terbium v2</h1>\n</center>\n\n## <span style=\"color: #32ae62;\">Some of the technologies used</span>\n\n- [Vite](https://vite.dev)\n- [React](https://react.dev)\n- [TailwindCSS](https://tailwindcss.com)\n- [TFS](https://github.com/terbiumos/tfs)\n- [Fflate](https://github.com/101arrowz/fflate/)\n- [BareMux](https://github.com/mercuryworkshop/bare-mux)\n\n## <span style=\"color: #32ae62;\">Features</span>\n\n- All new UI\n- A Dynamic Shell\n- Better Window Manager\n- A desktop\n- App Store\n- A brand new terminal\n- Anura Compatability layer (Liquor)\n- Electron Compatability layer (Lemonade)\n- And lots more!\n\n## <span style=\"color: #32ae62;\">Setup</span>\n\n> <span style=\"font-family: url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">NOTE:</span> Terbium **WILL NOT** build on versions of Node older than version 20.\n\nTo get started it's pretty easy, you need either npm, or pnpm, which can be installed by running: `npm i -g pnpm` and then you just need to the run following command:\n\n```bash\npnpm i && pnpm start # Replace pnpm with npm if your not going to use pnpm\n```\n\nand visit [http://localhost:3000](http://localhost:3000) you should be good to go!\n\nIf you are developing/modifying terbium you can just run `pnpm run dev`, **DO NOT** use the development server for Production use. Instead, just run `pnpm start`. For any further backend configuration visit the `.env` file to configure the backend a bit.\n\n> <span style=\"font-family: none; color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">Warning</span><br>\n> If you are going to static host Terbium, you will need to change the wisp server, and you would need to follow those steps. Refer to this [document](./docs/static-hosting.md) for more information.\n\n### <span style=\"color: #32ae62;\">Documentation</span>\n\nIf you wish to develop or just learn more about Terbium's components and stuff, feel free to read our [Documentation](/docs/README.md)\n\nIf you're looking to see what Anura APIs and features are supported in terbium, refer to: [here](/docs/anura-compat.md), if you're looking to see what Electron API's are supported in terbium refer to: [here](/docs/lemonade-compat.md)\n\n### <span style=\"color: #32ae62;\">Contributors</span>\n\n- [SNOOT](https://github.com/NovaAppsInc)\n- [XSTARS](https://github.com/Notplayingallday383)\n- [illusionTBA](https://github.com/illusionTBA)\n- [Rafflesia](https://github.com/ProgrammerIn-wonderland)\n- [Riftriot](https://github.com/Riftriot)\n- [ironswordX](https://github.com/ironswordX)\n- [Ryan](https://github.com/MovByte)\n\nLicensed under the [**AGPL3 License**](https://www.gnu.org/licenses/agpl-3.0.en.html)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Terbium Versions\n\n| Version | Supported |\n| ------- | --------- |\n| 2.0.0-beta | ❌ |\n| 2.0.0-beta2 | ❌ |\n| 2.0.0-beta3 | ❌ |\n| 2.1.x | ❌ |\n| 2.2.x | ✅ |\n| 2.3.x | ✅ |\n\nIf your version of terbium is unsupported, please do not make a GitHub Issue about it. Please update to a newer version if your running a unsupported version.\n\n### Supported Liquor Versions\n\n| Version | Supported |\n| ------- | --------- |\n| 2.1.0 (stable) | ❌ |\n| 2.1.1 (stable) | ✅ |\n\n### Supported Lemonade Versions\n\n| Version | Supported |\n| ------- | --------- |\n| 1.0.0 (stable) | ❌ |\n| 1.1.0 (stable) | ✅ |\n\n## Reporting a Vulnerability\n\nIn the case that you somehow manage to find a vulnerability in Terbium please contact security@terbiumon.top\n\nREMEMBER: Please DO NOT report vulnerabilities in the repository Issues tab.\n\n## What You Should Report\n\nIf you are wondering what counts as a vulnerability, heres a good list:\n\n- XSS in the URL your using\n- The ability to execute malicious code on the server hosting Terbium\n- Any kind of leak of data/information in the code\n"
  },
  {
    "path": "biome.json",
    "content": "{\n\t\"$schema\": \"./node_modules/@biomejs/biome/configuration_schema.json\",\n\t\"vcs\": {\n\t\t\"enabled\": false,\n\t\t\"clientKind\": \"git\",\n\t\t\"useIgnoreFile\": false\n\t},\n\t\"files\": {\n\t\t\"includes\": [\n\t\t\t\"**\",\n\t\t\t\"!**/node_modules/**\",\n\t\t\t\"!**/dist/**\",\n\t\t\t\"!**/public/assets/**\",\n\t\t\t\"!**/public/apps/*.lib/**\",\n\t\t\t\"!**/public/apps/nfsadapter/**\",\n\t\t\t\"!**/public/lib/**\",\n\t\t\t\"!**/.vscode/**\",\n\t\t\t\"!**/.zed/**\",\n\t\t\t\"!**/docs/**\",\n\t\t\t\"!**/public/apps/*.app/**\",\n\t\t\t\"!**/public/apps/files.tapp/webdav.js\",\n\t\t\t\"!**/public/apps/terminal.tapp/ssh-util.js\"\n\t\t]\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"indentStyle\": \"tab\",\n\t\t\"indentWidth\": 4,\n\t\t\"lineWidth\": 320\n\t},\n\t\"css\": {\n\t\t\"parser\": {\n\t\t\t\"tailwindDirectives\": true\n\t\t}\n\t},\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommended\": true,\n\t\t\t\"style\": {\n\t\t\t\t\"noParameterAssign\": \"error\",\n\t\t\t\t\"useAsConstAssertion\": \"error\",\n\t\t\t\t\"useDefaultParameterLast\": \"error\",\n\t\t\t\t\"useEnumInitializers\": \"error\",\n\t\t\t\t\"useSelfClosingElements\": \"error\",\n\t\t\t\t\"useSingleVarDeclarator\": \"error\",\n\t\t\t\t\"noUnusedTemplateLiteral\": \"error\",\n\t\t\t\t\"useNumberNamespace\": \"error\",\n\t\t\t\t\"noInferrableTypes\": \"error\",\n\t\t\t\t\"noUselessElse\": \"error\"\n\t\t\t}\n\t\t}\n\t},\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"quoteStyle\": \"double\",\n\t\t\t\"arrowParentheses\": \"asNeeded\",\n\t\t\t\"lineWidth\": 320\n\t\t}\n\t},\n\t\"assist\": {\n\t\t\"enabled\": true,\n\t\t\"actions\": {\n\t\t\t\"source\": {\n\t\t\t\t\"organizeImports\": \"on\"\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bootstrap.ts",
    "content": "import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport consola from \"consola\";\nimport { TServer } from \"./server\";\nimport { version } from \"./package.json\";\nimport open from \"open\";\nimport { exec } from \"child_process\";\nimport AdmZip from \"adm-zip\";\n\nconsola.info(\"Bootstrapping TerbiumOS [v\" + version + \"]\");\n\nexport default async function Bootstrap() {\n\tconst args = process.argv;\n\tconst nodever = fs.readFileSync(\".node_version\", \"utf-8\").trim();\n\tif (process.version < nodever) {\n\t\tconsola.warn(\"Your version of Node.JS is not supported. Please update node to use Terbium. (Current version: \" + process.version + \", Required version: \" + nodever + \" or higher)\");\n\t}\n\tawait BuildApps();\n\tawait CreateAppsPaths();\n\tif (!fs.existsSync(\".env\")) await CreateEnv();\n\tawait Updater();\n\tconsola.success(\"TerbiumOS bootstrapped successfully\");\n\tif (!(args.includes(\"--apps-only\") || args.includes(\"--dev\"))) {\n\t\tTServer();\n\t}\n}\n\nexport async function BuildApps() {\n\tconsola.start(\"Building apps...\");\n\tconst __dirname = path.dirname(fileURLToPath(import.meta.url)),\n\t\tbaseDir = path.join(__dirname, \"./public/apps\"),\n\t\toutputDir = path.join(__dirname, \"./src\"),\n\t\toutputJsonPath = path.join(outputDir, \"apps.json\"),\n\t\tresult: { name: string; config: any }[] = [];\n\tfunction scanDirectory(dir: string) {\n\t\tfs.readdirSync(dir, { withFileTypes: true }).forEach(i => {\n\t\t\tif (i.isDirectory()) {\n\t\t\t\tconst indexFilePath = path.join(dir, i.name, \"index.json\");\n\t\t\t\tif (fs.existsSync(indexFilePath)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(fs.readFileSync(indexFilePath, \"utf-8\"));\n\t\t\t\t\t\tif (data.name && data.config) {\n\t\t\t\t\t\t\tif (data.name !== \"Browser\") {\n\t\t\t\t\t\t\t\tdata.config.src = data.config.src.replace(`/apps/${data.name.toLowerCase()}.tapp/`, `/fs/apps/system/${data.name.toLowerCase()}.tapp/`);\n\t\t\t\t\t\t\t\tdata.config.icon = data.config.icon.replace(`/apps/${data.name.toLowerCase()}.tapp/`, `/fs/apps/system/${data.name.toLowerCase()}.tapp/`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult.push({ name: data.name, config: data.config });\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (t) {\n\t\t\t\t\t\tconsola.error(`Error parsing ${indexFilePath}:`, t instanceof Error ? t.message : String(t));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (i.name.endsWith(\".tapp.zip\")) {\n\t\t\t\tconst zipPath = path.join(dir, i.name);\n\t\t\t\ttry {\n\t\t\t\t\tconst zip = new AdmZip(zipPath);\n\t\t\t\t\tconst configEntry = zip.getEntry(\".tbconfig\");\n\t\t\t\t\tif (configEntry) {\n\t\t\t\t\t\tconst configData = JSON.parse(configEntry.getData().toString(\"utf-8\"));\n\t\t\t\t\t\tif (configData.title && configData.wmArgs) {\n\t\t\t\t\t\t\tresult.push({ name: configData.title, config: configData.wmArgs });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (t) {\n\t\t\t\t\tconsola.error(`Error reading ${zipPath}:`, t instanceof Error ? t.message : String(t));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\tscanDirectory(baseDir),\n\t\tfs.existsSync(path.join(__dirname, \"./build\")) || fs.mkdirSync(path.join(__dirname, \"./build\")),\n\t\tfs.existsSync(outputJsonPath) || fs.writeFileSync(outputJsonPath, \"[]\", \"utf-8\"),\n\t\tfs.writeFileSync(outputJsonPath, JSON.stringify(result, null, 2), \"utf-8\"),\n\t\tconsola.success(`Aggregated JSON saved to ${outputJsonPath}`);\n\texec(\"git rev-parse HEAD\", (error, stdout, stderr) => {\n\t\tif (error || stderr) {\n\t\t\tconsola.error(\"Failed to get git commit hash\");\n\t\t\tfs.writeFileSync(path.join(__dirname, \"./src/hash.json\"), JSON.stringify({ hash: \"2b14b5\", repository: \"terbiumos/web-v2\" }, null, 2), \"utf-8\");\n\t\t} else {\n\t\t\tconst hash = stdout.trim();\n\t\t\texec(\"git remote get-url origin\", (remoteError, remoteStdout, remoteStderr) => {\n\t\t\t\tconst repoUrl = remoteStdout.trim();\n\t\t\t\tconst data = { hash, repository: repoUrl.replace(\"https://github.com/\", \"\") };\n\t\t\t\tif (remoteError || remoteStderr) {\n\t\t\t\t\tconsola.error(\"Failed to get repository URL\");\n\t\t\t\t\tfs.writeFileSync(path.join(__dirname, \"./src/hash.json\"), JSON.stringify({ hash: null, repository: null }, null, 2), \"utf-8\");\n\t\t\t\t} else {\n\t\t\t\t\tfs.writeFileSync(path.join(__dirname, \"./src/hash.json\"), JSON.stringify(data, null, 2), \"utf-8\");\n\t\t\t\t\tconsola.success(`Git hash and repo saved to ${path.join(__dirname, \"./src/hash.json\")}`);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t});\n\treturn true;\n}\n\nexport async function CreateAppsPaths() {\n\tinterface Apps {\n\t\t[appName: string]: (string | { [path: string]: string[] })[];\n\t}\n\tconsola.start(\"Creating apps paths...\");\n\n\tconst __dirname = path.dirname(fileURLToPath(import.meta.url)),\n\t\tbaseDir = path.join(__dirname, \"./public/apps\"),\n\t\toutputDir = path.join(__dirname, \"./src\"),\n\t\toutputJsonPath = path.join(outputDir, \"installer.json\"),\n\t\toutput: string[] = [];\n\n\tfunction collectPaths(dir: string, base: string = dir): void {\n\t\tconst files: fs.Dirent[] = fs.readdirSync(dir, { withFileTypes: true });\n\t\tfiles.forEach((file: fs.Dirent) => {\n\t\t\tconst fullPath = path.join(dir, file.name);\n\t\t\tconst relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n\t\t\tif (file.isDirectory()) {\n\t\t\t\toutput.push(relativePath + \"/\");\n\t\t\t\tcollectPaths(fullPath, base);\n\t\t\t} else {\n\t\t\t\toutput.push(relativePath);\n\t\t\t}\n\t\t});\n\t}\n\n\tconst accmp: string[] = [];\n\tfs.readdirSync(baseDir, { withFileTypes: true }).forEach(app => {\n\t\tif (app.isDirectory() && app.name.toLocaleLowerCase().endsWith(\".tapp\")) {\n\t\t\tconst appPath = path.join(baseDir, app.name);\n\t\t\tif (app.name.toLowerCase() === \"settings.tapp\") {\n\t\t\t\tcollectPaths(appPath);\n\t\t\t\tif (fs.existsSync(path.join(appPath, \"accounts\"))) {\n\t\t\t\t\tcollectPaths(path.join(appPath, \"accounts\"));\n\t\t\t\t\taccmp.push(...output.splice(output.indexOf(\"settings.tapp/accounts/\")));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcollectPaths(appPath);\n\t\t\t}\n\t\t} else if (app.name.toLowerCase().endsWith(\".tapp.zip\")) {\n\t\t\taccmp.push(app.name);\n\t\t}\n\t});\n\n\toutput.push(...accmp);\n\tfs.writeFileSync(outputJsonPath, JSON.stringify(output, null, 2), \"utf-8\");\n\tconsola.success(`Installer JSON saved to ${outputJsonPath}`);\n\treturn true;\n}\n\nexport async function CreateEnv() {\n\tconst port =\n\t\t(await consola.prompt(\"Enter a port for the server to run on (3000): \", {\n\t\t\ttype: \"text\",\n\t\t\tdefault: \"3000\",\n\t\t\tplaceholder: \"3000\",\n\t\t\tcancel: \"default\",\n\t\t})) || 3000;\n\tconst masqr = await consola.prompt(\"Enable Masqr? (no): \", {\n\t\ttype: \"text\",\n\t\tdefault: \"false\",\n\t\tplaceholder: \"no\",\n\t\tcancel: \"default\",\n\t});\n\tif (masqr === \"no\" || masqr === \"false\" || masqr === \"n\") {\n\t\tfs.writeFileSync(\".env\", `MASQR=${false}\\nPORT=${port}`);\n\t} else {\n\t\tconst licenseServer = (await consola.prompt(\"Enter the masqr license server URL: \")) || \"\";\n\t\tconst whitelist = (await consola.prompt(\"Enter a comma separated array of domains to whitelist (Ex: ['https://balls.com', 'https://tomp.app']): \")) || [];\n\t\tfs.writeFileSync(\".env\", `MASQR=${true}\\nPORT=${port}\\nLICENSE_SERVER_URL=${licenseServer}\\nWHITELISTED_DOMAINS=${whitelist}\\n`);\n\t}\n\tconsola.success(\"Environment file created\");\n\treturn true;\n}\n\nexport async function Updater() {\n\tconsola.start(\"Checking for updates...\");\n\texec(\"git remote get-url origin\", async (remoteError, remoteStdout, remoteStderr) => {\n\t\tif (remoteError || remoteStderr) {\n\t\t\tconsola.error(\"Failed to get local repository URL\");\n\t\t\treturn;\n\t\t}\n\t\tconst repo = `https://raw.githubusercontent.com/${remoteStdout.trim().replace(\"https://github.com/\", \"\").replace(\".git\", \"\")}/refs/heads/main/package.json` || \"https://raw.githubusercontent.com/TerbiumOS/web-v2/refs/heads/main/package.json\";\n\t\ttry {\n\t\t\tconst response = await fetch(repo);\n\t\t\tconst ver = (await response.json()).version;\n\t\t\tif (ver > version) {\n\t\t\t\tconst res = await consola.prompt(`A new version of Terbium is available. Would you like to download it? (New Version: ${ver}, Current: ${version})`, {\n\t\t\t\t\ttype: \"confirm\",\n\t\t\t\t});\n\t\t\t\tif (res) {\n\t\t\t\t\tconsola.info(\"Downloading new version...\");\n\t\t\t\t\texec(\"git pull\", async (remoteError, remoteStdout, remoteStderr) => {\n\t\t\t\t\t\tif (remoteError || remoteStderr) {\n\t\t\t\t\t\t\tconsola.error(\"Failed to update Terbium, Please update manually\");\n\t\t\t\t\t\t\topen(`${remoteStdout.trim()}/releases/latest`);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconsola.success(\"Terbium updated successfully\");\n\t\t\t\t\t\tawait BuildApps();\n\t\t\t\t\t\tawait CreateAppsPaths();\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t} else return;\n\t\t\t} else {\n\t\t\t\tconsola.success(\"Terbium is up to date\");\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsola.error(`Failed to check for updates, ${e}`);\n\t\t}\n\t});\n\treturn true;\n}\n\nBootstrap();\n"
  },
  {
    "path": "docs/README.md",
    "content": "# <span style=\"color: #32ae62;\">Table of Contents</span>\n\nWelcome to Terbium v2's Documentation. Here is a simple table of contents to help you get where you need to get\n\n- [How to Contribute to Terbium v2](./contributions.md)\n- [Creating Terminal Commands](./creating-terminal-commands.md)\n- [Creating Apps](./creating-apps.md)\n- [Backend Configuration Options](./backend-configuration.md)\n- [Anura Compatability & API Support](./anura-compat.md)\n- [Electron Compatability & API Support](./lemonade-compat.md)\n- [Introduction to Lemonade](./lemonade.md)\n- [Terbium API Documentation](./apis/readme.md)\n- [Static Hosting](./static-hosting.md)\n- [UPK Builds](./upk-build.md)\n\nIf you cannot find what your looking for feel free to ask in the Terbium Discord or in the TN Discord\n"
  },
  {
    "path": "docs/anura-compat.md",
    "content": "# <span style=\"color: #32ae62;\">Liquor Compatability</span>\n\nLiquor is our Compatability layer for Anura. We named it liquor because its similar to how wine works.\n\nIf your wondering what version of anura liquor is based off of, its based of Anura v2.1 \"Starboy\", as of now Liquor mostly targets almost all of Anura v2.1's APIs\n\n## <span style=\"color: #32ae62;\">API Support</span>\n\nBelow we have a small chart with a list of the current supported apis in Liquor\n\n| API | Support | Notes |\n| :--: | :---: | :---: |\n| anura.fs | Full | - |\n| anura.registerExternalApp | Full | - |\n| anura.registerExternalLib | Full | - |\n| anura.notifacation | Full | - |\n| anura.x86 | **NO** | Not Implemented |\n| anura.filePicker | Full | - |\n| anura.net | Full | - |\n| anura.URIHandler | Partial | Working but not perfect |\n| anura.install | Full | - |\n| anura.wm | Full | - |\n| anura.config | Full | - |\n| anura.files | Full | - |\n| anura.dialog | Full | - |\n| anura.localfs | Full | - |\n| anura.platform | Full | - |\n| anura.process | Partial | Stubbed to work, Not Fully implemented |\n| anura.ui | Partial | Stubbed to work, Not fully implemented |\n| anura.filesystem | Full | - |\n| anura.libs | Full | - |\n| anura.version | Full | - |\n| anura.systray | Partial | - |\n| anura.python | **NO** | Not Implemented (Deprecated) |\n\n**⚠️ NOTE** Anura Plugins are **NOT** supported on liquor due to service worker infrastructure differences\n\nLiquor also bundles a few things that will be enabled. They are listed below\n\n| App Name | Version | Last Updated |\n| :--: | :---: | :---: |\n| fsapp.app | 2.0-tb | 3/21/2025 |\n| libfilepicker.lib | 2.0-tb | 11/14/2024 |\n| libfileview.lib | 2.0-tb | 11/14/2024 |\n| libpersist.lib | 2.0 | 8/17/2024 |\n| anura.bcc | BX2-tb/BX1 (UPK only) | 11/25/2024 |\n"
  },
  {
    "path": "docs/apis/readme.md",
    "content": "# <span style=\"color: #32ae62;\">API Docs</span>\n\n**Last Updated**: v2.3.0 - 03/31/2026\n\nSo you're looking to use Terbium APIs. Well, you're in the right place! Terbium has a decent amount of components which I will break down below. The pages will include a description of the functions and code examples.\n\n## Table of Contents\n  - [Battery](#battery)\n  - [Launcher](#launcher)\n  - [Theme](#theme)\n  - [Desktop](#desktop)\n  - [Window](#window)\n  - [Context Menu](#contextmenu)\n  - [User](#user)\n  - [Proxy](#proxy)\n  - [Notification](#notification)\n  - [Dialog](#dialog)\n  - [Node](#node)\n  - [Platform](#platform)\n  - [Process](#process)\n  - [Screen](#screen)\n  - [VFS](#vfs)\n  - [System](#system)\n  - [Terbium Cloud (tauth)](#terbium-cloud-tauth)\n  - [Mediaplayer](#mediaplayer)\n  - [File](#file)\n  - [Additional Libraries](#additional-libraries)\n\n### Battery\n  - **showPercentage**\n    - Description: Shows the battery percentage in the system tray.\n    - Returns: `Promise<string>` - Returns \"Success\" if successful.\n    - Example:\n      ```javascript\n      await tb.battery.showPercentage();\n      console.log(\"Battery percentage is now visible\");\n      ```\n\n  - **hidePercentage**\n    - Description: Hides the battery percentage in the system tray.\n    - Returns: `Promise<string>` - Returns \"Success\" if successful.\n    - Example:\n      ```javascript\n      await tb.battery.hidePercentage();\n      console.log(\"Battery percentage is now hidden\");\n      ```\n\n  - **canUse**\n    - Description: Checks if the Battery Manager API is available on the current browser.\n    - Returns: `Promise<boolean>` - `true` if available, `false` otherwise.\n    - Example:\n      ```javascript\n      const canUseBattery = await tb.battery.canUse();\n      if (canUseBattery) {\n        console.log(\"Battery API is supported\");\n      }\n      ```\n\n### Launcher\n  - **addApp**\n    - Description: Adds an app to the app launcher.\n    - Parameters:\n      - `props: { name: string, icon: string, src: string, etc }`\n    - Returns: `Promise<boolean>`\n    - Example:\n      ```javascript\n      const wasadded = await tb.launcher.addApp({\n        name: \"Example App\",\n        icon: \"/home/icon.png\",\n      });\n      console.log(wasadded)\n      ```\n\n  - **removeApp**\n    - Description: Removes an app from the app launcher.\n    - Parameters:\n      - `name: string` - The app name to remove.\n    - Returns: `Promise<boolean>`\n    - Example:\n      ```js\n      const removed = await tb.launcher.removeApp(\"exampleapp\");\n      if (removed) {\n        console.log(\"App removed successfully\");\n      } else {\n        console.log(\"App not found\");\n      }\n      ```\n\n### Theme [⚠ Deprecated]\n  > <span style=\"font-family: url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">NOTE:</span> The Theme API is deprecated and remains as a stub for legacy applications\n  - **get**\n    - Description: Gets the current theme settings.\n    - Returns: `Promise<string>` - Theme value.\n    - Example:\n      ```javascript\n      const themeSettings = await tb.theme.get();\n      console.log(\"Current Theme Settings:\", themeSettings);\n      ```\n\n  - **set**\n    - Description: Sets the theme settings.\n    - Parameters:\n      - `data: string` - New theme value.\n    - Returns: `Promise<boolean>` - `true` if successful.\n    - Example:\n      ```javascript\n      await tb.theme.set(\"#ffffff\");\n      console.log(\"Theme set successfully\");\n      ```\n\n### Desktop\n  - **preferences**\n    - **setTheme**\n      - Description: Sets the theme color.\n      - Parameters:\n        - `color: string` - The new theme color.\n      - Example:\n        ```javascript\n        await tb.desktop.preferences.setTheme(\"#ff0000\");\n        console.log(\"Theme color set successfully\");\n        ```\n\n    - **theme**\n      - Description: Retrieves the current theme color.\n      - Returns: `Promise<string>` - The current theme color.\n      - Example:\n      ```javascript\n      const currentTheme = await tb.desktop.preferences.theme();\n      console.log(\"Current theme color:\", currentTheme);\n      ```\n\n    - **setAccent**\n      - Description: Sets the accent color.\n      - Parameters:\n        - `color: string` - The new accent color.\n      - Example:\n        ```javascript\n        await tb.desktop.preferences.setAccent(\"#00ff00\");\n        console.log(\"Accent color set successfully\");\n        ```\n\n    - **getAccent**\n      - Description: Gets the accent color.\n      - Example:\n        ```javascript\n        await tb.desktop.preferences.getAccent();\n        ```\n\n  - **wallpaper**\n    - **set**\n      - Description: Sets the wallpaper path.\n      - Parameters:\n        - `path: string` - The file path of the wallpaper image.\n      - Example:\n        ```javascript\n        await tb.desktop.wallpaper.set(\"/path/to/wallpaper.jpg\");\n        console.log(\"Wallpaper set successfully\");\n        ```\n\n    - **contain**\n      - Description: Sets the wallpaper mode to \"contain\".\n      - Example:\n        ```javascript\n        await tb.desktop.wallpaper.contain();\n        console.log(\"Wallpaper mode set to contain\");\n        ```\n\n    - **stretch**\n      - Description: Sets the wallpaper mode to \"stretch\".\n      - Example:\n        ```javascript\n        await tb.desktop.wallpaper.stretch();\n        console.log(\"Wallpaper mode set to stretch\");\n        ```\n\n    - **cover**\n      - Description: Sets the wallpaper mode to \"cover\".\n      - Example:\n        ```javascript\n        await tb.desktop.wallpaper.cover();\n        console.log(\"Wallpaper mode set to cover\");\n        ```\n\n    - **fillMode**\n      - Description: Retrieves the current wallpaper mode.\n      - Returns: `Promise<string>` - The current wallpaper mode.\n      - Example:\n        ```javascript\n        const currentMode = await tb.desktop.wallpaper.fillMode();\n        console.log(\"Current wallpaper mode:\", currentMode);\n        ```\n\n  - **dock**\n    - **pin**\n      - Description: Pins a new application to the dock.\n      - Parameters:\n        - `app: any` - The application to pin.\n      - Returns: `Promise<string>` - Returns 'Success' if the app was pinned successfully.\n      - Example:\n        ```javascript\n        await tb.desktop.dock.pin({ title: \"MyApp\", path: \"/path/to/myapp\" });\n        console.log(\"Application pinned successfully\");\n        ```\n\n    - **unpin**\n      - Description: Unpins an application from the dock.\n      - Parameters:\n       - `app: string` - The title of the application to unpin.\n      - Returns: `Promise<string>` - Returns 'Success' if the app was unpinned successfully.\n      - Example:\n        ```javascript\n        await tb.desktop.dock.unpin(\"MyApp\");\n        console.log(\"Application unpinned successfully\");\n        ```\n\n### Window\n  - **create**\n    - Description: Creates a new window using window configuration.\n    - Parameters:\n      - `props: any` - Window configuration object.\n    - Example:\n      ```javascript\n      tb.window.create({\n        title: \"My Window\",\n        src: \"/fs/apps/system/about.tapp/index.html\"\n      });\n      ```\n\n  - **close**\n    - Description: closes the active window.\n    - Example:\n      ```javascript\n      tb.window.close()\n      ```\n  - **minimize**\n    - Description: minimizes the active window.\n    - Example:\n      ```javascript\n      tb.window.minimize()\n      ```\n  - **maximize**\n    - Description: maximize the active window.\n    - Example:\n      ```javascript\n      tb.window.maximize()\n      ```\n  - **reload**\n    - Description: refreshes the iframe (if present) in the active window.\n    - Example:\n      ```javascript\n      tb.window.reload()\n      ```\n  - **changeSrc**\n    - Description: Changes the src of the iframe (if present) in the active window.\n    - Example:\n      ```js\n      tb.window.changeSrc(\"/fs/apps/system/about.tapp/index.html\")\n      ```\n  - **getId**\n    - Description: Gets the ID of the currently active window.\n    - Returns: `number` - Window ID.\n    - Example:\n      ```javascript\n      const windowId = tb.window.getId();\n      console.log(\"Current Window ID:\", windowId);\n      ```\n  - **content**\n    - **get**\n      - Description: Gets the current HTML Content from inside the window\n      - Returns: `Promise<HTMLDivElement>` - The HTML Content inside the window.\n      - Example:\n      ```javascript\n      await tb.window.content.get()\n      ```\n    - **set**\n      - Description: Sets the current HTML Content from inside the window\n      - Example:\n      ```javascript\n      tb.window.content.set(`<div>hi (put any HTML Content here)</div>`)\n      ```\n  - **titlebar**\n    - **setColor**\n      - Description: Sets the fore-color of all the window's titlebars\n      - Example:\n      ```javascript\n      tb.window.titlebar.setColor('#fff')\n      ```\n    - **setText**\n      - Description: Sets the current window's title\n      - Example:\n      ```javascript\n      tb.window.titlebar.setText('TB Docs')\n      ```\n    - **setBackgroundColor**\n      - Description: Sets the background-color of all the window's titlebars\n      - Example:\n      ```javascript\n      tb.window.titlebar.setBackgroundColor('#000')\n      ```\n  - **island**\n    - **addControl**\n      - Description: Adds a control to the TB App Island\n      - Example:\n      ```javascript\n      tb.window.island.addControl({\n        text: \"<titleofcontrol>\",\n        appname: \"<appname>\",\n        id: \"<giverandomname>\",\n        click: () => {\n          // Execute code here for when clicked\n        }\n      })\n      ```\n    - **removeControl**\n      - Description: Removes a control from the TB App Island\n      - Parameters:\n        - `control_id: string` - The ID used when adding the control.\n      - Example:\n      ```javascript\n      tb.window.island.removeControl(\"<idfromthatyouusedforaddingit>\")\n      ```\n\n### ContextMenu\n  - **create**\n    - Description: Creates a Context Menu at your desired location\n    - Parameters:\n      - `props: { x: number, y: number, options: Array, titlebar?: boolean, iframe?: boolean }` - Context menu properties.\n    - Example:\n      ```javascript\n      tb.contextmenu.create({\n        x: 0,\n        y: 0,\n        options: [\n          { text: \"Option 1\", click: () => console.log(\"Option 1 clicked\") },\n          { text: \"Option 2\", click: () => console.log(\"Option 2 clicked\") },\n        ]\n      });\n      ```\n\n  - **close**\n    - Description: Closes the currently open context menu.\n    - Example:\n      ```javascript\n      tb.contextmenu.close();\n      ```\n\n### User\n  - **username**\n    - Description: Fetches the username of the current user.\n    - Returns: `Promise<string>` - User's username.\n    - Example:\n      ```javascript\n      const username = await tb.user.username();\n      console.log(\"username:\", username);\n      ```\n  - **pfp**\n    - Description: Fetches the profile picture of the current user.\n    - Returns: `Promise<string>` - URL/Base64 Encoding of the profile picture.\n    - Example:\n      ```javascript\n      const pfp = await tb.user.pfp();\n      console.log(\"PFP:\", pfp);\n      ```\n\n### Proxy\n  - **get**\n    - Description: Gets the current proxy settings.\n    - Returns: `Promise<string>` - Proxy settings.\n    - Example:\n      ```javascript\n      const proxySettings = await tb.proxy.get();\n      console.log(\"Using:\", proxySettings);\n      ```\n\n  - **set**\n    - Description: Selects the proxy.\n    - Parameters:\n      - `proxy: string` - New proxy settings.\n    - Returns: `Promise<boolean>` - `true` if successful.\n    - Example:\n      ```javascript\n      await tb.proxy.set(\"Ultraviolet\");\n      console.log(\"Proxy set successfully\");\n      ```\n\n  - **updateSWs**\n    - Description: Updates the Transport and Wisp Server of the proxy.\n    - Example:\n      ```javascript\n      await tb.proxy.updateSWs();\n      console.log(\"Service Workers updated successfully\");\n      ```\n\n  - **encode**\n    - Description: Encodes a URL in the desired format (Only avalible in XOR Currently)\n    - Parameters:\n      - `url: string` - The url to encode\n      - `encoder: string` - The encoder (Only avalible in XOR currently)\n    - Returns: `Promise<string>`\n    - Example: \n      ```javascript\n      await tb.proxy.encode('https://google.com', 'XOR')\n      ```\n  \n  - **decode**\n    - Description: Decodes a URL in the desired format (Only avalible in XOR Currently)\n    - Parameters:\n      - `url: string` - The url to decode\n      - `decoder: string` - The decoder (Only avalible in XOR currently)\n    - Returns: `Promise<string>`\n    - Example: \n      ```javascript\n      await tb.proxy.decode('https://google.com', 'XOR')\n      ```\n\n### Notification\n  - **Message [🧪Experimental]**\n    - Description: The notification that has an input field.\n    - Parameters:\n      - `props: { message: string, application: string, iconSrc: string, onOk?: Function, txt?: string, time?: number }` - Notification properties.\n    - Example:\n      ```javascript\n      tb.notification.Message({ \n        message: \"test\", \n        application: \"System\", \n        iconSrc: \"/assets/img/logo.png\", \n        txt: \"fieldtext\" \n      });\n      ```\n\n  - **Toast**\n    - Description: A simple notification\n    - Parameters:\n      - `props: { message: string, application: string, iconSrc: string, time?: number }` - Notification properties.\n    - Example:\n      ```javascript\n      tb.notification.Toast({ \n        message: \"test\", \n        application: \"System\", \n        iconSrc: \"/assets/img/logo.png\", \n        time: 10000 \n      });\n      ```\n\n  - **Installing**\n    - Description: An installing/progress style notification. If you pass a task Promise (or async function), the notification stays visible until the task finishes, then it automatically shows a completion toast (or failure toast if it throws).\n    - Parameters:\n      - `props: { message: string, application: string, iconSrc: string, time?: number }` - Installing notification properties.\n      - `task?: Promise<T> | (() => Promise<T>)` - Optional async task to track.\n      - `doneToast?: Partial<NotificationProps>` - Optional completion toast overrides.\n      - `failToast?: Partial<NotificationProps>` - Optional failure toast overrides.\n    - Returns: `Promise<T> | void` - Returns the task result when a task is passed.\n    - Example:\n      ```javascript\n      await tb.notification.Installing(\n        {\n          message: \"Extracting archive...\",\n          application: \"Files\",\n          iconSrc: \"/assets/img/logo.png\"\n        },\n        async () => await unzip(\"pathtoalargezipfolder.zip\", \"/home/user/documents\"),\n        { message: \"Archive extracted successfully\" },\n        { message: \"Archive extraction failed\" }\n      );\n      ```\n\n### Dialog\n  - **Alert**\n    - Description: The Alert dialog\n    - Parameters:\n      - `props: { title: string, message: string }` - Alert properties.\n    - Example:\n      ```javascript\n      tb.dialog.Alert({ title: \"Alert\", message: \"This is an alert message.\" });\n      ```\n\n  - **Message**\n    - Description: Displays a message dialog with specified properties.\n    - Parameters:\n      - `props: { title: string, defaultValue?: string, onOk?: Function, onCancel?: Function }` - Message dialog properties.\n    - Example:\n      ```javascript\n      await tb.dialog.Message({\n        title: \"Example Message\",\n        defaultValue: \"Default value\",\n        onOk: (value) => console.log(\"OK clicked with value:\", value),\n        onCancel: () => console.log(\"Cancel clicked\")\n      });\n      ```\n\n  - **Select**\n    - Description: Lets you select a value from a dropdown\n    - Parameters:\n      - `props: { title: string, message?: string, options: Array<{text: string, value: any}>, onOk?: Function, onCancel?: Function }` - Select dialog properties.\n    - Example:\n      ```javascript\n      await tb.dialog.Select({\n        title: \"Enter the permission level you wish to set\",\n        options: [{\n          text: \"Admin\",\n          value: \"admin\"\n        }, {\n          text: \"User\",\n          value: \"user\"\n        }, {\n          text: \"Group\",\n          value: \"group\"\n        }, {\n          text: \"Public\",\n          value: \"public\"\n        }],\n        onOk: async (perm) => {\n          console.log(perm);\n        }\n      });\n      ```\n\n  - **Auth**\n    - Description: TB Permissions Authentication Dialog\n    - Parameters:\n      - `props: { title: string, defaultUsername?: string, onOk?: Function, onCancel?: Function }` - Auth dialog properties.\n      - `options?: { sudo: boolean }` - Additional options to indicate if this is for sudo authentication.\n    - Example:\n      ```javascript\n      await tb.dialog.Auth({\n        title: \"Example Message\",\n        defaultUsername: \"Default value\",\n        onOk: (user, pass) => console.log(\"User and unhashed pass\", user, pass),\n        onCancel: () => console.log(\"Cancel clicked\")\n      }, { sudo: false });\n      ```\n      \n  - **Permissions**\n    - Description: Yes or No Dialog\n    - Parameters:\n      - `props: { title: string, message: string, onOk?: Function, onCancel?: Function }` - Permission dialog properties.\n    - Example:\n      ```javascript\n      await tb.dialog.Permissions({\n        title: \"Example Message\",\n        message: \"Do you want to continue?\",\n        onOk: () => console.log(\"OK clicked\"),\n        onCancel: () => console.log(\"Cancel clicked\")\n      });\n      ```\n\n  - **FileBrowser**\n    - Description: Simple FileBrowser Dialog\n    - Parameters:\n      - `props: { title: string, filter?: string, onOk?: Function, onCancel?: Function, local?: boolean }` - FileBrowser dialog properties.\n    - Example:\n      ```javascript\n      await tb.dialog.FileBrowser({\n        title: \"Select a file\",\n        filter: \".txt\",\n        onOk: (value) => console.log(\"File selected:\", value),\n      });\n      ```\n\n  - **DirectoryBrowser**\n    - Description: Simple Directory Browser Dialog\n    - Parameters:\n      - `props: { title: string, defualtDir?: string, onOk?: Function, onCancel?: Function, local?: boolean }` - DirectoryBrowser dialog properties.\n    - Example:\n      ```javascript\n      await tb.dialog.DirectoryBrowser({\n        title: \"Select a directory\",\n        defualtDir: \"/home/\",\n        onOk: (value) => console.log(\"Selected Dir:\", value),\n      });\n      ```\n\n  - **SaveFile**\n    - Description: Simple File Saving Dialog\n    - Parameters:\n      - `props: { title: string, defualtDir?: string, filename?: string, onOk?: Function, onCancel?: Function, local?: boolean }` - SaveFile dialog properties.\n    - Example:\n      ```javascript\n      await tb.dialog.SaveFile({\n        title: \"Example Title\",\n        defualtDir: \"/home/\",\n        filename: \"tbdocs.md\",\n        onOk: (value) => console.log(\"Saved file to:\", value)\n      });\n      ```\n\n  - **Cropper**\n    - Description: Image Cropper\n    - Parameters:\n      - `props: { title: string, img: string, onOk?: Function }` - Cropper dialog properties. **Image should be formatted in Base64**\n    - Returns: `Promise<string>` - Resolves image when the dialog is closed\n    - Example:\n      ```javascript\n      await tb.dialog.Cropper({\n        title: \"Example Title\",\n        img: \"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\",\n        onOk: (img) => console.log(\"new image\", img)\n      });\n      ```\n\n  - **WebAuth**\n    - Description: Simple Authentication Dialog (for use in Web Authentication)\n    - Parameters:\n      - `props: { title: string, message?: string, defaultUsername?: string, onOk?: Function, onCancel?: Function }` - Auth dialog properties.\n    > <span style=\"font-family: url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">NOTE:</span> Because by default the password is not hashed, please encrypt the password if you plan to store it using `tb.crypto`\n    - Example:\n      ```javascript\n      await tb.dialog.WebAuth({\n        title: \"Example Message\",\n        defaultUsername: \"Default value\",\n        onOk: (user, pass) => console.log(\"User and unhashed pass\", user, pass),\n        onCancel: () => console.log(\"Cancel clicked\")\n      });\n      ```\n\n### Node\n  - **webContainer**\n    - Description: The current webContainer instance for the Node Subsystem. Refer to [WebContainers API](https://webcontainers.io/api) for documentation.\n    - Returns: `WebContainer` instance\n  \n  - **servers**\n    - Description: A Map of ports running on the Node Subsystem\n    - Returns: `Map<number, string>` - Map of port numbers to server URLs\n\n  - **isReady**\n    - Description: Returns whether or not the WebContainer is booted.\n    - Returns: `boolean` - `true` if ready, `false` otherwise\n\n  - **start**\n    - Description: Boots the WebContainer\n    - Example:\n      ```javascript\n      tb.node.start();\n      console.log(\"WebContainer started\");\n      ```\n \n  - **stop**\n    - Description: Stops the WebContainer\n    - Returns: `boolean` - `true` if stopped successfully\n    - Example:\n      ```javascript\n      try {\n        const stopped = tb.node.stop();\n        console.log(\"WebContainer stopped\");\n      } catch (err) {\n        console.error(\"No WebContainer is running\");\n      }\n      ```\n\n### Platform\n  - **getPlatform**\n    - Description: Gets the current platform the user is using\n    - Returns: `Promise<string>` - Platform (\"mobile\" or \"desktop\")\n    - Example:\n      ```javascript\n      const platform = await tb.platform.getPlatform();\n      console.log(`You're on: ${platform}`);\n      ```\n\n### Process\n  - **kill**\n    - Description: Kill a process. The argument may be a PID (number or numeric string) or any object that resolves to a process when compared by PID.\n    - Parameters:\n      - `config: string | number | any` - The PID of the process to terminate (or an object containing a `pid` property).\n    - Example:\n      ```javascript\n      // simple kill by PID\n      tb.process.kill(69420);\n\n      // you can also look up a process then kill it\n      const procs = tb.process.list();\n      const first = Object.values(procs)[0];\n      tb.process.kill(first.pid);\n      ```\n\n  - **list**\n    - Description: Return the current process table.\n    - Returns: `Record<number, ProcInf>` - a map of PID to process information (see `ProcessInfo` type in `types.ts` for fields such as name, pid, parent, children, status, memory, cpu, etc.)\n    - Example:\n      ```javascript\n      const processes = tb.process.list();\n      console.log(processes);\n      ```\n\n  - **procs**\n    - Description: Public property exposing the live process record. It is equivalent to calling `tb.process.list()` but can be modified directly when spawning new entries.\n    - Example:\n      ```javascript\n      console.log(tb.process.procs); // same as tb.process.list()\n      ```\n\n  - **create**\n    - Description: Creates a new process entry. This is primarily used by the runtime when spawning windows or background tasks, but you can call it manually for testing.\n    - Parameters:\n      - `type: \"window\" | \"runtime\"` – the kind of process to create.\n      - `config: any` – configuration object describing the process (window size, title, etc.).\n    - Example:\n      ```javascript\n      tb.process.create(\"runtime\", { name: \"my-task\" });\n      ```\n\n  - **parse**\n    - **build [🧪Experimental]**\n      - Description: Building Process of Custom TML Formatted Apps\n      - Parameters:\n        - `src: string` - Source string to build\n      - Returns: `void`\n      - Example:\n        ```javascript\n        tb.process.parse.build(\"<tml>...</tml>\");\n        ```\n\n### Screen\n  - **captureScreen**\n    - Description: Creates a screenshot of your screen and saves it\n    - Returns: `Promise<void>`\n    > <span style=\"font-family: url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">NOTE:</span> The screen capture API is used with the alt+shift keybind. Be aware of that to prevent any conflictions with your application if you use a similar keybind.\n    - Example: \n      ```javascript\n      await tb.screen.captureScreen();\n      ```\n\n### VFS\n  - **servers**\n    - Description: A Map of the current users webdav servers\n    - Returns: `Object` - VFSOperations\n    - Example:\n    ```js\n    for (const instance of tb.vfs.servers) {\n      const davInfo = instance[1];\n      // Use dav instance info here including a already established connection if one is availible\n    }\n    ```\n  - **currentServer**\n    - Description: The current WebDav server to use for operations\n    - Returns: `Object` - VFSOperations\n    - Example:\n    ```js\n    const client = tb.vfs.currentServer.connection.client;\n    // use webdav methods here or use VFS Operations as a drop in for working between TFS and VFS\n    ```\n\n  - **create**\n    - Description: (async) Returns a new instance of VFS, You will probably not use this function unless your directly modifying terbiums codebase\n    - Returns: `Promise<VFS>`\n    - Example:\n    ```js\n    const vfs = await vfs.create();\n    ```\n\n  - **mount**\n    - Description: Mounts the inputed server from vfs.servers\n    - Parameters:\n      - `serverName: string` - the name of the server to mount\n    - Example:\n    ```js\n    await tb.vfs.mount(\"servername\");\n    ```\n\n  - **mountAll**\n    - Description: Mounts all servers avalible in vfs.servers\n    - Example:\n    ```js\n    await tb.vfs.mountAll()\n    ```\n\n  - **addServer**\n    - Description: Adds a server to the users WebDav server list\n    - Parameters:\n      - `Server: ServerInfo[]` - The server information to put in\n    - Example:\n    ```js\n    await tb.vfs.addServer({\n      name: \"any name you want for the drive name\";\n      url: \"https://somedavendpoint.com/\";\n      username: \"IloveTerbiumDev\";\n      password: \"XSTARSwasHere\";\n    })\n    ```\n\n  - **removeServer**\n    - Description: Removes a server from the users WebDav server list\n    - Parameters:\n      - `ServerName: string` - The name of the server to remove\n    - Example:\n    ```js\n    await tb.vfs.removeServer(\"webdav1\")\n    ```\n\n  - **setServer**\n    - Description: Sets `currentServer` to the requested server\n    - Parameters:\n      - `ServerName: string` - The server name to set the server too **NOTE** Server MUST be mounted to perform this operation.\n    - Example:\n    ```js\n    await tb.vfs.setServer(\"webdav1\");\n    // tb.vfs.currentServer is now the instance of VFSOperations that webdav1 uses\n    ```\n\n  - **whatFS**\n    - Description: Returns Either TFS or VFSOperations as the suitable File System for you to use for said drive\n    - Parameters:\n      - `Path: string` - The path to check\n    - Example:\n    ```js\n    const fs = await tb.vfs.whatFS(\"/mnt/dav\");\n    // FS is VFSOperations\n    const fs = await tb.vfs.whatFS(\"/home/XSTARS/\");\n    // FS is TFS.fs\n    ```\n\n  - **VFSOperations**\n    > **NOTE:** This is **NOT** an API. This is an instance representing File System actions, WebDav client information, etc., and is referenced by several APIs above.\n    #### Properties\n\n    - **client**: `WebDavClient`  \n      The WebDav Client Interface.\n\n    #### Methods\n\n    - **readdir(path, callback)**\n      - Reads the contents of a directory at the given path.\n      - **Parameters:**\n        - `path: string` — Directory path.\n        - `callback: (err: any, files?: any[]) => void` — Called with error or array of file names.\n\n    - **readFile(path, callback)**\n      - Reads the contents of a file as text.\n      - **Parameters:**\n        - `path: string` — File path.\n        - `callback: (err: any, data?: string) => void` — Called with error or file data.\n\n    - **writeFile(path, data, callback)**\n      - Writes data to a file, replacing its contents.\n      - **Parameters:**\n        - `path: string` — File path.\n        - `data: string | ArrayBuffer` — Data to write.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    - **delete(path, callback)**\n      - Deletes a file at the specified path.\n      - **Parameters:**\n        - `path: string` — File path.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    - **rename(oldPath, newPath, callback)**\n      - Renames or moves a file from `oldPath` to `newPath`.\n      - **Parameters:**\n        - `oldPath: string` — Original file path.\n        - `newPath: string` — New file path.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    - **createDirectory(path, callback)**\n      - Creates a new directory at the specified path.\n      - **Parameters:**\n        - `path: string` — Directory path.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    - **exists(path, callback)**\n      - Checks if a file or directory exists at the given path.\n      - **Parameters:**\n        - `path: string` — Path to check.\n        - `callback: (err: any, exists?: boolean) => void` — Called with error or existence boolean.\n\n    - **stat(path, callback)**\n      - Retrieves metadata/statistics about a file or directory.\n      - **Parameters:**\n        - `path: string` — Path to check.\n        - `callback: (err: any, stat?: any) => void` — Called with error or stat object.\n\n    - **copy(source, destination, callback)**\n      - Copies a file from source to destination.\n      - **Parameters:**\n        - `source: string` — Source file path.\n        - `destination: string` — Destination file path.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    - **unlink(path, callback)**\n      - Deletes a file at the specified path (alias for `delete`).\n      - **Parameters:**\n        - `path: string` — File path.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    - **move(source, destination, callback)**\n      - Moves a file from source to destination (alias for `rename`).\n      - **Parameters:**\n        - `source: string` — Source file path.\n        - `destination: string` — Destination file path.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    - **appendFile(path, data, callback)**\n      - Appends data to the end of a file.\n      - **Parameters:**\n        - `path: string` — File path.\n        - `data: string | ArrayBuffer` — Data to append.\n        - `callback: (err: any) => void` — Called with error if any.\n\n    All of these functions also have a Promises variant that has the exact same syntax except it does not have a callback instead you use it asynchronously\n\n\n### System\n  - **version**\n    - Description: Lists the version of Terbium\n    - Returns: `string` - Terbium version.\n    - Example:\n      ```javascript\n      const terbiumVersion = tb.system.version();\n      console.log(\"Terbium v:\", terbiumVersion);\n      ```\n\n  - **instance**\n    - **repo**\n      - Description: Lists the repository information\n      - Returns: `string` - Repository information.\n      - Example:\n        ```javascript\n        const repo = tb.system.instance.repo;\n        console.log(\"The repo is: \" + repo);\n        ```\n\n    - **hash**\n      - Description: Lists the git commit hash\n      - Returns: `string` - Git hash.\n      - Example:\n        ```javascript\n        const hash = tb.system.instance.hash;\n        console.log(\"The git hash is: \" + hash);\n        ```\n\n  - **openApp**\n    - Description: Opens an installed application\n    - Parameters:\n      - `pkg: string` - Package ID of the app.\n    - Example:\n      ```javascript\n      await tb.system.openApp(\"browser\");\n      ```\n\n  - **download**\n    - Description: Download a file from the internet to the File System\n    - Parameters:\n      - `url: string` - URL of the file to download.\n      - `location: string` - Destination path in the file system.\n    - Returns: `Promise<void>`\n    - Example: \n      ```javascript\n      await tb.system.download('https://example.com/example.txt', '/home/exampledownload.txt');\n      ```\n\n  - **exportfs**\n    - Description: Exports the file system as a zip file\n    - Parameters:\n      - `startPath?: string` - Starting path (default: \"/\")\n      - `filename?: string` - Output filename (default: \"tbfs.backup.zip\")\n    - Returns: `Promise<string>` - URL of the created zip file\n    - Example:\n      ```javascript\n      await tb.system.exportfs(\"/home/\", \"backup.zip\");\n      ```\n\n  - **users**\n    - **list**\n      - Description: Lists all users in the system\n      - Returns: `Promise<string[]>` - Array of usernames\n      - Example:\n        ```javascript\n        const users = await tb.system.users.list();\n        console.log(users);\n        ```\n\n    - **add**\n      - Description: Adds a user to the system\n      - Parameters:\n        - `user: { username: string, password: string, pfp: string, perm: string, securityQuestion?: { question: string, answer: string } }` - User information\n      - Returns: `Promise<boolean>` - `true` if successful\n      - Example:\n        ```javascript\n        await tb.system.users.add({ \n          username: 'XSTARS', \n          password: 'terbium1234', \n          pfp: 'data:image/png;base64,...', \n          perm: 'Admin' \n        });\n        ```\n\n    - **remove**\n      - Description: Removes a user from the system\n      - Parameters:\n        - `id: string` - Username to remove\n      - Returns: `Promise<boolean>` - `true` if successful\n      - Example:\n        ```javascript\n        await tb.system.users.remove('XSTARS');\n        ```\n\n    - **update**\n      - Description: Updates the data on a user\n      - Parameters:\n        - `user: { username: string, password?: string, pfp?: string, perm?: string, securityQuestion?: object }` - User information to update\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.users.update({ \n          username: 'XSTARS', \n          password: 'iloveterbium', \n          pfp: 'data:image/png;base64,...', \n          perm: 'Public' \n        });\n        ```\n\n    - **renameUser**\n      - Description: Renames a user in the system\n      - Parameters:\n        - `olduser: string` - Current username\n        - `newuser: string` - New username\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.users.renameUser('oldname', 'newname');\n        ```\n\n  - **startup**\n    - **addProc**\n      - Description: Adds a new process to the startup list. This will register a package or command to run on system or user startup.\n      - Parameters:\n        - `pkgorname: string` - The package name or unique identifier for the startup entry.\n        - `target: \"System\" | \"User\"` - Whether the startup entry is registered system-wide or for the current user.\n        - `cmd?: string` - Optional command to execute for the entry (if different from the package default).\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.startup.addProc('my-service', 'System', 'alert(\"alert evaled\")');\n        ```\n\n    - **removeProc**\n      - Description: Removes a previously registered startup process.\n      - Parameters:\n        - `pkgorname: string` - The package name or identifier of the entry to remove.\n        - `target: \"System\" | \"User\"` - The scope from which to remove the entry.\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.startup.removeProc('my-service', 'System');\n        ```\n\n    - **enable**\n      - Description: Enables a registered startup process so it will run at boot for the specified scope.\n      - Parameters:\n        - `pkgorname: string` - The package name or identifier of the entry to enable.\n        - `target: \"System\" | \"User\"` - The scope in which to enable the entry.\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.startup.enable('my-service', 'User');\n        ```\n\n    - **disable**\n      - Description: Disables a registered startup process so it will not run at boot.\n      - Parameters:\n        - `pkgorname: string` - The package name or identifier of the entry to disable.\n        - `target: \"System\" | \"User\"` - The scope in which to disable the entry.\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.startup.disable('my-service', 'User');\n        ```\n\n    - **list**\n      - Description: Lists all configured startup entries.\n      - Returns: `Promise<object[]>` - An array of startup entries (each entry contains details such as name, target, command, enabled state).\n      - Example:\n        ```javascript\n        const procs = await tb.system.startup.list();\n        console.log(procs);\n        ```\n\n  - **bootmenu**\n    - **addEntry**\n      - Description: Adds a boot entry into the Terbium Boot Menu\n      - Parameters:\n        - `name: string` - The name to display in the boot menu\n        - `file: string` - The file to boot from (file path)\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.bootmenu.addEntry('Legacy TB', '/legacy-tb/index.html');\n        ```\n\n    - **removeEntry**\n      - Description: Removes a boot entry from the Terbium Boot Menu\n      - Parameters:\n        - `name: string` - The name of the entry to remove\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.system.bootmenu.removeEntry('Legacy TB');\n        ```\n\n### Terbium Cloud (tauth)\n  - **client**\n    - Description: The authentication client instance for Terbium Cloud services\n    - Returns: `AuthClient` - Authentication client object\n\n  - **signIn**\n    - Description: Sign in to Terbium Cloud Account\n    - Returns: `Promise<any>` - Sign-in response with user data\n    - Example:\n      ```javascript\n      try {\n        const result = await tb.tauth.signIn();\n        console.log(\"Signed in:\", result.data.user);\n      } catch (err) {\n        console.error(\"Sign-in cancelled or failed:\", err);\n      }\n      ```\n\n  - **signOut**\n    - Description: Sign out from Terbium Cloud Account\n    - Returns: `Promise<void>`\n    - Example:\n      ```javascript\n      await tb.tauth.signOut();\n      console.log(\"Signed out successfully\");\n      ```\n\n  - **isTACC**\n    - Description: Checks if the current user (or specified user) is a Terbium Cloud Account\n    - Parameters:\n      - `username?: string` - Username to check (defaults to current user)\n    - Returns: `Promise<boolean>` - `true` if user has TACC, `false` otherwise\n    - Example:\n      ```javascript\n      const hasTACC = await tb.tauth.isTACC();\n      if (hasTACC) {\n        console.log(\"User has a Terbium Cloud Account\");\n      }\n      ```\n\n  - **updateInfo**\n    - Description: Updates Terbium Cloud Account information\n    - Parameters:\n      - `user: Partial<User>` - User information to update (can include username, pfp, email, password, etc.)\n    - Returns: `Promise<void>`\n    - Example:\n      ```javascript\n      await tb.tauth.updateInfo({ \n        username: \"newusername\", \n        pfp: \"data:image/png;base64,...\" \n      });\n      ```\n\n  - **reauth**\n    - Description: Logs back into Terbium Cloud\n    - Returns: `Promise<void>`\n    - Example:\n      ```javascript\n      await tb.tauth.reauth();\n      ```\n\n  - **getInfo**\n    - Description: Gets Terbium Cloud Account information\n    - Parameters:\n      - `username?: string` - Username to get info for (defaults to current user)\n    - Returns: `Promise<User | null>` - User account information or null if not found\n    - Example:\n      ```javascript\n      const info = await tb.tauth.getInfo();\n      if (info) {\n        console.log(\"Account info:\", info);\n      }\n      ```\n\n  - **sync**\n    - **retreive**\n      - Description: Retrieves synced data from Terbium Cloud (settings, WebDAV servers, etc.)\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.tauth.sync.retreive();\n        console.log(\"Settings synced from cloud\");\n        ```\n\n    - **upload**\n      - Description: Uploads local settings and data to Terbium Cloud\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.tauth.sync.upload();\n        console.log(\"Settings uploaded to cloud\");\n        ```\n\n    - **isSyncing**\n      - Description: Indicates whether a sync operation is currently in progress\n      - Returns: `boolean` - `true` if syncing, `false` otherwise\n      - Example:\n        ```javascript\n        if (tb.tauth.sync.isSyncing) {\n          console.log(\"Sync in progress...\");\n        }\n        ```\n\n### Mediaplayer\n  > <span style=\"font-family: url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">NOTE:</span> Make sure that the endtime for the music and video island is formatted in seconds and not milliseconds or minutes, that applies to the time parameter (start time) as well.\n  \n  - **music**\n    - Description: Activates the Music optimized Media Island\n    - Parameters:\n      - `props: { artist: string, track_name: string, album?: string, time?: number, background: string, endtime: number, onSeek?: void, onPausePlay: void, onNext?: void; onBack?: void }` - Music player properties.\n    - Example:\n      ```javascript\n      tb.mediaplayer.music({\n        track_name: \"Starboy\",\n        artist: \"The Weeknd\",\n        endtime: 231,\n        background: \"https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/02/17/ce/0217ce34-c2b9-3d3d-1dec-586db3948753/23UMGIM22526.rgb.jpg/1200x1200bf-60.jpg\"\n      });\n      ```\n\n  - **video**\n    - Description: Activates the Video optimized Media Island\n    - Parameters:\n      - `props: { creator: string, video_name: string, time?: number, background: string, endtime: number, onSeek?: void, onPausePlay: void, onNext?: void; onBack?: void }` - Video player properties.\n    - Example:\n      ```javascript\n      tb.mediaplayer.video({\n        video_name: \"The school smp one year later...\",\n        creator: \"Playingallday383\",\n        endtime: 1273,\n        background: \"https://i.ytimg.com/vi/kiKmSq4gxNU/hqdefault.jpg\"\n      });\n      ```\n\n  - **hide**\n    - Description: Hides the media island.\n    - Example:\n      ```javascript\n      tb.mediaplayer.hide();\n      ```\n\n  - **pauseplay**\n    - Description: Pauses or plays the content connected to the media island\n    - Example:\n      ```javascript\n      tb.mediaplayer.pauseplay();\n      ```\n\n  - **isExisting**\n    - Description: Tells you if the media island is already present or not.\n    - Returns: `Promise<boolean>` - `true` if media island exists, `false` otherwise\n    - Example:\n      ```javascript\n      const exists = await tb.mediaplayer.isExisting();\n      if (exists) {\n        console.log('A media island is already there');\n      }\n      ```\n\n### File\n  - **handler**\n    - **openFile**\n      - Description: Opens a file with the associated app based on file type.\n      - Parameters:\n        - `path: string` - Path of the file.\n        - `type: string` - Type of the file (e.g., \"text\", \"image\", \"video\", \"audio\", \"pdf\", \"webpage\").\n      - Returns: `Promise<void>`\n      - Example:\n        ```javascript\n        await tb.file.handler.openFile(\"/home/example.txt\", \"text\");\n        ```\n\n    - **addHandler**\n      - Description: Adds a handler for a specific file extension\n      - Parameters:\n        - `app: string` - App name to handle the file type\n        - `ext: string` - File extension\n      - Returns: `Promise<boolean>` - Returns `true` if succeeded\n      - Example:\n        ```javascript\n        await tb.file.handler.addHandler(\"ruffle\", \"swf\");\n        ```\n\n    - **removeHandler**\n      - Description: Removes a handler for a specific file extension\n      - Parameters:\n        - `ext: string` - File extension\n      - Returns: `Promise<boolean>` - Returns `true` if succeeded\n      - Example:\n        ```javascript\n        await tb.file.handler.removeHandler(\"swf\");\n        ```\n\n  - **icons**\n    - **get**\n      - Description: Gets icon path for a file extension.\n      - Parameters:\n        - `ext: string` - File extension.\n      - Returns: `Promise<string>` - Icon path.\n      - Example:\n        ```javascript\n        const icon = await tb.file.icons.get(\"png\");\n        console.log(icon);\n        ```\n\n    - **set**\n      - Description: Sets icon path for a file extension.\n      - Parameters:\n        - `ext: string` - File extension.\n        - `iconPath: string` - Path to icon.\n      - Returns: `Promise<boolean>`\n      - Example:\n        ```javascript\n        await tb.file.icons.set(\"log\", \"/assets/img/file-log.png\");\n        ```\n\n    - **remove**\n      - Description: Removes custom icon mapping for a file extension.\n      - Parameters:\n        - `ext: string` - File extension.\n      - Returns: `Promise<boolean>`\n      - Example:\n        ```javascript\n        await tb.file.icons.remove(\"log\");\n        ```\n\n### Additional Libraries\n  - **[libcurl](https://www.npmjs.com/package/libcurl.js)**\n    - Description: The libcurl networking API, used in Anura.net, TB Apps and tb.system.download\n  - **[fflate](https://www.npmjs.com/package/fflate)**\n    - Description: ZIP compression/decompression tool for Anura File Manager and TB Files App\n  - **fs**\n    - Description: File system API (TFS) for reading/writing files\n  - **crypto**\n    - Description: Password encryption tool\n    - Parameters:\n      - `pass: string` - Password to encrypt\n      - `file?: string` - (optional) File to save the password to\n    - Returns: `Promise<string>` - Encrypted password or \"Complete\" if saved to file\n  - **vfs**\n    - Description: Virtual File System for WebDAV servers and remote storage\n  - **buffer**\n    - Description: Buffer utility (from Filer) for working with binary data\n  - **registry**\n    - Description: System registry for storing and retrieving system-wide configuration\n  - **sh**\n    - Description: Shell interface for file system operations\n  - **liquor (Anura)**\n    - Description: Anura subsystem stub, provides compatibility with Anura applications\n  - **lemonade (Electron)**\n    - Description: Electron API compatibility layer for desktop-like features\n\nHave fun developing for Terbium!\n"
  },
  {
    "path": "docs/backend-configuration.md",
    "content": "# <span style=\"color: #32ae62;\">Backend Configuration Options</span>\n\nTerbium's backend is pretty cool and is configurable with the .env file in the root of this directory, alternatively during the setup if your .env file does not exist it will walk you through setting it up.\n\nNow with this being said theres a couple of things that could be confusing, so heres a rundown of the configurations:\n\n- <span style=\"color: #32ae62;\">MASQR</span>: This enables [MASQR](https://github.com/titaniumnetwork-dev/masqr-project) (A Anti Link Leaking System)\n- <span style=\"color: #32ae62;\">License Server Url</span>: This is another MASQR Configuration that is the License Server Url where MASQR communicated and validates keys with. If you dont have masqr enabled you do not need to change it.\n- <span style=\"color: #32ae62;\">Whitelisted Domains</span>: This is another MASQR Configuration that is the domains MASQR will not apply to.\n\nAlso in the backend, is [WispJS](https://github.com/MercuryWorkshop/wisp-client-js/tree/rewrite) pointing to /wisp/, please note that by default on all terbium instances the DNS Servers 1.1.1.3 and 1.0.0.3.\n"
  },
  {
    "path": "docs/contributions.md",
    "content": "# <span style=\"color: #32ae62;\">How to Contribute to Terbium v2</span>\nTable of Contents\n- [Understanding the File Structure](#understanding-the-file-structure)\n- [Learning What should and shouldn't be touched](#learning-what-should-and-shouldnt-be-touched)\n  \n## <a name=\"understanding-the-file-structure\" style=\"color: #32ae62;\">Understanding the File Structure</a>\nTerbium v2 for the most part is written in [React](https://react.dev). If you haven't already make sure you have all the dependencies installed which can be done via `pnpm i` (or the package manager of your choice).\n\nTerbium has 3 Folders that you should pay attention to and that are referenced throughout Terbium's Code.\n- <span style=\"color: #32ae62;\">src</span>: The location of the webOS's frontend code & apis Such as the `login`, `desktop`, `gui components` etc\n  - <span style=\"color: #32ae62;\">sys</span>: The location of the APIs and GUI components/styles\n    - <span style=\"color: #32ae62;\">gui</span>: The location of GUI Components\n      - <span style=\"color: #32ae62;\">styles</span>: Stylesheets for all the components\n    - <span style=\"color: #32ae62;\">liquor</span>: The location of all liquor related components and APIs\n    - <span style=\"color: #32ae62;\">apis</span>: The location of the API's code\n    \n- <span style=\"color: #32ae62;\">public</span>: The static folder for normal html, js, css components such as Default Applications, Libraries, and Anura Applications & Service Workers\n\n## <a name=\"learning-what-should-and-shouldnt-be-touched\" style=\"color: #32ae62;\">Learning What should and shouldn't be touched</a>\n\nFor the most part this is self explanatory. Unless you absolutely know what your doing **DO NOT** mess with anything in the `sys` folder or any typescript configuration files as they are system critical and will break things if you modify them without knowing what you're doing. If you do however know what you're doing and wish to expand upon the current functionality feel free to poke around in the `sys`.\n\nModifying apps in the `public` folder is fine and shouldn't break anything since it is not system dependent except for in the OOBE when it copies assets from there to the file system. If you wish to add Terminal Commands refer to [Creating Terminal Commands](./creating-terminal-commands.md)\n"
  },
  {
    "path": "docs/creating-apps.md",
    "content": "# <span style=\"color: #32ae62;\">Creating new applications</span>\n\nTable of Contents:\n\n- [Introduction](#introduction)\n- [Using TB Features](#using-tb-features)\n    - [Island Controls](#island-controls)\n    - [WM Controls](#wm-controls)\n- [Generating Manifests](#generating-manifests)\n- [Adding your application to the app launcher](#adding-your-application-to-the-app-launcher)\n- [Creating and Submiting your Application to the app store repo](#creating-new-applications)\n    - [Formatting PWAs](#formatting-pwas)\n    - [Formatting TAPPs](#formatting-tapps)\n    - [Formatting Anura Apps](#formatting-anura-apps)\n\n## <span style=\"color: #32ae62;\">Introduction</span>\n\nCreating a Terbium Application is really easy to do. First you need to decide wither or not you want your app to be a PWA or a TAPP. Once you have decided you can follow the steps bellow.\n\n## <span style=\"color: #32ae62;\">Using TB Features</span>\n\nTerbium v2 has Introduced many changes to the WM and General Window Functionality compared to [\"Legacy Terbium\"](https://github.com/terbiumos/webOS). \nSome of these new API Implementations for the WM that you want to use are as follows:\n\n```json\ntitle: {\n    text: \"<appname>\",\n},\nicon: \"<locationappicon>\",\nsrc: \"<locationofapp>\",\nnative: true,\nsize: {\n    width: 900,\n    height: 650,\n},\nsingle: false,\nresizable: true,\nsnapable: false,\n```\n\n- <span style=\"color: #32ae62;\">title</span>: The Title of the Application\n    - if you want to customize the weight follow the title example from above, if not just simply make the `title` field a string\n- <span style=\"color: #32ae62;\">icon</span>: The Applications Icon\n- <span style=\"color: #32ae62;\">src</span>: The Applications Main Page (Commonly index.html or an internet url)\n- <span style=\"color: #32ae62\">native</span>: Weither or not the application uses a custom UI or a normal ui\n- <span style=\"color: #32ae62;\">size</span>: The Size of the applications window\n    - <span style=\"color: #32ae62;\">width</span>: The Width of the Application\n    - <span style=\"color: #32ae62;\">height</span>: The Height of the Application\n    - <span style=\"color: #32ae62;\">minWidth</span>: The Minimum Width of the Application\n    - <span style=\"color: #32ae62;\">minHeight</span>: The Minimum Height of the Application\n- <span style=\"color: #32ae62;\">single</span>: Determiens if the Application allows you to open multiple windows or not\n- <span style=\"color: #32ae62;\">resizable</span>: Determines if the Application allows you to resize it\n- <span style=\"color: #32ae62;\">snapable</span>: Toggle for allowing window snapping\n- <span style=\"color: #32ae62;\">minimizable</span>: Toggle for allowing window to be minimized\n- <span style=\"color: #32ae62;\">maximizable</span>: Toggle for allowing the window to be maximized\n- <span style=\"color: #32ae62;\">closable</span>: Toggle for allowing the window to be closed\n- <span style=\"color: #32ae62;\">controls</span>: An array of which buttons are allowed to be on the window\n\n> A few new API's to be aware of:\n> - parent.window.tb.window.create({`wmargs`}): Allows you to create a Window in JS. Fill in `wmargs` with the Arguments you made above\n> - parent.window.tb.file.handler.openFile(fileItem.getAttribute(\"path\"), \"<extfromextentions.json>\"): Allows you to open a File in its respective app easily\n> - For API's in more in debpt visit the [API Docs](./apis/readme.md)\n\n### <a id=\"island-controls\" style=\"color: #32ae52;\">Island Controls</a>\n\nThe App Island allows you to have custom items in the left corner where it says the App Title.\n\nTo set it up you will be needing the app id and you will need to create a JS Script in your application to have the following JS Code:\n\n```js\nconst tb = parent.window.tb\nconst tb_island = tb.window.island;\nconst tb_window = tb.window;\nconst tb_context_menu = tb.context_menu;\n\ntb_island.addControl({\n    text: \"<titleofcontrol>\",\n    app_id: \"com.tb.<appid>\",\n    id: \"<giverandomname>\",\n    click: () => {\n        // Execute code here for when clicked\n    }\n})\n```\n\nIf you wish to use a context menu instead you can use this code instead\n\n```js\nconst tb = parent.window.tb\nconst tb_island = tb.window.island;\nconst tb_window = tb.window;\nconst tb_context_menu = tb.context_menu;\nconst tb_dialog = tb.dialog;\n\ntb_island.addControl({\n    text: \"<titleofcontrol>\",\n    app_id: \"com.tb.<appid>\",\n    id: \"<giverandomname>\",\n    click: () => {\n        const ctx = document.createElement(\"div\");\n        ctx.classList.add(\"context-menu\", \"fade-in\");\n        if(parent.document.querySelector(\".context-menu\")) {\n            parent.document.querySelector(\".context-menu\").remove();\n        }\n        ctx.id = \"<controlname_action>_ctx\";\n        ctx.style.left = `6px`;\n        ctx.style.top = parent.document.querySelector(\".app_island\").clientHeight + 12 + \"px\";\n        let isTrash = document.querySelector(\".exp\").getAttribute(\"path\") === \"/home/trash\" ? true : false;\n        const options = [\n            {\n                text: \"<controltitle>\",\n                click: () => {\n                    // Execute code here for when clicked\n                }\n            }\n        ]\n        options.forEach(option => {\n            if(option === null) return;\n            const btn = document.createElement(\"button\");\n            btn.classList.add(\"context-menu-button\");\n            btn.innerText = option.text;\n            btn.onclick = option.click;\n            ctx.appendChild(btn);\n        })\n        parent.document.body.appendChild(ctx);\n        parent.window.addEventListener(\"click\", (e) => {\n            if (!e.target.classList.contains(\"app_control\")) {\n                if(parent.document.querySelector(\".context-menu\")) {\n                    parent.document.querySelector(\".context-menu\").classList.add(\"fade-out\");\n                    setTimeout(() => {\n                        if(parent.document.querySelector(\".context-menu\"))\n                        parent.document.querySelector(\".context-menu\").remove();\n                    }, 150);\n                }\n            }\n        });\n    }\n})\n\nparent.document.querySelector(`[control-id=\"files-et\"]`).classList.add(\"hidden\");\n```\n\nWhat you can implement in the click is tottally up to you and you can do things like open a new window, alert something, etc\n\n### <a id=\"wm-controls\" style=\"color: #32ae62;\">WM Controls</a>\n\nThe WM Controls for the title bar are the only ones around right now.\nThose can be added by adding the following to the WM args:\n\n```json\ntitle: {\n    text: \"<appname>\",\n    weight: <text weight as number>,\n    html: `<html here>`\n},\n```\n\n## <span style=\"color: #32ae62;\">Generating Manifests</span>\n\nTo generate a TAPP Manifest for your TAPP Package you can use the TB Dev SDK 24 App avalible in the XSTARS XTRAS Repo and either use the manifest tool in the app gui or use it via the command line with tbsdk --gen-manifest --{args}\n\n## <span style=\"color: #32ae62;\">Adding your Application to the App Launcher</span>\n\n> <span style=\"font-family: url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">NOTE:</span> If you are going to submit this app to the app store ignore this step and follow the app store specific steps\n\nAdding your Application to the app launcher is pretty easy to do. If you want it to be installed on your site itself add the entry to the array in `/src/init/index.ts` or you can do the same thing within your terbium window itself using the api `tb.launcher.addApp({propshere})` or by editing the file: `/system/var/terbium/start.json`\n\n```json\n{\n    title: \"Calculator\",\n    icon: \"/apps/calculator.tapp/icon.svg\",\n    src: \"/apps/calculator.tapp/index.html\",\n    snapable: false,\n    maximizable: false,\n    size: {\n        width: 338,\n        height: 556\n    },\n},\n```\n\nOnce you fill and insert that you should notice your app has appeared in your launcher!\n\n## <span style=\"color: #32ae62;\">Creating and Submiting your Application to the app store repo</span>\n\n> Make sure you forked the repository: https://github.com/TerbiumOS/app-repo so you can make changes\n\nTo submit your repo follow the instructions below and make sure to follow our basic guidelines for your app to get accepted:\n\n## <span style=\"color: #32ae62;\">Formatting PWAs</span>\n\nPull Requests for apps are always viewed and heres some basic guidelines for your app to get accepted\n\n- App is in the repo in the folder `assets/com.tb.{appname}`\n- Icon should be defined in the `assets/com.tb.{appname}/icon.[ext]`\n- WM Arguments and Metadata is in the `apps.json` file in this repo\n    - Example\n    ```json\n    {\n        \"name\": \"YouTube\",\n        \"icon\": \"https://raw.githubusercontent.com/TerbiumOS/app-repo/main/assets/com.tb.youtube/icon.png\",\n        \"description\": \"Share your videos with friends, family, and the world.\",\n        \"authors\": [\"Google\"],\n        \"pkg-name\": \"youtube\",\n        \"images\": [\n            \"https://raw.githubusercontent.com/TerbiumOS/app-repo/main/assets/com.tb.youtube/images/1.png\"\n        ],\n        \"wmArgs\": {\n            \"title\": {\n                \"text\": \"YouTube\",\n                \"weight\": 600\n            },\n            \"icon\": \"https://raw.githubusercontent.com/TerbiumOS/app-repo/main/assets/com.tb.youtube/icon.png\",\n            \"src\": \"https://youtube.com\",\n            \"size\": {\n                \"width\": 600,\n                \"height\": 400\n            },\n            \"single\": true,\n            \"resizable\": true\n        }\n    }\n    ```\n\n## <span style=\"color: #32ae62;\">Formatting TAPP's</span>\n\n- The easiest way to creat TAPP's is to download the TB Dev SDK 2025 from the XSTARZ XTRAS repo and use the feilds to generate your TAPP and Manifest.\n- The app should be put into the assets folder with the naming scheme: {appname}.TAPP.zip or com.tb.{appname}.TAPP.zip\n- Also you can put into the app repo manifest where you want to download the TAPP from if you want to host it somewhere else for some reason, Image assets can still be stored in a folder in the assets folder as long as it follows the naming scheme of com.tb.{appname}\n  - Example\n  ```json\n    {\n        \"name\": \"About Proxy\",\n        \"icon\": \"https://aboutproxy.pages.dev/aboutbrowser/darkfavi.png\",\n        \"description\": \"Chrome for your browser\",\n        \"images\": [\n            \"https://raw.githubusercontent.com/TerbiumOS/app-repo/main/assets/com.tb.youtube/images/1.png\"\n        ],\n        \"authors\": [\"r58playz\"],\n        \"pkg-name\": \"aboutproxy\",\n        \"pkg-download\": \"https://tbapps.pages.dev/assets/aboutproxy.TAPP.zip\"\n    },\n  ```\n\n## <span style=\"color: #32ae62;\">Formatting Anura Apps</span>\n\n- Terbium app repos also support having Anura Apps however they must be formatted like so:\n- You can store the app in the assets folder as a regular zip file\n  - Example\n  ```json\n    {\n        \"name\": \"Snae Player\",\n        \"icon\": \"https://raw.githubusercontent.com/MercuryWorkshop/anura-repo/master/apps/anura.music/icon.png\",\n        \"description\": \"A music client ported to Anura\",\n        \"authors\": [\"Mercury Workshop\"],\n        \"pkg-name\": \"snaeplayer\",\n        \"anura-pkg\": \"https://raw.githubusercontent.com/MercuryWorkshop/anura-repo/master/apps/anura.music/app.zip\"\n    }\n  ```\n"
  },
  {
    "path": "docs/creating-terminal-commands.md",
    "content": "# <span style=\"color: #32ae62;\">Creating Terminal Commands</span>\n\n**Last Updated**: TSH-v2.3 - 02/11/2026\n\nWelcome — creating commands for the Terbium Terminal is simple and flexible. This document is refreshed to cover the new APIs, interactive behavior (passthrough), built-in commands, and best practices.\n\n## Where to put your command\n\nPlace your script in the Terminal app's `scripts` folder. From the root (`//`) open `fs/apps/system/terminal.tapp/scripts/` (or `fs/apps/user/<yourname>/terminal/`) and add your `.js` file.\n\nThe Terminal executes the script's code directly and expects your file to register a callable function (typical pattern: define and then call a function that accepts `args`).\n\n## Minimal example\n\n```js\nfunction hello(args) {\n  displayOutput(`Hi, how are you ${args[0] || 'stranger'}!`);\n  createNewCommandInput(); // show a new prompt\n}\n\nhello(args);\n```\n\nRun: `hello Alice` → prints `Hi, how are you Alice!` and then returns a prompt.\n\n## Script API (what your script can call)\n\nWhen your script runs inside an active Terminal session, it receives the following helper functions and objects (passed as parameters):\n\n- `args` — Array of raw arguments (strings) from the command line (e.g. for `foo a b` args = [`\"a\"`,`\"b\"`]).\n- `displayOutput(message, ...styles)` — Print message to the terminal. Supports `%c` style placeholders with CSS-style strings (`'color: #ff0000'`).\n- `displayError(message)` — Print an error message (red, prefixed with `ERR:`).\n- `createNewCommandInput()` — Show a new prompt (call when your command is complete). Note: this is debounced and will no-op during interactive passthrough mode (see below).\n- `term` — The raw xterm instance (advanced use only).\n- `path` — Current working path string.\n- `terbium` or `tb` — The [Terbium API](./apis/readme.md)\n- `buffer` — Internal buffer object (rarely needed).\n- `setTabTitle(title)` — (Available when running inside a session) Change the tab label for this session (e.g. `setTabTitle('Node: JSH')`).\n- `exitPassthrough()` — (Passed to interactive command scripts) Use this to explicitly end passthrough mode when your script finishes or the spawned interactive child exits.\n\nImportant: when running scripts from outside a session (no active session), fewer helpers are available (for example `setTabTitle` may *not* be provided). Write scripts defensively and check for the existence of optional helpers if you need to support both contexts.\n\n## Interactive / passthrough mode\n\nSome commands (for example `node` and `nano`) are interactive shells that echo input and control terminal state. The Terminal will automatically:\n\n- Enter **passthrough** mode when it detects common interactive commands. In passthrough mode:\n  - The engine stops processing typed commands itself.\n  - Local echo is disabled (the interactive program will echo input itself).\n  - `createNewCommandInput()` is intentionally ignored while passthrough is active (to avoid double prompts).\n- Exit passthrough when the interactive program prints an exit message (or when your script calls `exitPassthrough()` explicitly).\n\nIf you need to spawn an interactive process from a script, call `exitPassthrough()` after the process exits so the engine will restore the prompt.\n\n## Best practices\n\n- Always call `createNewCommandInput()` when your command finishes to show the prompt (unless your command intentionally stays interactive).\n- Avoid calling `createNewCommandInput()` multiple times in quick succession — the engine debounces prompt creation (short window) to prevent double prompts.\n- Use `displayError()` for errors so output is styled consistently.\n- Use `setTabTitle()` to reflect session state (e.g., `setTabTitle('Node: JSH')` while running a REPL).\n- Check for optional helpers if you expect your script to run both inside and outside a session.\n\n## Example: setting tab title and using passthrough\n\n```js\n// This example is a pattern — actual interactive processes depend on your environment\nasync function nodeWrapper(args) {\n  setTabTitle && setTabTitle('Node: JSH');\n  displayOutput('Starting Node.js...');\n\n  // If your script starts an interactive child, you can rely on the engine's passthrough,\n  // or call exitPassthrough() explicitly when the child finishes:\n  // (pseudo-code)\n  // await spawnNode(args).finally(() => exitPassthrough && exitPassthrough());\n}\n\nnodeWrapper(args);\n```\n\n## Troubleshooting\n\n- If you see duplicated input while in a REPL, it's usually because both the engine and the program are echoing — ensure passthrough is active for interactive programs (engine handles common shells automatically).\n- If the prompt appears twice, the engine now debounces `createNewCommandInput()` calls; check long-running scripts that might call it twice and avoid redundant calls.\n\nIf you'd like, I can add a small script template generator under `scripts/` to scaffold correct patterns (including usage of `setTabTitle()` and `exitPassthrough()`), and a short test harness that runs a few sample scripts to verify behavior.\n\nHappy scripting! 🚀\n"
  },
  {
    "path": "docs/lemonade-compat.md",
    "content": "# <span style=\"color: #32ae62;\">Lemonade Compatability</span>\n\nLemonade is our compatability layer for Electron, Its named Lemonade because of inspiration of how Nintendo Emulators are named.\n\nThe current specifications of Lemonade are up to date with the current version of electron (36.4.0)\n\n## <span style=\"color: #32ae62;\">API Support</span>\n\nBelow we have a small chart with a list of the current supported apis in Lemonaed\n\n| API | Support | Notes |\n| :--: | :---: | :---: |\n| BrowserWindow | Full | Works well enough for stable use |\n| Notification | Full | Works well enough for stable use |\n| Net | Full | Works well enough for stable use |\n| Dialog | Full | Works well enough for stable use |\n\nMore apis will be added in the future just like liquor but Lemonade currently provides drop in support for the most common Electron API's\n"
  },
  {
    "path": "docs/lemonade.md",
    "content": "# # <span style=\"color: #32ae62;\">Introduction to Lemonade</span>\n\nLemonade provides an Electron-compatible API layer for web-based applications, allowing Electron apps to run in a browser environment with minimal modifications.\n\n## Overview\n\nLemonade mimics Electron's API structure and provides browser-based implementations of common Electron modules. This allows developers to write code that works in both Electron and web environments.\n\n## Supported Modules\n\n### Core Modules\n\n#### `app` - Application Lifecycle\n- `app.getName()` / `app.setName()` - Get/set application name\n- `app.getVersion()` - Get application version\n- `app.getPath(name)` - Get special directories (home, appData, userData, temp, downloads, documents, desktop)\n- `app.isReady()` / `app.whenReady()` - Check application ready state\n- `app.quit()` / `app.exit()` - Exit application\n- `app.relaunch()` - Reload the application\n- `app.getLocale()` - Get user locale\n\n#### `BrowserWindow` - Window Management\n- Constructor with options (width, height, title, icon, resizable, etc.)\n- `loadURL(url)` - Load a URL (with proxy support)\n- `loadFile(path)` - Load a local file\n- `show()` / `hide()` - Show/hide window\n- `minimize()` / `maximize()` - Minimize/maximize window\n- `close()` / `destroy()` - Close window\n- `setTitle(title)` / `getTitle()` - Window title\n- `setSize(width, height)` / `getSize()` - Window size\n- `setPosition(x, y)` / `getPosition()` - Window position\n- `center()` - Center window on screen\n- `setFullScreen(flag)` / `isFullScreen()` - Fullscreen mode\n- Event listeners: `on()`, `once()`, `removeListener()`\n- Events: `close`, `closed`, `show`, `hide`, `focus`, `blur`, `maximize`, `minimize`\n\n#### `dialog` - Native Dialogs\n- `showOpenDialog(options)` - File/directory picker\n- `showSaveDialog(options)` - Save file dialog\n- `showMessageBox(options)` - Message box with buttons\n- `showErrorBox(title, content)` - Error alert\n\n#### `shell` - Desktop Integration\n- `openExternal(url)` - Open URL in default browser\n- `openPath(path)` - Open file/directory\n- `showItemInFolder(path)` - Show file in folder\n- `moveItemToTrash(path)` - Move to trash\n- `beep()` - Play system beep sound\n\n#### `clipboard` - Clipboard Access\n- `readText()` / `writeText(text)` - Text operations\n- `readHTML()` / `writeHTML(html)` - HTML operations\n- `readImage()` / `writeImage(image)` - Image operations\n- `clear()` - Clear clipboard\n- Supports async clipboard API\n\n#### `ipcRenderer` / `ipcMain` - Inter-Process Communication\n- `send(channel, ...args)` - Send message\n- `invoke(channel, ...args)` - Request/response pattern\n- `on(channel, listener)` - Listen for messages\n- `once(channel, listener)` - Listen once\n- `removeListener(channel, listener)` - Remove listener\n\n#### `net` - Network Requests\n- `request(url, options)` - Make HTTP request\n- `fetch(url, options)` - Fetch API wrapper\n- `isOnline()` - Check online status\n- Supports timeout and abort signals\n\n#### `screen` - Display Information\n- `getPrimaryDisplay()` - Get primary display info\n- `getAllDisplays()` - Get all displays\n- `getDisplayNearestPoint(point)` - Find display at point\n- Display info includes: bounds, workArea, size, scaleFactor, rotation\n\n#### `process` - Process Information & Node.js Execution\n- `process.platform` - OS platform (always \"linux\" in WebContainer)\n- `process.arch` - Architecture (x64)\n- `process.versions` - Version information (Node.js 18.x)\n- `process.env` - Environment variables\n- `process.argv` - Command line arguments\n- `process.cwd()` - Current working directory\n- `process.uptime()` - Process uptime\n- `process.memoryUsage()` - Memory statistics\n- `process.isNodeAvailable` - Check if WebContainer Node.js is ready\n- **`process.exec(command, args, options)`** - Execute Node.js command via WebContainer\n- **`process.spawn(command, args, options)`** - Spawn Node.js process via WebContainer\n- **`process.runScript(scriptPath, args)`** - Run a Node.js script file\n- **`process.evalNode(code)`** - Evaluate JavaScript in Node.js context\n- `process.kill(pid)` - Kill a process by PID\n\n**Note:** This module integrates with Terbium's WebContainer (`tb.node`) to provide real Node.js execution instead of simulation.\n\n#### `Notification` - System Notifications\n- Constructor with options (title, subtitle, body, icon)\n- Event handling with `on()` / `off()`\n- `show()` / `close()` - Display notification\n\n## Usage Examples\n\n### Basic Window Creation\n\n```typescript\nimport { BrowserWindow } from './sys/lemonade';\n\nconst win = new BrowserWindow({\n  width: 800,\n  height: 600,\n  title: 'My App',\n  resizable: true,\n});\n\nwin.loadURL('https://example.com');\n\nwin.on('close', () => {\n  console.log('Window closing');\n});\n```\n\n### Dialog Usage\n\n```typescript\nimport { dialog } from './sys/lemonade';\n\nconst result = await dialog.showOpenDialog({\n  title: 'Select File',\n  properties: ['openFile'],\n});\n\nconsole.log('Selected:', result);\n```\n\n### IPC Communication\n\n```typescript\nimport { ipcRenderer } from './sys/lemonade';\n\n// Send message\nipcRenderer.send('message-channel', 'Hello');\n\n// Listen for response\nipcRenderer.on('response-channel', (event, data) => {\n  console.log('Received:', data);\n});\n\n// Request/response pattern\nconst result = await ipcRenderer.invoke('get-data', params);\n```\n\n### Clipboard Operations\n\n```typescript\nimport { clipboard } from './sys/lemonade';\n\n// Write text\nawait clipboard.writeText('Hello World');\n\n// Read text\nconst text = await clipboard.readText();\n\n// Write HTML\nawait clipboard.writeHTML('<b>Bold text</b>');\n```\n\n### Application Paths\n\n```typescript\nimport { app } from './sys/lemonade';\n\nconst homePath = app.getPath('home');\nconst appDataPath = app.getPath('appData');\nconst userDataPath = app.getPath('userData');\n```\n\n### Node.js Execution with WebContainer\n\n```typescript\nimport { process } from './sys/lemonade';\n\n// Check if Node.js is available\nif (process.isNodeAvailable) {\n  // Execute a Node.js command\n  const exitCode = await process.exec('node', ['--version']);\n  \n  // Run a script file from Terbium's filesystem\n  await process.runScript('/home/user/script.js', ['arg1', 'arg2']);\n  \n  // Evaluate Node.js code directly\n  const output = await process.evalNode('console.log(process.version)');\n  \n  // Spawn a long-running process\n  const proc = await process.spawn('node', ['server.js']);\n  proc.output.pipeTo(new WritableStream({\n    write(chunk) {\n      console.log(chunk);\n    }\n  }));\n  \n  // Install npm packages\n  await process.exec('npm', ['install', 'express']);\n}\n```\n\n### Shell Integration\n\n```typescript\nimport { shell } from './sys/lemonade';\n\n// Open URL\nawait shell.openExternal('https://example.com');\n\n// Show file\nshell.showItemInFolder('/path/to/file.txt');\n\n// Move to trash\nawait shell.moveItemToTrash('/path/to/file.txt');\n```\n\n## Architecture\n\nLemonade wraps the underlying `window.tb` API (TerbiumOS API) and provides Electron-compatible interfaces. When an Electron method is called, Lemonade translates it to the appropriate `window.tb` call.\n\n### Key Adapters\n\n- **Window Management**: Maps to `window.tb.window.*`\n- **File System**: Maps to `window.tb.fs.*`\n- **Dialogs**: Maps to `window.tb.dialog.*`\n- **Notifications**: Maps to `window.tb.notification.*`\n- **Network**: Maps to `window.tb.libcurl.fetch`\n- **Proxy**: Handles URL proxying via `window.tb.proxy.encode`\n- **Node.js**: Uses `window.tb.node.webContainer` for real Node.js execution via WebContainer\n\n## Limitations\n\nSince Lemonade runs in a web environment, some Electron features have limitations:\nTerbium's virtual file system\n2. **Native Menus**: Not fully implemented\n3. **System Tray**: Not available in web\n4. **Native Notifications**: Uses Terbium's notification system\n5. **Process Control**: Some methods are simulated (can't exit browser)\n6. **Multiple Windows**: Limited support via Terbium's window manager\n7. **Synchronous APIs**: Some sync methods are async\n8. **Node.js**: Requires WebContainer to be initialized (`tb.node.isReady === true`)\n7. **Synchronous APIs**: Some sync methods are async\n\n## Future Enhancements\n\nPotential additions:\n- Menu/MenuItem support\n- Tray icons (where possible)\n- PowerMonitor\n- Protocol handling\n- Content tracing\n- Crash reporter\n- Native image handling\n- Web contents manipulation\n- Session management\n- Cookies API\n- Download manager\n\n## Compatibility\n\nLemonade aims to maintain API compatibility with Electron 18+. Not all features are available due to browser limitations, but the API surface matches Electron where possible.\n\n## Development\n\nTo extend Lemonade:\n\n1. Add new module in `src/sys/lemonade/modulename.ts`\n2. Export from `src/sys/lemonade/index.ts`\n3. Implement Electron-compatible API\n4. Map to `window.tb` equivalents\n5. Document usage and limitations\n"
  },
  {
    "path": "docs/static-hosting.md",
    "content": "# <span style=\"color: #32ae62;\">Static Hosting Terbium</span>\n\nFor this tutorial, Cloudflare pages will be used however the instructions will be similar on other static hosts.\n\n### <span style=\"color: #32ae62;\">Step 1.</span>\n\nFork this repository and connect your github account to the static host of your choise.\n\n> <span style=\"font-family: url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); color: #ffd900;\">⚠</span> <span style=\"color: #ffd900;\">NOTE:</span> On Cloudflare pages, Terbium is automatically configured to use Node 20. If your using a different host check with them that you are using Node 20 or later as Terbium **WILL NOT** build on older versions.\n\n### <span style=\"color: #32ae62;\">Step 2.</span>\n\nUnder the `build` section command put: `npm i; npm run build-static` **LEAVE THE START COMMAND BLANK IF IT EXISTS**\n\nThen under the output directory put the folder: `dist` and click Deploy\n\n### <span style=\"color: #32ae62;\">Step 3. (Optional)</span>\n\nNow that the sites deployed, you have probably noticed that the Default wisp server wont be running since your static hosting. If you wish to change this navigate to `sys/init/index.ts` scroll down to Line 41 and replace the line: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/` with the wisp server of your choice as a string. If you dont want to do this you dont have to as you can change it in the OOBE."
  },
  {
    "path": "docs/upk-build.md",
    "content": "# <span style=\"color: #32ae62;\">UPK Building</span>\n\nBy default, if you fork the Terbium v2 repo on github included will be a workflow that will automatically upload the latest UPK release to either the latest github release (if applicable) or to a new github tag\n\nHowever if you want to build it yourself on your local machine you can you just need the following dependencies:\n\n- NodeJS 22 or later\n- Python 3.12 or later\n\nFirst, download [UPK Tools](https://cdn.terbiumon.top/upk-tools.zip) from the terbium cdn:\n\n**⚠️ NOTE** Be sure to check the source you are downloading the UPK Tools from. If your downloading it form a source that is **NOT** hosted under `terbiumon.top` please make sure its not malicious content. TerbiumOS Developement is not responsible for any damage caused by fradulent downloads in non-official repositories\n\nNext, extract the zip file in your terbium instance and run either upk-all.ps1 if your on windows or upk-all.sh if your on any other unix system\n\nThis file will automatically install all the needed tools and nescessities for the UPK Build and compile it to a zip file named `terbium-upk.app.zip` which you can open in any anura instance and install Terbium on it\n"
  },
  {
    "path": "env.d.ts",
    "content": "declare namespace NodeJS {\n\tinterface ProcessEnv {\n\t\tport: number;\n\t\tmasqr: boolean;\n\t\tlicensingURL: string | any;\n\t\twhitelistedDomains: string[];\n\t}\n}\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport reactHooks from \"eslint-plugin-react-hooks\";\nimport reactRefresh from \"eslint-plugin-react-refresh\";\nimport tseslint from \"typescript-eslint\";\n\nexport default tseslint.config(\n\t{ ignores: [\"dist\", \"public\"] },\n\t{\n\t\textends: [js.configs.recommended, ...tseslint.configs.recommended],\n\t\tfiles: [\"**/*.{ts,tsx}\"],\n\t\tlanguageOptions: {\n\t\t\tecmaVersion: 2020,\n\t\t\tglobals: globals.browser,\n\t\t},\n\t\tplugins: {\n\t\t\t\"react-hooks\": reactHooks,\n\t\t\t\"react-refresh\": reactRefresh,\n\t\t},\n\t\trules: {\n\t\t\t...reactHooks.configs.recommended.rules,\n\t\t\t\"no-unused-vars\": \"off\",\n\t\t\t\"@typescript-eslint/no-unused-vars\": \"off\",\n\t\t\t\"@typescript-eslint/ban-ts-comment\": \"off\",\n\t\t\t\"@typescript-eslint/no-explicit-any\": \"off\",\n\t\t\t\"no-var\": \"off\",\n\t\t\t\"react-refresh/only-export-components\": [\"warn\", { allowConstantExport: true }],\n\t\t},\n\t},\n);\n"
  },
  {
    "path": "fail.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <title>Welcome to nginx!</title>\n    <style>\n      html {\n        color-scheme: light dark;\n      }\n      body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n      }\n    </style>\n  </head>\n  <body>\n    <h1>Welcome to nginx!</h1>\n    <p>\n      If you see this page, the nginx web server is successfully installed and\n      working. Further configuration is required. If you are expecting another\n      page, please check your network or\n      <a href=\"/\" id=\"rcheck\"><b>Refresh this page</b></a>\n    </p>\n\n    <p>\n      For online documentation and support please refer to\n      <a href=\"http://nginx.org/\">nginx.org</a>.<br />\n      Commercial support is available at\n      <a href=\"http://nginx.com/\">nginx.com</a>.\n    </p>\n\n    <p><em>Thank you for using nginx.</em></p>\n    <script>\n      if (!localStorage[\"auth\"] && new URL(document.all.rcheck.href).password) {\n        window.location.reload();\n        localStorage[\"auth\"] = 1;\n      }\n    </script>\n  </body>\n</html>"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Terbium</title>\n    <link rel=\"manifest\" href=\"/manifest.json\" />\n    <meta name=\"title\" content=\"Terbium\" />\n    <meta name=\"description\" content=\"The next generation of Terbium. built to last.\" />\n    <meta name=\"theme-color\" content=\"#D16FFF\" />\n    <meta property=\"og:image\" content=\"/tb.svg\" />\n    <meta name=\"google-adsense-account\" content=\"ca-pub-2875368391887289\" />\n    <script src=\"/tfs/tfs.js\"></script>\n    <script>\n      (async () => {\n        const handle = await navigator.storage.getDirectory();\n        window.tfs = new window.tfs(handle)\n        if (typeof window.tb === \"undefined\") window.tb = {};\n\t      if (typeof window.tb.fs === \"undefined\" && typeof window.tb !== \"undefined\") {\n\t\t      console.log(\"[FS] File System Ready\");\n\t\t      window.tb.fs = window.tfs.fs;\n\t\t      window.tb.buffer = window.tfs.buffer;\n\t\t      window.tb.sh = window.tfs.shell;\n\t      }\n        if (localStorage.getItem(\"eruda\")) {\n          const erudaScript = document.createElement(\"script\");\n          erudaScript.src = \"https://cdn.jsdelivr.net/npm/eruda\";\n          erudaScript.onload = () => {\n            window.eruda.init();\n          };\n          document.head.appendChild(erudaScript);\n        }\n      })();\n    </script>\n  </head>\n  <body>\n    <div id=\"root\" class=\"flex flex-col h-full bg-[#0e0e0e] overflow-hidden\"></div>\n    <script src=\"/scram/scramjet.all.js\"></script>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n    <script type=\"module\" src=\"/src/init/index.ts\"></script>\n    <script src=\"assets/libs/filer.min.js\"></script>\n    <!-- Google tag (gtag.js) -->\n    <script async src=\"https://www.googletagmanager.com/gtag/js?id=G-1RW33JJQS2\"></script>\n    <script>\n      window.dataLayer = window.dataLayer || [];\n      function gtag(){dataLayer.push(arguments);}\n      gtag('js', new Date());\n\n      gtag('config', 'G-1RW33JJQS2');\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"tb-v2\",\n\t\"private\": true,\n\t\"version\": \"2.3.0\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"start\": \"npm run build && tsx bootstrap.ts\",\n\t\t\"start:nobuild\": \"tsx bootstrap.ts\",\n\t\t\"build-apps-json\": \"tsx bootstrap.ts --apps-only\",\n\t\t\"build-static\": \"touch .env && tsx bootstrap.ts --apps-only && vite build\",\n\t\t\"dev\": \"tsx bootstrap.ts --dev && vite dev\",\n\t\t\"build\": \"tsx bootstrap.ts --apps-only && vite build\",\n\t\t\"lint\": \"eslint .\",\n\t\t\"vite\": \"vite\",\n\t\t\"preview\": \"vite preview\",\n\t\t\"check\": \"biome check . --write\",\n\t\t\"fmt\": \"biome format --write .\"\n\t},\n\t\"dependencies\": {\n\t\t\"@heroicons/react\": \"^2.2.0\",\n\t\t\"@hono/node-server\": \"^1.19.12\",\n\t\t\"@mercuryworkshop/bare-mux\": \"^2.1.8\",\n\t\t\"@mercuryworkshop/epoxy-transport\": \"^2.1.28\",\n\t\t\"@mercuryworkshop/libcurl-transport\": \"^1.5.2\",\n\t\t\"@mercuryworkshop/scramjet\": \"https://cdn.terbiumon.top/scramjet-v2.0-alpha.tgz\",\n\t\t\"@mercuryworkshop/wisp-js\": \"^0.4.1\",\n\t\t\"@paralleldrive/cuid2\": \"^3.3.0\",\n\t\t\"@terbiumos/tfs\": \"1.0.22\",\n\t\t\"@titaniumnetwork-dev/ultraviolet\": \"^3.2.10\",\n\t\t\"@webcontainer/api\": \"^1.6.1\",\n\t\t\"better-auth\": \"^1.5.6\",\n\t\t\"compressorjs\": \"^1.2.1\",\n\t\t\"cropperjs\": \"1.6.2\",\n\t\t\"crypto-js\": \"^4.2.0\",\n\t\t\"dotenv\": \"^17.3.1\",\n\t\t\"fflate\": \"^0.8.2\",\n\t\t\"hono\": \"^4.12.9\",\n\t\t\"htmlparser2\": \"^10.1.0\",\n\t\t\"libcurl.js\": \"^0.7.4\",\n\t\t\"modern-screenshot\": \"^4.6.8\",\n\t\t\"path-browserify\": \"^1.0.1\",\n\t\t\"react\": \"^19.2.4\",\n\t\t\"react-dom\": \"^19.2.4\",\n\t\t\"zustand\": \"5.0.12\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@biomejs/biome\": \"2.4.10\",\n\t\t\"@eslint/js\": \"^10.0.1\",\n\t\t\"@tailwindcss/postcss\": \"^4.2.2\",\n\t\t\"@types/adm-zip\": \"^0.5.8\",\n\t\t\"@types/node\": \"^25.3.3\",\n\t\t\"@types/path-browserify\": \"^1.0.3\",\n\t\t\"@types/react\": \"^19.2.14\",\n\t\t\"@types/react-dom\": \"^19.2.3\",\n\t\t\"@vitejs/plugin-react-swc\": \"^4.3.0\",\n\t\t\"adm-zip\": \"^0.5.17\",\n\t\t\"autoprefixer\": \"^10.4.27\",\n\t\t\"consola\": \"^3.4.2\",\n\t\t\"eslint\": \"^10.1.0\",\n\t\t\"eslint-plugin-react-hooks\": \"7.0.1\",\n\t\t\"eslint-plugin-react-refresh\": \"^0.5.2\",\n\t\t\"globals\": \"^17.4.0\",\n\t\t\"open\": \"^11.0.0\",\n\t\t\"postcss\": \"^8.5.8\",\n\t\t\"tailwindcss\": \"^4.2.2\",\n\t\t\"tsx\": \"^4.21.0\",\n\t\t\"typescript\": \"^6.0.2\",\n\t\t\"typescript-eslint\": \"^8.58.0\",\n\t\t\"vite\": \"^8.0.3\",\n\t\t\"vite-plugin-static-copy\": \"^3.4.0\"\n\t},\n\t\"pnpm\": {\n\t\t\"onlyBuiltDependencies\": [\n\t\t\t\"@prisma/engines\",\n\t\t\t\"@swc/core\",\n\t\t\t\"@tailwindcss/oxide\",\n\t\t\t\"bufferutil\",\n\t\t\t\"esbuild\",\n\t\t\t\"prisma\"\n\t\t],\n\t\t\"ignoredBuiltDependencies\": [\n\t\t\t\"@mercuryworkshop/scramjet\"\n\t\t]\n\t},\n\t\"engines\": {\n\t\t\"node\": \">=20.19.0\"\n\t}\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "export default {\n\tplugins: {\n\t\t\"@tailwindcss/postcss\": {},\n\t\tautoprefixer: {},\n\t},\n};\n"
  },
  {
    "path": "public/anura-sw.js",
    "content": "/* global workbox */\n/** @type {import('@terbiumos/tfs').TFS} */\n\n// was a workaround for a firefox quirk where crossOriginIsolated\n// is not reported properly in a service worker, now its just assumed for\n// compatibility with UV\nObject.defineProperty(globalThis, \"crossOriginIsolated\", {\n\tvalue: true,\n\twritable: false,\n});\n\n// Not recommended but a bypass for libs that expect window to exist\nself.window = self;\n\n// Due to anura's filesystem only being available once an anura instance is running,\n// we need a temporary filesystem to store files that are requested for caching.\n// As the anura filesystem is a wrapper around Filer, we can use default Filer here.\nimportScripts(\"/assets/libs/filer.min.js\");\nimportScripts(\"/tfs/tfs.js\");\n\n// Importing mime\nimportScripts(\"/assets/libs/mime.iife.js\");\n\n// Download handler\nimportScripts(\"/assets/libs/dl-handler.min.js\");\n\n// self.fs = new Filer.FileSystem({\n//     name: \"anura-mainContext\",\n//     provider: new Filer.FileSystem.providers.IndexedDB(),\n// });\n\nconst filerfs = new Filer.FileSystem({\n\tname: \"anura-mainContext\",\n\tprovider: new Filer.FileSystem.providers.IndexedDB(),\n});\nconst filersh = new filerfs.Shell();\n\n(async () => {\n\tconst handle = await navigator.storage.getDirectory();\n\twindow.tfs = new window.tfs(handle);\n\tself.opfs = window.tfs.fs;\n\tself.opfssh = window.tfs.sh;\n})();\n\nasync function currentFs() {\n\t// isConnected will return true if the anura instance is running, and otherwise infinitely wait.\n\t// it will never return false, but it may hang indefinitely if the anura instance is not running.\n\t// here, we race the isConnected promise with a timeout to prevent hanging indefinitely.\n\n\tif (!self.isConnected) {\n\t\t// An anura instance has not been started yet to populate the isConnected promise.\n\t\t// We automatically know that the filesystem is not connected.\n\t\treturn {\n\t\t\tfs: self.opfs || filerfs,\n\t\t\tsh: self.opfssh || filersh,\n\t\t};\n\t}\n\n\tconst CONN_TIMEOUT = 1000;\n\tconst winner = await Promise.race([\n\t\tnew Promise(resolve =>\n\t\t\tsetTimeout(() => {\n\t\t\t\tresolve({\n\t\t\t\t\tfs: self.opfs || filerfs,\n\t\t\t\t\tsh: self.opfssh || filersh,\n\t\t\t\t\tfallback: true,\n\t\t\t\t});\n\t\t\t}, CONN_TIMEOUT),\n\t\t),\n\t\tself.isConnected.then(() => ({\n\t\t\tfs: self.anurafs,\n\t\t\tsh: self.anurash,\n\t\t})),\n\t]);\n\n\tif (winner.fallback) {\n\t\tconsole.debug(\"Falling back to Filer\");\n\t\t// unset isConnected so that we don't hold up future requests\n\t\tself.isConnected = undefined;\n\t}\n\n\treturn winner;\n}\n\nself.Buffer = Filer.Buffer;\n\nimportScripts(\"/assets/libs/comlink.min.umd.js\");\nimportScripts(\"/assets/libs/idb-keyval.js\");\nimportScripts(\"/assets/libs/workbox/workbox-sw.js\");\nworkbox.setConfig({\n\tdebug: false,\n\tmodulePathPrefix: \"/assets/libs/workbox/\",\n});\n\nconst supportedWebDAVMethods = [\n\t\"OPTIONS\",\n\t\"PROPFIND\",\n\t\"PROPPATCH\",\n\t\"MKCOL\",\n\t\"GET\",\n\t\"HEAD\",\n\t\"POST\", // sometimes used for special operations\n\t\"PUT\",\n\t\"DELETE\",\n\t\"COPY\",\n\t\"MOVE\",\n\t\"LOCK\",\n\t\"UNLOCK\",\n];\n\nasync function handleDavRequest({ request, url }) {\n\tconst fsCallback = (await currentFs()).fs;\n\tconst fs = fsCallback.promises;\n\tconst shell = new (await currentFs()).sh();\n\tconst method = request.method;\n\tconst path = decodeURIComponent(url.pathname.replace(/^\\/dav/, \"\") || \"/\");\n\n\tconst getBuffer = async () => new Uint8Array(await request.arrayBuffer());\n\tconst getDestPath = () => decodeURIComponent(new URL(request.headers.get(\"Destination\"), url).pathname.replace(/^\\/dav/, \"\"));\n\n\ttry {\n\t\tswitch (method) {\n\t\t\tcase \"OPTIONS\":\n\t\t\t\treturn new Response(null, {\n\t\t\t\t\tstatus: 204,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAllow: \"OPTIONS, PROPFIND, PROPPATCH, MKCOL, GET, HEAD, POST, PUT, DELETE, COPY, MOVE, LOCK, UNLOCK\",\n\t\t\t\t\t\tDAV: \"1, 2\",\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\tcase \"PROPFIND\": {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = await fs.stat(path);\n\t\t\t\t\tconst isDirectory = stats.type === \"DIRECTORY\";\n\t\t\t\t\tconst href = url.pathname;\n\t\t\t\t\tlet responses = \"\";\n\n\t\t\t\t\tconst renderEntry = async (entryPath, stat) => {\n\t\t\t\t\t\tconst isDir = stat.type === \"DIRECTORY\";\n\t\t\t\t\t\tconst contentLength = isDir ? \"\" : `<a:getcontentlength b:dt=\"int\">${stat.size}</a:getcontentlength>`;\n\t\t\t\t\t\tconst contentType = isDir ? \"\" : `<a:getcontenttype>${mime.default.getType(entryPath) || \"application/octet-stream\"}</a:getcontenttype>`;\n\t\t\t\t\t\tconst creationDate = new Date(stat.ctime).toISOString();\n\t\t\t\t\t\tconst lastModified = new Date(stat.mtime).toUTCString();\n\t\t\t\t\t\tconst resourcetype = isDir ? \"<a:collection/>\" : \"\";\n\n\t\t\t\t\t\treturn `\n\t\t\t\t\t\t\t<a:response>\n\t\t\t\t\t\t\t\t<a:href>${entryPath}</a:href>\n\t\t\t\t\t\t\t\t<a:propstat>\n\t\t\t\t\t\t\t\t\t<a:status>HTTP/1.1 200 OK</a:status>\n\t\t\t\t\t\t\t\t\t<a:prop>\n\t\t\t\t\t\t\t\t\t\t<a:resourcetype>${resourcetype}</a:resourcetype>\n\t\t\t\t\t\t\t\t\t\t${contentLength}\n\t\t\t\t\t\t\t\t\t\t${contentType}\n\t\t\t\t\t\t\t\t\t\t<a:creationdate>${creationDate}</a:creationdate>\n\t\t\t\t\t\t\t\t\t\t<a:getlastmodified>${lastModified}</a:getlastmodified>\n\t\t\t\t\t\t\t\t\t</a:prop>\n\t\t\t\t\t\t\t\t</a:propstat>\n\t\t\t\t\t\t\t</a:response>\n\t\t\t\t\t\t`;\n\t\t\t\t\t};\n\n\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\tresponses = await renderEntry(href.endsWith(\"/\") ? href : href + \"/\", stats);\n\n\t\t\t\t\t\tconst files = await fs.readdir(path);\n\t\t\t\t\t\tconst fileResponses = await Promise.all(\n\t\t\t\t\t\t\tfiles.map(async file => {\n\t\t\t\t\t\t\t\tconst fullPath = path.endsWith(\"/\") ? path + file : `${path}/${file}`;\n\t\t\t\t\t\t\t\tconst stat = await fs.stat(fullPath);\n\t\t\t\t\t\t\t\tconst entryHref = `${href.endsWith(\"/\") ? href : href + \"/\"}${file}`;\n\t\t\t\t\t\t\t\treturn renderEntry(entryHref, stat);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tresponses += fileResponses.join(\"\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresponses = await renderEntry(href, stats);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst xml = `\n\t\t\t\t\t\t<?xml version=\"1.0\"?>\n\t\t\t\t\t\t<a:multistatus xmlns:a=\"DAV:\" xmlns:b=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n\t\t\t\t\t\t\t${responses}\n\t\t\t\t\t\t</a:multistatus>\n\t\t\t\t\t`.trim();\n\n\t\t\t\t\treturn new Response(xml, {\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/xml\" },\n\t\t\t\t\t\tstatus: 207,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(path, err);\n\t\t\t\t\tconst xml = `\n\t\t\t\t\t<?xml version=\"1.0\"?>\n\t\t\t\t\t<a:multistatus xmlns:a=\"DAV:\">\n\t\t\t\t\t\t<a:response>\n\t\t\t\t\t\t\t<a:href>${url.pathname}</a:href>\n\t\t\t\t\t\t\t<a:status>HTTP/1.1 404 Not Found</a:status>\n\t\t\t\t\t\t</a:response>\n\t\t\t\t\t</a:multistatus>\n\t\t\t\t`.trim();\n\n\t\t\t\t\treturn new Response(xml, {\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/xml\" },\n\t\t\t\t\t\tstatus: 207, // multi-status\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase \"PROPPATCH\":\n\t\t\t\treturn new Response(null, { status: 207 }); // No-op\n\n\t\t\tcase \"MKCOL\":\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.mkdir(path);\n\t\t\t\t\treturn new Response(null, { status: 201 });\n\t\t\t\t} catch {\n\t\t\t\t\treturn new Response(null, { status: 405 });\n\t\t\t\t}\n\n\t\t\tcase \"GET\":\n\t\t\tcase \"HEAD\": {\n\t\t\t\ttry {\n\t\t\t\t\tconst data = await fs.readFile(path, \"arraybuffer\");\n\t\t\t\t\treturn new Response(method === \"HEAD\" ? null : new Blob([data]), {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\"Content-Type\": mime.default.getType(path) || \"application/octet-stream\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tstatus: 200,\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\treturn new Response(null, { status: 404 });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase \"PUT\": {\n\t\t\t\tconst buffer = await getBuffer();\n\t\t\t\ttry {\n\t\t\t\t\tconsole.log(buffer);\n\t\t\t\t\tawait fs.writeFile(path, Filer.Buffer.from(buffer));\n\t\t\t\t\treturn new Response(null, { status: 201 });\n\t\t\t\t} catch {\n\t\t\t\t\treturn new Response(null, { status: 500 });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase \"DELETE\":\n\t\t\t\ttry {\n\t\t\t\t\tawait shell.promises.rm(path, { recursive: true });\n\t\t\t\t\treturn new Response(null, { status: 204 });\n\t\t\t\t} catch {\n\t\t\t\t\treturn new Response(null, { status: 404 });\n\t\t\t\t}\n\n\t\t\tcase \"COPY\": {\n\t\t\t\t// This is technically invalid -- Copy should handle full folders as well but filer doesn't have a convinient way to do this :/\n\t\t\t\t// take this broken solution in the interim - Rafflesia\n\n\t\t\t\tconst dest = getDestPath();\n\t\t\t\ttry {\n\t\t\t\t\tawait shell.promises.cpr(path, dest);\n\t\t\t\t\treturn new Response(null, { status: 201 });\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.error(e);\n\t\t\t\t\treturn new Response(null, { status: 404 });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase \"MOVE\": {\n\t\t\t\tconst dest = getDestPath();\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.rename(path, dest);\n\t\t\t\t\treturn new Response(null, { status: 201 });\n\t\t\t\t} catch {\n\t\t\t\t\treturn new Response(null, { status: 500 });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase \"LOCK\":\n\t\t\tcase \"UNLOCK\": {\n\t\t\t\treturn new Response(`<?xml version=\"1.0\"?><d:prop xmlns:d=\"DAV:\"><d:lockdiscovery/></d:prop>`, {\n\t\t\t\t\tstatus: 200,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/xml\",\n\t\t\t\t\t\t\"Lock-Token\": `<opaquelocktoken:fake-lock-${Date.now()}>`,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcase \"POST\":\n\t\t\t\treturn new Response(\"POST not implemented\", { status: 204 });\n\n\t\t\tdefault:\n\t\t\t\treturn new Response(\"Unsupported WebDAV method\", {\n\t\t\t\t\tstatus: 405,\n\t\t\t\t});\n\t\t}\n\t} catch (err) {\n\t\treturn new Response(`Internal error: ${err.message}`, { status: 500 });\n\t}\n}\n\nfor (const method of supportedWebDAVMethods) {\n\tworkbox.routing.registerRoute(\n\t\t/\\/dav/,\n\t\tasync event => {\n\t\t\treturn await handleDavRequest(event);\n\t\t},\n\t\tmethod,\n\t);\n}\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n\nvar cacheenabled = false;\n\nconst callbacks = {};\nconst filepickerCallbacks = {};\n\naddEventListener(\"message\", event => {\n\tif (event.data.anura_target === \"anura.x86.proxy\") {\n\t\tconst callback = callbacks[event.data.id];\n\t\tcallback(event.data.value);\n\t}\n\tif (event.data.anura_target === \"anura.cache\") {\n\t\tcacheenabled = event.data.value;\n\t\tidbKeyval.set(\"cacheenabled\", event.data.value);\n\t}\n\tif (event.data.anura_target === \"anura.filepicker.result\") {\n\t\tconst callback = filepickerCallbacks[event.data.id];\n\t\tcallback(event.data.value);\n\t}\n\tif (event.data.anura_target === \"anura.comlink.init\") {\n\t\tself.swShared = Comlink.wrap(event.data.value);\n\t\tswShared.test.then(console.log);\n\t\tself.isConnected = swShared.test;\n\t}\n\tif (event.data.anura_target === \"anura.nohost.set\") {\n\t\tself.anurafs = swShared.anura.fs;\n\t\tself.anurash = swShared.sh;\n\t}\n});\n\nworkbox.routing.registerRoute(/\\/extension\\//, async ({ url }) => {\n\tconst { fs } = await currentFs();\n\tconsole.debug(\"Caught a aboutbrowser extension request\");\n\ttry {\n\t\treturn new Response(await fs.promises.readFile(url.pathname));\n\t} catch (e) {\n\t\treturn new Response(\"File not found bruh\", { status: 404 });\n\t}\n});\n\nworkbox.routing.registerRoute(\n\t/\\/showFilePicker/,\n\tasync ({ url }) => {\n\t\tconst id = crypto.randomUUID();\n\t\tconst clients = (await self.clients.matchAll()).filter(v => new URL(v.url).pathname === \"/\");\n\t\tif (clients.length < 1) return new Response(\"no clients were available to take your request\");\n\t\tconst client = clients[0];\n\n\t\tconst regex = url.searchParams.get(\"regex\") || \".*\";\n\t\tconst type = url.searchParams.get(\"type\") || \"file\";\n\n\t\tclient.postMessage({\n\t\t\tanura_target: \"anura.filepicker\",\n\t\t\tregex,\n\t\t\tid,\n\t\t\ttype,\n\t\t});\n\n\t\tconst resp = await new Promise(resolve => {\n\t\t\tfilepickerCallbacks[id] = resolve;\n\t\t});\n\n\t\treturn new Response(JSON.stringify(resp), {\n\t\t\tstatus: resp.cancelled ? 444 : 200,\n\t\t});\n\t},\n\t\"GET\",\n);\n\nasync function serveFile(path, fsOverride, shOverride) {\n\tlet fs;\n\tlet sh;\n\n\tif (fsOverride && shOverride) {\n\t\tfs = fsOverride;\n\t\tsh = shOverride;\n\t} else {\n\t\tconst { fs: fs_, sh: sh_ } = await currentFs();\n\t\tfs = fsOverride || fs_;\n\t\tsh = shOverride || sh_;\n\t}\n\n\tif (!fs) {\n\t\t// HOPEFULLY this will never happen,\n\t\t// as the filesystem should always have a backup\n\t\treturn new Response(\n\t\t\tJSON.stringify({\n\t\t\t\terror: \"No filesystem available.\",\n\t\t\t}),\n\t\t\t{\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t...corsheaders,\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\ttry {\n\t\tconst stats = await fs.promises.stat(path);\n\t\tif (stats.type === \"DIRECTORY\") {\n\t\t\t// Can't do withFileTypes because it is unserializable\n\t\t\tconst entries = await Promise.all((await fs.promises.readdir(path)).map(async e => await fs.promises.stat(`${path}/${e}`)));\n\t\t\tfunction page() {\n\t\t\t\treturn `<!DOCTYPE html>\n\t\t\t\t\t<html>\n\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t<link rel=\"stylesheet\" href=\"/assets/fs.ui/fs.css\">\n\t\t\t\t\t\t</head>\n\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t<div class=\"flex flex-col pt-6 gap-2.5 px-4\">\n\t\t\t\t\t\t\t\t<div class=\"flex items-center gap-2 w-max leading-none font-bold text-2xl light:text-[#000000de]\">\n\t\t\t\t\t\t\t\t\t<h1>Index of</h1>\n\t\t\t\t\t\t\t\t\t<div class=\"breadcrumbs flex gap-1\"></div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t<svg class=\"nav-back size-8 dark:text-[#ffffff88] text-[#00000088] duration-150 ${path !== \"/\" ? \"dark:hover:text-[#ffffffde] hover:text-[#000000] cursor-(--cursor-pointer)\" : \"cursor-(--cursor-normal)\"}\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n\t\t\t\t\t\t\t\t\t\t\t<path fill-rule=\"evenodd\" d=\"M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-4.28 9.22a.75.75 0 0 0 0 1.06l3 3a.75.75 0 1 0 1.06-1.06l-1.72-1.72h5.69a.75.75 0 0 0 0-1.5h-5.69l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3Z\" clip-rule=\"evenodd\" />\n\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t<svg class=\"nav-home size-8 dark:text-[#ffffff88] text-[#00000088] duration-150 ${path !== \"/\" ? \"dark:hover:text-[#ffffffde] hover:text-[#000000] cursor-(--cursor-pointer)\" : \"cursor-(--cursor-normal)\"}\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n\t\t\t\t\t\t\t\t\t\t\t<path d=\"M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689-8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.689Z\" />\n\t\t\t\t\t\t\t\t\t\t\t<path d=\"m12 5.432 8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198a2.29 2.29 0 0 0 .091-.086L12 5.432Z\" />\n\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<table class=\"w-max\">\n\t\t\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t\t\t<tr class=\"border-b dark:border-[#ffffff38] border-[#00000068]\">\n\t\t\t\t\t\t\t\t\t\t\t<th class=\"text-left p-1.5 pl-2.5\">Name</th>\n\t\t\t\t\t\t\t\t\t\t\t<th class=\"text-left p-1.5\">Type</th>\n\t\t\t\t\t\t\t\t\t\t\t<th class=\"text-left p-1.5\">Size</th>\n\t\t\t\t\t\t\t\t\t\t\t<th class=\"text-left p-1.5\">Last Modified</th>\n\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t${entries\n\t\t\t\t\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t\t\t\t\tentry => `\n\t\t\t\t\t\t\t\t\t\t\t\t<tr class=\"dark:hover:bg-[#ffffff15] hover:bg-[#00000020] duration-150 ease-in-out select-none cursor-(--cursor-pointer)\" ondblclick=\"window.location.href='/fs${path}/${entry.name}'\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<th class=\"flex text-left py-1.5 pl-2 pr-25 gap-2 select-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentry.type === \"DIRECTORY\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6 dark:text-[#ffffff48] text-[#00000068]\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: `\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6 dark:text-[#ffffff48] text-[#00000068]\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z\" clip-rule=\"evenodd\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t${entry.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"pl-2 pr-3.5 select-none\">${entry.type}</td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"pl-2 pr-3.5 select-none\">${\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tentry.type === \"DIRECTORY\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? \"<span class='dark:text-[#ffffff50] text-[#00000070]'>-</span>\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: entry.size > 1024 * 1024 * 1024\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `${(entry.size / (1024 * 1024 * 1024)).toFixed(2)} GB`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: entry.size > 1024 * 1024\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `${(entry.size / (1024 * 1024)).toFixed(2)} MB`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: entry.size > 1024\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `${(entry.size / 1024).toFixed(2)} KB`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: `${entry.size} bytes`\n\t\t\t\t\t\t\t\t\t\t\t\t\t}</td>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"pl-2 pr-3.5 select-none\">${new Date(entry.mtime).toLocaleString()}</td>\n\t\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t.join(\"\")}\n\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<script src=\"/assets/libs/tailwind.min.js\"></script>\n\t\t\t\t\t\t\t<script src=\"/assets/fs.ui/fs.js\"></script>\n\t\t\t\t\t\t</body>\n\t\t\t\t\t</html>\n\t\t\t\t`;\n\t\t\t}\n\t\t\treturn new Response(page(), {\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"text/html\",\n\t\t\t\t\t...corsheaders,\n\t\t\t\t},\n\t\t\t});\n\t\t\t/* Custom Terbium way lol\n\t\t\treturn new Response(JSON.stringify(entries), {\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t...corsheaders,\n\t\t\t\t},\n\t\t\t});\n\t\t\t*/\n\t\t}\n\t\tconst type = mime.default.getType(path) || \"application/octet-stream\";\n\n\t\treturn new Response(await fs.promises.readFile(path, \"arraybuffer\"), {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": type,\n\t\t\t\t\"Content-Disposition\": `inline; filename=\"${path.split(\"/\").pop()}\"`,\n\t\t\t\t...corsheaders,\n\t\t\t},\n\t\t});\n\t} catch (e) {\n\t\treturn new Response(JSON.stringify({ error: e.message, code: e.code, status: 404 }), {\n\t\t\tstatus: 404,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t...corsheaders,\n\t\t\t},\n\t\t});\n\t}\n}\n\nasync function updateFile(path, data) {\n\tconst { fs, sh } = await currentFs();\n\tswitch (data.action) {\n\t\tcase \"write\":\n\t\t\tawait sh.promises.mkdirp(path.replace(/[^/]*$/g, \"\"));\n\t\t\tawait fs.promises.writeFile(path, data.contents);\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tstatus: \"ok\",\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t...corsheaders,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\tcase \"delete\":\n\t\t\tawait sh.promises.rm(path, { recursive: true });\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tstatus: \"ok\",\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t...corsheaders,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\tcase \"touch\":\n\t\t\tawait sh.promises.touch(path);\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tstatus: \"ok\",\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t...corsheaders,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\tcase \"mkdir\":\n\t\t\tawait sh.promises.mkdirp(path);\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tstatus: \"ok\",\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t...corsheaders,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t}\n}\n\nconst fsRegex = /\\/fs(\\/.*)/;\n\nconst corsheaders = {\n\t\"Cross-Origin-Embedder-Policy\": \"require-corp\",\n\t\"Access-Control-Allow-Origin\": \"*\",\n\t\"Cross-Origin-Opener-Policy\": \"same-origin\",\n\t\"Cross-Origin-Resource-Policy\": \"same-site\",\n};\n\nworkbox.routing.registerRoute(\n\tfsRegex,\n\tasync ({ url }) => {\n\t\tlet path = url.pathname.match(fsRegex)[1];\n\t\tpath = decodeURI(path);\n\t\treturn serveFile(path);\n\t},\n\t\"GET\",\n);\n\nworkbox.routing.registerRoute(\n\tfsRegex,\n\tasync ({ url, request }) => {\n\t\tlet path = url.pathname.match(fsRegex)[1];\n\t\tconst action = request.headers.get(\"x-fs-action\") || url.searchParams.get(\"action\");\n\t\tif (!action) {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\terror: \"No action specified\",\n\t\t\t\t\tstatus: 400,\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t...corsheaders,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tpath = decodeURI(path);\n\t\tconst body = await request.arrayBuffer();\n\t\treturn updateFile(path, {\n\t\t\taction,\n\t\t\tcontents: Buffer.from(body),\n\t\t});\n\t},\n\t\"POST\",\n);\n\nworkbox.routing.registerRoute(/^(?!.*(\\/config.json|\\/MILESTONE|\\/x86images\\/|\\/service\\/))/, async ({ url, request }) => {\n\tif (cacheenabled === undefined) {\n\t\tconsole.debug(\"retrieving cache value\");\n\t\tconst result = await idbKeyval.get(\"cacheenabled\");\n\t\tif (result !== undefined || result !== null) {\n\t\t\tcacheenabled = result;\n\t\t}\n\t}\n\tif ((!cacheenabled && url.pathname === \"/\" && !navigator.onLine) || (!cacheenabled && url.pathname === \"/index.html\" && !navigator.onLine)) {\n\t\treturn new Response(offlineError(), {\n\t\t\tstatus: 500,\n\t\t\theaders: { \"content-type\": \"text/html\" },\n\t\t});\n\t}\n\tif (!cacheenabled) {\n\t\tconst fetchResponse = await fetch(request);\n\t\treturn new Response(await fetchResponse.arrayBuffer(), {\n\t\t\theaders: {\n\t\t\t\t...Object.fromEntries(fetchResponse.headers.entries()),\n\t\t\t\t...corsheaders,\n\t\t\t},\n\t\t});\n\n\t\treturn fetchResponse;\n\t}\n\tif (url.pathname === \"/\") {\n\t\turl.pathname = \"/index.html\";\n\t}\n\tif (url.password) return new Response(\"<script>window.location.href = window.location.href</script>\", { headers: { \"content-type\": \"text/html\" } });\n\tconst basepath = \"/anura_files\";\n\tconst path = decodeURI(url.pathname);\n\n\t// Force Filer to be used in cache routes, as it does not require waiting for anura to be connected\n\tconst fs = self.opfs || filerfs;\n\tconst sh = self.opfssh || filersh;\n\n\t// Terbium already has its own way for caching files to the file system so doing it again is just a waste of space\n\t/*\n\t\tconst response = await serveFile(`${basepath}${path}`, fs, sh);\n\n\t\tif (response.ok) {\n\t\t\treturn response;\n\t\t} else {\n\t\t*/\n\ttry {\n\t\tconst fetchResponse = await fetch(request);\n\t\t// Promise so that we can return the response before we cache it, for faster response times\n\t\treturn new Promise(async resolve => {\n\t\t\tconst corsResponse = new Response(await fetchResponse.clone().arrayBuffer(), {\n\t\t\t\theaders: {\n\t\t\t\t\t...Object.fromEntries(fetchResponse.headers.entries()),\n\t\t\t\t\t...corsheaders,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tresolve(corsResponse);\n\n\t\t\t/*\n\t\t\t\t\tif (fetchResponse.ok) {\n\t\t\t\t\t\tconst buffer = await fetchResponse.clone().arrayBuffer();\n\t\t\t\t\t\tawait sh.promises.mkdirp(\n\t\t\t\t\t\t\t`${basepath}${path.replace(/[^/]*$/g, \"\")}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// Explicitly use Filer's fs here, as\n\t\t\t\t\t\t// Buffers lose their inheritance when passed\n\t\t\t\t\t\t// to anura's fs, causing them to be treated as\n\t\t\t\t\t\t// strings\n\t\t\t\t\t\tawait fs.promises.writeFile(\n\t\t\t\t\t\t\t`${basepath}${path}`,\n\t\t\t\t\t\t\tBuffer.from(buffer),\n\t\t\t\t\t\t);\n\t\t\t\t\t}*/\n\t\t}).catch(e => {\n\t\t\tconsole.error(\"I hate this bug: \", e);\n\t\t});\n\t} catch (e) {\n\t\treturn new Response(\n\t\t\tJSON.stringify({\n\t\t\t\terror: e.message,\n\t\t\t\tstatus: 500,\n\t\t\t}),\n\t\t\t{\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t...corsheaders,\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n});\n\nimportScripts(\"/uv/uv.bundle.js\");\nimportScripts(\"/uv/uv.config.js\");\nimportScripts(\"/uv/uv.sw.js\");\nimportScripts(\"/scram/scramjet.all.js\");\n\nconst { ScramjetServiceWorker } = $scramjetLoadWorker();\nconst scramjet = new ScramjetServiceWorker();\nconst uv = new UVServiceWorker();\n\nconst methods = [\"GET\", \"POST\", \"HEAD\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"];\n\nfunction sanitizeDownloadFilename(filename) {\n\tconst sanitized = String(filename || \"\")\n\t\t.replace(/[\\\\/:*?\"<>|]/g, \"_\")\n\t\t.trim();\n\treturn sanitized || `download-${Date.now()}`;\n}\n\nfunction splitFilename(name) {\n\tconst lastDot = name.lastIndexOf(\".\");\n\tif (lastDot <= 0 || lastDot === name.length - 1) {\n\t\treturn { base: name, ext: \"\" };\n\t}\n\treturn {\n\t\tbase: name.slice(0, lastDot),\n\t\text: name.slice(lastDot),\n\t};\n}\n\nasync function getUniqueDownloadPath(fsPromises, dirPath, filename) {\n\tconst { base, ext } = splitFilename(filename);\n\tlet attempt = 0;\n\twhile (true) {\n\t\tconst candidate = `${dirPath}/${attempt === 0 ? `${base}${ext}` : `${base} (${attempt})${ext}`}`;\n\t\ttry {\n\t\t\tawait fsPromises.stat(candidate);\n\t\t\tattempt += 1;\n\t\t} catch {\n\t\t\treturn candidate;\n\t\t}\n\t}\n}\n\nasync function saveTO(initialFilename) {\n\tconst id = crypto.randomUUID();\n\tconst clients = (await self.clients.matchAll({ type: \"window\", includeUncontrolled: true })).filter(v => new URL(v.url).pathname === \"/\");\n\tif (clients.length < 1) return null;\n\tconst client = clients[0];\n\tclient.postMessage({\n\t\tanura_target: \"anura.filepicker\",\n\t\tregex: sanitizeDownloadFilename(initialFilename || \"download.bin\"),\n\t\tid,\n\t\ttype: \"folder\",\n\t});\n\tconst resp = await new Promise(resolve => {\n\t\tfilepickerCallbacks[id] = resolve;\n\t});\n\tdelete filepickerCallbacks[id];\n\tif (!resp || resp.cancelled) return null;\n\tconst folder = Array.isArray(resp.folders) ? resp.folders[0] : null;\n\tif (!folder || typeof folder !== \"string\") return null;\n\treturn folder.replace(/\\/$/, \"\") || \"/\";\n}\n\nasync function saveFP(response, request, proxyName) {\n\ttry {\n\t\tconst { fs } = await currentFs();\n\t\tconst downloadHelper = window.DownloadHandler;\n\t\tif (!downloadHelper || !response || !request) return response;\n\t\tif (response.status >= 300 && response.status < 400) return response;\n\t\tif (!downloadHelper.isDownload(response.headers, request.destination || \"\")) return response;\n\t\tconst contentDisposition = response.headers.get(\"content-disposition\");\n\t\tconst parsedName = downloadHelper.parseDownloadFilename(contentDisposition, request.url || response.url);\n\t\tconst filename = sanitizeDownloadFilename(parsedName || \"download.bin\");\n\t\tconst selectedDir = await saveTO(filename);\n\t\tconst path = await getUniqueDownloadPath(fs.promises, selectedDir, filename);\n\t\tconst buffer = await response.clone().arrayBuffer();\n\t\tawait fs.promises.writeFile(path, Buffer.from(buffer));\n\t\tconsole.info(`[${proxyName}] Download saved to ${path}`);\n\t\treturn new Response(null, {\n\t\t\tstatus: 204,\n\t\t\tstatusText: \"No Content\",\n\t\t});\n\t} catch (error) {\n\t\tconsole.error(`[${proxyName}] Failed to save download`, error);\n\t\treturn response;\n\t}\n}\n\nmethods.forEach(method => {\n\tworkbox.routing.registerRoute(\n\t\t/\\/uv\\/service\\//,\n\t\tasync event => {\n\t\t\tconsole.debug(\"Got UV req\");\n\t\t\tuv.on(\"request\", event => {\n\t\t\t\tevent.data.headers[\"user-agent\"] = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0 Safari/537.36 Terbium-Browser/2.3.0\";\n\t\t\t});\n\t\t\treturn await uv.fetch(event);\n\t\t},\n\t\tmethod,\n\t);\n});\n\n// Route w-corp-staticblitz.com and subdomains through BareMux, so that the Node.js subsystem doesn't get blocked by filters\nmethods.forEach(method => {\n\tworkbox.routing.registerRoute(\n\t\t({ url }) => {\n\t\t\treturn url.hostname === \"w-corp-staticblitz.com\" || url.hostname.endsWith(\".w-corp-staticblitz.com\");\n\t\t},\n\t\tasync event => {\n\t\t\ttry {\n\t\t\t\t// Clone the request\n\t\t\t\tconst bareRequest = new Request(event.url.href, {\n\t\t\t\t\tmethod: event.request.method,\n\t\t\t\t\theaders: event.request.headers,\n\t\t\t\t\tbody: event.request.body,\n\t\t\t\t\tmode: event.request.mode,\n\t\t\t\t\tcredentials: event.request.credentials,\n\t\t\t\t\tcache: event.request.cache,\n\t\t\t\t\tredirect: event.request.redirect,\n\t\t\t\t\treferrer: event.request.referrer,\n\t\t\t\t\tintegrity: event.request.integrity,\n\t\t\t\t});\n\n\t\t\t\treturn await bareClient.fetch(bareRequest);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"BareMux *.w-corp-staticblitz.com proxy fetch failed\", error);\n\t\t\t\treturn new Response(\"Failed to fetch through BareMux\", {\n\t\t\t\t\tstatus: 500,\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tmethod,\n\t);\n});\n\nscramjet.loadConfig();\n\nmethods.forEach(method => {\n\tworkbox.routing.registerRoute(\n\t\t/\\/service\\//,\n\t\tasync ({ event }) => {\n\t\t\tconsole.log(\"Got SJ req\");\n\t\t\tawait scramjet.loadConfig();\n\t\t\tif (scramjet.route(event)) {\n\t\t\t\tconst response = await scramjet.fetch(event);\n\t\t\t\treturn await saveFP(response, event.request, \"Scramjet\");\n\t\t\t}\n\t\t\treturn fetch(event.request);\n\t\t},\n\t\tmethod,\n\t);\n});\n\n// have to put this here because no cache\nfunction offlineError() {\n\treturn `<!DOCTYPE html>\n            <html>\n            <head>\n            <style>\n            body {\n                font-family: \"Roboto\", RobotoDraft, \"Droid Sans\", Arial, Helvetica, -apple-system, BlinkMacSystemFont, system-ui, sans-serif;\n                text-align: center;\n                background: black;\n                color: white;\n                overflow: none;\n                margin: 0;\n            }\n            #wrapper {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n            }\n            </style>\n            </head>\n            <body>\n            <div id=\"wrapper\">\n            <h1>Offline Error</h1>\n            <p>Try refreshing the page if you are connected to the internet</p>\n            </div>\n            </body>\n            </html>\n            `;\n}\n\nasync function initSw() {\n\tfor (const client of await self.clients.matchAll()) {\n\t\tclient.postMessage({\n\t\t\tanura_target: \"anura.sw.reinit\",\n\t\t});\n\t}\n}\ninitSw();\n"
  },
  {
    "path": "public/apps/about.tapp/app.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(/fonts/Inter.ttf);\n}\n\nh1 {\n\tfont-family: Inter;\n\tfont-weight: 700;\n}\n\nh4 {\n\tmargin-top: 4px;\n\tmargin-bottom: 4px;\n}\n\nhtml,\nbody {\n\theight: 100%;\n\twidth: 100%;\n\tmargin: 0;\n\tcolor: #ffffff;\n\tfont-family: Inter;\n\tposition: relative;\n\toverflow: hidden;\n}\n\nbody {\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: start;\n\talign-items: start;\n\tpadding: 20px;\n\twidth: calc(100% - 20px);\n\theight: calc(100% - 14px);\n\tpadding-top: 4px;\n}\n\n.centered-image {\n\twidth: 180px;\n\tmargin-left: -34px;\n\tmargin-top: -14px;\n}\n\na {\n\tcolor: #5088ff;\n\ttext-decoration: none;\n}\n\na:hover {\n\ttext-decoration: underline;\n}\n"
  },
  {
    "path": "public/apps/about.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>About Terbium</title>\n    <link rel=\"stylesheet\" href=\"./app.css\">\n</head>\n    <body>\n        <img src=\"/assets/img/logo.png\" alt=\"TB Logo\" class=\"centered-image\">\n        <h4 class=\"centered-text\">Terbium WebOS</h4>\n        <h4 class=\"centered-text\" id=\"version\"></h4>\n        <h4 class=\"centered-text\" id=\"liquor\"></h4>\n        <h4 class=\"centered-text\">Developers: SNOOT, XSTARS, IllusionTBA, Rafflesia, EndlessVortex, ironswordX</h4>\n        <h4 class=\"centered-text\">&copy; Copyright 2026 TerbiumOS</h4>\n        <h4 class=\"centered-text\">Licensed under the <a href=\"https://www.gnu.org/licenses/agpl-3.0.en.html\" target=\"_blank\">AGPL 3.0 License</a></h4>\n    </body>\n    <script>\n        document.all.liquor.innerText = \"Liquor Version: \" + parent.anura.version.pretty\n        document.all.version.innerText = `Version: ${parent.tb.system.version()}`\n    </script>\n</html>\n"
  },
  {
    "path": "public/apps/about.tapp/index.json",
    "content": "{\n\t\"name\": \"About\",\n\t\"config\": {\n\t\t\"title\": \"About\",\n\t\t\"icon\": \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/about.tapp/index.html\"\n\t}\n}\n"
  },
  {
    "path": "public/apps/app store.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Terbium App Store</title>\n    <style>\n        @font-face {\n\t        font-family: Inter;\n\t        src: url(\"/fonts/Inter.ttf\");\n        }\n\n        html, body {\n            height: 100%;\n        }\n\n        body {\n            color: #fff;\n            font-family: Inter;\n            font-weight: bold;\n        }\n\n        ::-webkit-scrollbar {\n\t        width: 8px;\n\t        height: 100%;\n        }\n\n        ::-webkit-scrollbar-thumb {\n\t        background-color: #ffffff28;\n\t        border-radius: 8px;\n        }\n\n        ::-webkit-scrollbar-track {\n\t        background-color: #ffffff10;\n\t        border-radius: 8px;\n        }\n    </style>\n    <script src=\"/assets/libs/tailwind.min.js\"></script>\n    <script src=\"./index.js\"></script>\n</head>\n    <body>\n        <div class=\"fixed top-[5px] left-[5px] h-[calc(100vh-20px)] w-[200px] bg-[#00000022] rounded-lg flex flex-col gap-[7px] p-2\">\n            <div class=\"flex flex-col h-full\">\n                <div class=\"flex flex-col gap-[7px] flex-1 repo-list overflow-y-auto\"></div>\n                <div class=\"repo-card flex flex-row items-center bg-[#00000032] rounded-lg h-[50px] p-1 gap-1 mt-auto\" onClick=\"addRepo()\">\n                    <h3 class=\"text-white text-base font-black\">Add Repo</h3>\n                </div>\n            </div>\n        </div>\n        <div class=\"main flex flex-col items-center absolute left-[215px] top-0 right-0 min-h-screen gap-1\">\n            <h1 class=\"font-black text-3xl\">Featured App of the Day</h1>\n            <div class=\"featured flex w-[97%] bg-[#00000032] rounded-[22px] p-2 items-center gap-6 relative bg-no-repeat bg-center bg-cover\" style=\"height:175px;\">\n                <div class=\"info flex flex-col justify-end text-white absolute left-6 bottom-6\">\n                    <h3 class=\"text-2xl font-bold mb-2\">Loading...</h3>\n                    <h4 class=\"text-[16px] mb-1 text-[#ffffff75]\">Loading...</h4>\n                    <h4 class=\"text-[16px] text-[#ffffff75]\">Loading...</h4>\n                </div>\n            </div>\n            <div class=\"flex flex-row justify-center items-center w-full my-4\">\n                <div class=\"app-togg bg-[#5DD88122] text-white font-black p-1 px-6 rounded-[22px] mr-2\" onclick=\"view('apps')\">Apps</div>\n                <div class=\"pwa-togg bg-[#00000022] text-white font-black p-1 px-6 rounded-[22px] ml-2\" onclick=\"view('pwa')\">PWAs</div>\n            </div>\n            <div class=\"apps-list flex-col all-apps grid grid-cols-4 gap-1.5 w-full h-full ml-[-10px] overflow-y-auto\">\n            </div>\n            <div class=\"pwa-list hidden flex-col all-apps grid-cols-4 gap-1.5 w-full h-full ml-[-10px] overflow-y-auto\">\n            </div>\n        </div>\n        <div class=\"app-prev hidden flex-col absolute left-[215px] top-0 right-0 h-full gap-1 overflow-y-auto\">\n        </div>\n    </body>\n</html>\n"
  },
  {
    "path": "public/apps/app store.tapp/index.js",
    "content": "let currRepo;\nlet viewType = \"apps\";\n/**\n * Loads the repos content\n * @param {string} url\n */\nasync function loadRepo(url) {\n\tconst repo = await window.parent.tb.libcurl.fetch(url);\n\tlet data = await repo.json();\n\tlet type = \"Terbium\";\n\tif (data.maintainer) {\n\t\ttype = \"Anura\";\n\t\tconst list = await window.parent.tb.libcurl.fetch(url.replace(\"manifest.json\", \"list.json\"));\n\t\tcurrRepo = {\n\t\t\tname: data.name,\n\t\t\turl: url,\n\t\t};\n\t\tdata = await list.json();\n\t} else if (data.title) {\n\t\ttype = \"Xen\";\n\t} else {\n\t\tcurrRepo = data.repo.name;\n\t}\n\tdocument.querySelector(\".app-prev\").classList.remove(\"flex\");\n\tdocument.querySelector(\".app-prev\").classList.add(\"hidden\");\n\tdocument.querySelector(\".main\").classList.remove(\"hidden\");\n\tdocument.querySelector(\".main\").classList.add(\"flex\");\n\tconst featured = document.querySelector(\".featured\");\n\tswitch (type) {\n\t\tcase \"Terbium\":\n\t\t\tconst featuredList1 = data.apps;\n\t\t\tconst randomIndex1 = Math.floor(Math.random() * featuredList1.length);\n\t\t\tdata.featured = featuredList1[randomIndex1] || {};\n\t\t\tconst icn1 = await window.parent.tb.libcurl.fetch(data.featured.icon);\n\t\t\tconst blob1 = await icn1.blob();\n\t\t\tconst icnurl1 = URL.createObjectURL(blob1);\n\t\t\tfeatured.classList.forEach(cls => {\n\t\t\t\tif (cls.startsWith(\"bg-[url\")) {\n\t\t\t\t\tfeatured.classList.remove(cls);\n\t\t\t\t}\n\t\t\t});\n\t\t\tfeatured.classList.add(`bg-[url('${icnurl1 || \"/tb.svg\"}')]`);\n\t\t\tfeatured.onclick = () => {\n\t\t\t\tloadApp(data.featured, type);\n\t\t\t};\n\t\t\tfeatured.querySelector(\"h3\").textContent = data.featured.name;\n\t\t\tif (data.featured.version) {\n\t\t\t\tfeatured.querySelector(\"h4:nth-child(2)\").textContent = `Version ${data.featured.version}`;\n\t\t\t} else {\n\t\t\t\tfeatured.querySelector(\"h4:nth-child(2)\").textContent = `Progressive Web App`;\n\t\t\t}\n\t\t\tfeatured.querySelector(\"h4:nth-child(3)\").textContent = `By ${data.featured.developer || \"Unknown\"}`;\n\t\t\tconst appCards1 = await Promise.all(\n\t\t\t\tdata.apps.map(async app => {\n\t\t\t\t\tconst icn1 = await window.parent.tb.libcurl.fetch(app.icon);\n\t\t\t\t\tconst blob1 = await icn1.blob();\n\t\t\t\t\tconst icnurl1 = URL.createObjectURL(blob1);\n\t\t\t\t\tconst displayName = app.name && app.name.length > 10 ? app.name.slice(0, 10) + \"...\" : app.name || \"Unknown\";\n\t\t\t\t\tconst cardHtml = `\n\t\t\t\t\t\t<div class=\"app-card w-[100%] h-[105px] bg-[#00000032] rounded-[12px] flex flex-col items-center justify-center\" data-app-index=\"${data.apps.indexOf(app)}\">\n\t\t\t\t\t\t\t<img src=\"${icnurl1 || \"/tb.svg\"}\" alt=\"App Icon\" class=\"w-[50px] h-[50px] rounded-[12px] mb-4 object-cover\" />\n\t\t\t\t\t\t\t<span class=\"text-white text-lg font-bold\">${displayName}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t`;\n\t\t\t\t\treturn { html: cardHtml, hasWmArgs: !!app.wmArgs };\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst pwaCards = appCards1.filter(card => card.hasWmArgs).map(card => card.html);\n\t\t\tconst appCards = appCards1.filter(card => !card.hasWmArgs).map(card => card.html);\n\t\t\tif (pwaCards.length === 0) {\n\t\t\t\tpwaCards.push(`<h1 class=\"text-white text-xl font-bold w-full text-center\">There are no PWA apps available in this repo</h1>`);\n\t\t\t}\n\t\t\tif (appCards.length === 0) {\n\t\t\t\tappCards.push(`<h1 class=\"text-white text-xl font-bold w-full text-center\">There are no app card apps available in this repo</h1>`);\n\t\t\t}\n\t\t\tdocument.querySelector(\".pwa-list\").innerHTML = pwaCards.join(\"\");\n\t\t\tdocument.querySelector(\".apps-list\").innerHTML = appCards.join(\"\");\n\t\t\tdocument.querySelectorAll(\".app-card\").forEach(card => {\n\t\t\t\tcard.addEventListener(\"click\", function () {\n\t\t\t\t\tconst idx = parseInt(this.getAttribute(\"data-app-index\"), 10);\n\t\t\t\t\tloadApp(data.apps[idx], type);\n\t\t\t\t});\n\t\t\t});\n\t\t\tbreak;\n\t\tcase \"Anura\":\n\t\t\tconst featuredList2 = data.apps;\n\t\t\tconst randomIndex2 = Math.floor(Math.random() * featuredList2.length);\n\t\t\tdata.featured = featuredList2[randomIndex2] || {};\n\t\t\tconst icn2 = await window.parent.tb.libcurl.fetch(`${url.replace(\"manifest.json\", \"\")}/apps/${data.featured.package}/${data.featured.icon}`);\n\t\t\tconst blob2 = await icn2.blob();\n\t\t\tconst icnurl2 = URL.createObjectURL(blob2);\n\t\t\tfeatured.classList.forEach(cls => {\n\t\t\t\tif (cls.startsWith(\"bg-[url\")) {\n\t\t\t\t\tfeatured.classList.remove(cls);\n\t\t\t\t}\n\t\t\t});\n\t\t\tfeatured.classList.add(`bg-[url('${icnurl2 || \"/tb.svg\"}')]`);\n\t\t\tfeatured.onclick = () => {\n\t\t\t\tloadApp(data.featured, type);\n\t\t\t};\n\t\t\tfeatured.querySelector(\"h3\").textContent = data.featured.name;\n\t\t\tif (data.featured.version) {\n\t\t\t\tfeatured.querySelector(\"h4:nth-child(2)\").textContent = `Version ${data.featured.version}`;\n\t\t\t} else {\n\t\t\t\tfeatured.querySelector(\"h4:nth-child(2)\").textContent = `Progressive Web App`;\n\t\t\t}\n\t\t\tfeatured.querySelector(\"h4:nth-child(3)\").textContent = `Anura Application`;\n\t\t\tconst appCards2 = await Promise.all(\n\t\t\t\tdata.apps.map(async app => {\n\t\t\t\t\tconst icn1 = await window.parent.tb.libcurl.fetch(`${url.replace(\"manifest.json\", \"\")}/apps/${app.package}/${app.icon}`);\n\t\t\t\t\tconst blob1 = await icn1.blob();\n\t\t\t\t\tconst icnurl1 = URL.createObjectURL(blob1);\n\t\t\t\t\tconst displayName = app.name && app.name.length > 10 ? app.name.slice(0, 10) + \"...\" : app.name || \"Unknown\";\n\t\t\t\t\tconst cardHtml = `\n\t\t\t\t\t\t<div class=\"app-card w-[100%] h-[105px] bg-[#00000032] rounded-[12px] flex flex-col items-center justify-center\" data-app-index=\"${data.apps.indexOf(app)}\">\n\t\t\t\t\t\t\t<img src=\"${icnurl1 || \"/tb.svg\"}\" alt=\"App Icon\" class=\"w-[50px] h-[50px] rounded-[12px] mb-4 object-cover\" />\n\t\t\t\t\t\t\t<span class=\"text-white text-lg font-bold\">${displayName}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t`;\n\t\t\t\t\treturn { html: cardHtml, hasWmArgs: !!app.wmArgs };\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst pwaCards2 = appCards2.filter(card => card.hasWmArgs).map(card => card.html);\n\t\t\tconst appCards_2 = appCards2.filter(card => !card.hasWmArgs).map(card => card.html);\n\t\t\tif (pwaCards2.length === 0) {\n\t\t\t\tpwaCards2.push(`<h1 class=\"text-white text-xl font-bold w-full text-center\">There are no PWA apps available in this repo</h1>`);\n\t\t\t}\n\t\t\tif (appCards_2.length === 0) {\n\t\t\t\tappCards_2.push(`<h1 class=\"text-white text-xl font-bold w-full text-center\">There are no app card apps available in this repo</h1>`);\n\t\t\t}\n\t\t\tdocument.querySelector(\".pwa-list\").innerHTML = pwaCards2.join(\"\");\n\t\t\tdocument.querySelector(\".apps-list\").innerHTML = appCards_2.join(\"\");\n\t\t\tdocument.querySelectorAll(\".app-card\").forEach(card => {\n\t\t\t\tcard.addEventListener(\"click\", function () {\n\t\t\t\t\tconst idx = parseInt(this.getAttribute(\"data-app-index\"), 10);\n\t\t\t\t\tloadApp(data.apps[idx], type);\n\t\t\t\t});\n\t\t\t});\n\t\t\tbreak;\n\t\tcase \"Xen\":\n\t\t\tconsole.log(\"Xen repo not implemented yet.\");\n\t\t\tdocument.querySelector(\".featured h3\").textContent = \"Xen App Store\";\n\t\t\tbreak;\n\t}\n}\n\n/**\n * Loads the app content in the preview\n * @param {Object} app - The app to load\n * @param {string} type - The type of app (Terbium, Anura, Xen)\n */\nasync function loadApp(app, type) {\n\tdocument.querySelector(\".app-prev\").classList.remove(\"hidden\");\n\tdocument.querySelector(\".app-prev\").classList.add(\"flex\");\n\tdocument.querySelector(\".main\").classList.remove(\"flex\");\n\tdocument.querySelector(\".main\").classList.add(\"hidden\");\n\tlet icnUrl;\n\tlet isInstalled = false;\n\tlet uptodate = true;\n\tconst installedApps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\tif (installedApps.some(a => a.name === app.name)) {\n\t\tisInstalled = true;\n\t\tconst config = JSON.parse(await window.parent.tb.fs.promises.readFile(`${installedApps.find(a => a.name === app.name).config}`, \"utf8\"));\n\t\tif (app.version && config.version && semverCompare(app.version, config.version) > 0) {\n\t\t\tuptodate = false;\n\t\t}\n\t}\n\tif (app.wmArgs) {\n\t\ttype = \"tb-PWA\";\n\t} else if (\"anura-pkg\" in app) {\n\t\ttype = \"tb-liq\";\n\t}\n\tswitch (type) {\n\t\tcase \"Terbium\":\n\t\tcase \"tb-PWA\":\n\t\t\tconst icn1 = await window.parent.tb.libcurl.fetch(app.icon);\n\t\t\tconst blob1 = await icn1.blob();\n\t\t\ticnUrl = URL.createObjectURL(blob1);\n\t\t\tbreak;\n\t\tcase \"Anura\":\n\t\tcase \"tb-liq\":\n\t\t\tlet icn2;\n\t\t\tif (currRepo.url) {\n\t\t\t\ticn2 = await window.parent.tb.libcurl.fetch(`${currRepo.url.replace(\"manifest.json\", \"\")}/apps/${app.package}/${app.icon}`);\n\t\t\t} else {\n\t\t\t\ticn2 = await window.parent.tb.libcurl.fetch(app.icon);\n\t\t\t}\n\t\t\tif (!icn2.ok) {\n\t\t\t\ticn2 = await window.parent.tb.libcurl.fetch(\"https://terbiumon.top/favicon.ico\");\n\t\t\t}\n\t\t\tconst blob2 = await icn2.blob();\n\t\t\ticnUrl = URL.createObjectURL(blob2);\n\t\t\tbreak;\n\t\tcase \"Xen\":\n\t\t\tbreak;\n\t}\n\tdocument.querySelector(\".app-prev\").innerHTML = `\n\t\t<h1 class=\"font-black text-3xl\" onclick=\"document.querySelector('.app-prev').classList.remove('flex'); document.querySelector('.app-prev').classList.add('hidden'); document.querySelector('.main').classList.remove('hidden'); document.querySelector('.main').classList.add('flex');\">${typeof currRepo === \"object\" ? currRepo.name : currRepo} → ${app.name}</h1>\n\t\t<div class=\"featured flex w-[97%] h-[175px] bg-[#00000032] rounded-[22px] p-2 items-center gap-6 relative bg-no-repeat bg-[url('${icnUrl || \"/tb.svg\"}')] bg-center bg-cover\">\n\t\t\t<div class=\"info flex flex-col justify-end text-white absolute left-6 bottom-6\">\n\t\t\t\t<h3 class=\"text-2xl font-bold mb-2\">${app.name}</h3>\n\t\t\t\t<h4 class=\"text-[16px] text-[#ffffff75]\">By ${app.developer || \"Unknown\"}</h4>\n\t\t\t</div>\n\t\t\t<div class=\"info flex flex-col justify-end text-white absolute right-6 bottom-6\">\n\t\t\t\t${isInstalled ? (uptodate ? `<button class=\"uns-btn bg-[#4d4d4d] text-white rounded-lg p-1.5\">Uninstall</button>` : `<button class=\"upd-btn bg-[#5DD881] text-black rounded-lg p-1.5\">Update</button>`) : `<button class=\"ins-btn bg-[#5DD881] text-black rounded-lg p-1.5\">Install</button>`}\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"flex w-[97%] h-[45%] mt-6 gap-6\">\n\t\t\t<div class=\"w-1/2 bg-[#00000032] rounded-[12px] h-full p-4 overflow-auto\">\n\t\t\t\t<h2 class=\"font-black text-3xl mb-2\">About ${app.name}</h2>\n\t\t\t\t<p class=\"text-white text-base\">${app.description || \"No description available.\"}</p>\n\t\t\t\t<ul class=\"text-white text-base\">\n\t\t\t\t\t<li><strong>Version:</strong> ${app.version || \"1.0.0\"}</li>\n\t\t\t\t\t<li><strong>Developer:</strong> ${app.developer || \"Unknown\"}</li>\n\t\t\t\t\t<li><strong>License:</strong> ${app.license || \"N/A\"}</li>\n\t\t\t\t\t<li><strong>Scanned:</strong> ${app.scanned ? `<a href=\"${app.scanned}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-[#ffffff]\">${new URL(app.scanned).hostname.replace(/^www\\./, \"\")}</a>` : \"N/A\"}</li>\n\t\t\t\t\t<li><strong>Size:</strong> ${app.size || \"N/A\"}</li>\n\t\t\t\t\t<ul>\n\t\t\t\t\t\t<h2 class=\"font-black text-3xl mb-2\">Requirements:</h2>\n\t\t\t\t\t\t<li><strong>OS: ${(app.requirements && app.requirements.os) || \"Any\"}</strong></li>\n\t\t\t\t\t\t<li><strong>Proxy: ${(app.requirements && app.requirements.proxy) || \"Any\"}</strong></li>\n\t\t\t\t\t</ul>\n\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"w-1/2 bg-[#00000032] rounded-[12px] h-full p-4 overflow-auto\">\n\t\t\t\t\t<h2 class=\"font-black text-3xl mb-2\">Images</h2>\n\t\t\t\t\t<div class=\"flex flex-wrap gap-4\">\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tapp.images\n\t\t\t\t\t\t\t\t? app.images\n\t\t\t\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t\t\t\timg => `\n\t\t\t\t\t\t\t<div class=\"w-full\">\n\t\t\t\t\t\t\t\t<img src=\"${img}\" alt=\"App Image\" class=\"w-full h-[200px] rounded-[12px] object-cover\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t`,\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t.join(\"\")\n\t\t\t\t\t\t\t\t: \"<p class='text-white'>No images available.</p>\"\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t`;\n\n\tconst addBtns = () => {\n\t\tconst insBtn = document.querySelector(\".ins-btn\");\n\t\tconst updBtn = document.querySelector(\".upd-btn\");\n\t\tconst unsBtn = document.querySelector(\".uns-btn\");\n\t\tif (insBtn) {\n\t\t\tinsBtn.addEventListener(\"click\", async function handler() {\n\t\t\t\tinsBtn.disabled = true;\n\t\t\t\tinsBtn.textContent = \"Installing...\";\n\t\t\t\tinsBtn.classList.remove(\"bg-[#5DD881]\", \"text-black\");\n\t\t\t\tinsBtn.classList.add(\"bg-[#4d4d4d]\", \"text-white\");\n\t\t\t\tconst success = await install(app, type);\n\t\t\t\tif (success) {\n\t\t\t\t\tinsBtn.outerHTML = `<button class=\"uns-btn bg-[#4d4d4d] text-white rounded-lg p-1.5\">Uninstall</button>`;\n\t\t\t\t\taddBtns();\n\t\t\t\t} else {\n\t\t\t\t\tinsBtn.disabled = false;\n\t\t\t\t\tinsBtn.textContent = \"Install\";\n\t\t\t\t\tinsBtn.classList.remove(\"bg-[#4d4d4d]\", \"text-white\");\n\t\t\t\t\tinsBtn.classList.add(\"bg-[#5DD881]\", \"text-black\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tif (updBtn) {\n\t\t\tupdBtn.addEventListener(\"click\", async function handler() {\n\t\t\t\tupdBtn.disabled = true;\n\t\t\t\tupdBtn.textContent = \"Updating...\";\n\t\t\t\tupdBtn.classList.remove(\"bg-[#5DD881]\", \"text-black\");\n\t\t\t\tupdBtn.classList.add(\"bg-[#4d4d4d]\", \"text-white\");\n\t\t\t\tawait uninstall(app, type);\n\t\t\t\tconst success = await install(app, type);\n\t\t\t\tif (success) {\n\t\t\t\t\tupdBtn.outerHTML = `<button class=\"uns-btn bg-[#4d4d4d] text-white rounded-lg p-1.5\">Uninstall</button>`;\n\t\t\t\t\taddBtns();\n\t\t\t\t} else {\n\t\t\t\t\tupdBtn.disabled = false;\n\t\t\t\t\tupdBtn.textContent = \"Update\";\n\t\t\t\t\tupdBtn.classList.remove(\"bg-[#4d4d4d]\", \"text-white\");\n\t\t\t\t\tupdBtn.classList.add(\"bg-[#5DD881]\", \"text-black\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tif (unsBtn) {\n\t\t\tunsBtn.addEventListener(\"click\", async function handler() {\n\t\t\t\tawait uninstall(app, type);\n\t\t\t\tunsBtn.outerHTML = `<button class=\"ins-btn bg-[#5DD881] text-black rounded-lg p-1.5\">Install</button>`;\n\t\t\t\taddBtns();\n\t\t\t});\n\t\t}\n\t};\n\n\taddBtns();\n}\n\n/**\n * Loads the current list of repos\n */\nasync function loadRepos() {\n\tconst repoList = document.querySelector(\".repo-list\");\n\trepoList.innerHTML = \"\";\n\tconst repos = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, \"utf8\"));\n\tfor (const repo of repos) {\n\t\tconst repoinfo = await window.parent.tb.libcurl.fetch(repo.url);\n\t\tif (!repoinfo.ok) {\n\t\t\tconst displayName = repo.name && repo.name.length > 8 ? repo.name.slice(0, 8) + \"...\" : repo.name || \"Unknown\";\n\t\t\tconst repoCard = document.createElement(\"div\");\n\t\t\trepoCard.className = \"repo-card flex flex-row items-center bg-[#00000032] rounded-lg h-[50px] p-1 gap-1\";\n\t\t\trepoCard.onclick = () => loadRepo(repo.url);\n\t\t\trepoCard.innerHTML = `\n\t\t\t\t<img src=\"/tb.svg\" alt=\"Featured App\" class=\"w-[32px] h-[32px] rounded-[12px] object-cover\" />\n\t\t\t\t<h3 class=\"text-white text-base font-black\">${displayName}</h3>\n\t\t\t\t<svg class=\"flex-1\" width=\"20\" height=\"20\" viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t<circle cx=\"16\" cy=\"16\" r=\"16\" fill=\"#D8645D\"/>\n\t\t\t\t</svg>\n\t\t\t`;\n\t\t\trepoCard.addEventListener(\"contextmenu\", function (e) {\n\t\t\t\te.preventDefault();\n\t\t\t\twindow.parent.tb.contextmenu.create({\n\t\t\t\t\tx: e.clientX,\n\t\t\t\t\ty: e.clientY,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ text: \"Load Repo\", click: () => loadRepo(repo.url) },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Remove Repo\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\trepoList.removeChild(repoCard);\n\t\t\t\t\t\t\t\tconst index = repos.findIndex(r => r.url === repo.url);\n\t\t\t\t\t\t\t\tif (index !== -1) {\n\t\t\t\t\t\t\t\t\trepos.splice(index, 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twindow.parent.tb.fs.promises.writeFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, JSON.stringify(repos, null, 2));\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t});\n\t\t\trepoList.appendChild(repoCard);\n\t\t\tcontinue;\n\t\t}\n\t\tconst data = await repoinfo.json();\n\t\tif (data.maintainer) {\n\t\t\tconst icn = await window.parent.tb.libcurl.fetch(repo.icon);\n\t\t\tconst blob = await icn.blob();\n\t\t\tconst icnurl = URL.createObjectURL(blob);\n\t\t\tconst displayName = data.name && data.name.length > 8 ? data.name.slice(0, 8) + \"...\" : data.name || \"Unknown\";\n\t\t\tconst repoCard = document.createElement(\"div\");\n\t\t\trepoCard.className = \"repo-card flex flex-row items-center bg-[#00000032] rounded-lg h-[50px] p-1 gap-1\";\n\t\t\trepoCard.onclick = () => loadRepo(repo.url);\n\t\t\trepoCard.innerHTML = `\n\t\t\t\t<img src=\"${icnurl || \"/tb.svg\"}\" alt=\"Featured App\" class=\"w-[32px] h-[32px] rounded-[12px] object-cover\" />\n\t\t\t\t<h3 class=\"text-white text-base font-black\">${displayName}</h3>\n\t\t\t\t<svg class=\"flex-1\" width=\"20\" height=\"20\" viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t<circle cx=\"16\" cy=\"16\" r=\"16\" fill=\"#5DD881\"/>\n\t\t\t\t</svg>\n\t\t\t`;\n\t\t\trepoCard.addEventListener(\"contextmenu\", function (e) {\n\t\t\t\te.preventDefault();\n\t\t\t\twindow.parent.tb.contextmenu.create({\n\t\t\t\t\tx: e.clientX,\n\t\t\t\t\ty: e.clientY,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ text: \"Load Repo\", click: () => loadRepo(repo.url) },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Remove Repo\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\trepoList.removeChild(repoCard);\n\t\t\t\t\t\t\t\tconst index = repos.findIndex(r => r.url === repo.url);\n\t\t\t\t\t\t\t\tif (index !== -1) {\n\t\t\t\t\t\t\t\t\trepos.splice(index, 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twindow.parent.tb.fs.promises.writeFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, JSON.stringify(repos, null, 2));\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t});\n\t\t\trepoList.appendChild(repoCard);\n\t\t} else if (data.title) {\n\t\t\tthrow new Error(\"Xen repo not implemented yet.\");\n\t\t} else {\n\t\t\tconst icn = await window.parent.tb.libcurl.fetch(data.repo.icon);\n\t\t\tconst blob = await icn.blob();\n\t\t\tconst icnurl = URL.createObjectURL(blob);\n\t\t\tconst displayName = data.repo.name && data.repo.name.length > 8 ? data.repo.name.slice(0, 8) + \"...\" : data.repo.name || \"Unknown\";\n\t\t\tconst repoCard = document.createElement(\"div\");\n\t\t\trepoCard.className = \"repo-card flex flex-row items-center bg-[#00000032] rounded-lg h-[50px] p-1 gap-1\";\n\t\t\trepoCard.onclick = () => loadRepo(repo.url);\n\t\t\trepoCard.innerHTML = `\n\t\t\t\t<img src=\"${icnurl || \"/tb.svg\"}\" alt=\"Featured App\" class=\"w-[32px] h-[32px] rounded-[12px] object-cover\" />\n\t\t\t\t<h3 class=\"text-white text-base font-black\">${displayName}</h3>\n\t\t\t\t<svg class=\"flex-1\" width=\"20\" height=\"20\" viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t<circle cx=\"16\" cy=\"16\" r=\"16\" fill=\"#5DD881\"/>\n\t\t\t\t</svg>\n\t\t\t`;\n\t\t\trepoCard.addEventListener(\"contextmenu\", function (e) {\n\t\t\t\te.preventDefault();\n\t\t\t\twindow.parent.tb.contextmenu.create({\n\t\t\t\t\tx: e.clientX,\n\t\t\t\t\ty: e.clientY,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ text: \"Load Repo\", click: () => loadRepo(repo.url) },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Remove Repo\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\trepoList.removeChild(repoCard);\n\t\t\t\t\t\t\t\tconst index = repos.findIndex(r => r.url === repo.url);\n\t\t\t\t\t\t\t\tif (index !== -1) {\n\t\t\t\t\t\t\t\t\trepos.splice(index, 1);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twindow.parent.tb.fs.promises.writeFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, JSON.stringify(repos, null, 2));\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t});\n\t\t\trepoList.appendChild(repoCard);\n\t\t}\n\t}\n}\n\n/**\n * Changes the view between apps and PWAs\n * @param {string} type - The type of view (\"apps\" or \"pwa\")\n */\nfunction view(type) {\n\tif (type === \"apps\") {\n\t\tviewType = \"apps\";\n\t\tdocument.querySelector(\".apps-list\").classList.remove(\"hidden\");\n\t\tdocument.querySelector(\".pwa-list\").classList.add(\"hidden\");\n\t\tdocument.querySelector(\".apps-list\").classList.remove(\"flex\");\n\t\tdocument.querySelector(\".apps-list\").classList.add(\"grid\");\n\t\tdocument.querySelector(\".pwa-list\").classList.remove(\"grid\");\n\t\tdocument.querySelector(\".pwa-list\").classList.add(\"hidden\");\n\t\tdocument.querySelector(\".app-togg\").classList.add(\"bg-[#5DD88122]\");\n\t\tdocument.querySelector(\".app-togg\").classList.remove(\"bg-[#00000022]\");\n\t\tdocument.querySelector(\".pwa-togg\").classList.remove(\"bg-[#5DD88122]\");\n\t\tdocument.querySelector(\".pwa-togg\").classList.add(\"bg-[#00000022]\");\n\t} else if (type === \"pwa\") {\n\t\tviewType = \"pwa\";\n\t\tdocument.querySelector(\".pwa-list\").classList.remove(\"hidden\");\n\t\tdocument.querySelector(\".apps-list\").classList.add(\"hidden\");\n\t\tdocument.querySelector(\".pwa-list\").classList.remove(\"flex\");\n\t\tdocument.querySelector(\".pwa-list\").classList.add(\"grid\");\n\t\tdocument.querySelector(\".apps-list\").classList.remove(\"grid\");\n\t\tdocument.querySelector(\".apps-list\").classList.add(\"hidden\");\n\t\tdocument.querySelector(\".app-togg\").classList.remove(\"bg-[#5DD88122]\");\n\t\tdocument.querySelector(\".app-togg\").classList.add(\"bg-[#00000022]\");\n\t\tdocument.querySelector(\".pwa-togg\").classList.add(\"bg-[#5DD88122]\");\n\t\tdocument.querySelector(\".pwa-togg\").classList.remove(\"bg-[#00000022]\");\n\t}\n}\n\n/**\n * Adds a new repo to the repo list\n */\nasync function addRepo() {\n\twindow.parent.tb.dialog.Message({\n\t\ttitle: \"Enter a Repo URL\",\n\t\tonOk: async value => {\n\t\t\tconst res = await window.parent.tb.libcurl.fetch(value);\n\t\t\tconst meta = await res.json();\n\t\t\tconst repos = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, \"utf8\"));\n\t\t\tif (meta.maintainer) {\n\t\t\t\tconst list = await window.parent.tb.libcurl.fetch(value.replace(\"manifest.json\", \"list.json\"));\n\t\t\t\tif (list.ok) {\n\t\t\t\t\trepos.push({\n\t\t\t\t\t\tname: meta.name || \"Unknown\",\n\t\t\t\t\t\turl: value,\n\t\t\t\t\t\ticon: \"https://anura.pro/icon.png\",\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\t\tmessage: \"Failed to add repo. The URL does not point to a valid Anura repo manifest\",\n\t\t\t\t\t\tapplication: \"App Store\",\n\t\t\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\t\t\ttime: 5000,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (meta.title) {\n\t\t\t\trepos.push({\n\t\t\t\t\tname: meta.title || \"Unknown\",\n\t\t\t\t\turl: value,\n\t\t\t\t\ticon: \"https://raw.githubusercontent.com/NebulaServices/XenOS/refs/heads/main/public/assets/logo.svg\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\trepos.push({\n\t\t\t\t\tname: meta.repo.name || \"Unknown\",\n\t\t\t\t\turl: value,\n\t\t\t\t\ticon: meta.repo.icon,\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, JSON.stringify(repos, null, 2));\n\t\t\tloadRepos();\n\t\t},\n\t});\n}\n\n/**\n * Searches for the apps that are loaded in\n * @param {string} input - The search input\n */\nasync function search(input) {\n\tif (viewType === \"apps\") {\n\t\tconst applist = document.querySelector(\".apps-list\");\n\t\tapplist.querySelectorAll(\".app-card\").forEach(card => {\n\t\t\tconst appName = card.querySelector(\"span\").textContent.toLowerCase();\n\t\t\tif (appName.includes(input.toLowerCase())) {\n\t\t\t\tcard.classList.remove(\"hidden\");\n\t\t\t} else {\n\t\t\t\tcard.classList.add(\"hidden\");\n\t\t\t}\n\t\t});\n\t} else {\n\t\tconst pwaList = document.querySelector(\".pwa-list\");\n\t\tpwaList.querySelectorAll(\".app-card\").forEach(card => {\n\t\t\tconst appName = card.querySelector(\"span\").textContent.toLowerCase();\n\t\t\tif (appName.includes(input.toLowerCase())) {\n\t\t\t\tcard.classList.remove(\"hidden\");\n\t\t\t} else {\n\t\t\t\tcard.classList.add(\"hidden\");\n\t\t\t}\n\t\t});\n\t}\n}\n\n/**\n * Compares two semantic version strings.\n * @param {string} a - The first version string.\n * @param {string} b - The second version string.\n * @returns {number} - Returns 1 if a > b, -1 if a < b, 0 if they are equal.\n */\nconst semverCompare = (a, b) => {\n\tconst pa = a.split(/[-.]/);\n\tconst pb = b.split(/[-.]/);\n\tfor (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n\t\tconst na = pa[i] || \"0\";\n\t\tconst nb = pb[i] || \"0\";\n\t\tif (!isNaN(na) && !isNaN(nb)) {\n\t\t\tif (+na > +nb) return 1;\n\t\t\tif (+na < +nb) return -1;\n\t\t} else {\n\t\t\tif (na > nb) return 1;\n\t\t\tif (na < nb) return -1;\n\t\t}\n\t}\n\treturn 0;\n};\n\n/**\n * Installs the requested app\n * @param {string} type - The type of app (Terbium, tb-PWA, Anura, Xen)\n * @returns {Promise<boolean>} - Returns true if the installation was successful, false otherwise\n */\nasync function install(app, type) {\n\tif (app.requirements) {\n\t\tif (app.requirements.os) {\n\t\t\tif (semverCompare(window.parent.tb.system.version(), app.requirements.os.replace(/^v/, \"\")) < 0) {\n\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\tmessage: `Failed to install ${app.name}. Your version of Terbium does not meet the minimum requirements.`,\n\t\t\t\t\tapplication: \"App Store\",\n\t\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\t\ttime: 5000,\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tif (app.requirements.proxy && app.requirements.proxy !== window.parent.tb.proxy.get()) {\n\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\tmessage: `Failed to install ${app.name}. The current selected proxy does not meet the minimum requirements.`,\n\t\t\t\tapplication: \"App Store\",\n\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\ttime: 5000,\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t}\n\tswitch (type) {\n\t\tcase \"Terbium\":\n\t\t\ttry {\n\t\t\t\tawait window.parent.tb.system.download(app[\"pkg-download\"], `/apps/system/${app.name}.zip`);\n\t\t\t\tawait unzip(`/apps/system/${app.name}.zip`, `/apps/system/${app.name}.tapp/`);\n\t\t\t\tawait window.parent.tb.fs.promises.unlink(`/apps/system/${app.name}.zip`);\n\t\t\t\tconst appConf = await window.parent.tb.fs.promises.readFile(`/apps/system/${app.name}.tapp/.tbconfig`, \"utf8\");\n\t\t\t\tconst appData = JSON.parse(appConf);\n\t\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\t\ttitle:\n\t\t\t\t\t\ttypeof appData.wmArgs.title === \"object\"\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\ttext: appData.wmArgs.title.text,\n\t\t\t\t\t\t\t\t\tweight: appData.wmArgs.title.weight,\n\t\t\t\t\t\t\t\t\thtml: appData.wmArgs.title.html,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: appData.wmArgs.title,\n\t\t\t\t\tname: appData.title,\n\t\t\t\t\ticon: `/fs/apps/system/${app.name}.tapp/${appData.icon}`,\n\t\t\t\t\tsrc: `/fs/apps/system/${app.name}.tapp/${appData.wmArgs.src}`,\n\t\t\t\t\tsize: {\n\t\t\t\t\t\twidth: appData.wmArgs.size.width,\n\t\t\t\t\t\theight: appData.wmArgs.size.height,\n\t\t\t\t\t},\n\t\t\t\t\tsingle: appData.wmArgs.single,\n\t\t\t\t\tresizable: appData.wmArgs.resizable,\n\t\t\t\t\tcontrols: appData.wmArgs.controls,\n\t\t\t\t\tmessage: appData.wmArgs.message,\n\t\t\t\t\tsnapable: appData.wmArgs.snapable,\n\t\t\t\t});\n\t\t\t\ttry {\n\t\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\tapps.push({\n\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\tconfig: `/apps/system/${app.name}.tapp/.tbconfig`,\n\t\t\t\t\t});\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t\t} catch {\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\t\tconfig: `/apps/system/${app.name}.tapp/.tbconfig`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\tmessage: `${app.name} has been installed!`,\n\t\t\t\t\tapplication: \"App Store\",\n\t\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\t\ttime: 5000,\n\t\t\t\t\tonOk: () => {\n\t\t\t\t\t\twindow.parent.tb.system.openApp(app.name);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(\"Error installing the app:\", e);\n\t\t\t\tawait window.parent.tb.sh.promises.rm(`/apps/system/${app.name}.tapp`, { recursive: true });\n\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\tmessage: `Failed to install ${app.name}. Check the console for details.`,\n\t\t\t\t\tapplication: \"App Store\",\n\t\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\t\ttime: 5000,\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\t\tcase \"tb-PWA\":\n\t\t\tconst web_apps = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/web_apps.json\", \"utf8\"));\n\t\t\tweb_apps.apps.push(app.name.toLowerCase());\n\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/apps/web_apps.json\", JSON.stringify(web_apps));\n\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\ttitle: app[\"wmArgs\"][\"title\"],\n\t\t\t\tname: app.name,\n\t\t\t\ticon: app.icon,\n\t\t\t\tsrc: app[\"wmArgs\"][\"src\"],\n\t\t\t\tsize: {\n\t\t\t\t\twidth: app[\"wmArgs\"][\"size\"][\"width\"],\n\t\t\t\t\theight: app[\"wmArgs\"][\"size\"][\"height\"],\n\t\t\t\t},\n\t\t\t\tsingle: app[\"wmArgs\"][\"single\"],\n\t\t\t\tresizable: app[\"wmArgs\"][\"resizable\"],\n\t\t\t\tcontrols: app[\"wmArgs\"][\"controls\"],\n\t\t\t\tmessage: app[\"wmArgs\"][\"message\"],\n\t\t\t\tproxy: app[\"wmArgs\"][\"proxy\"],\n\t\t\t\tsnapable: app[\"wmArgs\"][\"snapable\"],\n\t\t\t});\n\t\t\tawait window.parent.tb.fs.promises.mkdir(`/apps/user/${await window.parent.tb.user.username()}/${app.name}`);\n\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${await window.parent.tb.user.username()}/${app.name}/index.json`, JSON.stringify(app));\n\t\t\ttry {\n\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\tapps.push({\n\t\t\t\t\tname: app.name,\n\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\tconfig: `/apps/user/${await window.parent.tb.user.username()}/${app.name}/index.json`,\n\t\t\t\t});\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t} catch {\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\tconfig: `/apps/user/${await window.parent.tb.user.username()}/${app.name}/index.json`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\tmessage: `${app.name} has been installed!`,\n\t\t\t\tapplication: \"App Store\",\n\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\ttime: 5000,\n\t\t\t\tonOk: () => {\n\t\t\t\t\twindow.parent.tb.system.openApp(app.name);\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn true;\n\t\tcase \"tb-liq\":\n\t\tcase \"Anura\":\n\t\t\ttry {\n\t\t\t\tif (type === \"tb-liq\") {\n\t\t\t\t\tawait window.parent.tb.system.download(app[\"anura-pkg\"], `/apps/anura/${app.name}.zip`);\n\t\t\t\t} else {\n\t\t\t\t\tawait window.parent.tb.system.download(`${currRepo.url.replace(\"manifest.json\", \"\")}/apps/${app.package}/${app.data}`, `/apps/anura/${app.name}.zip`);\n\t\t\t\t}\n\t\t\t\tawait unzip(`/apps/anura/${app.name}.zip`, `/apps/anura/${app.name}/`);\n\t\t\t\tawait window.parent.tb.fs.promises.unlink(`/apps/anura/${app.name}.zip`);\n\t\t\t\tconst appConf = await window.parent.tb.fs.promises.readFile(`/apps/anura/${app.name}/manifest.json`, \"utf8\");\n\t\t\t\tconst appData = JSON.parse(appConf);\n\t\t\t\tconsole.log(appData);\n\t\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\t\tname: appData.name,\n\t\t\t\t\ttitle: appData.wininfo.title,\n\t\t\t\t\ticon: `/fs/apps/anura/${app.name}/${appData.icon}`,\n\t\t\t\t\tsrc: `/fs/apps/anura/${app.name}/${appData.index}`,\n\t\t\t\t\tsize: {\n\t\t\t\t\t\twidth: appData.wininfo.width,\n\t\t\t\t\t\theight: appData.wininfo.height,\n\t\t\t\t\t},\n\t\t\t\t\tsingle: appData.wininfo.allowMultipleInstance,\n\t\t\t\t});\n\t\t\t\twindow.parent.anura.apps[appData.package] = {\n\t\t\t\t\ttitle: appData.name,\n\t\t\t\t\ticon: appData.icon,\n\t\t\t\t\tid: appData.package,\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\tapps.push({\n\t\t\t\t\t\tname: appData.name,\n\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\tconfig: `/apps/anura/${app.name}/manifest.json`,\n\t\t\t\t\t});\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t\t} catch {\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: appData.name,\n\t\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\t\tconfig: `/apps/anura/${app.name}/manifest.json`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\tmessage: `${app.name} has been installed!`,\n\t\t\t\t\tapplication: \"App Store\",\n\t\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\t\ttime: 5000,\n\t\t\t\t\tonOk: () => {\n\t\t\t\t\t\twindow.parent.tb.system.openApp(app.name);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(\"Error installing the app:\", e);\n\t\t\t\tawait window.parent.tb.sh.promises.rm(`/apps/anura/${app.name}`, { recursive: true });\n\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\tmessage: `Failed to install ${app.name}. Check the console for details.`,\n\t\t\t\t\tapplication: \"App Store\",\n\t\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\t\ttime: 5000,\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\t\tcase \"Xen\":\n\t\t\tthrow new Error(\"Xen repo not implemented yet.\");\n\t}\n}\n\n/**\n * Uninstalls the requested app\n * @param {Object} app - The app to uninstall\n * @param {string} type - The type of app (Terbium, tb-PWA, Anura, Xen)\n */\nasync function uninstall(app, type) {\n\tswitch (type) {\n\t\tcase \"Terbium\":\n\t\t\tif (await dirExists(`/apps/system/${app.name}.tapp`)) {\n\t\t\t\tawait window.parent.tb.fs.shell.promises.rm(`/apps/system/${app.name}.tapp`, { recursive: true });\n\t\t\t} else {\n\t\t\t\tawait window.parent.tb.fs.shell.promises.rm(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/${app.name}.tapp`, { recursive: true });\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tlet installedApps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\tinstalledApps = installedApps.filter(a => a.name !== app.name);\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(installedApps));\n\t\t\t} catch {\n\t\t\t\tthrow new Error(\"Failed to update the installed app list\");\n\t\t\t}\n\t\t\twindow.parent.tb.launcher.removeApp(app.name);\n\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\tmessage: `Successfully uninstalled ${app.name}.`,\n\t\t\t\tapplication: \"App Store\",\n\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\ttime: 5000,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase \"tb-PWA\":\n\t\t\tconst web_apps = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/web_apps.json\", \"utf8\"));\n\t\t\tconst index = web_apps.apps.indexOf(app.name.toLowerCase());\n\t\t\tif (index > -1) {\n\t\t\t\tweb_apps.apps.splice(index, 1);\n\t\t\t}\n\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/apps/web_apps.json\", JSON.stringify(web_apps));\n\t\t\twindow.parent.tb.launcher.removeApp(app.name);\n\t\t\tawait window.parent.tb.fs.promises.unlink(`/apps/user/${await window.parent.tb.user.username()}/${app.name}/index.json`);\n\t\t\tawait window.parent.tb.sh.promises.rm(`/apps/user/${await window.parent.tb.user.username()}/${app.name}`, { recursive: true });\n\t\t\ttry {\n\t\t\t\tlet installedApps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\tinstalledApps = installedApps.filter(a => a.name !== app.name);\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(installedApps));\n\t\t\t} catch {\n\t\t\t\tthrow new Error(\"Failed to update the installed app list\");\n\t\t\t}\n\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\tmessage: `Successfully uninstalled ${app.name}.`,\n\t\t\t\tapplication: \"App Store\",\n\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\ttime: 5000,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase \"Anura\":\n\t\tcase \"tb-liq\":\n\t\t\tawait window.parent.tb.fs.shell.promises.rm(`/apps/anura/${app.name}`, { recursive: true });\n\t\t\ttry {\n\t\t\t\tlet installedApps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\tinstalledApps = installedApps.filter(a => a.name !== app.name);\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(installedApps));\n\t\t\t} catch {\n\t\t\t\tthrow new Error(\"Failed to update the installed app list\");\n\t\t\t}\n\t\t\twindow.parent.tb.launcher.removeApp(app.name);\n\t\t\tdelete window.parent.anura.apps[app.package];\n\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\tmessage: `Successfully uninstalled ${app.name}.`,\n\t\t\t\tapplication: \"App Store\",\n\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\ttime: 5000,\n\t\t\t});\n\t\t\tbreak;\n\t}\n}\n\n/**\n * Unzips the requested file to the target dir\n * @param {string} path\n * @param {string} target\n */\nasync function unzip(path, target) {\n\tconst runUnzip = async () => {\n\t\tconst response = await fetch(\"/fs/\" + path);\n\t\tconst zipFileContent = await response.arrayBuffer();\n\t\tif (!(await dirExists(target))) {\n\t\t\tawait window.parent.tb.fs.promises.mkdir(target, { recursive: true });\n\t\t}\n\t\tconst compressedFiles = window.parent.tb.fflate.unzipSync(new Uint8Array(zipFileContent));\n\t\tfor (const [relativePath, content] of Object.entries(compressedFiles)) {\n\t\t\tconst fullPath = `${target}/${relativePath}`;\n\t\t\tconst pathParts = fullPath.split(\"/\");\n\t\t\tlet currentPath = \"\";\n\t\t\tfor (let i = 0; i < pathParts.length; i++) {\n\t\t\t\tcurrentPath += pathParts[i] + \"/\";\n\t\t\t\tif (i === pathParts.length - 1 && !relativePath.endsWith(\"/\")) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(`touch ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(currentPath.slice(0, -1), window.parent.tb.buffer.from(content));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tconsole.log(`Cant make ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t}\n\t\t\t\t} else if (!(await dirExists(currentPath))) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(`mkdir ${currentPath}`);\n\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(currentPath);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tconsole.log(`Cant make ${currentPath}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (relativePath.endsWith(\"/\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconsole.log(`mkdir fp ${fullPath}`);\n\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(fullPath);\n\t\t\t\t} catch {\n\t\t\t\t\tconsole.log(`Cant make ${fullPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn \"Done!\";\n\t};\n\n\treturn window.parent.tb.notification.Installing(\n\t\t{\n\t\t\tmessage: \"Installing package files...\",\n\t\t\tapplication: \"App Store\",\n\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t},\n\t\trunUnzip(),\n\t\tnull,\n\t\t{\n\t\t\tmessage: \"Failed to extract package\",\n\t\t\tapplication: \"App Store\",\n\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\ttime: 5500,\n\t\t},\n\t);\n}\n\n/**\n * Resolves if a directory exists\n * @param {string} path\n * @returns {Promise<Boolean>}\n */\nconst dirExists = async path => {\n\treturn new Promise(resolve => {\n\t\twindow.parent.tb.fs.stat(path, (err, stats) => {\n\t\t\tif (err) {\n\t\t\t\tif (err.code === \"ENOENT\") {\n\t\t\t\t\tresolve(false);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t\tresolve(false);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst exists = stats.type === \"DIRECTORY\";\n\t\t\t\tresolve(exists);\n\t\t\t}\n\t\t});\n\t});\n};\n\nwindow.addEventListener(\"load\", async () => {\n\tawait loadRepo(\"https://raw.githubusercontent.com/TerbiumOS/tb-repo/refs/heads/main/manifest.json\");\n\tloadRepos();\n\twindow.parent.document.querySelector(\".app-search\").addEventListener(\"input\", e => {\n\t\tsearch(e.target.value);\n\t});\n});\n\nwindow.addEventListener(\"contextmenu\", e => {\n\te.preventDefault();\n});\n"
  },
  {
    "path": "public/apps/app store.tapp/index.json",
    "content": "{\n\t\"name\": \"App Store\",\n\t\"config\": {\n\t\t\"title\": {\n\t\t\t\"text\": \"App Store\",\n\t\t\t\"html\": \"<div style=\\\"display: flex; flex-direction: row; align-items: center; height: 32px; z-index: 999999;\\\"><div style=\\\"width:350px; display:flex; justify-content:center;\\\"><input class=\\\"app-search bg-white/15 border-0 outline-none text-white py-1 px-2 rounded-lg transition-all duration-150 ease-in-out font-semibold\\\" type=\\\"search\\\" placeholder=\\\"Search for apps\\\" style=\\\"width:100%;\\\" /></div></div>\"\n\t\t},\n\t\t\"icon\": \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/app store.tapp/index.html\",\n\t\t\"size\": {\n\t\t\t\"width\": 775,\n\t\t\t\"height\": 500\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "public/apps/browser.tapp/index.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(\"/fonts/Inter.ttf\");\n}\n\n:root {\n\t--shell-primary: #ffffff34;\n\t--shell-secondary: #00000078;\n}\n\nbody {\n\tfont-family: Inter;\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tline-height: 20px;\n\tcolor: #ffffff;\n\tbackground-color: transparent;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: hidden;\n\tborder-bottom-left-radius: 10px;\n\tborder-bottom-right-radius: 10px;\n}\n\nbody,\nhtml {\n\theight: 100%;\n}\n\n.topbar {\n\twidth: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground-color: #000000a3;\n}\n\n.topbar .tab-container {\n\tpadding: 4px;\n\tdisplay: flex;\n\talign-items: center;\n\twidth: calc(100% - 8px);\n\tgap: 4px;\n}\n\n.topbar .controls {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 4px;\n\twidth: calc(100% - calc(16px + 8px));\n\tmargin-left: 4px;\n\tmargin-bottom: 4px;\n\theight: 24px;\n\tbackground-color: var(--shell-primary);\n\tpadding: 8px;\n\tborder-radius: 8px;\n}\n\n.topbar .controls .searchbars {\n\twidth: 100%;\n\tpadding: 3px 0 4px 11px;\n\tbox-shadow: inset 0 0 0 1px #ffffff28;\n\tborder-radius: 8px;\n\ttransition: 150ms ease-in-out;\n}\n\n.topbar .controls .searchbars.focus {\n\tbox-shadow: inset 0 0 0 2px #ffffff;\n}\n\n.topbar .controls input {\n\twidth: 100%;\n\theight: 100%;\n\tborder: none;\n\tborder-radius: 6px;\n\tbackground-color: transparent;\n\tcolor: #ffffff;\n\tfont-size: 13px;\n\tfont-weight: 800;\n\tfont-family: Inter;\n\tline-height: 0;\n\tpadding: 0;\n\toutline: none;\n}\n\n.topbar .controls input:not(.active) {\n\tdisplay: none;\n}\n\n.topbar .controls .navigation-button {\n\twidth: 16px !important;\n\theight: 16px !important;\n\tmin-height: 16px !important;\n\tmin-width: 16px !important;\n\tstroke: #ffffff;\n\tstroke-width: 1.5px;\n\tpadding: 6px;\n\tborder-radius: 20px;\n\ttransition: 150ms ease-in-out;\n}\n\n.topbar .controls .navigation-button.left-arrow {\n\tpadding-right: calc(6px + 1.5px);\n}\n\n.topbar .controls .navigation-button.right-arrow {\n\tpadding-left: calc(6px + 1.5px);\n}\n\n.topbar .controls .navigation-button:hover {\n\tbackground-color: #ffffff28;\n}\n\n.topbar .controls .navigation-button:not(.disabled) {\n\tcursor: var(--cursor-pointer);\n}\n\n.topbar .controls .navigation-button.disabled {\n\topacity: 0.5;\n}\n\n.topbar .tabs {\n\tdisplay: flex;\n\tgap: 4px;\n\toverflow: hidden;\n\tmax-width: calc(100% - 38px);\n}\n\n.topbar .tabs .tab {\n\tpadding: 6px;\n\tpadding-left: 8px;\n\twidth: 100px;\n\theight: 24px;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tjustify-content: space-between;\n\tborder-radius: 6px;\n\ttransition-property: opacity;\n\ttransition-duration: 150ms;\n\ttransition-timing-function: ease-in-out;\n}\n\n.topbar .tabs .tab.active {\n\tbackground-color: var(--shell-primary);\n}\n\n.topbar .tabs .tab:not(.active) {\n\tbackground-color: transparent;\n}\n\n.topbar .tabs .tab:not(.active):hover {\n\topacity: 0.8;\n}\n\n.topbar .tabs .tab .tab-title {\n\tfont-weight: 400;\n\tline-height: 0;\n\tcolor: #ffffff;\n\tpointer-events: none;\n\tuser-select: none;\n}\n\n.topbar .tabs .tab .tab-close {\n\twidth: 18px;\n\theight: 18px;\n\tborder-radius: 50%;\n\tpadding: 2px;\n\ttransition: 150ms ease-in-out;\n}\n\n.topbar .tabs .tab .tab-close:hover {\n\tbackground-color: #ffffff28;\n}\n\n.topbar .new-tab {\n\twidth: 26px;\n\theight: 26px;\n\tborder-radius: 6px;\n\ttransition: 150ms ease-in-out;\n}\n\n.topbar .new-tab:hover {\n\tbackground-color: #ffffff28;\n}\n\nmain {\n\twidth: 100%;\n\theight: calc(100% - calc(var(--topbar-height) + 10px));\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\toverflow: hidden;\n}\n\nmain .tab-content {\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\toverflow: hidden;\n\tborder: none;\n}\n\nmain .tab-content:not(.active) {\n\tdisplay: none;\n}\n\n.sidebar {\n\twidth: 200px;\n\theight: 100%;\n\tpadding: 10px 8px 8px 10px;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.set-search {\n\twidth: 150px;\n\tbackground-color: rgba(0, 0, 0, 0.396);\n\tcolor: rgb(255, 255, 255);\n\tfont-size: 14px;\n\tfont-weight: 800;\n\tfont-family: Inter;\n\tline-height: 0;\n\tborder-width: initial;\n\tborder-style: none;\n\tborder-color: initial;\n\tborder-image: initial;\n\tborder-radius: 6px;\n\tpadding: 4px 0px 6px 12px;\n\toutline: none;\n}\n\n.sidebar ul {\n\tlist-style: none;\n\tpadding: 0;\n}\n\n.sidebar li {\n\tmargin-bottom: 10px;\n\tcursor: var(--cursor-pointer);\n}\n\n.content {\n\tmargin-left: 250px;\n\tpadding: 20px;\n\tmargin-top: -54%;\n}\n\n.section {\n\tdisplay: none;\n\ttransition: opacity 0.5s ease;\n\topacity: 0;\n}\n\n.section.active {\n\tdisplay: block;\n\topacity: 1;\n\tanimation: fade-in 0.5s ease-in-out forwards;\n}\n\nbutton {\n\tbackground-color: #ffffff28;\n\tborder-radius: 6px;\n\tpadding: 6px 8px;\n\tborder: none;\n\tcolor: #ffffff;\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tfont-family: Inter;\n\tcursor: var(--cursor-pointer);\n\ttransition: 150ms ease-in-out;\n}\n"
  },
  {
    "path": "public/apps/browser.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Terbium Browser</title>\n    <link rel=\"stylesheet\" href=\"./index.css\">\n    <script src=\"https://cdn.jsdelivr.net/npm/eruda\"></script>\n</head>\n    <body class=\"start\">\n        <div class=\"topbar\">\n            <div class=\"tab-container\">\n                <div class=\"tabs\"></div>\n                <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"new-tab\">\n                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6v12m6-6H6\" />\n                </svg>\n            </div>\n            <div class=\"controls\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"navigate-back navigation-button left-arrow\">\n                    <path fill-rule=\"evenodd\" d=\"M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z\" clip-rule=\"evenodd\" />\n                </svg>\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"navigate-forward navigation-button right-arrow disabled\">\n                    <path fill-rule=\"evenodd\" d=\"M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z\" clip-rule=\"evenodd\" />\n                </svg>\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"navigation-button refresh-button\">\n                    <path fill-rule=\"evenodd\" d=\"M4.755 10.059a7.5 7.5 0 0112.548-3.364l1.903 1.903h-3.183a.75.75 0 100 1.5h4.992a.75.75 0 00.75-.75V4.356a.75.75 0 00-1.5 0v3.18l-1.9-1.9A9 9 0 003.306 9.67a.75.75 0 101.45.388zm15.408 3.352a.75.75 0 00-.919.53 7.5 7.5 0 01-12.548 3.364l-1.902-1.903h3.183a.75.75 0 000-1.5H2.984a.75.75 0 00-.75.75v4.992a.75.75 0 001.5 0v-3.18l1.9 1.9a9 9 0 0015.059-4.035.75.75 0 00-.53-.918z\" clip-rule=\"evenodd\" />\n                </svg>\n                <div class=\"searchbars\"></div>\n                <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"navigation-button fav-button\">\n                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z\" />\n                </svg>\n                <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"navigation-button ext-btn\">\n                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 0 1-.657.643 48.39 48.39 0 0 1-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 0 1-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 0 0-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 0 1-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 0 0 .657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 0 1-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 0 0 5.427-.63 48.05 48.05 0 0 0 .582-4.717.532.532 0 0 0-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 0 0 .658-.663 48.422 48.422 0 0 0-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 0 1-.61-.58v0Z\" />\n                </svg>\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" data-slot=\"icon\" is=\"\" class=\"navigation-button opt-menu\">\n                    <path fill-rule=\"evenodd\" d=\"M10.5 6a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm0 6a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm0 6a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z\" clip-rule=\"evenodd\" />\n                </svg>\n            </div>\n        </div>\n        <main></main>\n        <script src=\"./index.js\"></script>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/browser.tapp/index.js",
    "content": "const Filer = window.parent.tb.fs;\nconst IS_URL = /^(https?:\\/\\/)?(www\\.)?([-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}|localhost)\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)/;\nconst create_new_id = () => {\n\tconst id = Math.random().toString(36).substr(2, 9);\n\tif (document.getElementById(id)) {\n\t\treturn create_new_id();\n\t}\n\treturn id;\n};\n\nfunction customEncode(input) {\n\tif (input) {\n\t\tlet str = input.toString();\n\t\tlet charArray = str.split(\"\");\n\t\tlet encodedArray = charArray.map((char, index) => {\n\t\t\tif (index % 2) {\n\t\t\t\treturn String.fromCharCode(2 ^ char.charCodeAt());\n\t\t\t} else {\n\t\t\t\treturn char;\n\t\t\t}\n\t\t});\n\t\tlet encodedString = encodedArray.join(\"\");\n\t\tlet finalResult = encodeURIComponent(encodedString);\n\t\treturn finalResult;\n\t} else {\n\t\treturn input;\n\t}\n}\n\nfunction customDecode(encodedString) {\n\tif (!encodedString) return encodedString;\n\tlet [firstPart, ...restParts] = encodedString.split(\"?\");\n\tlet decodedString = decodeURIComponent(firstPart)\n\t\t.split(\"\")\n\t\t.map((char, index) => (index % 2 ? String.fromCharCode(2 ^ char.charCodeAt(0)) : char))\n\t\t.join(\"\");\n\tlet finalResult = decodedString + (restParts.length ? \"?\" + restParts.join(\"?\") : \"\");\n\treturn finalResult;\n}\n\nconst topbar_height = document.querySelector(\".topbar\").getBoundingClientRect().height;\ndocument.body.style.setProperty(`--topbar-height`, `${document.querySelector(\".topbar\").getBoundingClientRect().height}px`);\n\nconst new_tab = document.querySelector(\".new-tab\");\nnew_tab.addEventListener(\"click\", () => {\n\tnewTab();\n});\n\nfunction closeTab(id) {\n\tconst tab = document.getElementById(id);\n\tconst tab_content = document.getElementById(id + \"-content\");\n\ttab.remove();\n\twindow.parent.tb.mediaplayer.hide();\n\ttab_content.remove();\n\tconst lastTab = document.querySelector(\".tab:last-child\");\n\tconst lastTabContent = document.querySelector(\".tab-content:last-child\");\n\tif (lastTab && lastTabContent) {\n\t\tlastTab.classList.add(\"active\");\n\t\tlastTabContent.classList.add(\"active\");\n\t}\n\tif (!document.querySelector(\".tab\")) {\n\t\tnewTab();\n\t}\n}\n\nfunction newTab() {\n\tlet updateTab = false;\n\tlet interval;\n\tconst id = create_new_id();\n\tconst tab = document.createElement(\"div\");\n\ttab.classList.add(\"tab\");\n\ttab.id = id;\n\tconst tab_title = document.createElement(\"div\");\n\ttab_title.classList.add(\"tab-title\");\n\ttab_title.innerHTML = \"New Tab\";\n\ttab.appendChild(tab_title);\n\tconst tab_close = document.createElement(\"img\");\n\ttab_close.classList.add(\"tab-close\");\n\ttab_close.src = \"/apps/browser.tapp/close.svg\";\n\ttab.appendChild(tab_close);\n\tdocument.querySelectorAll(\".tab\").forEach(otab => {\n\t\tif (otab.id != tab.id) {\n\t\t\totab.classList.remove(\"active\");\n\t\t}\n\t});\n\tif (!localStorage.getItem(\"defUrl\")) {\n\t\tlocalStorage.setItem(\"defUrl\", \"about:newtab\");\n\t}\n\ttab.classList.add(\"active\");\n\tdocument.querySelector(\".tabs\").appendChild(tab);\n\tconst urlbar = document.createElement(\"input\");\n\tconst user = window.parent.sessionStorage.getItem(\"currAcc\");\n\turlbar.classList.add(\"urlbar\");\n\turlbar.addEventListener(\"focus\", e => {\n\t\tdocument.querySelector(\".searchbars\").classList.add(\"focus\");\n\t});\n\turlbar.addEventListener(\"blur\", e => {\n\t\tdocument.querySelector(\".searchbars\").classList.remove(\"focus\");\n\t});\n\turlbar.id = id + \"-urlbar\";\n\turlbar.type = \"text\";\n\turlbar.placeholder = \"Search or enter a URL\";\n\turlbar.autocomplete = \"off\";\n\turlbar.spellcheck = false;\n\turlbar.addEventListener(\"focus\", () => {\n\t\turlbar.select();\n\t});\n\tdocument.querySelector(\".searchbars\").appendChild(urlbar);\n\tdocument.querySelectorAll(\".urlbar\").forEach(ourlbar => {\n\t\tif (ourlbar.id != urlbar.id) {\n\t\t\tourlbar.classList.remove(\"active\");\n\t\t}\n\t});\n\turlbar.classList.add(\"active\");\n\turlbar.addEventListener(\"click\", () => {\n\t\tupdateTab = true;\n\t});\n\turlbar.addEventListener(\"keydown\", e => {\n\t\tif (e.key === \"Enter\") {\n\t\t\tupdateTab = false;\n\t\t\tconst url = urlbar.value.trim();\n\t\t\tconst activeTab = document.querySelector(\".tab.active\");\n\t\t\tconst activeTabContent = document.querySelector(\".tab-content.active\");\n\n\t\t\tconst localhostRegex = /^(https?:\\/\\/)?localhost(:\\d+)?(\\/.*)?$/i;\n\t\t\tconst localhostMatch = url.match(localhostRegex);\n\n\t\t\tif (localhostMatch) {\n\t\t\t\tconst port = localhostMatch[2] ? parseInt(localhostMatch[2].substring(1)) : 80;\n\t\t\t\tconst serverUrl = window.parent.tb.node.servers.get(port);\n\t\t\t\tif (serverUrl) {\n\t\t\t\t\tactiveTabContent.src = serverUrl;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch (url) {\n\t\t\t\tcase \"about:settings\":\n\t\t\t\t\tactiveTabContent.src = \"/apps/browser.tapp/settings.html\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"about:newtab\":\n\t\t\t\t\tactiveTabContent.src = \"/apps/browser.tapp/newtab.html\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"about:userscripts\":\n\t\t\t\t\tactiveTabContent.src = \"/apps/browser.tapp/userscripts.html\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tconst input = url;\n\t\t\t\t\tFiler.promises.readFile(`/home/${user}/settings.json`, \"utf8\").then(async data => {\n\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\tconst searchEngine = localStorage.getItem(\"sEngine\") || \"https://google.com/search?q=\";\n\t\t\t\t\t\tconst isUrl = /^(https?:\\/\\/)|(localhost(:\\d+)?([\\/?]|$))|([a-z0-9\\-]+\\.[a-z]{2,})/i.test(input) && !/\\s/.test(input);\n\t\t\t\t\t\tlet targetUrl;\n\t\t\t\t\t\tif (isUrl) {\n\t\t\t\t\t\t\ttargetUrl = input.startsWith(\"http\") ? input : `https://${input}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttargetUrl = `${searchEngine}${encodeURIComponent(input)}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (settings.proxy === \"Scramjet\") {\n\t\t\t\t\t\t\tactiveTabContent.src = `${window.location.origin}/service/${await window.parent.tb.proxy.encode(targetUrl, \"XOR\")}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tactiveTabContent.src = `${window.location.origin}/uv/service/${await window.parent.tb.proxy.encode(targetUrl, \"XOR\")}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tactiveTabContent.onload = () => {\n\t\t\t\tconst pageTitle = activeTabContent.contentDocument.title || \"Untitled\";\n\t\t\t\tconst maxTitleLength = 8;\n\t\t\t\tconst tabTitle = pageTitle.length > maxTitleLength ? pageTitle.substring(0, maxTitleLength) + \"...\" : pageTitle;\n\t\t\t\tactiveTab.querySelector(\".tab-title\").innerHTML = tabTitle;\n\t\t\t};\n\t\t}\n\t});\n\tconst tab_content = document.createElement(\"iframe\");\n\tFiler.promises.readFile(`/home/${user}/settings.json`, \"utf8\").then(data => {\n\t\tlet settings = JSON.parse(data);\n\t\tlet proxy = settings[\"proxy\"];\n\t\tconsole.log(proxy);\n\t\tconsole.log(localStorage.getItem(\"defUrl\"));\n\t\tif (localStorage.getItem(\"defUrl\") === \"about:newtab\") {\n\t\t\turlbar.value = \"about:newtab\";\n\t\t\tupdateTab = true;\n\t\t\ttab_content.src = \"/apps/browser.tapp/newtab.html\";\n\t\t\ttab_content.addEventListener(\"load\", () => {\n\t\t\t\ttab_content.contentWindow.addEventListener(\"updTab\", () => {\n\t\t\t\t\tupdateTab = false;\n\t\t\t\t\ttab_content.contentWindow.removeEventListener(\"updTab\", arguments.callee);\n\t\t\t\t});\n\t\t\t});\n\t\t} else {\n\t\t\tif (proxy === \"Ultraviolet\") {\n\t\t\t\ttab_content.src = parent.window.location.origin + \"/uv/service/\" + customEncode(localStorage.getItem(\"defUrl\") || \"about:newtab\");\n\t\t\t} else if (proxy === \"Scramjet\") {\n\t\t\t\ttab_content.src = parent.window.location.origin + \"/service/\" + customEncode(localStorage.getItem(\"defUrl\") || \"about:newtab\");\n\t\t\t}\n\t\t}\n\t});\n\tconst unloadHandler = function () {\n\t\tsetTimeout(function () {\n\t\t\tconst pageTitle = tab_content.contentDocument.title || \"Untitled\";\n\t\t\tconst maxTitleLength = 8;\n\t\t\tconst tabTitle = pageTitle.length > maxTitleLength ? pageTitle.substring(0, maxTitleLength) + \"...\" : pageTitle;\n\t\t\ttab_title.innerHTML = tabTitle;\n\t\t}, 0);\n\t};\n\ttab_content.classList.add(\"tab-content\");\n\ttab_content.id = id + \"-content\";\n\tconst inject_engines = () => {\n\t\tif (document.querySelector(\".left-arrow\").classList.contains(\"disabled\")) {\n\t\t\tdocument.querySelector(\".left-arrow\").classList.remove(\"disabled\");\n\t\t}\n\t\tif (\"__uv$location\" in tab_content.contentDocument) {\n\t\t\tif (updateTab === false) {\n\t\t\t\turlbar.value = tab_content.contentDocument.__uv$location?.href;\n\t\t\t}\n\t\t} else {\n\t\t\tif (updateTab === false) {\n\t\t\t\twindow.parent.tb.proxy.decode(tab_content.contentWindow.window.location.href.replace(/^.*\\/service\\//, \"\"), \"XOR\").then(decodedUrl => {\n\t\t\t\t\turlbar.value = decodedUrl;\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tif (!tab_content.contentDocument.getElementById(\"tb-cursor-controller\")) {\n\t\t\tconst cursor_controller = document.createElement(\"script\");\n\t\t\tcursor_controller.src = \"/cursor_changer.js\";\n\t\t\tcursor_controller.id = \"tb-cursor-controller\";\n\t\t\ttab_content.contentDocument.head.appendChild(cursor_controller);\n\t\t}\n\t\tif (!tab_content.contentDocument.getElementById(\"tb-media-controller\")) {\n\t\t\tconst media_controller = document.createElement(\"script\");\n\t\t\tmedia_controller.src = \"/media_interactions.js\";\n\t\t\tmedia_controller.id = \"tb-media-controller\";\n\t\t\ttab_content.contentDocument.head.appendChild(media_controller);\n\t\t}\n\t\tif (!tab_content.contentDocument.getElementById(\"userscript-container\")) {\n\t\t\tconst userscript_container = document.createElement(\"div\");\n\t\t\tuserscript_container.id = \"userscript-container\";\n\t\t\tFiler.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/browser/userscripts.json`).then(async data => {\n\t\t\t\tconst dat = JSON.parse(data);\n\t\t\t\tfor (const script of dat) {\n\t\t\t\t\tif (urlbar.value.includes(script.match)) {\n\t\t\t\t\t\tconst user_script = document.createElement(\"script\");\n\t\t\t\t\t\tconsole.log(\"Script: %a\\nScript path: %b\", script.name, script.file);\n\t\t\t\t\t\tuser_script.type = \"text/javascript\";\n\t\t\t\t\t\tuser_script.text = await Filer.promises.readFile(script.file, \"utf8\");\n\t\t\t\t\t\tuser_script.type = \"text/javascript\";\n\t\t\t\t\t\tuser_script.id = `userscript-${script.name}`;\n\t\t\t\t\t\tuser_script.setAttribute(\"data-script-path\", script.file);\n\t\t\t\t\t\tuserscript_container.appendChild(user_script);\n\t\t\t\t\t} else if (script.match === \"*://*/*\") {\n\t\t\t\t\t\tconst user_script = document.createElement(\"script\");\n\t\t\t\t\t\tconsole.log(\"Script: %a\\nScript path: %b\", script.name, script.file);\n\t\t\t\t\t\tuser_script.type = \"text/javascript\";\n\t\t\t\t\t\tuser_script.text = await Filer.promises.readFile(script.file, \"utf8\");\n\t\t\t\t\t\tuser_script.type = \"text/javascript\";\n\t\t\t\t\t\tuser_script.id = `userscript-${script.name}`;\n\t\t\t\t\t\tuser_script.setAttribute(\"data-script-path\", script.file);\n\t\t\t\t\t\tuserscript_container.appendChild(user_script);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(`Skipping script ${script.name}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\ttab_content.contentDocument.head.appendChild(userscript_container);\n\t\t\tconsole.log(\"Injected userscripts into the frame\");\n\t\t}\n\t\tif (!tab_content.contentDocument._hasKeydownListener) {\n\t\t\ttab_content.contentDocument.addEventListener(\"keydown\", e => {\n\t\t\t\tif (e.altKey && e.key.toLowerCase() === \"t\") {\n\t\t\t\t\tnewTab();\n\t\t\t\t} else if (e.altKey && e.key.toLowerCase() === \"w\") {\n\t\t\t\t\tcloseTab(document.querySelector(\".tab.active\").id);\n\t\t\t\t} else if (e.altKey && e.key.toLowerCase() === \"r\") {\n\t\t\t\t\tconst activeTabContent = document.querySelector(\".tab-content.active\");\n\t\t\t\t\twindow.parent.tb.mediaplayer.hide();\n\t\t\t\t\tactiveTabContent.contentWindow.location.reload();\n\t\t\t\t} else if (e.altKey && e.key.toLowerCase() === \"b\") {\n\t\t\t\t\tpwaIns();\n\t\t\t\t} else if (e.altKey && e.key.toLowerCase() === \"k\") {\n\t\t\t\t\tshowTabs();\n\t\t\t\t} else if (e.altKey && e.key.toLowerCase() === \"i\") {\n\t\t\t\t\tif (typeof window.eruda === \"undefined\") {\n\t\t\t\t\t\teruda.init();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (window.eruda._isInit) {\n\t\t\t\t\t\t\twindow.eruda.destroy();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twindow.eruda.init();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\ttab_content.contentDocument._hasKeydownListener = true;\n\t\t}\n\t};\n\tif (!interval) {\n\t\tinterval = setInterval(inject_engines, 1000);\n\t}\n\ttab_content.onload = () => {\n\t\tif (!interval) {\n\t\t\tinterval = setInterval(inject_engines, 1000);\n\t\t}\n\t\tconst pageTitle = tab_content.contentDocument.title || \"Untitled\";\n\t\tconst maxTitleLength = 8;\n\t\tconst tabTitle = pageTitle.length > maxTitleLength ? pageTitle.substring(0, maxTitleLength) + \"...\" : pageTitle;\n\t\ttab_title.innerHTML = tabTitle;\n\t\tlet url = tab_content.src;\n\t\turl = url.replace(parent.window.location.origin, \"\");\n\t\turl = url.replace(\"/uv/service/\", \"\");\n\t\turl = url.replace(\"/service/\", \"\");\n\t\ttab_content.contentWindow.removeEventListener(\"unload\", unloadHandler);\n\t\ttab_content.contentWindow.addEventListener(\"unload\", unloadHandler);\n\t\ttab_content.contentWindow.addEventListener(\"load\", unloadHandler);\n\t\ttab_content.contentWindow.removeEventListener(\"load\", unloadHandler);\n\t};\n\tdocument.querySelector(\"main\").appendChild(tab_content);\n\tdocument.querySelectorAll(\".tab-content\").forEach(otab => {\n\t\tif (otab.id != tab_content.id) {\n\t\t\totab.classList.remove(\"active\");\n\t\t}\n\t});\n\n\ttab_content.classList.add(\"active\");\n\ttab.addEventListener(\"click\", e => {\n\t\tif (e.target.classList.contains(\"tab-close\")) return;\n\t\tif (e.target.classList.contains(\"tab-close\")) {\n\t\t\ttab.remove();\n\t\t\twindow.parent.tb.mediaplayer.hide();\n\t\t\ttab_content.remove();\n\t\t\tclearInterval(interval);\n\t\t}\n\t\tdocument.querySelectorAll(\".tab\").forEach(otab => {\n\t\t\tif (otab.id != tab.id) {\n\t\t\t\totab.classList.remove(\"active\");\n\t\t\t}\n\t\t});\n\t\tdocument.querySelectorAll(\".tab-content\").forEach(otab => {\n\t\t\tif (otab.id != tab_content.id) {\n\t\t\t\totab.classList.remove(\"active\");\n\t\t\t}\n\t\t});\n\t\ttab.classList.add(\"active\");\n\t\ttab_content.classList.add(\"active\");\n\t\tdocument.querySelectorAll(\".urlbar\").forEach(ourlbar => {\n\t\t\tif (ourlbar.id != urlbar.id) {\n\t\t\t\tourlbar.classList.remove(\"active\");\n\t\t\t}\n\t\t});\n\t\turlbar.classList.add(\"active\");\n\t\tlet url = tab_content.src;\n\t\turl = url.replace(parent.window.location.origin, \"\");\n\t\turl = url.replace(\"/uv/service/\", \"\");\n\t\turl = url.replace(\"/service/\", \"\");\n\t\tif (!url.includes(\"about:\")) {\n\t\t\turlbar.value = customDecode(url);\n\t\t}\n\t});\n\ttab_close.addEventListener(\"click\", () => {\n\t\tcloseTab(id);\n\t\tclearInterval(interval);\n\t});\n}\n\nwindow.addEventListener(\"keypress\", e => {\n\tif (e.shiftKey && e.key === \"W\") {\n\t\te.preventDefault();\n\t\tnewTab();\n\t} else if (e.shiftKey && e.key === \"Q\") {\n\t\te.preventDefault();\n\t\tconst tabId = document.querySelector(\".tab.active\").id;\n\t\tcloseTab(tabId);\n\t}\n});\n\nwindow.onload = () => {\n\tnewTab();\n\tlet tabs = document.querySelector(\".tabs\");\n\ttabs.addEventListener(\"wheel\", e => {\n\t\tif (e.deltaY > 0) {\n\t\t\ttabs.scrollLeft += 100;\n\t\t} else {\n\t\t\ttabs.scrollLeft -= 100;\n\t\t}\n\t});\n};\n\ndocument.querySelector(\".refresh-button\").addEventListener(\"click\", () => {\n\tconst activeTabContent = document.querySelector(\".tab-content.active\");\n\twindow.parent.tb.mediaplayer.hide();\n\tactiveTabContent.contentWindow.location.reload();\n});\n\ndocument.querySelector(\".navigate-back\").addEventListener(\"click\", () => {\n\tif (window.history.canGoBack) {\n\t\tdocument.querySelector(\".right-arrow\").classList.remove(\"disabled\");\n\t\twindow.parent.tb.mediaplayer.hide();\n\t\twindow.history.back();\n\t}\n});\n\ndocument.querySelector(\".ext-btn\").addEventListener(\"click\", () => {\n\tconst activeTabContent = document.querySelector(\".tab-content.active\");\n\tactiveTabContent.contentWindow.location.href = \"/apps/browser.tapp/userscripts.html\";\n\tconst activeUrlbar = document.querySelector(\".urlbar.active\");\n\tactiveUrlbar.value = \"about:userscripts\";\n});\n\ndocument.querySelector(\".navigate-forward\").addEventListener(\"click\", () => {\n\tif (window.history.canGoForward) {\n\t\twindow.parent.tb.mediaplayer.hide();\n\t\twindow.history.forward();\n\t}\n});\n\ndocument.querySelector(\".fav-button\").addEventListener(\"click\", async () => {\n\tconst activeTabContent = document.querySelector(\".tab-content.active\");\n\tconst dat = JSON.parse(await Filer.promises.readFile(`/apps/user/${await window.parent.tb.user.username()}/browser/favorites.json`, \"utf8\"));\n\tconst favicon = activeTabContent.contentDocument.querySelector(\"link[rel~='icon']\")?.href || activeTabContent.contentDocument.querySelector(\"link[rel='shortcut icon']\")?.href || \"/apps/browser.tapp/icon.svg\";\n\tdat.push({\n\t\ttitle: activeTabContent.contentDocument.title || \"Untitled\",\n\t\ticon: favicon,\n\t\turl: await tb.proxy.decode(activeTabContent.src.replace(window.location.origin, \"\").replace(/\\/uv\\/service\\/|\\/service\\//, \"\"), \"XOR\"),\n\t});\n\tawait Filer.promises.writeFile(`/apps/user/${await window.parent.tb.user.username()}/browser/favorites.json`, JSON.stringify(dat));\n});\n\nconst pwaIns = async () => {\n\tconst activeTabContent = document.querySelector(\".tab-content.active\");\n\tconst pageTitle = activeTabContent.contentDocument.title || \"Untitled\";\n\tconst maxTitleLength = 8;\n\tlet tabTitle;\n\tif (pageTitle.includes(\" - \")) {\n\t\ttabTitle = pageTitle.split(\" - \")[0].trim();\n\t} else if (pageTitle.includes(\"|\")) {\n\t\ttabTitle = pageTitle.split(\"|\")[0].trim();\n\t} else {\n\t\ttabTitle = pageTitle.split(\" \")[0].trim();\n\t}\n\tif (!tabTitle) {\n\t\ttabTitle = pageTitle.length > maxTitleLength ? pageTitle.substring(0, maxTitleLength) : pageTitle;\n\t}\n\tconst favicon = activeTabContent.contentDocument.querySelector(\"link[rel~='icon']\")?.href || activeTabContent.contentDocument.querySelector(\"link[rel='shortcut icon']\")?.href || \"/apps/browser.tapp/icon.svg\";\n\tlet data = JSON.parse(await Filer.promises.readFile(\"/apps/web_apps.json\", \"utf8\"));\n\tawait Filer.exists(`/apps/user/${await window.parent.tb.user.username()}/${tabTitle}/index.json`, async exists => {\n\t\tlet apps = data.apps;\n\t\tif (apps.includes(tabTitle.toLowerCase()) || exists) {\n\t\t\tlet index = apps.indexOf(tabTitle.toLowerCase());\n\t\t\tapps.splice(index, 1);\n\t\t\tdata.apps = apps;\n\t\t\tawait Filer.promises.writeFile(\"/apps/web_apps.json\", JSON.stringify(data));\n\t\t\twindow.parent.tb.launcher.removeApp(tabTitle);\n\t\t\tawait new Filer.Shell().promises.rm(`/apps/user/${await window.parent.tb.user.username()}/${tabTitle}`, { recursive: true });\n\t\t} else {\n\t\t\tapps.push(tabTitle.toLowerCase());\n\t\t\tawait Filer.promises.writeFile(\"/apps/web_apps.json\", JSON.stringify(data));\n\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\tmessage: `${tabTitle} has been installed!`,\n\t\t\t\tapplication: \"App Store\",\n\t\t\t\ticonSrc: \"/fs/apps/system/app store.tapp/icon.svg\",\n\t\t\t\ttime: 5000,\n\t\t\t});\n\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\ttitle: tabTitle,\n\t\t\t\tname: tabTitle,\n\t\t\t\ticon: `${window.location.origin}/uv/service/${await window.parent.tb.proxy.encode(favicon, \"XOR\")}`,\n\t\t\t\tsrc: await window.parent.tb.proxy.decode(\n\t\t\t\t\tdocument\n\t\t\t\t\t\t.querySelector(\".tab-content.active\")\n\t\t\t\t\t\t.src.replace(window.location.origin, \"\")\n\t\t\t\t\t\t.replace(/\\/uv\\/service\\/|\\/service\\//, \"\"),\n\t\t\t\t\t\"XOR\",\n\t\t\t\t),\n\t\t\t\tsize: {\n\t\t\t\t\twidth: 600,\n\t\t\t\t\theight: 500,\n\t\t\t\t},\n\t\t\t\tproxy: true,\n\t\t\t});\n\t\t\tconsole.log(tabTitle);\n\t\t}\n\t});\n};\n\nconst showTabs = () => {\n\tif (document.querySelector(\".tab-container\").style.display === \"none\") {\n\t\tdocument.querySelector(\".tab-container\").style.display = \"flex\";\n\t\tdocument.querySelector(\"main\").style.height = `calc(100% - calc(var(--topbar-height) + 10px))`;\n\t\tdocument.querySelector(\".controls\").style.marginTop = \"0px\";\n\t} else {\n\t\tdocument.querySelector(\".controls\").style.marginTop = \"5px\";\n\t\tdocument.querySelector(\"main\").style.height = `100%`;\n\t\tdocument.querySelector(\".tab-container\").style.display = \"none\";\n\t}\n};\n\nconst newengine = () => {\n\twindow.parent.tb.dialog.Message({\n\t\ttitle: \"Enter a new search engine\",\n\t\tdefaultValue: \"https://google.com/search?q=\",\n\t\tonOk: value => {\n\t\t\tlocalStorage.setItem(\"sEngine\", value);\n\t\t},\n\t});\n};\n\nconst nt = () => {\n\twindow.parent.tb.dialog.Message({\n\t\ttitle: \"Enter a new start page\",\n\t\tdefaultValue: \"about:newtab\",\n\t\tonOk: value => {\n\t\t\tlocalStorage.setItem(\"defUrl\", value);\n\t\t},\n\t});\n};\n\ndocument.querySelector(\".opt-menu\").addEventListener(\"click\", () => {\n\tconst rect = document.querySelector(\".opt-menu\").getBoundingClientRect();\n\twindow.parent.tb.contextmenu.create({\n\t\tx: rect.x - 150,\n\t\ty: rect.y + 125,\n\t\toptions: [\n\t\t\t{\n\t\t\t\ttext: \"Toggle Tabs\",\n\t\t\t\tclick: () => showTabs(),\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Add site as PWA (beta)\",\n\t\t\t\tclick: async () => pwaIns(),\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Change default search engine\",\n\t\t\t\tclick: async () => newengine(),\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Change new tab page\",\n\t\t\t\tclick: async () => nt(),\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Toggle Eruda\",\n\t\t\t\tclick: () => {\n\t\t\t\t\tif (typeof window.eruda === \"undefined\") {\n\t\t\t\t\t\teruda.init();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (window.eruda._isInit) {\n\t\t\t\t\t\t\twindow.eruda.destroy();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twindow.eruda.init();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n});\n\nwindow.addEventListener(\"keydown\", e => {\n\tif (e.altKey && e.key.toLowerCase() === \"t\") {\n\t\tnewTab();\n\t} else if (e.altKey && e.key.toLowerCase() === \"w\") {\n\t\tcloseTab(document.querySelector(\".tab.active\").id);\n\t} else if (e.altKey && e.key.toLowerCase() === \"r\") {\n\t\tconst activeTabContent = document.querySelector(\".tab-content.active\");\n\t\twindow.parent.tb.mediaplayer.hide();\n\t\tactiveTabContent.contentWindow.location.reload();\n\t} else if (e.altKey && e.key.toLowerCase() === \"b\") {\n\t\tpwaIns();\n\t} else if (e.altKey && e.key.toLowerCase() === \"k\") {\n\t\tshowTabs();\n\t} else if (e.altKey && e.key.toLowerCase() === \"i\") {\n\t\tif (typeof window.eruda === \"undefined\") {\n\t\t\teruda.init();\n\t\t} else {\n\t\t\tif (window.eruda._isInit) {\n\t\t\t\twindow.eruda.destroy();\n\t\t\t} else {\n\t\t\t\twindow.eruda.init();\n\t\t\t}\n\t\t}\n\t}\n});\n"
  },
  {
    "path": "public/apps/browser.tapp/index.json",
    "content": "{\n\t\"name\": \"Browser\",\n\t\"proxy\": false,\n\t\"config\": {\n\t\t\"title\": \"Browser\",\n\t\t\"icon\": \"/apps/browser.tapp/icon.svg\",\n\t\t\"src\": \"/apps/browser.tapp/index.html\"\n\t}\n}\n"
  },
  {
    "path": "public/apps/browser.tapp/newtab.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>New Tab</title>\n    <link rel=\"stylesheet\" href=\"./index.css\">\n    <script src=\"/assets/libs/tailwind.min.js\"></script>\n    <script>\n        const main = async () => {\n            const favs = JSON.parse(await window.parent.parent.tb.fs.promises.readFile(`/apps/user/${await window.parent.tb.user.username()}/browser/favorites.json`, \"utf8\"))\n            const settings = JSON.parse(await window.parent.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"))\n            const favsDiv = document.getElementById(\"favs\");\n            for (const item of favs) {\n                console.log(item);\n                const div = document.createElement(\"div\");\n                div.className = \"flex flex-col bg-[#ffffff0a] w-35 h-18 rounded-[6px]\";\n                div.onclick = async () => {\n                    window.dispatchEvent(new Event(\"updTab\"))\n                    if (settings.proxy === \"Scramjet\") {\n                        window.location.href = `${window.location.origin}/service/${await window.parent.tb.proxy.encode(item.url, \"XOR\")}`;\n                    } else {\n                        window.location.href = `${window.location.origin}/uv/service/${await window.parent.tb.proxy.encode(item.url, \"XOR\")}`;\n                    }\n                }\n                const img = document.createElement(\"img\");\n                img.src = item.icon.startsWith(\"/apps/\") ? \"/apps/browser.tapp/icon.svg\" : `/service/${await window.parent.tb.proxy.encode(item.icon, \"XOR\")}`;\n                img.className = \"w-full h-10 object-cover rounded-t-[6px]\";\n                const h1 = document.createElement(\"h1\");\n                h1.textContent = item.title;\n                h1.className = \"text-lg font-bold text-center mt-2\";\n                div.appendChild(img);\n                div.appendChild(h1);\n                favsDiv.appendChild(div);\n            }\n            document.getElementById(\"inp\").addEventListener(\"keydown\", async (e) => {\n                if (e.key === \"Enter\") {\n                    window.dispatchEvent(new Event(\"updTab\"));\n                    const input = e.target.value.trim();\n                    const searchEngine = localStorage.getItem(\"sEngine\") || \"https://google.com/search?q=\";\n                    const isUrl = /^(https?:\\/\\/)|(localhost(:\\d+)?([\\/?]|$))|([a-z0-9\\-]+\\.[a-z]{2,})/i.test(input) && !/\\s/.test(input);\n                    let url;\n                    if (isUrl) {\n                        url = input.startsWith(\"http\") ? input : `https://${input}`;\n                    } else {\n                        url = `${searchEngine}${encodeURIComponent(input)}`;\n                    }\n                    if (settings.proxy === \"Scramjet\") {\n                        window.location.href = `${window.location.origin}/service/${await window.parent.tb.proxy.encode(url, \"XOR\")}`;\n                    } else {\n                        window.location.href = `${window.location.origin}/uv/service/${await window.parent.tb.proxy.encode(url, \"XOR\")}`;\n                    }\n                }\n            });\n        }\n        main();\n    </script>\n</head>\n<body>\n    <div class=\"flex flex-col items-center justify-center gap-4 mt-10\">\n        <span class=\"text-4xl font-black text-center text-accent text-stroke\">Terbium Browser</span>\n        <input type=\"text\" placeholder=\"Search or enter address\" class=\"w-1/2 rounded-[6px] px-[10px] py-[8px] text-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:placeholder-[#ffffff48] focus:text-[#ffffff] focus:outline-hidden focus:ring-2\" id=\"inp\">\n        <div class=\"flex gap-4\" id=\"favs\">\n            \n        </div>\n    </div>\n</body>\n</html>"
  },
  {
    "path": "public/apps/browser.tapp/userscripts.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Userscripts</title>\n    <link rel=\"stylesheet\" href=\"./index.css\">\n    <script src=\"/assets/libs/tailwind.min.js\"></script>\n    <script>\n        const getMeta = async (value) => {\n            const metaMatch = /\\/\\/\\s*==UserScript==([\\s\\S]*?)\\/\\/\\s*==\\/UserScript==/;\n            if (!(metaMatch.test(value))) {\n                window.parent.tb.dialog.Alert({ title: \"Userscript error\", message: \"Could not find meta info for the userscript.\" });\n                return;\n            };\n            const meta = value.match(/\\/\\/\\s*==UserScript==([\\s\\S]*?)\\/\\/\\s*==\\/UserScript==/);\n            const metaObj = meta[1].split(\"\\n\")\n            const m = {}\n            for (let l of metaObj) {\n                const match = l.match(/^\\s*\\/\\/\\s*@(\\S+)\\s+(.*)$/)\n                if (match) {\n                    const [, key, value] = match;\n                    if (m[key]) {\n                        if (Array.isArray(m[key])) {\n                            m[key].push(value);\n                        } else {\n                            m[key] = [m[key], value];\n                        }\n                    } else {\n                        m[key] = value;\n                    }\n                }\n            }\n            return m\n        }\n\n        const getAll = async () => {\n            const ujsDiv = document.getElementById(\"ujs\");\n            const userScripts = JSON.parse(await window.parent.parent.tb.fs.promises.readFile(`/apps/user/${await window.parent.tb.user.username()}/browser/userscripts.json`, \"utf8\"))\n            if (userScripts.length === 0) {\n                ujsDiv.innerHTML = \"<span class='text-gray-500'>No UserScripts found</span>\";\n            } else {\n                ujsDiv.innerHTML = \"\";\n                for (const ext of userScripts) {\n                    const div = document.createElement(\"div\");\n                    div.className = \"flex flex-col bg-[#ffffff0a] w-50 h-35 rounded-[6px]\";\n                    div.innerHTML = `<img class=\"w-full h-10 object-cover rounded-t-[6px]\" src=\"/service/${await window.parent.tb.proxy.encode(ext.icon, \"XOR\")}\" /><h1 class=\"text-lg font-bold text-center mt-2\" title=\"${ext.name}\">${ext.name.length > 16 ? ext.name.slice(0, 16) + \"...\" : ext.name}</h1><h5>Author: ${ext.author}</h5><h5>License: ${ext.license}</h5><h5>Version: ${ext.version}</h5>`;\n                    ujsDiv.appendChild(div);\n                }\n            }\n        }\n\n        document.addEventListener(\"DOMContentLoaded\", () => {\n            getAll();\n            document.getElementById(\"ujs-load\").addEventListener(\"click\", async () => {\n                window.parent.tb.dialog.Select({\n                    title: \"Import userscripts from directory or file?\",\n                    options: [{\n                      text: \"Directory\",\n                      value: \"d\"\n                    }, {\n                      text: \"File\",\n                      value: \"f\"\n                    }],\n                    onOk: async (val) => {\n                        const userScripts = JSON.parse(await window.parent.parent.tb.fs.promises.readFile(`/apps/user/${await window.parent.tb.user.username()}/browser/userscripts.json`, \"utf8\"));\n                        switch(val) {\n                            case 'f':\n                                window.parent.tb.dialog.FileBrowser({\n                                    title: \"Select a Userscript to load\",\n                                    defualtDir: \"//\",\n                                    onOk: async (path) => {\n                                        const fileData = await window.parent.parent.tb.fs.promises.readFile(path, \"utf8\");\n                                        const scrMeta = await getMeta(fileData);\n                                        userScripts.push({\n                                            name: scrMeta.name || \"userscript\",\n                                            version: scrMeta.version || \"1.0\",\n                                            author: scrMeta.author || \"Unknown\",\n                                            license: scrMeta.license || \"Unknown\",\n                                            match: scrMeta.match || \"*\",\n                                            file: path,\n                                            icon: scrMeta.icon || \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=\",\n                                        })\n                                        await window.parent.parent.tb.fs.promises.writeFile(`/apps/user/${await window.parent.tb.user.username()}/browser/userscripts.json`, JSON.stringify(userScripts, null, 2));\n                                        getAll();\n                                    }\n                                });\n                                break;\n                            case 'd':\n                                window.parent.tb.dialog.DirectoryBrowser({\n                                    title: \"Select a Userscript to load\",\n                                    defualtDir: \"//\",\n                                    onOk: async (path) => {\n                                        const dir = await window.parent.parent.tb.fs.promises.readdir(path);\n                                        for (const file of dir) {\n                                            if (file.endsWith(\".user.js\")) {\n                                                const fileData = await window.parent.parent.tb.fs.promises.readFile(`${path}/${file}`, \"utf8\");\n                                                const scrMeta = await getMeta(fileData);\n                                                userScripts.push({\n                                                    name: scrMeta.name || \"userscript\",\n                                                    version: scrMeta.version || \"1.0\",\n                                                    author: scrMeta.author || \"Unknown\",\n                                                    license: scrMeta.license || \"Unknown\",\n                                                    match: scrMeta.match || \"*\",\n                                                    file: `${path}/${file}`,\n                                                    icon: scrMeta.icon || \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=\",\n                                                })\n                                            };\n                                        };\n                                        await window.parent.parent.tb.fs.promises.writeFile(`/apps/user/${await window.parent.tb.user.username()}/browser/userscripts.json`, JSON.stringify(userScripts, null, 2))\n                                        getAll()\n                                    }\n                                });\n                                break;\n                            default:\n                                break;\n                        }\n                    }\n                })\n            });\n\n            document.getElementById(\"ujs-rm\").addEventListener(\"click\", async () => {\n                const userScripts = JSON.parse(await window.parent.parent.tb.fs.promises.readFile(`/apps/user/${await window.parent.tb.user.username()}/browser/userscripts.json`, \"utf8\"))\n                let opts = []\n                for (const sr of userScripts) {\n                    opts.push({\n                        text: sr.name,\n                        value: sr\n                    })\n                }\n                window.parent.tb.dialog.Select({\n                    title: \"Select a Userscript to remove\",\n                    options: opts,\n                    onOk: async (val) => {\n                        const idx = userScripts.indexOf(val);\n                        if (idx !== -1) {\n                            userScripts.splice(idx, 1);\n                            await window.parent.parent.tb.fs.promises.writeFile(`/apps/user/${await window.parent.tb.user.username()}/browser/userscripts.json`, JSON.stringify(userScripts, null, 2));\n                            getAll();\n                        }\n                    }\n                })\n            })\n        });\n    </script>\n</head>\n<body>\n    <div class=\"flex flex-col items-center justify-center gap-4 mt-5\">\n        <div class=\"flex flex-row gap-5\">\n            <span class=\"text-4xl font-black text-center text-accent text-stroke\">UserScripts (beta)</span>\n            <button id=\"ujs-load\">Load</button>\n            <button id=\"ujs-rm\">UnLoad</button>\n        </div>         \n        <div class=\"flex gap-4\" id=\"ujs\"></div>\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/apps/calculator.tapp/index.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(\"/fonts/Inter.ttf\");\n}\n\nhtml,\nbody {\n\theight: 100%;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: hidden;\n}\n\nbody {\n\tfont-family: Inter;\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tline-height: 20px;\n\tcolor: #ffffff;\n\tbackground-color: transparent;\n}\n\n.topbar {\n\theight: 96px;\n\tpadding: 0 10px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n}\n\n.eq {\n\twidth: 100%;\n\tbackground-color: transparent;\n\tborder: none;\n\toutline: none;\n\tpointer-events: none;\n\tcolor: #ffffff;\n\tfont-family: Inter;\n\tfont-size: 48px;\n\tfont-weight: 800;\n\tline-height: 48px;\n\ttext-align: right;\n}\n\n.buttons {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(4, 1fr);\n\tgrid-template-rows: repeat(5, 1fr);\n\tbackground-color: #ffffff28;\n\tgap: 6px;\n\tpadding: 8px;\n\tborder-bottom-left-radius: 10px;\n\tborder-bottom-right-radius: 10px;\n\toverflow: hidden;\n}\n\n.buttons button {\n\twidth: 76px;\n\theight: 76px;\n\tbackground-color: transparent;\n\tborder: none;\n\tborder-radius: 10px;\n\toutline: none;\n\tfont-family: Inter;\n\tfont-size: 20px;\n\tfont-weight: 800;\n\tline-height: 20px;\n\tpadding: 0;\n\tcolor: #ffffff;\n\tcursor: var(--cursor-pointer);\n\ttransition: 150ms ease-in-out;\n}\n\n.buttons button svg {\n\twidth: 26px;\n\theight: 26px;\n}\n\n.buttons button:hover {\n\tbackground-color: #ffffff42;\n}\n\n.buttons button.two-space {\n\tgrid-column: span 2;\n\twidth: 158px;\n}\n"
  },
  {
    "path": "public/apps/calculator.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Calculator</title>\n    <link rel=\"stylesheet\" href=\"index.css\">\n    <script src=\"/assets/libs/math.min.js\"></script>\n</head>\n    <body>\n        <div class=\"topbar\">\n            <input type=\"text\" class=\"eq\" disabled is-equal=\"false\" value=\"0\" />\n        </div>\n        <div class=\"buttons\">\n            <button class=\"cbtn\" data-value=\"del\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                    <path fill-rule=\"evenodd\" d=\"M2.515 10.674a1.875 1.875 0 0 0 0 2.652L8.89 19.7c.352.351.829.549 1.326.549H19.5a3 3 0 0 0 3-3V6.75a3 3 0 0 0-3-3h-9.284c-.497 0-.974.198-1.326.55l-6.375 6.374ZM12.53 9.22a.75.75 0 1 0-1.06 1.06L13.19 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06l1.72-1.72 1.72 1.72a.75.75 0 1 0 1.06-1.06L15.31 12l1.72-1.72a.75.75 0 1 0-1.06-1.06l-1.72 1.72-1.72-1.72Z\" clip-rule=\"evenodd\" />\n                </svg>\n            </button>\n            <button class=\"cbtn\" data-value=\"clear\">ac</button>\n            <button class=\"cbtn\" data-value=\"np\">+/-</button>\n            <button class=\"cbtn\" data-value=\"/\">/</button>\n            <button class=\"cbtn\" data-value=\"7\">7</button>\n            <button class=\"cbtn\" data-value=\"8\">8</button>\n            <button class=\"cbtn\" data-value=\"9\">9</button>\n            <button class=\"cbtn\" data-value=\"*\">*</button>\n            <button class=\"cbtn\" data-value=\"4\">4</button>\n            <button class=\"cbtn\" data-value=\"5\">5</button>\n            <button class=\"cbtn\" data-value=\"6\">6</button>\n            <button class=\"cbtn\" data-value=\"-\">-</button>\n            <button class=\"cbtn\" data-value=\"1\">1</button>\n            <button class=\"cbtn\" data-value=\"2\">2</button>\n            <button class=\"cbtn\" data-value=\"3\">3</button>\n            <button class=\"cbtn\" data-value=\"+\">+</button>\n            <button class=\"cbtn\" data-value=\"0\">0</button>\n            <button class=\"cbtn\" data-value=\".\">.</button>\n            <button class=\"cbtn two-space\" data-value=\"equal\">=</button>\n        </div>\n        <script src=\"index.js\"></script>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/calculator.tapp/index.js",
    "content": "document.querySelectorAll(\".cbtn\").forEach(btn => {\n\tbtn.addEventListener(\"click\", () => {\n\t\tconst display = document.querySelector(\".eq\");\n\t\tconst value = btn.getAttribute(\"data-value\");\n\t\tswitch (value) {\n\t\t\tdefault:\n\t\t\t\tconst displayValue = display.value;\n\t\t\t\tconst newValue = displayValue + value;\n\t\t\t\tif (display.value === \"0\") {\n\t\t\t\t\tif (value === \".\") return;\n\t\t\t\t\tif (value === \"0\") return;\n\t\t\t\t\tif (value === \"+\") return;\n\t\t\t\t\tif (value === \"-\") return;\n\t\t\t\t\tif (value === \"*\") return;\n\t\t\t\t\tif (value === \"/\") return;\n\t\t\t\t}\n\t\t\t\tif (display.getAttribute(\"is-equal\") === \"true\") {\n\t\t\t\t\tdisplay.value = \"\";\n\t\t\t\t\tdisplay.value = value;\n\t\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\t} else {\n\t\t\t\t\tif (display.value === \"0\") {\n\t\t\t\t\t\tdisplay.value = value;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdisplay.value = newValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"clear\":\n\t\t\t\tdisplay.value = \"0\";\n\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\tbreak;\n\t\t\tcase \"del\":\n\t\t\t\tif (display.value.length === 1) {\n\t\t\t\t\tdisplay.value = \"0\";\n\t\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tconst delValue = display.value.slice(0, -1);\n\t\t\t\t\tdisplay.value = delValue;\n\t\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"equal\":\n\t\t\t\tconst eqValue = math.evaluate(display.value);\n\t\t\t\tdisplay.value = eqValue;\n\t\t\t\tdisplay.setAttribute(\"is-equal\", \"true\");\n\t\t\t\tbreak;\n\t\t\tcase \"np\":\n\t\t\t\tconst npValue = display.value * -1;\n\t\t\t\tdisplay.value = npValue;\n\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\tbreak;\n\t\t}\n\t});\n});\n\nwindow.addEventListener(\"keydown\", e => {\n\tlet availableKeys = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \".\", \"+\", \"-\", \"*\", \"/\", \"%\", \"Backspace\", \"Enter\"];\n\tif (availableKeys.includes(e.key)) {\n\t\tconst display = document.querySelector(\".eq\");\n\t\tconst value = e.key;\n\t\tswitch (value) {\n\t\t\tdefault:\n\t\t\t\tconst displayValue = display.value;\n\t\t\t\tconst newValue = displayValue + value;\n\t\t\t\tif (display.getAttribute(\"is-equal\") == \"true\") {\n\t\t\t\t\tdisplay.value = \"\";\n\t\t\t\t\tdisplay.value = value;\n\t\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\t} else {\n\t\t\t\t\tif (display.value === \"0\") {\n\t\t\t\t\t\tconsole.log(\"zero\");\n\t\t\t\t\t\tdisplay.value = value;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdisplay.value = newValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"-\":\n\t\t\t\tif (display.value === \"0\") return;\n\t\t\t\tdisplay.value = display.value + value;\n\t\t\t\tif (display.getAttribute(\"is-equal\") == \"true\") display.setAttribute(\"is-equal\", \"false\");\n\t\t\t\tbreak;\n\t\t\tcase \"+\":\n\t\t\t\tif (display.value === \"0\") return;\n\t\t\t\tdisplay.value = display.value + value;\n\t\t\t\tif (display.getAttribute(\"is-equal\") == \"true\") display.setAttribute(\"is-equal\", \"false\");\n\t\t\t\tbreak;\n\t\t\tcase \"*\":\n\t\t\t\tif (display.value === \"0\") return;\n\t\t\t\tdisplay.value = display.value + value;\n\t\t\t\tif (display.getAttribute(\"is-equal\") == \"true\") display.setAttribute(\"is-equal\", \"false\");\n\t\t\t\tbreak;\n\t\t\tcase \"/\":\n\t\t\t\te.preventDefault();\n\t\t\t\tif (display.value === \"0\") return;\n\t\t\t\tdisplay.value = display.value + value;\n\t\t\t\tif (display.getAttribute(\"is-equal\") == \"true\") display.setAttribute(\"is-equal\", \"false\");\n\t\t\t\tbreak;\n\t\t\tcase \"Backspace\":\n\t\t\t\tif (display.value.length === 1) {\n\t\t\t\t\tdisplay.value = \"0\";\n\t\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tconst delValue = display.value.slice(0, -1);\n\t\t\t\t\tdisplay.value = delValue;\n\t\t\t\t\tdisplay.setAttribute(\"is-equal\", \"false\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"Enter\":\n\t\t\t\te.preventDefault();\n\t\t\t\tconst eqValue = math.evaluate(display.value);\n\t\t\t\tdisplay.value = eqValue;\n\t\t\t\tdisplay.setAttribute(\"is-equal\", \"true\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n});\n"
  },
  {
    "path": "public/apps/calculator.tapp/index.json",
    "content": "{\n\t\"name\": \"Calculator\",\n\t\"config\": {\n\t\t\"title\": \"Calculator\",\n\t\t\"icon\": \"/fs/apps/system/calculator.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/calculator.tapp/index.html\",\n\t\t\"snapable\": false,\n\t\t\"maximizable\": false,\n\t\t\"size\": {\n\t\t\t\"width\": 338,\n\t\t\t\"height\": 556\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "public/apps/feedback.tapp/index.json",
    "content": "{\n\t\"name\": \"Feedback\",\n\t\"config\": {\n\t\t\"title\": \"Feedback\",\n\t\t\"icon\": \"/fs/apps/system/feedback.tapp/icon.svg\",\n\t\t\"src\": \"https://forms.gle/m664xxmrugWQADQt9\",\n\t\t\"proxy\": true,\n\t\t\"size\": {\n\t\t\t\"width\": 600,\n\t\t\t\"height\": 500\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "public/apps/files.tapp/cm.css",
    "content": "@keyframes fade-in {\n\t0% {\n\t\topacity: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes fade-out {\n\t0% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n\n.fade-in {\n\tanimation: fade-in 150ms ease-in-out forwards;\n}\n\n.fade-out {\n\tanimation: fade-out 150ms ease-in-out forwards;\n}\n\nbody .context-menu {\n\tbackground-color: #ffffff1f;\n\tcolor: #ffffff;\n}\n\nbody.blurry .context-menu {\n\tbackdrop-filter: blur(100px);\n}\n\n.context-menu {\n\tposition: absolute;\n\tbackground-color: #ffffff1f;\n\tcolor: #fff;\n\tz-index: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 200px;\n\tborder-radius: 8px;\n\toverflow: hidden;\n\tbackdrop-filter: blur(10px);\n}\n\n.context-menu-button {\n\tbackground-color: transparent;\n\tdisplay: flex;\n\tfont-size: 15.5px;\n\tfont-weight: 700;\n\tline-height: 18px;\n\tpadding: 8px 10px;\n\tborder-color: transparent;\n\ttransition: 150ms ease-in-out;\n\tcursor: var(--cursor-pointer);\n}\n\nbody .context-menu-button:hover {\n\tbackground-color: #ffffff1f;\n}\n"
  },
  {
    "path": "public/apps/files.tapp/extensions.json",
    "content": "{\n\t\"image\": [\n\t\t\"ase\",\n\t\t\"art\",\n\t\t\"bmp\",\n\t\t\"blp\",\n\t\t\"cd5\",\n\t\t\"cit\",\n\t\t\"cpt\",\n\t\t\"cr2\",\n\t\t\"cut\",\n\t\t\"dds\",\n\t\t\"dib\",\n\t\t\"djvu\",\n\t\t\"egt\",\n\t\t\"exif\",\n\t\t\"gif\",\n\t\t\"gpl\",\n\t\t\"grf\",\n\t\t\"icns\",\n\t\t\"ico\",\n\t\t\"iff\",\n\t\t\"jng\",\n\t\t\"jpeg\",\n\t\t\"jpg\",\n\t\t\"jfif\",\n\t\t\"jp2\",\n\t\t\"jps\",\n\t\t\"lbm\",\n\t\t\"max\",\n\t\t\"miff\",\n\t\t\"mng\",\n\t\t\"msp\",\n\t\t\"nef\",\n\t\t\"nitf\",\n\t\t\"ota\",\n\t\t\"pbm\",\n\t\t\"pc1\",\n\t\t\"pc2\",\n\t\t\"pc3\",\n\t\t\"pcf\",\n\t\t\"pcx\",\n\t\t\"pdn\",\n\t\t\"pgm\",\n\t\t\"PI1\",\n\t\t\"PI2\",\n\t\t\"PI3\",\n\t\t\"pict\",\n\t\t\"pct\",\n\t\t\"pnm\",\n\t\t\"pns\",\n\t\t\"ppm\",\n\t\t\"psb\",\n\t\t\"psd\",\n\t\t\"pdd\",\n\t\t\"psp\",\n\t\t\"px\",\n\t\t\"pxm\",\n\t\t\"pxr\",\n\t\t\"qfx\",\n\t\t\"raw\",\n\t\t\"rle\",\n\t\t\"sct\",\n\t\t\"sgi\",\n\t\t\"rgb\",\n\t\t\"int\",\n\t\t\"bw\",\n\t\t\"tga\",\n\t\t\"tiff\",\n\t\t\"tif\",\n\t\t\"vtf\",\n\t\t\"xbm\",\n\t\t\"xcf\",\n\t\t\"xpm\",\n\t\t\"3dv\",\n\t\t\"amf\",\n\t\t\"ai\",\n\t\t\"awg\",\n\t\t\"cgm\",\n\t\t\"cdr\",\n\t\t\"cmx\",\n\t\t\"dxf\",\n\t\t\"e2d\",\n\t\t\"egt\",\n\t\t\"eps\",\n\t\t\"fs\",\n\t\t\"gbr\",\n\t\t\"odg\",\n\t\t\"svg\",\n\t\t\"stl\",\n\t\t\"vrml\",\n\t\t\"x3d\",\n\t\t\"sxd\",\n\t\t\"v2d\",\n\t\t\"vnd\",\n\t\t\"wmf\",\n\t\t\"emf\",\n\t\t\"art\",\n\t\t\"xar\",\n\t\t\"png\",\n\t\t\"webp\",\n\t\t\"jxr\",\n\t\t\"hdp\",\n\t\t\"wdp\",\n\t\t\"cur\",\n\t\t\"ecw\",\n\t\t\"iff\",\n\t\t\"lbm\",\n\t\t\"liff\",\n\t\t\"nrrd\",\n\t\t\"pam\",\n\t\t\"pcx\",\n\t\t\"pgf\",\n\t\t\"sgi\",\n\t\t\"rgb\",\n\t\t\"rgba\",\n\t\t\"bw\",\n\t\t\"int\",\n\t\t\"inta\",\n\t\t\"sid\",\n\t\t\"ras\",\n\t\t\"sun\",\n\t\t\"tga\",\n\t\t\"heic\",\n\t\t\"heif\"\n\t],\n\t\"video\": [\"3g2\", \"3gp\", \"aaf\", \"asf\", \"avchd\", \"avi\", \"drc\", \"flv\", \"m2v\", \"m3u8\", \"m4p\", \"m4v\", \"mkv\", \"mng\", \"mov\", \"mp2\", \"mp4\", \"mpe\", \"mpeg\", \"mpg\", \"mpv\", \"mxf\", \"nsv\", \"ogg\", \"ogv\", \"qt\", \"rm\", \"rmvb\", \"roq\", \"svi\", \"vob\", \"webm\", \"wmv\", \"yuv\"],\n\t\"audio\": [\"aac\", \"aiff\", \"ape\", \"it\", \"m3u\", \"m4a\", \"mid\", \"mod\", \"mp2\", \"mp3\", \"mpa\", \"oga\", \"ogg\", \"opus\", \"s3m\", \"sid\", \"wav\", \"weba\", \"xm\"],\n\t\"extractables\": [\"zip\", \"gz\", \"deflate\"],\n\t\"text\": [\"json\", \"ts\", \"js\", \"css\", \"cpp\", \"py3\", \"py\", \"java\", \"c\", \"env\", \"txt\", \"text\"],\n\t\"animated\": [\"gif\", \"apng\", \"webp\", \"flif\", \"mng\"],\n\t\"pdf\": [\"pdf\", \"pdfa\", \"pdfx\"]\n}\n"
  },
  {
    "path": "public/apps/files.tapp/files.com.js",
    "content": "const tb = parent.window.tb;\nconst tb_island = tb.window.island;\nconst tb_window = tb.window;\nconst tb_context_menu = tb.context_menu;\nconst tb_dialog = tb.dialog;\n\nconst appIsland = window.parent.document.querySelector(\".app_island\");\n\ntb_island.addControl({\n\ttext: \"File\",\n\tappname: \"Files\",\n\tid: \"files_file\",\n\tclick: () => {\n\t\tlet isTrash = document.querySelector(\".exp\").getAttribute(\"path\") === \"/home/trash\" ? true : false;\n\t\ttb.contextmenu.create({\n\t\t\tx: 6,\n\t\t\ty: appIsland.clientHeight + 12,\n\t\t\tiframe: false,\n\t\t\toptions: [\n\t\t\t\tisTrash\n\t\t\t\t\t? null\n\t\t\t\t\t: {\n\t\t\t\t\t\t\ttext: \"New File\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst response = await tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\ttitle: \"Enter a name for the new file\",\n\t\t\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\t\t\tonOk: async fileName => {\n\t\t\t\t\t\t\t\t\t\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\t\t\t\t\t\t\t\t\t\tconst createFile = async (path, fileName) => {\n\t\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.exists(`${path}/${fileName}`, async exists => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst ask = await tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `This file already exists. Enter a new name for ${fileName}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (ask !== undefined && ask !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait createFile(path, ask);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet sh = tb.sh;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait sh.touch(`${path}/${fileName}`, \"\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\topenPath(path);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\tawait createFile(path, fileName);\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\tconsole.error(error);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\tisTrash\n\t\t\t\t\t? null\n\t\t\t\t\t: {\n\t\t\t\t\t\t\ttext: \"New Folder\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\tconst response = await tb.dialog.Message({\n\t\t\t\t\t\t\t\t\ttitle: \"Enter a name for the new folder\",\n\t\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\t\tonOk: async response => {\n\t\t\t\t\t\t\t\t\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\t\t\t\t\t\t\t\t\tconst createUniqueFolder = async (path, folderName, number = null) => {\n\t\t\t\t\t\t\t\t\t\t\tconst folderPath = `${path}/${folderName}${number !== null ? ` (${number})` : \"\"}`;\n\t\t\t\t\t\t\t\t\t\t\tconst exists = await window.parent.tb.fs.promises.exists(folderPath);\n\t\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn createUniqueFolder(path, folderName, number !== null ? number + 1 : 2);\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(folderPath);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tawait createUniqueFolder(path, response);\n\t\t\t\t\t\t\t\t\t\topenPath(path);\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\tisTrash\n\t\t\t\t\t? null\n\t\t\t\t\t: {\n\t\t\t\t\t\t\ttext: \"Upload from Computer\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tconst fauxput = document.createElement(\"input\");\n\t\t\t\t\t\t\t\tfauxput.type = \"file\";\n\t\t\t\t\t\t\t\tfauxput.multiple = true;\n\t\t\t\t\t\t\t\tfauxput.onchange = async e => {\n\t\t\t\t\t\t\t\t\tfor (const file of e.target.files) {\n\t\t\t\t\t\t\t\t\t\tconst content = await file.arrayBuffer();\n\t\t\t\t\t\t\t\t\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\t\t\t\t\t\t\t\t\tconst filePath = `${path}/${file.name}`;\n\t\t\t\t\t\t\t\t\t\tif (await window.parent.tb.fs.promises.exists(filePath)) {\n\t\t\t\t\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: `File \"${file.name}\" already exists`,\n\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: file.name,\n\t\t\t\t\t\t\t\t\t\t\t\tonOk: async newFileName => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (newFileName !== null && newFileName !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`${path}/${newFileName}`, window.parent.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(filePath, window.parent.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfauxput.click();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t],\n\t\t});\n\t},\n});\n\ntb_island.addControl({\n\ttext: \"View\",\n\tappname: \"Files\",\n\tid: \"files_view\",\n\tclick: () => {\n\t\tconst options = [\n\t\t\t// {\n\t\t\t//     text: \"Settings\",\n\t\t\t//     click: () => {\n\t\t\t//         window.tb.window.create({\n\t\t\t//             title: `Settings`,\n\t\t\t//             icon: \"/apps/files.tapp/icon.svg\",\n\t\t\t//             src: \"/apps/files.tapp/settings.tapp/index.html\",\n\t\t\t//             controls: [\"minimize\", \"close\"]\n\t\t\t//         })\n\t\t\t//     }\n\t\t\t// },\n\t\t\t{\n\t\t\t\ttext: \"Go To\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tconst response = await tb.dialog.Message({\n\t\t\t\t\t\ttitle: \"Enter the directory to navigate to\",\n\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\tonOk: async response => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\topenPath(response);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\ttb.dialog.Alert({\n\t\t\t\t\t\t\t\t\ttitle: \"Error\",\n\t\t\t\t\t\t\t\t\tmessage: `Cannot find ${response}. Check your spelling and try again.`,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\ttb.contextmenu.create({\n\t\t\tx: 6,\n\t\t\ty: appIsland.clientHeight + 12,\n\t\t\tiframe: false,\n\t\t\toptions: options,\n\t\t});\n\t},\n});\n\ntb_island.addControl({\n\ttext: \"Network Storage\",\n\tappname: \"Files\",\n\tid: \"files_net\",\n\tclick: () => {\n\t\tconst options = [\n\t\t\t{\n\t\t\t\ttext: \"Map Dav Drive\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\ttitle: \"Enter a name for the Dav Drive\",\n\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\tonOk: async res1 => {\n\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\ttitle: \"Enter the URL for the Dav Drive\",\n\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\tonOk: async res2 => {\n\t\t\t\t\t\t\t\t\tawait tb.dialog.WebAuth({\n\t\t\t\t\t\t\t\t\t\ttitle: \"Enter the credentials for the Dav Drive\",\n\t\t\t\t\t\t\t\t\t\tonOk: async (username, password) => {\n\t\t\t\t\t\t\t\t\t\t\tconst davjson = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${await tb.user.username()}/files/davs.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\tdavjson.push({\n\t\t\t\t\t\t\t\t\t\t\t\tname: res1,\n\t\t\t\t\t\t\t\t\t\t\t\turl: res2,\n\t\t\t\t\t\t\t\t\t\t\t\tusername: username,\n\t\t\t\t\t\t\t\t\t\t\t\tpassword: password,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${await tb.user.username()}/files/davs.json`, JSON.stringify(davjson, null, 2));\n\t\t\t\t\t\t\t\t\t\t\tconst config = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${await tb.user.username()}/files/config.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\tconfig.drives[res1] = `/mnt/${res1}/`;\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${await tb.user.username()}/files/config.json`, JSON.stringify(config, null, 2));\n\t\t\t\t\t\t\t\t\t\t\tawait tb.notification.Toast({\n\t\t\t\t\t\t\t\t\t\t\t\tapplication: \"System\",\n\t\t\t\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\t\t\tmessage: \"New Dav Device has been added\",\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"updcfg\"));\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"UnMap Dav Drive\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\ttitle: \"Enter the name of the Dav Drive to remove\",\n\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\tonOk: async response => {\n\t\t\t\t\t\t\tconst davjson = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${await tb.user.username()}/files/davs.json`, \"utf8\"));\n\t\t\t\t\t\t\tconst index = davjson.findIndex(entry => entry.name.toLowerCase() === response.toLowerCase());\n\t\t\t\t\t\t\tif (index !== -1) {\n\t\t\t\t\t\t\t\tdavjson.splice(index, 1);\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${await tb.user.username()}/files/davs.json`, JSON.stringify(davjson, null, 2));\n\t\t\t\t\t\t\t\tconst config = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${await tb.user.username()}/files/config.json`, \"utf8\"));\n\t\t\t\t\t\t\t\tdelete config.drives[response.toLowerCase()];\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${await tb.user.username()}/files/config.json`, JSON.stringify(config, null, 2));\n\t\t\t\t\t\t\t\tawait tb.notification.Toast({\n\t\t\t\t\t\t\t\t\tapplication: \"System\",\n\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\tmessage: \"Dav Drive has been removed\",\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"updcfg\"));\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tawait tb.notification.Toast({\n\t\t\t\t\t\t\t\t\tapplication: \"System\",\n\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\tmessage: \"Dav Drive not found\",\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\ttb.contextmenu.create({\n\t\t\tx: 6,\n\t\t\ty: appIsland.clientHeight + 12,\n\t\t\tiframe: false,\n\t\t\toptions: options,\n\t\t});\n\t},\n});\n\ntb_island.addControl({\n\ttext: \"Empty Trash\",\n\tappname: \"Files\",\n\tid: \"files-et\",\n\tclick: () => {\n\t\temptyTrash();\n\t},\n});\n\nparent.document.querySelector(`[control-id=\"files-et\"]`)?.classList.add(\"hidden\");\n"
  },
  {
    "path": "public/apps/files.tapp/icons.json",
    "content": "{\n\t\"ext-to-name\": {\n\t\t\"js\": \"JavaScript\",\n\t\t\"html\": \"HTML\",\n\t\t\"css\": \"CSS\",\n\t\t\"php\": \"PHP\",\n\t\t\"py\": \"Python\",\n\t\t\"java\": \"Java\",\n\t\t\"c\": \"C\",\n\t\t\"h\": \"C\",\n\t\t\"c++\": \"C++\",\n\t\t\"cpp\": \"C++\",\n\t\t\"cxx\": \"C++\",\n\t\t\"cc\": \"C++\",\n\t\t\"c#\": \"C#\",\n\t\t\"cs\": \"C#\",\n\t\t\"rb\": \"Ruby\",\n\t\t\"swift\": \"Swift\",\n\t\t\"kt\": \"Kotlin\",\n\t\t\"kts\": \"Kotlin\",\n\t\t\"ts\": \"TypeScript\",\n\t\t\"tsx\": \"TypeScript\",\n\t\t\"rs\": \"Rust\",\n\t\t\"dart\": \"Dart\",\n\t\t\"hs\": \"Haskell\",\n\t\t\"ex\": \"Elixir\",\n\t\t\"exs\": \"Elixir\",\n\t\t\"clj\": \"Clojure\",\n\t\t\"cljs\": \"Clojure\",\n\t\t\"cljr\": \"Clojure\",\n\t\t\"cljc\": \"Clojure\",\n\t\t\"edn\": \"Clojure\",\n\t\t\"lua\": \"Lua\",\n\t\t\"json\": \"JSON\",\n\t\t\"xml\": \"XML\",\n\t\t\"yaml\": \"YAML\",\n\t\t\"yml\": \"YAML\",\n\t\t\"zip\": \"ZIP\",\n\t\t\"zig\": \"Zig\",\n\t\t\"ml\": \"OCaml\",\n\t\t\"mli\": \"OCaml\",\n\t\t\"env\": \"ENV\",\n\t\t\"config\": \"ENV\",\n\t\t\"jsx\": \"JavaScript\",\n\t\t\"cjs\": \"JavaScript\",\n\t\t\"mjs\": \"JavaScript\",\n\t\t\"jar\": \"Java\",\n\t\t\"png\": \"Image\",\n\t\t\"jpg\": \"Image\",\n\t\t\"jpeg\": \"Image\",\n\t\t\"gif\": \"Image\",\n\t\t\"bmp\": \"Image\",\n\t\t\"svg\": \"Image\",\n\t\t\"webp\": \"Image\",\n\t\t\"mp3\": \"Music\",\n\t\t\"wav\": \"Music\",\n\t\t\"flac\": \"Music\",\n\t\t\"ogg\": \"Music\",\n\t\t\"mp4\": \"Video\",\n\t\t\"mov\": \"Video\",\n\t\t\"avi\": \"Video\",\n\t\t\"mkv\": \"Video\",\n\t\t\"webm\": \"Video\",\n\t\t\"txt\": \"Document\",\n\t\t\"text\": \"Document\",\n\t\t\"md\": \"Markdown\",\n\t\t\"tbconfig\": \"JSON\",\n\t\t\"lnk\": \"Shortcut\"\n\t},\n\t\"name-to-path\": {\n\t\t\"JavaScript\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M3 3H21V21H3V3ZM7.73 18.04C8.13 18.89 8.92 19.59 10.27 19.59C11.77 19.59 12.8 18.79 12.8 17.04V11.26H11.1V17C11.1 17.86 10.75 18.08 10.2 18.08C9.62 18.08 9.38 17.68 9.11 17.21L7.73 18.04ZM13.71 17.86C14.21 18.84 15.22 19.59 16.8 19.59C18.4 19.59 19.6 18.76 19.6 17.23C19.6 15.82 18.79 15.19 17.35 14.57L16.93 14.39C16.2 14.08 15.89 13.87 15.89 13.37C15.89 12.96 16.2 12.64 16.7 12.64C17.18 12.64 17.5 12.85 17.79 13.37L19.1 12.5C18.55 11.54 17.77 11.17 16.7 11.17C15.19 11.17 14.22 12.13 14.22 13.4C14.22 14.78 15.03 15.43 16.25 15.95L16.67 16.13C17.45 16.47 17.91 16.68 17.91 17.26C17.91 17.74 17.46 18.09 16.76 18.09C15.93 18.09 15.45 17.66 15.09 17.06L13.71 17.86Z' fill='white'/></svg>\",\n\t\t\"HTML\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M12 17.56L16.07 16.43L16.62 10.33H9.38L9.2 8.3H16.8L17 6.31H7L7.56 12.32H14.45L14.22 14.9L12 15.5L9.78 14.9L9.64 13.24H7.64L7.93 16.43L12 17.56ZM4.07 3H19.93L18.5 19.2L12 21L5.5 19.2L4.07 3Z' fill='white'/></svg>\",\n\t\t\"CSS\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M5 3L4.35 6.34H17.94L17.5 8.5H3.92L3.26 11.83H16.85L16.09 15.64L10.61 17.45L5.86 15.64L6.19 14H2.85L2.06 18L9.91 21L18.96 18L20.16 11.97L20.4 10.76L21.94 3H5Z' fill='white'/></svg>\",\n\t\t\"PHP\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M12 18.08C5.37 18.08 0 15.36 0 12C0 8.64001 5.37 5.92001 12 5.92001C18.63 5.92001 24 8.64001 24 12C24 15.36 18.63 18.08 12 18.08ZM6.81 10.13C7.35 10.13 7.72 10.23 7.9 10.44C8.08 10.64 8.12 11 8.03 11.47C7.93 12 7.74 12.34 7.45 12.56C7.17 12.78 6.74 12.89 6.16 12.89H5.29L5.82 10.13H6.81ZM3.31 15.68H4.75L5.09 13.93H6.32C6.86 13.93 7.3 13.87 7.65 13.76C8 13.64 8.32 13.45 8.61 13.18C8.85 12.96 9.04 12.72 9.19 12.45C9.34 12.19 9.45 11.89 9.5 11.57C9.66 10.79 9.55 10.18 9.17 9.75001C8.78 9.31001 8.18 9.10001 7.35 9.10001H4.59L3.31 15.68ZM10.56 7.35001L9.28 13.93H10.7L11.44 10.16H12.58C12.94 10.16 13.18 10.22 13.29 10.34C13.4 10.46 13.42 10.68 13.36 11L12.79 13.93H14.24L14.83 10.86C14.96 10.24 14.86 9.79001 14.56 9.50001C14.26 9.23001 13.71 9.10001 12.91 9.10001H11.64L12 7.35001H10.56ZM18 10.13C18.55 10.13 18.91 10.23 19.09 10.44C19.27 10.64 19.31 11 19.22 11.47C19.12 12 18.93 12.34 18.65 12.56C18.36 12.78 17.93 12.89 17.35 12.89H16.5L17 10.13H18ZM14.5 15.68H15.94L16.28 13.93H17.5C18.05 13.93 18.5 13.87 18.85 13.76C19.2 13.64 19.5 13.45 19.8 13.18C20.04 12.96 20.24 12.72 20.38 12.45C20.53 12.19 20.64 11.89 20.7 11.57C20.85 10.79 20.74 10.18 20.36 9.75001C20 9.31001 19.37 9.10001 18.54 9.10001H15.79L14.5 15.68Z' fill='white'/></svg>\",\n\t\t\"Python\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M11.865 3C7.27501 3 7.50001 4.995 7.50001 4.995V7.05H12V7.68H5.83501C5.83501 7.68 2.83501 7.335 2.83501 11.97C2.83501 16.605 5.40001 16.47 5.40001 16.47H6.90001V14.295C6.87975 13.949 6.93376 13.6027 7.05838 13.2792C7.18301 12.9558 7.37539 12.6628 7.62259 12.4199C7.8698 12.1769 8.16611 11.9897 8.49165 11.8707C8.81718 11.7517 9.1644 11.7037 9.51001 11.73H13.86C14.1778 11.7465 14.4956 11.6975 14.7937 11.5861C15.0919 11.4748 15.364 11.3034 15.5932 11.0827C15.8224 10.8619 16.0038 10.5964 16.1263 10.3027C16.2487 10.009 16.3095 9.6932 16.305 9.375V5.4C16.305 5.4 16.665 3 11.865 3ZM9.45001 4.395C9.55369 4.39301 9.65674 4.41171 9.75311 4.45001C9.84948 4.48832 9.93725 4.54546 10.0113 4.61808C10.0853 4.69071 10.1441 4.77738 10.1842 4.873C10.2244 4.96863 10.245 5.07129 10.245 5.175C10.249 5.2819 10.2315 5.38851 10.1933 5.48845C10.1552 5.5884 10.0973 5.67962 10.023 5.75665C9.94881 5.83367 9.8598 5.89493 9.76133 5.93675C9.66287 5.97856 9.55698 6.00008 9.45001 6C9.3463 6.00002 9.24363 5.97936 9.14801 5.93922C9.05238 5.89909 8.96572 5.84029 8.89309 5.76627C8.82046 5.69224 8.76332 5.60447 8.72502 5.5081C8.68671 5.41173 8.66801 5.30869 8.67001 5.205C8.66597 5.10007 8.68316 4.99541 8.72054 4.89729C8.75793 4.79916 8.81474 4.7096 8.88758 4.63396C8.96042 4.55832 9.04777 4.49817 9.14442 4.45711C9.24106 4.41605 9.345 4.39492 9.45001 4.395Z' fill='white'/><path d='M12 21C16.59 21 16.305 19.005 16.305 19.005V16.95H12V16.32H18C18 16.32 21 16.665 21 12.03C21 7.39501 18.435 7.53001 18.435 7.53001H16.935V9.70501C16.9522 10.0464 16.8977 10.3876 16.7748 10.7066C16.652 11.0256 16.4637 11.3153 16.222 11.557C15.9803 11.7987 15.6906 11.987 15.3716 12.1098C15.0526 12.2327 14.7114 12.2872 14.37 12.27H10.02C9.7022 12.2536 9.38439 12.3025 9.08627 12.4139C8.78815 12.5252 8.51606 12.6966 8.28686 12.9173C8.05765 13.1381 7.87621 13.4036 7.75376 13.6973C7.63132 13.9911 7.57048 14.3068 7.57501 14.625V18.6C7.57501 18.6 7.20001 21 12 21ZM14.415 19.605C14.2081 19.605 14.0097 19.5228 13.8635 19.3766C13.7172 19.2303 13.635 19.0319 13.635 18.825C13.6289 18.7189 13.6445 18.6126 13.681 18.5127C13.7174 18.4128 13.7739 18.3214 13.8469 18.2441C13.92 18.1669 14.0081 18.1054 14.1057 18.0634C14.2034 18.0214 14.3087 17.9998 14.415 18C14.6259 18 14.8281 18.0838 14.9772 18.2329C15.1263 18.382 15.21 18.5842 15.21 18.795C15.2141 18.9012 15.1965 19.0071 15.1582 19.1063C15.1199 19.2054 15.0617 19.2957 14.9873 19.3715C14.9128 19.4474 14.8237 19.5072 14.7252 19.5474C14.6268 19.5875 14.5213 19.6071 14.415 19.605Z' fill='white'/></svg>\",\n\t\t\"Java\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><g clip-path='url(#clip0_218_34)'><path d='M8.7165 18.555C8.7165 18.555 7.794 19.116 9.35775 19.2765C11.2403 19.5165 12.243 19.4768 14.3265 19.0763C14.7452 19.3327 15.1881 19.5475 15.6488 19.7175C10.9613 21.7215 5.0295 19.5975 8.7165 18.555ZM8.1165 15.9503C8.1165 15.9503 7.1145 16.7115 8.6775 16.8728C10.7213 17.073 12.324 17.1128 15.09 16.5525C15.359 16.8253 15.6884 17.0311 16.0515 17.1533C10.401 18.8363 4.0695 17.313 8.1165 15.9503ZM19.1768 20.5178C19.1768 20.5178 19.8578 21.0788 18.4155 21.5198C15.7305 22.3223 7.155 22.5623 4.7505 21.5198C3.909 21.1598 5.51175 20.6385 6.0255 20.5583C6.54675 20.4383 6.828 20.4383 6.828 20.4383C5.9055 19.797 0.69675 21.7605 4.1835 22.3208C13.761 23.8838 21.6555 21.6398 19.1708 20.5178H19.1768ZM9.15 13.2248C9.15 13.2248 4.782 14.2673 7.587 14.6273C8.78925 14.7878 11.1533 14.7473 13.3575 14.5875C15.1605 14.427 16.965 14.1075 16.965 14.1075C16.965 14.1075 16.3238 14.388 15.8828 14.6685C11.4353 15.831 2.89875 15.3098 5.34375 14.1075C7.42725 13.1055 9.15075 13.2263 9.15075 13.2263L9.15 13.2248ZM16.965 17.5928C21.453 15.2678 19.3695 13.0253 17.9265 13.305C17.5665 13.3853 17.4053 13.4655 17.4053 13.4655C17.4053 13.4655 17.5252 13.2255 17.8057 13.1453C20.6512 12.1433 22.8953 16.1505 16.8833 17.7128C16.8833 17.7128 16.923 17.673 16.9635 17.5928H16.965ZM9.5925 23.9243C13.92 24.2048 20.5328 23.7638 20.6925 21.7193C20.6925 21.7193 20.3723 22.5218 17.1263 23.1218C13.4393 23.8028 8.871 23.7218 6.186 23.2823C6.186 23.2823 6.747 23.763 9.5925 23.9243Z' fill='white'/><path d='M14.247 0.000762939C14.247 0.000762939 16.7318 2.52526 11.883 6.33226C7.99575 9.41776 11.0018 11.181 11.883 13.185C9.5985 11.1413 7.956 9.33751 9.078 7.65526C10.7205 5.16976 15.249 3.97501 14.247 0.000762939ZM12.972 11.502C14.1345 12.8243 12.6518 14.0265 12.6518 14.0265C12.6518 14.0265 15.6173 12.504 14.2545 10.62C13.0125 8.81701 12.0495 7.93501 17.2598 4.92976C17.2598 4.92976 9.045 6.97351 12.972 11.502Z' fill='white'/></g><defs><clipPath id='clip0_218_34'><rect width='24' height='24' fill='white'/></clipPath></defs></svg>\",\n\t\t\"C\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M16.45 15.97L16.87 18.41C16.61 18.55 16.19 18.68 15.63 18.8C15.06 18.93 14.39 19 13.62 19C11.41 18.96 9.75001 18.3 8.64001 17.04C7.50001 15.77 6.96001 14.16 6.96001 12.21C7.00001 9.9 7.68001 8.13 9.00001 6.89C10.28 5.64 11.92 5 13.9 5C14.65 5 15.3 5.07 15.84 5.19C16.38 5.31 16.78 5.44 17.04 5.59L16.44 8.08L15.4 7.74C15 7.64 14.53 7.59 14 7.59C12.85 7.58 11.89 7.95 11.14 8.69C10.38 9.42 10 10.54 9.96001 12.03C9.97001 13.39 10.33 14.45 11.04 15.23C11.75 16 12.74 16.4 14.03 16.41L15.36 16.29C15.79 16.21 16.15 16.1 16.45 15.97Z' fill='white'/></svg>\",\n\t\t\"C++\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M10 15.97L10.41 18.41C10.15 18.55 9.73 18.68 9.17 18.8C8.6 18.93 7.93 19 7.16 19C4.95 18.96 3.29 18.3 2.18 17.04C1.06 15.77 0.5 14.16 0.5 12.21C0.55 9.9 1.22 8.13 2.5 6.89C3.82 5.64 5.46 5 7.44 5C8.19 5 8.84 5.07 9.38 5.19C9.92 5.31 10.32 5.44 10.58 5.59L10 8.08L8.94 7.74C8.54 7.64 8.08 7.59 7.55 7.59C6.39 7.58 5.43 7.95 4.68 8.69C3.92 9.42 3.53 10.54 3.5 12.03C3.5 13.39 3.87 14.45 4.58 15.23C5.29 16 6.29 16.4 7.57 16.41L8.9 16.29C9.33 16.21 9.69 16.1 10 15.97ZM10.5 11H12.5V9H14.5V11H16.5V13H14.5V15H12.5V13H10.5V11ZM17.5 11H19.5V9H21.5V11H23.5V13H21.5V15H19.5V13H17.5V11Z' fill='white'/></svg>\",\n\t\t\"C#\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M11.5 15.97L11.91 18.41C11.65 18.55 11.23 18.68 10.67 18.8C10.1 18.93 9.43 19 8.66 19C6.45 18.96 4.79 18.3 3.68 17.04C2.56 15.77 2 14.16 2 12.21C2.05 9.9 2.72 8.13 4 6.89C5.32 5.64 6.96 5 8.94 5C9.69 5 10.34 5.07 10.88 5.19C11.42 5.31 11.82 5.44 12.08 5.59L11.5 8.08L10.44 7.74C10.04 7.64 9.58 7.59 9.05 7.59C7.89 7.58 6.93 7.95 6.18 8.69C5.42 9.42 5.03 10.54 5 12.03C5 13.39 5.37 14.45 6.08 15.23C6.79 16 7.79 16.4 9.07 16.41L10.4 16.29C10.83 16.21 11.19 16.1 11.5 15.97ZM13.89 19L14.5 15H13L13.34 13H14.84L15.16 11H13.66L14 9H15.5L16.11 5H18.11L17.5 9H18.5L19.11 5H21.11L20.5 9H22L21.66 11H20.16L19.84 13H21.34L21 15H19.5L18.89 19H16.89L17.5 15H16.5L15.89 19H13.89ZM16.84 13H17.84L18.16 11H17.16L16.84 13Z' fill='white'/></svg>\",\n\t\t\"Ruby\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M18.041 3.17701C20.281 3.55901 20.92 5.09601 20.884 6.70401V6.67001L19.871 19.936L6.73901 20.833H6.74701C5.65401 20.789 3.22901 20.682 3.11301 17.288L4.33001 15.066L6.79201 20.806L8.88901 14.036L8.84401 14.045L8.86201 14.027L15.712 16.213L13.945 9.30001L20.475 8.89101L15.331 4.67901L18.041 3.16901V3.17701ZM6.91601 6.87401C9.54601 4.25201 12.949 2.70601 14.256 4.03001C15.553 5.33601 14.184 8.55301 11.554 11.165C8.88801 13.778 5.53901 15.413 4.23201 14.098C2.92601 12.774 4.26801 9.48601 6.90701 6.87401H6.91601Z' fill='white'/></svg>\",\n\t\t\"Swift\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M17.087 19.721C14.727 21.081 11.497 21.221 8.22701 19.821C5.65995 18.7188 3.48763 16.8638 1.99701 14.501C2.66701 15.051 3.45701 15.501 4.29701 15.901C7.66701 17.471 11.027 17.361 13.397 15.901C10.027 13.311 7.15701 9.94099 5.02701 7.19099C4.57701 6.74099 4.24701 6.18099 3.90701 5.68099C12.187 11.731 11.827 13.271 6.31701 4.67099C11.207 9.61099 15.747 12.411 15.747 12.411C15.907 12.501 15.997 12.571 16.107 12.631C16.207 12.381 16.297 12.121 16.367 11.851C17.157 9.00098 16.257 5.73099 14.287 3.04099C18.837 5.79099 21.537 10.951 20.407 15.281C20.377 15.391 20.347 15.501 20.357 15.671C22.597 18.501 21.997 21.451 21.707 20.891C20.497 18.501 18.227 19.241 17.087 19.721Z' fill='white'/></svg>\",\n\t\t\"Kotlin\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M2.97501 2.97601V21.024H21.025V20.994L16.547 16.483L12.067 11.968L16.547 7.45301L20.99 2.97601H2.97501Z' fill='white'/><path d='M12.223 2.97601L2.99301 12.206V21.024H3.07601L12.108 11.992L12.084 11.968L16.564 7.45301L21.007 2.97601H12.223Z' fill='#949494'/></svg>\",\n\t\t\"TypeScript\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M2.20801 2.20801V21.792H21.792V2.20801H2.20801ZM17.089 11.7202V11.7205C17.2767 11.721 17.4902 11.7307 17.6507 11.7481C18.3044 11.8188 18.8135 12.1009 19.2466 12.6321C19.4623 12.8967 19.5361 13.0125 19.5204 13.062C19.5102 13.094 19.3622 13.2 18.8918 13.5122C18.4296 13.8191 18.2807 13.911 18.2462 13.911C18.2111 13.911 18.138 13.8359 18.0327 13.6915C17.8299 13.4137 17.6229 13.2868 17.3029 13.2446C16.9587 13.199 16.65 13.3074 16.4988 13.5272C16.3695 13.715 16.35 14.0171 16.4527 14.2366C16.5718 14.4909 16.7872 14.6315 17.6127 14.9934C18.5649 15.4109 19.0474 15.6967 19.3988 16.0514C19.7772 16.4332 19.9689 16.8742 20.0278 17.4979C20.0566 17.8019 20.0214 18.1622 19.9345 18.4551C19.7211 19.1733 19.1453 19.6991 18.3015 19.9466C18.0682 20.015 17.8512 20.0558 17.6365 20.0716C17.3087 20.0957 16.8395 20.0824 16.5571 20.0414C15.8427 19.9372 15.0362 19.5215 14.6341 19.0502C14.4366 18.8187 14.1846 18.4379 14.1846 18.371C14.1846 18.3386 14.2006 18.3202 14.2641 18.2799C14.4523 18.1603 15.531 17.5437 15.5519 17.5437C15.5646 17.5437 15.6209 17.6101 15.6771 17.6912C15.8043 17.875 16.1173 18.1908 16.2763 18.2961C16.4063 18.3822 16.5724 18.4513 16.7697 18.5011C16.8827 18.5293 16.9425 18.5343 17.189 18.5343C17.441 18.5342 17.4926 18.53 17.6053 18.4997C17.9034 18.4195 18.136 18.2535 18.2346 18.0507C18.2778 17.9631 18.2787 17.9522 18.2787 17.7397V17.5194L18.2257 17.4143C18.0974 17.1596 17.8209 16.9848 16.9467 16.6062C16.5452 16.4322 16.0535 16.187 15.8615 16.065C15.4233 15.7865 15.1196 15.4682 14.9203 15.0785C14.722 14.691 14.6925 14.5468 14.692 13.9641C14.6916 13.508 14.6908 13.5136 14.7852 13.2225C14.8709 12.9583 15.0463 12.6632 15.2399 12.4574C15.6263 12.0466 16.1909 11.7826 16.7954 11.7301C16.8727 11.7228 16.9763 11.7199 17.0889 11.7203L17.089 11.7202ZM11.8877 11.8103H11.8879C13.052 11.8108 13.7194 11.8149 13.7301 11.8216C13.7502 11.8341 13.7528 11.9354 13.7528 12.582V13.328L12.5911 13.3322L11.4295 13.3364V16.6346C11.4295 18.4487 11.4258 19.9437 11.4207 19.9569C11.4125 19.9791 11.3232 19.9808 10.5673 19.9808H9.72313L9.71439 19.9467C9.70878 19.928 9.70465 18.4329 9.70455 16.6244L9.70426 13.3362L8.54266 13.3321L7.38097 13.3279V12.5907C7.38097 12.0061 7.385 11.8495 7.4006 11.834C7.41658 11.8175 8.00943 11.8133 10.5639 11.8107C11.0564 11.8103 11.4996 11.81 11.8877 11.8103Z' fill='white'/></svg>\",\n\t\t\"Rust\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M11.3753 4.36785C11.3753 4.21042 11.4379 4.05944 11.5492 3.94812C11.6605 3.8368 11.8115 3.77426 11.9689 3.77426C12.1263 3.77426 12.2773 3.8368 12.3886 3.94812C12.5 4.05944 12.5625 4.21042 12.5625 4.36785C12.5625 4.52528 12.5 4.67626 12.3886 4.78757C12.2773 4.89889 12.1263 4.96143 11.9689 4.96143C11.8115 4.96143 11.6605 4.89889 11.5492 4.78757C11.4379 4.67626 11.3753 4.52528 11.3753 4.36785ZM4.29433 9.74201C4.29433 9.58459 4.35687 9.43361 4.46819 9.32229C4.57951 9.21097 4.73049 9.14843 4.88791 9.14843C5.04534 9.14843 5.19632 9.21097 5.30764 9.32229C5.41896 9.43361 5.4815 9.58459 5.4815 9.74201C5.4815 9.89944 5.41896 10.0504 5.30764 10.1617C5.19632 10.2731 5.04534 10.3356 4.88791 10.3356C4.73049 10.3356 4.57951 10.2731 4.46819 10.1617C4.35687 10.0504 4.29433 9.89944 4.29433 9.74201ZM18.456 9.76968C18.456 9.61225 18.5185 9.46127 18.6299 9.34995C18.7412 9.23864 18.8922 9.1761 19.0496 9.1761C19.207 9.1761 19.358 9.23864 19.4693 9.34995C19.5806 9.46127 19.6432 9.61225 19.6432 9.76968C19.6432 9.92711 19.5806 10.0781 19.4693 10.1894C19.358 10.3007 19.207 10.3633 19.0496 10.3633C18.8922 10.3633 18.7412 10.3007 18.6299 10.1894C18.5185 10.0781 18.456 9.92711 18.456 9.76968ZM6.0125 10.583C6.14375 10.5246 6.24646 10.4165 6.29806 10.2825C6.34965 10.1484 6.34592 9.99933 6.28767 9.86801L6.02433 9.27235H7.06V13.9407H4.9705C4.71256 13.0347 4.63219 12.0875 4.73383 11.151L6.0125 10.583ZM10.3448 10.6977V9.32168H12.8112C12.9385 9.32168 13.7107 9.46885 13.7107 10.0462C13.7107 10.5257 13.1185 10.6975 12.6315 10.6975L10.3448 10.6977ZM6.97767 18.1063C6.97767 17.9489 7.0402 17.7979 7.15152 17.6866C7.26284 17.5753 7.41382 17.5128 7.57125 17.5128C7.72868 17.5128 7.87966 17.5753 7.99098 17.6866C8.10229 17.7979 8.16483 17.9489 8.16483 18.1063C8.16483 18.2638 8.10229 18.4148 7.99098 18.5261C7.87966 18.6374 7.72868 18.6999 7.57125 18.6999C7.41382 18.6999 7.26284 18.6374 7.15152 18.5261C7.0402 18.4148 6.97767 18.2638 6.97767 18.1063ZM15.7725 18.134C15.7725 17.9766 15.835 17.8256 15.9464 17.7143C16.0577 17.603 16.2087 17.5404 16.3661 17.5404C16.5235 17.5404 16.6745 17.603 16.7858 17.7143C16.8971 17.8256 16.9597 17.9766 16.9597 18.134C16.9597 18.2914 16.8971 18.4424 16.7858 18.5537C16.6745 18.6651 16.5235 18.7276 16.3661 18.7276C16.2087 18.7276 16.0577 18.6651 15.9464 18.5537C15.835 18.4424 15.7725 18.2914 15.7725 18.134ZM15.956 16.788C15.8865 16.7731 15.8147 16.772 15.7447 16.7849C15.6748 16.7977 15.608 16.8243 15.5483 16.8629C15.4886 16.9016 15.4372 16.9517 15.3968 17.0102C15.3565 17.0688 15.3281 17.1348 15.3133 17.2043L15.0155 18.5947C14.0574 19.0287 13.0169 19.2507 11.9651 19.2457C10.9134 19.2407 9.87505 19.0087 8.92117 18.5655L8.62333 17.1752C8.60851 17.1057 8.58013 17.0398 8.53981 16.9812C8.4995 16.9227 8.44805 16.8726 8.3884 16.834C8.32875 16.7954 8.26206 16.7688 8.19216 16.756C8.12226 16.7431 8.0505 16.7441 7.981 16.759L6.7535 17.0225C6.52543 16.7877 6.31339 16.5378 6.11883 16.2745H12.0912C12.1588 16.2745 12.2038 16.2622 12.2038 16.2007V14.088C12.2038 14.0265 12.1588 14.0143 12.0912 14.0143H10.3445V12.6752H12.2337C12.4062 12.6752 13.1557 12.7245 13.3953 13.6827C13.4703 13.9773 13.6353 14.9358 13.748 15.2427C13.8603 15.5868 14.3175 16.2743 14.8047 16.2743H17.8882C17.6814 16.5511 17.4553 16.8129 17.2115 17.0577L15.956 16.788ZM19.2723 11.21C19.3143 11.6317 19.3195 12.0561 19.2878 12.4787H18.538C18.463 12.4787 18.4328 12.528 18.4328 12.6015V12.9458C18.4328 13.7563 17.9758 13.9325 17.5753 13.9775C17.194 14.0205 16.7712 13.8178 16.719 13.5845C16.494 12.319 16.119 12.0488 15.527 11.5818C16.2618 11.1152 17.0263 10.4268 17.0263 9.50568C17.0263 8.51068 16.3443 7.88418 15.8795 7.57702C15.2272 7.14702 14.5052 7.06101 14.3103 7.06101H6.556C7.62792 5.86305 9.06495 5.05195 10.6445 4.75335L11.5585 5.71218C11.6075 5.76363 11.6662 5.8049 11.7313 5.83363C11.7963 5.86236 11.8663 5.87798 11.9374 5.87961C12.0084 5.88123 12.0791 5.86883 12.1454 5.8431C12.2116 5.81738 12.2722 5.77883 12.3235 5.72968L13.3463 4.75151C14.3889 4.94682 15.3764 5.36673 16.2403 5.98207C17.1042 6.59741 17.8238 7.39343 18.3492 8.31485L17.649 9.89601C17.5909 10.0274 17.5873 10.1764 17.6389 10.3104C17.6905 10.4444 17.7932 10.5525 17.9243 10.611L19.2723 11.21ZM21.0185 11.2357L20.9947 10.9912L21.7158 10.3185C21.8625 10.1818 21.8075 9.90651 21.6202 9.83668L20.6982 9.49201L20.626 9.25401L21.201 8.45535C21.3183 8.29301 21.2107 8.03368 21.0132 8.00135L20.041 7.84318L19.9243 7.62485L20.3327 6.72818C20.4163 6.54568 20.261 6.31218 20.06 6.31985L19.0733 6.35418L18.9175 6.16502L19.1442 5.20401C19.19 5.00901 18.992 4.81068 18.7968 4.85651L17.836 5.08301L17.6467 4.92718L17.6812 3.94051C17.6888 3.74085 17.455 3.58468 17.2728 3.66768L16.3763 4.07635L16.158 3.95918L15.9997 2.98701C15.9675 2.78985 15.708 2.68201 15.5458 2.79901L14.7465 3.37401L14.509 3.30201L14.1643 2.38001C14.0943 2.19218 13.819 2.13801 13.6827 2.28401L13.01 3.00568L12.7655 2.98185L12.246 2.14251C12.141 1.97251 11.8597 1.97251 11.755 2.14251L11.2355 2.98185L10.991 3.00568L10.3182 2.28401C10.1817 2.13801 9.9065 2.19218 9.8365 2.38001L9.49167 3.30201L9.254 3.37401L8.45483 2.79901C8.2925 2.68185 8.03317 2.78985 8.001 2.98701L7.8425 3.95918L7.62416 4.07635L6.72767 3.66768C6.5455 3.58435 6.31166 3.74085 6.31933 3.94051L6.35367 4.92718L6.16433 5.08301L5.2035 4.85635C5.00833 4.81101 4.81016 5.00885 4.85583 5.20401L5.08216 6.16502L4.92666 6.35418L3.94 6.31985C3.74116 6.31401 3.58433 6.54568 3.66717 6.72818L4.076 7.62485L3.95883 7.84318L2.98683 8.00135C2.78933 8.03335 2.68233 8.29301 2.79883 8.45535L3.37383 9.25401L3.30166 9.49201L2.37966 9.83668C2.1925 9.90668 2.13766 10.1817 2.284 10.3185L3.00533 10.9912L2.9815 11.2357L2.14233 11.755C1.97233 11.86 1.97233 12.1413 2.14233 12.246L2.9815 12.7655L3.00533 13.01L2.284 13.6828C2.13766 13.8192 2.1925 14.0942 2.37966 14.1645L3.30166 14.5092L3.37383 14.7472L2.79883 15.546C2.682 15.7087 2.7895 15.968 2.987 15.9998L3.95883 16.1578L4.076 16.3765L3.66717 17.2728C3.58383 17.455 3.74117 17.6895 3.94017 17.6813L4.92633 17.6468L5.08216 17.8362L4.85583 18.7975C4.81 18.9922 5.00833 19.19 5.2035 19.1442L6.16433 18.918L6.35383 19.0733L6.31933 20.0602C6.31166 20.26 6.5455 20.4162 6.72767 20.3328L7.62416 19.9245L7.8425 20.0415L8.00083 21.0132C8.033 21.211 8.2925 21.318 8.455 21.2015L9.25366 20.626L9.4915 20.6985L9.83633 21.6202C9.90633 21.8073 10.1817 21.8625 10.318 21.7158L10.9908 20.9945L11.2353 21.0188L11.7548 21.858C11.8595 22.0273 12.1408 22.0277 12.2458 21.858L12.7653 21.0188L13.0098 20.9945L13.6825 21.7158C13.8188 21.8625 14.0942 21.8073 14.1642 21.6202L14.5088 20.6985L14.7468 20.626L15.5457 21.2015C15.708 21.318 15.9673 21.2107 15.9993 21.0132L16.158 20.0415L16.3763 19.9243L17.2727 20.3328C17.4548 20.4162 17.6882 20.2603 17.681 20.0602L17.6467 19.0735L17.8358 18.918L18.7967 19.1442C18.9918 19.19 19.19 18.9922 19.1442 18.7975L18.9178 17.8362L19.0732 17.6468L20.0598 17.6813C20.2588 17.6893 20.4162 17.455 20.3325 17.2728L19.9242 16.3765L20.0408 16.1578L21.013 15.9998C21.2108 15.9682 21.3182 15.7087 21.2008 15.546L20.6258 14.7472L20.698 14.5092L21.62 14.1645C21.8075 14.0942 21.8623 13.8192 21.7157 13.6828L20.9945 13.01L21.0183 12.7655L21.8575 12.246C22.0275 12.1413 22.0277 11.8602 21.8577 11.755L21.0185 11.2357Z' fill='white'/></svg>\",\n\t\t\"Dart\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M12.618 1.56601C12.3627 1.56707 12.1179 1.66792 11.936 1.84701L11.926 1.85401L5.53799 5.54601L11.91 11.918V11.922L19.568 19.581L21.028 16.951L15.764 4.31101L13.307 1.85401C13.2167 1.76283 13.1092 1.69044 12.9908 1.64101C12.8724 1.59158 12.7453 1.56609 12.617 1.56601H12.618Z' fill='white'/><path d='M5.55301 5.531L1.86301 11.914L1.85601 11.924C1.76609 12.0143 1.69489 12.1214 1.6465 12.2392C1.59811 12.3571 1.57349 12.4833 1.57404 12.6107C1.5746 12.7381 1.60033 12.8642 1.64975 12.9816C1.69917 13.099 1.7713 13.2055 1.86201 13.295L4.92001 16.356L16.883 21.062L19.588 19.56L19.515 19.487L19.496 19.489L11.996 11.977H11.987L5.55301 5.531Z' fill='#949494'/><path d='M5.53699 5.534L12.055 12.059H12.065L19.566 19.569L22.422 19.025L22.426 10.576L19.411 7.621C18.751 6.974 17.736 6.557 16.716 6.419L16.718 6.387L5.53799 5.535L5.53699 5.534Z' fill='#949494'/><path d='M5.54501 5.54199L12.067 12.064V12.073L19.573 19.579L19.027 22.434H10.578L7.62401 19.417C6.97701 18.757 6.56101 17.741 6.42401 16.721L6.39101 16.724L5.54501 5.54199Z' fill='white'/></svg>\",\n\t\t\"Haskell\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M1.91425 19.24L6.70945 12.0518L1.91425 4.86343H5.51065L10.3058 12.0518L5.51065 19.24H1.91425Z' fill='white'/><path d='M6.70953 19.24L11.5047 12.0518L6.70953 4.86343H10.3059L19.8963 19.2402H16.3003L13.3035 14.7478L10.3061 19.2402L6.70953 19.24Z' fill='white'/><path d='M18.2976 15.0464L16.6992 12.6502H22.2936V15.0466H18.2976V15.0464ZM15.9 11.4525L14.3016 9.05624H22.2937V11.4525H15.9Z' fill='white'/></svg>\",\n\t\t\"Elixir\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M12.173 22.681C8.31298 22.681 5.18298 19.041 5.18298 14.551C5.18298 10.873 7.95598 6.379 10.099 3.641C11.113 2.345 13.029 1.319 13.029 1.319C13.029 1.319 12.047 6.558 14.712 8.638C17.078 10.485 18.818 12.888 18.818 15.001C18.818 19.233 16.034 22.681 12.173 22.681Z' fill='white'/></svg>\",\n\t\t\"Clojure\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M11.574 12.1852C11.4613 12.4294 11.3507 12.6746 11.2421 12.9207C10.8291 13.8564 10.3717 14.9951 10.2047 15.7246C10.1446 15.9844 10.1075 16.3063 10.1082 16.6633C10.1082 16.8045 10.1157 16.9529 10.1277 17.1041C10.7277 17.3243 11.3618 17.4373 12.0009 17.4378C12.5837 17.4368 13.1626 17.3424 13.7156 17.1583C13.5889 17.0425 13.4679 16.919 13.3579 16.7798C12.627 15.8478 12.2192 14.4819 11.574 12.1852ZM8.87546 7.55493C7.47958 8.53869 6.56615 10.1612 6.56268 12C6.56615 13.8113 7.4521 15.4128 8.81286 16.4007C9.14665 15.0103 9.98334 13.7372 11.238 11.1853C11.157 10.9625 11.0722 10.7411 10.9836 10.5212C10.636 9.64933 10.1343 8.63676 9.68663 8.17823C9.45839 7.93869 9.18142 7.7317 8.87546 7.55493Z' fill='white'/><path d='M16.7374 18.2377C16.0172 18.1474 15.4228 18.0385 14.9028 17.8551C14.001 18.3031 13.0074 18.5357 12.0004 18.5346C8.39188 18.5346 5.46665 15.6099 5.46621 12.0002C5.46621 10.0417 6.32904 8.28599 7.69407 7.08857C7.32898 7.00036 6.94825 6.94888 6.55933 6.94959C4.64214 6.96764 2.61834 8.02867 1.77578 10.8941C1.697 11.3112 1.71576 11.6266 1.71576 12.0004C1.71576 17.6808 6.32077 22.2862 12.0006 22.2862C15.4791 22.2862 18.5521 20.5577 20.413 17.9143C19.4064 18.1651 18.4385 18.2851 17.6096 18.2876C17.299 18.2876 17.0073 18.271 16.7373 18.238' fill='white'/><path d='M14.8052 16.2442C14.8687 16.2757 15.0126 16.3271 15.213 16.3839C16.5607 15.3945 17.4365 13.8009 17.4399 11.9997H17.4394C17.4343 8.99697 15.0044 6.56736 12.0009 6.5614C11.4206 6.56247 10.8442 6.65618 10.2935 6.83899C11.3977 8.09768 11.9287 9.89644 12.4424 11.864C12.4426 11.8649 12.4431 11.8656 12.4433 11.8664C12.4442 11.868 12.6076 12.4127 12.888 13.1355C13.1665 13.8573 13.5632 14.7509 13.9959 15.4022C14.28 15.8386 14.5924 16.1522 14.8052 16.2442Z' fill='white'/><path d='M12.0008 1.71462C8.55591 1.71462 5.5086 3.41051 3.64209 6.01084C4.61349 5.40248 5.60534 5.18303 6.47093 5.19086C7.66649 5.19433 8.60641 5.56501 9.05739 5.8186C9.1645 5.88107 9.26911 5.94772 9.37099 6.01839C10.1995 5.65325 11.0951 5.46503 12.0006 5.46578C15.6096 5.46623 18.5352 8.39101 18.5358 12H18.5352C18.5352 13.8196 17.791 15.4648 16.5913 16.6495C16.8858 16.6825 17.1998 16.703 17.5205 16.7014C18.6596 16.7019 19.8908 16.4506 20.8139 15.6742C21.4157 15.1672 21.92 14.4249 22.1996 13.3118C22.2543 12.8819 22.2858 12.4449 22.2858 12.0002C22.2858 6.32008 17.6812 1.71445 12.0009 1.71445' fill='white'/></svg>\",\n\t\t\"Lua\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M10.5 5C8.24566 5 6.08365 5.89553 4.48959 7.48959C2.89553 9.08365 2 11.2457 2 13.5C2 15.7543 2.89553 17.9163 4.48959 19.5104C6.08365 21.1045 8.24566 22 10.5 22C11.6162 22 12.7215 21.7801 13.7528 21.353C14.7841 20.9258 15.7211 20.2997 16.5104 19.5104C17.2997 18.7211 17.9258 17.7841 18.353 16.7528C18.7801 15.7215 19 14.6162 19 13.5C19 11.2457 18.1045 9.08365 16.5104 7.48959C14.9163 5.89553 12.7543 5 10.5 5ZM13.5 13C12.837 13 12.2011 12.7366 11.7322 12.2678C11.2634 11.7989 11 11.163 11 10.5C11 9.83696 11.2634 9.20107 11.7322 8.73223C12.2011 8.26339 12.837 8 13.5 8C14.163 8 14.7989 8.26339 15.2678 8.73223C15.7366 9.20107 16 9.83696 16 10.5C16 11.163 15.7366 11.7989 15.2678 12.2678C14.7989 12.7366 14.163 13 13.5 13ZM19.5 2C18.837 2 18.2011 2.26339 17.7322 2.73223C17.2634 3.20107 17 3.83696 17 4.5C17 5.16304 17.2634 5.79893 17.7322 6.26777C18.2011 6.73661 18.837 7 19.5 7C19.8283 7 20.1534 6.93534 20.4567 6.8097C20.76 6.68406 21.0356 6.49991 21.2678 6.26777C21.4999 6.03562 21.6841 5.76002 21.8097 5.45671C21.9353 5.15339 22 4.8283 22 4.5C22 3.83696 21.7366 3.20107 21.2678 2.73223C20.7989 2.26339 20.163 2 19.5 2Z' fill='white'/></svg>\",\n\t\t\"JSON\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M5 11.5266V10.7031C5.78992 10.7031 6.34097 10.5482 6.65316 10.2383C6.97007 9.92839 7.12853 9.41042 7.12853 8.68438V6.55938C7.12853 5.94844 7.19002 5.4194 7.313 4.97227C7.44071 4.52513 7.64647 4.15547 7.93028 3.86328C8.21408 3.57109 8.59249 3.35417 9.06549 3.2125C9.5385 3.07083 10.1227 3 10.818 3V4.30156C10.2693 4.30156 9.83649 4.38125 9.51958 4.54063C9.2074 4.7 8.98508 4.94792 8.85264 5.28438C8.72493 5.61641 8.66107 6.04141 8.66107 6.55938V9.21563C8.66107 9.56094 8.61141 9.87526 8.51208 10.1586C8.41747 10.4419 8.2401 10.6854 7.97994 10.8891C7.71979 11.0927 7.34848 11.2499 6.86601 11.3605C6.38827 11.4712 5.76627 11.5266 5 11.5266ZM10.818 20C10.1227 20 9.5385 19.9292 9.06549 19.7875C8.59249 19.6458 8.21408 19.4289 7.93028 19.1367C7.64647 18.8445 7.44071 18.4749 7.313 18.0277C7.19002 17.5806 7.12853 17.0516 7.12853 16.4406V14.3156C7.12853 13.5896 6.97007 13.0716 6.65316 12.7617C6.34097 12.4518 5.78992 12.2969 5 12.2969V11.4734C5.76627 11.4734 6.38827 11.5288 6.86601 11.6395C7.34848 11.7501 7.71979 11.9073 7.97994 12.1109C8.2401 12.3146 8.41747 12.5581 8.51208 12.8414C8.61141 13.1247 8.66107 13.4391 8.66107 13.7844V16.4406C8.66107 16.9586 8.72493 17.3836 8.85264 17.7156C8.98508 18.0477 9.2074 18.2934 9.51958 18.4527C9.83649 18.6165 10.2693 18.6984 10.818 18.6984V20ZM5 12.2969V10.7031H6.67444V12.2969H5Z' fill='white'/><path d='M18 11.4734V12.2969C17.2101 12.2969 16.6567 12.4518 16.3397 12.7617C16.0276 13.0716 15.8715 13.5896 15.8715 14.3156V16.4406C15.8715 17.0516 15.8076 17.5806 15.6799 18.0277C15.5569 18.4749 15.3535 18.8445 15.0697 19.1367C14.7859 19.4289 14.4075 19.6458 13.9345 19.7875C13.4615 19.9292 12.8773 20 12.182 20V18.6984C12.7307 18.6984 13.1611 18.6165 13.4733 18.4527C13.7902 18.2934 14.0126 18.0477 14.1403 17.7156C14.2727 17.3836 14.3389 16.9586 14.3389 16.4406V13.7844C14.3389 13.4391 14.3862 13.1247 14.4808 12.8414C14.5802 12.5581 14.7599 12.3146 15.0201 12.1109C15.2802 11.9073 15.6492 11.7501 16.1269 11.6395C16.6094 11.5288 17.2337 11.4734 18 11.4734ZM12.182 3C12.8773 3 13.4615 3.07083 13.9345 3.2125C14.4075 3.35417 14.7859 3.57109 15.0697 3.86328C15.3535 4.15547 15.5569 4.52513 15.6799 4.97227C15.8076 5.4194 15.8715 5.94844 15.8715 6.55938V8.68438C15.8715 9.41042 16.0276 9.92839 16.3397 10.2383C16.6567 10.5482 17.2101 10.7031 18 10.7031V11.5266C17.2337 11.5266 16.6094 11.4712 16.1269 11.3605C15.6492 11.2499 15.2802 11.0927 15.0201 10.8891C14.7599 10.6854 14.5802 10.4419 14.4808 10.1586C14.3862 9.87526 14.3389 9.56094 14.3389 9.21563V6.55938C14.3389 6.04141 14.2727 5.61641 14.1403 5.28438C14.0126 4.94792 13.7902 4.7 13.4733 4.54063C13.1611 4.38125 12.7307 4.30156 12.182 4.30156V3ZM18 10.7031V12.2969H16.3256V10.7031H18Z' fill='white'/></svg>\",\n\t\t\"XML\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M3.75 3.375C3.75 2.34 4.589 1.5 5.625 1.5H9C9.99456 1.5 10.9484 1.89509 11.6517 2.59835C12.3549 3.30161 12.75 4.25544 12.75 5.25V7.125C12.75 7.62228 12.9475 8.0992 13.2992 8.45083C13.6508 8.80246 14.1277 9 14.625 9H16.5C17.4946 9 18.4484 9.39509 19.1517 10.0983C19.8549 10.8016 20.25 11.7554 20.25 12.75V20.625C20.25 21.66 19.41 22.5 18.375 22.5H5.625C4.59 22.5 3.75 21.66 3.75 20.625V3.375ZM14.25 5.25C14.2518 3.98855 13.7975 2.76896 12.971 1.816C14.6443 2.25596 16.1707 3.13248 17.3941 4.35589C18.6175 5.5793 19.494 7.10571 19.934 8.779C18.981 7.95248 17.7615 7.49825 16.5 7.5H14.625C14.418 7.5 14.25 7.332 14.25 7.125V5.25ZM8.85992 16.0484C8.32967 16.3389 8 16.8954 8 17.5C8 18.1046 8.32967 18.6611 8.85992 18.9516L11.4861 20.3904C11.9869 20.6648 12.5987 20.3024 12.5987 19.7314C12.5987 19.4566 12.4487 19.2037 12.2077 19.072L9.33204 17.5L12.2077 15.928C12.4487 15.7963 12.5987 15.5434 12.5987 15.2686C12.5987 14.6976 11.9869 14.3352 11.4861 14.6096L8.85992 16.0484ZM18 17.5C18 16.8954 17.6703 16.3389 17.1401 16.0484L14.5139 14.6096C14.0131 14.3352 13.4013 14.6976 13.4013 15.2686C13.4013 15.5434 13.5513 15.7963 13.7923 15.928L16.668 17.5L13.7923 19.072C13.5513 19.2037 13.4013 19.4566 13.4013 19.7314C13.4013 20.3024 14.0131 20.6648 14.5139 20.3904L17.1401 18.9516C17.6703 18.6611 18 18.1046 18 17.5Z' fill='white'/></svg>\",\n\t\t\"YAML\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M4 3.875C4 2.84 4.839 2 5.875 2H9.25C10.2446 2 11.1984 2.39509 11.9017 3.09835C12.6049 3.80161 13 4.75544 13 5.75V7.625C13 8.12228 13.1975 8.59919 13.5492 8.95083C13.9008 9.30246 14.3777 9.5 14.875 9.5H16.75C17.7446 9.5 18.6984 9.89509 19.4017 10.5983C20.1049 11.3016 20.5 12.2554 20.5 13.25V21.125C20.5 22.16 19.66 23 18.625 23H5.875C4.84 23 4 22.16 4 21.125V3.875ZM14.5 5.75001C14.5018 4.48856 14.0475 3.26897 13.221 2.31601C14.8943 2.75597 16.4207 3.63248 17.6441 4.8559C18.8675 6.07931 19.744 7.60572 20.184 9.27901C19.231 8.45249 18.0115 7.99826 16.75 8.00001H14.875C14.668 8.00001 14.5 7.83201 14.5 7.62501V5.75001ZM7 13C6.44772 13 6 13.4477 6 14C6 14.5523 6.44772 15 7 15H13C13.5523 15 14 14.5523 14 14C14 13.4477 13.5523 13 13 13H7ZM10 17C9.44772 17 9 17.4477 9 18C9 18.5523 9.44772 19 10 19H17C17.5523 19 18 18.5523 18 18C18 17.4477 17.5523 17 17 17H10Z' fill='white'/></svg>\",\n\t\t\"ZIP\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M14 17H12V15H10V13H12V15H14M14 9H12V11H14V13H12V11H10V9H12V7H10V5H12V7H14M19 3H5C3.89 3 3 3.89 3 5V19C3 19.5304 3.21071 20.0391 3.58579 20.4142C3.96086 20.7893 4.46957 21 5 21H19C19.5304 21 20.0391 20.7893 20.4142 20.4142C20.7893 20.0391 21 19.5304 21 19V5C21 4.46957 20.7893 3.96086 20.4142 3.58579C20.0391 3.21071 19.5304 3 19 3Z' fill='white'/></svg>\",\n\t\t\"Zig\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M1.82397 6.5708V17.4252H4.40189L5.21597 16.0684L6.84413 14.7116H4.53757V9.2844H6.57277V6.5708H1.82397ZM7.25117 6.5708V9.2844H15.6633V6.5708H7.25117ZM19.5981 6.5708L18.784 7.9276L17.1558 9.2844H19.4624V14.7116H17.4272V17.4252H22.176V6.5708H19.5981ZM8.33661 14.7116V17.4252H16.7488V14.7116H8.33661Z' fill='white'/><path d='M6.84413 14.7116L4.40189 17.4252L4.40189 15.39L6.84413 14.7116ZM17.1558 9.2844L19.5981 6.5708L19.5981 8.606L17.1558 9.2844ZM14.9822 6.65628L19.9047 5.24521L9.01094 17.3438L4.08847 18.7549L14.9822 6.65628Z' fill='white'/></svg>\",\n\t\t\"OCaml\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M4.51002 3.27301C4.17861 3.27288 3.85043 3.33804 3.54421 3.46478C3.23799 3.59151 2.95973 3.77733 2.72535 4.01163C2.49096 4.24592 2.30503 4.5241 2.17817 4.83027C2.05132 5.13644 1.98602 5.4646 1.98602 5.79601V11.3C2.34702 11.17 2.86602 10.402 3.02902 10.215C3.31402 9.88801 3.36602 9.47201 3.50702 9.20901C3.83002 8.61201 3.88602 8.20001 4.62002 8.20001C4.96202 8.20001 5.09802 8.28001 5.33002 8.59001C5.49002 8.80601 5.76802 9.20501 5.89802 9.47201C6.04802 9.77901 6.29402 10.196 6.40102 10.28C6.48102 10.342 6.56102 10.39 6.63402 10.417C6.75302 10.461 6.85202 10.38 6.93102 10.317C7.03302 10.235 7.07602 10.07 7.17102 9.85001C7.30602 9.53301 7.45402 9.15301 7.53802 9.02001C7.68402 8.79001 7.73302 8.51901 7.89002 8.38701C8.12202 8.19201 8.42502 8.17901 8.50802 8.16201C8.97402 8.07001 9.18502 8.38701 9.41502 8.59201C9.56502 8.72501 9.77002 8.99501 9.91502 9.35701C10.029 9.64001 10.175 9.90101 10.235 10.064C10.294 10.222 10.438 10.474 10.524 10.777C10.601 11.052 10.81 11.263 10.889 11.393C10.889 11.393 11.01 11.733 11.747 12.043C11.907 12.11 12.229 12.219 12.421 12.289C12.741 12.405 13.051 12.39 13.446 12.343C13.727 12.343 13.88 11.935 14.008 11.609C14.083 11.416 14.156 10.864 14.205 10.707C14.253 10.554 14.141 10.437 14.236 10.302C14.348 10.146 14.414 10.138 14.478 9.93401C14.616 9.49801 15.414 9.47601 15.862 9.47601C16.236 9.47601 16.189 9.83901 16.822 9.71501C17.186 9.64301 17.536 9.76101 17.922 9.86401C18.246 9.95001 18.552 10.048 18.734 10.262C18.853 10.401 19.146 11.096 18.847 11.125C18.876 11.16 18.897 11.224 18.951 11.259C18.884 11.521 18.594 11.334 18.433 11.3C18.216 11.255 18.063 11.307 17.85 11.401C17.487 11.563 16.956 11.544 16.64 11.808C16.37 12.031 16.371 12.529 16.246 12.808C16.246 12.808 15.898 13.703 15.14 14.251C14.946 14.391 14.566 14.728 13.74 14.856C13.3758 14.9084 13.0072 14.9228 12.64 14.899C12.454 14.89 12.278 14.881 12.091 14.879C11.981 14.877 11.611 14.866 11.63 14.901L11.589 15.004L11.613 15.142C11.628 15.225 11.632 15.291 11.635 15.367C11.641 15.524 11.622 15.687 11.63 15.845C11.647 16.173 11.768 16.472 11.784 16.803C11.801 17.171 11.983 17.561 12.159 17.862C12.226 17.976 12.328 17.99 12.372 18.131C12.424 18.292 12.375 18.464 12.4 18.636C12.5 19.304 12.692 20.002 12.992 20.606C12.9945 20.6108 12.9971 20.6155 13 20.62C13.371 20.558 13.743 20.424 14.226 20.353C15.111 20.221 16.341 20.289 17.132 20.215C19.132 20.027 20.217 21.035 22.014 20.622V5.79601C22.014 5.12687 21.7482 4.48513 21.2751 4.01198C20.8019 3.53883 20.1602 3.27301 19.491 3.27301H4.51002ZM3.60302 14.417C3.58802 14.417 3.57302 14.417 3.55702 14.42C3.39802 14.445 3.24402 14.5 3.14502 14.66C3.06502 14.79 3.03702 15.015 2.98102 15.165C2.91702 15.34 2.80502 15.503 2.70702 15.67C2.52702 15.975 2.20302 16.251 2.06302 16.549C2.03502 16.609 2.01002 16.679 1.98702 16.749V20.151C2.15002 20.179 2.32002 20.213 2.51102 20.264C3.91802 20.639 4.26102 20.671 5.64102 20.514L5.77102 20.496C5.87602 20.276 5.95802 19.528 6.02602 19.296C6.08002 19.118 6.15302 18.976 6.18102 18.796C6.20702 18.623 6.17802 18.459 6.16402 18.303C6.12402 17.91 6.44902 17.77 6.60402 17.433C6.74402 17.129 6.82402 16.782 6.94002 16.47C7.05002 16.172 7.22402 15.749 7.51902 15.598C7.48302 15.557 6.90202 15.538 6.74702 15.522C6.5793 15.507 6.41242 15.4836 6.24702 15.452C5.93302 15.388 5.59102 15.326 5.28202 15.252C4.96115 15.1583 4.6451 15.0489 4.33502 14.924C4.03702 14.786 3.83202 14.427 3.60302 14.417ZM9.34002 15.247C8.60002 15.396 8.37002 16.123 8.02002 16.698C7.82802 17.017 7.62402 17.288 7.47202 17.626C7.33202 17.938 7.34402 18.283 7.10402 18.55C6.86524 18.8169 6.68533 19.131 6.57602 19.472C6.55302 19.539 6.48802 20.248 6.41802 20.415L7.51902 20.337C8.54502 20.407 8.24902 20.801 9.85102 20.715L12.38 20.637C12.3127 20.4377 12.2367 20.2415 12.152 20.049C12.082 19.902 11.992 19.615 11.934 19.489C11.8464 19.3051 11.743 19.1291 11.625 18.963C11.441 18.748 11.398 18.733 11.345 18.46C11.25 17.987 11.001 17.13 10.708 16.537C10.557 16.231 10.305 15.975 10.074 15.753C9.87402 15.558 9.41902 15.231 9.34002 15.248V15.247Z' fill='white'/></svg>\",\n\t\t\"ENV\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M6.849 3.85101C6.849 3.29873 6.40128 2.85101 5.849 2.85101V2.85101C5.29671 2.85101 4.849 3.29873 4.849 3.85101V7.85101C4.849 8.4033 5.29671 8.85101 5.849 8.85101V8.85101C6.40128 8.85101 6.849 8.4033 6.849 7.85101V3.85101ZM18.849 3.85101C18.849 3.29873 18.4013 2.85101 17.849 2.85101V2.85101C17.2967 2.85101 16.849 3.29873 16.849 3.85101V11.851C16.849 12.4033 17.2967 12.851 17.849 12.851V12.851C18.4013 12.851 18.849 12.4033 18.849 11.851V3.85101ZM2.849 11.851C2.849 12.4033 3.29671 12.851 3.849 12.851H4.349C4.62514 12.851 4.849 13.0749 4.849 13.351L4.849 19.851C4.849 20.4033 5.29671 20.851 5.849 20.851V20.851C6.40128 20.851 6.849 20.4033 6.849 19.851L6.849 13.351C6.849 13.0749 7.07286 12.851 7.349 12.851H7.849C8.40128 12.851 8.849 12.4033 8.849 11.851V11.851C8.849 11.2987 8.40128 10.851 7.849 10.851L3.849 10.851C3.29671 10.851 2.849 11.2987 2.849 11.851V11.851ZM14.849 7.85101C14.849 7.29873 14.4013 6.85101 13.849 6.85101L13.249 6.85101C13.0281 6.85101 12.849 6.67193 12.849 6.45101V3.85101C12.849 3.29873 12.4013 2.85101 11.849 2.85101V2.85101C11.2967 2.85101 10.849 3.29873 10.849 3.85101V6.45101C10.849 6.67193 10.6699 6.85101 10.449 6.85101H9.849C9.29671 6.85101 8.849 7.29873 8.849 7.85101V7.85101C8.849 8.4033 9.29671 8.85101 9.849 8.85101H13.849C14.4013 8.85101 14.849 8.4033 14.849 7.85101V7.85101ZM10.849 19.851C10.849 20.4033 11.2967 20.851 11.849 20.851V20.851C12.4013 20.851 12.849 20.4033 12.849 19.851V11.851C12.849 11.2987 12.4013 10.851 11.849 10.851V10.851C11.2967 10.851 10.849 11.2987 10.849 11.851L10.849 19.851ZM15.849 14.851C15.2967 14.851 14.849 15.2987 14.849 15.851V15.851C14.849 16.4033 15.2967 16.851 15.849 16.851H16.849V19.851C16.849 20.4033 17.2967 20.851 17.849 20.851V20.851C18.4013 20.851 18.849 20.4033 18.849 19.851V16.851H19.849C20.4013 16.851 20.849 16.4033 20.849 15.851V15.851C20.849 15.2987 20.4013 14.851 19.849 14.851H15.849Z' fill='white'/></svg>\",\n\t\t\"Image\": \"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'><path fill-rule='evenodd' d='M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z' clip-rule='evenodd'/></svg>\",\n\t\t\"Music\": \"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'><path fill-rule='evenodd' d='M19.952 1.651a.75.75 0 01.298.599V16.303a3 3 0 01-2.176 2.884l-1.32.377a2.553 2.553 0 11-1.403-4.909l2.311-.66a1.5 1.5 0 001.088-1.442V6.994l-9 2.572v9.737a3 3 0 01-2.176 2.884l-1.32.377a2.553 2.553 0 11-1.402-4.909l2.31-.66a1.5 1.5 0 001.088-1.442V9.017 5.25a.75.75 0 01.544-.721l10.5-3a.75.75 0 01.658.122z' clip-rule='evenodd'/></svg>\",\n\t\t\"Document\": \"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor' class='w-6 h-6'><path fill-rule='evenodd' d='M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z' clip-rule='evenodd' fill='white'/><path d='M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z' fill='white'/></svg>\",\n\t\t\"Video\": \"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'><path fill-rule='evenodd' d='M1.5 5.625c0-1.036.84-1.875 1.875-1.875h17.25c1.035 0 1.875.84 1.875 1.875v12.75c0 1.035-.84 1.875-1.875 1.875H3.375A1.875 1.875 0 011.5 18.375V5.625zm1.5 0v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5A.375.375 0 003 5.625zm16.125-.375a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5A.375.375 0 0021 7.125v-1.5a.375.375 0 00-.375-.375h-1.5zM21 9.375A.375.375 0 0020.625 9h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zm0 3.75a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zm0 3.75a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zM4.875 18.75a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5zM3.375 15h1.5a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375zm0-3.75h1.5a.375.375 0 00.375-.375v-1.5A.375.375 0 004.875 9h-1.5A.375.375 0 003 9.375v1.5c0 .207.168.375.375.375zm4.125 0a.75.75 0 000 1.5h9a.75.75 0 000-1.5h-9z' clip-rule='evenodd'/></svg>\",\n\t\t\"Unknown\": \"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'><path d='M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625z' fill='white' /><path d='M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z' fill='white' /></svg>\",\n\t\t\"Markdown\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M3.0101 5H20.9899C21.5478 5 22 5.53117 22 6.18644V17.8136C22 18.4688 21.5478 19 20.9899 19H3.0101C2.45222 19 2 18.4688 2 17.8136V6.18644C2 5.53117 2.45222 5 3.0101 5Z' stroke='white' stroke-width='3'/><path d='M5 16V9H6.80645L8.6129 11.5735L10.4194 9H12.2258V16H10.4194V11.9853L8.6129 14.5588L6.80645 11.9853V16H5ZM16.2903 16L13.5806 12.6029H15.3871V9H17.1935V12.6029H19L16.2903 16Z' fill='white'/></svg>\",\n\t\t\"Shortcut\": \"<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M13.2209 1.316C14.0475 2.26897 14.5017 3.48855 14.4999 4.75V6.625C14.4999 6.832 14.6679 7 14.8749 7H16.7499C18.0114 6.99825 19.231 7.45249 20.1839 8.279C19.744 6.60571 18.8675 5.0793 17.6441 3.85589C16.4206 2.63248 14.8942 1.75597 13.2209 1.316Z' fill='white'/><path fill-rule='evenodd' clip-rule='evenodd' d='M4 2.875C4 1.84 4.839 1 5.875 1H9.25C10.2446 1 11.1984 1.39509 11.9017 2.09835C12.6049 2.80161 13 3.75544 13 4.75V6.625C13 7.12228 13.1975 7.5992 13.5492 7.95083C13.9008 8.30246 14.3777 8.5 14.875 8.5H16.75C17.7446 8.5 18.6984 8.89509 19.4017 9.59835C20.1049 10.3016 20.5 11.2554 20.5 12.25V20.125C20.5 21.16 19.66 22 18.625 22H5.875C4.84 22 4 21.16 4 20.125V2.875ZM9.17157 12.1716C10.7337 10.6094 13.2663 10.6094 14.8284 12.1716C16.3906 13.7337 16.3906 16.2663 14.8284 17.8284C13.2663 19.3906 10.7337 19.3906 9.17157 17.8284C7.6094 16.2663 7.6094 13.7337 9.17157 12.1716ZM13.3052 13.6948C13.3629 13.7524 13.3953 13.8306 13.3954 13.9121V15.6527C13.3968 15.694 13.3899 15.7352 13.3751 15.7738C13.3603 15.8123 13.3379 15.8475 13.3092 15.8773C13.2805 15.907 13.2461 15.9306 13.2081 15.9468C13.1701 15.9629 13.1292 15.9713 13.0879 15.9713C13.0465 15.9713 13.0056 15.9629 12.9676 15.9468C12.9296 15.9306 12.8952 15.907 12.8665 15.8773C12.8378 15.8475 12.8154 15.8123 12.8006 15.7738C12.7858 15.7352 12.7789 15.694 12.7803 15.6527V14.6548L11.1297 16.3054C11.072 16.3631 10.9937 16.3955 10.9121 16.3955C10.8305 16.3955 10.7523 16.3631 10.6946 16.3054C10.6369 16.2477 10.6044 16.1695 10.6044 16.0879C10.6044 16.0063 10.6369 15.928 10.6946 15.8703L12.3452 14.2196H11.3473C11.306 14.2211 11.2648 14.2142 11.2262 14.1994C11.1877 14.1846 11.1525 14.1622 11.1227 14.1335C11.093 14.1048 11.0694 14.0704 11.0532 14.0324C11.0371 13.9944 11.0287 13.9535 11.0287 13.9121C11.0287 13.8708 11.0371 13.8299 11.0532 13.7919C11.0694 13.7539 11.093 13.7195 11.1227 13.6908C11.1525 13.6621 11.1877 13.6397 11.2262 13.6249C11.2648 13.6101 11.306 13.6032 11.3473 13.6046H13.0879C13.1694 13.6047 13.2476 13.6371 13.3052 13.6948Z' fill='white'/></svg>\"\n\t}\n}\n"
  },
  {
    "path": "public/apps/files.tapp/index.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(/fonts/Inter.ttf);\n}\n\nhtml,\nbody {\n\theight: 100%;\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbody {\n\tdisplay: flex;\n\tflex-direction: column;\n\tfont-family: Inter;\n\tcolor: #ffffff;\n}\n\n.topbar {\n\tposition: relative;\n\tcolor: #ffffff;\n\twidth: calc(100%);\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 8px 8px 8px;\n}\n\n.nav-buttons {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tgap: 8px;\n}\n\n.nav-button {\n\twidth: 18px;\n\theight: 18px;\n\tstroke-width: 2px;\n\tstroke: currentColor;\n\tcursor: var(--cursor-pointer) !important;\n}\n\n.nav-input {\n\toutline: none;\n\tborder: none;\n\tbackground-color: #00000028;\n\tcolor: #ffffff;\n\tborder-radius: 8px;\n\tpadding: 6px 10px;\n\tfont-weight: 700;\n\tfont-family: Inter;\n\tfont-size: 14.5px;\n}\n\n.nav-input.dir {\n\twidth: 100%;\n}\n\nbutton {\n\tbackground-color: transparent;\n\tborder-color: transparent;\n\tcolor: #ffffff;\n\tcursor: var(--cursor-pointer) !important;\n}\n\nbutton.path-button {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tjustify-content: center;\n}\n\nbutton.path-button svg {\n\twidth: 18px;\n\theight: 18px;\n}\n\n.icon-button {\n\twidth: 18px;\n\theight: 18px;\n\tstroke-width: 2px;\n\tstroke: currentColor;\n\tcursor: var(--cursor-pointer) !important;\n}\n\n.sidebar {\n\tposition: relative;\n\twidth: var(--sidebar-width);\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: stretch;\n\tpadding: 8px;\n\tpadding-right: 0;\n\toverflow: auto;\n\tscrollbar-width: thin;\n\tscrollbar-color: #ffffff30 transparent;\n}\n\n.sidebar::-webkit-scrollbar {\n\twidth: 8px;\n}\n\n.sidebar::-webkit-scrollbar-track {\n\tbackground-color: transparent;\n}\n\n.sidebar::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff30;\n\tborder-radius: 8px;\n\tborder: 2px solid transparent;\n}\n\n.absolute-path-item {\n\twidth: max-content;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 8px 4px 6px;\n\tborder-radius: 6px;\n\tcursor: var(--cursor-pointer) !important;\n\ttransition: 150ms ease-in-out;\n\tuser-select: none;\n}\n\n.absolute-path-item:hover {\n\tbackground-color: #ffffff30;\n}\n\n.absolute-path-item .icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.absolute-path-item svg {\n\twidth: 18px;\n\theight: 18px;\n}\n\n.absolute-path-item .title {\n\tfont-size: 16px;\n\tfont-weight: 500;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.absolute-path-item .title,\n.absolute-path-item .icon,\n.absolute-path-item .icon svg,\n.absolute-path-item .icon svg path {\n\tpointer-events: none;\n}\n\n.collapsible-path .title {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\twidth: max-content;\n\tcursor: var(--cursor-pointer) !important;\n\tpadding: 4px 6px;\n\tborder-radius: 6px;\n\ttransition: 150ms ease-in-out;\n}\n\n.collapsible-path .title h2 {\n\tfont-size: 18px;\n\tfont-weight: 800;\n\tmargin: 0;\n\tpadding: 0;\n\tcursor: default;\n}\n\n.collapsible-path .title .collapsible-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.collapsible-path .title svg.collapsed {\n\ttransform: rotate(-90deg);\n}\n\n.collapsible-path .title svg,\n.collapsible-path .title svg path,\n.collapsible-path .title h2 {\n\tpointer-events: none;\n}\n\n.collapsible-path .paths {\n\tdisplay: flex;\n\tflex-direction: column;\n\tpadding-left: 10px;\n\twidth: max-content;\n}\n\n.collapsible-path .paths.collapsed {\n\tdisplay: none;\n}\n\n.collapsible-path .path-item {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\twidth: max-content;\n\tgap: 8px;\n\tpadding: 4px 8px 4px 6px;\n\tborder-radius: 6px;\n\tcursor: var(--cursor-pointer) !important;\n\ttransition: 150ms ease-in-out;\n\tuser-select: none;\n}\n\n.collapsible-path .path-item:hover {\n\tbackground-color: #ffffff30;\n}\n\n.collapsible-path .path-item svg {\n\twidth: 18px;\n\theight: 18px;\n}\n\n.collapsible-path .path-item span {\n\tfont-size: 16px;\n\tfont-weight: 500;\n\tmargin: 0;\n\tpadding: 0;\n\tcursor: default;\n}\n\n.collapsible-path .path-item svg,\n.collapsible-path .path-item svg path,\n.collapsible-path .path-item span {\n\tpointer-events: none;\n}\n\nmain {\n\tdisplay: flex;\n\tflex-direction: row;\n\theight: calc(100% - var(--topbar-height));\n}\n\n.exp {\n\tposition: relative;\n\twidth: calc(100% - var(--sidebar-width));\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tpadding-bottom: 8px;\n\toverflow: auto;\n\tscrollbar-width: thin;\n\tscrollbar-color: #ffffff30 transparent;\n}\n\n.exp::-webkit-scrollbar {\n\twidth: 8px;\n}\n\n.exp::-webkit-scrollbar-track {\n\tbackground-color: transparent;\n}\n\n.exp::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff30;\n\tborder-radius: 8px;\n\tborder: 2px solid transparent;\n}\n\n.exp .path-item {\n\twidth: max-content;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tborder: 1px solid transparent;\n\tpadding: 4px 8px 4px 6px;\n\tborder-radius: 6px;\n\tcursor: var(--cursor-pointer) !important;\n\ttransition: 150ms ease-in-out;\n\ttransition-property: background-color;\n\tuser-select: none;\n}\n\n.exp .path-item.selected {\n\tborder: 1px solid #ffffff;\n\tbackground-color: #ffffff20;\n}\n\n.exp .path-item:hover {\n\tbackground-color: #ffffff30;\n}\n\n.exp .path-item svg {\n\twidth: 24px;\n\theight: 24px;\n}\n\n.exp .path-item img {\n\twidth: 24px;\n\theight: 24px;\n}\n\n.exp .path-item .icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.exp .path-item .title {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.exp .path-item .title,\n.exp .path-item .icon,\n.exp .path-item .icon svg,\n.exp .path-item .icon svg path {\n\tpointer-events: none;\n}\n\n.exp .sd-items {\n\tdisplay: flex;\n\tflex-direction: row;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n\n.exp .sd-items .sd-item {\n\twidth: max-content;\n\theight: max-content;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px;\n\tborder-radius: 6px;\n\ttransition: 150ms ease-in-out;\n\tuser-select: none;\n\tbackground-color: #ffffff30;\n}\n\n.exp .sd-items .sd-item .icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.exp .sd-items .sd-item .icon svg {\n\twidth: 42px;\n\theight: 42px;\n}\n\n.exp .sd-items .sd-item .icon img {\n\twidth: 42px;\n\theight: 42px;\n}\n\n.exp .info {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.exp .info .title {\n\tfont-size: 16px;\n\tfont-weight: 500;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.exp .info .title,\n.exp .info .icon,\n.exp .info .icon svg,\n.exp .info .icon svg path {\n\tpointer-events: none;\n}\n\n.exp .info .percent-container {\n\twidth: 160px;\n\theight: 8px;\n\tbackground-color: #ffffff30;\n\tborder-radius: 16px;\n\tborder: 1px solid #ffffff30;\n}\n\n.exp .info .percent {\n\tdisplay: block;\n\theight: 8px;\n\tbackground-color: #ffffff;\n\tborder-radius: 4px;\n}\n\n.exp .info .percent.low {\n\tbackground-color: #df3a3a;\n}\n"
  },
  {
    "path": "public/apps/files.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Terbium File Manager</title>\n    <link rel=\"stylesheet\" href=\"./index.css\">\n    <link rel=\"stylesheet\" href=\"./cm.css\">\n    <script src=\"/assets/libs/filer.min.js\"></script>\n    <script src=\"/assets/libs/tailwind.min.js\"></script>\n    <script src=\"./files.com.js\"></script>\n    <link rel=\"stylesheet\" href=\"/assets/materialsymbols.css\" />\n  </head>\n  <body>\n    <div class=\"topbar\">\n      <div class=\"nav-buttons\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"icon-button\" id=\"back\" action=\"back\">\n          <path fill-rule=\"evenodd\" d=\"M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z\" clip-rule=\"evenodd\" />\n        </svg>\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"icon-button\" id=\"forward\" action=\"forward\">\n          <path fill-rule=\"evenodd\" d=\"M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z\" clip-rule=\"evenodd\" />\n        </svg>\n        <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"icon-button\" id=\"reload\">\n          <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99\" />\n        </svg>\n      </div>\n      <div style=\"display: flex; flex-direction: row; gap: 4px;\">\n        <div class=\"nav-input drive-modal\" style=\"display: none; flex-direction: row; gap: 3px;\">\n        </div>\n      </div>\n      <input class=\"nav-input dir focus-within:outline-none\" type=\"text\">\n      <div class=\"relative flex\">\n        <input class=\"nav-input search focus-within:outline-none max-w-48\" type=\"text\" placeholder=\"Search\" />\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"clear-search absolute size-6 right-1 top-1/2 -translate-y-1/2 stroke-[currentColor] stroke-[1px] opacity-0 pointer-events-none duration-150\">\n          <path fill-rule=\"evenodd\" d=\"M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" />\n        </svg>\n      </div>\n    </div>\n    <main>\n      <div class=\"sidebar\"></div>\n      <div class=\"exp\"></div>\n    </main>\n    <script src=\"./index.js\" type=\"module\"></script>\n  </body>\n</html>"
  },
  {
    "path": "public/apps/files.tapp/index.js",
    "content": "const Filer = window.Filer;\n\nconst user = sessionStorage.getItem(\"currAcc\");\nwindow.addEventListener(\"load\", event => {\n\tconst dirInput = document.querySelector(\".nav-input.dir\");\n\tif (sessionStorage.getItem(\"ldir\")) {\n\t\tdirInput.value = sessionStorage.getItem(\"ldir\");\n\t\topenPath(sessionStorage.getItem(\"ldir\"));\n\t\tsessionStorage.removeItem(\"ldir\");\n\t} else {\n\t\tdirInput.value = `/home/${user}`;\n\t\topenPath(`/home/${user}`);\n\t}\n\tlet currentDir = \"/home\";\n\tdirInput.addEventListener(\"keydown\", e => {\n\t\tif (e.key === \"Enter\") {\n\t\t\tif (e.target.value === \"\") {\n\t\t\t\tdirInput.value = currentDir;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (e.target.value !== currentDir) {\n\t\t\t\topenPath(e.target.value);\n\t\t\t\tcurrentDir = e.target.value;\n\t\t\t}\n\t\t}\n\t});\n});\n\nconst back = async () => {\n\tconst dirInput = document.querySelector(\".nav-input.dir\");\n\tsessionStorage.setItem(\"lastDir\", dirInput.value);\n\tif (dirInput.value === \"/home\") {\n\t\topenPath(\"//\");\n\t} else {\n\t\tconst input = dirInput.value.trim();\n\t\tconst parts = input.split(\"/\");\n\t\tparts.pop();\n\t\tconst inp = parts.join(\"/\") + \"/\";\n\t\topenPath(inp);\n\t}\n};\n\nconst forward = async () => {\n\tconst dir = sessionStorage.getItem(\"lastDir\");\n\topenPath(dir);\n};\n\ndocument.getElementById(\"back\").addEventListener(\"click\", back);\ndocument.getElementById(\"forward\").addEventListener(\"click\", forward);\ndocument.getElementById(\"reload\").addEventListener(\"click\", () => {\n\topenPath(document.querySelector(\".nav-input.dir\").value);\n});\n\nconst emptyTrash = async () => {\n\tawait window.parent.tb.fs.promises.readdir(\"/system/trash\").then(async files => {\n\t\tif (files.length > 0) {\n\t\t\tfor (let file of files) {\n\t\t\t\tconst filePath = `/system/trash/${file}`;\n\t\t\t\twindow.parent.tb.fs.promises.stat(filePath, async (err, stats) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (stats.isFile()) {\n\t\t\t\t\t\twindow.parent.tb.fs.promises.unlink(filePath);\n\t\t\t\t\t} else if (stats.isDirectory()) {\n\t\t\t\t\t\tawait window.parent.tb.sh.promises.rm(filePath, { recursive: true });\n\t\t\t\t\t}\n\t\t\t\t\tif (document.querySelector(\".exp\").getAttribute(\"path\") === \"/system/trash\") {\n\t\t\t\t\t\tdocument.querySelectorAll(\".exp .path-item\").forEach(item => {\n\t\t\t\t\t\t\titem.remove();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t});\n};\n\nconst createCollapsible = async (title, id, opened, children) => {\n\tconst collapsible = document.createElement(\"div\");\n\tcollapsible.classList.add(\"collapsible-path\");\n\tcollapsible.id = id;\n\tconst collapsibleTitleContainer = document.createElement(\"div\");\n\tcollapsibleTitleContainer.classList.add(\"title\");\n\tconst pathTitle = document.createElement(\"h2\");\n\tpathTitle.textContent = title;\n\tconst collaspeIcon = document.createElement(\"div\");\n\tcollaspeIcon.classList.add(\"collapsible-icon\");\n\tcollaspeIcon.innerHTML = `\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"icon-button collapse-button\">\n            <path fill-rule=\"evenodd\" d=\"M12.53 16.28a.75.75 0 01-1.06 0l-7.5-7.5a.75.75 0 011.06-1.06L12 14.69l6.97-6.97a.75.75 0 111.06 1.06l-7.5 7.5z\" clip-rule=\"evenodd\" />\n        </svg>\n    `;\n\tcollapsibleTitleContainer.appendChild(pathTitle);\n\tcollapsibleTitleContainer.appendChild(collaspeIcon);\n\tcollapsible.appendChild(collapsibleTitleContainer);\n\n\tconst paths = document.createElement(\"div\");\n\tpaths.classList.add(\"paths\");\n\tif (opened) {\n\t\tpaths.classList.remove(\"collapsed\");\n\t\tcollaspeIcon.querySelector(\"svg\").classList.remove(\"collapsed\");\n\t} else if (opened === undefined || opened === false) {\n\t\tpaths.classList.add(\"collapsed\");\n\t\tcollaspeIcon.querySelector(\"svg\").classList.add(\"collapsed\");\n\t}\n\tfor (let title in children) {\n\t\tconst path = document.createElement(\"div\");\n\t\tpath.classList.add(\"path-item\");\n\t\tif (title.toLocaleLowerCase().endsWith(\".tapp\")) {\n\t\t\ttry {\n\t\t\t\tconst data = await window.parent.tb.fs.promises.readFile(`${path}/icon.svg`, \"utf8\");\n\t\t\t\tpath.innerHTML = data;\n\t\t\t} catch {\n\t\t\t\ticon.innerHTML = `\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                        <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\" clip-rule=\"evenodd\" />\n                        <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n                    </svg>\n                `;\n\t\t\t}\n\t\t} else {\n\t\t\tswitch (title.toLowerCase()) {\n\t\t\t\tcase \"desktop\":\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M2.25 5.25a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3V15a3 3 0 0 1-3 3h-3v.257c0 .597.237 1.17.659 1.591l.621.622a.75.75 0 0 1-.53 1.28h-9a.75.75 0 0 1-.53-1.28l.621-.622a2.25 2.25 0 0 0 .659-1.59V18h-3a3 3 0 0 1-3-3V5.25Zm1.5 0v7.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5v-7.5a1.5 1.5 0 0 0-1.5-1.5H5.25a1.5 1.5 0 0 0-1.5 1.5Z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"documents\":\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\" clip-rule=\"evenodd\" />\n                            <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"images\":\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"videos\":\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M1.5 5.625c0-1.036.84-1.875 1.875-1.875h17.25c1.035 0 1.875.84 1.875 1.875v12.75c0 1.035-.84 1.875-1.875 1.875H3.375A1.875 1.875 0 011.5 18.375V5.625zm1.5 0v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5A.375.375 0 003 5.625zm16.125-.375a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5A.375.375 0 0021 7.125v-1.5a.375.375 0 00-.375-.375h-1.5zM21 9.375A.375.375 0 0020.625 9h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zm0 3.75a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zm0 3.75a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zM4.875 18.75a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5zM3.375 15h1.5a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375zm0-3.75h1.5a.375.375 0 00.375-.375v-1.5A.375.375 0 004.875 9h-1.5A.375.375 0 003 9.375v1.5c0 .207.168.375.375.375zm4.125 0a.75.75 0 000 1.5h9a.75.75 0 000-1.5h-9z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"music\":\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M19.952 1.651a.75.75 0 01.298.599V16.303a3 3 0 01-2.176 2.884l-1.32.377a2.553 2.553 0 11-1.403-4.909l2.311-.66a1.5 1.5 0 001.088-1.442V6.994l-9 2.572v9.737a3 3 0 01-2.176 2.884l-1.32.377a2.553 2.553 0 11-1.402-4.909l2.31-.66a1.5 1.5 0 001.088-1.442V9.017 5.25a.75.75 0 01.544-.721l10.5-3a.75.75 0 01.658.122z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"trash\":\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M16.5 4.478v.227a48.816 48.816 0 013.878.512.75.75 0 11-.256 1.478l-.209-.035-1.005 13.07a3 3 0 01-2.991 2.77H8.084a3 3 0 01-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 01-.256-1.478A48.567 48.567 0 017.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 013.369 0c1.603.051 2.815 1.387 2.815 2.951zm-6.136-1.452a51.196 51.196 0 013.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 00-6 0v-.113c0-.794.609-1.428 1.364-1.452zm-.355 5.945a.75.75 0 10-1.5.058l.347 9a.75.75 0 101.499-.058l-.346-9zm5.48.058a.75.75 0 10-1.498-.058l-.347 9a.75.75 0 001.5.058l.345-9z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"file system\":\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path d=\"M4.08 5.227A3 3 0 016.979 3H17.02a3 3 0 012.9 2.227l2.113 7.926A5.228 5.228 0 0018.75 12H5.25a5.228 5.228 0 00-3.284 1.153L4.08 5.227z\" />\n                            <path fill-rule=\"evenodd\" d=\"M5.25 13.5a3.75 3.75 0 100 7.5h13.5a3.75 3.75 0 100-7.5H5.25zm10.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5zm3.75-.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tpath.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path d=\"M19.5 21a3 3 0 003-3v-4.5a3 3 0 00-3-3h-15a3 3 0 00-3 3V18a3 3 0 003 3h15zM1.5 10.146V6a3 3 0 013-3h5.379a2.25 2.25 0 011.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 013 3v1.146A4.483 4.483 0 0019.5 9h-15a4.483 4.483 0 00-3 1.146z\"></path>\n                        </svg>\n                    `;\n\t\t\t\t\tpath.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\tpath.setAttribute(\"oneclick\", \"true\");\n\t\t\t\t\tpath.id = `f-${title.toLowerCase()}`;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst pathTitle = document.createElement(\"span\");\n\t\tpathTitle.textContent = title;\n\t\tpath.appendChild(pathTitle);\n\t\tpaths.appendChild(path);\n\t\tfunction click() {\n\t\t\topenPath(children[title]);\n\t\t}\n\t\tpath.getAttribute(\"oneclick\") === \"true\" ? path.addEventListener(\"click\", e => click()) : path.addEventListener(\"dblclick\", e => click());\n\t}\n\tcollapsible.appendChild(paths);\n\tcollapsibleTitleContainer.addEventListener(\"click\", async e => {\n\t\tconst icon = collapsible.querySelector(\".collapsible-icon svg\");\n\t\tconst paths = collapsible.querySelector(\".paths\");\n\t\tlet qcdata = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/config.json`, \"utf8\"));\n\t\tif (qcdata[\"open-collapsibles\"]) {\n\t\t\tif (qcdata[\"open-collapsibles\"][id]) {\n\t\t\t\tif (qcdata[\"open-collapsibles\"][id] === true) {\n\t\t\t\t\tqcdata[\"open-collapsibles\"][id] = false;\n\t\t\t\t} else if (qcdata[\"open-collapsibles\"][\"quick-center\"] === false) {\n\t\t\t\t\tqcdata[\"open-collapsibles\"][id] = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (collapsible.classList.contains(\"collapsed\")) {\n\t\t\t\t\tqcdata[\"open-collapsibles\"][id] = false;\n\t\t\t\t} else {\n\t\t\t\t\tqcdata[\"open-collapsibles\"][id] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/config.json`, JSON.stringify(qcdata));\n\t\t}\n\t\tif (icon.classList.contains(\"collapsed\")) {\n\t\t\ticon.classList.remove(\"collapsed\");\n\t\t\tpaths.classList.remove(\"collapsed\");\n\t\t\tcollapsible.classList.remove(\"collapsed\");\n\t\t} else {\n\t\t\ticon.classList.add(\"collapsed\");\n\t\t\tpaths.classList.add(\"collapsed\");\n\t\t\tcollapsible.classList.add(\"collapsed\");\n\t\t}\n\t});\n\tdocument.querySelector(\".sidebar\").appendChild(collapsible);\n\treturn true;\n};\n\nconst createStorageDeviceCard = (type, davInfo) => {\n\tconst item = document.createElement(\"div\");\n\titem.classList.add(\"sd-item\");\n\tconst icon = document.createElement(\"div\");\n\ticon.classList.add(\"icon\");\n\ticon.innerHTML = `\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n            <path d=\"M4.08 5.227A3 3 0 016.979 3H17.02a3 3 0 012.9 2.227l2.113 7.926A5.228 5.228 0 0018.75 12H5.25a5.228 5.228 0 00-3.284 1.153L4.08 5.227z\" />\n            <path fill-rule=\"evenodd\" d=\"M5.25 13.5a3.75 3.75 0 100 7.5h13.5a3.75 3.75 0 100-7.5H5.25zm10.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5zm3.75-.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0z\" clip-rule=\"evenodd\" />\n        </svg>\n    `;\n\titem.appendChild(icon);\n\tconst info = document.createElement(\"div\");\n\tinfo.classList.add(\"info\");\n\tconst title = document.createElement(\"span\");\n\ttitle.classList.add(\"title\");\n\tinfo.appendChild(title);\n\tconst percentContainer = document.createElement(\"span\");\n\tpercentContainer.classList.add(\"percent-container\");\n\tconst percent = document.createElement(\"span\");\n\tpercent.classList.add(\"percent\");\n\tpercent.style.width = \"100%\";\n\tpercentContainer.appendChild(percent);\n\tinfo.appendChild(percentContainer);\n\tconst size = document.createElement(\"div\");\n\tsize.classList.add(\"size\");\n\tinfo.appendChild(size);\n\titem.addEventListener(\"dblclick\", () => {\n\t\tswitch (type) {\n\t\t\tcase \"local\":\n\t\t\t\topenPath(\"local storage\");\n\t\t\t\tbreak;\n\t\t\tcase \"fs\":\n\t\t\t\topenPath(\"//\");\n\t\t\t\tbreak;\n\t\t\tcase \"dav\":\n\t\t\t\topenPath(`/mnt/${davInfo.name}/`);\n\t\t\t\tbreak;\n\t\t}\n\t});\n\n\tswitch (type) {\n\t\tcase \"local\":\n\t\t\ttitle.textContent = \"Local Storage\";\n\t\t\tconst maxStorage = 10 * 1024 * 1024;\n\t\t\tconst warningThreshold = 90;\n\t\t\tconst usedSize = Object.keys(localStorage).reduce((total, key) => {\n\t\t\t\tconst item = localStorage.getItem(key);\n\t\t\t\tconst itemSize = JSON.stringify(item).length * 2;\n\t\t\t\tconst keySize = key.length * 2;\n\t\t\t\ttotal += itemSize + keySize;\n\t\t\t\treturn total;\n\t\t\t}, 0);\n\n\t\t\tconst usedPercentage = (usedSize / maxStorage) * 100;\n\t\t\tlet formattedSize;\n\t\t\tif (usedSize >= 1024 * 1024 * 1024) {\n\t\t\t\tformattedSize = `${(usedSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t\t\t} else {\n\t\t\t\tformattedSize = `${(usedSize / (1024 * 1024)).toFixed(2)} MB`;\n\t\t\t}\n\n\t\t\tsize.textContent = `${formattedSize} of ${maxStorage / (1024 * 1024)} MB`;\n\t\t\tconst minWidth = 8;\n\t\t\tconst maxWidth = 165;\n\t\t\tconst calculatedWidth = (Math.min(usedPercentage, 100) * (maxWidth - minWidth)) / 100 + minWidth;\n\t\t\tpercent.style.width = `${Math.min(calculatedWidth, 160)}px`;\n\n\t\t\tif (usedPercentage >= warningThreshold) {\n\t\t\t\tpercent.style.backgroundColor = \"#D8645D\";\n\t\t\t} else {\n\t\t\t\tpercent.style.backgroundColor = \"#5D78D8\";\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"fs\":\n\t\t\ttitle.textContent = \"File System\";\n\t\t\tif (\"navigator\" in window && \"storage\" in navigator) {\n\t\t\t\tnavigator.storage.estimate().then(estimate => {\n\t\t\t\t\tconst totalSize = estimate.quota;\n\t\t\t\t\tconst usedSize = estimate.usage;\n\t\t\t\t\tconst usedPercentage = (usedSize / totalSize) * 100;\n\t\t\t\t\tlet formattedUsedSize, formattedTotalSize;\n\t\t\t\t\tif (usedSize >= 1024 * 1024 * 1024) {\n\t\t\t\t\t\tformattedUsedSize = `${(usedSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tformattedUsedSize = `${(usedSize / (1024 * 1024)).toFixed(2)} MB`;\n\t\t\t\t\t}\n\t\t\t\t\tif (totalSize >= 1024 * 1024 * 1024) {\n\t\t\t\t\t\tformattedTotalSize = `${(totalSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tformattedTotalSize = `${Math.round((totalSize / (1024 * 1024)).toFixed(2))} MB`;\n\t\t\t\t\t}\n\t\t\t\t\tsize.textContent = `${formattedUsedSize} of ${formattedTotalSize}`;\n\t\t\t\t\tconst minWidth = 8;\n\t\t\t\t\tconst maxWidth = 165;\n\t\t\t\t\tconst calculatedWidth = (Math.min(usedPercentage, 100) * (maxWidth - minWidth)) / 100 + minWidth;\n\t\t\t\t\tpercent.style.width = `${calculatedWidth}px`;\n\t\t\t\t\tif (usedPercentage >= 90) {\n\t\t\t\t\t\tpercent.style.backgroundColor = \"#D8645D\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpercent.style.backgroundColor = \"#5D78D8\";\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"dav\":\n\t\t\ttitle.textContent = davInfo.name || \"Dav Drive\";\n\t\t\tconst displayText = davInfo.url || \"http://localhost:3001/dav/\";\n\t\t\tsize.textContent = displayText.length > 18 ? displayText.slice(0, 18) + \"...\" : displayText;\n\t\t\titem.id = `f-${davInfo.name.toLocaleLowerCase()}`;\n\t\t\tpercent.style.width = \"100%\";\n\t\t\tconst test = async () => {\n\t\t\t\tconst servers = window.parent.tb.vfs.servers;\n\t\t\t\tif (servers.has(davInfo.name) && servers.get(davInfo.name).connected === true) {\n\t\t\t\t\tconst icn = `\n                        <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                            <path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\"/>\n                            <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"/>\n                            <circle cx=\"18\" cy=\"17.25\" r=\"3\" fill=\"#5DD881\"/>\n                        </svg>\n                    `;\n\t\t\t\t\tdocument.getElementById(`f-${davInfo.name.toLocaleLowerCase()}`).innerHTML = `\n                        ${icn}\n                        <span>${davInfo.name}</span>\n                    `;\n\t\t\t\t\ticon.innerHTML = icn;\n\t\t\t\t\tpercent.style.backgroundColor = \"#5DD881\";\n\t\t\t\t} else {\n\t\t\t\t\tconst icn = `\n                        <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                            <path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\"/>\n                            <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"/>\n                            <circle cx=\"18\" cy=\"17.25\" r=\"3\" fill=\"#D8645D\"/>\n                        </svg>\n                    `;\n\t\t\t\t\tdocument.getElementById(`f-${davInfo.name.toLocaleLowerCase()}`).innerHTML = `\n                        ${icn}\n                        <span>${davInfo.name}</span>\n                    `;\n\t\t\t\t\ticon.innerHTML = icn;\n\t\t\t\t\tpercent.style.backgroundColor = \"#D8645D\";\n\t\t\t\t}\n\t\t\t};\n\t\t\ttest();\n\t\t\tbreak;\n\t}\n\n\titem.appendChild(info);\n\treturn item;\n};\n\nconst showStorageDevices = () => {\n\tconst exp = document.querySelector(\".exp\");\n\texp.innerHTML = \"\";\n\tlet fscard = createStorageDeviceCard(\"fs\");\n\tlet lscard = createStorageDeviceCard(\"local\");\n\tconst sd_items = document.createElement(\"div\");\n\tsd_items.classList.add(\"sd-items\");\n\tsd_items.appendChild(fscard);\n\tsd_items.appendChild(lscard);\n\tconst getdav = async () => {\n\t\tconst davInstances = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\tfor (const dav of davInstances) {\n\t\t\tlet si = createStorageDeviceCard(\"dav\", { name: dav.name, url: dav.url, user: dav.username, pass: dav.password });\n\t\t\tsd_items.appendChild(si);\n\t\t}\n\t};\n\tgetdav();\n\texp.appendChild(sd_items);\n\tdocument.querySelector(\".drive-modal\").style.display = \"none\";\n\tconst dirInput = document.querySelector(\".nav-input.dir\");\n\tdirInput.value = \"storage devices\";\n};\n\nconst showLS = async () => {\n\tconst keys = Object.keys(localStorage);\n\tconst exp = document.querySelector(\".exp\");\n\texp.innerHTML = \"\";\n\tconst dirInput = document.querySelector(\".nav-input.dir\");\n\tdirInput.value = \"local storage\";\n\tkeys.forEach(key => {\n\t\tconst pathItem = document.createElement(\"div\");\n\t\tpathItem.classList.add(\"path-item\", \"ls-item\");\n\t\tconst icon = document.createElement(\"div\");\n\t\ticon.classList.add(\"icon\");\n\t\ticon.innerHTML = `\n            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z\" clip-rule=\"evenodd\" />\n                <path d=\"M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z\" />\n            </svg>\n        `;\n\t\tpathItem.appendChild(icon);\n\t\tconst title = document.createElement(\"span\");\n\t\ttitle.classList.add(\"title\");\n\t\ttitle.textContent = key;\n\t\tpathItem.appendChild(title);\n\t\tpathItem.setAttribute(\"path\", `local storage/${key}`);\n\t\texp.appendChild(pathItem);\n\t\tpathItem.addEventListener(\"dblclick\", e => {\n\t\t\tlet lsitem = localStorage.getItem(key);\n\t\t\ttb.dialog.Message({\n\t\t\t\ttitle: `Change the key for ${key}`,\n\t\t\t\tdefaultValue: lsitem,\n\t\t\t\tonOk: async newKey => {\n\t\t\t\t\tif (newKey !== null && newKey !== \"\" && newKey !== lsitem) {\n\t\t\t\t\t\tlocalStorage.setItem(key, newKey);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t});\n};\n\nconst useDavClient = async path => {\n\tconst client = window.parent.tb.vfs.currentServer.connection.client;\n\tlet filePath;\n\tif (path.startsWith(\"http\")) {\n\t\tconst match = path.match(/^https?:\\/\\/[^\\/]+\\/dav\\/([^\\/]+\\/)?(.+)$/);\n\t\tfilePath = match ? \"/\" + match[2] : path;\n\t} else {\n\t\tfilePath = path.replace(davUrl, \"/\");\n\t}\n\tlet mntPath;\n\tif (path.startsWith(\"/mnt/\")) {\n\t\tmntPath = path;\n\t} else {\n\t\tmntPath = window.parent.tb.fs.normalizePath(`/mnt/${window.parent.tb.vfs.currentServer.name}/${filePath}`);\n\t}\n\treturn { client, filePath, mntPath };\n};\n\nconst getItemDetails = async path => {\n\tif (path.includes(\"http\")) {\n\t\tconst { client, filePath, mntPath } = await useDavClient(path);\n\t\tconst stats = await client.stat(filePath, { depth: 1 });\n\t\tlet message = JSON.stringify({\n\t\t\ttype: \"item-details\",\n\t\t\tpath: mntPath,\n\t\t\tdetails: {\n\t\t\t\tname: stats.basename,\n\t\t\t\ttype: stats.mime,\n\t\t\t\tsize: stats.size,\n\t\t\t\tcreated: null,\n\t\t\t\tmodified: stats.lastmod,\n\t\t\t\taccessed: null,\n\t\t\t\towner: \"WebDav\",\n\t\t\t\tmode: null,\n\t\t\t\tversion: stats.etag,\n\t\t\t},\n\t\t});\n\n\t\tparent.window.tb.window.create({\n\t\t\ttitle: `Properties`,\n\t\t\ticon: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\tsrc: \"/fs/apps/system/files.tapp/properties/index.html\",\n\t\t\tsize: {\n\t\t\t\twidth: 350,\n\t\t\t\theight: 275,\n\t\t\t},\n\t\t\tcontrols: [\"minimize\", \"close\"],\n\t\t\tmessage: message,\n\t\t});\n\t} else {\n\t\twindow.parent.tb.fs.stat(path, (err, stats) => {\n\t\t\tif (err) return console.error(err);\n\t\t\tlet name = stats.name;\n\t\t\tlet type = stats.isFile() ? \"File\" : stats.isDirectory() ? \"Folder\" : \"Symbolic Link\";\n\t\t\tlet size = stats.size;\n\t\t\tlet created = stats.ctime;\n\t\t\tlet modified = stats.mtime;\n\t\t\tlet accessed = stats.atime;\n\t\t\tlet owner = stats.uid;\n\t\t\tlet mode = stats.mode;\n\t\t\tlet version = stats.version;\n\t\t\tlet mime = stats.mime;\n\n\t\t\tlet message = JSON.stringify({\n\t\t\t\ttype: \"item-details\",\n\t\t\t\tpath: path,\n\t\t\t\tdetails: {\n\t\t\t\t\tname: name,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tsize: size,\n\t\t\t\t\tmime: mime,\n\t\t\t\t\tcreated: created,\n\t\t\t\t\tmodified: modified,\n\t\t\t\t\taccessed: accessed,\n\t\t\t\t\towner: owner,\n\t\t\t\t\tmode: mode,\n\t\t\t\t\tversion: version,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tparent.window.tb.window.create({\n\t\t\t\ttitle: `Properties`,\n\t\t\t\ticon: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\tsrc: \"/fs/apps/system/files.tapp/properties/index.html\",\n\t\t\t\tsize: {\n\t\t\t\t\twidth: 350,\n\t\t\t\t\theight: 275,\n\t\t\t\t},\n\t\t\t\tcontrols: [\"minimize\", \"close\"],\n\t\t\t\tmessage: message,\n\t\t\t});\n\t\t});\n\t}\n};\n\nlet copied = null;\nlet cut = null;\n\nconst cm = async e => {\n\te.preventDefault();\n\tif (document.querySelector(\".context-menu\")) document.querySelector(\".context-menu\").remove();\n\tconst context = document.createElement(\"div\");\n\tcontext.classList.add(\"context-menu\");\n\n\tcontext.classList.add(\"fade-in\");\n\tsetTimeout(() => {\n\t\tcontext.classList.remove(\"fade-in\");\n\t}, 200);\n\tlet options = [];\n\tlet isTrash = document.querySelector(\".exp\").getAttribute(\"path\") === \"/system/trash\" ? true : false;\n\tif (e.target.getAttribute(\"type\") === \"file\") {\n\t\toptions = [\n\t\t\t{\n\t\t\t\ttext: \"Open\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tconst name = e.target.getAttribute(\"name\");\n\t\t\t\t\tconst parts = name.split(\".\");\n\t\t\t\t\tlet ext;\n\t\t\t\t\tif (parts.length > 2) {\n\t\t\t\t\t\text = parts.slice(-2).join(\".\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\text = parts.slice(-1).join(\".\");\n\t\t\t\t\t}\n\t\t\t\t\tconst data = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\t\t\t\t\tif (data[\"image\"].includes(ext)) {\n\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"image\");\n\t\t\t\t\t} else if (data[\"video\"].includes(ext)) {\n\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"video\");\n\t\t\t\t\t} else if (data[\"audio\"].includes(ext)) {\n\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"audio\");\n\t\t\t\t\t} else if (data[\"pdf\"].includes(ext)) {\n\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"pdf\");\n\t\t\t\t\t} else if (ext.toLowerCase() === \"tapp.zip\") {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst path = e.target.getAttribute(\"path\");\n\t\t\t\t\t\t\tawait window.parent.tb.dialog.Permissions({\n\t\t\t\t\t\t\t\ttitle: \"Install application\",\n\t\t\t\t\t\t\t\tmessage: `Would you like to install the application: ${path}?`,\n\t\t\t\t\t\t\t\tonOk: async () => {\n\t\t\t\t\t\t\t\t\tconst appPath = `/fs/${path}`.replace(\"//\", \"/\");\n\t\t\t\t\t\t\t\t\tconst appName =\n\t\t\t\t\t\t\t\t\t\tpath\n\t\t\t\t\t\t\t\t\t\t\t.replace(`/home/${window.parent.sessionStorage.getItem(\"currAcc\")}/`, \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\//g, \".\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\.zip$/, \"\") + \"\";\n\t\t\t\t\t\t\t\t\twindow.parent.tb.notification.Installing({\n\t\t\t\t\t\t\t\t\t\tmessage: `Installing ${appName}...`,\n\t\t\t\t\t\t\t\t\t\tapplication: \"Files\",\n\t\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\ttime: 500,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tconst zipFilePath = e.target.getAttribute(\"path\");\n\t\t\t\t\t\t\t\t\t\tconst targetDirectory = `/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}`;\n\t\t\t\t\t\t\t\t\t\tawait unzip(zipFilePath, targetDirectory, true);\n\t\t\t\t\t\t\t\t\t\tconsole.log(\"Done!\");\n\t\t\t\t\t\t\t\t\t\tconst appConf = await window.parent.tb.fs.promises.readFile(`/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}/.tbconfig`, \"utf8\");\n\t\t\t\t\t\t\t\t\t\tconst appData = JSON.parse(appConf);\n\t\t\t\t\t\t\t\t\t\tconsole.log(appData);\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\t\t\t\t\t\t\t\tname: appData.title,\n\t\t\t\t\t\t\t\t\t\t\ticon: appData.icon.includes(\"http\") ? appData.icon : `/fs/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}/${appData.icon}`,\n\t\t\t\t\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\t\t\t\t\ttypeof appData.wmArgs.title === \"object\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttext: appData.wmArgs.title.text,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tweight: appData.wmArgs.title.weight,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thtml: appData.wmArgs.title.html,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t: appData.wmArgs.title,\n\t\t\t\t\t\t\t\t\t\t\tsrc: `/fs/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}/${appData.wmArgs.src}`,\n\t\t\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\t\t\twidth: appData.wmArgs.size.width,\n\t\t\t\t\t\t\t\t\t\t\t\theight: appData.wmArgs.size.height,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tsingle: appData.wmArgs.single,\n\t\t\t\t\t\t\t\t\t\t\tresizable: appData.wmArgs.resizable,\n\t\t\t\t\t\t\t\t\t\t\tcontrols: appData.wmArgs.controls,\n\t\t\t\t\t\t\t\t\t\t\tmessage: appData.wmArgs.message,\n\t\t\t\t\t\t\t\t\t\t\tsnapable: appData.wmArgs.snapable,\n\t\t\t\t\t\t\t\t\t\t\tuser: window.parent.sessionStorage.getItem(\"currAcc\"),\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\tapps.push({\n\t\t\t\t\t\t\t\t\t\t\t\tname: appName,\n\t\t\t\t\t\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\t\t\t\t\t\tconfig: `/apps/system/${appName}.tapp/.tbconfig`,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t\t\t\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\t\t\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tname: appName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconfig: `/apps/system/${appName}.tapp/.tbconfig`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\t\t\t\t\t\t\tmessage: `${appName} has been installed!`,\n\t\t\t\t\t\t\t\t\t\t\tapplication: \"Files\",\n\t\t\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\t\ttime: 5000,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\t\t\tconsole.error(\"Error installing the app:\", e);\n\t\t\t\t\t\t\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\t\t\t\t\t\t\tmessage: `Failed to install ${appName}. Check the console for details.`,\n\t\t\t\t\t\t\t\t\t\t\tapplication: \"Files\",\n\t\t\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\t\ttime: 5000,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\twindow.parent.tb.dialog.Alert({\n\t\t\t\t\t\t\t\ttitle: \"Unexpected Error\",\n\t\t\t\t\t\t\t\tmessage: `❌ An Unexpected error occurred when trying to install the app: ${path} Error: ${e}`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (data[\"extractables\"].includes(ext) || ext.toLowerCase() === \"app.zip\" || ext.toLowerCase() === \"lib.zip\") {\n\t\t\t\t\t\tconst zipFilePath = e.target.getAttribute(\"path\");\n\t\t\t\t\t\tconst path = item.getAttribute(\"path\").replace(\".zip\", \"\");\n\t\t\t\t\t\tconst targetDirectory = `${path}`;\n\t\t\t\t\t\tawait unzip(zipFilePath, targetDirectory);\n\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t} else if (data[\"text\"].includes(ext)) {\n\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"text\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst path = e.target.getAttribute(\"path\");\n\t\t\t\t\t\tconst name = e.target.getAttribute(\"name\");\n\t\t\t\t\t\tconst parts = name.split(\".\");\n\t\t\t\t\t\tconst extKey = parts.length > 2 ? parts.slice(-2).join(\".\").toLowerCase() : parts.slice(-1).join(\".\").toLowerCase();\n\t\t\t\t\t\tconst allHandlers = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"fileAssociatedApps\"] || {};\n\t\t\t\t\t\tif (allHandlers[extKey]) {\n\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, extKey);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet handlers = Object.entries(allHandlers).filter(([type, app]) => {\n\t\t\t\t\t\t\treturn !(type === \"text\" && app === \"text-editor\") && !(type === \"image\" && app === \"media-viewer\") && !(type === \"video\" && app === \"media-viewer\") && !(type === \"audio\" && app === \"media-viewer\");\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlet hands = [];\n\t\t\t\t\t\tfor (const [type, app] of handlers) {\n\t\t\t\t\t\t\thands.push({ text: app, value: type });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait tb.dialog.Select({\n\t\t\t\t\t\t\ttitle: `Select a application to open: ${path.split(\"/\").pop()}`,\n\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Text Editor\",\n\t\t\t\t\t\t\t\t\tvalue: \"text\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Media Viewer\",\n\t\t\t\t\t\t\t\t\tvalue: \"media\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Webview\",\n\t\t\t\t\t\t\t\t\tvalue: \"webview\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t...hands,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Other\",\n\t\t\t\t\t\t\t\t\tvalue: \"other\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\t\tswitch (val) {\n\t\t\t\t\t\t\t\t\tcase \"text\":\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"text\");\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"media\":\n\t\t\t\t\t\t\t\t\t\tconst ext = name.split(\".\").pop();\n\t\t\t\t\t\t\t\t\t\tif (data[\"image\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"image\");\n\t\t\t\t\t\t\t\t\t\t} else if (data[\"video\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"video\");\n\t\t\t\t\t\t\t\t\t\t} else if (data[\"audio\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"audio\");\n\t\t\t\t\t\t\t\t\t\t} else if (data[\"pdf\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"pdf\");\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"webview\":\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"webpage\");\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"other\":\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\t\tfilter: \".tapp\",\n\t\t\t\t\t\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst app = JSON.parse(await window.parent.tb.fs.promises.readFile(`${val}/.tbconfig`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\t\twindow.parent.tb.window.create({ ...app.wmArgs, message: { type: \"process\", path: item.item } });\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tif (hands.length === 0) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"text\");\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, val);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Open With\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tlet handlers = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"fileAssociatedApps\"];\n\t\t\t\t\thandlers = Object.entries(handlers).filter(([type, app]) => {\n\t\t\t\t\t\treturn !(type === \"text\" && app === \"text-editor\") && !(type === \"image\" && app === \"media-viewer\") && !(type === \"video\" && app === \"media-viewer\") && !(type === \"audio\" && app === \"media-viewer\");\n\t\t\t\t\t});\n\t\t\t\t\tlet hands = [];\n\t\t\t\t\tfor (const [type, app] of handlers) {\n\t\t\t\t\t\thands.push({ text: app, value: type });\n\t\t\t\t\t}\n\t\t\t\t\tconst data = JSON.parse(await window.parent.window.parent.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\t\t\t\t\tawait tb.dialog.Select({\n\t\t\t\t\t\ttitle: `Select a application to open: ${e.target.getAttribute(\"path\").split(\"/\").pop()}`,\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Text Editor\",\n\t\t\t\t\t\t\t\tvalue: \"text\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Media Viewer\",\n\t\t\t\t\t\t\t\tvalue: \"media\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Webview\",\n\t\t\t\t\t\t\t\tvalue: \"webview\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t...hands,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Other\",\n\t\t\t\t\t\t\t\tvalue: \"other\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\tswitch (val) {\n\t\t\t\t\t\t\t\tcase \"text\":\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"text\");\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase \"media\":\n\t\t\t\t\t\t\t\t\tconst ext = e.target.getAttribute(\"name\").split(\".\").pop();\n\t\t\t\t\t\t\t\t\tif (data[\"image\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"image\");\n\t\t\t\t\t\t\t\t\t} else if (data[\"video\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"video\");\n\t\t\t\t\t\t\t\t\t} else if (data[\"audio\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"audio\");\n\t\t\t\t\t\t\t\t\t} else if (data[\"pdf\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"pdf\");\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase \"webview\":\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"webpage\");\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase \"other\":\n\t\t\t\t\t\t\t\t\tparent.window.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\tfilter: \".tapp\",\n\t\t\t\t\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\t\t\t\t\tconst app = JSON.parse(await window.parent.tb.fs.promises.readFile(`${val}/.tbconfig`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\twindow.parent.tb.window.create({ ...app.wmArgs, message: { type: \"process\", path: item.item } });\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\tif (hands.length === 0) {\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), \"text\");\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(e.target.getAttribute(\"path\"), val);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Rename\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst path = e.target.getAttribute(\"path\");\n\t\t\t\t\t\tconst currentFileName = e.target.querySelector(\".title\").textContent;\n\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\ttitle: `Enter a new name for ${currentFileName}`,\n\t\t\t\t\t\t\tdefaultValue: currentFileName,\n\t\t\t\t\t\t\tonOk: async newFileName => {\n\t\t\t\t\t\t\t\tif (newFileName === currentFileName) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (path.includes(\"http\")) {\n\t\t\t\t\t\t\t\t\tconst { client, filePath } = await useDavClient(path);\n\t\t\t\t\t\t\t\t\tawait client.moveFile(filePath, `${path}/${newFileName}`);\n\t\t\t\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.rename(path, `${e.target.getAttribute(\"parent-path\")}/${newFileName}`);\n\t\t\t\t\t\t\t\t\t\tconst type = e.target.classList.contains(\"file-item\") ? \"file\" : \"folder\";\n\t\t\t\t\t\t\t\t\t\tdocument.querySelector(`[path=\"${path}\"]`).remove();\n\t\t\t\t\t\t\t\t\t\tconst item = document.createElement(\"div\");\n\t\t\t\t\t\t\t\t\t\titem.classList.add(`${type}-item`, \"path-item\");\n\t\t\t\t\t\t\t\t\t\tconst icon = document.createElement(\"div\");\n\t\t\t\t\t\t\t\t\t\ticon.classList.add(\"icon\");\n\t\t\t\t\t\t\t\t\t\tlet ext = newFileName.split(\".\").pop();\n\t\t\t\t\t\t\t\t\t\tconst data = await fetch(`/fs//system/etc/terbium/file-icons.json`).then(res => res.json());\n\t\t\t\t\t\t\t\t\t\tlet iconName = data[\"ext-to-name\"][ext];\n\t\t\t\t\t\t\t\t\t\tlet iconPath = data[\"name-to-path\"][iconName];\n\t\t\t\t\t\t\t\t\t\tlet unknown = data[\"name-to-path\"][\"Unknown\"];\n\t\t\t\t\t\t\t\t\t\tif (iconPath) {\n\t\t\t\t\t\t\t\t\t\t\ticon.innerHTML = iconPath;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\ticon.innerHTML = unknown;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\titem.appendChild(icon);\n\t\t\t\t\t\t\t\t\t\tconst itemTitle = document.createElement(\"span\");\n\t\t\t\t\t\t\t\t\t\titemTitle.classList.add(\"title\");\n\t\t\t\t\t\t\t\t\t\titemTitle.textContent = newFileName;\n\t\t\t\t\t\t\t\t\t\titem.appendChild(itemTitle);\n\t\t\t\t\t\t\t\t\t\titem.setAttribute(\"path\", `${e.target.getAttribute(\"parent-path\")}/${newFileName}`);\n\t\t\t\t\t\t\t\t\t\titem.setAttribute(\"name\", newFileName);\n\t\t\t\t\t\t\t\t\t\tlet pbs = path.split(\"/\");\n\t\t\t\t\t\t\t\t\t\tpbs.pop();\n\t\t\t\t\t\t\t\t\t\tpbs = pbs.join(\"/\");\n\t\t\t\t\t\t\t\t\t\titem.setAttribute(\"parent-path\", pbs);\n\t\t\t\t\t\t\t\t\t\tdocument.querySelector(\".exp\").appendChild(item);\n\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\tconsole.log(error);\n\t\t\t\t\t\t\t\t\t\tconst ask = await tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\ttitle: `This file already exists. Enter a new name for ${newFileName}`,\n\t\t\t\t\t\t\t\t\t\t\tdefaultValue: newFileName,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tif (ask !== undefined && ask !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\tawait rename(path, ask);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(error);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Copy\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tcopied = { path: e.target.getAttribute(\"path\"), name: e.target.getAttribute(\"name\") };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Cut\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tcopied = { path: e.target.getAttribute(\"path\"), name: e.target.getAttribute(\"name\") };\n\t\t\t\t\te.target.classList.add(\"opacity-50\");\n\t\t\t\t\tcut = true;\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: isTrash ? \"Delete\" : e.target.getAttribute(\"path\").includes(\"http\") ? \"Delete File\" : \"Move To Trash\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tconst path = e.target.getAttribute(\"path\");\n\t\t\t\t\tif (path.includes(\"http\")) {\n\t\t\t\t\t\tconst { client, filePath } = await useDavClient(path);\n\t\t\t\t\t\tclient.deleteFile(filePath);\n\t\t\t\t\t\tdocument.querySelector(\".exp\").removeChild(e.target);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (document.querySelector(\".exp\").getAttribute(\"path\") === \"/system/trash\") {\n\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.unlink(path);\n\t\t\t\t\t\t\tdocument.querySelector(\".exp\").removeChild(e.target);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet data = await window.parent.tb.fs.promises.readFile(path, \"utf8\");\n\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/system/trash/${e.target.getAttribute(\"name\")}`, data);\n\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.unlink(path);\n\t\t\t\t\t\t\tdocument.querySelector(\".exp\").removeChild(e.target);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Download to Computer\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tconst path = e.target.getAttribute(\"path\");\n\t\t\t\t\tconst name = e.target.getAttribute(\"name\");\n\t\t\t\t\tconst lk = document.createElement(\"a\");\n\t\t\t\t\tlk.download = name;\n\t\t\t\t\tif (path.includes(\"http\")) {\n\t\t\t\t\t\tconst { client, filePath } = await useDavClient(path);\n\t\t\t\t\t\tconst blob = await client.getFileContents(filePath);\n\t\t\t\t\t\tconst stats = await client.stat(filePath);\n\t\t\t\t\t\tconst fileBlob = new Blob([blob], { type: stats.mime });\n\t\t\t\t\t\tconst url = URL.createObjectURL(fileBlob);\n\t\t\t\t\t\tlk.href = url;\n\t\t\t\t\t\tlk.click();\n\t\t\t\t\t\tURL.revokeObjectURL(url);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfetch(`${window.location.origin}/fs/${path}`)\n\t\t\t\t\t\t\t.then(response => response.blob())\n\t\t\t\t\t\t\t.then(blob => {\n\t\t\t\t\t\t\t\tconst extension = path.split(\".\").pop().toLowerCase();\n\t\t\t\t\t\t\t\tlet mimeType;\n\t\t\t\t\t\t\t\tswitch (extension) {\n\t\t\t\t\t\t\t\t\tcase \"txt\":\n\t\t\t\t\t\t\t\t\t\tmimeType = \"text/plain\";\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"html\":\n\t\t\t\t\t\t\t\t\t\tmimeType = \"text/html\";\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"jpg\":\n\t\t\t\t\t\t\t\t\tcase \"jpeg\":\n\t\t\t\t\t\t\t\t\t\tmimeType = \"image/jpeg\";\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"png\":\n\t\t\t\t\t\t\t\t\t\tmimeType = \"image/png\";\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"mp4\":\n\t\t\t\t\t\t\t\t\t\tmimeType = \"video/mp4\";\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"mp3\":\n\t\t\t\t\t\t\t\t\t\tmimeType = \"audio/mp3\";\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tmimeType = \"application/octet-stream\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst fileBlob = new Blob([blob], { type: mimeType });\n\t\t\t\t\t\t\t\tconst url = URL.createObjectURL(fileBlob);\n\t\t\t\t\t\t\t\tlk.href = url;\n\t\t\t\t\t\t\t\tlk.click();\n\t\t\t\t\t\t\t\tURL.revokeObjectURL(url);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.catch(error => {\n\t\t\t\t\t\t\t\tconsole.error(error);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Properties\",\n\t\t\t\tclick: () => {\n\t\t\t\t\tgetItemDetails(e.target.getAttribute(\"path\"));\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t} else if (e.target.getAttribute(\"type\") === \"folder\") {\n\t\toptions = [\n\t\t\t{\n\t\t\t\ttext: \"Open\",\n\t\t\t\tclick: () => {\n\t\t\t\t\topenPath(e.target.getAttribute(\"path\"));\n\t\t\t\t},\n\t\t\t},\n\t\t\te.target.getAttribute(\"path\") === \"/system/trash\" || isTrash\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\ttext: \"New File\",\n\t\t\t\t\t\tclick: () => {},\n\t\t\t\t\t},\n\t\t\te.target.getAttribute(\"path\") === \"/system/trash\" || isTrash\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\ttext: \"New Folder\",\n\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\ttitle: \"Enter a name for the new folder\",\n\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\tonOk: async response => {\n\t\t\t\t\t\t\t\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\t\t\t\t\t\t\t\tconst createUniqueFolder = async (path, folderName, number = null) => {\n\t\t\t\t\t\t\t\t\t\tconst folderPath = `${path}/${folderName}${number !== null ? ` (${number})` : \"\"}`;\n\t\t\t\t\t\t\t\t\t\tconst exists = await window.parent.tb.fs.promises.exists(folderPath);\n\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\treturn createUniqueFolder(path, folderName, number !== null ? number + 1 : 2);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(folderPath);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\tawait createUniqueFolder(path, response);\n\t\t\t\t\t\t\t\t\topenPath(path);\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t/* To be finished soon\n            e.target.getAttribute(\"path\") === \"/system/trash\" || isTrash ? null : {\n                text: \"ZIP Folder\",\n                click: async () => {\n                    self.t = e\n                    let zip = {};\n                    async function addzip(inp, basePath = '') {\n                        const files = await window.parent.tb.fs.promises.readdir(inp);\n                        for (const file of files) {\n                            const fullPath = `${inp}/${file}`;\n                            const stats = await window.parent.tb.fs.promises.stat(fullPath);\n                            const zipPath = `${basePath}${file}`;\n                            if (stats.isDirectory()) {\n                                await addzip(fullPath, `${zipPath}/`);\n                            } else {\n                                const fileData = await window.parent.tb.fs.promises.readFile(fullPath);\n                                zip[zipPath] = new Uint8Array(fileData);\n                            }\n                        }\n                    }\n                    await addzip(e.target.getAttribute(\"path\"));\n                    await tb.dialog.Select({\n                        title: \"Where do you want to save the ZIP?\",\n                        options: [{\n                            text: \"File System\",\n                            value: \"fs\"\n                        }, {\n                            text: \"Computer\",\n                            value: \"pc\"\n                        }],\n                        onOk: async (perm) => {\n                            const zipped = window.parent.tb.fflate.zipSync(zip);\n                            if (perm === \"fs\") {\n                                await tb.dialog.SaveFile({\n                                    title: \"Enter a name for the ZIP file\",\n                                    defualtDir: `/home/${window.parent.sessionStorage.getItem(\"currAcc\")}/`,\n                                    filename: `${e.target.getAttribute(\"name\")}.zip`,\n                                    onOk: async (value) => {\n                                        const zipBlob = new Blob([zipped.buffer], { type: 'application/zip' });\n                                        const ab = await zipBlob.arrayBuffer();\n                                        await window.parent.tb.fs.promises.writeFile(value, new Uint8Array(ab));\n                                        window.parent.tb.notification.Toast({\n                                            message: `ZIP file created at ${value}`,\n                                            application: \"Files\",\n                                            iconSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n                                            time: 5000,\n                                        });\n                                    },\n                                });\n                            } else if (perm === \"pc\") {\n                                const zipBlob = new Blob([zipped.buffer], { type: 'application/zip' });\n                                const url = URL.createObjectURL(zipBlob);\n                                const link = document.createElement('a');\n                                link.href = url;\n                                link.download = `${e.target.getAttribute(\"name\")}.zip`;\n                                link.click();\n                                setTimeout(() => URL.revokeObjectURL(url), 1000);\n                            }\n                        }\n                    });\n                }\n            },*/\n\t\t\te.target.getAttribute(\"path\") === \"/system/trash\" || isTrash || e.target.getAttribute(\"system-folder\") === \"true\"\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\ttext: \"Rename\",\n\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\tlet path = e.target.getAttribute(\"path\");\n\t\t\t\t\t\t\ttb.dialog.Message({\n\t\t\t\t\t\t\t\ttitle: `Enter a new name for folder ${e.target.querySelector(\".title\").textContent}`,\n\t\t\t\t\t\t\t\tdefaultValue: e.target.getAttribute(\"name\"),\n\t\t\t\t\t\t\t\tonOk: async newName => {\n\t\t\t\t\t\t\t\t\tif (newName !== null && newName !== \"\" && newName !== e.target.querySelector(\".title\").textContent) {\n\t\t\t\t\t\t\t\t\t\tconst rename = async (path, fileName) => {\n\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.rename(path, `${e.target.getAttribute(\"parent-path\")}/${fileName}`);\n\t\t\t\t\t\t\t\t\t\t\t\tconst type = e.target.classList.contains(\"file-item\") ? \"file\" : \"folder\";\n\t\t\t\t\t\t\t\t\t\t\t\tdocument.querySelector(`[path=\"${path}\"]`).remove();\n\t\t\t\t\t\t\t\t\t\t\t\tconst item = document.createElement(\"div\");\n\t\t\t\t\t\t\t\t\t\t\t\titem.classList.add(`${type}-item`, \"path-item\");\n\t\t\t\t\t\t\t\t\t\t\t\tconst icon = document.createElement(\"div\");\n\t\t\t\t\t\t\t\t\t\t\t\ticon.classList.add(\"icon\");\n\t\t\t\t\t\t\t\t\t\t\t\tswitch (type) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tcase \"file\":\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ticon.innerHTML = `\n                                                    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                                                        <path d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625z\" />\n                                                        <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n                                                    </svg>\n                                                `;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t\tcase \"folder\":\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ticon.innerHTML = `\n                                                    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                                                        <path d=\"M19.5 21a3 3 0 003-3v-4.5a3 3 0 00-3-3h-15a3 3 0 00-3 3V18a3 3 0 003 3h15zM1.5 10.146V6a3 3 0 013-3h5.379a2.25 2.25 0 011.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 013 3v1.146A4.483 4.483 0 0019.5 9h-15a4.483 4.483 0 00-3 1.146z\" />\n                                                    </svg>\n                                                `;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titem.addEventListener(\"dblclick\", e => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\topenPath(e.target.getAttribute(\"path\"));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\titem.appendChild(icon);\n\t\t\t\t\t\t\t\t\t\t\t\tconst itemTitle = document.createElement(\"span\");\n\t\t\t\t\t\t\t\t\t\t\t\titemTitle.classList.add(\"title\");\n\t\t\t\t\t\t\t\t\t\t\t\titemTitle.textContent = fileName;\n\t\t\t\t\t\t\t\t\t\t\t\titem.appendChild(itemTitle);\n\t\t\t\t\t\t\t\t\t\t\t\titem.setAttribute(\"path\", `${e.target.getAttribute(\"parent-path\")}/${fileName}`);\n\t\t\t\t\t\t\t\t\t\t\t\tlet pbs = path.split(\"/\");\n\t\t\t\t\t\t\t\t\t\t\t\tpbs.pop();\n\t\t\t\t\t\t\t\t\t\t\t\tpbs = pbs.join(\"/\");\n\t\t\t\t\t\t\t\t\t\t\t\titem.setAttribute(\"parent-path\", pbs);\n\t\t\t\t\t\t\t\t\t\t\t\tdocument.querySelector(\".exp\").appendChild(item);\n\t\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\t\tconsole.log(error);\n\t\t\t\t\t\t\t\t\t\t\t\ttb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `Enter a new name for ${fileName}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: fileName,\n\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: async ask => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (ask !== null && ask !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait rename(path, ask);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tawait rename(path, newName);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t// isTrash ? {\n\t\t\t//     text: \"Restore\",\n\t\t\t//     click: async () => {}\n\t\t\t// } : null,\n\t\t\te.target.getAttribute(\"path\") === \"/system/trash\" || e.target.getAttribute(\"system-folder\") === \"true\"\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\ttext: isTrash ? \"Delete\" : \"Move To Trash\",\n\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\tconst path = e.target.getAttribute(\"path\");\n\t\t\t\t\t\t\tif (document.querySelector(\".exp\").getAttribute(\"path\") === \"/system/trash\") {\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.readdir(path, async (err, files) => {\n\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (files.length > 0) {\n\t\t\t\t\t\t\t\t\t\tfor (const file of files) {\n\t\t\t\t\t\t\t\t\t\t\tconst filePath = `${path}/${file}`;\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.unlink(filePath);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.rmdir(path);\n\t\t\t\t\t\t\t\t\t\tdocument.querySelector(\".exp\").removeChild(e.target);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.rmdir(path);\n\t\t\t\t\t\t\t\t\t\tdocument.querySelector(\".exp\").removeChild(e.target);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.readdir(path, async (err, files) => {\n\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (files.length > 0) {\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(\"/system/trash/\" + path.split(\"/\").pop());\n\t\t\t\t\t\t\t\t\t\tfor (const file of files) {\n\t\t\t\t\t\t\t\t\t\t\tconst filePath = `${path}/${file}`;\n\t\t\t\t\t\t\t\t\t\t\tlet data = await window.parent.tb.fs.readFile(filePath, \"utf8\");\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/system/trash/${path.split(\"/\").pop()}/${file}`, data);\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.unlink(filePath);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.rmdir(path);\n\t\t\t\t\t\t\t\t\t\tdocument.querySelector(\".exp\").removeChild(e.target);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.rmdir(path);\n\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(\"/system/trash/\" + path.split(\"/\").pop());\n\t\t\t\t\t\t\t\t\t\tdocument.querySelector(\".exp\").removeChild(e.target);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t// e.target.getAttribute(\"path\") === \"/system/trash\" ? {\n\t\t\t//     text: \"Restore All\",\n\t\t\t//     click: () => {}\n\t\t\t// } : null,\n\t\t\te.target.getAttribute(\"path\") === \"/system/trash\"\n\t\t\t\t? {\n\t\t\t\t\t\ttext: \"Empty Trash\",\n\t\t\t\t\t\tclick: async () => emptyTrash(),\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t\t// {\n\t\t\t//     text: \"Properties\",\n\t\t\t//     click: () => {}\n\t\t\t// }\n\t\t];\n\t} else {\n\t\toptions = [\n\t\t\tisTrash\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\ttext: \"New File\",\n\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\t\ttitle: \"Enter a name for the new file\",\n\t\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\t\tonOk: async fileName => {\n\t\t\t\t\t\t\t\t\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\t\t\t\t\t\t\t\t\tconst createFile = async (path, fileName) => {\n\t\t\t\t\t\t\t\t\t\t\tif (e.target.getAttribute(\"path\").includes(\"http\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst { client, filePath } = await useDavClient(path);\n\t\t\t\t\t\t\t\t\t\t\t\tconst exists = await client.exists(`${filePath}/${fileName}`);\n\t\t\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst ask = await tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `This file already exists. Enter a new name for ${fileName}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (ask !== undefined && ask !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait createFile(path, ask);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\tclient.putFileContents(`${filePath}/${fileName}`, \"\");\n\t\t\t\t\t\t\t\t\t\t\t\t\tcreatePath(fileName, `${filePath}/${fileName}`, \"file\");\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.exists(`${path}/${fileName}`, async exists => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst ask = await tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `This file already exists. Enter a new name for ${fileName}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (ask !== undefined && ask !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait createFile(path, ask);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet sh = window.parent.tb.sh;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait sh.touch(`${path}/${fileName}`, \"\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcreatePath(fileName, `${path}/${fileName}`, \"file\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tawait createFile(path, fileName);\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconsole.error(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\tisTrash\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\ttext: \"New Folder\",\n\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\ttitle: \"Enter a name for the new folder\",\n\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\tonOk: async response => {\n\t\t\t\t\t\t\t\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\t\t\t\t\t\t\t\tif (path.includes(\"http\")) {\n\t\t\t\t\t\t\t\t\t\tconst { client, filePath } = await useDavClient(path);\n\t\t\t\t\t\t\t\t\t\tconst exists = await client.exists(`${filePath}/${response}`);\n\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\tconst ask = await tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: `This folder already exists. Enter a new name for ${response}`,\n\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: \"\",\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\tif (ask !== undefined && ask !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\tawait createFile(path, ask);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tclient.createDirectory(`${filePath}/${response}`);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tconst createUniqueFolder = async (path, folderName, number = null) => {\n\t\t\t\t\t\t\t\t\t\t\tconst folderPath = `${path}/${folderName}${number !== null ? ` (${number})` : \"\"}`;\n\t\t\t\t\t\t\t\t\t\t\tconst exists = await window.parent.tb.fs.promises.exists(folderPath);\n\t\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn createUniqueFolder(path, folderName, number !== null ? number + 1 : 2);\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(folderPath);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tawait createUniqueFolder(path, response);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcreatePath(response, `${path}/${response}`, \"folder\");\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\tisTrash\n\t\t\t\t? null\n\t\t\t\t: copied || cut\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttext: \"Paste\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`${document.querySelector(\".exp\").getAttribute(\"path\")}/${copied.name}`, await window.parent.tb.fs.promises.readFile(copied.path, \"utf8\"));\n\t\t\t\t\t\t\t\tif (cut) {\n\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.unlink(copied.path);\n\t\t\t\t\t\t\t\t\tcut = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcopied = null;\n\t\t\t\t\t\t\t\topenPath(`${document.querySelector(\".exp\").getAttribute(\"path\")}`);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: null,\n\t\t\tisTrash\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\ttext: \"Upload from Computer\",\n\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\tconst fauxput = document.createElement(\"input\");\n\t\t\t\t\t\t\tfauxput.type = \"file\";\n\t\t\t\t\t\t\tfauxput.multiple = true;\n\t\t\t\t\t\t\tfauxput.onchange = async e => {\n\t\t\t\t\t\t\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\t\t\t\t\t\t\tif (path.includes(\"http\")) {\n\t\t\t\t\t\t\t\t\tfor (const file of e.target.files) {\n\t\t\t\t\t\t\t\t\t\tconst content = await file.arrayBuffer();\n\t\t\t\t\t\t\t\t\t\tconst { client, filePath } = await useDavClient(path);\n\t\t\t\t\t\t\t\t\t\tconst exists = await client.exists(`${filePath}/${file.name}`);\n\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: `File \"${file.name}\" already exists`,\n\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: file.name,\n\t\t\t\t\t\t\t\t\t\t\t\tonOk: async newFileName => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (newFileName !== null && newFileName !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclient.putFileContents(`${filePath}/${newFileName}`, window.parent.tb.buffer.from(content));\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tclient.putFileContents(`${filePath}/${file.name}`, window.parent.tb.buffer.from(content));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tfor (const file of e.target.files) {\n\t\t\t\t\t\t\t\t\t\tconst content = await file.arrayBuffer();\n\t\t\t\t\t\t\t\t\t\tconst filePath = `${path}/${file.name}`;\n\t\t\t\t\t\t\t\t\t\tconst exists = await window.parent.tb.fs.promises.exists(filePath);\n\t\t\t\t\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: `File \"${file.name}\" already exists`,\n\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue: file.name,\n\t\t\t\t\t\t\t\t\t\t\t\tonOk: async newFileName => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (newFileName !== null && newFileName !== \"\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`${path}/${newFileName}`, window.parent.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(filePath, window.parent.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tfauxput.click();\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\tisTrash\n\t\t\t\t? null\n\t\t\t\t: !showHidden\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttext: \"Show hidden files\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\tconst config = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${user}/files/config.json`, \"utf8\"));\n\t\t\t\t\t\t\t\tconfig[\"show-hidden-files\"] = true;\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${user}/files/config.json`, JSON.stringify(config));\n\t\t\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\ttext: \"Hide hidden files\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\tconst config = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${user}/files/config.json`, \"utf8\"));\n\t\t\t\t\t\t\t\tconfig[\"show-hidden-files\"] = false;\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${user}/files/config.json`, JSON.stringify(config));\n\t\t\t\t\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t// isTrash ? null : {\n\t\t\t//     text: \"Paste\",\n\t\t\t//     click: () => {}\n\t\t\t// },\n\t\t\tisTrash\n\t\t\t\t? {\n\t\t\t\t\t\ttext: \"Restore All\",\n\t\t\t\t\t\tclick: () => {},\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t\tisTrash\n\t\t\t\t? {\n\t\t\t\t\t\ttext: \"Empty Trash\",\n\t\t\t\t\t\tclick: async () => emptyTrash(),\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t\t\t// {\n\t\t\t//     text: \"Properties\",\n\t\t\t//     click: () => {}\n\t\t\t// },\n\t\t];\n\t}\n\tfor (let option of options) {\n\t\tif (option === null) continue;\n\t\tconst optionEl = document.createElement(\"div\");\n\t\toptionEl.classList.add(\"context-menu-button\");\n\t\toptionEl.textContent = option.text;\n\t\toptionEl.addEventListener(\"click\", option.click);\n\t\tcontext.appendChild(optionEl);\n\t}\n\tdocument.body.appendChild(context);\n\tif (e.clientX + context.offsetWidth > window.innerWidth) {\n\t\tcontext.style.left = `${e.clientX - context.offsetWidth}px`;\n\t} else {\n\t\tcontext.style.left = `${e.clientX}px`;\n\t}\n\tif (e.clientY + context.offsetHeight > window.innerHeight) {\n\t\tcontext.style.top = `${e.clientY - context.offsetHeight}px`;\n\t} else {\n\t\tcontext.style.top = `${e.clientY}px`;\n\t}\n\twindow.addEventListener(\"click\", e => {\n\t\tif (e.button === 0) {\n\t\t\tif (!e.target.classList.contains(\"context-menu\")) {\n\t\t\t\tif (document.querySelector(\".context-menu\")) document.querySelector(\".context-menu\").remove();\n\t\t\t}\n\t\t}\n\t});\n};\nwindow.addEventListener(\"contextmenu\", cm);\nwindow.addEventListener(\"touchhold\", cm);\n\nlet showHidden = false;\n\nconst createPath = async (title, path, type) => {\n\tconst config = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${user}/files/config.json`, \"utf8\"));\n\tif (config[\"show-hidden-files\"] === false && title.startsWith(\".\")) return;\n\n\tshowHidden = config[\"show-hidden-files\"];\n\tlet item = document.createElement(\"div\");\n\titem.classList.add(\"path-item\");\n\titem.setAttribute(\"path\", path);\n\titem.setAttribute(\"name\", title);\n\titem.setAttribute(\"type\", type);\n\tlet pbs = path.split(\"/\");\n\tpbs.pop();\n\tpbs = pbs.join(\"/\");\n\titem.setAttribute(\"parent-path\", pbs);\n\tconst icon = document.createElement(\"div\");\n\ticon.classList.add(\"icon\");\n\tlet itemTitle = document.createElement(\"span\");\n\titemTitle.classList.add(\"title\");\n\titemTitle.textContent = title;\n\tif (type === \"file\") {\n\t\titem.classList.add(\"file-item\");\n\t\tlet ext = path.split(\".\").pop();\n\t\tconst data = JSON.parse(await window.parent.window.parent.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\"));\n\t\tlet iconName = data[\"ext-to-name\"][ext];\n\t\tlet iconPath = data[\"name-to-path\"][iconName];\n\t\tlet unknown = data[\"name-to-path\"][\"Unknown\"];\n\t\tif (iconPath) {\n\t\t\tconst imgData = await window.parent.tb.fs.promises.readFile(iconPath, \"utf8\");\n\t\t\ticon.innerHTML = imgData;\n\t\t} else {\n\t\t\tconst imgData = await window.parent.tb.fs.promises.readFile(unknown, \"utf8\");\n\t\t\ticon.innerHTML = imgData;\n\t\t}\n\t\tif (copied && copied.name === item.getAttribute(\"name\").toLowerCase() && cut) {\n\t\t\titem.classList.add(\"opacity-50\");\n\t\t}\n\t\titem.ondblclick = async e => {\n\t\t\tlet ext = path.split(\".\");\n\t\t\tif (ext.length > 2) {\n\t\t\t\text = ext.slice(-2).join(\".\");\n\t\t\t} else {\n\t\t\t\text = ext.slice(-1).join(\".\");\n\t\t\t}\n\t\t\tconst data = JSON.parse(await window.parent.window.parent.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\t\t\tif (data[\"image\"].includes(ext)) {\n\t\t\t\tparent.window.tb.file.handler.openFile(item.getAttribute(\"path\"), \"image\");\n\t\t\t} else if (data[\"video\"].includes(ext)) {\n\t\t\t\tparent.window.tb.file.handler.openFile(item.getAttribute(\"path\"), \"video\");\n\t\t\t} else if (data[\"audio\"].includes(ext)) {\n\t\t\t\tparent.window.tb.file.handler.openFile(item.getAttribute(\"path\"), \"audio\");\n\t\t\t} else if (data[\"pdf\"].includes(ext)) {\n\t\t\t\tparent.window.tb.file.handler.openFile(item.getAttribute(\"path\"), \"pdf\");\n\t\t\t} else if (ext.toLowerCase() === \"tapp.zip\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst path = e.target.getAttribute(\"path\");\n\t\t\t\t\tawait window.parent.tb.dialog.Permissions({\n\t\t\t\t\t\ttitle: \"Install application\",\n\t\t\t\t\t\tmessage: `Would you like to install the application: ${path}?`,\n\t\t\t\t\t\tonOk: async () => {\n\t\t\t\t\t\t\tconst appPath = `/fs/${path}`.replace(\"//\", \"/\");\n\t\t\t\t\t\t\tconst appName =\n\t\t\t\t\t\t\t\tpath\n\t\t\t\t\t\t\t\t\t.replace(`/home/${window.parent.sessionStorage.getItem(\"currAcc\")}/`, \"\")\n\t\t\t\t\t\t\t\t\t.replace(/\\//g, \".\")\n\t\t\t\t\t\t\t\t\t.replace(/\\.zip$/, \"\") + \"\";\n\t\t\t\t\t\t\twindow.parent.tb.notification.Installing({\n\t\t\t\t\t\t\t\tmessage: `Installing ${appName}...`,\n\t\t\t\t\t\t\t\tapplication: \"Files\",\n\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\ttime: 500,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst zipFilePath = e.target.getAttribute(\"path\");\n\t\t\t\t\t\t\t\tconst targetDirectory = `/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}`;\n\t\t\t\t\t\t\t\tawait unzip(zipFilePath, targetDirectory, true);\n\t\t\t\t\t\t\t\tconsole.log(\"Done!\");\n\t\t\t\t\t\t\t\tconst appConf = await window.parent.tb.fs.promises.readFile(`/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}/.tbconfig`, \"utf8\");\n\t\t\t\t\t\t\t\tconst appData = JSON.parse(appConf);\n\t\t\t\t\t\t\t\tconsole.log(appData);\n\t\t\t\t\t\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\t\t\t\t\t\tname: appData.title,\n\t\t\t\t\t\t\t\t\ticon: appData.icon.includes(\"http\") ? appData.icon : `/fs/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}/${appData.icon}`,\n\t\t\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\t\t\ttypeof appData.wmArgs.title === \"object\"\n\t\t\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttext: appData.wmArgs.title.text,\n\t\t\t\t\t\t\t\t\t\t\t\t\tweight: appData.wmArgs.title.weight,\n\t\t\t\t\t\t\t\t\t\t\t\t\thtml: appData.wmArgs.title.html,\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t: appData.wmArgs.title,\n\t\t\t\t\t\t\t\t\tsrc: `/fs/apps/user/${window.parent.sessionStorage.getItem(\"currAcc\")}/${appName}/${appData.wmArgs.src}`,\n\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\twidth: appData.wmArgs.size.width,\n\t\t\t\t\t\t\t\t\t\theight: appData.wmArgs.size.height,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tsingle: appData.wmArgs.single,\n\t\t\t\t\t\t\t\t\tresizable: appData.wmArgs.resizable,\n\t\t\t\t\t\t\t\t\tcontrols: appData.wmArgs.controls,\n\t\t\t\t\t\t\t\t\tmessage: appData.wmArgs.message,\n\t\t\t\t\t\t\t\t\tsnapable: appData.wmArgs.snapable,\n\t\t\t\t\t\t\t\t\tuser: window.parent.sessionStorage.getItem(\"currAcc\"),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\tapps.push({\n\t\t\t\t\t\t\t\t\t\tname: appName,\n\t\t\t\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\t\t\t\tconfig: `/apps/user/${await window.parent.tb.user.username()}/${appName}/.tbconfig`,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tname: appName,\n\t\t\t\t\t\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\t\t\t\t\t\tconfig: `/apps/user/${await window.parent.tb.user.username()}/${appName}/.tbconfig`,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\t\t\t\t\tmessage: `${appName} has been installed!`,\n\t\t\t\t\t\t\t\t\tapplication: \"Files\",\n\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\ttime: 5000,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\tconsole.error(\"Error installing the app:\", e);\n\t\t\t\t\t\t\t\twindow.parent.tb.notification.Toast({\n\t\t\t\t\t\t\t\t\tmessage: `Failed to install ${appName}. Check the console for details.`,\n\t\t\t\t\t\t\t\t\tapplication: \"Files\",\n\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\ttime: 5000,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\twindow.parent.tb.dialog.Alert({\n\t\t\t\t\t\ttitle: \"Unexpected Error\",\n\t\t\t\t\t\tmessage: `❌ An Unexpected error occurred when trying to install the app: ${path} Error: ${e}`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (data[\"extractables\"].includes(ext) || ext.toLowerCase() === \"app.zip\" || ext.toLowerCase() === \"lib.zip\") {\n\t\t\t\tconst zipFilePath = e.target.getAttribute(\"path\");\n\t\t\t\tconst path = item.getAttribute(\"path\").replace(\".zip\", \"\");\n\t\t\t\tconst targetDirectory = `${path}`;\n\t\t\t\tawait unzip(zipFilePath, targetDirectory);\n\t\t\t\topenPath(document.querySelector(\".nav-input.dir\").value);\n\t\t\t} else if (data[\"text\"].includes(ext)) {\n\t\t\t\tparent.window.tb.file.handler.openFile(item.getAttribute(\"path\"), \"text\");\n\t\t\t} else {\n\t\t\t\tconst path = item.getAttribute(\"path\");\n\t\t\t\tconst name = item.getAttribute(\"name\");\n\t\t\t\tconst parts = name.split(\".\");\n\t\t\t\tconst extKey = parts.length > 2 ? parts.slice(-2).join(\".\").toLowerCase() : parts.slice(-1).join(\".\").toLowerCase();\n\t\t\t\tconst allHandlers = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"fileAssociatedApps\"] || {};\n\t\t\t\tif (allHandlers[extKey]) {\n\t\t\t\t\tparent.window.tb.file.handler.openFile(path, extKey);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet handlers = Object.entries(allHandlers).filter(([type, app]) => {\n\t\t\t\t\treturn !(type === \"text\" && app === \"text-editor\") && !(type === \"image\" && app === \"media-viewer\") && !(type === \"video\" && app === \"media-viewer\") && !(type === \"audio\" && app === \"media-viewer\");\n\t\t\t\t});\n\t\t\t\tlet hands = [];\n\t\t\t\tfor (const [type, app] of handlers) {\n\t\t\t\t\thands.push({ text: app, value: type });\n\t\t\t\t}\n\t\t\t\tawait tb.dialog.Select({\n\t\t\t\t\ttitle: `Select a application to open: ${path.split(\"/\").pop()}`,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Text Editor\",\n\t\t\t\t\t\t\tvalue: \"text\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Media Viewer\",\n\t\t\t\t\t\t\tvalue: \"media\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Webview\",\n\t\t\t\t\t\t\tvalue: \"webview\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...hands,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Other\",\n\t\t\t\t\t\t\tvalue: \"other\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\tswitch (val) {\n\t\t\t\t\t\t\tcase \"text\":\n\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"text\");\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"media\":\n\t\t\t\t\t\t\t\tconst ext = name.split(\".\").pop();\n\t\t\t\t\t\t\t\tif (data[\"image\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"image\");\n\t\t\t\t\t\t\t\t} else if (data[\"video\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"video\");\n\t\t\t\t\t\t\t\t} else if (data[\"audio\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"audio\");\n\t\t\t\t\t\t\t\t} else if (data[\"pdf\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"pdf\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"webview\":\n\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"webpage\");\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"other\":\n\t\t\t\t\t\t\t\tparent.window.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\tfilter: \".tapp\",\n\t\t\t\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\t\t\t\tconst app = JSON.parse(await window.parent.tb.fs.promises.readFile(`${val}/.tbconfig`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\twindow.parent.tb.window.create({ ...app.wmArgs, message: { type: \"process\", path: item.item } });\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tif (hands.length === 0) {\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, \"text\");\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(path, val);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t} else if (type === \"folder\") {\n\t\tif (title.toLocaleLowerCase().endsWith(\".tapp\")) {\n\t\t\ttry {\n\t\t\t\tif (await window.parent.tb.vfs.whatFS(path).promises.exists(`${path}/.tbconfig`)) {\n\t\t\t\t\tconst appData = JSON.parse(await window.parent.tb.vfs.whatFS(path).promises.readFile(`${path}/.tbconfig`, \"utf8\"));\n\t\t\t\t\tconst iconPath = appData.icon.includes(\"http\") ? appData.icon : `${path}/${appData.icon}`;\n\t\t\t\t\tif (iconPath.startsWith(\"http\")) {\n\t\t\t\t\t\ticon.innerHTML = `<img src=\"${iconPath}\" alt=\"${appData.title || \"\"}\" />`;\n\t\t\t\t\t} else if (iconPath.toLowerCase().endsWith(\".svg\")) {\n\t\t\t\t\t\tconst imgData = await window.parent.tb.vfs.whatFS(path).promises.readFile(iconPath, \"utf8\");\n\t\t\t\t\t\ticon.innerHTML = imgData;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst bin = await window.parent.tb.vfs.whatFS(path).promises.readFile(iconPath);\n\t\t\t\t\t\tconst b64 = window.parent.tb.buffer.from(bin).toString(\"base64\");\n\t\t\t\t\t\tlet mime = \"application/octet-stream\";\n\t\t\t\t\t\tif (iconPath.toLowerCase().endsWith(\".png\")) mime = \"image/png\";\n\t\t\t\t\t\telse if (iconPath.toLowerCase().endsWith(\".jpg\") || iconPath.toLowerCase().endsWith(\".jpeg\")) mime = \"image/jpeg\";\n\t\t\t\t\t\telse if (iconPath.toLowerCase().endsWith(\".webp\")) mime = \"image/webp\";\n\t\t\t\t\t\ticon.innerHTML = `<img src=\"data:${mime};base64,${b64}\" alt=\"${appData.title || \"\"}\" />`;\n\t\t\t\t\t}\n\t\t\t\t} else if (await window.parent.tb.vfs.whatFS(path).promises.exists(`${path}/index.json`)) {\n\t\t\t\t\tconst appData = JSON.parse(await window.parent.tb.vfs.whatFS(path).promises.readFile(`${path}/index.json`, \"utf8\"));\n\t\t\t\t\tconst iconPath = appData.config.icon.includes(\"http\") ? appData.config.icon : `${appData.config.icon.replace(\"/fs/\", \"/\")}`;\n\t\t\t\t\tconst imgData = await window.parent.tb.vfs.whatFS(path).promises.readFile(iconPath, \"utf8\");\n\t\t\t\t\ticon.innerHTML = imgData;\n\t\t\t\t} else {\n\t\t\t\t\tconst data = await window.parent.tb.vfs.whatFS(path).promises.readFile(`${path}/icon.svg`, \"utf8\");\n\t\t\t\t\ticon.innerHTML = data;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\ticon.innerHTML = `\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                        <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\" clip-rule=\"evenodd\" />\n                        <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n                    </svg>\n                `;\n\t\t\t}\n\t\t} else if (title.toLocaleLowerCase().endsWith(\".app\")) {\n\t\t\ttry {\n\t\t\t\tif (await window.parent.tb.vfs.whatFS(path).promises.exists(`${path}/manifest.json`)) {\n\t\t\t\t\tconst appData = JSON.parse(await window.parent.tb.vfs.whatFS(path).promises.readFile(`${path}/manifest.json`, \"utf8\"));\n\t\t\t\t\tconst iconPath = appData.icon.includes(\"http\") ? appData.icon : `${path}/${appData.icon}`;\n\t\t\t\t\tconst imgData = await window.parent.tb.vfs.whatFS(path).promises.readFile(iconPath, \"utf8\");\n\t\t\t\t\ticon.innerHTML = imgData;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\ticon.innerHTML = `\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                        <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\" clip-rule=\"evenodd\" />\n                        <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n                    </svg>\n                `;\n\t\t\t}\n\t\t} else {\n\t\t\tswitch (title.toLowerCase()) {\n\t\t\t\tcase \"desktop\":\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                            <path fill-rule=\"evenodd\" d=\"M2.25 5.25a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3V15a3 3 0 0 1-3 3h-3v.257c0 .597.237 1.17.659 1.591l.621.622a.75.75 0 0 1-.53 1.28h-9a.75.75 0 0 1-.53-1.28l.621-.622a2.25 2.25 0 0 0 .659-1.59V18h-3a3 3 0 0 1-3-3V5.25Zm1.5 0v7.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5v-7.5a1.5 1.5 0 0 0-1.5-1.5H5.25a1.5 1.5 0 0 0-1.5 1.5Z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"documents\":\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                            <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\" clip-rule=\"evenodd\" />\n                            <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n                        </svg>\n                    `;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"images\":\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"videos\":\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M1.5 5.625c0-1.036.84-1.875 1.875-1.875h17.25c1.035 0 1.875.84 1.875 1.875v12.75c0 1.035-.84 1.875-1.875 1.875H3.375A1.875 1.875 0 011.5 18.375V5.625zm1.5 0v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5A.375.375 0 003 5.625zm16.125-.375a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5A.375.375 0 0021 7.125v-1.5a.375.375 0 00-.375-.375h-1.5zM21 9.375A.375.375 0 0020.625 9h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zm0 3.75a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zm0 3.75a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 00.375-.375v-1.5zM4.875 18.75a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375h1.5zM3.375 15h1.5a.375.375 0 00.375-.375v-1.5a.375.375 0 00-.375-.375h-1.5a.375.375 0 00-.375.375v1.5c0 .207.168.375.375.375zm0-3.75h1.5a.375.375 0 00.375-.375v-1.5A.375.375 0 004.875 9h-1.5A.375.375 0 003 9.375v1.5c0 .207.168.375.375.375zm4.125 0a.75.75 0 000 1.5h9a.75.75 0 000-1.5h-9z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"music\":\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M19.952 1.651a.75.75 0 01.298.599V16.303a3 3 0 01-2.176 2.884l-1.32.377a2.553 2.553 0 11-1.403-4.909l2.311-.66a1.5 1.5 0 001.088-1.442V6.994l-9 2.572v9.737a3 3 0 01-2.176 2.884l-1.32.377a2.553 2.553 0 11-1.402-4.909l2.31-.66a1.5 1.5 0 001.088-1.442V9.017 5.25a.75.75 0 01.544-.721l10.5-3a.75.75 0 01.658.122z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"trash\":\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path fill-rule=\"evenodd\" d=\"M16.5 4.478v.227a48.816 48.816 0 013.878.512.75.75 0 11-.256 1.478l-.209-.035-1.005 13.07a3 3 0 01-2.991 2.77H8.084a3 3 0 01-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 01-.256-1.478A48.567 48.567 0 017.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 013.369 0c1.603.051 2.815 1.387 2.815 2.951zm-6.136-1.452a51.196 51.196 0 013.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 00-6 0v-.113c0-.794.609-1.428 1.364-1.452zm-.355 5.945a.75.75 0 10-1.5.058l.347 9a.75.75 0 101.499-.058l-.346-9zm5.48.058a.75.75 0 10-1.498-.058l-.347 9a.75.75 0 001.5.058l.345-9z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    `;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path d=\"M19.5 21a3 3 0 003-3v-4.5a3 3 0 00-3-3h-15a3 3 0 00-3 3V18a3 3 0 003 3h15zM1.5 10.146V6a3 3 0 013-3h5.379a2.25 2.25 0 011.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 013 3v1.146A4.483 4.483 0 0019.5 9h-15a4.483 4.483 0 00-3 1.146z\" />\n                        </svg>\n                    `;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tswitch (pbs) {\n\t\t\tcase \"/home\":\n\t\t\t\tswitch (title.toLowerCase()) {\n\t\t\t\t\tcase \"documents\":\n\t\t\t\t\t\ttitle = \"Documents\";\n\t\t\t\t\t\titem.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"images\":\n\t\t\t\t\t\ttitle = \"Images\";\n\t\t\t\t\t\titem.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"videos\":\n\t\t\t\t\t\ttitle = \"Videos\";\n\t\t\t\t\t\titem.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"music\":\n\t\t\t\t\t\ttitle = \"Music\";\n\t\t\t\t\t\titem.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"trash\":\n\t\t\t\t\t\ttitle = \"Trash\";\n\t\t\t\t\t\titem.setAttribute(\"system-folder\", \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\titem.addEventListener(\"dblclick\", e => {\n\t\t\topenPath(path);\n\t\t\tconst dirInput = document.querySelector(\".nav-input.dir\");\n\t\t\tdirInput.value = path;\n\t\t});\n\t}\n\titem.appendChild(icon);\n\titem.appendChild(itemTitle);\n\tdocument.querySelector(\".exp\").appendChild(item);\n};\n\nconst openPath = async (path, override = false) => {\n\tif (path === \"/system/trash\") {\n\t\tif (parent.document.querySelector(`[control-id=\"files-et\"]`)) {\n\t\t\tparent.document.querySelector(`[control-id=\"files-et\"]`).classList.remove(\"hidden\");\n\t\t}\n\t} else if (path === \"cmd\") {\n\t\tconst path = document.querySelector(\".exp\").getAttribute(\"path\");\n\t\tlet message = JSON.stringify({\n\t\t\ttype: \"open-path\",\n\t\t\tpath: path,\n\t\t});\n\t\tdocument.querySelector(\".nav-input.dir\").value = path;\n\t\tparent.window.tb.window.create({\n\t\t\ttitle: \"Terminal\",\n\t\t\ticon: \"/fs/apps/system/terminal.tapp/icon.svg\",\n\t\t\tsrc: \"/fs/apps/system/terminal.tapp/index.html\",\n\t\t\tsize: {\n\t\t\t\twidth: 438,\n\t\t\t\theight: 326,\n\t\t\t},\n\t\t\tsingle: true,\n\t\t\tmessage: message,\n\t\t});\n\t\treturn;\n\t} else {\n\t\tif (parent.document.querySelector(`[control-id=\"files-et\"]`)) {\n\t\t\tparent.document.querySelector(`[control-id=\"files-et\"]`).classList.add(\"hidden\");\n\t\t}\n\t}\n\tif (path === \"storage devices\") {\n\t\tshowStorageDevices();\n\t\treturn;\n\t}\n\tif (path === \"local storage\") {\n\t\tshowLS();\n\t\tconst modal = document.querySelector(\".drive-modal\");\n\t\tmodal.style.display = \"flex\";\n\t\tmodal.innerHTML = `\n\t\t\t<svg style=\"width: 22px; height: fit-content;\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                <path d=\"M4.08 5.227A3 3 0 016.979 3H17.02a3 3 0 012.9 2.227l2.113 7.926A5.228 5.228 0 0018.75 12H5.25a5.228 5.228 0 00-3.284 1.153L4.08 5.227z\"></path>\n                <path fill-rule=\"evenodd\" d=\"M5.25 13.5a3.75 3.75 0 100 7.5h13.5a3.75 3.75 0 100-7.5H5.25zm10.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5zm3.75-.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0z\" clip-rule=\"evenodd\"></path>\n            </svg>\n\t\t\t<span>LFS</span>\n\t\t`;\n\t\treturn;\n\t}\n\tif (path.includes(\"mnt\")) {\n\t\twindow.parent.tb.vfs.setServer(path.split(\"/\")[2]);\n\t\tlet davConfig = window.parent.tb.vfs.currentServer;\n\t\tconsole.log(\"Loading webdav: \" + davConfig.url + path.split(\"/\")[2]);\n\t\tlet relPath = path.replace(`/mnt/${davConfig.name}/`, \"/\");\n\t\tconsole.log(relPath);\n\t\tconst exp = document.querySelector(\".exp\");\n\t\texp.innerHTML = \"\";\n\t\texp.setAttribute(\"path\", davConfig.url + relPath);\n\t\tconst dirInput = document.querySelector(\".nav-input.dir\");\n\t\tdirInput.value = path;\n\t\texp.innerHTML = `<div style=\"padding:1em;\">Loading WebDAV...</div>`;\n\t\tconst modal = document.querySelector(\".drive-modal\");\n\t\ttry {\n\t\t\tconst client = window.parent.tb.vfs.currentServer.connection.client;\n\t\t\tconst contents = await client.getDirectoryContents(relPath);\n\t\t\texp.innerHTML = \"\";\n\t\t\tdocument.getElementById(`f-${davConfig.name.toLocaleLowerCase()}`).innerHTML = `\n                <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                    <path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\"/>\n                    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"/>\n                    <circle cx=\"18\" cy=\"17.25\" r=\"3\" fill=\"#5DD881\"/>\n                </svg>\n                <span>${davConfig.name}</span>\n            `;\n\t\t\tmodal.style.display = \"flex\";\n\t\t\tmodal.innerHTML = `\n\t\t\t\t<svg style=\"width: 22px; height: fit-content;\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                    <path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\"/>\n                    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"/>\n                    <circle cx=\"18\" cy=\"17.25\" r=\"3\" fill=\"#5DD881\"/>\n                </svg>\n\t\t\t\t<span>WebDav</span>\n\t\t\t`;\n\t\t\tfor (const item of contents) {\n\t\t\t\tconst name = item.basename;\n\t\t\t\tconst itemPath = `${davConfig.url}${relPath}/${name}`;\n\t\t\t\tif (item.type === \"directory\" && item.filename === path.replace(`/mnt/${davConfig.name}/`, \"\")) continue;\n\t\t\t\tconst type = item.type === \"directory\" ? \"folder\" : \"file\";\n\t\t\t\tconst el = document.createElement(\"div\");\n\t\t\t\tel.classList.add(\"path-item\", type === \"folder\" ? \"folder-item\" : \"file-item\");\n\t\t\t\tel.setAttribute(\"path\", itemPath);\n\t\t\t\tel.setAttribute(\"name\", name);\n\t\t\t\tel.setAttribute(\"type\", type);\n\t\t\t\tconst icon = document.createElement(\"div\");\n\t\t\t\ticon.classList.add(\"icon\");\n\t\t\t\tif (type === \"folder\") {\n\t\t\t\t\ticon.innerHTML = `\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                            <path d=\"M19.5 21a3 3 0 003-3v-4.5a3 3 0 00-3-3h-15a3 3 0 00-3 3V18a3 3 0 003 3h15zM1.5 10.146V6a3 3 0 013-3h5.379a2.25 2.25 0 011.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 013 3v1.146A4.483 4.483 0 0019.5 9h-15a4.483 4.483 0 00-3 1.146z\" />\n                        </svg>\n                    `;\n\t\t\t\t} else {\n\t\t\t\t\tconst data = JSON.parse(await window.parent.window.parent.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\"));\n\t\t\t\t\tconst ext = itemPath.split(\".\").pop();\n\t\t\t\t\tconst iconName = data[\"ext-to-name\"][ext];\n\t\t\t\t\tlet iconPath = data[\"name-to-path\"][iconName];\n\t\t\t\t\tlet unknown = data[\"name-to-path\"][\"Unknown\"];\n\t\t\t\t\tif (iconPath) {\n\t\t\t\t\t\ticon.innerHTML = await window.parent.window.parent.tb.fs.promises.readFile(iconPath, \"utf8\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\ticon.innerHTML = await window.parent.window.parent.tb.fs.promises.readFile(unknown, \"utf8\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tel.appendChild(icon);\n\t\t\t\tconst itemTitle = document.createElement(\"span\");\n\t\t\t\titemTitle.classList.add(\"title\");\n\t\t\t\titemTitle.textContent = name;\n\t\t\t\tel.appendChild(itemTitle);\n\t\t\t\tif (type === \"folder\") {\n\t\t\t\t\tel.addEventListener(\"dblclick\", () => openPath(itemPath.replace(davConfig.url, `/mnt/${davConfig.name}`)));\n\t\t\t\t} else {\n\t\t\t\t\tel.addEventListener(\"dblclick\", async () => {\n\t\t\t\t\t\tlet handlers = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"fileAssociatedApps\"];\n\t\t\t\t\t\thandlers = Object.entries(handlers).filter(([type, app]) => {\n\t\t\t\t\t\t\treturn !(type === \"text\" && app === \"text-editor\") && !(type === \"image\" && app === \"media-viewer\") && !(type === \"video\" && app === \"media-viewer\") && !(type === \"audio\" && app === \"media-viewer\");\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlet hands = [];\n\t\t\t\t\t\tfor (const [type, app] of handlers) {\n\t\t\t\t\t\t\thands.push({ text: app, value: type });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst data = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\t\t\t\t\t\tawait tb.dialog.Select({\n\t\t\t\t\t\t\ttitle: `Select a application to open: ${itemPath.split(\"/\").pop()}`,\n\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Text Editor\",\n\t\t\t\t\t\t\t\t\tvalue: \"text\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Media Viewer\",\n\t\t\t\t\t\t\t\t\tvalue: \"media\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t...hands,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Other\",\n\t\t\t\t\t\t\t\t\tvalue: \"other\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\t\tswitch (val) {\n\t\t\t\t\t\t\t\t\tcase \"text\":\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, \"text\");\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"media\":\n\t\t\t\t\t\t\t\t\t\tconst ext = itemPath.split(\".\").pop();\n\t\t\t\t\t\t\t\t\t\tif (data[\"image\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, \"image\");\n\t\t\t\t\t\t\t\t\t\t} else if (data[\"video\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, \"video\");\n\t\t\t\t\t\t\t\t\t\t} else if (data[\"audio\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, \"audio\");\n\t\t\t\t\t\t\t\t\t\t} else if (data[\"pdf\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, \"pdf\");\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"webview\":\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, \"webpage\");\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase \"other\":\n\t\t\t\t\t\t\t\t\t\tparent.window.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\t\tfilter: \".tapp\",\n\t\t\t\t\t\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst app = JSON.parse(await window.parent.tb.fs.promises.readFile(`${val}/.tbconfig`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\t\twindow.parent.tb.window.create({ ...app.wmArgs, message: { type: \"process\", path: item.item } });\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tif (hands.length === 0) {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, \"text\");\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(itemPath, val);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\texp.appendChild(el);\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t\texp.innerHTML = `<h1 style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; display: flex; align-items: center; justify-content: center; height: 100%;\">Failed to load WebDAV: ${e}</h1>`;\n\t\t\tdocument.getElementById(`f-${davConfig.name.toLocaleLowerCase()}`).innerHTML = `\n                <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                    <path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\"/>\n                    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"/>\n                    <circle cx=\"18\" cy=\"17.25\" r=\"3\" fill=\"#D8645D\"/>\n                </svg>\n                <span>${davConfig.name}</span>\n            `;\n\t\t\tmodal.style.display = \"flex\";\n\t\t\tmodal.innerHTML = `\n\t\t\t\t<svg style=\"width: 22px; height: fit-content;\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                    <path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\"/>\n                    <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"/>\n                    <circle cx=\"18\" cy=\"17.25\" r=\"3\" fill=\"#D8645D\"/>\n                </svg>\n\t\t\t\t<span>WebDav</span>\n\t\t\t`;\n\t\t}\n\t\tconst search = document.querySelector(\".nav-input.search\");\n\t\tsearch.setAttribute(\"placeholder\", `Search ${path.split(\"/\").pop()}`);\n\t\treturn;\n\t} else {\n\t\tdocument.querySelector(\".drive-modal\").style.display = \"none\";\n\t}\n\tif (path.split(\"/\").pop() === \"\") {\n\t\tpath = path.substring(0, path.length - 1);\n\t}\n\tawait window.parent.tb.fs.exists(path, async exists => {\n\t\tif (!exists) {\n\t\t\tconsole.error(\"Path does not exist\");\n\t\t\treturn;\n\t\t}\n\t});\n\tconst dirInput = document.querySelector(\".nav-input.dir\");\n\tdirInput.value = path;\n\tconst exp = document.querySelector(\".exp\");\n\texp.innerHTML = \"\";\n\texp.setAttribute(\"path\", path);\n\tif (path.toLowerCase().endsWith(\".app\") && override !== true) {\n\t\ttb.dialog.Select({\n\t\t\ttitle: `Sideload App`,\n\t\t\tmessage: `Do you want to sideload the anura app found at ${path}?`,\n\t\t\toptions: [\n\t\t\t\t{ text: \"Yes\", value: \"yes\" },\n\t\t\t\t{ text: \"View Source\", value: \"source\" },\n\t\t\t\t{ text: \"No\", value: \"no\" },\n\t\t\t],\n\t\t\tonOk: async val => {\n\t\t\t\tif (val === \"yes\") {\n\t\t\t\t\tsideloadApp(path);\n\t\t\t\t} else if (val === \"source\") {\n\t\t\t\t\topenPath(path, true);\n\t\t\t\t} else {\n\t\t\t\t\topenPath(\"/home/\" + user);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tconst sideloadApp = async path => {\n\t\t\ttry {\n\t\t\t\tconst anura = window.parent.anura;\n\t\t\t\tconst appPath = `/fs${path}`.replace(\"//\", \"/\");\n\t\t\t\tawait anura.registerExternalApp(appPath);\n\t\t\t} catch (e) {\n\t\t\t\twindow.parent.tb.dialog.Alert({\n\t\t\t\t\ttitle: \"Unexpected Error\",\n\t\t\t\t\tmessage: `❌ An Unexpected error occurred when trying to sideload the anura app: ${path} Error: ${e}`,\n\t\t\t\t});\n\t\t\t}\n\t\t\topenPath(document.getElementById(\".nav-input\"));\n\t\t};\n\t} else {\n\t\twindow.parent.tb.fs.readdir(path, async (err, files) => {\n\t\t\tif (err) return console.error(err);\n\t\t\tfor (let file of files) {\n\t\t\t\tawait window.parent.tb.fs.stat(path + \"/\" + file, (err, stats) => {\n\t\t\t\t\tif (err) return console.error(err);\n\t\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\t\tcreatePath(file, path + \"/\" + file, \"folder\");\n\t\t\t\t\t} else if (stats.isFile()) {\n\t\t\t\t\t\tcreatePath(file, path + \"/\" + file, \"file\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\tconst search = document.querySelector(\".nav-input.search\");\n\tsearch.setAttribute(\"placeholder\", `Search ${path.split(\"/\").pop()}`);\n};\n// For use elsewhere like the app island\nself.openPath = openPath;\nself.emptyTrash = emptyTrash;\n\nasync function unzip(path, target, app) {\n\tconst runUnzip = async () => {\n\t\tconst response = await fetch(\"/fs/\" + path);\n\t\tconst zipFileContent = await response.arrayBuffer();\n\t\tif (!(await dirExists(target))) {\n\t\t\tawait window.parent.tb.fs.promises.mkdir(target, { recursive: true });\n\t\t}\n\t\tconst compressedFiles = window.parent.tb.fflate.unzipSync(new Uint8Array(zipFileContent));\n\t\tfor (const [relativePath, content] of Object.entries(compressedFiles)) {\n\t\t\tconst fullPath = `${target}/${relativePath}`;\n\t\t\tconst pathParts = fullPath.split(\"/\");\n\t\t\tlet currentPath = \"\";\n\t\t\tfor (let i = 0; i < pathParts.length; i++) {\n\t\t\t\tcurrentPath += pathParts[i] + \"/\";\n\t\t\t\tif (i === pathParts.length - 1 && !relativePath.endsWith(\"/\")) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(`touch ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(currentPath.slice(0, -1), window.parent.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tconsole.log(`Cant make ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t}\n\t\t\t\t} else if (!(await dirExists(currentPath))) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(`mkdir ${currentPath}`);\n\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(currentPath);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tconsole.log(`Cant make ${currentPath}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (relativePath.endsWith(\"/\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconsole.log(`mkdir fp ${fullPath}`);\n\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(fullPath);\n\t\t\t\t} catch {\n\t\t\t\t\tconsole.log(`Cant make ${fullPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn \"Done!\";\n\t};\n\n\tif (!app) {\n\t\treturn window.parent.tb.notification.Installing(\n\t\t\t{\n\t\t\t\tmessage: \"Unzipping...\",\n\t\t\t\tapplication: \"Files\",\n\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t},\n\t\t\trunUnzip(),\n\t\t\t{\n\t\t\t\tmessage: `Finished unzipping ${path}`,\n\t\t\t\tapplication: \"Files\",\n\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\ttime: 3500,\n\t\t\t},\n\t\t\t{\n\t\t\t\tmessage: `Failed to unzip ${path}`,\n\t\t\t\tapplication: \"Files\",\n\t\t\t\ticonSrc: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\ttime: 2500,\n\t\t\t},\n\t\t);\n\t}\n\n\treturn runUnzip();\n}\n\nconst dirExists = async path => {\n\treturn new Promise(resolve => {\n\t\twindow.parent.tb.fs.stat(path, (err, stats) => {\n\t\t\tif (err) {\n\t\t\t\tif (err.code === \"ENOENT\") {\n\t\t\t\t\tresolve(false);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t\tresolve(false);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst exists = stats.type === \"DIRECTORY\";\n\t\t\t\tresolve(exists);\n\t\t\t}\n\t\t});\n\t});\n};\n\nconst clearSearchButton = document.querySelector(\".clear-search\");\nconst search = document.querySelector(\".nav-input.search\");\nsearch.addEventListener(\"input\", e => {\n\tconst exp = document.querySelector(\".exp\");\n\tlet path = exp.getAttribute(\"path\");\n\tif (search.value === \"\") {\n\t\topenPath(path);\n\t\tclearSearchButton.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t\treturn;\n\t}\n\texp.innerHTML = \"\";\n\texp.setAttribute(\"path\", path);\n\twindow.parent.tb.fs.readdir(path, async (err, files) => {\n\t\tif (err) {\n\t\t\tconsole.error(err);\n\t\t\treturn;\n\t\t}\n\t\tfor (let file of files) {\n\t\t\tawait window.parent.tb.fs.stat(path + \"/\" + file, (err, stats) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (clearSearchButton.classList.contains(\"opacity-0\")) {\n\t\t\t\t\tclearSearchButton.classList.remove(\"opacity-0\", \"pointer-events-none\");\n\t\t\t\t}\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tif (file.toLowerCase().includes(search.value.toLowerCase())) {\n\t\t\t\t\t\tcreatePath(file, path + \"/\" + file, \"folder\");\n\t\t\t\t\t}\n\t\t\t\t} else if (stats.isFile()) {\n\t\t\t\t\tif (file.toLowerCase().includes(search.value.toLowerCase())) {\n\t\t\t\t\t\tcreatePath(file, path + \"/\" + file, \"file\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t});\n});\n\nconst cfgload = async () => {\n\tconst search = document.querySelector(\".nav-input.search\");\n\tdocument.querySelector(\".sidebar\").innerHTML = \"\";\n\tif (search.value !== \"\") {\n\t\tsearch.value = \"\";\n\t}\n\tconst topbarheight = document.querySelector(\".topbar\").offsetHeight;\n\tdocument.querySelector(\"main\").style.setProperty(\"--topbar-height\", `${topbarheight}px`);\n\tlet config = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/config.json`, \"utf8\"));\n\tif (config[\"quick-center\"] === true) {\n\t\tawait createCollapsible(\"Quick Center\", \"quick-center\", config[\"open-collapsibles\"][\"quick-center\"], JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/quick-center.json`, \"utf8\"))[\"paths\"]);\n\t}\n\tif (config[\"drives\"]) {\n\t\tawait createCollapsible(\"Drives\", \"drives\", config[\"open-collapsibles\"][\"drives\"], config[\"drives\"]);\n\t}\n\tif (config[\"storage\"]) {\n\t\tconst storageDevices = document.createElement(\"div\");\n\t\tstorageDevices.classList.add(\"absolute-path-item\");\n\t\tconst icon = document.createElement(\"div\");\n\t\ticon.classList.add(\"icon\");\n\t\ticon.innerHTML = `\n            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n                <path d=\"M5.507 4.048A3 3 0 017.785 3h8.43a3 3 0 012.278 1.048l1.722 2.008A4.533 4.533 0 0019.5 6h-15c-.243 0-.482.02-.715.056l1.722-2.008z\" />\n                <path fill-rule=\"evenodd\" d=\"M1.5 10.5a3 3 0 013-3h15a3 3 0 110 6h-15a3 3 0 01-3-3zm15 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm2.25.75a.75.75 0 100-1.5.75.75 0 000 1.5zM4.5 15a3 3 0 100 6h15a3 3 0 100-6h-15zm11.25 3.75a.75.75 0 100-1.5.75.75 0 000 1.5zM19.5 18a.75.75 0 11-1.5 0 .75.75 0 011.5 0z\" clip-rule=\"evenodd\" />\n            </svg>\n        `;\n\t\tstorageDevices.appendChild(icon);\n\t\tconst itemTitle = document.createElement(\"span\");\n\t\titemTitle.classList.add(\"title\");\n\t\titemTitle.textContent = \"Storage Devices\";\n\t\tstorageDevices.appendChild(itemTitle);\n\t\tdocument.querySelector(\".sidebar\").appendChild(storageDevices);\n\t\tstorageDevices.addEventListener(\"click\", e => showStorageDevices());\n\t}\n\tconst sidebarwidth = document.querySelector(\".sidebar\").offsetWidth;\n\tdocument.querySelector(\"main\").style.setProperty(\"--sidebar-width\", `${sidebarwidth}px`);\n};\n\nwindow.addEventListener(\"load\", cfgload);\nwindow.addEventListener(\"updcfg\", cfgload);\n"
  },
  {
    "path": "public/apps/files.tapp/index.json",
    "content": "{\n\t\"name\": \"Files\",\n\t\"config\": {\n\t\t\"title\": \"Files\",\n\t\t\"icon\": \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/files.tapp/index.html\",\n\t\t\"size\": {\n\t\t\t\"width\": 600,\n\t\t\t\"height\": 500\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "public/apps/files.tapp/properties/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Properties</title>\n    <script src=\"/assets/libs/tailwind.min.js\"></script>\n    <style>\n        @font-face {\n            font-family: Inter;\n            src: url(\"/fonts/Inter.ttf\");\n        }\n\n        * {\n            font-family: Inter;\n        }\n    </style>\n</head>\n    <body class=\"text-white\">\n        <script src=\"index.js\"></script>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/files.tapp/properties/index.js",
    "content": "window.addEventListener(\"message\", e => {\n\tlet data = JSON.parse(e.data);\n\tlet file_name = data.details.name;\n\tlet tof = data.details.type;\n\tlet loc = data.path;\n\tlet size = data.details.size;\n\tif (size > 1000000000) {\n\t\tsize = `${(size / 1000000000).toFixed(2)} GB`;\n\t} else if (size > 1000000) {\n\t\tsize = `${(size / 1000000).toFixed(2)} MB`;\n\t} else if (size > 1000) {\n\t\tsize = `${(size / 1000).toFixed(2)} KB`;\n\t} else {\n\t\tsize = `${size} B`;\n\t}\n\tlet created = data.details.created;\n\tlet createdDate = new Date(created);\n\tcreated = `${createdDate.getFullYear()}-${createdDate.getMonth()}-${createdDate.getDate()}`;\n\tlet modified = data.details.modified;\n\tlet modifiedDate = new Date(modified);\n\tmodified = `${modifiedDate.getFullYear()}-${modifiedDate.getMonth()}-${modifiedDate.getDate()}`;\n\tlet accessed = data.details.accessed;\n\tlet accessedDate = new Date(accessed);\n\taccessed = `${accessedDate.getFullYear()}-${accessedDate.getMonth()}-${accessedDate.getDate()}`;\n\tdocument.body.innerHTML = `\n        <div class=\"flex flex-col h-full py-2 px-4\">\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">Name:</div>\n                <div class=\"text-base font-bold\">${file_name}</div>\n            </div>\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">Type of file:</div>\n                <div class=\"text-base font-bold\">${tof}</div>\n            </div>\n            ${\n\t\t\t\tdata.details.mime\n\t\t\t\t\t? `\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">MIME:</div>\n                <div class=\"text-base font-bold\">${data.details.mime}</div>\n            </div>\n            `\n\t\t\t\t\t: ``\n\t\t\t}\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">Location:</div>\n                <div class=\"text-base font-bold\">${loc}</div>\n            </div>\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">Size:</div>\n                <div class=\"text-base font-bold\">${size}</div>\n            </div>\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">Created:</div>\n                <div class=\"text-base font-bold\">${created}</div>\n            </div>\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">Modified:</div>\n                <div class=\"text-base font-bold\">${modified}</div>\n            </div>\n            <div class=\"flex gap-1 items-center\">\n                <div class=\"text-lg font-extrabold\">Accessed:</div>\n                <div class=\"text-base font-bold\">${accessed}</div>\n            </div>\n        </div>\n    `;\n});\n"
  },
  {
    "path": "public/apps/files.tapp/webdav.js",
    "content": "/*! For license information please see webdav.js.LICENSE.txt */\nvar t={0:()=>{},80:(t,e)=>{const n=\":A-Za-z_\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD\",r=\"[\"+n+\"][\"+n+\"\\\\-.\\\\d\\\\u00B7\\\\u0300-\\\\u036F\\\\u203F-\\\\u2040]*\",o=new RegExp(\"^\"+r+\"$\");e.isExist=function(t){return void 0!==t},e.isEmptyObject=function(t){return 0===Object.keys(t).length},e.merge=function(t,e,n){if(e){const r=Object.keys(e),o=r.length;for(let i=0;i<o;i++)t[r[i]]=\"strict\"===n?[e[r[i]]]:e[r[i]]}},e.getValue=function(t){return e.isExist(t)?t:\"\"},e.isName=function(t){return!(null==o.exec(t))},e.getAllMatches=function(t,e){const n=[];let r=e.exec(t);for(;r;){const o=[];o.startIndex=e.lastIndex-r[0].length;const i=r.length;for(let t=0;t<i;t++)o.push(r[t]);n.push(o),r=e.exec(t)}return n},e.nameRegexp=r},156:(t,e,n)=>{const{buildOptions:r}=n(785),o=n(385),{prettify:i}=n(978),s=n(993);t.exports=class{constructor(t){this.externalEntities={},this.options=r(t)}parse(t,e){if(\"string\"==typeof t);else{if(!t.toString)throw new Error(\"XML data is accepted in String or Bytes[] form.\");t=t.toString()}if(e){!0===e&&(e={});const n=s.validate(t,e);if(!0!==n)throw Error(`${n.err.msg}:${n.err.line}:${n.err.col}`)}const n=new o(this.options);n.addExternalEntities(this.externalEntities);const r=n.parseXml(t);return this.options.preserveOrder||void 0===r?r:i(r,this.options)}addEntity(t,e){if(-1!==e.indexOf(\"&\"))throw new Error(\"Entity value can't have '&'\");if(-1!==t.indexOf(\"&\")||-1!==t.indexOf(\";\"))throw new Error(\"An entity must be set without '&' and ';'. Eg. use '#xD' for '&#xD;'\");if(\"&\"===e)throw new Error(\"An entity with value '&' is not permitted\");this.externalEntities[t]=e}}},173:(t,e,n)=>{const r=n(993),o=n(156),i=n(179);t.exports={XMLParser:o,XMLValidator:r,XMLBuilder:i}},179:(t,e,n)=>{const r=n(829),o=n(940),i={attributeNamePrefix:\"@_\",attributesGroupName:!1,textNodeName:\"#text\",ignoreAttributes:!0,cdataPropName:!1,format:!1,indentBy:\"  \",suppressEmptyNode:!1,suppressUnpairedNode:!0,suppressBooleanAttributes:!0,tagValueProcessor:function(t,e){return e},attributeValueProcessor:function(t,e){return e},preserveOrder:!1,commentPropName:!1,unpairedTags:[],entities:[{regex:new RegExp(\"&\",\"g\"),val:\"&amp;\"},{regex:new RegExp(\">\",\"g\"),val:\"&gt;\"},{regex:new RegExp(\"<\",\"g\"),val:\"&lt;\"},{regex:new RegExp(\"'\",\"g\"),val:\"&apos;\"},{regex:new RegExp('\"',\"g\"),val:\"&quot;\"}],processEntities:!0,stopNodes:[],oneListGroup:!1};function s(t){this.options=Object.assign({},i,t),!0===this.options.ignoreAttributes||this.options.attributesGroupName?this.isAttribute=function(){return!1}:(this.ignoreAttributesFn=o(this.options.ignoreAttributes),this.attrPrefixLen=this.options.attributeNamePrefix.length,this.isAttribute=c),this.processTextOrObjNode=a,this.options.format?(this.indentate=u,this.tagEndChar=\">\\n\",this.newLine=\"\\n\"):(this.indentate=function(){return\"\"},this.tagEndChar=\">\",this.newLine=\"\")}function a(t,e,n,r){const o=this.j2x(t,n+1,r.concat(e));return void 0!==t[this.options.textNodeName]&&1===Object.keys(t).length?this.buildTextValNode(t[this.options.textNodeName],e,o.attrStr,n):this.buildObjectNode(o.val,e,o.attrStr,n)}function u(t){return this.options.indentBy.repeat(t)}function c(t){return!(!t.startsWith(this.options.attributeNamePrefix)||t===this.options.textNodeName)&&t.substr(this.attrPrefixLen)}s.prototype.build=function(t){return this.options.preserveOrder?r(t,this.options):(Array.isArray(t)&&this.options.arrayNodeName&&this.options.arrayNodeName.length>1&&(t={[this.options.arrayNodeName]:t}),this.j2x(t,0,[]).val)},s.prototype.j2x=function(t,e,n){let r=\"\",o=\"\";const i=n.join(\".\");for(let s in t)if(Object.prototype.hasOwnProperty.call(t,s))if(void 0===t[s])this.isAttribute(s)&&(o+=\"\");else if(null===t[s])this.isAttribute(s)||s===this.options.cdataPropName?o+=\"\":\"?\"===s[0]?o+=this.indentate(e)+\"<\"+s+\"?\"+this.tagEndChar:o+=this.indentate(e)+\"<\"+s+\"/\"+this.tagEndChar;else if(t[s]instanceof Date)o+=this.buildTextValNode(t[s],s,\"\",e);else if(\"object\"!=typeof t[s]){const n=this.isAttribute(s);if(n&&!this.ignoreAttributesFn(n,i))r+=this.buildAttrPairStr(n,\"\"+t[s]);else if(!n)if(s===this.options.textNodeName){let e=this.options.tagValueProcessor(s,\"\"+t[s]);o+=this.replaceEntitiesValue(e)}else o+=this.buildTextValNode(t[s],s,\"\",e)}else if(Array.isArray(t[s])){const r=t[s].length;let i=\"\",a=\"\";for(let u=0;u<r;u++){const r=t[s][u];if(void 0===r);else if(null===r)\"?\"===s[0]?o+=this.indentate(e)+\"<\"+s+\"?\"+this.tagEndChar:o+=this.indentate(e)+\"<\"+s+\"/\"+this.tagEndChar;else if(\"object\"==typeof r)if(this.options.oneListGroup){const t=this.j2x(r,e+1,n.concat(s));i+=t.val,this.options.attributesGroupName&&r.hasOwnProperty(this.options.attributesGroupName)&&(a+=t.attrStr)}else i+=this.processTextOrObjNode(r,s,e,n);else if(this.options.oneListGroup){let t=this.options.tagValueProcessor(s,r);t=this.replaceEntitiesValue(t),i+=t}else i+=this.buildTextValNode(r,s,\"\",e)}this.options.oneListGroup&&(i=this.buildObjectNode(i,s,a,e)),o+=i}else if(this.options.attributesGroupName&&s===this.options.attributesGroupName){const e=Object.keys(t[s]),n=e.length;for(let o=0;o<n;o++)r+=this.buildAttrPairStr(e[o],\"\"+t[s][e[o]])}else o+=this.processTextOrObjNode(t[s],s,e,n);return{attrStr:r,val:o}},s.prototype.buildAttrPairStr=function(t,e){return e=this.options.attributeValueProcessor(t,\"\"+e),e=this.replaceEntitiesValue(e),this.options.suppressBooleanAttributes&&\"true\"===e?\" \"+t:\" \"+t+'=\"'+e+'\"'},s.prototype.buildObjectNode=function(t,e,n,r){if(\"\"===t)return\"?\"===e[0]?this.indentate(r)+\"<\"+e+n+\"?\"+this.tagEndChar:this.indentate(r)+\"<\"+e+n+this.closeTag(e)+this.tagEndChar;{let o=\"</\"+e+this.tagEndChar,i=\"\";return\"?\"===e[0]&&(i=\"?\",o=\"\"),!n&&\"\"!==n||-1!==t.indexOf(\"<\")?!1!==this.options.commentPropName&&e===this.options.commentPropName&&0===i.length?this.indentate(r)+`\\x3c!--${t}--\\x3e`+this.newLine:this.indentate(r)+\"<\"+e+n+i+this.tagEndChar+t+this.indentate(r)+o:this.indentate(r)+\"<\"+e+n+i+\">\"+t+o}},s.prototype.closeTag=function(t){let e=\"\";return-1!==this.options.unpairedTags.indexOf(t)?this.options.suppressUnpairedNode||(e=\"/\"):e=this.options.suppressEmptyNode?\"/\":`></${t}`,e},s.prototype.buildTextValNode=function(t,e,n,r){if(!1!==this.options.cdataPropName&&e===this.options.cdataPropName)return this.indentate(r)+`<![CDATA[${t}]]>`+this.newLine;if(!1!==this.options.commentPropName&&e===this.options.commentPropName)return this.indentate(r)+`\\x3c!--${t}--\\x3e`+this.newLine;if(\"?\"===e[0])return this.indentate(r)+\"<\"+e+n+\"?\"+this.tagEndChar;{let o=this.options.tagValueProcessor(e,t);return o=this.replaceEntitiesValue(o),\"\"===o?this.indentate(r)+\"<\"+e+n+this.closeTag(e)+this.tagEndChar:this.indentate(r)+\"<\"+e+n+\">\"+o+\"</\"+e+this.tagEndChar}},s.prototype.replaceEntitiesValue=function(t){if(t&&t.length>0&&this.options.processEntities)for(let e=0;e<this.options.entities.length;e++){const n=this.options.entities[e];t=t.replace(n.regex,n.val)}return t},t.exports=s},180:t=>{function e(t){return e=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t},e(t)}function n(t){var e=\"function\"==typeof Map?new Map:void 0;return n=function(t){if(null===t||(n=t,-1===Function.toString.call(n).indexOf(\"[native code]\")))return t;var n;if(\"function\"!=typeof t)throw new TypeError(\"Super expression must either be null or a function\");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,s)}function s(){return r(t,arguments,i(this).constructor)}return s.prototype=Object.create(t.prototype,{constructor:{value:s,enumerable:!1,writable:!0,configurable:!0}}),o(s,t)},n(t)}function r(t,e,n){return r=function(){if(\"undefined\"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}()?Reflect.construct:function(t,e,n){var r=[null];r.push.apply(r,e);var i=new(Function.bind.apply(t,r));return n&&o(i,n.prototype),i},r.apply(null,arguments)}function o(t,e){return o=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t},o(t,e)}function i(t){return i=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)},i(t)}var s=function(t){function n(t){var r;return function(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}(this,n),(r=function(t,n){return!n||\"object\"!==e(n)&&\"function\"!=typeof n?function(t){if(void 0===t)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return t}(t):n}(this,i(n).call(this,t))).name=\"ObjectPrototypeMutationError\",r}return function(t,e){if(\"function\"!=typeof e&&null!==e)throw new TypeError(\"Super expression must either be null or a function\");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&o(t,e)}(n,t),n}(n(Error));function a(t,n){for(var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},o=n.split(\".\"),i=o.length,s=function(e){var n=o[e];if(!t)return{v:void 0};if(\"+\"===n){if(Array.isArray(t))return{v:t.map(function(n,i){var s=o.slice(e+1);return s.length>0?a(n,s.join(\".\"),r):r(t,i,o,e)})};var i=o.slice(0,e).join(\".\");throw new Error(\"Object at wildcard (\".concat(i,\") is not an array\"))}t=r(t,n,o,e)},u=0;u<i;u++){var c=s(u);if(\"object\"===e(c))return c.v}return t}function u(t,e){return t.length===e+1}t.exports={set:function(t,n,r){if(\"object\"!=e(t)||null===t)return t;if(void 0===n)return t;if(\"number\"==typeof n)return t[n]=r,t[n];try{return a(t,n,function(t,e,n,o){if(t===Reflect.getPrototypeOf({}))throw new s(\"Attempting to mutate Object.prototype\");if(!t[e]){var i=Number.isInteger(Number(n[o+1])),a=\"+\"===n[o+1];t[e]=i||a?[]:{}}return u(n,o)&&(t[e]=r),t[e]})}catch(e){if(e instanceof s)throw e;return t}},get:function(t,n){if(\"object\"!=e(t)||null===t)return t;if(void 0===n)return t;if(\"number\"==typeof n)return t[n];try{return a(t,n,function(t,e){return t[e]})}catch(e){return t}},has:function(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(\"object\"!=e(t)||null===t)return!1;if(void 0===n)return!1;if(\"number\"==typeof n)return n in t;try{var o=!1;return a(t,n,function(t,e,n,i){if(!u(n,i))return t&&t[e];o=r.own?t.hasOwnProperty(e):e in t}),o}catch(t){return!1}},hasOwn:function(t,e,n){return this.has(t,e,n||{own:!0})},isIn:function(t,n,r){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};if(\"object\"!=e(t)||null===t)return!1;if(void 0===n)return!1;try{var i=!1,s=!1;return a(t,n,function(t,n,o,a){return i=i||t===r||!!t&&t[n]===r,s=u(o,a)&&\"object\"===e(t)&&n in t,t&&t[n]}),o.validPath?i&&s:i}catch(t){return!1}},ObjectPrototypeMutationError:s}},214:t=>{var e,n;e=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\",n={rotl:function(t,e){return t<<e|t>>>32-e},rotr:function(t,e){return t<<32-e|t>>>e},endian:function(t){if(t.constructor==Number)return 16711935&n.rotl(t,8)|4278255360&n.rotl(t,24);for(var e=0;e<t.length;e++)t[e]=n.endian(t[e]);return t},randomBytes:function(t){for(var e=[];t>0;t--)e.push(Math.floor(256*Math.random()));return e},bytesToWords:function(t){for(var e=[],n=0,r=0;n<t.length;n++,r+=8)e[r>>>5]|=t[n]<<24-r%32;return e},wordsToBytes:function(t){for(var e=[],n=0;n<32*t.length;n+=8)e.push(t[n>>>5]>>>24-n%32&255);return e},bytesToHex:function(t){for(var e=[],n=0;n<t.length;n++)e.push((t[n]>>>4).toString(16)),e.push((15&t[n]).toString(16));return e.join(\"\")},hexToBytes:function(t){for(var e=[],n=0;n<t.length;n+=2)e.push(parseInt(t.substr(n,2),16));return e},bytesToBase64:function(t){for(var n=[],r=0;r<t.length;r+=3)for(var o=t[r]<<16|t[r+1]<<8|t[r+2],i=0;i<4;i++)8*r+6*i<=8*t.length?n.push(e.charAt(o>>>6*(3-i)&63)):n.push(\"=\");return n.join(\"\")},base64ToBytes:function(t){t=t.replace(/[^A-Z0-9+\\/]/gi,\"\");for(var n=[],r=0,o=0;r<t.length;o=++r%4)0!=o&&n.push((e.indexOf(t.charAt(r-1))&Math.pow(2,-2*o+8)-1)<<2*o|e.indexOf(t.charAt(r))>>>6-2*o);return n}},t.exports=n},221:(t,e,n)=>{!function(){var e=n(214),r=n(706).utf8,o=n(552),i=n(706).bin,s=function(t,n){t.constructor==String?t=n&&\"binary\"===n.encoding?i.stringToBytes(t):r.stringToBytes(t):o(t)?t=Array.prototype.slice.call(t,0):Array.isArray(t)||t.constructor===Uint8Array||(t=t.toString());for(var a=e.bytesToWords(t),u=8*t.length,c=1732584193,l=-271733879,h=-1732584194,p=271733878,f=0;f<a.length;f++)a[f]=16711935&(a[f]<<8|a[f]>>>24)|4278255360&(a[f]<<24|a[f]>>>8);a[u>>>5]|=128<<u%32,a[14+(u+64>>>9<<4)]=u;var g=s._ff,d=s._gg,m=s._hh,y=s._ii;for(f=0;f<a.length;f+=16){var v=c,b=l,w=h,x=p;c=g(c,l,h,p,a[f+0],7,-680876936),p=g(p,c,l,h,a[f+1],12,-389564586),h=g(h,p,c,l,a[f+2],17,606105819),l=g(l,h,p,c,a[f+3],22,-1044525330),c=g(c,l,h,p,a[f+4],7,-176418897),p=g(p,c,l,h,a[f+5],12,1200080426),h=g(h,p,c,l,a[f+6],17,-1473231341),l=g(l,h,p,c,a[f+7],22,-45705983),c=g(c,l,h,p,a[f+8],7,1770035416),p=g(p,c,l,h,a[f+9],12,-1958414417),h=g(h,p,c,l,a[f+10],17,-42063),l=g(l,h,p,c,a[f+11],22,-1990404162),c=g(c,l,h,p,a[f+12],7,1804603682),p=g(p,c,l,h,a[f+13],12,-40341101),h=g(h,p,c,l,a[f+14],17,-1502002290),c=d(c,l=g(l,h,p,c,a[f+15],22,1236535329),h,p,a[f+1],5,-165796510),p=d(p,c,l,h,a[f+6],9,-1069501632),h=d(h,p,c,l,a[f+11],14,643717713),l=d(l,h,p,c,a[f+0],20,-373897302),c=d(c,l,h,p,a[f+5],5,-701558691),p=d(p,c,l,h,a[f+10],9,38016083),h=d(h,p,c,l,a[f+15],14,-660478335),l=d(l,h,p,c,a[f+4],20,-405537848),c=d(c,l,h,p,a[f+9],5,568446438),p=d(p,c,l,h,a[f+14],9,-1019803690),h=d(h,p,c,l,a[f+3],14,-187363961),l=d(l,h,p,c,a[f+8],20,1163531501),c=d(c,l,h,p,a[f+13],5,-1444681467),p=d(p,c,l,h,a[f+2],9,-51403784),h=d(h,p,c,l,a[f+7],14,1735328473),c=m(c,l=d(l,h,p,c,a[f+12],20,-1926607734),h,p,a[f+5],4,-378558),p=m(p,c,l,h,a[f+8],11,-2022574463),h=m(h,p,c,l,a[f+11],16,1839030562),l=m(l,h,p,c,a[f+14],23,-35309556),c=m(c,l,h,p,a[f+1],4,-1530992060),p=m(p,c,l,h,a[f+4],11,1272893353),h=m(h,p,c,l,a[f+7],16,-155497632),l=m(l,h,p,c,a[f+10],23,-1094730640),c=m(c,l,h,p,a[f+13],4,681279174),p=m(p,c,l,h,a[f+0],11,-358537222),h=m(h,p,c,l,a[f+3],16,-722521979),l=m(l,h,p,c,a[f+6],23,76029189),c=m(c,l,h,p,a[f+9],4,-640364487),p=m(p,c,l,h,a[f+12],11,-421815835),h=m(h,p,c,l,a[f+15],16,530742520),c=y(c,l=m(l,h,p,c,a[f+2],23,-995338651),h,p,a[f+0],6,-198630844),p=y(p,c,l,h,a[f+7],10,1126891415),h=y(h,p,c,l,a[f+14],15,-1416354905),l=y(l,h,p,c,a[f+5],21,-57434055),c=y(c,l,h,p,a[f+12],6,1700485571),p=y(p,c,l,h,a[f+3],10,-1894986606),h=y(h,p,c,l,a[f+10],15,-1051523),l=y(l,h,p,c,a[f+1],21,-2054922799),c=y(c,l,h,p,a[f+8],6,1873313359),p=y(p,c,l,h,a[f+15],10,-30611744),h=y(h,p,c,l,a[f+6],15,-1560198380),l=y(l,h,p,c,a[f+13],21,1309151649),c=y(c,l,h,p,a[f+4],6,-145523070),p=y(p,c,l,h,a[f+11],10,-1120210379),h=y(h,p,c,l,a[f+2],15,718787259),l=y(l,h,p,c,a[f+9],21,-343485551),c=c+v>>>0,l=l+b>>>0,h=h+w>>>0,p=p+x>>>0}return e.endian([c,l,h,p])};s._ff=function(t,e,n,r,o,i,s){var a=t+(e&n|~e&r)+(o>>>0)+s;return(a<<i|a>>>32-i)+e},s._gg=function(t,e,n,r,o,i,s){var a=t+(e&r|n&~r)+(o>>>0)+s;return(a<<i|a>>>32-i)+e},s._hh=function(t,e,n,r,o,i,s){var a=t+(e^n^r)+(o>>>0)+s;return(a<<i|a>>>32-i)+e},s._ii=function(t,e,n,r,o,i,s){var a=t+(n^(e|~r))+(o>>>0)+s;return(a<<i|a>>>32-i)+e},s._blocksize=16,s._digestsize=16,t.exports=function(t,n){if(null==t)throw new Error(\"Illegal argument \"+t);var r=e.wordsToBytes(s(t,n));return n&&n.asBytes?r:n&&n.asString?i.bytesToString(r):e.bytesToHex(r)}}()},320:(t,e)=>{var n=Object.prototype.hasOwnProperty;function r(t){try{return decodeURIComponent(t.replace(/\\+/g,\" \"))}catch(t){return null}}function o(t){try{return encodeURIComponent(t)}catch(t){return null}}e.stringify=function(t,e){e=e||\"\";var r,i,s=[];for(i in\"string\"!=typeof e&&(e=\"?\"),t)if(n.call(t,i)){if((r=t[i])||null!=r&&!isNaN(r)||(r=\"\"),i=o(i),r=o(r),null===i||null===r)continue;s.push(i+\"=\"+r)}return s.length?e+s.join(\"&\"):\"\"},e.parse=function(t){for(var e,n=/([^=?#&]+)=?([^&]*)/g,o={};e=n.exec(t);){var i=r(e[1]),s=r(e[2]);null===i||null===s||i in o||(o[i]=s)}return o}},345:()=>{},385:(t,e,n)=>{const r=n(80),o=n(611),i=n(519),s=n(701),a=n(940);function u(t){const e=Object.keys(t);for(let n=0;n<e.length;n++){const r=e[n];this.lastEntities[r]={regex:new RegExp(\"&\"+r+\";\",\"g\"),val:t[r]}}}function c(t,e,n,r,o,i,s){if(void 0!==t&&(this.options.trimValues&&!r&&(t=t.trim()),t.length>0)){s||(t=this.replaceEntitiesValue(t));const r=this.options.tagValueProcessor(e,t,n,o,i);return null==r?t:typeof r!=typeof t||r!==t?r:this.options.trimValues||t.trim()===t?x(t,this.options.parseTagValue,this.options.numberParseOptions):t}}function l(t){if(this.options.removeNSPrefix){const e=t.split(\":\"),n=\"/\"===t.charAt(0)?\"/\":\"\";if(\"xmlns\"===e[0])return\"\";2===e.length&&(t=n+e[1])}return t}const h=new RegExp(\"([^\\\\s=]+)\\\\s*(=\\\\s*(['\\\"])([\\\\s\\\\S]*?)\\\\3)?\",\"gm\");function p(t,e,n){if(!0!==this.options.ignoreAttributes&&\"string\"==typeof t){const n=r.getAllMatches(t,h),o=n.length,i={};for(let t=0;t<o;t++){const r=this.resolveNameSpace(n[t][1]);if(this.ignoreAttributesFn(r,e))continue;let o=n[t][4],s=this.options.attributeNamePrefix+r;if(r.length)if(this.options.transformAttributeName&&(s=this.options.transformAttributeName(s)),\"__proto__\"===s&&(s=\"#__proto__\"),void 0!==o){this.options.trimValues&&(o=o.trim()),o=this.replaceEntitiesValue(o);const t=this.options.attributeValueProcessor(r,o,e);i[s]=null==t?o:typeof t!=typeof o||t!==o?t:x(o,this.options.parseAttributeValue,this.options.numberParseOptions)}else this.options.allowBooleanAttributes&&(i[s]=!0)}if(!Object.keys(i).length)return;if(this.options.attributesGroupName){const t={};return t[this.options.attributesGroupName]=i,t}return i}}const f=function(t){t=t.replace(/\\r\\n?/g,\"\\n\");const e=new o(\"!xml\");let n=e,r=\"\",s=\"\";for(let a=0;a<t.length;a++)if(\"<\"===t[a])if(\"/\"===t[a+1]){const e=v(t,\">\",a,\"Closing Tag is not closed.\");let o=t.substring(a+2,e).trim();if(this.options.removeNSPrefix){const t=o.indexOf(\":\");-1!==t&&(o=o.substr(t+1))}this.options.transformTagName&&(o=this.options.transformTagName(o)),n&&(r=this.saveTextToParentTag(r,n,s));const i=s.substring(s.lastIndexOf(\".\")+1);if(o&&-1!==this.options.unpairedTags.indexOf(o))throw new Error(`Unpaired tag can not be used as closing tag: </${o}>`);let u=0;i&&-1!==this.options.unpairedTags.indexOf(i)?(u=s.lastIndexOf(\".\",s.lastIndexOf(\".\")-1),this.tagsNodeStack.pop()):u=s.lastIndexOf(\".\"),s=s.substring(0,u),n=this.tagsNodeStack.pop(),r=\"\",a=e}else if(\"?\"===t[a+1]){let e=b(t,a,!1,\"?>\");if(!e)throw new Error(\"Pi Tag is not closed.\");if(r=this.saveTextToParentTag(r,n,s),this.options.ignoreDeclaration&&\"?xml\"===e.tagName||this.options.ignorePiTags);else{const t=new o(e.tagName);t.add(this.options.textNodeName,\"\"),e.tagName!==e.tagExp&&e.attrExpPresent&&(t[\":@\"]=this.buildAttributesMap(e.tagExp,s,e.tagName)),this.addChild(n,t,s)}a=e.closeIndex+1}else if(\"!--\"===t.substr(a+1,3)){const e=v(t,\"--\\x3e\",a+4,\"Comment is not closed.\");if(this.options.commentPropName){const o=t.substring(a+4,e-2);r=this.saveTextToParentTag(r,n,s),n.add(this.options.commentPropName,[{[this.options.textNodeName]:o}])}a=e}else if(\"!D\"===t.substr(a+1,2)){const e=i(t,a);this.docTypeEntities=e.entities,a=e.i}else if(\"![\"===t.substr(a+1,2)){const e=v(t,\"]]>\",a,\"CDATA is not closed.\")-2,o=t.substring(a+9,e);r=this.saveTextToParentTag(r,n,s);let i=this.parseTextData(o,n.tagname,s,!0,!1,!0,!0);null==i&&(i=\"\"),this.options.cdataPropName?n.add(this.options.cdataPropName,[{[this.options.textNodeName]:o}]):n.add(this.options.textNodeName,i),a=e+2}else{let i=b(t,a,this.options.removeNSPrefix),u=i.tagName;const c=i.rawTagName;let l=i.tagExp,h=i.attrExpPresent,p=i.closeIndex;this.options.transformTagName&&(u=this.options.transformTagName(u)),n&&r&&\"!xml\"!==n.tagname&&(r=this.saveTextToParentTag(r,n,s,!1));const f=n;if(f&&-1!==this.options.unpairedTags.indexOf(f.tagname)&&(n=this.tagsNodeStack.pop(),s=s.substring(0,s.lastIndexOf(\".\"))),u!==e.tagname&&(s+=s?\".\"+u:u),this.isItStopNode(this.options.stopNodes,s,u)){let e=\"\";if(l.length>0&&l.lastIndexOf(\"/\")===l.length-1)\"/\"===u[u.length-1]?(u=u.substr(0,u.length-1),s=s.substr(0,s.length-1),l=u):l=l.substr(0,l.length-1),a=i.closeIndex;else if(-1!==this.options.unpairedTags.indexOf(u))a=i.closeIndex;else{const n=this.readStopNodeData(t,c,p+1);if(!n)throw new Error(`Unexpected end of ${c}`);a=n.i,e=n.tagContent}const r=new o(u);u!==l&&h&&(r[\":@\"]=this.buildAttributesMap(l,s,u)),e&&(e=this.parseTextData(e,u,s,!0,h,!0,!0)),s=s.substr(0,s.lastIndexOf(\".\")),r.add(this.options.textNodeName,e),this.addChild(n,r,s)}else{if(l.length>0&&l.lastIndexOf(\"/\")===l.length-1){\"/\"===u[u.length-1]?(u=u.substr(0,u.length-1),s=s.substr(0,s.length-1),l=u):l=l.substr(0,l.length-1),this.options.transformTagName&&(u=this.options.transformTagName(u));const t=new o(u);u!==l&&h&&(t[\":@\"]=this.buildAttributesMap(l,s,u)),this.addChild(n,t,s),s=s.substr(0,s.lastIndexOf(\".\"))}else{const t=new o(u);this.tagsNodeStack.push(n),u!==l&&h&&(t[\":@\"]=this.buildAttributesMap(l,s,u)),this.addChild(n,t,s),n=t}r=\"\",a=p}}else r+=t[a];return e.child};function g(t,e,n){const r=this.options.updateTag(e.tagname,n,e[\":@\"]);!1===r||(\"string\"==typeof r?(e.tagname=r,t.addChild(e)):t.addChild(e))}const d=function(t){if(this.options.processEntities){for(let e in this.docTypeEntities){const n=this.docTypeEntities[e];t=t.replace(n.regx,n.val)}for(let e in this.lastEntities){const n=this.lastEntities[e];t=t.replace(n.regex,n.val)}if(this.options.htmlEntities)for(let e in this.htmlEntities){const n=this.htmlEntities[e];t=t.replace(n.regex,n.val)}t=t.replace(this.ampEntity.regex,this.ampEntity.val)}return t};function m(t,e,n,r){return t&&(void 0===r&&(r=0===e.child.length),void 0!==(t=this.parseTextData(t,e.tagname,n,!1,!!e[\":@\"]&&0!==Object.keys(e[\":@\"]).length,r))&&\"\"!==t&&e.add(this.options.textNodeName,t),t=\"\"),t}function y(t,e,n){const r=\"*.\"+n;for(const n in t){const o=t[n];if(r===o||e===o)return!0}return!1}function v(t,e,n,r){const o=t.indexOf(e,n);if(-1===o)throw new Error(r);return o+e.length-1}function b(t,e,n){const r=function(t,e){let n,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:\">\",o=\"\";for(let i=e;i<t.length;i++){let e=t[i];if(n)e===n&&(n=\"\");else if('\"'===e||\"'\"===e)n=e;else if(e===r[0]){if(!r[1])return{data:o,index:i};if(t[i+1]===r[1])return{data:o,index:i}}else\"\\t\"===e&&(e=\" \");o+=e}}(t,e+1,arguments.length>3&&void 0!==arguments[3]?arguments[3]:\">\");if(!r)return;let o=r.data;const i=r.index,s=o.search(/\\s/);let a=o,u=!0;-1!==s&&(a=o.substring(0,s),o=o.substring(s+1).trimStart());const c=a;if(n){const t=a.indexOf(\":\");-1!==t&&(a=a.substr(t+1),u=a!==r.data.substr(t+1))}return{tagName:a,tagExp:o,closeIndex:i,attrExpPresent:u,rawTagName:c}}function w(t,e,n){const r=n;let o=1;for(;n<t.length;n++)if(\"<\"===t[n])if(\"/\"===t[n+1]){const i=v(t,\">\",n,`${e} is not closed`);if(t.substring(n+2,i).trim()===e&&(o--,0===o))return{tagContent:t.substring(r,n),i};n=i}else if(\"?\"===t[n+1])n=v(t,\"?>\",n+1,\"StopNode is not closed.\");else if(\"!--\"===t.substr(n+1,3))n=v(t,\"--\\x3e\",n+3,\"StopNode is not closed.\");else if(\"![\"===t.substr(n+1,2))n=v(t,\"]]>\",n,\"StopNode is not closed.\")-2;else{const r=b(t,n,\">\");r&&((r&&r.tagName)===e&&\"/\"!==r.tagExp[r.tagExp.length-1]&&o++,n=r.closeIndex)}}function x(t,e,n){if(e&&\"string\"==typeof t){const e=t.trim();return\"true\"===e||\"false\"!==e&&s(t,n)}return r.isExist(t)?t:\"\"}t.exports=class{constructor(t){this.options=t,this.currentNode=null,this.tagsNodeStack=[],this.docTypeEntities={},this.lastEntities={apos:{regex:/&(apos|#39|#x27);/g,val:\"'\"},gt:{regex:/&(gt|#62|#x3E);/g,val:\">\"},lt:{regex:/&(lt|#60|#x3C);/g,val:\"<\"},quot:{regex:/&(quot|#34|#x22);/g,val:'\"'}},this.ampEntity={regex:/&(amp|#38|#x26);/g,val:\"&\"},this.htmlEntities={space:{regex:/&(nbsp|#160);/g,val:\" \"},cent:{regex:/&(cent|#162);/g,val:\"¢\"},pound:{regex:/&(pound|#163);/g,val:\"£\"},yen:{regex:/&(yen|#165);/g,val:\"¥\"},euro:{regex:/&(euro|#8364);/g,val:\"€\"},copyright:{regex:/&(copy|#169);/g,val:\"©\"},reg:{regex:/&(reg|#174);/g,val:\"®\"},inr:{regex:/&(inr|#8377);/g,val:\"₹\"},num_dec:{regex:/&#([0-9]{1,7});/g,val:(t,e)=>String.fromCharCode(Number.parseInt(e,10))},num_hex:{regex:/&#x([0-9a-fA-F]{1,6});/g,val:(t,e)=>String.fromCharCode(Number.parseInt(e,16))}},this.addExternalEntities=u,this.parseXml=f,this.parseTextData=c,this.resolveNameSpace=l,this.buildAttributesMap=p,this.isItStopNode=y,this.replaceEntitiesValue=d,this.readStopNodeData=w,this.saveTextToParentTag=m,this.addChild=g,this.ignoreAttributesFn=a(this.options.ignoreAttributes)}}},388:()=>{},519:(t,e,n)=>{const r=n(80);function o(t,e){let n=\"\";for(;e<t.length&&\"'\"!==t[e]&&'\"'!==t[e];e++)n+=t[e];if(n=n.trim(),-1!==n.indexOf(\" \"))throw new Error(\"External entites are not supported\");const r=t[e++];let o=\"\";for(;e<t.length&&t[e]!==r;e++)o+=t[e];return[n,o,e]}function i(t,e){return\"!\"===t[e+1]&&\"-\"===t[e+2]&&\"-\"===t[e+3]}function s(t,e){return\"!\"===t[e+1]&&\"E\"===t[e+2]&&\"N\"===t[e+3]&&\"T\"===t[e+4]&&\"I\"===t[e+5]&&\"T\"===t[e+6]&&\"Y\"===t[e+7]}function a(t,e){return\"!\"===t[e+1]&&\"E\"===t[e+2]&&\"L\"===t[e+3]&&\"E\"===t[e+4]&&\"M\"===t[e+5]&&\"E\"===t[e+6]&&\"N\"===t[e+7]&&\"T\"===t[e+8]}function u(t,e){return\"!\"===t[e+1]&&\"A\"===t[e+2]&&\"T\"===t[e+3]&&\"T\"===t[e+4]&&\"L\"===t[e+5]&&\"I\"===t[e+6]&&\"S\"===t[e+7]&&\"T\"===t[e+8]}function c(t,e){return\"!\"===t[e+1]&&\"N\"===t[e+2]&&\"O\"===t[e+3]&&\"T\"===t[e+4]&&\"A\"===t[e+5]&&\"T\"===t[e+6]&&\"I\"===t[e+7]&&\"O\"===t[e+8]&&\"N\"===t[e+9]}function l(t){if(r.isName(t))return t;throw new Error(`Invalid entity name ${t}`)}t.exports=function(t,e){const n={};if(\"O\"!==t[e+3]||\"C\"!==t[e+4]||\"T\"!==t[e+5]||\"Y\"!==t[e+6]||\"P\"!==t[e+7]||\"E\"!==t[e+8])throw new Error(\"Invalid Tag instead of DOCTYPE\");{e+=9;let r=1,h=!1,p=!1,f=\"\";for(;e<t.length;e++)if(\"<\"!==t[e]||p)if(\">\"===t[e]){if(p?\"-\"===t[e-1]&&\"-\"===t[e-2]&&(p=!1,r--):r--,0===r)break}else\"[\"===t[e]?h=!0:f+=t[e];else{if(h&&s(t,e)){let r,i;e+=7,[r,i,e]=o(t,e+1),-1===i.indexOf(\"&\")&&(n[l(r)]={regx:RegExp(`&${r};`,\"g\"),val:i})}else if(h&&a(t,e))e+=8;else if(h&&u(t,e))e+=8;else if(h&&c(t,e))e+=9;else{if(!i)throw new Error(\"Invalid DOCTYPE\");p=!0}r++,f=\"\"}if(0!==r)throw new Error(\"Unclosed DOCTYPE\")}return{entities:n,i:e}}},552:t=>{function e(t){return!!t.constructor&&\"function\"==typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}t.exports=function(t){return null!=t&&(e(t)||function(t){return\"function\"==typeof t.readFloatLE&&\"function\"==typeof t.slice&&e(t.slice(0,0))}(t)||!!t._isBuffer)}},574:(t,e,n)=>{var r=n(773);t.exports=function(t){return t?(\"{}\"===t.substr(0,2)&&(t=\"\\\\{\\\\}\"+t.substr(2)),m(function(t){return t.split(\"\\\\\\\\\").join(o).split(\"\\\\{\").join(i).split(\"\\\\}\").join(s).split(\"\\\\,\").join(a).split(\"\\\\.\").join(u)}(t),!0).map(l)):[]};var o=\"\\0SLASH\"+Math.random()+\"\\0\",i=\"\\0OPEN\"+Math.random()+\"\\0\",s=\"\\0CLOSE\"+Math.random()+\"\\0\",a=\"\\0COMMA\"+Math.random()+\"\\0\",u=\"\\0PERIOD\"+Math.random()+\"\\0\";function c(t){return parseInt(t,10)==t?parseInt(t,10):t.charCodeAt(0)}function l(t){return t.split(o).join(\"\\\\\").split(i).join(\"{\").split(s).join(\"}\").split(a).join(\",\").split(u).join(\".\")}function h(t){if(!t)return[\"\"];var e=[],n=r(\"{\",\"}\",t);if(!n)return t.split(\",\");var o=n.pre,i=n.body,s=n.post,a=o.split(\",\");a[a.length-1]+=\"{\"+i+\"}\";var u=h(s);return s.length&&(a[a.length-1]+=u.shift(),a.push.apply(a,u)),e.push.apply(e,a),e}function p(t){return\"{\"+t+\"}\"}function f(t){return/^-?0\\d/.test(t)}function g(t,e){return t<=e}function d(t,e){return t>=e}function m(t,e){var n=[],o=r(\"{\",\"}\",t);if(!o)return[t];var i=o.pre,a=o.post.length?m(o.post,!1):[\"\"];if(/\\$$/.test(o.pre))for(var u=0;u<a.length;u++){var l=i+\"{\"+o.body+\"}\"+a[u];n.push(l)}else{var y,v,b=/^-?\\d+\\.\\.-?\\d+(?:\\.\\.-?\\d+)?$/.test(o.body),w=/^[a-zA-Z]\\.\\.[a-zA-Z](?:\\.\\.-?\\d+)?$/.test(o.body),x=b||w,N=o.body.indexOf(\",\")>=0;if(!x&&!N)return o.post.match(/,(?!,).*\\}/)?m(t=o.pre+\"{\"+o.body+s+o.post):[t];if(x)y=o.body.split(/\\.\\./);else if(1===(y=h(o.body)).length&&1===(y=m(y[0],!1).map(p)).length)return a.map(function(t){return o.pre+y[0]+t});if(x){var P=c(y[0]),A=c(y[1]),E=Math.max(y[0].length,y[1].length),O=3==y.length?Math.abs(c(y[2])):1,T=g;A<P&&(O*=-1,T=d);var j=y.some(f);v=[];for(var S=P;T(S,A);S+=O){var $;if(w)\"\\\\\"===($=String.fromCharCode(S))&&($=\"\");else if($=String(S),j){var C=E-$.length;if(C>0){var I=new Array(C+1).join(\"0\");$=S<0?\"-\"+I+$.slice(1):I+$}}v.push($)}}else{v=[];for(var k=0;k<y.length;k++)v.push.apply(v,m(y[k],!1))}for(k=0;k<v.length;k++)for(u=0;u<a.length;u++)l=i+v[k]+a[u],(!e||x||l)&&n.push(l)}return n}},611:t=>{t.exports=class{constructor(t){this.tagname=t,this.child=[],this[\":@\"]={}}add(t,e){\"__proto__\"===t&&(t=\"#__proto__\"),this.child.push({[t]:e})}addChild(t){\"__proto__\"===t.tagname&&(t.tagname=\"#__proto__\"),t[\":@\"]&&Object.keys(t[\":@\"]).length>0?this.child.push({[t.tagname]:t.child,\":@\":t[\":@\"]}):this.child.push({[t.tagname]:t.child})}}},694:(t,e)=>{e.d=function(t){if(!t)return 0;for(var e=(t=t.toString()).length,n=t.length;n--;){var r=t.charCodeAt(n);56320<=r&&r<=57343&&n--,127<r&&r<=2047?e++:2047<r&&r<=65535&&(e+=2)}return e}},699:(t,e,n)=>{var r=n(0),o=function(t){return\"string\"==typeof t};function i(t,e){for(var n=[],r=0;r<t.length;r++){var o=t[r];o&&\".\"!==o&&(\"..\"===o?n.length&&\"..\"!==n[n.length-1]?n.pop():e&&n.push(\"..\"):n.push(o))}return n}var s=/^(\\/?|)([\\s\\S]*?)((?:\\.{1,2}|[^\\/]+?|)(\\.[^.\\/]*|))(?:[\\/]*)$/,a={};function u(t){return s.exec(t).slice(1)}a.resolve=function(){for(var t=\"\",e=!1,n=arguments.length-1;n>=-1&&!e;n--){var r=n>=0?arguments[n]:process.cwd();if(!o(r))throw new TypeError(\"Arguments to path.resolve must be strings\");r&&(t=r+\"/\"+t,e=\"/\"===r.charAt(0))}return(e?\"/\":\"\")+(t=i(t.split(\"/\"),!e).join(\"/\"))||\".\"},a.normalize=function(t){var e=a.isAbsolute(t),n=\"/\"===t.substr(-1);return(t=i(t.split(\"/\"),!e).join(\"/\"))||e||(t=\".\"),t&&n&&(t+=\"/\"),(e?\"/\":\"\")+t},a.isAbsolute=function(t){return\"/\"===t.charAt(0)},a.join=function(){for(var t=\"\",e=0;e<arguments.length;e++){var n=arguments[e];if(!o(n))throw new TypeError(\"Arguments to path.join must be strings\");n&&(t+=t?\"/\"+n:n)}return a.normalize(t)},a.relative=function(t,e){function n(t){for(var e=0;e<t.length&&\"\"===t[e];e++);for(var n=t.length-1;n>=0&&\"\"===t[n];n--);return e>n?[]:t.slice(e,n+1)}t=a.resolve(t).substr(1),e=a.resolve(e).substr(1);for(var r=n(t.split(\"/\")),o=n(e.split(\"/\")),i=Math.min(r.length,o.length),s=i,u=0;u<i;u++)if(r[u]!==o[u]){s=u;break}var c=[];for(u=s;u<r.length;u++)c.push(\"..\");return(c=c.concat(o.slice(s))).join(\"/\")},a._makeLong=function(t){return t},a.dirname=function(t){var e=u(t),n=e[0],r=e[1];return n||r?(r&&(r=r.substr(0,r.length-1)),n+r):\".\"},a.basename=function(t,e){var n=u(t)[2];return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},a.extname=function(t){return u(t)[3]},a.format=function(t){if(!r.isObject(t))throw new TypeError(\"Parameter 'pathObject' must be an object, not \"+typeof t);var e=t.root||\"\";if(!o(e))throw new TypeError(\"'pathObject.root' must be a string or undefined, not \"+typeof t.root);return(t.dir?t.dir+a.sep:\"\")+(t.base||\"\")},a.parse=function(t){if(!o(t))throw new TypeError(\"Parameter 'pathString' must be a string, not \"+typeof t);var e=u(t);if(!e||4!==e.length)throw new TypeError(\"Invalid path '\"+t+\"'\");return e[1]=e[1]||\"\",e[2]=e[2]||\"\",e[3]=e[3]||\"\",{root:e[0],dir:e[0]+e[1].slice(0,e[1].length-1),base:e[2],ext:e[3],name:e[2].slice(0,e[2].length-e[3].length)}},a.sep=\"/\",a.delimiter=\":\",t.exports=a},701:t=>{const e=/^[-+]?0x[a-fA-F0-9]+$/,n=/^([\\-\\+])?(0*)([0-9]*(\\.[0-9]*)?)$/,r={hex:!0,leadingZeros:!0,decimalPoint:\".\",eNotation:!0};t.exports=function(t){let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(o=Object.assign({},r,o),!t||\"string\"!=typeof t)return t;let i=t.trim();if(void 0!==o.skipLike&&o.skipLike.test(i))return t;if(\"0\"===t)return 0;if(o.hex&&e.test(i))return function(t){if(parseInt)return parseInt(t,16);if(Number.parseInt)return Number.parseInt(t,16);if(window&&window.parseInt)return window.parseInt(t,16);throw new Error(\"parseInt, Number.parseInt, window.parseInt are not supported\")}(i);if(-1!==i.search(/[eE]/)){const e=i.match(/^([-\\+])?(0*)([0-9]*(\\.[0-9]*)?[eE][-\\+]?[0-9]+)$/);if(e){if(o.leadingZeros)i=(e[1]||\"\")+e[3];else if(\"0\"!==e[2]||\".\"!==e[3][0])return t;return o.eNotation?Number(i):t}return t}{const e=n.exec(i);if(e){const n=e[1],r=e[2];let a=(s=e[3])&&-1!==s.indexOf(\".\")?(\".\"===(s=s.replace(/0+$/,\"\"))?s=\"0\":\".\"===s[0]?s=\"0\"+s:\".\"===s[s.length-1]&&(s=s.substr(0,s.length-1)),s):s;if(!o.leadingZeros&&r.length>0&&n&&\".\"!==i[2])return t;if(!o.leadingZeros&&r.length>0&&!n&&\".\"!==i[1])return t;if(o.leadingZeros&&r===t)return 0;{const e=Number(i),s=\"\"+e;return-1!==s.search(/[eE]/)?o.eNotation?e:t:-1!==i.indexOf(\".\")?\"0\"===s&&\"\"===a||s===a||n&&s===\"-\"+a?e:t:r?a===s||n+a===s?e:t:i===s||i===n+s?e:t}}return t}var s}},706:t=>{var e={utf8:{stringToBytes:function(t){return e.bin.stringToBytes(unescape(encodeURIComponent(t)))},bytesToString:function(t){return decodeURIComponent(escape(e.bin.bytesToString(t)))}},bin:{stringToBytes:function(t){for(var e=[],n=0;n<t.length;n++)e.push(255&t.charCodeAt(n));return e},bytesToString:function(t){for(var e=[],n=0;n<t.length;n++)e.push(String.fromCharCode(t[n]));return e.join(\"\")}}};t.exports=e},715:(t,e,n)=>{var r=n(745),o=n(320),i=/^[\\x00-\\x20\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]+/,s=/[\\n\\r\\t]/g,a=/^[A-Za-z][A-Za-z0-9+-.]*:\\/\\//,u=/:\\d+$/,c=/^([a-z][a-z0-9.+-]*:)?(\\/\\/)?([\\\\/]+)?([\\S\\s]*)/i,l=/^[a-zA-Z]:/;function h(t){return(t||\"\").toString().replace(i,\"\")}var p=[[\"#\",\"hash\"],[\"?\",\"query\"],function(t,e){return d(e.protocol)?t.replace(/\\\\/g,\"/\"):t},[\"/\",\"pathname\"],[\"@\",\"auth\",1],[NaN,\"host\",void 0,1,1],[/:(\\d*)$/,\"port\",void 0,1],[NaN,\"hostname\",void 0,1,1]],f={hash:1,query:1};function g(t){var e,n=(\"undefined\"!=typeof window?window:\"undefined\"!=typeof global?global:\"undefined\"!=typeof self?self:{}).location||{},r={},o=typeof(t=t||n);if(\"blob:\"===t.protocol)r=new y(unescape(t.pathname),{});else if(\"string\"===o)for(e in r=new y(t,{}),f)delete r[e];else if(\"object\"===o){for(e in t)e in f||(r[e]=t[e]);void 0===r.slashes&&(r.slashes=a.test(t.href))}return r}function d(t){return\"file:\"===t||\"ftp:\"===t||\"http:\"===t||\"https:\"===t||\"ws:\"===t||\"wss:\"===t}function m(t,e){t=(t=h(t)).replace(s,\"\"),e=e||{};var n,r=c.exec(t),o=r[1]?r[1].toLowerCase():\"\",i=!!r[2],a=!!r[3],u=0;return i?a?(n=r[2]+r[3]+r[4],u=r[2].length+r[3].length):(n=r[2]+r[4],u=r[2].length):a?(n=r[3]+r[4],u=r[3].length):n=r[4],\"file:\"===o?u>=2&&(n=n.slice(2)):d(o)?n=r[4]:o?i&&(n=n.slice(2)):u>=2&&d(e.protocol)&&(n=r[4]),{protocol:o,slashes:i||d(o),slashesCount:u,rest:n}}function y(t,e,n){if(t=(t=h(t)).replace(s,\"\"),!(this instanceof y))return new y(t,e,n);var i,a,u,c,f,v,b=p.slice(),w=typeof e,x=this,N=0;for(\"object\"!==w&&\"string\"!==w&&(n=e,e=null),n&&\"function\"!=typeof n&&(n=o.parse),i=!(a=m(t||\"\",e=g(e))).protocol&&!a.slashes,x.slashes=a.slashes||i&&e.slashes,x.protocol=a.protocol||e.protocol||\"\",t=a.rest,(\"file:\"===a.protocol&&(2!==a.slashesCount||l.test(t))||!a.slashes&&(a.protocol||a.slashesCount<2||!d(x.protocol)))&&(b[3]=[/(.*)/,\"pathname\"]);N<b.length;N++)\"function\"!=typeof(c=b[N])?(u=c[0],v=c[1],u!=u?x[v]=t:\"string\"==typeof u?~(f=\"@\"===u?t.lastIndexOf(u):t.indexOf(u))&&(\"number\"==typeof c[2]?(x[v]=t.slice(0,f),t=t.slice(f+c[2])):(x[v]=t.slice(f),t=t.slice(0,f))):(f=u.exec(t))&&(x[v]=f[1],t=t.slice(0,f.index)),x[v]=x[v]||i&&c[3]&&e[v]||\"\",c[4]&&(x[v]=x[v].toLowerCase())):t=c(t,x);n&&(x.query=n(x.query)),i&&e.slashes&&\"/\"!==x.pathname.charAt(0)&&(\"\"!==x.pathname||\"\"!==e.pathname)&&(x.pathname=function(t,e){if(\"\"===t)return e;for(var n=(e||\"/\").split(\"/\").slice(0,-1).concat(t.split(\"/\")),r=n.length,o=n[r-1],i=!1,s=0;r--;)\".\"===n[r]?n.splice(r,1):\"..\"===n[r]?(n.splice(r,1),s++):s&&(0===r&&(i=!0),n.splice(r,1),s--);return i&&n.unshift(\"\"),\".\"!==o&&\"..\"!==o||n.push(\"\"),n.join(\"/\")}(x.pathname,e.pathname)),\"/\"!==x.pathname.charAt(0)&&d(x.protocol)&&(x.pathname=\"/\"+x.pathname),r(x.port,x.protocol)||(x.host=x.hostname,x.port=\"\"),x.username=x.password=\"\",x.auth&&(~(f=x.auth.indexOf(\":\"))?(x.username=x.auth.slice(0,f),x.username=encodeURIComponent(decodeURIComponent(x.username)),x.password=x.auth.slice(f+1),x.password=encodeURIComponent(decodeURIComponent(x.password))):x.username=encodeURIComponent(decodeURIComponent(x.auth)),x.auth=x.password?x.username+\":\"+x.password:x.username),x.origin=\"file:\"!==x.protocol&&d(x.protocol)&&x.host?x.protocol+\"//\"+x.host:\"null\",x.href=x.toString()}y.prototype={set:function(t,e,n){var i=this;switch(t){case\"query\":\"string\"==typeof e&&e.length&&(e=(n||o.parse)(e)),i[t]=e;break;case\"port\":i[t]=e,r(e,i.protocol)?e&&(i.host=i.hostname+\":\"+e):(i.host=i.hostname,i[t]=\"\");break;case\"hostname\":i[t]=e,i.port&&(e+=\":\"+i.port),i.host=e;break;case\"host\":i[t]=e,u.test(e)?(e=e.split(\":\"),i.port=e.pop(),i.hostname=e.join(\":\")):(i.hostname=e,i.port=\"\");break;case\"protocol\":i.protocol=e.toLowerCase(),i.slashes=!n;break;case\"pathname\":case\"hash\":if(e){var s=\"pathname\"===t?\"/\":\"#\";i[t]=e.charAt(0)!==s?s+e:e}else i[t]=e;break;case\"username\":case\"password\":i[t]=encodeURIComponent(e);break;case\"auth\":var a=e.indexOf(\":\");~a?(i.username=e.slice(0,a),i.username=encodeURIComponent(decodeURIComponent(i.username)),i.password=e.slice(a+1),i.password=encodeURIComponent(decodeURIComponent(i.password))):i.username=encodeURIComponent(decodeURIComponent(e))}for(var c=0;c<p.length;c++){var l=p[c];l[4]&&(i[l[1]]=i[l[1]].toLowerCase())}return i.auth=i.password?i.username+\":\"+i.password:i.username,i.origin=\"file:\"!==i.protocol&&d(i.protocol)&&i.host?i.protocol+\"//\"+i.host:\"null\",i.href=i.toString(),i},toString:function(t){t&&\"function\"==typeof t||(t=o.stringify);var e,n=this,r=n.host,i=n.protocol;i&&\":\"!==i.charAt(i.length-1)&&(i+=\":\");var s=i+(n.protocol&&n.slashes||d(n.protocol)?\"//\":\"\");return n.username?(s+=n.username,n.password&&(s+=\":\"+n.password),s+=\"@\"):n.password?(s+=\":\"+n.password,s+=\"@\"):\"file:\"!==n.protocol&&d(n.protocol)&&!r&&\"/\"!==n.pathname&&(s+=\"@\"),(\":\"===r[r.length-1]||u.test(n.hostname)&&!n.port)&&(r+=\":\"),s+=r+n.pathname,(e=\"object\"==typeof n.query?t(n.query):n.query)&&(s+=\"?\"!==e.charAt(0)?\"?\"+e:e),n.hash&&(s+=n.hash),s}},y.extractProtocol=m,y.location=g,y.trimLeft=h,y.qs=o,t.exports=y},745:t=>{t.exports=function(t,e){if(e=e.split(\":\")[0],!(t=+t))return!1;switch(e){case\"http\":case\"ws\":return 80!==t;case\"https\":case\"wss\":return 443!==t;case\"ftp\":return 21!==t;case\"gopher\":return 70!==t;case\"file\":return!1}return 0!==t}},773:t=>{function e(t,e,o){t instanceof RegExp&&(t=n(t,o)),e instanceof RegExp&&(e=n(e,o));var i=r(t,e,o);return i&&{start:i[0],end:i[1],pre:o.slice(0,i[0]),body:o.slice(i[0]+t.length,i[1]),post:o.slice(i[1]+e.length)}}function n(t,e){var n=e.match(t);return n?n[0]:null}function r(t,e,n){var r,o,i,s,a,u=n.indexOf(t),c=n.indexOf(e,u+1),l=u;if(u>=0&&c>0){if(t===e)return[u,c];for(r=[],i=n.length;l>=0&&!a;)l==u?(r.push(l),u=n.indexOf(t,l+1)):1==r.length?a=[r.pop(),c]:((o=r.pop())<i&&(i=o,s=c),c=n.indexOf(e,l+1)),l=u<c&&u>=0?u:c;r.length&&(a=[i,s])}return a}t.exports=e,e.range=r},785:(t,e)=>{const n={preserveOrder:!1,attributeNamePrefix:\"@_\",attributesGroupName:!1,textNodeName:\"#text\",ignoreAttributes:!0,removeNSPrefix:!1,allowBooleanAttributes:!1,parseTagValue:!0,parseAttributeValue:!1,trimValues:!0,cdataPropName:!1,numberParseOptions:{hex:!0,leadingZeros:!0,eNotation:!0},tagValueProcessor:function(t,e){return e},attributeValueProcessor:function(t,e){return e},stopNodes:[],alwaysCreateTextNode:!1,isArray:()=>!1,commentPropName:!1,unpairedTags:[],processEntities:!0,htmlEntities:!1,ignoreDeclaration:!1,ignorePiTags:!1,transformTagName:!1,transformAttributeName:!1,updateTag:function(t,e,n){return t}};e.buildOptions=function(t){return Object.assign({},n,t)},e.defaultOptions=n},800:()=>{},802:function(t,e,n){var r;t=n.nmd(t),function(){var o=(t&&t.exports,\"object\"==typeof global&&global);o.global!==o&&o.window;var i=function(t){this.message=t};(i.prototype=new Error).name=\"InvalidCharacterError\";var s=function(t){throw new i(t)},a=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\",u=/[\\t\\n\\f\\r ]/g,c={encode:function(t){t=String(t),/[^\\0-\\xFF]/.test(t)&&s(\"The string to be encoded contains characters outside of the Latin1 range.\");for(var e,n,r,o,i=t.length%3,u=\"\",c=-1,l=t.length-i;++c<l;)e=t.charCodeAt(c)<<16,n=t.charCodeAt(++c)<<8,r=t.charCodeAt(++c),u+=a.charAt((o=e+n+r)>>18&63)+a.charAt(o>>12&63)+a.charAt(o>>6&63)+a.charAt(63&o);return 2==i?(e=t.charCodeAt(c)<<8,n=t.charCodeAt(++c),u+=a.charAt((o=e+n)>>10)+a.charAt(o>>4&63)+a.charAt(o<<2&63)+\"=\"):1==i&&(o=t.charCodeAt(c),u+=a.charAt(o>>2)+a.charAt(o<<4&63)+\"==\"),u},decode:function(t){var e=(t=String(t).replace(u,\"\")).length;e%4==0&&(e=(t=t.replace(/==?$/,\"\")).length),(e%4==1||/[^+a-zA-Z0-9/]/.test(t))&&s(\"Invalid character: the string to be decoded is not correctly encoded.\");for(var n,r,o=0,i=\"\",c=-1;++c<e;)r=a.indexOf(t.charAt(c)),n=o%4?64*n+r:r,o++%4&&(i+=String.fromCharCode(255&n>>(-2*o&6)));return i},version:\"1.0.0\"};void 0===(r=function(){return c}.call(e,n,e,t))||(t.exports=r)}()},805:()=>{},829:t=>{function e(t,s,a,u){let c=\"\",l=!1;for(let h=0;h<t.length;h++){const p=t[h],f=n(p);if(void 0===f)continue;let g=\"\";if(g=0===a.length?f:`${a}.${f}`,f===s.textNodeName){let t=p[f];o(g,s)||(t=s.tagValueProcessor(f,t),t=i(t,s)),l&&(c+=u),c+=t,l=!1;continue}if(f===s.cdataPropName){l&&(c+=u),c+=`<![CDATA[${p[f][0][s.textNodeName]}]]>`,l=!1;continue}if(f===s.commentPropName){c+=u+`\\x3c!--${p[f][0][s.textNodeName]}--\\x3e`,l=!0;continue}if(\"?\"===f[0]){const t=r(p[\":@\"],s),e=\"?xml\"===f?\"\":u;let n=p[f][0][s.textNodeName];n=0!==n.length?\" \"+n:\"\",c+=e+`<${f}${n}${t}?>`,l=!0;continue}let d=u;\"\"!==d&&(d+=s.indentBy);const m=u+`<${f}${r(p[\":@\"],s)}`,y=e(p[f],s,g,d);-1!==s.unpairedTags.indexOf(f)?s.suppressUnpairedNode?c+=m+\">\":c+=m+\"/>\":y&&0!==y.length||!s.suppressEmptyNode?y&&y.endsWith(\">\")?c+=m+`>${y}${u}</${f}>`:(c+=m+\">\",y&&\"\"!==u&&(y.includes(\"/>\")||y.includes(\"</\"))?c+=u+s.indentBy+y+u:c+=y,c+=`</${f}>`):c+=m+\"/>\",l=!0}return c}function n(t){const e=Object.keys(t);for(let n=0;n<e.length;n++){const r=e[n];if(t.hasOwnProperty(r)&&\":@\"!==r)return r}}function r(t,e){let n=\"\";if(t&&!e.ignoreAttributes)for(let r in t){if(!t.hasOwnProperty(r))continue;let o=e.attributeValueProcessor(r,t[r]);o=i(o,e),!0===o&&e.suppressBooleanAttributes?n+=` ${r.substr(e.attributeNamePrefix.length)}`:n+=` ${r.substr(e.attributeNamePrefix.length)}=\"${o}\"`}return n}function o(t,e){let n=(t=t.substr(0,t.length-e.textNodeName.length-1)).substr(t.lastIndexOf(\".\")+1);for(let r in e.stopNodes)if(e.stopNodes[r]===t||e.stopNodes[r]===\"*.\"+n)return!0;return!1}function i(t,e){if(t&&t.length>0&&e.processEntities)for(let n=0;n<e.entities.length;n++){const r=e.entities[n];t=t.replace(r.regex,r.val)}return t}t.exports=function(t,n){let r=\"\";return n.format&&n.indentBy.length>0&&(r=\"\\n\"),e(t,n,\"\",r)}},940:t=>{t.exports=function(t){return\"function\"==typeof t?t:Array.isArray(t)?e=>{for(const n of t){if(\"string\"==typeof n&&e===n)return!0;if(n instanceof RegExp&&n.test(e))return!0}}:()=>!1}},978:(t,e)=>{function n(t,e,s){let a;const u={};for(let c=0;c<t.length;c++){const l=t[c],h=r(l);let p=\"\";if(p=void 0===s?h:s+\".\"+h,h===e.textNodeName)void 0===a?a=l[h]:a+=\"\"+l[h];else{if(void 0===h)continue;if(l[h]){let t=n(l[h],e,p);const r=i(t,e);l[\":@\"]?o(t,l[\":@\"],p,e):1!==Object.keys(t).length||void 0===t[e.textNodeName]||e.alwaysCreateTextNode?0===Object.keys(t).length&&(e.alwaysCreateTextNode?t[e.textNodeName]=\"\":t=\"\"):t=t[e.textNodeName],void 0!==u[h]&&u.hasOwnProperty(h)?(Array.isArray(u[h])||(u[h]=[u[h]]),u[h].push(t)):e.isArray(h,p,r)?u[h]=[t]:u[h]=t}}}return\"string\"==typeof a?a.length>0&&(u[e.textNodeName]=a):void 0!==a&&(u[e.textNodeName]=a),u}function r(t){const e=Object.keys(t);for(let t=0;t<e.length;t++){const n=e[t];if(\":@\"!==n)return n}}function o(t,e,n,r){if(e){const o=Object.keys(e),i=o.length;for(let s=0;s<i;s++){const i=o[s];r.isArray(i,n+\".\"+i,!0,!0)?t[i]=[e[i]]:t[i]=e[i]}}}function i(t,e){const{textNodeName:n}=e,r=Object.keys(t).length;return 0===r||!(1!==r||!t[n]&&\"boolean\"!=typeof t[n]&&0!==t[n])}e.prettify=function(t,e){return n(t,e)}},993:(t,e,n)=>{const r=n(80),o={allowBooleanAttributes:!1,unpairedTags:[]};function i(t){return\" \"===t||\"\\t\"===t||\"\\n\"===t||\"\\r\"===t}function s(t,e){const n=e;for(;e<t.length;e++)if(\"?\"==t[e]||\" \"==t[e]){const r=t.substr(n,e-n);if(e>5&&\"xml\"===r)return g(\"InvalidXml\",\"XML declaration allowed only at the start of the document.\",y(t,e));if(\"?\"==t[e]&&\">\"==t[e+1]){e++;break}continue}return e}function a(t,e){if(t.length>e+5&&\"-\"===t[e+1]&&\"-\"===t[e+2]){for(e+=3;e<t.length;e++)if(\"-\"===t[e]&&\"-\"===t[e+1]&&\">\"===t[e+2]){e+=2;break}}else if(t.length>e+8&&\"D\"===t[e+1]&&\"O\"===t[e+2]&&\"C\"===t[e+3]&&\"T\"===t[e+4]&&\"Y\"===t[e+5]&&\"P\"===t[e+6]&&\"E\"===t[e+7]){let n=1;for(e+=8;e<t.length;e++)if(\"<\"===t[e])n++;else if(\">\"===t[e]&&(n--,0===n))break}else if(t.length>e+9&&\"[\"===t[e+1]&&\"C\"===t[e+2]&&\"D\"===t[e+3]&&\"A\"===t[e+4]&&\"T\"===t[e+5]&&\"A\"===t[e+6]&&\"[\"===t[e+7])for(e+=8;e<t.length;e++)if(\"]\"===t[e]&&\"]\"===t[e+1]&&\">\"===t[e+2]){e+=2;break}return e}e.validate=function(t,e){e=Object.assign({},o,e);const n=[];let r=!1,u=!1;\"\\ufeff\"===t[0]&&(t=t.substr(1));for(let o=0;o<t.length;o++)if(\"<\"===t[o]&&\"?\"===t[o+1]){if(o+=2,o=s(t,o),o.err)return o}else{if(\"<\"!==t[o]){if(i(t[o]))continue;return g(\"InvalidChar\",\"char '\"+t[o]+\"' is not expected.\",y(t,o))}{let c=o;if(o++,\"!\"===t[o]){o=a(t,o);continue}{let h=!1;\"/\"===t[o]&&(h=!0,o++);let d=\"\";for(;o<t.length&&\">\"!==t[o]&&\" \"!==t[o]&&\"\\t\"!==t[o]&&\"\\n\"!==t[o]&&\"\\r\"!==t[o];o++)d+=t[o];if(d=d.trim(),\"/\"===d[d.length-1]&&(d=d.substring(0,d.length-1),o--),!m(d)){let e;return e=0===d.trim().length?\"Invalid space after '<'.\":\"Tag '\"+d+\"' is an invalid name.\",g(\"InvalidTag\",e,y(t,o))}const v=l(t,o);if(!1===v)return g(\"InvalidAttr\",\"Attributes for '\"+d+\"' have open quote.\",y(t,o));let b=v.value;if(o=v.index,\"/\"===b[b.length-1]){const n=o-b.length;b=b.substring(0,b.length-1);const i=p(b,e);if(!0!==i)return g(i.err.code,i.err.msg,y(t,n+i.err.line));r=!0}else if(h){if(!v.tagClosed)return g(\"InvalidTag\",\"Closing tag '\"+d+\"' doesn't have proper closing.\",y(t,o));if(b.trim().length>0)return g(\"InvalidTag\",\"Closing tag '\"+d+\"' can't have attributes or invalid starting.\",y(t,c));if(0===n.length)return g(\"InvalidTag\",\"Closing tag '\"+d+\"' has not been opened.\",y(t,c));{const e=n.pop();if(d!==e.tagName){let n=y(t,e.tagStartPos);return g(\"InvalidTag\",\"Expected closing tag '\"+e.tagName+\"' (opened in line \"+n.line+\", col \"+n.col+\") instead of closing tag '\"+d+\"'.\",y(t,c))}0==n.length&&(u=!0)}}else{const i=p(b,e);if(!0!==i)return g(i.err.code,i.err.msg,y(t,o-b.length+i.err.line));if(!0===u)return g(\"InvalidXml\",\"Multiple possible root nodes found.\",y(t,o));-1!==e.unpairedTags.indexOf(d)||n.push({tagName:d,tagStartPos:c}),r=!0}for(o++;o<t.length;o++)if(\"<\"===t[o]){if(\"!\"===t[o+1]){o++,o=a(t,o);continue}if(\"?\"!==t[o+1])break;if(o=s(t,++o),o.err)return o}else if(\"&\"===t[o]){const e=f(t,o);if(-1==e)return g(\"InvalidChar\",\"char '&' is not expected.\",y(t,o));o=e}else if(!0===u&&!i(t[o]))return g(\"InvalidXml\",\"Extra text at the end\",y(t,o));\"<\"===t[o]&&o--}}}return r?1==n.length?g(\"InvalidTag\",\"Unclosed tag '\"+n[0].tagName+\"'.\",y(t,n[0].tagStartPos)):!(n.length>0)||g(\"InvalidXml\",\"Invalid '\"+JSON.stringify(n.map(t=>t.tagName),null,4).replace(/\\r?\\n/g,\"\")+\"' found.\",{line:1,col:1}):g(\"InvalidXml\",\"Start tag expected.\",1)};const u='\"',c=\"'\";function l(t,e){let n=\"\",r=\"\",o=!1;for(;e<t.length;e++){if(t[e]===u||t[e]===c)\"\"===r?r=t[e]:r!==t[e]||(r=\"\");else if(\">\"===t[e]&&\"\"===r){o=!0;break}n+=t[e]}return\"\"===r&&{value:n,index:e,tagClosed:o}}const h=new RegExp(\"(\\\\s*)([^\\\\s=]+)(\\\\s*=)?(\\\\s*(['\\\"])(([\\\\s\\\\S])*?)\\\\5)?\",\"g\");function p(t,e){const n=r.getAllMatches(t,h),o={};for(let t=0;t<n.length;t++){if(0===n[t][1].length)return g(\"InvalidAttr\",\"Attribute '\"+n[t][2]+\"' has no space in starting.\",v(n[t]));if(void 0!==n[t][3]&&void 0===n[t][4])return g(\"InvalidAttr\",\"Attribute '\"+n[t][2]+\"' is without value.\",v(n[t]));if(void 0===n[t][3]&&!e.allowBooleanAttributes)return g(\"InvalidAttr\",\"boolean attribute '\"+n[t][2]+\"' is not allowed.\",v(n[t]));const r=n[t][2];if(!d(r))return g(\"InvalidAttr\",\"Attribute '\"+r+\"' is an invalid name.\",v(n[t]));if(o.hasOwnProperty(r))return g(\"InvalidAttr\",\"Attribute '\"+r+\"' is repeated.\",v(n[t]));o[r]=1}return!0}function f(t,e){if(\";\"===t[++e])return-1;if(\"#\"===t[e])return function(t,e){let n=/\\d/;for(\"x\"===t[e]&&(e++,n=/[\\da-fA-F]/);e<t.length;e++){if(\";\"===t[e])return e;if(!t[e].match(n))break}return-1}(t,++e);let n=0;for(;e<t.length;e++,n++)if(!(t[e].match(/\\w/)&&n<20)){if(\";\"===t[e])break;return-1}return e}function g(t,e,n){return{err:{code:t,msg:e,line:n.line||n,col:n.col}}}function d(t){return r.isName(t)}function m(t){return r.isName(t)}function y(t,e){const n=t.substring(0,e).split(/\\r?\\n/);return{line:n.length,col:n[n.length-1].length+1}}function v(t){return t.startIndex+t[1].length}}},e={};function n(r){var o=e[r];if(void 0!==o)return o.exports;var i=e[r]={id:r,loaded:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.nmd=t=>(t.paths=[],t.children||(t.children=[]),t);var r=n(715),o=n.n(r);function i(t){if(!s(t))throw new Error(\"Parameter was not an error\")}function s(t){return!!t&&\"object\"==typeof t&&\"[object Error]\"===(e=t,Object.prototype.toString.call(e))||t instanceof Error;var e}class a extends Error{constructor(t,e){const n=[...arguments],{options:r,shortMessage:o}=function(t){let e,n=\"\";if(0===t.length)e={};else if(s(t[0]))e={cause:t[0]},n=t.slice(1).join(\" \")||\"\";else if(t[0]&&\"object\"==typeof t[0])e=Object.assign({},t[0]),n=t.slice(1).join(\" \")||\"\";else{if(\"string\"!=typeof t[0])throw new Error(\"Invalid arguments passed to Layerr\");e={},n=n=t.join(\" \")||\"\"}return{options:e,shortMessage:n}}(n);let i=o;if(r.cause&&(i=`${i}: ${r.cause.message}`),super(i),this.message=i,r.name&&\"string\"==typeof r.name?this.name=r.name:this.name=\"Layerr\",r.cause&&Object.defineProperty(this,\"_cause\",{value:r.cause}),Object.defineProperty(this,\"_info\",{value:{}}),r.info&&\"object\"==typeof r.info&&Object.assign(this._info,r.info),Error.captureStackTrace){const t=r.constructorOpt||this.constructor;Error.captureStackTrace(this,t)}}static cause(t){return i(t),t._cause&&s(t._cause)?t._cause:null}static fullStack(t){i(t);const e=a.cause(t);return e?`${t.stack}\\ncaused by: ${a.fullStack(e)}`:t.stack??\"\"}static info(t){i(t);const e={},n=a.cause(t);return n&&Object.assign(e,a.info(n)),t._info&&Object.assign(e,t._info),e}toString(){let t=this.name||this.constructor.name||this.constructor.prototype.name;return this.message&&(t=`${t}: ${this.message}`),t}}var u=n(699),c=n.n(u);const l=\"__PATH_SEPARATOR_POSIX__\",h=\"__PATH_SEPARATOR_WINDOWS__\";function p(t){try{const e=t.replace(/\\//g,l).replace(/\\\\\\\\/g,h);return encodeURIComponent(e).split(h).join(\"\\\\\\\\\").split(l).join(\"/\")}catch(t){throw new a(t,\"Failed encoding path\")}}function f(t){return t.startsWith(\"/\")?t:\"/\"+t}function g(t){let e=t;return\"/\"!==e[0]&&(e=\"/\"+e),/^.+\\/$/.test(e)&&(e=e.substr(0,e.length-1)),e}function d(t){let e=new(o())(t).pathname;return e.length<=0&&(e=\"/\"),g(e)}function m(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];return function(){return function(t){var e=[];if(0===t.length)return\"\";if(\"string\"!=typeof t[0])throw new TypeError(\"Url must be a string. Received \"+t[0]);if(t[0].match(/^[^/:]+:\\/*$/)&&t.length>1){var n=t.shift();t[0]=n+t[0]}t[0].match(/^file:\\/\\/\\//)?t[0]=t[0].replace(/^([^/:]+):\\/*/,\"$1:///\"):t[0]=t[0].replace(/^([^/:]+):\\/*/,\"$1://\");for(var r=0;r<t.length;r++){var o=t[r];if(\"string\"!=typeof o)throw new TypeError(\"Url must be a string. Received \"+o);\"\"!==o&&(r>0&&(o=o.replace(/^[\\/]+/,\"\")),o=r<t.length-1?o.replace(/[\\/]+$/,\"\"):o.replace(/[\\/]+$/,\"/\"),e.push(o))}var i=e.join(\"/\"),s=(i=i.replace(/\\/(\\?|&|#[^!])/g,\"$1\")).split(\"?\");return s.shift()+(s.length>0?\"?\":\"\")+s.join(\"&\")}(\"object\"==typeof arguments[0]?arguments[0]:[].slice.call(arguments))}(e.reduce((t,e,n)=>((0===n||\"/\"!==e||\"/\"===e&&\"/\"!==t[t.length-1])&&t.push(e),t),[]))}var y=n(221),v=n.n(y);function b(t,e){const n=t.url.replace(\"//\",\"\"),r=-1==n.indexOf(\"/\")?\"/\":n.slice(n.indexOf(\"/\")),o=t.method?t.method.toUpperCase():\"GET\",i=!!/(^|,)\\s*auth\\s*($|,)/.test(e.qop)&&\"auth\",s=`00000000${e.nc}`.slice(-8),a=function(t,e,n,r,o,i,s){const a=s||v()(`${e}:${n}:${r}`);return t&&\"md5-sess\"===t.toLowerCase()?v()(`${a}:${o}:${i}`):a}(e.algorithm,e.username,e.realm,e.password,e.nonce,e.cnonce,e.ha1),u=v()(`${o}:${r}`),c=i?v()(`${a}:${e.nonce}:${s}:${e.cnonce}:${i}:${u}`):v()(`${a}:${e.nonce}:${u}`),l={username:e.username,realm:e.realm,nonce:e.nonce,uri:r,qop:i,response:c,nc:s,cnonce:e.cnonce,algorithm:e.algorithm,opaque:e.opaque},h=[];for(const t in l)l[t]&&(\"qop\"===t||\"nc\"===t||\"algorithm\"===t?h.push(`${t}=${l[t]}`):h.push(`${t}=\"${l[t]}\"`));return`Digest ${h.join(\", \")}`}function w(t){return\"digest\"===(t.headers&&t.headers.get(\"www-authenticate\")||\"\").split(/\\s/)[0].toLowerCase()}var x=n(802),N=n.n(x);function P(t){return N().decode(t)}function A(t,e){var n;return`Basic ${n=`${t}:${e}`,N().encode(n)}`}const E=\"undefined\"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:\"undefined\"!=typeof window?window:globalThis,O=(E.fetch.bind(E),E.Headers,E.Request),T=E.Response;let j=function(t){return t.Auto=\"auto\",t.Digest=\"digest\",t.None=\"none\",t.Password=\"password\",t.Token=\"token\",t}({}),S=function(t){return t.DataTypeNoLength=\"data-type-no-length\",t.InvalidAuthType=\"invalid-auth-type\",t.InvalidOutputFormat=\"invalid-output-format\",t.LinkUnsupportedAuthType=\"link-unsupported-auth\",t.InvalidUpdateRange=\"invalid-update-range\",t.NotSupported=\"not-supported\",t}({});function $(t,e,n,r,o){switch(t.authType){case j.Auto:e&&n&&(t.headers.Authorization=A(e,n));break;case j.Digest:t.digest=function(t,e,n){return{username:t,password:e,ha1:n,nc:0,algorithm:\"md5\",hasDigestAuth:!1}}(e,n,o);break;case j.None:break;case j.Password:t.headers.Authorization=A(e,n);break;case j.Token:t.headers.Authorization=`${(i=r).token_type} ${i.access_token}`;break;default:throw new a({info:{code:S.InvalidAuthType}},`Invalid auth type: ${t.authType}`)}var i}function C(t,e){const n=new URL(t,window.location.origin).origin;return window.location.origin===n?(console.log(\"[WebDav] Libcurl will not be used since this request is local\"),window.fetch(t,{mode:\"cors\",credentials:\"include\",...e})):window.parent.tb.libcurl.fetch(t,{...e})}n(345),n(800);const I=\"@@HOTPATCHER\",k=()=>{};function R(t){return{original:t,methods:[t],final:!1}}class L{constructor(){this._configuration={registry:{},getEmptyAction:\"null\"},this.__type__=I}get configuration(){return this._configuration}get getEmptyAction(){return this.configuration.getEmptyAction}set getEmptyAction(t){this.configuration.getEmptyAction=t}control(t){let e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if(!t||t.__type__!==I)throw new Error(\"Failed taking control of target HotPatcher instance: Invalid type or object\");return Object.keys(t.configuration.registry).forEach(n=>{this.configuration.registry.hasOwnProperty(n)?e&&(this.configuration.registry[n]=Object.assign({},t.configuration.registry[n])):this.configuration.registry[n]=Object.assign({},t.configuration.registry[n])}),t._configuration=this.configuration,this}execute(t){const e=this.get(t)||k;for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o<n;o++)r[o-1]=arguments[o];return e(...r)}get(t){const e=this.configuration.registry[t];if(!e)switch(this.getEmptyAction){case\"null\":return null;case\"throw\":throw new Error(`Failed handling method request: No method provided for override: ${t}`);default:throw new Error(`Failed handling request which resulted in an empty method: Invalid empty-action specified: ${this.getEmptyAction}`)}return function(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];if(0===e.length)throw new Error(\"Failed creating sequence: No functions provided\");return function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];let o=n;const i=this;for(;e.length>0;)o=[e.shift().apply(i,o)];return o[0]}}(...e.methods)}isPatched(t){return!!this.configuration.registry[t]}patch(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{chain:r=!1}=n;if(this.configuration.registry[t]&&this.configuration.registry[t].final)throw new Error(`Failed patching '${t}': Method marked as being final`);if(\"function\"!=typeof e)throw new Error(`Failed patching '${t}': Provided method is not a function`);if(r)this.configuration.registry[t]?this.configuration.registry[t].methods.push(e):this.configuration.registry[t]=R(e);else if(this.isPatched(t)){const{original:n}=this.configuration.registry[t];this.configuration.registry[t]=Object.assign(R(e),{original:n})}else this.configuration.registry[t]=R(e);return this}patchInline(t,e){this.isPatched(t)||this.patch(t,e);for(var n=arguments.length,r=new Array(n>2?n-2:0),o=2;o<n;o++)r[o-2]=arguments[o];return this.execute(t,...r)}plugin(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),r=1;r<e;r++)n[r-1]=arguments[r];return n.forEach(e=>{this.patch(t,e,{chain:!0})}),this}restore(t){if(!this.isPatched(t))throw new Error(`Failed restoring method: No method present for key: ${t}`);if(\"function\"!=typeof this.configuration.registry[t].original)throw new Error(`Failed restoring method: Original method not found or of invalid type for key: ${t}`);return this.configuration.registry[t].methods=[this.configuration.registry[t].original],this}setFinal(t){if(!this.configuration.registry.hasOwnProperty(t))throw new Error(`Failed marking '${t}' as final: No method found for key`);return this.configuration.registry[t].final=!0,this}}let _=null;function M(){return _||(_=new L),_}function U(t){return function(t){if(\"object\"!=typeof t||null===t||\"[object Object]\"!=Object.prototype.toString.call(t))return!1;if(null===Object.getPrototypeOf(t))return!0;let e=t;for(;null!==Object.getPrototypeOf(e);)e=Object.getPrototypeOf(e);return Object.getPrototypeOf(t)===e}(t)?Object.assign({},t):Object.setPrototypeOf(Object.assign({},t),Object.getPrototypeOf(t))}function F(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];let r=null,o=[...e];for(;o.length>0;){const t=o.shift();r=r?D(r,t):U(t)}return r}function D(t,e){const n=U(t);return Object.keys(e).forEach(t=>{n.hasOwnProperty(t)?Array.isArray(e[t])?n[t]=Array.isArray(n[t])?[...n[t],...e[t]]:[...e[t]]:\"object\"==typeof e[t]&&e[t]?n[t]=\"object\"==typeof n[t]&&n[t]?D(n[t],e[t]):U(e[t]):n[t]=e[t]:n[t]=e[t]}),n}function B(t){const e={};for(const n of t.keys())e[n]=t.get(n);return e}function W(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];if(0===e.length)return{};const r={};return e.reduce((t,e)=>(Object.keys(e).forEach(n=>{const o=n.toLowerCase();r.hasOwnProperty(o)?t[r[o]]=e[n]:(r[o]=n,t[n]=e[n])}),t),{})}n(805);const V=\"function\"==typeof ArrayBuffer,{toString:z}=Object.prototype;function G(t){return V&&(t instanceof ArrayBuffer||\"[object ArrayBuffer]\"===z.call(t))}function q(t){return null!=t&&null!=t.constructor&&\"function\"==typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}function H(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}function Z(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const X=H(function(t){const e=t._digest;return delete t._digest,e.hasDigestAuth&&(t=F(t,{headers:{Authorization:b(t,e)}})),Z(Q(t),function(n){let r=!1;return o=function(t){return r?t:n},(i=function(){if(401==n.status)return e.hasDigestAuth=function(t,e){if(!w(t))return!1;const n=/([a-z0-9_-]+)=(?:\"([^\"]+)\"|([a-z0-9_-]+))/gi;for(;;){const r=t.headers&&t.headers.get(\"www-authenticate\")||\"\",o=n.exec(r);if(!o)break;e[o[1]]=o[2]||o[3]}return e.nc+=1,e.cnonce=function(){let t=\"\";for(let e=0;e<32;++e)t=`${t}${\"abcdef0123456789\"[Math.floor(16*Math.random())]}`;return t}(),!0}(n,e),function(){if(e.hasDigestAuth)return Z(Q(t=F(t,{headers:{Authorization:b(t,e)}})),function(t){return 401==t.status?e.hasDigestAuth=!1:e.nc++,r=!0,t})}();e.nc++}())&&i.then?i.then(o):o(i);var o,i})}),Y=H(function(t,e){return Z(Q(t),function(n){return n.ok?(e.authType=j.Password,n):401==n.status&&w(n)?(e.authType=j.Digest,$(e,e.username,e.password,void 0,void 0),t._digest=e.digest,X(t)):n})}),J=H(function(t,e){return e.authType===j.Auto?Y(t,e):t._digest?X(t):Q(t)});function K(t,e,n){const r=U(t);return r.headers=W(e.headers,r.headers||{},n.headers||{}),void 0!==n.data&&(r.data=n.data),n.signal&&(r.signal=n.signal),e.httpAgent&&(r.httpAgent=e.httpAgent),e.httpsAgent&&(r.httpsAgent=e.httpsAgent),e.digest&&(r._digest=e.digest),\"boolean\"==typeof e.withCredentials&&(r.withCredentials=e.withCredentials),r}function Q(t){const e=M();return e.patchInline(\"request\",t=>e.patchInline(\"fetch\",C,t.url,function(t){let e={};const n={method:t.method};if(t.headers&&(e=W(e,t.headers)),void 0!==t.data){const[r,o]=function(t){if(\"string\"==typeof t)return[t,{}];if(q(t))return[new Uint8Array(t),{}];if(G(t))return[t,{}];if(t&&\"object\"==typeof t)return[JSON.stringify(t),{\"content-type\":\"application/json\"}];throw new Error(\"Unable to convert request body: Unexpected body type: \"+typeof t)}(t.data);n.body=r,e=W(e,o)}return t.signal&&(n.signal=t.signal),t.withCredentials&&(n.credentials=\"include\"),n.headers=e,n}(t)),t)}var tt=n(574);const et=t=>{if(\"string\"!=typeof t)throw new TypeError(\"invalid pattern\");if(t.length>65536)throw new TypeError(\"pattern is too long\")},nt={\"[:alnum:]\":[\"\\\\p{L}\\\\p{Nl}\\\\p{Nd}\",!0],\"[:alpha:]\":[\"\\\\p{L}\\\\p{Nl}\",!0],\"[:ascii:]\":[\"\\\\x00-\\\\x7f\",!1],\"[:blank:]\":[\"\\\\p{Zs}\\\\t\",!0],\"[:cntrl:]\":[\"\\\\p{Cc}\",!0],\"[:digit:]\":[\"\\\\p{Nd}\",!0],\"[:graph:]\":[\"\\\\p{Z}\\\\p{C}\",!0,!0],\"[:lower:]\":[\"\\\\p{Ll}\",!0],\"[:print:]\":[\"\\\\p{C}\",!0],\"[:punct:]\":[\"\\\\p{P}\",!0],\"[:space:]\":[\"\\\\p{Z}\\\\t\\\\r\\\\n\\\\v\\\\f\",!0],\"[:upper:]\":[\"\\\\p{Lu}\",!0],\"[:word:]\":[\"\\\\p{L}\\\\p{Nl}\\\\p{Nd}\\\\p{Pc}\",!0],\"[:xdigit:]\":[\"A-Fa-f0-9\",!1]},rt=t=>t.replace(/[[\\]\\\\-]/g,\"\\\\$&\"),ot=t=>t.join(\"\"),it=(t,e)=>{const n=e;if(\"[\"!==t.charAt(n))throw new Error(\"not in a brace expression\");const r=[],o=[];let i=n+1,s=!1,a=!1,u=!1,c=!1,l=n,h=\"\";t:for(;i<t.length;){const e=t.charAt(i);if(\"!\"!==e&&\"^\"!==e||i!==n+1){if(\"]\"===e&&s&&!u){l=i+1;break}if(s=!0,\"\\\\\"!==e||u){if(\"[\"===e&&!u)for(const[e,[s,u,c]]of Object.entries(nt))if(t.startsWith(e,i)){if(h)return[\"$.\",!1,t.length-n,!0];i+=e.length,c?o.push(s):r.push(s),a=a||u;continue t}u=!1,h?(e>h?r.push(rt(h)+\"-\"+rt(e)):e===h&&r.push(rt(e)),h=\"\",i++):t.startsWith(\"-]\",i+1)?(r.push(rt(e+\"-\")),i+=2):t.startsWith(\"-\",i+1)?(h=e,i+=2):(r.push(rt(e)),i++)}else u=!0,i++}else c=!0,i++}if(l<i)return[\"\",!1,0,!1];if(!r.length&&!o.length)return[\"$.\",!1,t.length-n,!0];if(0===o.length&&1===r.length&&/^\\\\?.$/.test(r[0])&&!c){return[(p=2===r[0].length?r[0].slice(-1):r[0],p.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g,\"\\\\$&\")),!1,l-n,!1]}var p;const f=\"[\"+(c?\"^\":\"\")+ot(r)+\"]\",g=\"[\"+(c?\"\":\"^\")+ot(o)+\"]\";return[r.length&&o.length?\"(\"+f+\"|\"+g+\")\":r.length?f:g,a,l-n,!0]},st=function(t){let{windowsPathsNoEscape:e=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e?t.replace(/\\[([^\\/\\\\])\\]/g,\"$1\"):t.replace(/((?!\\\\).|^)\\[([^\\/\\\\])\\]/g,\"$1$2\").replace(/\\\\([^\\/])/g,\"$1\")},at=new Set([\"!\",\"?\",\"+\",\"*\",\"@\"]),ut=t=>at.has(t),ct=\"(?!\\\\.)\",lt=new Set([\"[\",\".\"]),ht=new Set([\"..\",\".\"]),pt=new Set(\"().*{}+?[]^$\\\\!\"),ft=t=>t.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g,\"\\\\$&\"),gt=\"[^/]\",dt=gt+\"*?\",mt=gt+\"+?\";class yt{type;#t;#e;#n=!1;#r=[];#o;#i;#s;#a=!1;#u;#c;#l=!1;constructor(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};this.type=t,t&&(this.#e=!0),this.#o=e,this.#t=this.#o?this.#o.#t:this,this.#u=this.#t===this?n:this.#t.#u,this.#s=this.#t===this?[]:this.#t.#s,\"!\"!==t||this.#t.#a||this.#s.push(this),this.#i=this.#o?this.#o.#r.length:0}get hasMagic(){if(void 0!==this.#e)return this.#e;for(const t of this.#r)if(\"string\"!=typeof t&&(t.type||t.hasMagic))return this.#e=!0;return this.#e}toString(){return void 0!==this.#c?this.#c:this.type?this.#c=this.type+\"(\"+this.#r.map(t=>String(t)).join(\"|\")+\")\":this.#c=this.#r.map(t=>String(t)).join(\"\")}#h(){if(this!==this.#t)throw new Error(\"should only call on root\");if(this.#a)return this;let t;for(this.toString(),this.#a=!0;t=this.#s.pop();){if(\"!\"!==t.type)continue;let e=t,n=e.#o;for(;n;){for(let r=e.#i+1;!n.type&&r<n.#r.length;r++)for(const e of t.#r){if(\"string\"==typeof e)throw new Error(\"string part in extglob AST??\");e.copyIn(n.#r[r])}e=n,n=e.#o}}return this}push(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];for(const t of e)if(\"\"!==t){if(\"string\"!=typeof t&&!(t instanceof yt&&t.#o===this))throw new Error(\"invalid part: \"+t);this.#r.push(t)}}toJSON(){const t=null===this.type?this.#r.slice().map(t=>\"string\"==typeof t?t:t.toJSON()):[this.type,...this.#r.map(t=>t.toJSON())];return this.isStart()&&!this.type&&t.unshift([]),this.isEnd()&&(this===this.#t||this.#t.#a&&\"!\"===this.#o?.type)&&t.push({}),t}isStart(){if(this.#t===this)return!0;if(!this.#o?.isStart())return!1;if(0===this.#i)return!0;const t=this.#o;for(let e=0;e<this.#i;e++){const n=t.#r[e];if(!(n instanceof yt&&\"!\"===n.type))return!1}return!0}isEnd(){if(this.#t===this)return!0;if(\"!\"===this.#o?.type)return!0;if(!this.#o?.isEnd())return!1;if(!this.type)return this.#o?.isEnd();const t=this.#o?this.#o.#r.length:0;return this.#i===t-1}copyIn(t){\"string\"==typeof t?this.push(t):this.push(t.clone(this))}clone(t){const e=new yt(this.type,t);for(const t of this.#r)e.copyIn(t);return e}static#p(t,e,n,r){let o=!1,i=!1,s=-1,a=!1;if(null===e.type){let u=n,c=\"\";for(;u<t.length;){const n=t.charAt(u++);if(o||\"\\\\\"===n)o=!o,c+=n;else if(i)u===s+1?\"^\"!==n&&\"!\"!==n||(a=!0):\"]\"!==n||u===s+2&&a||(i=!1),c+=n;else if(\"[\"!==n){if(!r.noext&&ut(n)&&\"(\"===t.charAt(u)){e.push(c),c=\"\";const o=new yt(n,e);u=yt.#p(t,o,u,r),e.push(o);continue}c+=n}else i=!0,s=u,a=!1,c+=n}return e.push(c),u}let u=n+1,c=new yt(null,e);const l=[];let h=\"\";for(;u<t.length;){const n=t.charAt(u++);if(o||\"\\\\\"===n)o=!o,h+=n;else if(i)u===s+1?\"^\"!==n&&\"!\"!==n||(a=!0):\"]\"!==n||u===s+2&&a||(i=!1),h+=n;else if(\"[\"!==n){if(ut(n)&&\"(\"===t.charAt(u)){c.push(h),h=\"\";const e=new yt(n,c);c.push(e),u=yt.#p(t,e,u,r);continue}if(\"|\"!==n){if(\")\"===n)return\"\"===h&&0===e.#r.length&&(e.#l=!0),c.push(h),h=\"\",e.push(...l,c),u;h+=n}else c.push(h),h=\"\",l.push(c),c=new yt(null,e)}else i=!0,s=u,a=!1,h+=n}return e.type=null,e.#e=void 0,e.#r=[t.substring(n-1)],u}static fromGlob(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=new yt(null,void 0,e);return yt.#p(t,n,0,e),n}toMMPattern(){if(this!==this.#t)return this.#t.toMMPattern();const t=this.toString(),[e,n,r,o]=this.toRegExpSource();if(!(r||this.#e||this.#u.nocase&&!this.#u.nocaseMagicOnly&&t.toUpperCase()!==t.toLowerCase()))return n;const i=(this.#u.nocase?\"i\":\"\")+(o?\"u\":\"\");return Object.assign(new RegExp(`^${e}$`,i),{_src:e,_glob:t})}get options(){return this.#u}toRegExpSource(t){const e=t??!!this.#u.dot;if(this.#t===this&&this.#h(),!this.type){const n=this.isStart()&&this.isEnd(),r=this.#r.map(e=>{const[r,o,i,s]=\"string\"==typeof e?yt.#f(e,this.#e,n):e.toRegExpSource(t);return this.#e=this.#e||i,this.#n=this.#n||s,r}).join(\"\");let o=\"\";if(this.isStart()&&\"string\"==typeof this.#r[0]&&(1!==this.#r.length||!ht.has(this.#r[0]))){const n=lt,i=e&&n.has(r.charAt(0))||r.startsWith(\"\\\\.\")&&n.has(r.charAt(2))||r.startsWith(\"\\\\.\\\\.\")&&n.has(r.charAt(4)),s=!e&&!t&&n.has(r.charAt(0));o=i?\"(?!(?:^|/)\\\\.\\\\.?(?:$|/))\":s?ct:\"\"}let i=\"\";return this.isEnd()&&this.#t.#a&&\"!\"===this.#o?.type&&(i=\"(?:$|\\\\/)\"),[o+r+i,st(r),this.#e=!!this.#e,this.#n]}const n=\"*\"===this.type||\"+\"===this.type,r=\"!\"===this.type?\"(?:(?!(?:\":\"(?:\";let o=this.#g(e);if(this.isStart()&&this.isEnd()&&!o&&\"!\"!==this.type){const t=this.toString();return this.#r=[t],this.type=null,this.#e=void 0,[t,st(this.toString()),!1,!1]}let i=!n||t||e?\"\":this.#g(!0);i===o&&(i=\"\"),i&&(o=`(?:${o})(?:${i})*?`);let s=\"\";return s=\"!\"===this.type&&this.#l?(this.isStart()&&!e?ct:\"\")+mt:r+o+(\"!\"===this.type?\"))\"+(!this.isStart()||e||t?\"\":ct)+dt+\")\":\"@\"===this.type?\")\":\"?\"===this.type?\")?\":\"+\"===this.type&&i?\")\":\"*\"===this.type&&i?\")?\":`)${this.type}`),[s,st(o),this.#e=!!this.#e,this.#n]}#g(t){return this.#r.map(e=>{if(\"string\"==typeof e)throw new Error(\"string type in extglob ast??\");const[n,r,o,i]=e.toRegExpSource(t);return this.#n=this.#n||i,n}).filter(t=>!(this.isStart()&&this.isEnd()&&!t)).join(\"|\")}static#f(t,e){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=!1,o=\"\",i=!1;for(let s=0;s<t.length;s++){const a=t.charAt(s);if(r)r=!1,o+=(pt.has(a)?\"\\\\\":\"\")+a;else if(\"\\\\\"!==a){if(\"[\"===a){const[n,r,a,u]=it(t,s);if(a){o+=n,i=i||r,s+=a-1,e=e||u;continue}}\"*\"!==a?\"?\"!==a?o+=ft(a):(o+=gt,e=!0):(o+=n&&\"*\"===t?mt:dt,e=!0)}else s===t.length-1?o+=\"\\\\\\\\\":r=!0}return[o,st(t),!!e,i]}}const vt=function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return et(e),!(!n.nocomment&&\"#\"===e.charAt(0))&&new zt(e,n).match(t)},bt=/^\\*+([^+@!?\\*\\[\\(]*)$/,wt=t=>e=>!e.startsWith(\".\")&&e.endsWith(t),xt=t=>e=>e.endsWith(t),Nt=t=>(t=t.toLowerCase(),e=>!e.startsWith(\".\")&&e.toLowerCase().endsWith(t)),Pt=t=>(t=t.toLowerCase(),e=>e.toLowerCase().endsWith(t)),At=/^\\*+\\.\\*+$/,Et=t=>!t.startsWith(\".\")&&t.includes(\".\"),Ot=t=>\".\"!==t&&\"..\"!==t&&t.includes(\".\"),Tt=/^\\.\\*+$/,jt=t=>\".\"!==t&&\"..\"!==t&&t.startsWith(\".\"),St=/^\\*+$/,$t=t=>0!==t.length&&!t.startsWith(\".\"),Ct=t=>0!==t.length&&\".\"!==t&&\"..\"!==t,It=/^\\?+([^+@!?\\*\\[\\(]*)?$/,kt=t=>{let[e,n=\"\"]=t;const r=Mt([e]);return n?(n=n.toLowerCase(),t=>r(t)&&t.toLowerCase().endsWith(n)):r},Rt=t=>{let[e,n=\"\"]=t;const r=Ut([e]);return n?(n=n.toLowerCase(),t=>r(t)&&t.toLowerCase().endsWith(n)):r},Lt=t=>{let[e,n=\"\"]=t;const r=Ut([e]);return n?t=>r(t)&&t.endsWith(n):r},_t=t=>{let[e,n=\"\"]=t;const r=Mt([e]);return n?t=>r(t)&&t.endsWith(n):r},Mt=t=>{let[e]=t;const n=e.length;return t=>t.length===n&&!t.startsWith(\".\")},Ut=t=>{let[e]=t;const n=e.length;return t=>t.length===n&&\".\"!==t&&\"..\"!==t},Ft=\"object\"==typeof process&&process?\"object\"==typeof process.env&&process.env&&process.env.__MINIMATCH_TESTING_PLATFORM__||process.platform:\"posix\";vt.sep=\"win32\"===Ft?\"\\\\\":\"/\";const Dt=Symbol(\"globstar **\");vt.GLOBSTAR=Dt,vt.filter=function(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return n=>vt(n,t,e)};const Bt=function(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object.assign({},t,e)};vt.defaults=t=>{if(!t||\"object\"!=typeof t||!Object.keys(t).length)return vt;const e=vt;return Object.assign(function(n,r){return e(n,r,Bt(t,arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}))},{Minimatch:class extends e.Minimatch{constructor(e){super(e,Bt(t,arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}))}static defaults(n){return e.defaults(Bt(t,n)).Minimatch}},AST:class extends e.AST{constructor(e,n){super(e,n,Bt(t,arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}))}static fromGlob(n){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.AST.fromGlob(n,Bt(t,r))}},unescape:function(n){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.unescape(n,Bt(t,r))},escape:function(n){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.escape(n,Bt(t,r))},filter:function(n){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.filter(n,Bt(t,r))},defaults:n=>e.defaults(Bt(t,n)),makeRe:function(n){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.makeRe(n,Bt(t,r))},braceExpand:function(n){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.braceExpand(n,Bt(t,r))},match:function(n,r){let o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.match(n,r,Bt(t,o))},sep:e.sep,GLOBSTAR:Dt})};const Wt=function(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return et(t),e.nobrace||!/\\{(?:(?!\\{).)*\\}/.test(t)?[t]:tt(t)};vt.braceExpand=Wt,vt.makeRe=function(t){return new zt(t,arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).makeRe()},vt.match=function(t,e){const n=new zt(e,arguments.length>2&&void 0!==arguments[2]?arguments[2]:{});return t=t.filter(t=>n.match(t)),n.options.nonull&&!t.length&&t.push(e),t};const Vt=/[?*]|[+@!]\\(.*?\\)|\\[|\\]/;class zt{options;set;pattern;windowsPathsNoEscape;nonegate;negate;comment;empty;preserveMultipleSlashes;partial;globSet;globParts;nocase;isWindows;platform;windowsNoMagicRoot;regexp;constructor(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};et(t),e=e||{},this.options=e,this.pattern=t,this.platform=e.platform||Ft,this.isWindows=\"win32\"===this.platform,this.windowsPathsNoEscape=!!e.windowsPathsNoEscape||!1===e.allowWindowsEscape,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\\\/g,\"/\")),this.preserveMultipleSlashes=!!e.preserveMultipleSlashes,this.regexp=null,this.negate=!1,this.nonegate=!!e.nonegate,this.comment=!1,this.empty=!1,this.partial=!!e.partial,this.nocase=!!this.options.nocase,this.windowsNoMagicRoot=void 0!==e.windowsNoMagicRoot?e.windowsNoMagicRoot:!(!this.isWindows||!this.nocase),this.globSet=[],this.globParts=[],this.set=[],this.make()}hasMagic(){if(this.options.magicalBraces&&this.set.length>1)return!0;for(const t of this.set)for(const e of t)if(\"string\"!=typeof e)return!0;return!1}debug(){}make(){const t=this.pattern,e=this.options;if(!e.nocomment&&\"#\"===t.charAt(0))return void(this.comment=!0);if(!t)return void(this.empty=!0);this.parseNegate(),this.globSet=[...new Set(this.braceExpand())],e.debug&&(this.debug=function(){return console.error(...arguments)}),this.debug(this.pattern,this.globSet);const n=this.globSet.map(t=>this.slashSplit(t));this.globParts=this.preprocess(n),this.debug(this.pattern,this.globParts);let r=this.globParts.map((t,e,n)=>{if(this.isWindows&&this.windowsNoMagicRoot){const e=!(\"\"!==t[0]||\"\"!==t[1]||\"?\"!==t[2]&&Vt.test(t[2])||Vt.test(t[3])),n=/^[a-z]:/i.test(t[0]);if(e)return[...t.slice(0,4),...t.slice(4).map(t=>this.parse(t))];if(n)return[t[0],...t.slice(1).map(t=>this.parse(t))]}return t.map(t=>this.parse(t))});if(this.debug(this.pattern,r),this.set=r.filter(t=>-1===t.indexOf(!1)),this.isWindows)for(let t=0;t<this.set.length;t++){const e=this.set[t];\"\"===e[0]&&\"\"===e[1]&&\"?\"===this.globParts[t][2]&&\"string\"==typeof e[3]&&/^[a-z]:$/i.test(e[3])&&(e[2]=\"?\")}this.debug(this.pattern,this.set)}preprocess(t){if(this.options.noglobstar)for(let e=0;e<t.length;e++)for(let n=0;n<t[e].length;n++)\"**\"===t[e][n]&&(t[e][n]=\"*\");const{optimizationLevel:e=1}=this.options;return e>=2?(t=this.firstPhasePreProcess(t),t=this.secondPhasePreProcess(t)):t=e>=1?this.levelOneOptimize(t):this.adjascentGlobstarOptimize(t),t}adjascentGlobstarOptimize(t){return t.map(t=>{let e=-1;for(;-1!==(e=t.indexOf(\"**\",e+1));){let n=e;for(;\"**\"===t[n+1];)n++;n!==e&&t.splice(e,n-e)}return t})}levelOneOptimize(t){return t.map(t=>0===(t=t.reduce((t,e)=>{const n=t[t.length-1];return\"**\"===e&&\"**\"===n?t:\"..\"===e&&n&&\"..\"!==n&&\".\"!==n&&\"**\"!==n?(t.pop(),t):(t.push(e),t)},[])).length?[\"\"]:t)}levelTwoFileOptimize(t){Array.isArray(t)||(t=this.slashSplit(t));let e=!1;do{if(e=!1,!this.preserveMultipleSlashes){for(let n=1;n<t.length-1;n++){const r=t[n];1===n&&\"\"===r&&\"\"===t[0]||\".\"!==r&&\"\"!==r||(e=!0,t.splice(n,1),n--)}\".\"!==t[0]||2!==t.length||\".\"!==t[1]&&\"\"!==t[1]||(e=!0,t.pop())}let n=0;for(;-1!==(n=t.indexOf(\"..\",n+1));){const r=t[n-1];r&&\".\"!==r&&\"..\"!==r&&\"**\"!==r&&(e=!0,t.splice(n-1,2),n-=2)}}while(e);return 0===t.length?[\"\"]:t}firstPhasePreProcess(t){let e=!1;do{e=!1;for(let n of t){let r=-1;for(;-1!==(r=n.indexOf(\"**\",r+1));){let o=r;for(;\"**\"===n[o+1];)o++;o>r&&n.splice(r+1,o-r);let i=n[r+1];const s=n[r+2],a=n[r+3];if(\"..\"!==i)continue;if(!s||\".\"===s||\"..\"===s||!a||\".\"===a||\"..\"===a)continue;e=!0,n.splice(r,1);const u=n.slice(0);u[r]=\"**\",t.push(u),r--}if(!this.preserveMultipleSlashes){for(let t=1;t<n.length-1;t++){const r=n[t];1===t&&\"\"===r&&\"\"===n[0]||\".\"!==r&&\"\"!==r||(e=!0,n.splice(t,1),t--)}\".\"!==n[0]||2!==n.length||\".\"!==n[1]&&\"\"!==n[1]||(e=!0,n.pop())}let o=0;for(;-1!==(o=n.indexOf(\"..\",o+1));){const t=n[o-1];if(t&&\".\"!==t&&\"..\"!==t&&\"**\"!==t){e=!0;const t=1===o&&\"**\"===n[o+1]?[\".\"]:[];n.splice(o-1,2,...t),0===n.length&&n.push(\"\"),o-=2}}}}while(e);return t}secondPhasePreProcess(t){for(let e=0;e<t.length-1;e++)for(let n=e+1;n<t.length;n++){const r=this.partsMatch(t[e],t[n],!this.preserveMultipleSlashes);if(r){t[e]=[],t[n]=r;break}}return t.filter(t=>t.length)}partsMatch(t,e){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=0,o=0,i=[],s=\"\";for(;r<t.length&&o<e.length;)if(t[r]===e[o])i.push(\"b\"===s?e[o]:t[r]),r++,o++;else if(n&&\"**\"===t[r]&&e[o]===t[r+1])i.push(t[r]),r++;else if(n&&\"**\"===e[o]&&t[r]===e[o+1])i.push(e[o]),o++;else if(\"*\"!==t[r]||!e[o]||!this.options.dot&&e[o].startsWith(\".\")||\"**\"===e[o]){if(\"*\"!==e[o]||!t[r]||!this.options.dot&&t[r].startsWith(\".\")||\"**\"===t[r])return!1;if(\"a\"===s)return!1;s=\"b\",i.push(e[o]),r++,o++}else{if(\"b\"===s)return!1;s=\"a\",i.push(t[r]),r++,o++}return t.length===e.length&&i}parseNegate(){if(this.nonegate)return;const t=this.pattern;let e=!1,n=0;for(let r=0;r<t.length&&\"!\"===t.charAt(r);r++)e=!e,n++;n&&(this.pattern=t.slice(n)),this.negate=e}matchOne(t,e){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const r=this.options;if(this.isWindows){const n=\"string\"==typeof t[0]&&/^[a-z]:$/i.test(t[0]),r=!n&&\"\"===t[0]&&\"\"===t[1]&&\"?\"===t[2]&&/^[a-z]:$/i.test(t[3]),o=\"string\"==typeof e[0]&&/^[a-z]:$/i.test(e[0]),i=r?3:n?0:void 0,s=!o&&\"\"===e[0]&&\"\"===e[1]&&\"?\"===e[2]&&\"string\"==typeof e[3]&&/^[a-z]:$/i.test(e[3])?3:o?0:void 0;if(\"number\"==typeof i&&\"number\"==typeof s){const[n,r]=[t[i],e[s]];n.toLowerCase()===r.toLowerCase()&&(e[s]=n,s>i?e=e.slice(s):i>s&&(t=t.slice(i)))}}const{optimizationLevel:o=1}=this.options;o>=2&&(t=this.levelTwoFileOptimize(t)),this.debug(\"matchOne\",this,{file:t,pattern:e}),this.debug(\"matchOne\",t.length,e.length);for(var i=0,s=0,a=t.length,u=e.length;i<a&&s<u;i++,s++){this.debug(\"matchOne loop\");var c=e[s],l=t[i];if(this.debug(e,c,l),!1===c)return!1;if(c===Dt){this.debug(\"GLOBSTAR\",[e,c,l]);var h=i,p=s+1;if(p===u){for(this.debug(\"** at the end\");i<a;i++)if(\".\"===t[i]||\"..\"===t[i]||!r.dot&&\".\"===t[i].charAt(0))return!1;return!0}for(;h<a;){var f=t[h];if(this.debug(\"\\nglobstar while\",t,h,e,p,f),this.matchOne(t.slice(h),e.slice(p),n))return this.debug(\"globstar found match!\",h,a,f),!0;if(\".\"===f||\"..\"===f||!r.dot&&\".\"===f.charAt(0)){this.debug(\"dot detected!\",t,h,e,p);break}this.debug(\"globstar swallow a segment, and continue\"),h++}return!(!n||(this.debug(\"\\n>>> no match, partial?\",t,h,e,p),h!==a))}let o;if(\"string\"==typeof c?(o=l===c,this.debug(\"string match\",c,l,o)):(o=c.test(l),this.debug(\"pattern match\",c,l,o)),!o)return!1}if(i===a&&s===u)return!0;if(i===a)return n;if(s===u)return i===a-1&&\"\"===t[i];throw new Error(\"wtf?\")}braceExpand(){return Wt(this.pattern,this.options)}parse(t){et(t);const e=this.options;if(\"**\"===t)return Dt;if(\"\"===t)return\"\";let n,r=null;(n=t.match(St))?r=e.dot?Ct:$t:(n=t.match(bt))?r=(e.nocase?e.dot?Pt:Nt:e.dot?xt:wt)(n[1]):(n=t.match(It))?r=(e.nocase?e.dot?Rt:kt:e.dot?Lt:_t)(n):(n=t.match(At))?r=e.dot?Ot:Et:(n=t.match(Tt))&&(r=jt);const o=yt.fromGlob(t,this.options).toMMPattern();return r&&\"object\"==typeof o&&Reflect.defineProperty(o,\"test\",{value:r}),o}makeRe(){if(this.regexp||!1===this.regexp)return this.regexp;const t=this.set;if(!t.length)return this.regexp=!1,this.regexp;const e=this.options,n=e.noglobstar?\"[^/]*?\":e.dot?\"(?:(?!(?:\\\\/|^)(?:\\\\.{1,2})($|\\\\/)).)*?\":\"(?:(?!(?:\\\\/|^)\\\\.).)*?\",r=new Set(e.nocase?[\"i\"]:[]);let o=t.map(t=>{const e=t.map(t=>{if(t instanceof RegExp)for(const e of t.flags.split(\"\"))r.add(e);return\"string\"==typeof t?t.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g,\"\\\\$&\"):t===Dt?Dt:t._src});return e.forEach((t,r)=>{const o=e[r+1],i=e[r-1];t===Dt&&i!==Dt&&(void 0===i?void 0!==o&&o!==Dt?e[r+1]=\"(?:\\\\/|\"+n+\"\\\\/)?\"+o:e[r]=n:void 0===o?e[r-1]=i+\"(?:\\\\/|\"+n+\")?\":o!==Dt&&(e[r-1]=i+\"(?:\\\\/|\\\\/\"+n+\"\\\\/)\"+o,e[r+1]=Dt))}),e.filter(t=>t!==Dt).join(\"/\")}).join(\"|\");const[i,s]=t.length>1?[\"(?:\",\")\"]:[\"\",\"\"];o=\"^\"+i+o+s+\"$\",this.negate&&(o=\"^(?!\"+o+\").+$\");try{this.regexp=new RegExp(o,[...r].join(\"\"))}catch(t){this.regexp=!1}return this.regexp}slashSplit(t){return this.preserveMultipleSlashes?t.split(\"/\"):this.isWindows&&/^\\/\\/[^\\/]+/.test(t)?[\"\",...t.split(/\\/+/)]:t.split(/\\/+/)}match(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.partial;if(this.debug(\"match\",t,this.pattern),this.comment)return!1;if(this.empty)return\"\"===t;if(\"/\"===t&&e)return!0;const n=this.options;this.isWindows&&(t=t.split(\"\\\\\").join(\"/\"));const r=this.slashSplit(t);this.debug(this.pattern,\"split\",r);const o=this.set;this.debug(this.pattern,\"set\",o);let i=r[r.length-1];if(!i)for(let t=r.length-2;!i&&t>=0;t--)i=r[t];for(let t=0;t<o.length;t++){const s=o[t];let a=r;if(n.matchBase&&1===s.length&&(a=[i]),this.matchOne(a,s,e))return!!n.flipNegate||!this.negate}return!n.flipNegate&&this.negate}static defaults(t){return vt.defaults(t).Minimatch}}function Gt(t){const e=new Error(`${arguments.length>1&&void 0!==arguments[1]?arguments[1]:\"\"}Invalid response: ${t.status} ${t.statusText}`);return e.status=t.status,e.response=t,e}function qt(t,e){const{status:n}=e;if(401===n&&t.digest)return e;if(n>=400)throw Gt(e);return e}function Ht(t,e){return arguments.length>2&&void 0!==arguments[2]&&arguments[2]?{data:e,headers:t.headers?B(t.headers):{},status:t.status,statusText:t.statusText}:e}vt.AST=yt,vt.Minimatch=zt,vt.escape=function(t){let{windowsPathsNoEscape:e=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e?t.replace(/[?*()[\\]]/g,\"[$&]\"):t.replace(/[?*()[\\]\\\\]/g,\"\\\\$&\")},vt.unescape=st;const Zt=(Xt=function(t,e,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};const o=K({url:m(t.remoteURL,p(e)),method:\"COPY\",headers:{Destination:m(t.remoteURL,p(n)),Overwrite:!1===r.overwrite?\"F\":\"T\",Depth:r.shallow?\"0\":\"infinity\"}},t,r);return s=function(e){qt(t,e)},(i=J(o,t))&&i.then||(i=Promise.resolve(i)),s?i.then(s):i;var i,s},function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];try{return Promise.resolve(Xt.apply(this,t))}catch(t){return Promise.reject(t)}});var Xt,Yt=n(173),Jt=n(180),Kt=n.n(Jt),Qt=function(t){return t.Array=\"array\",t.Object=\"object\",t.Original=\"original\",t}(Qt||{});function te(t,e){if(!t.endsWith(\"propstat.prop.displayname\"))return e}function ee(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:Qt.Original;const r=Kt().get(t,e);return\"array\"===n&&!1===Array.isArray(r)?[r]:\"object\"===n&&Array.isArray(r)?r[0]:r}function ne(t,e){return e=e??{attributeNamePrefix:\"@\",attributeParsers:[],tagParsers:[te]},new Promise(n=>{n(function(t){const{multistatus:e}=t;if(\"\"===e)return{multistatus:{response:[]}};if(!e)throw new Error(\"Invalid response: No root multistatus found\");const n={multistatus:Array.isArray(e)?e[0]:e};return Kt().set(n,\"multistatus.response\",ee(n,\"multistatus.response\",Qt.Array)),Kt().set(n,\"multistatus.response\",Kt().get(n,\"multistatus.response\").map(t=>function(t){const e=Object.assign({},t);return e.status?Kt().set(e,\"status\",ee(e,\"status\",Qt.Object)):(Kt().set(e,\"propstat\",ee(e,\"propstat\",Qt.Object)),Kt().set(e,\"propstat.prop\",ee(e,\"propstat.prop\",Qt.Object))),e}(t))),n}(function(t){let{attributeNamePrefix:e,attributeParsers:n,tagParsers:r}=t;return new Yt.XMLParser({allowBooleanAttributes:!0,attributeNamePrefix:e,textNodeName:\"text\",ignoreAttributes:!1,removeNSPrefix:!0,numberParseOptions:{hex:!0,leadingZeros:!1},attributeValueProcessor(t,e,r){for(const t of n)try{const n=t(r,e);if(n!==e)return n}catch(t){}return e},tagValueProcessor(t,e,n){for(const t of r)try{const r=t(n,e);if(r!==e)return r}catch(t){}return e}})}(e).parse(t)))})}function re(t,e){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const{getlastmodified:r=null,getcontentlength:o=\"0\",resourcetype:i=null,getcontenttype:s=null,getetag:a=null}=t,u=i&&\"object\"==typeof i&&void 0!==i.collection?\"directory\":\"file\",l={filename:e,basename:c().basename(e),lastmod:r,size:parseInt(o,10),type:u,etag:\"string\"==typeof a?a.replace(/\"/g,\"\"):null};return\"file\"===u&&(l.mime=s&&\"string\"==typeof s?s.split(\";\")[0]:\"\"),n&&(void 0!==t.displayname&&(t.displayname=String(t.displayname)),l.props=t),l}function oe(t,e){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=null;try{t.multistatus.response[0].propstat&&(r=t.multistatus.response[0])}catch(t){}if(!r)throw new Error(\"Failed getting item stat: bad response\");const{propstat:{prop:o,status:i}}=r,[s,a,u]=i.split(\" \",3),c=parseInt(a,10);if(c>=400){const t=new Error(`Invalid response: ${c} ${u}`);throw t.status=c,t}return re(o,g(e),n)}function ie(t){switch(String(t)){case\"-3\":return\"unlimited\";case\"-2\":case\"-1\":return\"unknown\";default:return parseInt(String(t),10)}}function se(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const ae=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{details:r=!1}=n,o=K({url:m(t.remoteURL,p(e)),method:\"PROPFIND\",headers:{Accept:\"text/plain,application/xml\",Depth:\"0\"}},t,n);return se(J(o,t),function(n){return qt(t,n),se(n.text(),function(o){return se(ne(o,t.parsing),function(t){const o=oe(t,e,r);return Ht(n,o,r)})})})});function ue(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const ce=le(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=function(t){if(!t||\"/\"===t)return[];let e=t;const n=[];do{n.push(e),e=c().dirname(e)}while(e&&\"/\"!==e);return n}(g(e));r.sort((t,e)=>t.length>e.length?1:e.length>t.length?-1:0);let o=!1;return function(t,e,n){if(\"function\"==typeof t[fe]){var r,o,i,s=t[fe]();function l(t){try{for(;!((r=s.next()).done||n&&n());)if((t=e(r.value))&&t.then){if(!me(t))return void t.then(l,i||(i=ge.bind(null,o=new de,2)));t=t.v}o?ge(o,1,t):o=t}catch(t){ge(o||(o=new de),2,t)}}if(l(),s.return){var a=function(t){try{r.done||s.return()}catch(t){}return t};if(o&&o.then)return o.then(a,function(t){throw a(t)});a()}return o}if(!(\"length\"in t))throw new TypeError(\"Object is not iterable\");for(var u=[],c=0;c<t.length;c++)u.push(t[c]);return function(t,e,n){var r,o,i=-1;return function s(a){try{for(;++i<t.length&&(!n||!n());)if((a=e(i))&&a.then){if(!me(a))return void a.then(s,o||(o=ge.bind(null,r=new de,2)));a=a.v}r?ge(r,1,a):r=a}catch(t){ge(r||(r=new de),2,t)}}(),r}(u,function(t){return e(u[t])},n)}(r,function(r){return i=function(){return function(n,o){try{var i=ue(ae(t,r),function(t){if(\"directory\"!==t.type)throw new Error(`Path includes a file: ${e}`)})}catch(t){return o(t)}return i&&i.then?i.then(void 0,o):i}(0,function(e){const i=e;return function(){if(404===i.status)return o=!0,pe(ye(t,r,{...n,recursive:!1}));throw e}()})},(s=function(){if(o)return pe(ye(t,r,{...n,recursive:!1}))}())&&s.then?s.then(i):i();var i,s},function(){return!1})});function le(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}function he(){}function pe(t,e){if(!e)return t&&t.then?t.then(he):Promise.resolve()}const fe=\"undefined\"!=typeof Symbol?Symbol.iterator||(Symbol.iterator=Symbol(\"Symbol.iterator\")):\"@@iterator\";function ge(t,e,n){if(!t.s){if(n instanceof de){if(!n.s)return void(n.o=ge.bind(null,t,e));1&e&&(e=n.s),n=n.v}if(n&&n.then)return void n.then(ge.bind(null,t,e),ge.bind(null,t,2));t.s=e,t.v=n;const r=t.o;r&&r(t)}}const de=function(){function t(){}return t.prototype.then=function(e,n){const r=new t,o=this.s;if(o){const t=1&o?e:n;if(t){try{ge(r,1,t(this.v))}catch(t){ge(r,2,t)}return r}return this}return this.o=function(t){try{const o=t.v;1&t.s?ge(r,1,e?e(o):o):n?ge(r,1,n(o)):ge(r,2,o)}catch(t){ge(r,2,t)}},r},t}();function me(t){return t instanceof de&&1&t.s}const ye=le(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!0===n.recursive)return ce(t,e,n);const r=K({url:m(t.remoteURL,(o=p(e),o.endsWith(\"/\")?o:o+\"/\")),method:\"MKCOL\"},t,n);var o;return ue(J(r,t),function(e){qt(t,e)})});var ve=n(388),be=n.n(ve);const we=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r={};if(\"object\"==typeof n.range&&\"number\"==typeof n.range.start){let t=`bytes=${n.range.start}-`;\"number\"==typeof n.range.end&&(t=`${t}${n.range.end}`),r.Range=t}const o=K({url:m(t.remoteURL,p(e)),method:\"GET\",headers:r},t,n);return s=function(e){if(qt(t,e),r.Range&&206!==e.status){const t=new Error(`Invalid response code for partial request: ${e.status}`);throw t.status=e.status,t}return n.callback&&setTimeout(()=>{n.callback(e)},0),e.body},(i=J(o,t))&&i.then||(i=Promise.resolve(i)),s?i.then(s):i;var i,s}),xe=()=>{},Ne=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e,n){n.url||(n.url=m(t.remoteURL,p(e)));const r=K(n,t,{});return i=function(e){return qt(t,e),e},(o=J(r,t))&&o.then||(o=Promise.resolve(o)),i?o.then(i):o;var o,i}),Pe=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=K({url:m(t.remoteURL,p(e)),method:\"DELETE\"},t,n);return i=function(e){qt(t,e)},(o=J(r,t))&&o.then||(o=Promise.resolve(o)),i?o.then(i):o;var o,i}),Ae=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return function(r,o){try{var i=(s=ae(t,e,n),a=function(){return!0},u?a?a(s):s:(s&&s.then||(s=Promise.resolve(s)),a?s.then(a):s))}catch(t){return o(t)}var s,a,u;return i&&i.then?i.then(void 0,o):i}(0,function(t){if(404===t.status)return!1;throw t})});function Ee(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const Oe=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=K({url:m(t.remoteURL,p(e),\"/\"),method:\"PROPFIND\",headers:{Accept:\"text/plain,application/xml\",Depth:n.deep?\"infinity\":\"1\"}},t,n);return Ee(J(r,t),function(r){return qt(t,r),Ee(r.text(),function(o){if(!o)throw new Error(\"Failed parsing directory contents: Empty response\");return Ee(ne(o,t.parsing),function(o){const i=f(e);let s=function(t,e,n){let r=arguments.length>3&&void 0!==arguments[3]&&arguments[3],o=arguments.length>4&&void 0!==arguments[4]&&arguments[4];const i=c().join(e,\"/\"),{multistatus:{response:s}}=t,u=s.map(t=>{const e=function(t){try{return t.replace(/^https?:\\/\\/[^\\/]+/,\"\")}catch(t){throw new a(t,\"Failed normalising HREF\")}}(t.href),{propstat:{prop:n}}=t;return re(n,\"/\"===i?decodeURIComponent(g(e)):g(c().relative(decodeURIComponent(i),decodeURIComponent(e))),r)});return o?u:u.filter(t=>t.basename&&(\"file\"===t.type||t.filename!==n.replace(/\\/$/,\"\")))}(o,f(t.remoteBasePath||t.remotePath),i,n.details,n.includeSelf);return n.glob&&(s=function(t,e){return t.filter(t=>vt(t.filename,e,{matchBase:!0}))}(s,n.glob)),Ht(r,s,n.details)})})})});function Te(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}const je=Te(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=K({url:m(t.remoteURL,p(e)),method:\"GET\",headers:{Accept:\"text/plain\"},transformResponse:[Ie]},t,n);return Se(J(r,t),function(e){return qt(t,e),Se(e.text(),function(t){return Ht(e,t,n.details)})})});function Se(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const $e=Te(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=K({url:m(t.remoteURL,p(e)),method:\"GET\"},t,n);return Se(J(r,t),function(e){let r;return qt(t,e),function(t,e){var n=t();return n&&n.then?n.then(e):e()}(function(){return Se(e.arrayBuffer(),function(t){r=t})},function(){return Ht(e,r,n.details)})})}),Ce=Te(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{format:r=\"binary\"}=n;if(\"binary\"!==r&&\"text\"!==r)throw new a({info:{code:S.InvalidOutputFormat}},`Invalid output format: ${r}`);return\"text\"===r?je(t,e,n):$e(t,e,n)}),Ie=t=>t;function ke(t){return new Yt.XMLBuilder({attributeNamePrefix:\"@_\",format:!0,ignoreAttributes:!1,suppressEmptyNode:!0}).build(Re({lockinfo:{\"@_xmlns:d\":\"DAV:\",lockscope:{exclusive:{}},locktype:{write:{}},owner:{href:t}}},\"d\"))}function Re(t,e){const n={...t};for(const t in n)n.hasOwnProperty(t)&&(n[t]&&\"object\"==typeof n[t]&&-1===t.indexOf(\":\")?(n[`${e}:${t}`]=Re(n[t],e),delete n[t]):!1===/^@_/.test(t)&&(n[`${e}:${t}`]=n[t],delete n[t]));return n}function Le(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}function _e(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}const Me=_e(function(t,e,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};const o=K({url:m(t.remoteURL,p(e)),method:\"UNLOCK\",headers:{\"Lock-Token\":n}},t,r);return Le(J(o,t),function(e){if(qt(t,e),204!==e.status&&200!==e.status)throw Gt(e)})}),Ue=_e(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{refreshToken:r,timeout:o=Fe}=n,i={Accept:\"text/plain,application/xml\",Timeout:o};r&&(i.If=r);const s=K({url:m(t.remoteURL,p(e)),method:\"LOCK\",headers:i,data:ke(t.contactHref)},t,n);return Le(J(s,t),function(e){return qt(t,e),Le(e.text(),function(t){const n=(i=t,new Yt.XMLParser({removeNSPrefix:!0,parseAttributeValue:!0,parseTagValue:!0}).parse(i)),r=Kt().get(n,\"prop.lockdiscovery.activelock.locktoken.href\"),o=Kt().get(n,\"prop.lockdiscovery.activelock.timeout\");var i;if(!r)throw Gt(e,\"No lock token received: \");return{token:r,serverTimeout:o}})})}),Fe=\"Infinite, Second-4100000000\";function De(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const Be=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=e.path||\"/\",r=K({url:m(t.remoteURL,n),method:\"PROPFIND\",headers:{Accept:\"text/plain,application/xml\",Depth:\"0\"}},t,e);return De(J(r,t),function(n){return qt(t,n),De(n.text(),function(r){return De(ne(r,t.parsing),function(t){const r=function(t){try{const[e]=t.multistatus.response,{propstat:{prop:{\"quota-used-bytes\":n,\"quota-available-bytes\":r}}}=e;return void 0!==n&&void 0!==r?{used:parseInt(String(n),10),available:ie(r)}:null}catch(t){}return null}(t);return Ht(n,r,e.details)})})})});function We(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const Ve=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{details:r=!1}=n,o=K({url:m(t.remoteURL,p(e)),method:\"SEARCH\",headers:{Accept:\"text/plain,application/xml\",\"Content-Type\":t.headers[\"Content-Type\"]||\"application/xml; charset=utf-8\"}},t,n);return We(J(o,t),function(n){return qt(t,n),We(n.text(),function(o){return We(ne(o,t.parsing),function(t){const o=function(t,e,n){const r={truncated:!1,results:[]};return r.truncated=t.multistatus.response.some(t=>\"507\"===(t.status||t.propstat?.status).split(\" \",3)?.[1]&&t.href.replace(/\\/$/,\"\").endsWith(p(e).replace(/\\/$/,\"\"))),t.multistatus.response.forEach(t=>{if(void 0===t.propstat)return;const e=t.href.split(\"/\").map(decodeURIComponent).join(\"/\");r.results.push(re(t.propstat.prop,e,n))}),r}(t,e,r);return Ht(n,o,r)})})})}),ze=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};const o=K({url:m(t.remoteURL,p(e)),method:\"MOVE\",headers:{Destination:m(t.remoteURL,p(n)),Overwrite:!1===r.overwrite?\"F\":\"T\"}},t,r);return s=function(e){qt(t,e)},(i=J(o,t))&&i.then||(i=Promise.resolve(i)),s?i.then(s):i;var i,s});var Ge=n(694);function qe(t){if(G(t))return t.byteLength;if(q(t))return t.length;if(\"string\"==typeof t)return(0,Ge.d)(t);throw new a({info:{code:S.DataTypeNoLength}},\"Cannot calculate data length: Invalid type\")}const He=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};const{contentLength:o=!0,overwrite:i=!0}=r,s={\"Content-Type\":\"application/octet-stream\"};!1===o||(s[\"Content-Length\"]=\"number\"==typeof o?`${o}`:`${qe(n)}`),i||(s[\"If-None-Match\"]=\"*\");const a=K({url:m(t.remoteURL,p(e)),method:\"PUT\",headers:s,data:n},t,r);return c=function(e){try{qt(t,e)}catch(t){const e=t;if(412!==e.status||i)throw e;return!1}return!0},(u=J(a,t))&&u.then||(u=Promise.resolve(u)),c?u.then(c):u;var u,c}),Ze=function(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}(function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=K({url:m(t.remoteURL,p(e)),method:\"OPTIONS\"},t,n);return i=function(e){try{qt(t,e)}catch(t){throw t}return{compliance:(e.headers.get(\"DAV\")??\"\").split(\",\").map(t=>t.trim()),server:e.headers.get(\"Server\")??\"\"}},(o=J(r,t))&&o.then||(o=Promise.resolve(o)),i?o.then(i):o;var o,i});function Xe(t,e,n){return n?e?e(t):t:(t&&t.then||(t=Promise.resolve(t)),e?t.then(e):t)}const Ye=Qe(function(t,e,n,r,o){let i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{};if(n>r||n<0)throw new a({info:{code:S.InvalidUpdateRange}},`Invalid update range ${n} for partial update`);const s={\"Content-Type\":\"application/octet-stream\",\"Content-Length\":\"\"+(r-n+1),\"Content-Range\":`bytes ${n}-${r}/*`},u=K({url:m(t.remoteURL,p(e)),method:\"PUT\",headers:s,data:o},t,i);return Xe(J(u,t),function(e){qt(t,e)})});function Je(t,e){var n=t();return n&&n.then?n.then(e):e(n)}const Ke=Qe(function(t,e,n,r,o){let i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{};if(n>r||n<0)throw new a({info:{code:S.InvalidUpdateRange}},`Invalid update range ${n} for partial update`);const s={\"Content-Type\":\"application/x-sabredav-partialupdate\",\"Content-Length\":\"\"+(r-n+1),\"X-Update-Range\":`bytes=${n}-${r}`},u=K({url:m(t.remoteURL,p(e)),method:\"PATCH\",headers:s,data:o},t,i);return Xe(J(u,t),function(e){qt(t,e)})});function Qe(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];try{return Promise.resolve(t.apply(this,e))}catch(t){return Promise.reject(t)}}}const tn=Qe(function(t,e,n,r,o){let i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{};return Xe(Ze(t,e,i),function(s){let u=!1;return Je(function(){if(s.compliance.includes(\"sabredav-partialupdate\"))return Xe(Ke(t,e,n,r,o,i),function(t){return u=!0,t})},function(c){let l=!1;return u?c:Je(function(){if(s.server.includes(\"Apache\")&&s.compliance.includes(\"<http://apache.org/dav/propset/fs/1>\"))return Xe(Ye(t,e,n,r,o,i),function(t){return l=!0,t})},function(t){if(l)return t;throw new a({info:{code:S.NotSupported}},\"Not supported\")})})})}),en=\"https://github.com/perry-mitchell/webdav-client/blob/master/LOCK_CONTACT.md\";function nn(t){let e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{authType:n=null,remoteBasePath:r,contactHref:o=en,ha1:i,headers:s={},httpAgent:u,httpsAgent:c,password:l,token:h,username:f,withCredentials:g}=e;let y=n;y||(y=f||l?j.Password:j.None);const v={authType:y,remoteBasePath:r,contactHref:o,ha1:i,headers:Object.assign({},s),httpAgent:u,httpsAgent:c,password:l,parsing:{attributeNamePrefix:e.attributeNamePrefix??\"@\",attributeParsers:[],tagParsers:[te]},remotePath:d(t),remoteURL:t,token:h,username:f,withCredentials:g};return $(v,v.username,v.password,v.token,v.ha1),{copyFile:(t,e,n)=>Zt(v,t,e,n),createDirectory:(t,e)=>ye(v,t,e),createReadStream:(t,e)=>function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=new(0,be().PassThrough);return we(t,e,n).then(t=>{t.pipe(r)}).catch(t=>{r.emit(\"error\",t)}),r}(v,t,e),createWriteStream:(t,e,n)=>function(t,e){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:xe;const o=new(0,be().PassThrough),i={};!1===n.overwrite&&(i[\"If-None-Match\"]=\"*\");const s=K({url:m(t.remoteURL,p(e)),method:\"PUT\",headers:i,data:o,maxRedirects:0},t,n);return J(s,t).then(e=>qt(t,e)).then(t=>{setTimeout(()=>{r(t)},0)}).catch(t=>{o.emit(\"error\",t)}),o}(v,t,e,n),customRequest:(t,e)=>Ne(v,t,e),deleteFile:(t,e)=>Pe(v,t,e),exists:(t,e)=>Ae(v,t,e),getDirectoryContents:(t,e)=>Oe(v,t,e),getFileContents:(t,e)=>Ce(v,t,e),getFileDownloadLink:t=>function(t,e){let n=m(t.remoteURL,p(e));const r=/^https:/i.test(n)?\"https\":\"http\";switch(t.authType){case j.None:break;case j.Password:{const e=P(t.headers.Authorization.replace(/^Basic /i,\"\").trim());n=n.replace(/^https?:\\/\\//,`${r}://${e}@`);break}default:throw new a({info:{code:S.LinkUnsupportedAuthType}},`Unsupported auth type for file link: ${t.authType}`)}return n}(v,t),getFileUploadLink:t=>function(t,e){let n=`${m(t.remoteURL,p(e))}?Content-Type=application/octet-stream`;const r=/^https:/i.test(n)?\"https\":\"http\";switch(t.authType){case j.None:break;case j.Password:{const e=P(t.headers.Authorization.replace(/^Basic /i,\"\").trim());n=n.replace(/^https?:\\/\\//,`${r}://${e}@`);break}default:throw new a({info:{code:S.LinkUnsupportedAuthType}},`Unsupported auth type for file link: ${t.authType}`)}return n}(v,t),getHeaders:()=>Object.assign({},v.headers),getQuota:t=>Be(v,t),lock:(t,e)=>Ue(v,t,e),moveFile:(t,e,n)=>ze(v,t,e,n),putFileContents:(t,e,n)=>He(v,t,e,n),partialUpdateFileContents:(t,e,n,r,o)=>tn(v,t,e,n,r,o),getDAVCompliance:t=>Ze(v,t),search:(t,e)=>Ve(v,t,e),setHeaders:t=>{v.headers=Object.assign({},t)},stat:(t,e)=>ae(v,t,e),unlock:(t,e,n)=>Me(v,t,e,n),registerAttributeParser:t=>{v.parsing.attributeParsers.push(t)},registerTagParser:t=>{v.parsing.tagParsers.push(t)}}}export{j as AuthType,S as ErrorCode,O as Request,T as Response,qe as calculateDataLength,nn as createClient,M as getPatcher,oe as parseStat,ne as parseXML,re as prepareFileFromProps,Ht as processResponsePayload,ie as translateDiskSpace};"
  },
  {
    "path": "public/apps/fsapp.app/GUI.js",
    "content": "// implementing it myself was too hard so i just stole it from https://codepen.io/adam-lynch/pen/GaqgXP\n\n// This context menu is for files and folders\nconst newcontextmenu = new anura.ContextMenu();\n// This context menu is for applications and libraries\nconst appcontextmenu = new anura.ContextMenu();\n// This context menu is for when no files are selected\nconst emptycontextmenu = new anura.ContextMenu();\n\n// Helper to add context menu items to both menus\nfunction addContextMenuItem(name, func) {\n    newcontextmenu.addItem(name, func);\n    appcontextmenu.addItem(name, func);\n}\n\n// addContextMenuItem(\"Get Info\", function () {});\n// addContextMenuItem(\"Pin to Shelf\", function () {});\naddContextMenuItem(\"Cut\", function () {\n    cut();\n});\naddContextMenuItem(\"Copy\", function () {\n    copy();\n});\naddContextMenuItem(\"Paste\", function () {\n    paste();\n});\naddContextMenuItem(\"Delete\", function () {\n    deleteFile();\n});\naddContextMenuItem(\"Rename\", function () {\n    rename();\n});\naddContextMenuItem(\"Refresh\", function () {\n    reload();\n});\n\nappcontextmenu.addItem(\"Install (Session)\", function () {\n    // While this is the same as double clicking, it's still useful to have the verbosely named option\n    installSession();\n});\n\nappcontextmenu.addItem(\"Install (Permanent)\", function () {\n    // This is not the same as double clicking, as it will install the app permanently\n    installPermanent();\n});\n\nappcontextmenu.addItem(\"Navigate\", function () {\n    // Normally, double clicking a folder will navigate into it, but for apps and libs, this is not the case\n    navigate();\n});\n\nemptycontextmenu.addItem(\"Upload from PC\", function () {\n    upload();\n});\nemptycontextmenu.addItem(\"New folder\", function () {\n    newFolder();\n});\nemptycontextmenu.addItem(\"New file\", function () {\n    newFile();\n});\nemptycontextmenu.addItem(\"Paste\", function () {\n    paste();\n});\nemptycontextmenu.addItem(\"Refresh\", function () {\n    reload();\n});\n\nconst min = 150;\n// The max (fr) values for grid-template-columns\nconst columnTypeToRatioMap = {\n    icon: 0.1,\n    name: 3,\n    size: 1,\n    type: 1,\n    modified: 1,\n};\n\nconst table = document.querySelector(\"table\");\n/*\nThe following will soon be filled with column objects containing\nthe header element and their size value for grid-template-columns\n*/\nconst columns = [];\nlet headerBeingResized;\n\n// The next three functions are mouse event callbacks\n\n// Where the magic happens. I.e. when they're actually resizing\nconst onMouseMove = (e) =>\n    requestAnimationFrame(() => {\n        console.log(\"onMouseMove\");\n\n        (window.getSelection\n            ? window.getSelection()\n            : document.selection\n        ).empty();\n\n        // Calculate the desired width\n        horizontalScrollOffset = document.documentElement.scrollLeft;\n        const width =\n            horizontalScrollOffset + e.clientX - headerBeingResized.offsetLeft;\n\n        // Update the column object with the new size value\n        const column = columns.find(\n            ({ header }) => header === headerBeingResized,\n        );\n        column.size = Math.max(min, width) + \"px\"; // Enforce our minimum\n\n        // For the other headers which don't have a set width, fix it to their computed width\n        columns.forEach((column) => {\n            if (column.size.startsWith(\"minmax\")) {\n                // isn't fixed yet (it would be a pixel value otherwise)\n                column.size = parseInt(column.header.clientWidth, 10) + \"px\";\n            }\n        });\n\n        /*\n      Update the column sizes\n      Reminder: grid-template-columns sets the width for all columns in one value\n  */\n        table.style.gridTemplateColumns = columns\n            .map(({ header, size }) => size)\n            .join(\" \");\n    });\n\n// Clean up event listeners, classes, etc.\nconst onMouseUp = () => {\n    console.log(\"onMouseUp\");\n\n    window.removeEventListener(\"mousemove\", onMouseMove);\n    window.removeEventListener(\"mouseup\", onMouseUp);\n    headerBeingResized.classList.remove(\"header--being-resized\");\n    headerBeingResized = null;\n};\n\n// Get ready, they're about to resize\nconst initResize = ({ target }) => {\n    console.log(\"initResize\");\n\n    headerBeingResized = target.parentNode;\n    window.addEventListener(\"mousemove\", onMouseMove);\n    window.addEventListener(\"mouseup\", onMouseUp);\n    headerBeingResized.classList.add(\"header--being-resized\");\n};\n\ndocument.querySelectorAll(\"th\").forEach((header) => {\n    const max = columnTypeToRatioMap[header.dataset.type] + \"fr\";\n    columns.push({\n        header,\n        size: `minmax(${min}px, ${max})`,\n    });\n    header\n        .querySelector(\".resize-handle\")\n        .addEventListener(\"mousedown\", initResize);\n});\n\ndocument.addEventListener(\"contextmenu\", (e) => {\n    if (e.shiftKey) {\n        return;\n    }\n    e.preventDefault();\n    const boundingRect = window.frameElement.getBoundingClientRect();\n\n    const containsApps =\n        currentlySelected\n            .map(\n                (item) =>\n                    item.getAttribute(\"data-path\").split(\".\").slice(\"-1\")[0],\n            )\n            .filter((item) => item == \"app\" || item == \"lib\").length > 0;\n\n    if (containsApps) {\n        appcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y);\n        newcontextmenu.hide();\n        emptycontextmenu.hide();\n    } else if (currentlySelected.length != 0) {\n        newcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y);\n        appcontextmenu.hide();\n        emptycontextmenu.hide();\n    } else {\n        emptycontextmenu.show(\n            e.pageX + boundingRect.x,\n            e.pageY + boundingRect.y,\n        );\n        newcontextmenu.hide();\n        appcontextmenu.hide();\n    }\n});\n\ndocument.addEventListener(\"click\", (e) => {\n    newcontextmenu.hide();\n    appcontextmenu.hide();\n    emptycontextmenu.hide();\n});\n"
  },
  {
    "path": "public/apps/fsapp.app/appview.html",
    "content": "<!doctype html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>App Info Viewer</title>\n        <link rel=\"stylesheet\" href=\"/assets/matter.css\" />\n        <style>\n            @font-face {\n                font-family: Roboto;\n                src: url(\"/assets/fonts/Roboto-Regular.ttf\") format(\"truetype\");\n            }\n\n            html {\n                height: 100%;\n                width: 100%;\n            }\n            .app-info-mnt {\n                display: none;\n            }\n\n            body {\n                background-color: #202124;\n                display: flex;\n                flex-direction: column;\n                align-items: center;\n                justify-content: center;\n                color: white;\n                margin: 0;\n                font-family: Roboto, sans-serif;\n                overflow: scroll;\n            }\n\n            ::-webkit-scrollbar {\n                display: none;\n            }\n\n            #app-title {\n                display: flex;\n                flex-direction: column;\n                align-items: center;\n                justify-content: center;\n                overflow: hidden;\n                width: 100%;\n            }\n\n            #app-name {\n                font-size: 2rem;\n                margin: 0;\n                overflow: hidden;\n                text-overflow: ellipsis;\n                white-space: nowrap;\n                max-width: 100%;\n            }\n\n            #app-version {\n                font-size: 1rem;\n                margin: 0;\n            }\n\n            #app-top-info {\n                display: flex;\n                flex-direction: row;\n                align-items: center;\n                justify-content: center;\n                width: calc(100% - 56px);\n                gap: 1rem;\n            }\n            #install-button-strip {\n                position: absolute;\n                bottom: 5px;\n            }\n            #install-button-strip > button {\n                margin: 10px;\n            }\n\n            #app-logo {\n                height: 128px;\n                width: 128px;\n                flex-shrink: 0;\n            }\n\n            #app-logo img {\n                height: 100%;\n                width: 100%;\n            }\n\n            #app-divider {\n                margin: 1rem 0;\n                height: 1px;\n                background-color: #9ca3af;\n                flex-shrink: 0;\n                width: calc(100% - 56px);\n            }\n\n            #app-info {\n                display: flex;\n                flex-direction: column;\n                align-items: center;\n                justify-content: center;\n                gap: 1rem;\n                padding-bottom: 1rem;\n                width: calc(100% - 56px);\n            }\n\n            .mono {\n                font-family:\n                    Roboto Mono,\n                    monospace;\n            }\n\n            table {\n                width: 100%;\n                border-collapse: collapse;\n            }\n\n            td,\n            th {\n                padding: 0.5rem;\n                border: 1px solid #555;\n                text-align: left;\n            }\n\n            tr {\n                background-color: #2d2f33;\n            }\n        </style>\n        <script>\n            const url = new URL(window.location.href);\n            const args = window.parent.ExternalApp.deserializeArgs(\n                url.searchParams.get(\"manifest\"),\n            );\n            if (!args) {\n                console.error(\"No args found\");\n                instanceWindow.close();\n            }\n            console.log(args);\n            window.manifest = JSON.parse(args[0]);\n            window.iconurl = args[1];\n            window.isLib = args[2] === \"lib\";\n        </script>\n    </head>\n    <body>\n        <div id=\"app-top-info\">\n            <div id=\"app-logo\"></div>\n            <div id=\"app-title\">\n                <h1 id=\"app-name\"></h1>\n                <span id=\"app-version\"></span>\n            </div>\n        </div>\n        <div id=\"app-divider\"></div>\n        <div id=\"app-info\">\n            <span id=\"button-strip\"\n                ><button\n                    class=\"matter-button-contained\"\n                    id=\"manifestButton\"\n                    onclick=\"manifestShow()\"\n                >\n                    Manifest\n                </button></span\n            >\n            <table id=\"app-info-mnt\" style=\"\"></table>\n        </div>\n        <span id=\"install-button-strip\"></span>\n        <script>\n            let currentlyShowingMenu = document.getElementById(\"app-info-mnt\");\n            let currentlyShowingButton =\n                document.getElementById(\"manifestButton\");\n            function manifestShow() {\n                const appInfoTable = document.getElementById(\"app-info-mnt\");\n                const appInfoButton = document.getElementById(\"manifestButton\");\n                if (appInfoTable !== currentlyShowingMenu) {\n                    currentlyShowingMenu.style.display = \"none\";\n                    appInfoTable.style.display = \"\";\n                    currentlyShowingMenu = appInfoTable;\n                    currentlyShowingButton.className = \"matter-button-text\";\n                    appInfoButton.className = \"matter-button-contained\";\n                    currentlyShowingButton = appInfoButton;\n                }\n            }\n\n            const appName = document.querySelector(\"#app-name\");\n            appName.innerText = manifest.name;\n\n            const appVersion = document.querySelector(\"#app-version\");\n            if (isLib) {\n                appVersion.innerText =\n                    \"v\" + (manifest.currentVersion || \"0.0.0\");\n\n                if (!manifest.currentVersion) {\n                    appVersion.title =\n                        \"No version information found in manifest.\";\n                    // This library is not compliant if this happens.\n                }\n            } else {\n                appVersion.innerText = \"v\" + (manifest.version || \"0.0.0\");\n\n                if (!manifest.version) {\n                    appVersion.title =\n                        \"No version information found in manifest.\";\n                }\n            }\n\n            const appLogo = document.querySelector(\"#app-logo\");\n\n            const img = document.createElement(\"img\");\n            img.src = iconurl;\n            img.alt = manifest.name;\n            appLogo.appendChild(img);\n\n            const appInfoMnt = document.querySelector(\"#app-info-mnt\");\n\n            appInfoMnt.classList.add(\"mono\");\n\n            function parseObject(table, object) {\n                let headerRow = document.createElement(\"tr\");\n                let headerKey = document.createElement(\"th\");\n                let headerValue = document.createElement(\"th\");\n                headerKey.innerText = \"Key\";\n                headerValue.innerText = \"Value\";\n                headerRow.appendChild(headerKey);\n                headerRow.appendChild(headerValue);\n                table.appendChild(headerRow);\n                Object.entries(object).forEach(([key, value]) => {\n                    if (typeof value === \"object\") {\n                        const subTable = document.createElement(\"table\");\n                        parseObject(subTable, value);\n                        subTable.style.display = \"none\";\n\n                        const subTableTitle = document.createElement(\"button\");\n                        subTableTitle.className = \"matter-button-text\";\n                        subTableTitle.innerText = key;\n                        subTableTitle.onclick = () => {\n                            if (subTable !== currentlyShowingMenu) {\n                                currentlyShowingMenu.style.display = \"none\";\n                                subTable.style.display = \"\";\n                                currentlyShowingMenu = subTable;\n                                currentlyShowingButton.className =\n                                    \"matter-button-text\";\n                                subTableTitle.className =\n                                    \"matter-button-contained\";\n                                currentlyShowingButton = subTableTitle;\n                            }\n                        };\n\n                        document\n                            .getElementById(\"button-strip\")\n                            .appendChild(subTableTitle);\n                        document\n                            .getElementById(\"app-info\")\n                            .appendChild(subTable);\n                    } else {\n                        const tr = document.createElement(\"tr\");\n                        const tdKey = document.createElement(\"td\");\n                        const tdValue = document.createElement(\"td\");\n                        tdKey.innerText = key;\n                        tdValue.innerText = value;\n                        tr.appendChild(tdKey);\n                        tr.appendChild(tdValue);\n                        table.appendChild(tr);\n                    }\n                });\n            }\n\n            parseObject(appInfoMnt, window.manifest);\n\n            if (window.install) {\n                if (window.install.session) {\n                    const button = document.createElement(\"button\");\n                    button.innerText = \"Install For Session\";\n                    button.classList.add(\"matter-button-contained\");\n                    button.style.backgroundColor = \"#4f46e5\";\n                    button.onclick = window.install.session;\n                    document\n                        .getElementById(\"install-button-strip\")\n                        .appendChild(button);\n                }\n                if (window.install.permanent) {\n                    const button = document.createElement(\"button\");\n                    button.innerText = \"Install Permanently\";\n                    button.classList.add(\"matter-button-contained\");\n                    button.style.backgroundColor = \"#22b30f\";\n                    button.onclick = window.install.permanent;\n                    document\n                        .getElementById(\"install-button-strip\")\n                        .appendChild(button);\n                }\n            }\n        </script>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/fsapp.app/components/File.mjs",
    "content": "function formatBytes(bytes, decimals = 2) {\n    if (bytes === 0) return \"0 Bytes\";\n\n    const k = 1024;\n    const dm = decimals < 0 ? 0 : decimals;\n    const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"];\n\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + \" \" + sizes[i];\n}\nexport function File() {\n    this.mount = async () => {\n        this.absolutePath = `${this.path}/${this.file}`;\n        this.icon = anura.files.fallbackIcon;\n        try {\n            const iconURL = await anura.files.getIcon(this.absolutePath);\n            this.icon = iconURL;\n        } catch (e) {\n            console.error(e);\n        }\n        this.description = \"Anura File\";\n        try {\n            const fileType = await anura.files.getFileType(this.absolutePath);\n            this.description = fileType;\n        } catch (e) {\n            console.error(e);\n        }\n    };\n    return html`\n        <table>\n        <thead></thead>\n        <tbody>\n            <tr\n                on:mouseenter=${(e) => {\n                    e.currentTarget.classList.add(\"hover\");\n                }}\n                on:mouseleave=${(e) => {\n                    e.currentTarget.classList.remove(\"hover\");\n                }}\n                on:contextmenu=${(e) => {\n                    if (currentlySelected.length > 0) {\n                        return;\n                    }\n                    e.currentTarget.classList.add(\"selected\");\n                    currentlySelected = [e.currentTarget];\n                }}\n                on:click=${(e) => {\n                    if (currentlySelected.includes(e.currentTarget)) {\n                        if (\n                            self.filePicker &&\n                            self.filePicker?.type === \"file\" &&\n                            e.currentTarget.getAttribute(\"data-type\") === \"file\"\n                        ) {\n                            selectAction(currentlySelected);\n                        } else {\n                            fileAction(currentlySelected);\n                        }\n                        currentlySelected.forEach((row) => {\n                            row.classList.remove(\"selected\");\n                        });\n                        currentlySelected = [];\n                        return;\n                    }\n                    if (!e.shiftKey) {\n                        if (!e.ctrlKey) {\n                            currentlySelected.forEach((row) => {\n                                row.classList.remove(\"selected\");\n                            });\n                            currentlySelected = [];\n                        }\n                        e.currentTarget.classList.add(\"selected\");\n                        currentlySelected.push(e.currentTarget);\n                    } else {\n                        if (currentlySelected.length == 0) {\n                            e.currentTarget.classList.add(\"selected\");\n                            currentlySelected.push(e.currentTarget);\n                        } else {\n                            var arr = Array.from(\n                                document.querySelectorAll(\"tr\"),\n                            ).filter(\n                                (row) =>\n                                    row.parentNode.nodeName.toLowerCase() !==\n                                    \"thead\",\n                            );\n                            var firstI = arr.indexOf(\n                                currentlySelected[currentlySelected.length - 1],\n                            );\n                            var lastI = arr.indexOf(e.currentTarget);\n                            var first = Math.min(firstI, lastI);\n                            var last = Math.max(firstI, lastI);\n                            for (var i = first; i <= last; i++) {\n                                if (!currentlySelected.includes(arr[i])) {\n                                    currentlySelected.push(arr[i]);\n                                    arr[i].classList.add(\"selected\");\n                                }\n                            }\n                        }\n                    }\n                }}\n                data-type=\"file\"\n                data-path=${use(this.absolutePath)}\n            >\n                <td id=\"iconContainer\">\n                    <img class=\"icon\" src=${use(this.icon)}></img>\n                </td>\n                <td id=\"name\">${this.file}</td>\n                <td id=\"size\">${formatBytes(this.stats.size)}</td>\n                <td id=\"description\">${use(this.description)}</td>\n                <td id=\"date\">${new Date(this.stats.mtime).toLocaleString()}</td>\n            </tr>\n        </tbody>\n        </table>\n    `;\n}\n"
  },
  {
    "path": "public/apps/fsapp.app/components/Folder.mjs",
    "content": "export function Folder() {\n    this.mount = async () => {\n        this.absolutePath = `${this.path}/${this.file}`;\n        this.description = \"Folder\";\n        try {\n            let manifestPath = `${this.absolutePath}/manifest.json`;\n            console.log(manifestPath);\n\n            const data = await fs.promises.readFile(manifestPath);\n            let manifest = JSON.parse(data);\n            let folderExt = this.file.split(\".\").slice(\"-1\")[0];\n\n            this.icon = `/fs${this.absolutePath}/${manifest.icon}`;\n            console.log(this.icon);\n            this.description = `Anura ${folderExt == \"app\" ? \"Application\" : \"Library\"}`;\n        } catch (error) {\n            console.log(error);\n            this.icon = anura.files.folderIcon;\n        }\n    };\n    return html`\n        <table>\n        <thead></thead>\n        <tbody>\n            <tr\n                on:mouseenter=${(e) => {\n                    e.currentTarget.classList.add(\"hover\");\n                }}\n                on:mouseleave=${(e) => {\n                    e.currentTarget.classList.remove(\"hover\");\n                }}\n                on:contextmenu=${(e) => {\n                    if (self.currentlySelected.length > 0) {\n                        return;\n                    }\n                    e.currentTarget.classList.add(\"selected\");\n                    self.currentlySelected = [e.currentTarget];\n                }}\n                on:click=${(e) => {\n                    if (currentlySelected.includes(e.currentTarget)) {\n                        if (\n                            self.filePicker &&\n                            self.filePicker?.type === \"file\" &&\n                            e.currentTarget.getAttribute(\"data-type\") === \"file\"\n                        ) {\n                            selectAction(currentlySelected);\n                        } else {\n                            fileAction(currentlySelected);\n                        }\n                        currentlySelected.forEach((row) => {\n                            row.classList.remove(\"selected\");\n                        });\n                        currentlySelected = [];\n                        return;\n                    }\n                    if (!e.shiftKey) {\n                        if (!e.ctrlKey) {\n                            currentlySelected.forEach((row) => {\n                                row.classList.remove(\"selected\");\n                            });\n                            currentlySelected = [];\n                        }\n                        e.currentTarget.classList.add(\"selected\");\n                        currentlySelected.push(e.currentTarget);\n                    } else {\n                        if (currentlySelected.length == 0) {\n                            e.currentTarget.classList.add(\"selected\");\n                            currentlySelected.push(e.currentTarget);\n                        } else {\n                            var arr = Array.from(\n                                document.querySelectorAll(\"tr\"),\n                            ).filter(\n                                (row) =>\n                                    row.parentNode.nodeName.toLowerCase() !==\n                                    \"thead\",\n                            );\n                            var firstI = arr.indexOf(\n                                currentlySelected[currentlySelected.length - 1],\n                            );\n                            var lastI = arr.indexOf(e.currentTarget);\n                            var first = Math.min(firstI, lastI);\n                            var last = Math.max(firstI, lastI);\n                            for (var i = first; i <= last; i++) {\n                                if (!currentlySelected.includes(arr[i])) {\n                                    currentlySelected.push(arr[i]);\n                                    arr[i].classList.add(\"selected\");\n                                }\n                            }\n                        }\n                    }\n                }}\n                data-type=\"dir\"\n                data-path=${use(this.absolutePath)}\n            >\n                <td id=\"iconContainer\">\n                    <img class=\"icon\" src=${use(this.icon)}></img>\n                </td>\n                <td id=\"name\">${this.file}/</td>\n                <td id=\"size\">N/A</td>\n                <td id=\"description\">${use(this.description)}</td>\n                <td id=\"date\">${new Date(this.stats.mtime).toLocaleString()}</td>\n            </tr>\n        </tbody>\n        </table>\n    `;\n}\n"
  },
  {
    "path": "public/apps/fsapp.app/components/Selector.mjs",
    "content": "export function Selector() {\n    this.css = `\n    margin-top: 0.3em;\n    margin-right: 1em;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    position: fixed;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    justify-content: flex-end;\n    padding: 0.5em;\n\n    button {\n        background: var(--theme-accent);\n        margin: 1rem 0.5rem;\n        padding: 1.5em;\n        display: flex;\n        align-items: center;\n        border-radius: 9999px;\n    }\n    `;\n\n    return html`\n        <div class=\"topbar\" id=\"selector\">\n            <div class=\"sep\"></div>\n            <button\n                on:click=${() => {\n                    selectAction(currentlySelected);\n                }}\n            >\n                Select\n            </button>\n        </div>\n    `;\n}\n"
  },
  {
    "path": "public/apps/fsapp.app/components/SideBar.mjs",
    "content": "export function SideBar() {\n    this.css = `\n    display: flex;\n    flex-direction: column;\n    flex: 0 0 15em;\n    margin-right: 3em;\n    button {\n        height: 3em;\n        border-bottom-right-radius: 5em;\n        border-top-right-radius: 5em;\n        background-color: var(--theme-bg);\n        border: none;\n        text-align: left;\n        display: flex;\n        align-items: center;\n    }\n    button:hover {\n        background-color: var(--theme-secondary-bg);\n    }\n    button:active {\n        background-color: color-mix(\n            var(--theme-bg),\n            var(--theme-secondary-bg),\n            0.5\n        );\n    }\n    \n    i {\n        margin-right: 1em;\n        margin-left: 0.5em;\n    }\n    `;\n    return html`\n        <div>\n            <br />\n            <button>\n                <i class=\"material-symbols-outlined\">history</i>Recent\n            </button>\n            <hr />\n            <button onclick=\"loadPath('/')\">\n                <i class=\"material-symbols-outlined\">laptop_chromebook</i>My\n                files\n            </button>\n            <button\n                on:click=${async () => {\n                    if (!window.showDirectoryPicker) {\n                        anura.dialog.alert(\n                            \"Your browser does not support mounting local directories.\",\n                            \"Error\",\n                        );\n                        return;\n                    }\n                    let path = await anura.dialog.prompt(\n                        \"Enter the path where you want to mount the local filesystem\",\n                    );\n                    if (!path.startsWith(\"/\")) {\n                        anura.dialog.alert(\n                            \"Path does not start with a \" / \" character\",\n                            \"Error\",\n                        );\n                        return;\n                    }\n                    window.tb.sh.mkdirp(path, async function (err) {\n                        if (err) console.error(err);\n                        await window.parent.LocalFS.new(path);\n                        reload();\n                    });\n                }}\n            >\n                <i class=\"material-symbols-outlined\">usb</i>Mount local drive\n            </button>\n            <button style=\"margin-top: 5px;\" onclick=\"parent.window.tb.window.create({title: {text: 'Files', weight: 600,},icon: '/apps/files.tapp/icon.svg',src: '/apps/files.tapp/index.html',size: {width: 500,height: 500},single: false,resizable: true,app_id: 'com.tb.files'})\">\n                <i class=\"material-symbols-outlined\">laptop_chromebook</i>TB FS\n            </button>\n        </div>\n    `;\n}\n"
  },
  {
    "path": "public/apps/fsapp.app/components/TopBar.mjs",
    "content": "export function TopBar() {\n    this.css = `\n    margin-top: 0.3em;\n    margin-right: 1em;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    \n    button {\n        border-radius: 1em;\n        border: none;\n        background-color: var(--theme-bg);\n        height: 2.25em;\n        transition: background-color 0.1s;\n    }\n    \n    button > i {\n        padding: 0.25em;\n    }\n    \n    button:hover {\n        transition: background-color 0.1s;\n        background-color: var(--theme-secondary-bg);\n    }\n    \n    button:active {\n        background-color: color-mix(\n            var(--theme-bg),\n            var(--theme-secondary-bg),\n            0.5\n        );\n    }\n    \n    .sep {\n        flex-grow: 1;\n    }\n    \n    .breadcrumbs button {\n        font-size: 16px;\n        margin-right: 0.25em;\n        margin-left: 0.25em;\n        border-radius: 0;\n        display: inline-block;\n    }\n    \n    .breadcrumbs button:hover {\n        background-color: transparent;\n    }\n    `;\n    return html`\n        <div>\n            <div class=\"breadcrumbs\">\n                <button>My files</button><span>></span><button>owo :3</button>\n            </div>\n            <div class=\"sep\"></div>\n            <button>\n                <i class=\"material-symbols-outlined\">search</i>\n            </button>\n            <button>\n                <i class=\"material-symbols-outlined\">table_rows</i>\n            </button>\n            <button>\n                <i class=\"material-symbols-outlined\">sort_by_alpha</i>\n                <!--<i class=\"fa-solid fa-arrow-down-z-a fa-lg\"></i> - opposite-->\n            </button>\n            <button>\n                <i class=\"material-symbols-outlined\">settings</i>\n            </button>\n        </div>\n    `;\n}\n"
  },
  {
    "path": "public/apps/fsapp.app/filemanager.css",
    "content": "@font-face {\n    font-family: Roboto;\n    src: url(\"/assets/fonts/Roboto-Regular.ttf\") format(\"truetype\");\n}\n\n:root {\n    --theme-fg: #FFFFFF;\n    --theme-secondary-fg: #C1C1C1;\n    --theme-border: #444444;\n    --material-border: #444444;\n    --theme-dark-border: #000000;\n    --theme-bg: #202124;\n    --material-bg: #202124;\n    --theme-secondary-bg: #383838;\n    --theme-dark-bg: #161616;\n    --theme-accent: #4285F4;\n    --matter-helper-theme: #4285F4;\n}\n\n* {\n    color: var(--theme-fg);\n    font-family:\n        \"Roboto\",\n        RobotoDraft,\n        \"Droid Sans\",\n        Arial,\n        Helvetica,\n        -apple-system,\n        BlinkMacSystemFont,\n        system-ui,\n        sans-serif;\n    user-select: none;\n}\nbody {\n    margin: 0;\n}\n*::-webkit-scrollbar {\n    width: 8px;\n}\n\n*::-webkit-scrollbar-thumb {\n    background-color: var(--theme-secondary-bg);\n    border-radius: 8px;\n}\n\n*::-webkit-scrollbar-button {\n    display: none;\n}\n.container {\n    background-color: var(--theme-bg);\n    width: 100%;\n    height: 100%;\n    display: flex;\n    flex-direction: row;\n}\n.sidebar {\n    display: flex;\n    flex-direction: column;\n    flex: 0 0 15em;\n    margin-right: 3em;\n}\n.sidebar button {\n    height: 3em;\n    border-bottom-right-radius: 5em;\n    border-top-right-radius: 5em;\n    background-color: var(--theme-bg);\n    border: none;\n    text-align: left;\n    display: flex;\n    align-items: center;\n    cursor: var(--cursor-pointer);\n}\n.sidebar button:hover {\n    background-color: var(--theme-secondary-bg);\n}\n.sidebar button:active {\n    background-color: color-mix(\n        var(--theme-bg),\n        var(--theme-secondary-bg),\n        0.5\n    );\n}\n\n.sidebar i {\n    margin-right: 1em;\n    margin-left: 0.5em;\n}\n\n.fileView {\n    flex-grow: 1;\n    display: flex;\n    flex-direction: column;\n    overflow: hidden;\n}\n\n.resize-handle {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    background: var(--theme-secondary-fg);\n    opacity: 0;\n    width: 2.5px;\n    cursor: col-resize;\n    transition: opacity 0.1s;\n    margin-block: 0.2em;\n}\n\n.hidden-resize-handle {\n    opacity: 0;\n    transition: opacity 0.1s;\n}\n\n.resize-handle:hover,\n.header--being-resized .resize-handle {\n    opacity: 0.5;\n}\n\nth:hover .resize-handle {\n    opacity: 0.3;\n}\n\ntable {\n    transition: background-color 0.1s;\n    flex-grow: 1;\n    overflow: scroll;\n    display: grid;\n    grid-template-columns: min-content 3fr 1fr 1fr 1fr;\n    grid-auto-rows: min-content;\n    overflow-x: hidden;\n    overflow-y: auto;\n}\n\ntr,\nthead,\ntbody {\n    display: contents;\n}\n\nth {\n    position: relative;\n}\n\nth > tr {\n    margin: 0;\n}\n\ntr > * {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    padding-top: 0.5em;\n    padding-bottom: 0.5em;\n}\n\n.iconContainer {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n}\n\n.icon {\n    height: 1em;\n    width: 1em;\n}\n\n.selected > * {\n    transition: background-color 0.1s;\n    background-color: color-mix(\n        in srgb,\n        var(--theme-bg) 50%,\n        var(--theme-accent) 50%\n    ) !important;\n}\n\n.hover > * {\n    transition: background-color 0.1s;\n    background-color: var(--theme-secondary-bg);\n}\n\n.topbar {\n    margin-top: 0.3em;\n    margin-right: 1em;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n}\n\n.topbar button {\n    border-radius: 1em;\n    border: none;\n    background-color: var(--theme-bg);\n    height: 2.25em;\n    transition: background-color 0.1s;\n}\n\n.topbar button > i {\n    padding: 0.25em;\n}\n\n.topbar button:hover {\n    transition: background-color 0.1s;\n    background-color: var(--theme-secondary-bg);\n}\n\n.topbar button:active {\n    background-color: color-mix(\n        var(--theme-bg),\n        var(--theme-secondary-bg),\n        0.5\n    );\n}\n\n.topbar .sep {\n    flex-grow: 1;\n}\n\n.topbar .breadcrumbs button {\n    font-size: 16px;\n    margin-right: 0.25em;\n    margin-left: 0.25em;\n    border-radius: 0;\n    display: inline-block;\n}\n\n.topbar .breadcrumbs button:hover {\n    background-color: transparent;\n}\n\nhr {\n    color: transparent;\n    border-bottom: 1px solid var(--theme-border);\n    opacity: 0.25;\n    width: 100%;\n}\n"
  },
  {
    "path": "public/apps/fsapp.app/index.html",
    "content": "<html>\n    <head>\n        <link rel=\"stylesheet\" href=\"filemanager.css\" />\n        <link rel=\"stylesheet\" href=\"/assets/materialsymbols.css\" />\n        <script src=\"/assets/libs/filer.min.js\"></script>\n        <script src=\"/lib/dreamland/all.js\"></script>\n        <script src=\"GUI.js\"></script>\n        <script src=\"index.mjs\" type=\"module\"></script>\n        <script src=\"operations.js\"></script>\n    </head>\n</html>\n"
  },
  {
    "path": "public/apps/fsapp.app/index.mjs",
    "content": "// importing libaries\nself.fflate = window.parent.tb.fflate;\nself.mime = await anura.import(\"npm:mime\");\n\nself.currentlySelected = [];\nself.clipboard = [];\nself.removeAfterPaste = false;\nself.fs = anura.fs;\nself.filePicker = false;\nself.Buffer = Filer.Buffer;\nself.sh = new anura.fs.Shell();\n\n// components\nimport { File } from \"./components/File.mjs\";\nimport { Folder } from \"./components/Folder.mjs\";\nimport { TopBar } from \"./components/TopBar.mjs\";\nimport { SideBar } from \"./components/SideBar.mjs\";\nimport { Selector } from \"./components/Selector.mjs\";\n\nconst url = new URL(window.location.href);\nif (url.searchParams.get(\"picker\")) {\n    const picker = window.parent.ExternalApp.deserializeArgs(url.searchParams.get(\"picker\"));\n    if (picker) {\n        self.filePicker = {};\n        self.filePicker.regex = new RegExp(picker[0]);\n        self.filePicker.type = picker[1];\n        self.filePicker.multiple = picker[2];\n        self.filePicker.id = picker[3];\n    }\n}\n\nfunction App() {\n    this.css = `\n        background-color: var(--theme-bg);\n        width: 100%;\n        height: 100%;\n        display: flex;\n        flex-direction: row;\n        .fileView {\n            flex-grow: 1;\n            display: flex;\n            flex-direction: column;\n            overflow: hidden;\n        }\n    `;\n\n    return html`\n        <div id=\"app\">\n            <${SideBar}></${SideBar}>\n            <div class=\"fileView\">\n                <${TopBar}></${TopBar}>\n                <hr>\n                <table on:click=${(e) => {\n                    if (e.currentTarget === e.target) {\n                        currentlySelected.forEach((row) => {\n                            row.classList.remove(\"selected\");\n                        });\n                        currentlySelected = [];\n                    }\n                }}>\n                    <thead>\n                        <tr>\n                            <th data-type=\"icon\">\n                                <span\n                                    class=\"resize-handle hidden-resize-handle\"\n                                ></span>\n                            </th>\n                            <th data-type=\"name\">\n                                Name<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"size\">\n                                Size<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"type\">\n                                Type<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"modified\">\n                                Date modified<span class=\"resize-handle\"></span>\n                            </th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                    </tbody>\n            </div>\n        </div>\n    `;\n}\nself.loadPath = async (path) => {\n    console.debug(\"loading path: \", path);\n    const files = await fs.promises.readdir(path + \"/\");\n    files.sort();\n    console.debug(\"files: \", files);\n    setBreadcrumbs(path);\n    const table = document.querySelector(\"tbody\");\n    table.innerHTML = \"\";\n    for (const file of files) {\n        console.log(file);\n        const stats = await fs.promises.stat(`${path}/${file}`);\n        if (stats.isDirectory()) {\n            const element = html`<${Folder} path=${path} file=${file} stats=${stats}></${File}>`;\n            // oh my god this is horrid\n            table.appendChild(element.children[1].children[0]);\n        } else {\n            const ext = file.split(\"/\").pop().split(\".\").pop();\n            const element = html`<${File} path=${path} file=${file} stats=${stats}></${File}>`;\n            console.log(element);\n            if (\n                self.filePicker &&\n                self.filePicker.type !== \"dir\" &&\n                self.filePicker.regex.test(ext)\n            ) {\n                table.appendChild(element.children[1].children[0]);\n            } else if (!self.filePicker) {\n                console.log(element.children[1].children[0]);\n                table.appendChild(element.children[1].children[0]);\n            }\n        }\n    }\n};\ndocument.body.appendChild(html`<${App} />`);\nif (filePicker) {\n    document\n        .getElementById(\"app\")\n        .appendChild(html`<${Selector}></${Selector}>`);\n}\nloadPath(\"/\");\n"
  },
  {
    "path": "public/apps/fsapp.app/manifest.json",
    "content": "{\n    \"name\": \"Anura File Manager\",\n    \"type\": \"auto\",\n    \"package\": \"anura.fsapp\",\n    \"index\": \"index.html\",\n    \"icon\": \"files.png\",\n    \"wininfo\": {\n        \"title\": \"Anura File Manager\",\n        \"width\": \"700px\",\n        \"height\": \"500px\"\n    }\n}\n"
  },
  {
    "path": "public/apps/fsapp.app/operations.js",
    "content": "const fs = window.parent.anura.fs\n\nasync function selectAction(selected) {\n    currentlySelected.forEach((row) => {\n        row.classList.remove(\"selected\");\n    });\n    currentlySelected = [];\n    if (selected.length == 1) {\n        var fileSelected = selected[0];\n        if (fileSelected.getAttribute(\"data-type\") == filePicker.type) {\n            let fileData = {\n                message: \"FileSelected\",\n                id: filePicker.id,\n                filePath: fileSelected\n                    .getAttribute(\"data-path\")\n                    .replace(/(\\/)\\1+/g, \"$1\"),\n            };\n            window.callback({ data: fileData });\n            // window.parent.postMessage(fileData, \"*\");\n        }\n    } else if (selected.length > 1 && filePicker.multiple) {\n        let dataPaths = [];\n        for (var i = 0; i < selected.length; i++) {\n            var dataType = selected[i].getAttribute(\"data-type\");\n            var dataPath = selected[i].getAttribute(\"data-path\");\n            if (dataType !== filePicker.type) {\n                return;\n            }\n            if (dataPath !== null) {\n                dataPaths.push(dataPath.replace(/(\\/)\\1+/g, \"$1\"));\n            }\n        }\n        let fileData = {\n            message: \"FileSelected\",\n            id: filePicker.id,\n            filePath: dataPaths,\n        };\n        window.callback({ data: fileData });\n        // window.parent.postMessage(fileData, \"*\");\n    } else if (selected.length == 0) {\n        if (filePicker.type == \"dir\") {\n            let fileData = {\n                message: \"FileSelected\",\n                id: filePicker.id,\n                filePath: document\n                    .querySelector(\".breadcrumbs\")\n                    .getAttribute(\"data-current-path\"),\n            };\n\n            window.callback({ data: fileData });\n        }\n    }\n}\n\nasync function fileAction(selected) {\n    const instance = {\n        hidden: false,\n        icon: \"apps/fsapp.app/files.png\",\n        manifest: {name: 'Anura File Manager', type: 'auto', package: 'anura.fsapp', index: 'index.html', icon: 'files.png'},\n        name: \"Anura File Manager\",\n        title: \"Anura File Manager\",\n        package: \"anura.fsapp\",\n        source: \"apps/fsapp.app\",\n        windows: []\n    }\n    if (selected.length == 1) {\n        // SINGLE FILE SELECTION //\n\n        var fileSelected = selected[0];\n        if (fileSelected.getAttribute(\"data-type\") == \"file\") {\n            console.debug(\"Clicked on file\");\n            if (\n                fileSelected\n                    .getAttribute(\"data-path\")\n                    .split(\".\")\n                    .slice(\"-2\")\n                    .join(\".\") == \"app.zip\"\n            ) {\n                console.log(\"App Archive detected, extracting\");\n\n                let data = await fs.promises.readFile(\n                    fileSelected.getAttribute(\"data-path\"),\n                );\n\n                const path = fileSelected\n                    .getAttribute(\"data-path\")\n                    .split(\".\")\n                    .slice(0, -1)\n                    .join(\".\");\n\n                const zip = await unzip(new Uint8Array(data));\n                const manifest = JSON.parse(\n                    new TextDecoder().decode(zip[\"manifest.json\"]),\n                );\n                const icon = new Blob([zip[manifest.icon]], {\n                    //type: mime.default.getType(manifest.icon),\n                });\n                const win = await anura.wm.create(instance, {\n                    title: \"Anura File Manager\",\n                    width: \"450px\",\n                    height: \"525px\",\n                });\n\n                const iframe = document.createElement(\"iframe\");\n\n                iframe.setAttribute(\n                    \"src\",\n                    document.location.href.split(\"/\").slice(0, -1).join(\"/\") +\n                        \"/appview.html?manifest=\" +\n                        window.parent.ExternalApp.serializeArgs([\n                            JSON.stringify(manifest),\n                            URL.createObjectURL(icon),\n                            \"app\",\n                        ]),\n                );\n\n                iframe.style =\n                    \"top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;\";\n\n                win.content.appendChild(iframe);\n\n                Object.assign(iframe.contentWindow, {\n                    anura,\n                    ExternalApp,\n                    instance,\n                    instanceWindow: win,\n                    install: {\n                        session: async () => {\n                            anura.notifications.add({\n                                title: \"Application Installing for Session\",\n                                description: `Application ${path.replace(\n                                    \"//\",\n                                    \"/\",\n                                )} is being installed, please wait`,\n                                timeout: 50000,\n                            });\n                            await fs.mkdir(`${path.replace(\"//\", \"/\")}`);\n                            try {\n                                for (const [\n                                    relativePath,\n                                    content,\n                                ] of Object.entries(zip)) {\n                                    if (relativePath.endsWith(\"/\")) {\n                                        fs.mkdir(`${path}/${relativePath}`);\n                                    } else {\n                                        console.log(`${path}/${relativePath}`);\n                                        fs.writeFile(\n                                            `${path}/${relativePath}`,\n                                            await Buffer.from(content),\n                                        );\n                                    }\n                                }\n                                await anura.registerExternalApp(\n                                    `/fs${path}`.replace(\"//\", \"/\"),\n                                );\n                                anura.notifications.add({\n                                    title: \"Application Installed for Session\",\n                                    description: `Application ${path.replace(\n                                        \"//\",\n                                        \"/\",\n                                    )} has been installed temporarily, it will go away on refresh`,\n                                    timeout: 50000,\n                                });\n                            } catch (e) {\n                                console.error(e);\n                            }\n                        },\n                        permanent: async () => {\n                            anura.notifications.add({\n                                title: \"Application Installing\",\n                                description: `Application ${path.replace(\n                                    \"//\",\n                                    \"/\",\n                                )} is being installed, please wait`,\n                                timeout: 50000,\n                            });\n                            await fs.promises.mkdir(\n                                anura.settings.get(\"directories\")[\"apps\"] +\n                                    \"/\" +\n                                    path.split(\"/\").slice(\"-1\")[0],\n                            );\n\n                            try {\n                                for (const [\n                                    relativePath,\n                                    content,\n                                ] of Object.entries(zip)) {\n                                    if (relativePath.endsWith(\"/\")) {\n                                        await fs.promises.mkdir(\n                                            `${anura.settings.get(\"directories\")[\"apps\"]}/${path.split(\"/\").slice(\"-1\")[0]}/${relativePath}`,\n                                        );\n                                    } else {\n                                        await fs.promises.writeFile(\n                                            `${anura.settings.get(\"directories\")[\"apps\"]}/${path.split(\"/\").slice(\"-1\")[0]}/${relativePath}`,\n                                            Buffer.from(content),\n                                        );\n                                    }\n                                }\n                                await anura.registerExternalApp(\n                                    `/fs${anura.settings.get(\"directories\")[\"apps\"]}/${path.split(\"/\").slice(\"-1\")[0]}`.replace(\n                                        \"//\",\n                                        \"/\",\n                                    ),\n                                );\n                                anura.notifications.add({\n                                    title: \"Application Installed\",\n                                    description: `Application ${path.replace(\n                                        \"//\",\n                                        \"/\",\n                                    )} has been installed permanently`,\n                                    timeout: 50000,\n                                });\n                            } catch (e) {\n                                console.error(e);\n                            }\n                        },\n                    },\n                });\n\n                iframe.contentWindow.addEventListener(\"load\", () => {\n                    const matter = document.createElement(\"link\");\n                    matter.setAttribute(\"rel\", \"stylesheet\");\n                    matter.setAttribute(\"href\", \"/assets/matter.css\");\n                    iframe.contentDocument.head.appendChild(matter);\n                });\n            } else if (\n                fileSelected\n                    .getAttribute(\"data-path\")\n                    .split(\".\")\n                    .slice(\"-2\")\n                    .join(\".\") == \"lib.zip\"\n            ) {\n                console.log(\"Library Archive detected, extracting\");\n                const data = await fs.promises.readFile(\n                    fileSelected.getAttribute(\"data-path\"),\n                );\n\n                const path = fileSelected\n                    .getAttribute(\"data-path\")\n                    .split(\".\")\n                    .slice(0, -1)\n                    .join(\".\");\n\n                const zip = await unzip(new Uint8Array(data));\n                console.log(zip);\n                const manifest = JSON.parse(\n                    new TextDecoder().decode(zip[\"manifest.json\"]),\n                );\n                const icon = new Blob([zip[manifest.icon]], {\n                    //type: mime.default.getType(manifest.icon),\n                });\n\n                const win = await anura.wm.create(instance, {\n                    title: \"Anura File Manager\",\n                    width: \"450px\",\n                    height: \"525px\",\n                });\n\n                const iframe = document.createElement(\"iframe\");\n\n                iframe.setAttribute(\n                    \"src\",\n                    document.location.href.split(\"/\").slice(0, -1).join(\"/\") +\n                        \"/appview.html?manifest=\" +\n                        window.parent.ExternalApp.serializeArgs([\n                            JSON.stringify(manifest),\n                            URL.createObjectURL(icon),\n                            \"lib\",\n                        ]),\n                );\n\n                iframe.style =\n                    \"top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;\";\n\n                win.content.appendChild(iframe);\n\n                Object.assign(iframe.contentWindow, {\n                    anura,\n                    ExternalApp,\n                    instance,\n                    instanceWindow: win,\n                    install: {\n                        session: async () => {\n                            anura.notifications.add({\n                                title: \"Library Installing for Session\",\n                                description: `Library ${path.replace(\n                                    \"//\",\n                                    \"/\",\n                                )} is being installed, please wait`,\n                                timeout: 50000,\n                            });\n                            await fs.promises.mkdir(`${path}`);\n\n                            let filesRemaining = Object.keys(zip).length;\n\n                            Object.entries(zip).forEach(\n                                async ([relativePath, content]) => {\n                                    if (relativePath.endsWith(\"/\")) {\n                                        await fs.promises.mkdir(\n                                            `${path}/${relativePath}`,\n                                        );\n                                    } else {\n                                        await fs.promises.writeFile(\n                                            `${path}/${relativePath}`,\n                                            Buffer.from(content),\n                                        );\n                                    }\n                                    filesRemaining--;\n                                    console.log(filesRemaining);\n                                    if (filesRemaining == 0) {\n                                        await anura.registerExternalLib(\n                                            `/fs/${path}`.replace(\"//\", \"/\"),\n                                        );\n                                        anura.notifications.add({\n                                            title: \"Library Installed for Session\",\n                                            description: `Library ${path.replace(\n                                                \"//\",\n                                                \"/\",\n                                            )} has been installed temporarily, it will go away on refresh`,\n                                            timeout: 50000,\n                                        });\n                                    }\n                                },\n                                function (e) {\n                                    console.error(e);\n                                },\n                            );\n                        },\n                        permanent: async () => {\n                            anura.notifications.add({\n                                title: \"Library Installing\",\n                                description: `Library ${path.replace(\n                                    \"//\",\n                                    \"/\",\n                                )} is being installed`,\n                                timeout: 50000,\n                            });\n                            await fs.mkdir(\n                                anura.settings.get(\"directories\")[\"libs\"] +\n                                    \"/\" +\n                                    path.split(\"/\").slice(\"-1\")[0],\n                            );\n\n                            let filesRemaining = Object.keys(zip).length;\n\n                            Object.entries(zip).forEach(\n                                async ([relativePath, content]) => {\n                                    if (relativePath.endsWith(\"/\")) {\n                                        await fs.promises.mkdir(\n                                            `${anura.settings.get(\"directories\")[\"libs\"]}/${path.split(\"/\").slice(\"-1\")[0]}/${relativePath}`,\n                                        );\n                                    } else {\n                                        await fs.promises.writeFile(\n                                            `${anura.settings.get(\"directories\")[\"libs\"]}/${path.split(\"/\").slice(\"-1\")[0]}/${relativePath}`,\n                                            Buffer.from(content),\n                                        );\n                                    }\n                                    filesRemaining--;\n                                    console.log(filesRemaining);\n                                    if (filesRemaining == 0) {\n                                        await anura.registerExternalLib(\n                                            `/fs${anura.settings.get(\"directories\")[\"libs\"]}/${path.split(\"/\").slice(\"-1\")[0]}`.replace(\n                                                \"//\",\n                                                \"/\",\n                                            ),\n                                        );\n                                        anura.notifications.add({\n                                            title: \"Library Installed\",\n                                            description: `Library ${path.replace(\n                                                \"//\",\n                                                \"/\",\n                                            )} has been installed permanently`,\n                                            timeout: 50000,\n                                        });\n                                    }\n                                },\n                                function (e) {\n                                    console.error(e);\n                                },\n                            );\n                        },\n                    },\n                });\n\n                iframe.contentWindow.addEventListener(\"load\", () => {\n                    const matter = document.createElement(\"link\");\n                    matter.setAttribute(\"rel\", \"stylesheet\");\n                    matter.setAttribute(\"href\", \"/assets/matter.css\");\n                    iframe.contentDocument.head.appendChild(matter);\n                });\n            } else {\n                anura.files.open(fileSelected.getAttribute(\"data-path\"));\n            }\n        } else if (fileSelected.getAttribute(\"data-type\") == \"dir\") {\n            if (\n                fileSelected\n                    .getAttribute(\"data-path\")\n                    .split(\".\")\n                    .slice(\"-1\")[0] == \"app\"\n            ) {\n                try {\n                    let data;\n                    try {\n                        data = await fs.promises.readFile(\n                            `${fileSelected.getAttribute(\"data-path\")}/manifest.json`,\n                        );\n                    } catch {\n                        console.debug(\n                            \"Changing folder to \",\n                            fileSelected.getAttribute(\"data-path\"),\n                        );\n                        loadPath(fileSelected.getAttribute(\"data-path\"));\n                        return;\n                    }\n                    const manifest = JSON.parse(data);\n                    if (anura.apps[manifest.package]) {\n                        anura.apps[manifest.package].open();\n                        return;\n                    }\n\n                    const iconData = await fs.promises.readFile(\n                        `${fileSelected.getAttribute(\"data-path\")}/${manifest.icon}`,\n                    );\n\n                    const icon = new Blob([iconData]);\n\n                    const win = await anura.wm.create(instance, {\n                        title: \"Anura File Manager\",\n                        width: \"450px\",\n                        height: \"525px\",\n                    });\n\n                    const iframe = document.createElement(\"iframe\");\n                    iframe.setAttribute(\n                        \"src\",\n                        document.location.href\n                            .split(\"/\")\n                            .slice(0, -1)\n                            .join(\"/\") +\n                            \"/appview.html?manifest=\" +\n                            window.parent.ExternalApp.serializeArgs([\n                                data.toString(),\n                                URL.createObjectURL(icon),\n                                \"app\",\n                            ]),\n                    );\n                    iframe.style =\n                        \"top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;\";\n\n                    win.content.appendChild(iframe);\n                    Object.assign(iframe.contentWindow, {\n                        anura,\n                        ExternalApp,\n                        instance,\n                        instanceWindow: win,\n                        install: {\n                            session: async () => {\n                                await anura.registerExternalApp(\n                                    `/fs${fileSelected.getAttribute(\"data-path\")}`.replace(\n                                        \"//\",\n                                        \"/\",\n                                    ),\n                                );\n                                anura.notifications.add({\n                                    title: \"Application Installed for Session\",\n                                    description: `Application ${fileSelected\n                                        .getAttribute(\"data-path\")\n                                        .replace(\n                                            \"//\",\n                                            \"/\",\n                                        )} has been installed temporarily, it will go away on refresh`,\n                                    timeout: 50000,\n                                });\n                                win.close();\n                            },\n                            permanent: async () => {\n                                await fs.promises.rename(\n                                    fileSelected.getAttribute(\"data-path\"),\n                                    anura.settings.get(\"directories\")[\"apps\"] +\n                                        \"/\" +\n                                        fileSelected\n                                            .getAttribute(\"data-path\")\n                                            .split(\"/\")\n                                            .slice(\"-1\")[0],\n                                );\n                                await anura.registerExternalApp(\n                                    `/fs${anura.settings.get(\"directories\")[\"apps\"]}/${fileSelected.getAttribute(\"data-path\").split(\"/\").slice(\"-1\")[0]}`.replace(\n                                        \"//\",\n                                        \"/\",\n                                    ),\n                                );\n                                anura.notifications.add({\n                                    title: \"Application Installed\",\n                                    description: `Application ${fileSelected\n                                        .getAttribute(\"data-path\")\n                                        .replace(\n                                            \"//\",\n                                            \"/\",\n                                        )} has been installed permanently`,\n                                    timeout: 50000,\n                                });\n                                win.close();\n                            },\n                        },\n                    });\n\n                    iframe.contentWindow.addEventListener(\"load\", () => {\n                        const matter = document.createElement(\"link\");\n                        matter.setAttribute(\"rel\", \"stylesheet\");\n                        matter.setAttribute(\"href\", \"/assets/matter.css\");\n                        iframe.contentDocument.head.appendChild(matter);\n                    });\n                } catch (e) {\n                    anura.dialog.alert(\n                        `There was an error: ${e}`,\n                        \"Error installing app\",\n                    );\n                }\n            } else if (\n                fileSelected\n                    .getAttribute(\"data-path\")\n                    .split(\".\")\n                    .slice(\"-1\")[0] == \"lib\"\n            ) {\n                try {\n                    let data;\n                    try {\n                        data = await fs.promises.readFile(\n                            `${fileSelected.getAttribute(\"data-path\")}/manifest.json`,\n                        );\n                    } catch {\n                        console.debug(\n                            \"Changing folder to \",\n                            fileSelected.getAttribute(\"data-path\"),\n                        );\n                        loadPath(fileSelected.getAttribute(\"data-path\"));\n                        return;\n                    }\n\n                    const manifest = JSON.parse(data);\n                    if (anura.libs[manifest.package]) {\n                        return;\n                    }\n\n                    const iconData = await fs.promises.readFile(\n                        `${fileSelected.getAttribute(\"data-path\")}/${manifest.icon}`,\n                    );\n\n                    const icon = new Blob([iconData]);\n\n                    const win = await anura.wm.create(instance, {\n                        title: \"Anura File Manager\",\n                        width: \"450px\",\n                        height: \"525px\",\n                    });\n\n                    const iframe = document.createElement(\"iframe\");\n\n                    iframe.setAttribute(\n                        \"src\",\n                        document.location.href\n                            .split(\"/\")\n                            .slice(0, -1)\n                            .join(\"/\") +\n                            \"/appview.html?manifest=\" +\n                            window.parent.ExternalApp.serializeArgs([\n                                data.toString(),\n                                URL.createObjectURL(icon),\n                                \"lib\",\n                            ]),\n                    );\n\n                    iframe.style =\n                        \"top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;\";\n\n                    win.content.appendChild(iframe);\n\n                    Object.assign(iframe.contentWindow, {\n                        anura,\n                        ExternalApp,\n                        instance,\n                        instanceWindow: win,\n                        install: {\n                            session: async () => {\n                                await anura.registerExternalLib(\n                                    `/fs${fileSelected.getAttribute(\"data-path\")}`.replace(\n                                        \"//\",\n                                        \"/\",\n                                    ),\n                                );\n                                anura.notifications.add({\n                                    title: \"Library Installed for Session\",\n                                    description: `Library ${fileSelected\n                                        .getAttribute(\"data-path\")\n                                        .replace(\n                                            \"//\",\n                                            \"/\",\n                                        )} has been installed temporarily, it will go away on refresh`,\n                                    timeout: 50000,\n                                });\n                                win.close();\n                            },\n                            permanent: async () => {\n                                await fs.promises.rename(\n                                    fileSelected.getAttribute(\"data-path\"),\n                                    anura.settings.get(\"directories\")[\"libs\"] +\n                                        \"/\" +\n                                        fileSelected\n                                            .getAttribute(\"data-path\")\n                                            .split(\"/\")\n                                            .slice(\"-1\")[0],\n                                );\n                                await anura.registerExternalLib(\n                                    `/fs${anura.settings.get(\"directories\")[\"libs\"]}/${fileSelected.getAttribute(\"data-path\").split(\"/\").slice(\"-1\")[0]}`.replace(\n                                        \"//\",\n                                        \"/\",\n                                    ),\n                                );\n                                anura.notifications.add({\n                                    title: \"Library Installed\",\n                                    description: `Library ${fileSelected\n                                        .getAttribute(\"data-path\")\n                                        .replace(\n                                            \"//\",\n                                            \"/\",\n                                        )} has been installed permanently`,\n                                    timeout: 50000,\n                                });\n                                win.close();\n                            },\n                        },\n                    });\n                    iframe.contentWindow.addEventListener(\"load\", () => {\n                        const matter = document.createElement(\"link\");\n                        matter.setAttribute(\"rel\", \"stylesheet\");\n                        matter.setAttribute(\"href\", \"/assets/matter.css\");\n                        iframe.contentDocument.head.appendChild(matter);\n                    });\n                } catch (e) {\n                    anura.notifications.add({\n                        title: \"Library Install Error\",\n                        description: `Library had an error installing: ${e}`,\n                        timeout: 50000,\n                    });\n                }\n            } else {\n                console.debug(\n                    \"Changing folder to \",\n                    fileSelected.getAttribute(\"data-path\"),\n                );\n                loadPath(fileSelected.getAttribute(\"data-path\"));\n            }\n        } else {\n            console.warn(\n                \"Unknown filetype \",\n                fileSelected.getAttribute(\"data-type\"),\n                \" doing nothing!\",\n            );\n        }\n    } else {\n        // MULTIPLE FILE SELECTION //\n\n        console.error(\"raff please implement\");\n    }\n}\n\nfunction setBreadcrumbs(path) {\n    path = path.replace(/(\\/)\\1+/g, \"$1\");\n    var pathSplit = path.split(\"/\");\n    pathSplit[0] = \"My files\";\n    var breadcrumbs = document.querySelector(\".breadcrumbs\");\n    breadcrumbs.setAttribute(\"data-current-path\", path);\n    breadcrumbs.innerHTML = \"\";\n    if (\n        pathSplit.length == 2 &&\n        pathSplit[0] == \"My files\" &&\n        pathSplit[1] == \"\"\n    ) {\n        var breadcrumb = document.createElement(\"button\");\n        breadcrumb.innerText = \"My files\";\n        breadcrumb.addEventListener(\"click\", () => {\n            loadPath(\"/\");\n        });\n        breadcrumbs.appendChild(breadcrumb);\n        return;\n    }\n    for (var i = 0; i < pathSplit.length; i++) {\n        console.log(i);\n        var breadcrumb = document.createElement(\"button\");\n        breadcrumb.innerText = pathSplit[i];\n        var index = i;\n        breadcrumb.addEventListener(\"click\", () => {\n            loadPath(\"/\" + pathSplit.slice(1, index).join(\"/\"));\n        });\n        breadcrumbs.appendChild(breadcrumb);\n        if (pathSplit[i] !== pathSplit[pathSplit.length - 1]) {\n            var breadcrumbSpan = document.createElement(\"span\");\n            breadcrumbSpan.innerText = \">\";\n            breadcrumbs.appendChild(breadcrumbSpan);\n        }\n    }\n}\n\nasync function newFolder(path) {\n    if (path === undefined) {\n        let folderName = await anura.dialog.prompt(\"Folder Name: \");\n        if (folderName) {\n            path =\n                document\n                    .querySelector(\".breadcrumbs\")\n                    .getAttribute(\"data-current-path\") +\n                \"/\" +\n                folderName;\n        }\n    }\n    if (path) {\n        fs.mkdir(path);\n        reload();\n    }\n}\n\nasync function newFile(path) {\n    if (path === undefined) {\n        let fileName = await anura.dialog.prompt(\"File Name: \");\n        console.log(fileName)\n        if (fileName) {\n            path =\n                document\n                    .querySelector(\".breadcrumbs\")\n                    .getAttribute(\"data-current-path\") +\n                \"/\" +\n                fileName;\n        }\n    }\n    await fs.promises.writeFile(path, \"\");\n    reload();\n}\n\nfunction reload() {\n    loadPath(\n        document\n            .querySelector(\".breadcrumbs\")\n            .getAttribute(\"data-current-path\"),\n    );\n}\n\nfunction reload() {\n    loadPath(\n        document\n            .querySelector(\".breadcrumbs\")\n            .getAttribute(\"data-current-path\"),\n    );\n}\n\nfunction upload() {\n    let fauxput = document.createElement(\"input\"); // fauxput - fake input that isn't shown or ever added to page TODO: think of a better name for this variable\n    fauxput.type = \"file\";\n    fauxput.onchange = async (e) => {\n        const file = await e.target.files[0];\n        const content = await file.arrayBuffer();\n        fs.writeFile(\n            `${document\n                .querySelector(\".breadcrumbs\")\n                .getAttribute(\"data-current-path\")}/${file.name}`,\n            Buffer.from(content),\n            function (err) {\n                reload();\n            },\n        );\n    };\n    fauxput.click();\n}\n\nfunction deleteFile() {\n    if (currentlySelected.length == 0) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description:\n                \"BUG: You have no files selected, right clicking does not select files\",\n            timeout: 5000,\n        });\n    }\n    currentlySelected.forEach(async (item) => {\n        await sh.rm(\n            item.getAttribute(\"data-path\"),\n            {\n                recursive: true,\n            },\n            function (err) {\n                if (err) throw err;\n                reload();\n            },\n        );\n    });\n}\n\nfunction copy() {\n    clipboard = currentlySelected;\n    removeAfterPaste = false;\n}\n\nfunction cut() {\n    clipboard = currentlySelected;\n    removeAfterPaste = true;\n}\n\nasync function paste() {\n    const path = document\n        .querySelector(\".breadcrumbs\")\n        .getAttribute(\"data-current-path\");\n    if (!removeAfterPaste) {\n        for (item of clipboard) {\n            if (item.attributes[\"data-type\"].value == \"dir\") {\n                //INPUT\n                let newPath = path;\n                let oldPath = item.attributes[\"data-path\"].value;\n\n                // Normalize (remove trailing slash, replace // with /)\n                if (oldPath.endsWith(\"/\")) oldPath = oldPath.slice(0, -1);\n                if (newPath.endsWith(\"/\")) newPath = newPath.slice(0, -1);\n                newPath = newPath.replace(\"//\", \"/\");\n                oldPath = oldPath.replace(\"//\", \"/\");\n\n                const oldFolderName = oldPath.split(\"/\").pop();\n                // Search\n                const files = await sh.promises.ls(oldPath, {\n                    recursive: true,\n                });\n                console.log(files);\n                // Apply\n                for (file of files) {\n                    // Creating the relative path string\n                    let path = file.split(\"/\");\n                    const filename = path.pop();\n                    path = path.join(\"/\");\n                    path = path.substring(oldPath.length);\n\n                    await sh.promises.mkdirp(\n                        `${newPath}/${oldFolderName}${path}`,\n                    );\n                    const data = await fs.promises.readFile(\n                        `${oldPath}${path}/${filename}`,\n                    );\n                    await fs.promises.writeFile(\n                        `${newPath}/${oldFolderName}${path}/${filename}`,\n                        data,\n                    );\n                }\n            } else {\n                let origin = item.attributes[\"data-path\"].value;\n                fs.promises.writeFile(\n                    `${path}/${origin.split(\"/\").slice(\"-1\")[0]}`,\n                    await fs.promises.readFile(origin),\n                );\n            }\n        }\n        clipboard = [];\n        reload();\n    }\n    if (removeAfterPaste) {\n        // cut\n        for (const item of clipboard) {\n            itemName = item.getAttribute(\"data-path\");\n            await fs.promises.rename(\n                itemName,\n                `${path}/${itemName.split(\"/\").slice(\"-1\")[0]}`,\n            );\n            reload();\n        }\n        clipboard = [];\n    }\n}\n\nasync function rename() {\n    const path = document\n        .querySelector(\".breadcrumbs\")\n        .getAttribute(\"data-current-path\");\n    if (currentlySelected.length == 0) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description:\n                \"BUG: You have no files selected, right clicking does not select files\",\n            timeout: 5000,\n        });\n\n        return;\n    }\n    if (currentlySelected.length > 1) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description: \"Renaming only works with one file\",\n            timeout: 5000,\n        });\n        return;\n    }\n    let filename = await anura.dialog.prompt(\"Filename:\");\n    if (filename) {\n        fs.rename(\n            currentlySelected[0].getAttribute(\"data-path\"),\n            `${path}/${filename}`,\n            function (err) {\n                if (err) throw err;\n                reload();\n            },\n        );\n    }\n}\n\nfunction installSession() {\n    if (currentlySelected.length == 0) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description:\n                \"BUG: You have no files selected, right clicking does not select files\",\n            timeout: 5000,\n        });\n        return;\n    }\n    currentlySelected.forEach(async (item) => {\n        const path = item.getAttribute(\"data-path\");\n        const ext = path.split(\".\").slice(\"-1\")[0];\n        fs.stat(path, async function (err, stats) {\n            if (stats.isDirectory()) {\n                if (ext == \"app\") {\n                    try {\n                        await anura.registerExternalApp(\n                            `/fs${path}`.replace(\"//\", \"/\"),\n                        );\n                        anura.notifications.add({\n                            title: \"Application Installed for Session\",\n                            description: `Application ${path.replace(\n                                \"//\",\n                                \"/\",\n                            )} has been installed temporarily, it will go away on refresh`,\n                            timeout: 50000,\n                        });\n                    } catch (e) {\n                        anura.dialog.alert(\n                            `There was an error: ${e}`,\n                            \"Error installing app\",\n                        );\n                    }\n                }\n                if (ext == \"lib\") {\n                    try {\n                        await anura.registerExternalLib(\n                            `/fs${path}`.replace(\"//\", \"/\"),\n                        );\n                        anura.notifications.add({\n                            title: \"Library Installed for Session\",\n                            description: `Library ${path.replace(\n                                \"//\",\n                                \"/\",\n                            )} has been installed temporarily, it will go away on refresh`,\n                            timeout: 50000,\n                        });\n                    } catch (e) {\n                        anura.dialog.alert(\n                            `There was an error: ${e}`,\n                            \"Error installing library\",\n                        );\n                    }\n                }\n            }\n        });\n    });\n}\n\nfunction installPermanent() {\n    if (currentlySelected.length == 0) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description:\n                \"BUG: You have no files selected, right clicking does not select files\",\n            timeout: 5000,\n        });\n        return;\n    }\n    currentlySelected.forEach(async (item) => {\n        const path = item.getAttribute(\"data-path\");\n        const ext = path.split(\".\").slice(\"-1\")[0];\n\n        fs.stat(path, async function (err, stats) {\n            if (stats.isDirectory()) {\n                if (ext == \"app\") {\n                    const destination =\n                        anura.settings.get(\"directories\")[\"apps\"];\n                    try {\n                        await fs.promises.rename(\n                            path,\n                            destination + \"/\" + path.split(\"/\").slice(\"-1\")[0],\n                        );\n                        await anura.registerExternalApp(\n                            `/fs${destination}/${path.split(\"/\").slice(\"-1\")[0]}`.replace(\n                                \"//\",\n                                \"/\",\n                            ),\n                        );\n                    } catch (e) {\n                        anura.notifications.add({\n                            title: \"Application Install Error\",\n                            description: `Application had an error installing: ${e}`,\n                            timeout: 50000,\n                        });\n                    }\n                }\n                if (ext == \"lib\") {\n                    const destination =\n                        anura.settings.get(\"directories\")[\"libs\"];\n                    try {\n                        sh.ls(\n                            path,\n                            {\n                                recursive: true,\n                            },\n                            async function (err, entries) {\n                                if (err) throw err;\n                                let items = [];\n                                let dirs = [];\n                                entries.forEach((entry) => {\n                                    function recurse(dirnode, path) {\n                                        dirnode.contents.forEach((entry) => {\n                                            if (entry.type === \"DIRECTORY\") {\n                                                recurse(\n                                                    entry,\n                                                    path + \"/\" + entry.name,\n                                                );\n                                                dirs.push(\n                                                    path + \"/\" + entry.name,\n                                                );\n                                            } else {\n                                                items.push(\n                                                    path + \"/\" + entry.name,\n                                                );\n                                            }\n                                        });\n                                    }\n\n                                    const topLevelFolder = path;\n                                    dirs.push(path);\n                                    if (entry.type === \"DIRECTORY\") {\n                                        recurse(entry, path + \"/\" + entry.name);\n                                        dirs.push(path + \"/\" + entry.name);\n                                    } else {\n                                        items.push(path + \"/\" + entry.name);\n                                    }\n                                });\n                                destItems = [];\n                                destDirs = [];\n                                numberToSubBy =\n                                    path.length - path.split(\"/\").pop().length;\n\n                                for (item in items) {\n                                    destItems.push(\n                                        destination +\n                                            \"/\" +\n                                            items[item].slice(numberToSubBy),\n                                    );\n                                }\n                                for (dir in dirs) {\n                                    destDirs.push(\n                                        destination +\n                                            \"/\" +\n                                            dirs[dir].slice(numberToSubBy),\n                                    );\n                                }\n                                console.log(\"initials\");\n                                console.log(items);\n                                console.log(\"destinations\");\n                                console.log(destItems);\n                                console.log(\"directories to mkdir -p \");\n                                console.log(destDirs);\n                                for (dir in destDirs) {\n                                    await new Promise((resolve, reject) => {\n                                        sh.mkdirp(\n                                            destDirs[dir],\n                                            function (err) {\n                                                if (err) {\n                                                    reject(err);\n                                                    console.error(err);\n                                                }\n                                                resolve();\n                                            },\n                                        );\n                                    });\n                                }\n\n                                for (item in items) {\n                                    await new Promise((resolve, reject) => {\n                                        fs.readFile(\n                                            items[item],\n                                            function (err, data) {\n                                                fs.writeFile(\n                                                    destItems[item],\n                                                    data,\n                                                    function (err) {\n                                                        if (err) {\n                                                            reject(err);\n                                                            console.error(err);\n                                                        }\n                                                        resolve();\n                                                    },\n                                                );\n                                            },\n                                        );\n                                    });\n                                }\n\n                                console.log(\"finished copying files???\");\n\n                                await anura.registerExternalLib(\n                                    `/fs${destination}/${path.split(\"/\").slice(\"-1\")[0]}`.replace(\n                                        \"//\",\n                                        \"/\",\n                                    ),\n                                );\n                                anura.notifications.add({\n                                    title: \"Library Installed\",\n                                    description: `Library ${path.replaceAll(\n                                        \"/\",\n                                        \"\",\n                                    )} has been installed permanently.`,\n                                    timeout: 50000,\n                                });\n\n                                reload();\n                            },\n                        );\n                    } catch (e) {\n                        anura.notifications.add({\n                            title: \"Library Install Error\",\n                            description: `Library had an error installing: ${e}`,\n                            timeout: 50000,\n                        });\n                    }\n                }\n            }\n        });\n    });\n}\n\n// Context menu version of the loadPath function\n// Used to enter app and lib folders, as double\n// clicking on them will install them.\nfunction navigate() {\n    if (currentlySelected.length == 1) {\n        loadPath(currentlySelected[0].getAttribute(\"data-path\"));\n    }\n    // Can't navigate to multiple folders\n}\n\nfunction unzip(zip) {\n    return new Promise((res, rej) => {\n        window.parent.tb.fflate.unzip(zip, (err, unzipped) => {\n            if (err) rej(err);\n            else res(unzipped);\n        });\n    });\n}\n"
  },
  {
    "path": "public/apps/libfilepicker.lib/GUI.js",
    "content": "// This context menu is for files and folders\nconst newcontextmenu = new parent.anura.ContextMenu();\n// This context menu is for applications and libraries\nconst appcontextmenu = new parent.anura.ContextMenu();\n// This context menu is for when no files are selected\nconst emptycontextmenu = new parent.anura.ContextMenu();\n\n// Helper to add context menu items to both menus\nfunction addContextMenuItem(name, func) {\n    newcontextmenu.addItem(name, func);\n    appcontextmenu.addItem(name, func);\n}\n\n// addContextMenuItem(\"Get Info\", function () {});\n// addContextMenuItem(\"Pin to Shelf\", function () {});\naddContextMenuItem(\"Cut\", function () {\n    cut();\n});\naddContextMenuItem(\"Copy\", function () {\n    copy();\n});\naddContextMenuItem(\"Paste\", function () {\n    paste();\n});\naddContextMenuItem(\"Delete\", function () {\n    deleteFile();\n});\naddContextMenuItem(\"Rename\", function () {\n    rename();\n});\n\nappcontextmenu.addItem(\"Install (Session)\", function () {\n    // While this is the same as double clicking, it's still useful to have the verbosely named option\n    installSession();\n});\n\nappcontextmenu.addItem(\"Install (Permanent)\", function () {\n    // This is not the same as double clicking, as it will install the app permanently\n    installPermanent();\n});\n\nappcontextmenu.addItem(\"Navigate\", function () {\n    // Normally, double clicking a folder will navigate into it, but for apps and libs, this is not the case\n    navigate();\n});\n\nemptycontextmenu.addItem(\"Upload from PC\", function () {\n    upload();\n});\nemptycontextmenu.addItem(\"New folder\", function () {\n    newFolder();\n});\nemptycontextmenu.addItem(\"New file\", function () {\n    newFile();\n})\nemptycontextmenu.addItem(\"Refresh\", function () {\n    reload();\n});\n\nconst min = 150;\n// The max (fr) values for grid-template-columns\nconst columnTypeToRatioMap = {\n    icon: 0.1,\n    name: 3,\n    size: 1,\n    type: 1,\n    modified: 1,\n};\n\nconst table = document.querySelector(\"table\");\n/*\nThe following will soon be filled with column objects containing\nthe header element and their size value for grid-template-columns\n*/\nconst columns = [];\nlet headerBeingResized;\n\n// The next three functions are mouse event callbacks\n\n// Where the magic happens. I.e. when they're actually resizing\nconst onMouseMove = (e) =>\n    requestAnimationFrame(() => {\n        console.log(\"onMouseMove\");\n\n        (window.getSelection\n            ? window.getSelection()\n            : document.selection\n        ).empty();\n\n        // Calculate the desired width\n        horizontalScrollOffset = document.documentElement.scrollLeft;\n        const width =\n            horizontalScrollOffset + e.clientX - headerBeingResized.offsetLeft;\n\n        // Update the column object with the new size value\n        const column = columns.find(\n            ({ header }) => header === headerBeingResized,\n        );\n        column.size = Math.max(min, width) + \"px\"; // Enforce our minimum\n\n        // For the other headers which don't have a set width, fix it to their computed width\n        columns.forEach((column) => {\n            if (column.size.startsWith(\"minmax\")) {\n                // isn't fixed yet (it would be a pixel value otherwise)\n                column.size = parseInt(column.header.clientWidth, 10) + \"px\";\n            }\n        });\n\n        /*\n      Update the column sizes\n      Reminder: grid-template-columns sets the width for all columns in one value\n  */\n        table.style.gridTemplateColumns = columns\n            .map(({ header, size }) => size)\n            .join(\" \");\n    });\n\n// Clean up event listeners, classes, etc.\nconst onMouseUp = () => {\n    console.log(\"onMouseUp\");\n\n    window.removeEventListener(\"mousemove\", onMouseMove);\n    window.removeEventListener(\"mouseup\", onMouseUp);\n    headerBeingResized.classList.remove(\"header--being-resized\");\n    headerBeingResized = null;\n};\n\n// Get ready, they're about to resize\nconst initResize = ({ target }) => {\n    console.log(\"initResize\");\n\n    headerBeingResized = target.parentNode;\n    window.addEventListener(\"mousemove\", onMouseMove);\n    window.addEventListener(\"mouseup\", onMouseUp);\n    headerBeingResized.classList.add(\"header--being-resized\");\n};\n\n// Let's populate that columns array and add listeners to the resize handles\ndocument.querySelectorAll(\"th\").forEach((header) => {\n    const max = columnTypeToRatioMap[header.dataset.type] + \"fr\";\n    columns.push({\n        header,\n        // The initial size value for grid-template-columns:\n        size: `minmax(${min}px, ${max})`,\n    });\n    header\n        .querySelector(\".resize-handle\")\n        .addEventListener(\"mousedown\", initResize);\n});\n"
  },
  {
    "path": "public/apps/libfilepicker.lib/README.md",
    "content": "# Usage\n\nAdding the library to your app:\n```html\n<script type=\"module\">\n    let { selectFile, selectFolder } = await anura.import(\"anura.filepicker\")\n    window.selectFile = selectFile\n    window.selectFolder = selectFolder\n</script>\n```\n\nPicking a file:\n```js\n// Synchronously:\nselectFile().then((filePath) => {\n// implement your logic here\n});\n// Asynchronously:\nawait selectFile()\n\n// Parameters:\n\n// selectFile(fileExtension {can use regex or just be a file extension}) \n// Example: selectFile(\"txt\")\n\n// Returning:\n\n// One file\n\"/example.txt\"\n\n// Multiple files\n[\"/example.txt\", \"/example1.txt\"]\n```\n\nPicking a folder:\n```js\n// Synchronously:\nselectFolder().then((filePath) => {\n// implement your logic here\n});\n// Asynchronously:\nawait selectFolder()\n\n// Returns:\n\n// One folder\n\"/folder\"\n\n// Multiple folders\n[\"/folder\", \"/folder2\"]\n```"
  },
  {
    "path": "public/apps/libfilepicker.lib/file.html",
    "content": "<html>\n    <head>\n        <link rel=\"stylesheet\" href=\"filemanager.css\" />\n        <link rel=\"stylesheet\" href=\"/assets/materialsymbols.css\" />\n        <script src=\"/assets/libs/filer.min.js\"></script>\n    </head>\n\n    <body>\n        <div class=\"container\">\n            <div class=\"sidebar\">\n                <button>\n                    <i class=\"material-symbols-outlined\">history</i>Recent\n                </button>\n                <hr />\n                <button>\n                    <i class=\"material-symbols-outlined\">laptop_chromebook</i>My files\n                </button>\n            </div>\n            <div class=\"fileView\">\n                <div class=\"topbar\">\n                    <div class=\"breadcrumbs\">\n                        <button>My files</button><span>></span\n                        ><button>owo :3</button>\n                    </div>\n                    <div class=\"sep\"></div>\n                    <button>\n                        <i class=\"material-symbols-outlined\">search</i>\n                    </button>\n                    <button>\n                        <i class=\"material-symbols-outlined\">table_rows</i>\n                    </button>\n                    <button>\n                        <i class=\"material-symbols-outlined\">sort_by_alpha</i>\n                        <!--<i class=\"fa-solid fa-arrow-down-z-a fa-lg\"></i> - opposite-->\n                    </button>\n                    <button>\n                        <i class=\"material-symbols-outlined\">settings</i>\n                    </button>\n                </div>\n                <hr />\n                <table>\n                    <thead>\n                        <tr>\n                            <th data-type=\"icon\">\n                                Icon<span\n                                    class=\"resize-handle hidden-resize-handle\"\n                                ></span>\n                            </th>\n                            <th data-type=\"name\">\n                                Name<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"size\">\n                                Size<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"type\">\n                                Type<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"modified\">\n                                Date modified<span class=\"resize-handle\"></span>\n                            </th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        <tr>\n                            <td><i class=\"fa-brands fa-rust\"></i></td>\n                            <td>nya.rs</td>\n                            <td>1 MB</td>\n                            <td>Rust source code</td>\n                            <td>Today 17:00</td>\n                        </tr>\n                    </tbody>\n                </table>\n                <div class=\"topbar\">\n                    <div class=\"sep\"></div>\n                    <button onclick=\"selectAction(currentlySelected);\">\n                        Select\n                    </button>\n                </div>\n            </div>\n        </div>\n        <script src=\"GUI.js\"></script>\n        <script>\n            let selectorType = \"file\"\n            let url = new URL(document.location)\n            let regex = url.searchParams.get(\"regex\")\n            if (regex) {\n                regex = decodeURIComponent(regex)\n                fileRegex = new RegExp(regex)\n            }\n        </script>\n        <script src=\"operations.js\"></script>\n        <div style=\"display: none\" id=\"contextMenu\">\n            <button>Get info</button>\n            <button>Pin to shelf</button>\n            <button onclick=\"reload()\">Refresh</button>\n            <hr />\n            <button class=\"needs-selection\" onclick=\"cut()\">Cut</button>\n            <button class=\"needs-selection\" onclick=\"copy()\">Copy</button>\n            <button onclick=\"paste()\">Paste</button>\n            <button class=\"needs-selection\" onclick=\"deleteFile()\">\n                Delete\n            </button>\n            <button onclick=\"upload()\">Upload from PC</button>\n            <hr />\n            <button onclick=\"newFolder()\">New folder</button>\n        </div>\n    </body>\n</html>\n"
  },
  {
    "path": "public/apps/libfilepicker.lib/filemanager.css",
    "content": "@font-face {\n    font-family: Roboto;\n    src: url(\"/assets/fonts/Roboto-Regular.ttf\") format(\"truetype\");\n }\n\n* {\n    color: #ffffff;\n    font-family: \"Roboto\", sans-serif;\n    user-select: none;\n}\nbody {\n    margin: 0;\n}\n.container {\n    background-color: #202124;\n    width: 100%;\n    height: 100%;\n    display: flex;\n    flex-direction: row;\n}\n.sidebar {\n    display: flex;\n    flex-direction: column;\n    flex: 0 0 15em;\n    margin-right: 3em;\n}\n.sidebar button {\n    height: 3em;\n    border-bottom-right-radius: 5em;\n    border-top-right-radius: 5em;\n    background-color: #202124;\n    border: none;\n    text-align: left;\n    display: flex;\n    align-items: center;\n}\n.sidebar button:hover {\n    background-color: #2d2e31;\n}\n.sidebar button:active {\n    background-color: #3f4d63;\n}\n\n.sidebar i {\n    margin-right: 1em;\n    margin-left: 0.5em;\n}\n\n.fileView {\n    flex-grow: 1;\n    display: flex;\n    flex-direction: column;\n    overflow: hidden;\n}\n\n.resize-handle {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    background: black;\n    opacity: 0;\n    width: 3px;\n    cursor: col-resize;\n}\n\n.hidden-resize-handle {\n    display: none;\n}\n\n.resize-handle:hover,\n.header--being-resized .resize-handle {\n    opacity: 0.5;\n}\n\nth:hover .resize-handle {\n    opacity: 0.3;\n}\n\ntable {\n    flex-grow: 1;\n    overflow: scroll;\n    display: grid;\n    grid-template-columns: min-content 3fr 1fr 1fr 1fr;\n    grid-auto-rows: min-content;\n    overflow-x: hidden;\n    overflow-y: auto;\n}\n\ntr,\nthead,\ntbody {\n    display: contents;\n}\n\nth {\n    position: relative;\n}\n\nth > tr {\n    margin: 0;\n}\n\ntr > * {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    padding-top: 0.5em;\n    padding-bottom: 0.5em;\n}\n\n.iconContainer {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n}\n\n.icon {\n    height: 1em;\n    width: 1em;\n}\n\n.selected > * {\n    background-color: color-mix(in srgb, #8aadf4, #303446) !important;\n}\n\n.hover > * {\n    background-color: #414559;\n}\n\n.topbar {\n    margin-top: 0.3em;\n    margin-right: 1em;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n}\n\n.topbar button {\n    border-radius: 1em;\n    border: none;\n    background-color: #202124;\n    height: 2.25em;\n}\n\n.topbar button > i {\n    padding: 0.25em;\n}\n\n.topbar button:hover {\n    background-color: #292c3c;\n}\n\n.topbar button:active {\n    background-color: #232634 !important;\n}\n\n.topbar .sep {\n    flex-grow: 1;\n}\n\n.topbar .breadcrumbs button {\n    font-size: 16px;\n    margin-right: 0.25em;\n    margin-left: 0.25em;\n    border-radius: 0;\n    display: inline-block;\n}\n\n.topbar .breadcrumbs button:hover {\n    background-color: transparent;\n}\n\nhr {\n    color: transparent;\n    border-bottom: 1px solid #414559;\n    opacity: 0.25;\n    width: 100%;\n}"
  },
  {
    "path": "public/apps/libfilepicker.lib/folder.html",
    "content": "<html>\n    <head>\n        <link rel=\"stylesheet\" href=\"filemanager.css\" />\n        <link rel=\"stylesheet\" href=\"/assets/materialsymbols.css\" />\n        <script src=\"/assets/libs/filer.min.js\"></script>\n    </head>\n\n    <body>\n        <div class=\"container\">\n            <div class=\"sidebar\">\n                <button>\n                    <i class=\"material-symbols-outlined\">history</i>Recent\n                </button>\n                <hr />\n                <button>\n                    <i class=\"material-symbols-outlined\">laptop_chromebook</i>My files\n                </button>\n            </div>\n            <div class=\"fileView\">\n                <div class=\"topbar\">\n                    <div class=\"breadcrumbs\">\n                        <button>My files</button><span>></span\n                        ><button>owo :3</button>\n                    </div>\n                    <div class=\"sep\"></div>\n                    <button>\n                        <i class=\"material-symbols-outlined\">search</i>\n                    </button>\n                    <button>\n                        <i class=\"material-symbols-outlined\">table_rows</i>\n                    </button>\n                    <button>\n                        <i class=\"material-symbols-outlined\">sort_by_alpha</i>\n                        <!--<i class=\"fa-solid fa-arrow-down-z-a fa-lg\"></i> - opposite-->\n                    </button>\n                    <button>\n                        <i class=\"material-symbols-outlined\">settings</i>\n                    </button>\n                </div>\n                <hr />\n                <table>\n                    <thead>\n                        <tr>\n                            <th data-type=\"icon\">\n                                Icon<span\n                                    class=\"resize-handle hidden-resize-handle\"\n                                ></span>\n                            </th>\n                            <th data-type=\"name\">\n                                Name<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"size\">\n                                Size<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"type\">\n                                Type<span class=\"resize-handle\"></span>\n                            </th>\n                            <th data-type=\"modified\">\n                                Date modified<span class=\"resize-handle\"></span>\n                            </th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        <tr>\n                            <td><i class=\"fa-brands fa-rust\"></i></td>\n                            <td>nya.rs</td>\n                            <td>1 MB</td>\n                            <td>Rust source code</td>\n                            <td>Today 17:00</td>\n                        </tr>\n                    </tbody>\n                </table>\n                <div class=\"topbar\">\n                    <div class=\"sep\"></div>\n                    <button onclick=\"selectAction(currentlySelected);\">\n                        Select\n                    </button>\n                </div>\n            </div>\n        </div>\n        <script src=\"GUI.js\"></script>\n        <script>\n            let selectorType = \"dir\"\n            let url = new URL(document.location)\n            let regex = url.searchParams.get(\"regex\")\n            if (regex) {\n                regex = decodeURIComponent(regex)\n                fileRegex = new RegExp(regex)\n            }\n        </script>\n        <script src=\"operations.js\"></script>\n        <div style=\"display: none\" id=\"contextMenu\">\n            <button>Get info</button>\n            <button>Pin to shelf</button>\n            <button onclick=\"reload()\">Refresh</button>\n            <hr />\n            <button class=\"needs-selection\" onclick=\"cut()\">Cut</button>\n            <button class=\"needs-selection\" onclick=\"copy()\">Copy</button>\n            <button onclick=\"paste()\">Paste</button>\n            <button class=\"needs-selection\" onclick=\"deleteFile()\">\n                Delete\n            </button>\n            <button onclick=\"upload()\">Upload from PC</button>\n            <hr />\n            <button onclick=\"newFolder()\">New folder</button>\n        </div>\n    </body>\n</html>\n"
  },
  {
    "path": "public/apps/libfilepicker.lib/handler.js",
    "content": "export function selectFile(options) {\n    return new Promise(async (resolve, reject) => {\n        await window.tb.dialog.FileBrowser({\n            title: \"Select a File\",\n            onOk: async (val) => {\n                resolve(val)\n            },\n            onCancel: () => {\n                reject('User Rejected')\n            }\n        })\n    })\n}\n\nexport function selectFolder(options) {\n    return new Promise(async (resolve, reject) => {\n        await window.tb.dialog.DirectoryBrowser({\n            title: \"Select a Directory\",\n            onOk: async (val) => {\n                resolve(val)\n            },\n            onCancel: () => {\n                reject('User Rejected')\n            }\n        })\n    })\n}\n"
  },
  {
    "path": "public/apps/libfilepicker.lib/install.js",
    "content": "// Runs on every boot as the lib is installed\nexport default async function install(_, filePickerLib) {\n    const { selectFile, selectFolder } = await filePickerLib.getImport();\n    top.navigator.serviceWorker.addEventListener(\"message\", async (event) => {\n        if (event.data.anura_target === \"anura.filepicker\") {\n            if (event.data.type === \"folder\") {\n                let folders;\n                let cancelled = false;\n                try {\n                    folders = await selectFolder({ regex: event.data.regex });\n                    if (typeof folders === \"string\") {\n                        folders = [folders];\n                    }\n                } catch (e) {\n                    folders = [];\n                    cancelled = true;\n                }\n                top.navigator.serviceWorker.controller.postMessage({\n                    anura_target: \"anura.filepicker.result\",\n                    id: event.data.id,\n                    value: {\n                        folders,\n                        cancelled,\n                    },\n                });\n                return;\n            } else {\n                let files;\n                let cancelled = false;\n                try {\n                    files = await selectFile({ regex: event.data.regex });\n                    if (typeof files === \"string\") {\n                        files = [files];\n                    }\n                } catch (e) {\n                    files = [];\n                    cancelled = true;\n                }\n                top.navigator.serviceWorker.controller.postMessage({\n                    anura_target: \"anura.filepicker.result\",\n                    id: event.data.id,\n                    value: {\n                        files,\n                        cancelled,\n                    },\n                });\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "public/apps/libfilepicker.lib/manifest.json",
    "content": "{\n    \"name\": \"File Picker\",\n    \"icon\": \"files.png\",\n    \"package\": \"anura.filepicker\",\n    \"versions\": {\n        \"1.0.0\": \"handler.js\"\n    },\n    \"currentVersion\": \"1.0.0\",\n    \"installHook\": \"install.js\"\n}"
  },
  {
    "path": "public/apps/libfilepicker.lib/operations.js",
    "content": "var currentlySelected = [];\nvar clipboard = [];\nvar removeAfterPaste = false;\n\nwindow.fs = parent.anura.fs;\nwindow.anura = parent.anura;\nwindow.Buffer = Filer.Buffer;\nlet sh = new fs.Shell();\n\nfunction loadPath(path) {\n    console.debug(\"loading path: \", path);\n    fs.readdir(path, (err, files) => {\n        if (err) throw err;\n        setBreadcrumbs(path);\n        let table = document.querySelector(\"tbody\");\n        table.innerHTML = \"\";\n        files.forEach((file) => {\n            let row = document.createElement(\"tr\");\n            let iconContainer = document.createElement(\"td\");\n            let icon = document.createElement(\"img\");\n            let name = document.createElement(\"td\");\n            let size = document.createElement(\"td\");\n            let description = document.createElement(\"td\");\n            let date = document.createElement(\"td\");\n            let type = document.createElement(\"td\");\n            \n            iconContainer.className = \"iconContainer\";\n            icon.className = \"icon\";\n            fs.stat(`${path}/${file}`, function (err, stats) {\n                if (err) throw err;\n                if (stats.isDirectory()) {\n                    name.innerText = `${file}/`;\n                    description.innerText = \"Folder\";\n                    date.innerText = new Date(stats.mtime).toLocaleString();\n\n                    size.innerText = stats.size;\n\n                    let folderExt = file.split(\".\").slice(\"-1\")[0]\n                    \n                    if (folderExt == \"app\" | folderExt == \"lib\" && file !== \"lib\") {\n                        let manifestPath = `${path}/${file}/manifest.json`;\n                        fs.readFile(manifestPath, function (err, data) {\n                            if (err) {\n                                icon.src = anura.files.folderIcon;\n                            }\n                            let manifest = JSON.parse(data);\n                            icon.src = `/fs${path}/${file}/${manifest.icon}`;\n                            icon.onerror = () => {\n                                icon.src = anura.files.folderIcon;\n                            };\n                            description.innerText = `Anura ${folderExt == \"app\" ? \"Application\" : \"Library\"}`;\n                        });\n                    } else {\n                        icon.src = anura.files.folderIcon;\n                    }\n                    iconContainer.appendChild(icon);\n                    row.appendChild(iconContainer);\n                    row.appendChild(name);\n                    row.appendChild(size);\n                    row.appendChild(description);\n                    row.appendChild(date);\n\n                    row.setAttribute(\"data-type\", \"dir\");\n                    row.setAttribute(\"data-path\", `${path}/${file}`);\n                } else {\n                    if (selectorType !== \"dir\") {\n                        let ext = file.split(\"/\").pop().split(\".\").pop();\n\n                        if (fileRegex.test(ext)) {\n                            name.innerText = `${file}`;\n                            description.innerText = \"Anura File\";\n                            anura.files.getFileType(`${path}/${file}`).then((type) => {\n                                description.innerText = type;\n                            });\n                            date.innerText = new Date(stats.mtime).toLocaleString();\n                            size.innerText = stats.size;\n        \n                            anura.files.getIcon(`${path}/${file}`).then((iconURL) => {\n                                icon.src = iconURL;\n                            }).catch((e) => {\n                                icon.src = anura.files.fallbackIcon;\n                                console.error(e);\n                            });\n\n                            iconContainer.appendChild(icon);\n                            row.appendChild(iconContainer);\n                            row.appendChild(name);\n                            row.appendChild(size);\n                            row.appendChild(description);\n                            row.appendChild(date);\n\n                            row.setAttribute(\"data-type\", \"file\");\n                            row.setAttribute(\"data-path\", `${path}/${file}`);\n                        }\n                    }\n                }\n                console.debug(\"appending\");\n                table.appendChild(row);\n                if (files[files.length - 1] === file) {\n                    reloadListeners();\n                }\n            });\n        });\n    });\n}\n\nfunction reloadListeners() {\n    console.debug(\"reloading listeners\");\n    console.debug(document.querySelectorAll(\"tr\"));\n    document.querySelectorAll(\"tr\").forEach((row) => {\n        if (row.parentNode.nodeName.toLowerCase() !== \"thead\") {\n            console.debug(\"adding listeners to \", row);\n            row.addEventListener(\"mouseenter\", (e) => {\n                e.currentTarget.classList.add(\"hover\");\n            });\n            row.addEventListener(\"mouseleave\", (e) => {\n                e.currentTarget.classList.remove(\"hover\");\n            });\n            row.addEventListener(\"contextmenu\", (e) => {\n                if (currentlySelected.length > 0) {\n                    return;\n                }\n                e.currentTarget.classList.add(\"selected\");\n                currentlySelected = [e.currentTarget];\n            });\n            row.addEventListener(\"click\", (e) => {\n                if (currentlySelected.includes(e.currentTarget)) {\n                    fileAction(currentlySelected);\n                    currentlySelected.forEach((row) => {\n                        row.classList.remove(\"selected\");\n                    });\n                    currentlySelected = [];\n                    return;\n                }\n                if (!e.shiftKey) {\n                    if (!e.ctrlKey) {\n                        currentlySelected.forEach((row) => {\n                            row.classList.remove(\"selected\");\n                        });\n                        currentlySelected = [];\n                    }\n                    e.currentTarget.classList.add(\"selected\");\n                    currentlySelected.push(e.currentTarget);\n                } else {\n                    if (currentlySelected.length == 0) {\n                        e.currentTarget.classList.add(\"selected\");\n                        currentlySelected.push(e.currentTarget);\n                    } else {\n                        var arr = Array.from(\n                            document.querySelectorAll(\"tr\"),\n                        ).filter(\n                            (row) =>\n                                row.parentNode.nodeName.toLowerCase() !==\n                                \"thead\",\n                        );\n                        var firstI = arr.indexOf(\n                            currentlySelected[currentlySelected.length - 1],\n                        );\n                        var lastI = arr.indexOf(e.currentTarget);\n                        var first = Math.min(firstI, lastI);\n                        var last = Math.max(firstI, lastI);\n                        for (var i = first; i <= last; i++) {\n                            if (!currentlySelected.includes(arr[i])) {\n                                currentlySelected.push(arr[i]);\n                                arr[i].classList.add(\"selected\");\n                            }\n                        }\n                    }\n                }\n            });\n        }\n    });\n}\nasync function selectAction(selected) {\n    currentlySelected.forEach((row) => {\n        row.classList.remove(\"selected\");\n    });\n    currentlySelected = [];\n    if (selected.length == 1) {\n        var fileSelected = selected[0];\n        if (fileSelected.getAttribute(\"data-type\") == selectorType) {\n            let fileData = {\n                message: 'FileSelected',\n                filePath: fileSelected.getAttribute(\"data-path\").replace(/(\\/)\\1+/g, \"$1\")\n            };\n\n            window.parent.postMessage(fileData, '*');\n        }\n    } else if (selected.length > 1) {\n        let dataPaths = [];\n        for (var i = 0; i < selected.length; i++) {\n            var dataType = selected[i].getAttribute(\"data-type\");\n            var dataPath = selected[i].getAttribute(\"data-path\");\n            if (dataType !== selectorType ) {\n                return;\n            }\n            if (dataPath !== null) {\n              dataPaths.push(dataPath.replace(/(\\/)\\1+/g, \"$1\"));\n            }\n        }\n        let fileData = {\n            message: 'FileSelected',\n            filePath: dataPaths\n        };\n    \n        window.parent.postMessage(fileData, '*');\n    } else if (selected.length == 0) {\n        if (selectorType == \"dir\") {\n            let fileData = {\n                message: 'FileSelected',\n                filePath: document.querySelector(\".breadcrumbs\").getAttribute(\"data-current-path\")\n              };\n        \n              window.parent.postMessage(fileData, '*');\n        }\n    }\n}\nasync function fileAction(selected) {\n    if (selected.length == 1) {\n        var fileSelected = selected[0];\n\n        if (fileSelected.getAttribute(\"data-type\") == \"file\") {\n            if (selectorType == \"file\") {\n                let fileData = {\n                    message: 'FileSelected',\n                    filePath: fileSelected.getAttribute(\"data-path\").replace(/(\\/)\\1+/g, \"$1\")\n                  };\n            \n                  window.parent.postMessage(fileData, '*');\n            }\n        } else if (fileSelected.getAttribute(\"data-type\") == \"dir\") {\n            console.debug(\n                \"Changing folder to \",\n                fileSelected.getAttribute(\"data-path\"),\n            );\n            loadPath(fileSelected.getAttribute(\"data-path\"));\n        } else {\n            console.warn(\n                \"Unknown filetype \",\n                fileSelected.getAttribute(\"data-type\"),\n                \" doing nothing!\",\n            );\n        }\n    } else if (selected.length > 1) {\n        let dataPaths = [];\n        for (var i = 0; i < selected.length; i++) {\n            var dataType = selected[i].getAttribute(\"data-type\");\n            var dataPath = selected[i].getAttribute(\"data-path\");\n            if (dataType !== selectorType ) {\n                return;\n            }\n            if (dataPath !== null) {\n              dataPaths.push(dataPath.replace(/(\\/)\\1+/g, \"$1\"));\n            }\n        }\n        let fileData = {\n            message: 'FileSelected',\n            filePath: dataPaths\n        };\n    \n        window.parent.postMessage(fileData, '*');\n    }\n}\n\nfunction setBreadcrumbs(path) {\n    path = path.replace(/(\\/)\\1+/g, \"$1\");\n    var pathSplit = path.split(\"/\");\n    pathSplit[0] = \"My files\";\n    var breadcrumbs = document.querySelector(\".breadcrumbs\");\n    breadcrumbs.setAttribute(\"data-current-path\", path);\n    breadcrumbs.innerHTML = \"\";\n    if (\n        pathSplit.length == 2 &&\n        pathSplit[0] == \"My files\" &&\n        pathSplit[1] == \"\"\n    ) {\n        var breadcrumb = document.createElement(\"button\");\n        breadcrumb.innerText = \"My files\";\n        breadcrumb.addEventListener(\"click\", () => {\n            loadPath(\"/\");\n        });\n        breadcrumbs.appendChild(breadcrumb);\n        return;\n    }\n    for (var i = 0; i < pathSplit.length; i++) {\n        console.log(i);\n        var breadcrumb = document.createElement(\"button\");\n        breadcrumb.innerText = pathSplit[i];\n        var index = i;\n        breadcrumb.addEventListener(\"click\", () => {\n            loadPath(\"/\" + pathSplit.slice(1, index).join(\"/\"));\n        });\n        breadcrumbs.appendChild(breadcrumb);\n        if (pathSplit[i] !== pathSplit[pathSplit.length - 1]) {\n            var breadcrumbSpan = document.createElement(\"span\");\n            breadcrumbSpan.innerText = \">\";\n            breadcrumbs.appendChild(breadcrumbSpan);\n        }\n    }\n}\n\ndocument.querySelector(\"table\").addEventListener(\"click\", (e) => {\n    if (e.currentTarget === e.target) {\n        currentlySelected.forEach((row) => {\n            row.classList.remove(\"selected\");\n        });\n        currentlySelected = [];\n    }\n});\ndocument.addEventListener(\"contextmenu\", (e) => {\n    if (e.shiftKey) {\n        return;\n    }\n    e.preventDefault();\n    const boundingRect = window.frameElement.getBoundingClientRect();\n\n    // var contextmenu = document.querySelector(\"#contextMenu\");\n\n    // contextmenu.style.left = e.pageX + \"px\";\n    // contextmenu.style.top = e.pageY + \"px\";\n    // const hasSelection = currentlySelected.length > 0;\n    // for (const elt of contextmenu.getElementsByClassName(\"needs-selection\")) {\n    //     elt.ariaDisabled = !hasSelection;\n    //     elt.onclick = hasSelection ? elt.onclick : null;\n    // }\n    // contextmenu.style.removeProperty(\"display\");\n    const containsApps = currentlySelected.map((item) => item.getAttribute(\"data-path\").split(\".\").slice(\"-1\")[0]).filter((item) => item == \"app\" || item == \"lib\").length > 0;\n\n    if (containsApps) {\n        appcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y);\n        newcontextmenu.hide();\n        emptycontextmenu.hide();\n    } else if (currentlySelected.length != 0) {\n        newcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y);\n        appcontextmenu.hide();\n        emptycontextmenu.hide();\n    } else {\n        emptycontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y);\n        newcontextmenu.hide();\n        appcontextmenu.hide();\n    }\n});\n\ndocument.addEventListener(\"click\", (e) => {\n    if (\n        !document.querySelector(\"#contextMenu\").contains(e.target) ||\n        !e.target.ariaDisabled\n    ) {\n        // document.querySelector(\"#contextMenu\").style.setProperty(\"display\", \"none\");\n        newcontextmenu.hide();\n        appcontextmenu.hide();\n        emptycontextmenu.hide();\n    }\n});\n\nfunction newFolder(path) {\n    if (path === undefined) {\n        path =\n            document\n                .querySelector(\".breadcrumbs\")\n                .getAttribute(\"data-current-path\") +\n            \"/\" +\n            prompt(\"Folder Name: \");\n    }\n    fs.mkdir(path);\n    reload();\n}\n\nfunction newFile(path) {\n    if (path === undefined) {\n        path =\n            document\n                .querySelector(\".breadcrumbs\")\n                .getAttribute(\"data-current-path\") +\n            \"/\" +\n            prompt(\"File Name: \");\n    }\n    fs.writeFile(path, \"\");\n    reload();\n}\n\nfunction reload() {\n    loadPath(\n        document\n            .querySelector(\".breadcrumbs\")\n            .getAttribute(\"data-current-path\"),\n    );\n}\n\nfunction reload() {\n    loadPath(\n        document\n            .querySelector(\".breadcrumbs\")\n            .getAttribute(\"data-current-path\"),\n    );\n}\n\nfunction upload() {\n    let fauxput = document.createElement(\"input\"); // fauxput - fake input that isn't shown or ever added to page TODO: think of a better name for this variable\n    fauxput.type = \"file\";\n    fauxput.onchange = async (e) => {\n        const file = await e.target.files[0];\n        const content = await file.arrayBuffer();\n        fs.writeFile(\n            `${document\n                .querySelector(\".breadcrumbs\")\n                .getAttribute(\"data-current-path\")}/${file.name}`,\n            Buffer.from(content),\n            function (err) {\n                reload();\n            },\n        );\n    };\n    fauxput.click();\n}\n\nfunction deleteFile() {\n    if (currentlySelected.length == 0) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description: \"BUG: You have no files selected, right clicking does not select files\",\n            timeout: 5000,\n        });\n    }\n    currentlySelected.forEach(async (item) => {\n        await sh.rm(\n            item.getAttribute(\"data-path\"),\n            {\n                recursive: true,\n            },\n            function (err) {\n                if (err) throw err;\n                reload();\n            },\n        );\n    });\n}\n\nfunction copy() {\n    clipboard = currentlySelected;\n    removeAfterPaste = false;\n}\n\nfunction cut() {\n    clipboard = currentlySelected;\n    removeAfterPaste = true;\n}\n\nfunction paste() {\n    const path = document\n        .querySelector(\".breadcrumbs\")\n        .getAttribute(\"data-current-path\");\n    if (!removeAfterPaste) {\n        // copy\n        destination = path;\n        clipboard.forEach((item) => {\n            origin = item.getAttribute(\"data-path\");\n            fs.stat(origin, function (err, data) {\n                if (data.isDirectory()) {\n                    // Ok so you are about to be in for a ride\n                    sh.ls(\n                        origin,\n                        {\n                            recursive: true,\n                        },\n                        async function (err, entries) {\n                            if (err) throw err;\n                            let items = [];\n                            let dirs = [];\n                            entries.forEach((entry) => {\n                                function recurse(dirnode, path) {\n                                    dirnode.contents.forEach((entry) => {\n                                        if (entry.type === \"DIRECTORY\") {\n                                            recurse(\n                                                entry,\n                                                path + \"/\" + entry.name,\n                                            );\n                                            dirs.push(path + \"/\" + entry.name);\n                                        } else {\n                                            items.push(path + \"/\" + entry.name);\n                                        }\n                                    });\n                                }\n\n                                const topLevelFolder = origin;\n                                dirs.push(origin);\n                                if (entry.type === \"DIRECTORY\") {\n                                    recurse(entry, origin + \"/\" + entry.name);\n                                    dirs.push(origin + \"/\" + entry.name);\n                                } else {\n                                    items.push(origin + \"/\" + entry.name);\n                                }\n                            });\n                            destItems = [];\n                            destDirs = [];\n                            numberToSubBy =\n                                origin.length - origin.split(\"/\").pop().length;\n\n                            for (item in items) {\n                                destItems.push(\n                                    destination +\n                                        \"/\" +\n                                        items[item].slice(numberToSubBy),\n                                );\n                            }\n                            for (dir in dirs) {\n                                destDirs.push(\n                                    destination +\n                                        \"/\" +\n                                        dirs[dir].slice(numberToSubBy),\n                                );\n                            }\n                            console.log(\"initials\");\n                            console.log(items);\n                            console.log(\"destinations\");\n                            console.log(destItems);\n                            console.log(\"directories to mkdir -p \");\n                            console.log(destDirs);\n                            for (dir in destDirs) {\n                                await new Promise((resolve, reject) => {\n                                    sh.mkdirp(destDirs[dir], function (err) {\n                                        if (err) {\n                                            reject(err);\n                                            console.error(err);\n                                        }\n                                        resolve();\n                                    });\n                                });\n                            }\n\n                            for (item in items) {\n                                fs.readFile(items[item], function (err, data) {\n                                    fs.writeFile(destItems[item], data);\n                                });\n                            }\n                            reload();\n                        },\n                    );\n                } else {\n                    fs.readFile(origin, function (err, data) {\n                        if (err) throw err;\n                        fs.writeFile(\n                            `${path}/${origin.split(\"/\").slice(\"-1\")[0]}`, data\n                        );\n                        reload();\n                    });\n                }\n            });\n        });\n    }\n    if (removeAfterPaste) {\n        // cut\n        clipboard.forEach((item) => {\n            itemName = item.getAttribute(\"data-path\");\n            fs.rename(\n                itemName,\n                `${path}/${itemName.split(\"/\").slice(\"-1\")[0]}`,\n                function (err) {\n                    if (err) throw err;\n                    reload();\n                },\n            );\n        });\n    }\n}\n\nfunction rename() {\n    const path = document\n        .querySelector(\".breadcrumbs\")\n        .getAttribute(\"data-current-path\");\n    if (currentlySelected.length == 0) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description: \"BUG: You have no files selected, right clicking does not select files\",\n            timeout: 5000,\n        });\n        \n        return;\n    }\n    if (currentlySelected.length > 1) {\n        anura.notifications.add({\n            title: \"Filesystem app\",\n            description: \"Renaming only works with one file\",\n            timeout: 5000,\n        });\n        return;\n    }\n    fs.rename(\n        currentlySelected[0].getAttribute(\"data-path\"),\n        `${path}/${prompt(\"filename:\")}`,\n        function (err) {\n            if (err) throw err;\n            reload();\n        },\n    );\n}\n\n// Context menu version of the loadPath function\n// Used to enter app and lib folders, as double\n// clicking on them will install them.\nfunction navigate() {\n    if (currentlySelected.length == 1) {\n        loadPath(currentlySelected[0].getAttribute(\"data-path\"));\n    }\n    // Can't navigate to multiple folders\n}\n\nloadPath(\"/\");\n"
  },
  {
    "path": "public/apps/libfileview.lib/fileHandler.js",
    "content": "const icons = await (await fetch(localPathToURL(\"icons.json\"))).json();\n\nexport function openFile(path) {\n    const fs = anura.fs || tb.fs;\n    function openImage(path, mimetype) {\n        fs.readFile(path, function (err, data) {\n            tb.file.handler.openFile(path, 'image')\n        });\n    }\n\n    function openPDF(path) {\n        fs.readFile(path, function (err, data) {\n            tb.file.handler.openFile(path, 'pdf')\n        });\n    }\n\n    function openAudio(path, mimetype) {\n        fs.readFile(path, function (err, data) {\n            tb.file.handler.openFile(path, 'audio')\n        });\n    }\n    function openVideo(path, mimetype) {\n        fs.readFile(path, function (err, data) {\n            tb.file.handler.openFile(path, 'video')\n        });\n    }\n\n    function openText(path) {\n        fs.readFile(path, function (err, data) {\n            tb.file.handler.openFile(path, 'text')\n        });\n    }\n\n    function openHTML(path) {\n        fs.readFile(path, function (err, data) {\n            tb.file.handler.openFile(path, 'webpage')\n        });\n    }\n\n    let ext = path.split(\".\").slice(\"-1\")[0];\n    switch (ext) {\n        case \"txt\":\n        case \"js\":\n        case \"mjs\":\n        case \"cjs\":\n        case \"json\":\n        case \"css\":\n            openText(path);\n            break;\n        case \"ajs\":\n            anura.processes.execute(path);\n            break;\n        case \"mp3\":\n            openAudio(path, \"audio/mpeg\");\n            break;\n        case \"flac\":\n            openAudio(path, \"audio/flac\");\n            break;\n        case \"wav\":\n            openAudio(path, \"audio/wav\");\n            break;\n        case \"ogg\":\n            openAudio(path, \"audio/ogg\");\n            break;\n        case \"mp4\":\n            openVideo(path, \"video/mp4\");\n            break;\n        case \"mov\":\n            openVideo(path, \"video/mp4\");\n            break;\n        case \"webm\":\n            openVideo(path, \"video/webm\");\n            break;\n        case \"gif\":\n            openImage(path, \"image/gif\");\n            break;\n        case \"png\":\n            openImage(path, \"image/png\");\n            break;\n        case \"svg\":\n            openImage(path, \"image/svg+xml\");\n            break;\n        case \"jpg\":\n        case \"jpeg\":\n            openImage(path, \"image/jpeg\");\n            break;\n        case \"pdf\":\n            openPDF(path);\n            break;\n        case \"html\":\n            openHTML(path);\n            break;\n        default:\n            openText(path);\n            break;\n    }\n}\n\nexport function getIcon(path) {\n    let ext = path.split(\".\").slice(\"-1\")[0];\n    let iconObject = icons.files.find((icon) => icon.ext == ext);\n    if (iconObject) {\n        return localPathToURL(iconObject.icon);\n    }\n    return localPathToURL(icons.default);\n}\n\nexport function getFileType(path) {\n    let ext = path.split(\".\").slice(\"-1\")[0];\n    let iconObject = icons.files.find((icon) => icon.ext == ext);\n    if (iconObject) {\n        return iconObject.type;\n    }\n    return \"Anura File\";\n}\n\nfunction localPathToURL(path) {\n    return (\n        import.meta.url.substring(0, import.meta.url.lastIndexOf(\"/\")) +\n        \"/\" +\n        path\n    );\n}\n"
  },
  {
    "path": "public/apps/libfileview.lib/icons.json",
    "content": "{\n    \"files\": [\n        {\n            \"ext\": \"txt\",\n            \"icon\": \"icons/txt.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/text-plain.svg\",\n            \"type\": \"Text File\"\n        },\n        {\n            \"ext\": \"mp3\",\n            \"icon\": \"icons/mp3.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/audio-mpeg.svg\",\n            \"type\": \"MPEG Audio\"\n        },\n        {\n            \"ext\": \"flac\",\n            \"icon\": \"icons/flac.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/audio-flac.svg\",\n            \"type\": \"FLAC Audio\"\n        },\n        {\n            \"ext\": \"wav\",\n            \"icon\": \"icons/wav.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/audio-x-wav.svg\",\n            \"type\": \"WAV Audio\"\n        },\n        {\n            \"ext\": \"ogg\",\n            \"icon\": \"icons/ogg.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/audio-x-generic.svg\",\n            \"type\": \"OGG Audio\"\n        },\n        {\n            \"ext\": \"mp4\",\n            \"icon\": \"icons/mp4.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/video-mp4.svg\",\n            \"type\": \"MPEG Video\"\n        },\n        {\n            \"ext\": \"mov\",\n            \"icon\": \"icons/mov.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/video.svg\",\n            \"type\": \"Quicktime Video\"\n        },\n        {\n            \"ext\": \"webm\",\n            \"icon\": \"icons/webm.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/video-webm.svg\",\n            \"type\": \"WebM Video\"\n        },\n        {\n            \"ext\": \"gif\",\n            \"icon\": \"icons/gif.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/image-gif.svg\",\n            \"type\": \"GIF Image\"\n        },\n        {\n            \"ext\": \"png\",\n            \"icon\": \"icons/png.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/image-png.svg\",\n            \"type\": \"PNG Image\"\n        },\n        {\n            \"ext\": \"jpg\",\n            \"icon\": \"icons/jpeg.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/image-jpeg.svg\",\n            \"type\": \"JPEG Image\"\n        },\n        {\n            \"ext\": \"jpeg\",\n            \"icon\": \"icons/jpeg.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/image-jpeg.svg\",\n            \"type\": \"JPEG Image\"\n        },\n        {\n            \"ext\": \"svg\",\n            \"icon\": \"icons/svg.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/image-svg+xml.svg\",\n            \"type\": \"SVG Image\"\n        },\n        {\n            \"ext\": \"pdf\",\n            \"icon\": \"icons/pdf.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/application-pdf.svg\",\n            \"type\": \"PDF Document\"\n        },\n        {\n            \"ext\": \"py\",\n            \"icon\": \"icons/py.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/application-x-python-bytecode.svg\",\n            \"type\": \"Python Script\"\n        },\n        {\n            \"ext\": \"js\",\n            \"icon\": \"icons/js.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/application-javascript.svg\",\n            \"type\": \"JavaScript Code\"\n        },\n        {\n            \"ext\": \"mjs\",\n            \"icon\": \"icons/js.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/application-javascript.svg\",\n            \"type\": \"JavaScript Module\"\n        },\n        {\n            \"ext\": \"cjs\",\n            \"icon\": \"icons/js.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/application-javascript.svg\",\n            \"type\": \"JavaScript CommonJS Module\"\n        },\n        {\n            \"ext\": \"ajs\",\n            \"icon\": \"icons/js.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/application-javascript.svg\",\n            \"type\": \"Anura JavaScript Binary\"\n        },\n        {\n            \"ext\": \"json\",\n            \"icon\": \"icons/json.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/application-json.svg\",\n            \"type\": \"JSON Data\"\n        },\n        {\n            \"ext\": \"html\",\n            \"icon\": \"icons/html.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/text-html.svg\",\n            \"type\": \"HTML Document\"\n        },\n        {\n            \"ext\": \"css\",\n            \"icon\": \"icons/css.svg\",\n            \"source\": \"papirus/Papirus/16x16/mimetypes/text-css.svg\",\n            \"type\": \"CSS Stylesheet\"\n        }\n    ],\n    \"default\": \"icons/txt.svg\",\n    \"defaultSource\": \"papirus/Papirus/16x16/mimetypes/text-plain.svg\",\n    \"folder\": \"icons/folder.svg\",\n    \"folderSource\": \"papirus/Papirus/16x16/places/folder-white.svg\"\n}\n"
  },
  {
    "path": "public/apps/libfileview.lib/install.js",
    "content": "const icons = await (await fetch(localPathToURL(\"icons.json\"))).json();\n\n// This constant is our own ID. It is used when registering the library.\nconst HANDLER = \"anura.fileviewer\";\n\n// This is the list of file extensions that we will handle\nconst defaultHandlers = [...icons.files.map((file) => file.ext), \"default\"];\n\n// The install function is called when the library is registered on boot.\n// If you want to detect the first install, you can set an anura registry\n// key and retrieve it later.\n// Here, we set the file handler for a few common file types to ourself.\n// If you want to restore to this file handler after an override, you can\n// set the `libfileview.reset` key to true.\n// following code in the console:\n// anura.settings.set('libfileview.reset', true)\n// If you want to disable the default file handler entirely, you can set\n// the `libfileview.disable` key to true.\n// anura.settings.set('libfileview.disable', true)\n\nexport default function install(anura) {\n    if (anura.settings.get(\"libfileview.disable\")) {\n        return;\n    }\n    anura.files.setFolderIcon(localPathToURL(icons.folder));\n    const exts = anura.settings.get(\"FileExts\") || {};\n    const resetMode = anura.settings.get(\"libfileview.reset\");\n    const externalHandlers = Object.keys(exts).filter(\n        (ext) => exts[ext].id !== HANDLER,\n    );\n    defaultHandlers.forEach((ext) => {\n        if (!externalHandlers.includes(ext) || resetMode) {\n            anura.files.setModule(HANDLER, ext);\n        }\n    });\n    anura.settings.set(\"libfileview.reset\", false);\n}\n\nfunction localPathToURL(path) {\n    return (\n        import.meta.url.substring(0, import.meta.url.lastIndexOf(\"/\")) +\n        \"/\" +\n        path\n    );\n}\n"
  },
  {
    "path": "public/apps/libfileview.lib/manifest.json",
    "content": "{\n    \"name\": \"File Viewer\",\n    \"icon\": \"files.png\",\n    \"package\": \"anura.fileviewer\",\n    \"installHook\": \"install.js\",\n    \"versions\": {\n        \"1.0.0\": \"fileHandler.js\"\n    },\n    \"currentVersion\": \"1.0.0\"\n}"
  },
  {
    "path": "public/apps/libpersist.lib/install.js",
    "content": "export default function install(anura) {\n    const directories = anura.settings.get(\"directories\");\n\n    anura.fs.exists(directories[\"opt\"] + \"/anura.persistence\", (exists) => {\n        if (exists) return;\n        anura.fs.mkdir(directories[\"opt\"] + \"/anura.persistence\");\n        anura.fs.mkdir(directories[\"opt\"] + \"/anura.persistence/providers\");\n        anura.fs.mkdir(\n            directories[\"opt\"] + \"/anura.persistence/providers/anureg\",\n        );\n\n        anura.fs.writeFile(\n            directories[\"opt\"] +\n                \"/anura.persistence/providers/anureg/manifest.json\",\n            JSON.stringify({\n                name: \"anureg\",\n                vendor: \"[[internal]]\",\n                description:\n                    \"Anura's default persistance provider, using a simple JSON file\",\n                handler: \"index.js\",\n            }),\n        );\n\n        anura.fs.writeFile(\n            directories[\"opt\"] + \"/anura.persistence/providers/anureg/index.js\",\n            `const { PersistenceProvider } = await anura.import(\"anura.persistence\");\nexport default class Anureg extends PersistenceProvider {\n    cache = {};\n    fs;\n    basepath;\n    file;\n    config;\n\n    constructor(anura, config, fs, basepath) {\n        super(anura);\n        this.fs = fs;\n        this.basepath = basepath;\n        this.config = config;\n        this.file = config.path || (this.basepath + (config.filename || \"//system/etc/anura/anura_settings.json\"));\n    }\n\n    async init() {\n        this.fs.exists(this.basepath, async (exists) => {\n            if (!exists) {\n                await this.fs.promises.mkdir(this.basepath);\n            }\n        });\n        try {\n            const text = await this.fs.promises.readFile(this.file);\n            this.cache = JSON.parse(text);\n        }\n        catch (e) {\n            this.fs.writeFile(this.file, JSON.stringify(this.cache));\n        }\n    }\n\n    async get(prop) {\n        return this.cache[prop];\n    }\n\n    async has(prop) {\n        return prop in this.cache;\n    }\n\n    async set(prop, val) {\n        this.cache[prop] = val;\n        return new Promise((r) => this.fs.writeFile(this.file, JSON.stringify(this.cache), r));\n    }\n\n    createStoreFn(stateful, win) {\n        return async (\n            target,\n            ident,\n            _backing\n        ) => {\n            target = (await this.get(\"dreamland.\" + ident)) || target;\n\n            win.addEventListener(\"close\", () => {\n                console.info(\"[dreamland.js]: saving \" + ident);\n                this.set(\"dreamland.\" + ident, target);\n            });\n            \n            return stateful(target);\n        }\n    }\n}\nexport const using = [\"fs\", \"basepath\"];\nexport const lifecycle = [\"init\"];`,\n        );\n    });\n}\n"
  },
  {
    "path": "public/apps/libpersist.lib/manifest.json",
    "content": "{\n    \"name\": \"AnuraOS Persistent Storage\",\n    \"icon\": \"icon.svg\",\n    \"package\": \"anura.persistence\",\n    \"versions\": {\n        \"1.0.0\": \"src/index.js\"\n    },\n    \"installHook\": \"install.js\",\n    \"cache\": true,\n    \"currentVersion\": \"1.0.0\"\n}\n"
  },
  {
    "path": "public/apps/libpersist.lib/src/index.js",
    "content": "/**\n * @typedef Anura\n * @type {any}\n */\n\n/**\n * Base class for persisting data\n * This class is meant to be extended\n * by a specific implementation, such as\n * Anureg. It is not meant to be manually\n * used, however it can technically be\n * used as a memory-based cache. However,\n * this is mostly useless as the cache\n * is not actually persisted.\n */\nexport class PersistenceProvider {\n    cache = {};\n\n    constructor(anura) {\n        this.anura = anura;\n    }\n\n    async init() {\n        console.log(\"init\");\n    }\n\n    async get(prop) {\n        return this.cache[prop];\n    }\n\n    async has(prop) {\n        return prop in this.cache;\n    }\n\n    async set(prop, val) {\n        this.cache[prop] = val;\n    }\n\n    createStoreFn(_stateful, _win) {\n        return function () {\n            // Not implemented for generic provider\n            throw new Error(\"Not implemented\");\n        };\n    }\n\n    toProxy() {\n        return new Proxy(this, {\n            get: (target, prop) => {\n                return target.get(prop);\n            },\n            set: (target, prop, val) => {\n                target.set(prop, val);\n                return true;\n            },\n        });\n    }\n}\n\nexport class ProviderLoader {\n    fs;\n    anura;\n    basepath;\n\n    constructor(anura, fs, basepath) {\n        this.fs = fs;\n        this.anura = anura;\n        this.basepath = basepath;\n\n        this.providers = {};\n    }\n\n    async locate() {\n        const providers = await this.fs.promises.readdir(this.basepath);\n        for (const provider of providers) {\n            const manifest = JSON.parse(\n                await this.fs.promises.readFile(\n                    this.basepath + \"/\" + provider + \"/manifest.json\",\n                ),\n            );\n            let mod = await import(\n                \"/fs/\" + this.basepath + \"/\" + provider + \"/\" + manifest.handler\n            );\n            this.providers[manifest.name] = {\n                manifest,\n                mod,\n            };\n        }\n    }\n\n    /**\n     * Build a new persistenceProvider\n     * @param {Anura} anura - The Anura instance\n     * @param {Object} app - The app instance\n     * @param {Object} config - The configuration object\n     * @param {string} provider - The provider name\n     * @returns {PersistenceProvider} The provider\n     */\n    async build(app, config = {}, provider = \"anureg\") {\n        let args = [config, this.anura];\n        let using = this.providers[provider].mod.using || [];\n        let lifecycle = this.providers[provider].mod.lifecycle || [];\n        for (let i = 0; i < using.length; i++) {\n            switch (using[i]) {\n                case \"fs\":\n                    args.push(this.fs);\n                    break;\n                case \"basepath\":\n                    args.push(\n                        this.anura.settings.get(\"directories\")[\"opt\"] +\n                            \"/\" +\n                            app.package,\n                    );\n                    break;\n                default:\n                    throw new Error(\"Unknown dependency: \" + using[i]);\n            }\n        }\n        let providerInstance = new this.providers[provider].mod.default(\n            ...args,\n        );\n        if (lifecycle.includes(\"init\")) {\n            await providerInstance.init();\n        }\n        return providerInstance;\n    }\n}\n\nexport function buildLoader(anura, basepath) {\n    if (!basepath) {\n        basepath =\n            anura.settings.get(\"directories\")[\"opt\"] +\n            \"/anura.persistence/providers\";\n    }\n    return new ProviderLoader(anura, anura.fs, basepath);\n}\n"
  },
  {
    "path": "public/apps/media viewer.tapp/index.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(/fonts/inter.ttf);\n}\n\nhtml,\nbody {\n\tfont-family: Inter;\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tline-height: 20px;\n\tcolor: #ffffff;\n\tbackground-color: transparent;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: hidden;\n\theight: 100%;\n}\n\n.media {\n\toverflow: auto;\n\theight: 100%;\n\twidth: 100%;\n\tscrollbar-width: thin;\n\tscrollbar-color: #ffffff44 transparent;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\talign-items: center;\n\tbackground-image: url(./bg.png);\n\tbackground-repeat: repeat;\n\tbackground-position: center;\n\tbackground-size: 5%;\n}\n\n.media::-webkit-scrollbar {\n\twidth: 8px;\n\theight: 8px;\n}\n\n.media::-webkit-scrollbar-corner {\n\tbackground-color: #ffffff;\n}\n\n.media::-webkit-scrollbar-track {\n\tbackground-color: #ffffff;\n\twidth: 8px;\n\theight: 8px;\n}\n\n.media::-webkit-scrollbar-thumb {\n\tbackground-color: #00000028;\n\tborder: none;\n\twidth: 8px;\n\theight: 8px;\n}\n\n.media img {\n\twidth: max-content;\n\theight: max-content;\n\tmax-width: 100%;\n\tmax-height: 100%;\n}\n"
  },
  {
    "path": "public/apps/media viewer.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Terbium Media Viewer</title>\n    <link rel=\"stylesheet\" href=\"index.css\">\n</head>\n    <body>\n        <main class=\"media\"></main>\n        <script src=\"./index.js\" type=\"module\"></script>\n        <script src=\"media.com.js\"></script>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/media viewer.tapp/index.js",
    "content": "import * as id3 from \"https://unpkg.com/id3js@latest/lib/id3.js\";\nimport * as pdfjsLib from \"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.149/pdf.min.mjs\";\n\nif (pdfjsLib && pdfjsLib.GlobalWorkerOptions) {\n\tconsole.log(\"Setting up PDF.js worker\");\n\tpdfjsLib.GlobalWorkerOptions.workerSrc = \"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.149/pdf.worker.min.mjs\";\n}\n\nwindow.addEventListener(\"load\", async () => {\n\tparent.postMessage(JSON.stringify({ type: \"ready\" }), \"*\");\n\tsetTimeout(() => {\n\t\tif (!asked) {\n\t\t\tshowFileBrowser();\n\t\t}\n\t}, 100);\n});\n\nasync function openFile(url, ext, fileName, dav) {\n\tlet exts = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\tif (exts[\"animated\"].includes(ext)) {\n\t\tlet imgObj = new Image();\n\t\timgObj.src = url;\n\t\timgObj.setAttribute(\"draggable\", false);\n\t\tdocument.querySelector(\".media\").innerHTML = \"\";\n\t\tdocument.querySelector(\".media\").appendChild(imgObj);\n\t\tlet scale = 1;\n\n\t\twindow.addEventListener(\"wheel\", function (e) {\n\t\t\tconst zoomSpeed = 0.1;\n\t\t\te.preventDefault();\n\t\t\tif (e.deltaY < 0) {\n\t\t\t\tscale *= 1 + zoomSpeed;\n\t\t\t} else {\n\t\t\t\tscale /= 1 + zoomSpeed;\n\t\t\t}\n\t\t\timgObj.style.transform = `scale(${scale})`;\n\t\t});\n\n\t\twindow.addEventListener(\"resize\", () => {\n\t\t\timgObj.style.transform = `scale(${scale})`;\n\t\t});\n\n\t\timgObj.addEventListener(\"load\", () => {\n\t\t\timgObj.style.transform = `scale(${scale})`;\n\t\t});\n\t} else if (exts[\"image\"].includes(ext)) {\n\t\tlet canvas = document.createElement(\"canvas\");\n\t\tlet ctx = canvas.getContext(\"2d\");\n\t\tdocument.querySelector(\".media\").innerHTML = \"\";\n\t\tdocument.querySelector(\".media\").appendChild(canvas);\n\t\tlet isDragging = false;\n\t\tlet isMouseDown = false;\n\t\tlet startCoords = { x: 0, y: 0 };\n\t\tlet offset = { x: 0, y: 0 };\n\t\tlet scale = 0.5;\n\t\tlet imgObj = new Image();\n\t\tif (dav) {\n\t\t\ttry {\n\t\t\t\tconst davInstances = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\t\t\tconst davUrl = url.split(\"/dav/\")[0] + \"/dav/\";\n\t\t\t\tconst dav = davInstances.find(d => d.url.toLowerCase().includes(davUrl));\n\t\t\t\tif (!dav) throw new Error(\"No matching dav instance found\");\n\t\t\t\tconst client = window.parent.tb.vfs.servers.get(dav.name);\n\t\t\t\tlet filePath;\n\t\t\t\tif (url.startsWith(\"http\")) {\n\t\t\t\t\tconst match = url.match(/^https?:\\/\\/[^\\/]+\\/dav\\/([^\\/]+\\/)?(.+)$/);\n\t\t\t\t\tfilePath = match ? \"/\" + match[2] : url;\n\t\t\t\t} else {\n\t\t\t\t\tfilePath = url.replace(davUrl, \"/\");\n\t\t\t\t}\n\t\t\t\tconst response = await client.getFileContents(filePath);\n\t\t\t\tconst blob = new Blob([response], { type: \"image/\" + ext });\n\t\t\t\timgObj.src = URL.createObjectURL(blob);\n\t\t\t} catch (err) {\n\t\t\t\twindow.tb.dialog.Alert({\n\t\t\t\t\ttitle: \"Failed to read dav file\",\n\t\t\t\t\tmessage: err,\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\timgObj.src = url;\n\t\t}\n\t\timgObj.onload = () => {\n\t\t\tinitializeCanvas();\n\t\t};\n\t\tfunction initializeCanvas() {\n\t\t\tcanvas.width = window.innerWidth;\n\t\t\tcanvas.height = window.innerHeight;\n\t\t\tdrawImageWithOffsetAndScale();\n\t\t}\n\t\twindow.addEventListener(\"resize\", function () {\n\t\t\tinitializeCanvas();\n\t\t});\n\t\tcanvas.addEventListener(\"mousedown\", function (e) {\n\t\t\tisDragging = true;\n\t\t\tisMouseDown = true;\n\t\t\tstartCoords = { x: e.clientX, y: e.clientY };\n\t\t});\n\t\twindow.addEventListener(\"mouseup\", function () {\n\t\t\tisDragging = false;\n\t\t\tisMouseDown = false;\n\t\t});\n\t\tcanvas.addEventListener(\"mousemove\", function (e) {\n\t\t\tif (isDragging) {\n\t\t\t\tconst deltaX = e.clientX - startCoords.x;\n\t\t\t\tconst deltaY = e.clientY - startCoords.y;\n\t\t\t\toffset.x += deltaX;\n\t\t\t\toffset.y += deltaY;\n\t\t\t\tstartCoords = { x: e.clientX, y: e.clientY };\n\t\t\t\tdrawImageWithOffsetAndScale();\n\t\t\t}\n\t\t});\n\t\tcanvas.addEventListener(\"touchstart\", e => {\n\t\t\tisDragging = true;\n\t\t\tisMouseDown = true;\n\t\t\tstartCoords = { x: e.touches[0].clientX, y: e.touches[0].clientY };\n\t\t});\n\t\twindow.addEventListener(\"touchend\", () => {\n\t\t\tisDragging = false;\n\t\t\tisMouseDown = false;\n\t\t});\n\t\tcanvas.addEventListener(\"touchmove\", e => {\n\t\t\tif (isDragging) {\n\t\t\t\tconst deltaX = e.touches[0].clientX - startCoords.x;\n\t\t\t\tconst deltaY = e.touches[0].clientY - startCoords.y;\n\t\t\t\toffset.x += deltaX;\n\t\t\t\toffset.y += deltaY;\n\t\t\t\tstartCoords = { x: e.touches[0].clientX, y: e.touches[0].clientY };\n\t\t\t\tdrawImageWithOffsetAndScale();\n\t\t\t}\n\t\t});\n\t\tcanvas.addEventListener(\"mouseleave\", () => {\n\t\t\tisDragging = false;\n\t\t});\n\t\tcanvas.addEventListener(\"mouseenter\", () => {\n\t\t\tif (isMouseDown) {\n\t\t\t\tisDragging = true;\n\t\t\t}\n\t\t});\n\t\tcanvas.addEventListener(\"wheel\", function (e) {\n\t\t\tconst zoomSpeed = 0.1;\n\t\t\te.preventDefault();\n\t\t\tif (e.deltaY < 0) {\n\t\t\t\tscale *= 1 + zoomSpeed;\n\t\t\t} else {\n\t\t\t\tscale /= 1 + zoomSpeed;\n\t\t\t}\n\t\t\tdrawImageWithOffsetAndScale();\n\t\t});\n\t\tfunction drawImageWithOffsetAndScale() {\n\t\t\tctx.clearRect(0, 0, canvas.width, canvas.height);\n\t\t\tconst newWidth = imgObj.width * scale;\n\t\t\tconst newHeight = imgObj.height * scale;\n\t\t\tconst x = offset.x + (canvas.width - newWidth) / 2;\n\t\t\tconst y = offset.y + (canvas.height - newHeight) / 2;\n\t\t\tctx.drawImage(imgObj, x, y, newWidth, newHeight);\n\t\t}\n\t} else if (exts[\"pdf\"].includes(ext)) {\n\t\tdocument.querySelector(\".media\").innerHTML = \"\";\n\t\tconst pdfContainer = document.createElement(\"div\");\n\t\tpdfContainer.className = \"pdf-container\";\n\t\tpdfContainer.style.display = \"flex\";\n\t\tpdfContainer.style.flexDirection = \"column\";\n\t\tpdfContainer.style.alignItems = \"center\";\n\t\tpdfContainer.style.gap = \"20px\";\n\t\tpdfContainer.style.overflow = \"auto\";\n\t\tpdfContainer.style.width = \"100%\";\n\t\tpdfContainer.style.height = \"100%\";\n\t\tpdfContainer.style.transformOrigin = \"top center\";\n\t\tpdfContainer.style.paddingTop = \"56px\";\n\t\tdocument.querySelector(\".media\").appendChild(pdfContainer);\n\t\tconst loadingTask = pdfjsLib.getDocument({ url });\n\t\tconst pdf = await loadingTask.promise;\n\t\tfor (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\n\t\t\tconst page = await pdf.getPage(pageNum);\n\t\t\tconst viewport = page.getViewport({ scale: 1 });\n\t\t\tconst canvas = document.createElement(\"canvas\");\n\t\t\tcanvas.style.maxWidth = \"75%\";\n\t\t\tcanvas.style.height = \"auto\";\n\t\t\tcanvas.width = viewport.width;\n\t\t\tcanvas.height = viewport.height;\n\t\t\tconst context = canvas.getContext(\"2d\");\n\t\t\tconst renderContext = {\n\t\t\t\tcanvasContext: context,\n\t\t\t\tviewport: viewport,\n\t\t\t};\n\t\t\tawait page.render(renderContext).promise;\n\t\t\tpdfContainer.appendChild(canvas);\n\t\t}\n\t\tconst controls = document.createElement(\"div\");\n\t\tcontrols.className = \"pdf-controls\";\n\t\tcontrols.innerHTML = `\n\t\t\t<div class=\"pdf-controls-inner\">\n\t\t\t\t<button class=\"prev-btn\" title=\"Previous page\">◀</button>\n\t\t\t\t<button class=\"next-btn\" title=\"Next page\">▶</button>\n\t\t\t\t<span class=\"page-indicator\">1 / ${pdf.numPages}</span>\n\t\t\t\t<input type=\"number\" min=\"1\" max=\"${pdf.numPages}\" class=\"page-input\" value=\"1\" style=\"width:68px\">\n\t\t\t\t<button class=\"go-btn\">Go</button>\n\t\t\t</div>\n\t\t\t<style>\n\t\t\t\t.pdf-controls {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 12px;\n\t\t\t\t\tleft: 50%;\n\t\t\t\t\ttransform: translateX(-50%);\n\t\t\t\t\tz-index: 50;\n\t\t\t\t\tfont-family: 'Inter', sans-serif;\n\t\t\t\t}\n\t\t\t\t.pdf-controls-inner {\n\t\t\t\t\tbackground: rgba(0,0,0,0.55);\n\t\t\t\t\tcolor: #fff;\n\t\t\t\t\tpadding: 8px 10px;\n\t\t\t\t\tborder-radius: 6px;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tgap: 8px;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\tbox-shadow: 0 2px 10px rgba(0,0,0,0.3);\n\t\t\t\t}\n\t\t\t\t.pdf-controls button {\n\t\t\t\t\tbackground: rgba(255,255,255,0.08);\n\t\t\t\t\tborder: none;\n\t\t\t\t\tcolor: #fff;\n\t\t\t\t\tpadding: 6px 8px;\n\t\t\t\t\tborder-radius: 4px;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t\t.pdf-controls button:disabled {\n\t\t\t\t\topacity: 0.45;\n\t\t\t\t\tcursor: default;\n\t\t\t\t}\n\t\t\t\t.pdf-controls input {\n\t\t\t\t\toutline: none;\n\t\t\t\t\tborder: none;\n\t\t\t\t\tbackground-color: #00000028;\n\t\t\t\t\tcolor: #ffffff;\n\t\t\t\t\tborder-radius: 8px;\n\t\t\t\t\tpadding: 6px 10px;\n\t\t\t\t\tfont-weight: 700;\n\t\t\t\t\tfont-family: Inter, sans-serif;\n\t\t\t\t\tfont-size: 14.5px;\n\t\t\t\t}\n\t\t\t</style>\n\t\t`;\n\t\tdocument.querySelector(\".media\").appendChild(controls);\n\t\tconst canvases = pdfContainer.querySelectorAll(\"canvas\");\n\t\tlet currentPage = 1;\n\t\tconst prevBtn = controls.querySelector(\".prev-btn\");\n\t\tconst nextBtn = controls.querySelector(\".next-btn\");\n\t\tconst pageIndicator = controls.querySelector(\".page-indicator\");\n\t\tconst pageInput = controls.querySelector(\".page-input\");\n\t\tconst goBtn = controls.querySelector(\".go-btn\");\n\t\tfunction updateControls() {\n\t\t\tpageIndicator.textContent = `${currentPage} / ${canvases.length}`;\n\t\t\tpageInput.value = currentPage;\n\t\t\tprevBtn.disabled = currentPage <= 1;\n\t\t\tnextBtn.disabled = currentPage >= canvases.length;\n\t\t}\n\t\tfunction showPage(page) {\n\t\t\tpage = Math.max(1, Math.min(page, canvases.length));\n\t\t\tconst target = canvases[page - 1];\n\t\t\tif (!target) return;\n\t\t\tpdfContainer.scrollTo({ top: target.offsetTop - 10, behavior: \"smooth\" });\n\t\t\tcurrentPage = page;\n\t\t\tupdateControls();\n\t\t}\n\t\tprevBtn.addEventListener(\"click\", () => showPage(currentPage - 1));\n\t\tnextBtn.addEventListener(\"click\", () => showPage(currentPage + 1));\n\t\tgoBtn.addEventListener(\"click\", () => showPage(parseInt(pageInput.value, 10)));\n\t\tpageInput.addEventListener(\"keydown\", e => {\n\t\t\tif (e.key === \"Enter\") showPage(parseInt(pageInput.value, 10));\n\t\t});\n\t\tpdfContainer.addEventListener(\"scroll\", () => {\n\t\t\tconst containerRect = pdfContainer.getBoundingClientRect();\n\t\t\tconst containerCenter = containerRect.top + containerRect.height / 2;\n\t\t\tlet closestIndex = 0;\n\t\t\tlet closestDist = Infinity;\n\t\t\tcanvases.forEach((c, idx) => {\n\t\t\t\tconst r = c.getBoundingClientRect();\n\t\t\t\tconst cCenter = r.top + r.height / 2;\n\t\t\t\tconst dist = Math.abs(cCenter - containerCenter);\n\t\t\t\tif (dist < closestDist) {\n\t\t\t\t\tclosestDist = dist;\n\t\t\t\t\tclosestIndex = idx;\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (currentPage !== closestIndex + 1) {\n\t\t\t\tcurrentPage = closestIndex + 1;\n\t\t\t\tupdateControls();\n\t\t\t}\n\t\t});\n\t\tupdateControls();\n\t\tshowPage(1);\n\t\twindow.addEventListener(\"keydown\", e => {\n\t\t\tif (e.key === \"ArrowLeft\") showPage(currentPage - 1);\n\t\t\tif (e.key === \"ArrowRight\") showPage(currentPage + 1);\n\t\t});\n\t} else if (exts[\"video\"].includes(ext)) {\n\t\tconst videoPlayerHTML = `\n\t\t\t<div class=\"custom-video-player\">\n\t\t\t\t<div class=\"title-overlay\"></div>\n\t\t\t\t<video></video>\n\t\t\t\t<div class=\"video-controls\">\n\t\t\t\t\t<button class=\"play-pause-btn\">⏸</button>\n\t\t\t\t\t<input type=\"range\" class=\"seek-bar\" value=\"0\" step=\"0.1\">\n\t\t\t\t\t<span class=\"time-display\">0:00 / 0:00</span>\n\t\t\t\t\t<input type=\"range\" class=\"volume-bar\" min=\"0\" max=\"100\" value=\"100\">\n\t\t\t\t\t<button class=\"fullscreen-btn\">⛶</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<style>\n\t\t\t\t.custom-video-player {\n\t\t\t\t\twidth: 90%;\n\t\t\t\t\theight: 75%;\n\t\t\t\t\tbackground: #000;\n\t\t\t\t\tborder-radius: 8px;\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tflex-direction: column;\n\t\t\t\t}\n\t\t\t\t.custom-video-player video {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\tflex: 1;\n\t\t\t\t\tbackground: #000;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t\t.title-overlay {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tright: 0;\n\t\t\t\t\tpadding: 16px 20px;\n\t\t\t\t\tbackground: linear-gradient(to bottom, rgba(0,0,0,0.8), transparent);\n\t\t\t\t\tcolor: white;\n\t\t\t\t\tfont-size: 16px;\n\t\t\t\t\tfont-weight: 500;\n\t\t\t\t\topacity: 0;\n\t\t\t\t\ttransition: opacity 0.3s ease;\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t\tz-index: 10;\n\t\t\t\t}\n\t\t\t\t.custom-video-player:hover .title-overlay {\n\t\t\t\t\topacity: 1;\n\t\t\t\t}\n\t\t\t\t.video-controls {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tgap: 10px;\n\t\t\t\t\tpadding: 10px;\n\t\t\t\t\tbackground: linear-gradient(to top, rgba(0,0,0,0.8), transparent);\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tright: 0;\n\t\t\t\t}\n\t\t\t\t.video-controls button {\n\t\t\t\t\tbackground: rgba(255,255,255,0.2);\n\t\t\t\t\tborder: none;\n\t\t\t\t\tcolor: white;\n\t\t\t\t\tpadding: 8px 12px;\n\t\t\t\t\tborder-radius: 4px;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t\tfont-size: 14px;\n\t\t\t\t}\n\t\t\t\t.video-controls button:hover {\n\t\t\t\t\tbackground: rgba(255,255,255,0.3);\n\t\t\t\t}\n\t\t\t\t.seek-bar {\n\t\t\t\t\tflex: 1;\n\t\t\t\t\theight: 5px;\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\tbackground: rgba(255,255,255,0.3);\n\t\t\t\t\tborder-radius: 5px;\n\t\t\t\t\toutline: none;\n\t\t\t\t}\n\t\t\t\t.seek-bar::-webkit-slider-thumb {\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\twidth: 15px;\n\t\t\t\t\theight: 15px;\n\t\t\t\t\tbackground: white;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t\t.time-display {\n\t\t\t\t\tcolor: white;\n\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\tmin-width: 100px;\n\t\t\t\t}\n\t\t\t\t.volume-bar {\n\t\t\t\t\twidth: 80px;\n\t\t\t\t\theight: 5px;\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\tbackground: rgba(255,255,255,0.3);\n\t\t\t\t\tborder-radius: 5px;\n\t\t\t\t\toutline: none;\n\t\t\t\t}\n\t\t\t\t.volume-bar::-webkit-slider-thumb {\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\twidth: 12px;\n\t\t\t\t\theight: 12px;\n\t\t\t\t\tbackground: white;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t</style>\n\t\t`;\n\t\tdocument.querySelector(\".media\").innerHTML = videoPlayerHTML;\n\t\tconst videoContainer = document.querySelector(\".custom-video-player\");\n\t\tconst videoElem = videoContainer.querySelector(\"video\");\n\t\tconst playPauseBtn = videoContainer.querySelector(\".play-pause-btn\");\n\t\tconst seekBar = videoContainer.querySelector(\".seek-bar\");\n\t\tconst timeDisplay = videoContainer.querySelector(\".time-display\");\n\t\tconst volumeBar = videoContainer.querySelector(\".volume-bar\");\n\t\tconst fullscreenBtn = videoContainer.querySelector(\".fullscreen-btn\");\n\t\tconst titleOverlay = videoContainer.querySelector(\".title-overlay\");\n\t\tif (dav) {\n\t\t\ttry {\n\t\t\t\tconst davInstances = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\t\t\tconst davUrl = url.split(\"/dav/\")[0] + \"/dav/\";\n\t\t\t\tconst dav = davInstances.find(d => d.url.toLowerCase().includes(davUrl));\n\t\t\t\tif (!dav) throw new Error(\"No matching dav instance found\");\n\t\t\t\tconst client = window.parent.tb.vfs.servers.get(dav.name);\n\t\t\t\tlet filePath;\n\t\t\t\tif (url.startsWith(\"http\")) {\n\t\t\t\t\tconst match = url.match(/^https?:\\/\\/[^\\/]+\\/dav\\/([^\\/]+\\/)?(.+)$/);\n\t\t\t\t\tfilePath = match ? \"/\" + match[2] : url;\n\t\t\t\t} else {\n\t\t\t\t\tfilePath = url.replace(davUrl, \"/\");\n\t\t\t\t}\n\t\t\t\tconst response = await client.getFileContents(filePath);\n\t\t\t\tconst blob = new Blob([response], { type: \"video/\" + ext });\n\t\t\t\tvideoElem.src = URL.createObjectURL(blob);\n\t\t\t} catch (err) {\n\t\t\t\twindow.tb.dialog.Alert({\n\t\t\t\t\ttitle: \"Failed to read dav file\",\n\t\t\t\t\tmessage: err,\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tvideoElem.src = url;\n\t\t}\n\t\tlet scale = 1;\n\t\ttitleOverlay.textContent = fileName || \"Video.mp4\";\n\t\tfunction formatTime(seconds) {\n\t\t\tconst mins = Math.floor(seconds / 60);\n\t\t\tconst secs = Math.floor(seconds % 60);\n\t\t\treturn `${mins}:${secs.toString().padStart(2, \"0\")}`;\n\t\t}\n\t\tvideoElem.addEventListener(\"click\", () => {\n\t\t\tif (videoElem.paused) {\n\t\t\t\tvideoElem.play();\n\t\t\t\tplayPauseBtn.textContent = \"⏸\";\n\t\t\t} else {\n\t\t\t\tvideoElem.pause();\n\t\t\t\tplayPauseBtn.textContent = \"▶\";\n\t\t\t}\n\t\t});\n\t\tplayPauseBtn.addEventListener(\"click\", () => {\n\t\t\tif (videoElem.paused) {\n\t\t\t\tvideoElem.play();\n\t\t\t\tplayPauseBtn.textContent = \"⏸\";\n\t\t\t} else {\n\t\t\t\tvideoElem.pause();\n\t\t\t\tplayPauseBtn.textContent = \"▶\";\n\t\t\t}\n\t\t});\n\t\tvideoElem.addEventListener(\"timeupdate\", () => {\n\t\t\tseekBar.value = (videoElem.currentTime / videoElem.duration) * 100 || 0;\n\t\t\ttimeDisplay.textContent = `${formatTime(videoElem.currentTime)} / ${formatTime(videoElem.duration)}`;\n\t\t});\n\t\tseekBar.addEventListener(\"input\", () => {\n\t\t\tconst time = (seekBar.value / 100) * videoElem.duration;\n\t\t\tvideoElem.currentTime = time;\n\t\t});\n\t\tvolumeBar.addEventListener(\"input\", () => {\n\t\t\tvideoElem.volume = volumeBar.value / 100;\n\t\t});\n\t\tfullscreenBtn.addEventListener(\"click\", () => {\n\t\t\tif (videoContainer.requestFullscreen) {\n\t\t\t\tvideoContainer.requestFullscreen();\n\t\t\t}\n\t\t});\n\t\tvideoElem.play();\n\t\twindow.addEventListener(\"wheel\", function (e) {\n\t\t\tconst zoomSpeed = 0.1;\n\t\t\te.preventDefault();\n\t\t\tif (e.deltaY < 0) {\n\t\t\t\tscale *= 1 + zoomSpeed;\n\t\t\t} else {\n\t\t\t\tscale /= 1 + zoomSpeed;\n\t\t\t}\n\t\t\tvideoContainer.style.transform = `scale(${scale})`;\n\t\t});\n\t\twindow.addEventListener(\"resize\", () => {\n\t\t\tvideoContainer.style.transform = `scale(${scale})`;\n\t\t});\n\t\tvideoElem.addEventListener(\"load\", () => {\n\t\t\tvideoContainer.style.transform = `scale(${scale})`;\n\t\t});\n\t} else if (exts[\"audio\"].includes(ext)) {\n\t\tconst audioPlayerHTML = `\n\t\t\t<div class=\"custom-audio-player\">\n\t\t\t\t<div class=\"title-overlay\"></div>\n\t\t\t\t<div class=\"audio-visual\"></div>\n\t\t\t\t<audio></audio>\n\t\t\t\t<div class=\"audio-controls\">\n\t\t\t\t\t<button class=\"play-pause-btn\">⏸</button>\n\t\t\t\t\t<input type=\"range\" class=\"seek-bar\" value=\"0\" step=\"0.1\">\n\t\t\t\t\t<span class=\"time-display\">0:00 / 0:00</span>\n\t\t\t\t\t<input type=\"range\" class=\"volume-bar\" min=\"0\" max=\"100\" value=\"100\">\n\t\t\t\t\t<button class=\"download-btn\">⬇</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<style>\n\t\t\t\t.custom-audio-player {\n\t\t\t\t\twidth: 82%;\n\t\t\t\t\theight: 200px;\n\t\t\t\t\tbackground: #000;\n\t\t\t\t\tborder-radius: 8px;\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tflex-direction: column;\n\t\t\t\t}\n\t\t\t\t.audio-visual {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\tflex: 1;\n\t\t\t\t\tbackground: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\tfont-size: 48px;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t\t.audio-visual::before {\n\t\t\t\t\tcontent: '🎵';\n\t\t\t\t}\n\t\t\t\t.title-overlay {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tright: 0;\n\t\t\t\t\tpadding: 16px 20px;\n\t\t\t\t\tbackground: linear-gradient(to bottom, rgba(0,0,0,0.8), transparent);\n\t\t\t\t\tcolor: white;\n\t\t\t\t\tfont-size: 16px;\n\t\t\t\t\tfont-weight: 500;\n\t\t\t\t\topacity: 0;\n\t\t\t\t\ttransition: opacity 0.3s ease;\n\t\t\t\t\tpointer-events: none;\n\t\t\t\t\tz-index: 10;\n\t\t\t\t}\n\t\t\t\t.custom-audio-player:hover .title-overlay {\n\t\t\t\t\topacity: 1;\n\t\t\t\t}\n\t\t\t\t.audio-controls {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tgap: 10px;\n\t\t\t\t\tpadding: 10px;\n\t\t\t\t\tbackground: linear-gradient(to top, rgba(0,0,0,0.8), transparent);\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\tright: 0;\n\t\t\t\t}\n\t\t\t\t.audio-controls button {\n\t\t\t\t\tbackground: rgba(255,255,255,0.2);\n\t\t\t\t\tborder: none;\n\t\t\t\t\tcolor: white;\n\t\t\t\t\tpadding: 8px 12px;\n\t\t\t\t\tborder-radius: 4px;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t\tfont-size: 14px;\n\t\t\t\t}\n\t\t\t\t.audio-controls button:hover {\n\t\t\t\t\tbackground: rgba(255,255,255,0.3);\n\t\t\t\t}\n\t\t\t\t.seek-bar {\n\t\t\t\t\tflex: 1;\n\t\t\t\t\theight: 5px;\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\tbackground: rgba(255,255,255,0.3);\n\t\t\t\t\tborder-radius: 5px;\n\t\t\t\t\toutline: none;\n\t\t\t\t}\n\t\t\t\t.seek-bar::-webkit-slider-thumb {\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\twidth: 15px;\n\t\t\t\t\theight: 15px;\n\t\t\t\t\tbackground: white;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t\t.time-display {\n\t\t\t\t\tcolor: white;\n\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\tmin-width: 100px;\n\t\t\t\t}\n\t\t\t\t.volume-bar {\n\t\t\t\t\twidth: 80px;\n\t\t\t\t\theight: 5px;\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\tbackground: rgba(255,255,255,0.3);\n\t\t\t\t\tborder-radius: 5px;\n\t\t\t\t\toutline: none;\n\t\t\t\t}\n\t\t\t\t.volume-bar::-webkit-slider-thumb {\n\t\t\t\t\t-webkit-appearance: none;\n\t\t\t\t\twidth: 12px;\n\t\t\t\t\theight: 12px;\n\t\t\t\t\tbackground: white;\n\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t</style>\n\t\t`;\n\t\tdocument.querySelector(\".media\").innerHTML = audioPlayerHTML;\n\t\tconst audioContainer = document.querySelector(\".custom-audio-player\");\n\t\tconst audioElem = audioContainer.querySelector(\"audio\");\n\t\tconst playPauseBtn = audioContainer.querySelector(\".play-pause-btn\");\n\t\tconst seekBar = audioContainer.querySelector(\".seek-bar\");\n\t\tconst timeDisplay = audioContainer.querySelector(\".time-display\");\n\t\tconst volumeBar = audioContainer.querySelector(\".volume-bar\");\n\t\tconst audioVisual = audioContainer.querySelector(\".audio-visual\");\n\t\tconst titleOverlay = audioContainer.querySelector(\".title-overlay\");\n\t\tif (dav) {\n\t\t\ttry {\n\t\t\t\tconst davInstances = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\t\t\tconst davUrl = url.split(\"/dav/\")[0] + \"/dav/\";\n\t\t\t\tconst dav = davInstances.find(d => d.url.toLowerCase().includes(davUrl));\n\t\t\t\tif (!dav) throw new Error(\"No matching dav instance found\");\n\t\t\t\tconst client = window.parent.tb.vfs.servers.get(dav.name);\n\t\t\t\tlet filePath;\n\t\t\t\tif (url.startsWith(\"http\")) {\n\t\t\t\t\tconst match = url.match(/^https?:\\/\\/[^\\/]+\\/dav\\/([^\\/]+\\/)?(.+)$/);\n\t\t\t\t\tfilePath = match ? \"/\" + match[2] : url;\n\t\t\t\t} else {\n\t\t\t\t\tfilePath = url.replace(davUrl, \"/\");\n\t\t\t\t}\n\t\t\t\tconst response = await client.getFileContents(filePath);\n\t\t\t\tconst blob = new Blob([response], { type: \"audio/\" + ext });\n\t\t\t\taudioElem.src = URL.createObjectURL(blob);\n\t\t\t} catch (err) {\n\t\t\t\twindow.tb.dialog.Alert({\n\t\t\t\t\ttitle: \"Failed to read dav file\",\n\t\t\t\t\tmessage: err,\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\taudioElem.src = url;\n\t\t}\n\t\tlet scale = 1;\n\t\ttitleOverlay.textContent = fileName || \"Audio.mp3\";\n\t\tfunction formatTime(seconds) {\n\t\t\tconst mins = Math.floor(seconds / 60);\n\t\t\tconst secs = Math.floor(seconds % 60);\n\t\t\treturn `${mins}:${secs.toString().padStart(2, \"0\")}`;\n\t\t}\n\t\taudioVisual.addEventListener(\"click\", () => {\n\t\t\tif (audioElem.paused) {\n\t\t\t\taudioElem.play();\n\t\t\t\tplayPauseBtn.textContent = \"⏸\";\n\t\t\t} else {\n\t\t\t\taudioElem.pause();\n\t\t\t\tplayPauseBtn.textContent = \"▶\";\n\t\t\t}\n\t\t});\n\t\tplayPauseBtn.addEventListener(\"click\", () => {\n\t\t\tif (audioElem.paused) {\n\t\t\t\taudioElem.play();\n\t\t\t\tplayPauseBtn.textContent = \"⏸\";\n\t\t\t} else {\n\t\t\t\taudioElem.pause();\n\t\t\t\tplayPauseBtn.textContent = \"▶\";\n\t\t\t}\n\t\t});\n\t\taudioElem.addEventListener(\"timeupdate\", () => {\n\t\t\tseekBar.value = (audioElem.currentTime / audioElem.duration) * 100 || 0;\n\t\t\ttimeDisplay.textContent = `${formatTime(audioElem.currentTime)} / ${formatTime(audioElem.duration)}`;\n\t\t});\n\t\tseekBar.addEventListener(\"input\", () => {\n\t\t\tconst time = (seekBar.value / 100) * audioElem.duration;\n\t\t\taudioElem.currentTime = time;\n\t\t});\n\t\tvolumeBar.addEventListener(\"input\", () => {\n\t\t\taudioElem.volume = volumeBar.value / 100;\n\t\t});\n\t\tconst p = async () => {\n\t\t\tconst ext = await window.parent.tb.mediaplayer.isExisting();\n\t\t\tif (ext === false) {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch(url);\n\t\t\t\t\tconst blob = await response.blob();\n\t\t\t\t\tconst tags = await id3.fromFile(new File([blob], \"audio.mp3\", { type: blob.type }));\n\t\t\t\t\tlet image = null;\n\t\t\t\t\tif (tags.images && tags.images.length > 0) {\n\t\t\t\t\t\tconst imageData = tags.images[0];\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (image && typeof image === \"string\" && image.startsWith(\"blob:\")) {\n\t\t\t\t\t\t\t\tURL.revokeObjectURL(image);\n\t\t\t\t\t\t\t\timage = null;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlet data = imageData.data;\n\t\t\t\t\t\t\tlet uint8;\n\t\t\t\t\t\t\tif (data instanceof ArrayBuffer) {\n\t\t\t\t\t\t\t\tuint8 = new Uint8Array(data);\n\t\t\t\t\t\t\t} else if (ArrayBuffer.isView(data)) {\n\t\t\t\t\t\t\t\tuint8 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n\t\t\t\t\t\t\t} else if (Array.isArray(data)) {\n\t\t\t\t\t\t\t\tuint8 = new Uint8Array(data);\n\t\t\t\t\t\t\t} else if (typeof data === \"string\") {\n\t\t\t\t\t\t\t\tconst b64 = data.replace(/^data:\\w+\\/[a-zA-Z+]+;base64,/, \"\");\n\t\t\t\t\t\t\t\tconst raw = atob(b64);\n\t\t\t\t\t\t\t\tuint8 = new Uint8Array(raw.length);\n\t\t\t\t\t\t\t\tfor (let i = 0; i < raw.length; ++i) uint8[i] = raw.charCodeAt(i);\n\t\t\t\t\t\t\t} else if (window.parent && window.parent.tb && window.parent.tb.buffer && typeof window.parent.tb.buffer.from === \"function\") {\n\t\t\t\t\t\t\t\tconst buf = window.parent.tb.buffer.from(data);\n\t\t\t\t\t\t\t\tuint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthrow new Error(\"Unknown image data type: \" + Object.prototype.toString.call(data));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst mime = imageData.format || imageData.mime || \"image/jpeg\";\n\t\t\t\t\t\t\tconst signatures = [\n\t\t\t\t\t\t\t\t{ name: \"jpeg\", sig: [0xff, 0xd8, 0xff] },\n\t\t\t\t\t\t\t\t{ name: \"png\", sig: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] },\n\t\t\t\t\t\t\t\t{ name: \"gif\", sig: [0x47, 0x49, 0x46, 0x38] },\n\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\tlet startIndex = 0;\n\t\t\t\t\t\t\tfor (const s of signatures) {\n\t\t\t\t\t\t\t\tconst sig = s.sig;\n\t\t\t\t\t\t\t\tfor (let i = 0; i <= uint8.length - sig.length; i++) {\n\t\t\t\t\t\t\t\t\tlet ok = true;\n\t\t\t\t\t\t\t\t\tfor (let j = 0; j < sig.length; j++) {\n\t\t\t\t\t\t\t\t\t\tif (uint8[i + j] !== sig[j]) {\n\t\t\t\t\t\t\t\t\t\t\tok = false;\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (ok) {\n\t\t\t\t\t\t\t\t\t\tstartIndex = i;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (startIndex) break;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (startIndex > 0) {\n\t\t\t\t\t\t\t\tuint8 = uint8.subarray(startIndex);\n\t\t\t\t\t\t\t\tconsole.warn(\"Sliced album art buffer to skip\", startIndex, \"bytes of leading data\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst blob = new Blob([uint8], { type: mime });\n\t\t\t\t\t\t\timage = URL.createObjectURL(blob);\n\t\t\t\t\t\t\taudioVisual.style.backgroundImage = `url(\"${image}\")`;\n\t\t\t\t\t\t\taudioVisual.style.backgroundSize = \"cover\";\n\t\t\t\t\t\t\taudioVisual.style.backgroundPosition = \"center\";\n\t\t\t\t\t\t\taudioVisual.innerHTML = \"\";\n\t\t\t\t\t\t\tconst uint8ToBase64 = u8 => {\n\t\t\t\t\t\t\t\tconst CHUNK = 0x8000;\n\t\t\t\t\t\t\t\tlet index = 0;\n\t\t\t\t\t\t\t\tlet result = [];\n\t\t\t\t\t\t\t\twhile (index < u8.length) {\n\t\t\t\t\t\t\t\t\tresult.push(String.fromCharCode.apply(null, u8.subarray(index, Math.min(index + CHUNK, u8.length))));\n\t\t\t\t\t\t\t\t\tindex += CHUNK;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn btoa(result.join(\"\"));\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tconst testImg = new Image();\n\t\t\t\t\t\t\ttestImg.onload = () => {\n\t\t\t\t\t\t\t\tif (audioVisual.contains(testImg)) audioVisual.removeChild(testImg);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\ttestImg.onerror = () => {\n\t\t\t\t\t\t\t\tconsole.warn(\"Album art blob failed to load; trying base64 fallback\");\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst b64 = uint8ToBase64(uint8);\n\t\t\t\t\t\t\t\t\tconst dataUrl = `data:${mime};base64,${b64}`;\n\t\t\t\t\t\t\t\t\ttestImg.src = dataUrl;\n\t\t\t\t\t\t\t\t\taudioVisual.style.backgroundImage = `url(\"${dataUrl}\")`;\n\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\tconsole.warn(\"Base64 fallback failed\", err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\ttestImg.src = image;\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconsole.warn(\"Failed to decode album art:\", err, imageData);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst title = tags.title || fileName || \"Unknown Title\";\n\t\t\t\t\tconst artist = tags.artist || \"Unknown Artist\";\n\t\t\t\t\ttitleOverlay.textContent = `${title} - ${artist}`;\n\t\t\t\t\twindow.parent.tb.mediaplayer.music({\n\t\t\t\t\t\ttrack_name: title,\n\t\t\t\t\t\tartist: artist,\n\t\t\t\t\t\tendtime: Math.trunc(audioElem.duration),\n\t\t\t\t\t\tbackground: image,\n\t\t\t\t\t\tonPausePlay: () => {\n\t\t\t\t\t\t\tif (audioElem.paused) {\n\t\t\t\t\t\t\t\taudioElem.play();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\taudioElem.pause();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonSeek: time => {\n\t\t\t\t\t\t\taudioElem.currentTime = time;\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.log(\"Error reading ID3 tags:\", e);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\taudioElem.addEventListener(\"play\", p);\n\t\tp();\n\t\taudioElem.play();\n\t\twindow.addEventListener(\"wheel\", function (e) {\n\t\t\tconst zoomSpeed = 0.1;\n\t\t\te.preventDefault();\n\t\t\tif (e.deltaY < 0) {\n\t\t\t\tscale *= 1 + zoomSpeed;\n\t\t\t} else {\n\t\t\t\tscale /= 1 + zoomSpeed;\n\t\t\t}\n\t\t\taudioContainer.style.transform = `scale(${scale})`;\n\t\t});\n\t\twindow.addEventListener(\"resize\", () => {\n\t\t\taudioContainer.style.transform = `scale(${scale})`;\n\t\t});\n\t\taudioElem.addEventListener(\"load\", () => {\n\t\t\taudioContainer.style.transform = `scale(${scale})`;\n\t\t});\n\t}\n}\n\nfunction showFileBrowser() {\n\tif (asked) return;\n\tasked = true;\n\tif (!(window.parent && window.parent.tb && window.parent.tb.dialog && window.parent.tb.fs && window.parent.tb.window)) return;\n\twindow.parent.tb.dialog.FileBrowser({\n\t\ttitle: \"Open a file\",\n\t\tonOk: async file => {\n\t\t\tconst ext = file.split(\".\").pop();\n\t\t\tlet json = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\t\t\tif (file.includes(\"http\")) {\n\t\t\t\topenFile(file, ext, file.split(\"/\").pop(), true);\n\t\t\t} else if (json[\"image\"].includes(ext)) {\n\t\t\t\tlet img = await window.parent.tb.fs.promises.readFile(file);\n\t\t\t\tlet blob = new Blob([img], { type: \"image/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, file.split(\"/\").pop());\n\t\t\t} else if (json[\"animated\"].includes(ext)) {\n\t\t\t\tlet img = await window.parent.tb.fs.promises.readFile(file);\n\t\t\t\tlet blob = new Blob([img], { type: \"image/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, file.split(\"/\").pop());\n\t\t\t} else if (json[\"pdf\"].includes(ext)) {\n\t\t\t\tlet pdf = await window.parent.tb.fs.promises.readFile(file);\n\t\t\t\tlet blob = new Blob([pdf], { type: \"application/pdf\" });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, file.split(\"/\").pop());\n\t\t\t} else if (json[\"video\"].includes(ext)) {\n\t\t\t\tlet video = await window.parent.tb.fs.promises.readFile(file);\n\t\t\t\tlet blob = new Blob([video], { type: \"video/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, file.split(\"/\").pop());\n\t\t\t} else if (json[\"audio\"].includes(ext)) {\n\t\t\t\tlet audio = await window.parent.tb.fs.promises.readFile(file);\n\t\t\t\tlet blob = new Blob([audio], { type: \"audio/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, file.split(\"/\").pop());\n\t\t\t}\n\t\t},\n\t\tonCancel: () => {\n\t\t\twindow.parent.tb.window.close();\n\t\t},\n\t});\n}\n\nlet asked = false;\nwindow.addEventListener(\"message\", async e => {\n\tlet data;\n\ttry {\n\t\tdata = JSON.parse(e.data);\n\t} catch (e) {\n\t\tdata = e.data;\n\t}\n\tif (data === undefined && !asked) {\n\t\tshowFileBrowser();\n\t}\n\tif (data && data.type === \"process\") {\n\t\tasked = true;\n\t\tif (data.path) {\n\t\t\tconst ext = data.path.split(\".\").pop();\n\t\t\tlet json = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\t\t\tif (data.path.includes(\"http\")) {\n\t\t\t\topenFile(data.path, ext, data.path.split(\"/\").pop(), true);\n\t\t\t} else if (json[\"image\"].includes(ext)) {\n\t\t\t\tlet img = await window.parent.tb.fs.promises.readFile(data.path);\n\t\t\t\tlet blob = new Blob([img], { type: \"image/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, data.path.split(\"/\").pop());\n\t\t\t} else if (json[\"animated\"].includes(ext)) {\n\t\t\t\tlet img = await window.parent.tb.fs.promises.readFile(data.path);\n\t\t\t\tlet blob = new Blob([img], { type: \"image/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, data.path.split(\"/\").pop());\n\t\t\t} else if (json[\"pdf\"].includes(ext)) {\n\t\t\t\tlet pdf = await window.parent.tb.fs.promises.readFile(data.path);\n\t\t\t\tlet blob = new Blob([pdf], { type: \"application/pdf\" });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, data.path.split(\"/\").pop());\n\t\t\t} else if (json[\"video\"].includes(ext)) {\n\t\t\t\tlet video = await window.parent.tb.fs.promises.readFile(data.path);\n\t\t\t\tlet blob = new Blob([video], { type: \"video/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, data.path.split(\"/\").pop());\n\t\t\t} else if (json[\"audio\"].includes(ext)) {\n\t\t\t\tlet audio = await window.parent.tb.fs.promises.readFile(data.path);\n\t\t\t\tlet blob = new Blob([audio], { type: \"audio/\" + ext });\n\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\topenFile(url, ext, data.path.split(\"/\").pop());\n\t\t\t}\n\t\t}\n\t}\n});\n"
  },
  {
    "path": "public/apps/media viewer.tapp/index.json",
    "content": "{\n\t\"name\": \"Media Viewer\",\n\t\"config\": {\n\t\t\"title\": \"Media Viewer\",\n\t\t\"icon\": \"/fs/apps/system/media viewer.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/media viewer.tapp/index.html\"\n\t}\n}\n"
  },
  {
    "path": "public/apps/media viewer.tapp/media.com.js",
    "content": "const tb = parent.window.tb;\nconst tb_island = tb.window.island;\nconst tb_window = tb.window;\nconst tb_context_menu = tb.context_menu;\nconst tb_dialog = tb.dialog;\n\ntb_island.addControl({\n\ttext: \"File\",\n\tappname: \"Media Viewer\",\n\tid: \"media-file\",\n\tclick: () => {\n\t\tconst appIsland = parent.document.querySelector(\".app_island\");\n\t\tconst options = [\n\t\t\t{\n\t\t\t\ttext: \"Open File\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tawait tb.dialog.FileBrowser({\n\t\t\t\t\t\ttitle: \"Select a file to view\",\n\t\t\t\t\t\tonOk: async file => {\n\t\t\t\t\t\t\tconst url = `${parent.window.location.origin}/fs/${file}`;\n\t\t\t\t\t\t\tconst ext = file.split(\".\").pop();\n\t\t\t\t\t\t\topenFile(url, ext);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\ttb.contextmenu.create({\n\t\t\tx: appIsland.clientWidth - 110,\n\t\t\ty: appIsland.clientHeight + 12,\n\t\t\tiframe: false,\n\t\t\toptions: options,\n\t\t});\n\t},\n});\ntb_island.addControl({\n\ttext: \"Computer\",\n\tappname: \"Media Viewer\",\n\tid: \"media-computer\",\n\tclick: () => {\n\t\tconst appIsland = parent.document.querySelector(\".app_island\");\n\t\tconst options = [\n\t\t\t{\n\t\t\t\ttext: \"Open File from PC\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tconst file = document.createElement(\"input\");\n\t\t\t\t\tfile.type = \"file\";\n\t\t\t\t\tfile.accept = \"image/*,video/*\";\n\t\t\t\t\tfile.onchange = async () => {\n\t\t\t\t\t\tconst url = URL.createObjectURL(file.files[0]);\n\t\t\t\t\t\tconst ext = file.files[0].name.split(\".\").pop();\n\t\t\t\t\t\topenFile(url, ext);\n\t\t\t\t\t};\n\t\t\t\t\tfile.click();\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\ttb.contextmenu.create({\n\t\t\tx: appIsland.clientWidth - 110,\n\t\t\ty: appIsland.clientHeight + 12,\n\t\t\tiframe: false,\n\t\t\toptions: options,\n\t\t});\n\t},\n});\n"
  },
  {
    "path": "public/apps/nfsadapter/FileSystemDirectoryHandle.js",
    "content": "import FileSystemHandle from './FileSystemHandle.js'\nimport { errors } from './util.js'\n\nconst { GONE, MOD_ERR } = errors\n\nconst kAdapter = Symbol('adapter')\n\nclass FileSystemDirectoryHandle extends FileSystemHandle {\n  /** @type {FileSystemDirectoryHandle} */\n  [kAdapter]\n\n  constructor (adapter) {\n    super(adapter)\n    this[kAdapter] = adapter\n  }\n\n  /**\n   * @param {string} name Name of the directory\n   * @param {object} [options]\n   * @param {boolean} [options.create] create the directory if don't exist\n   * @returns {Promise<FileSystemDirectoryHandle>}\n   */\n  async getDirectoryHandle (name, options = {}) {\n    if (name === '') {\n      throw new TypeError(`Name can't be an empty string.`)\n    }\n    if (name === '.' || name === '..' || name.includes('/')) {\n      throw new TypeError(`Name contains invalid characters.`)\n    }\n    options.create = !!options.create\n    const handle = await this[kAdapter].getDirectoryHandle(name, options)\n    return new FileSystemDirectoryHandle(handle)\n  }\n\n  /** @returns {AsyncGenerator<[string, FileSystemHandle | FileSystemDirectoryHandle]>} */\n  async * entries () {\n    const {FileSystemFileHandle} = await import('./FileSystemFileHandle.js')\n\n    for await (const [_, entry] of this[kAdapter].entries())\n      yield [entry.name, entry.kind === 'file'\n        ? new FileSystemFileHandle(entry)\n        : new FileSystemDirectoryHandle(entry)]\n  }\n\n  /** @deprecated use .entries() instead */\n  async * getEntries() {\n    const {FileSystemFileHandle} = await import('./FileSystemFileHandle.js')\n    console.warn('deprecated, use .entries() instead')\n    for await (let entry of this[kAdapter].entries())\n      yield entry.kind === 'file'\n        ? new FileSystemFileHandle(entry)\n        : new FileSystemDirectoryHandle(entry)\n  }\n\n  /**\n   * @param {string} name Name of the file\n   * @param {object} [options]\n   * @param {boolean} [options.create] create the file if don't exist\n   */\n  async getFileHandle (name, options = {}) {\n    const {FileSystemFileHandle} = await import('./FileSystemFileHandle.js')\n    if (name === '') throw new TypeError(`Name can't be an empty string.`)\n    if (name === '.' || name === '..' || name.includes('/')) {\n      throw new TypeError(`Name contains invalid characters.`)\n    }\n    options.create = !!options.create\n    const handle = await this[kAdapter].getFileHandle(name, options)\n    return new FileSystemFileHandle(handle)\n  }\n\n  /**\n   * @param {string} name\n   * @param {object} [options]\n   * @param {boolean} [options.recursive]\n   */\n  async removeEntry (name, options = {}) {\n    if (name === '') {\n      throw new TypeError(`Name can't be an empty string.`)\n    }\n    if (name === '.' || name === '..' || name.includes('/')) {\n      throw new TypeError(`Name contains invalid characters.`)\n    }\n    options.recursive = !!options.recursive // cuz node's fs.rm require boolean\n    return this[kAdapter].removeEntry(name, options)\n  }\n\n  async resolve (possibleDescendant) {\n    if (await possibleDescendant.isSameEntry(this)) {\n      return []\n    }\n\n    const openSet = [{ handle: this, path: [] }]\n\n    while (openSet.length) {\n      let { handle: current, path } = openSet.pop()\n\n      for await (const entry of current.values()) {\n        if (await entry.isSameEntry(possibleDescendant)) {\n          return [...path, entry.name]\n        }\n        if (entry.kind === 'directory') {\n          openSet.push({ handle: entry, path: [...path, entry.name] })\n        }\n      }\n    }\n\n    return null\n  }\n\n  async * keys () {\n    for await (const [name] of this[kAdapter].entries())\n      yield name\n  }\n\n  async * values () {\n    for await (const [_, entry] of this)\n      yield entry\n  }\n\n  [Symbol.asyncIterator]() {\n    return this.entries()\n  }\n}\n\nObject.defineProperty(FileSystemDirectoryHandle.prototype, Symbol.toStringTag, {\n\tvalue: 'FileSystemDirectoryHandle',\n\twritable: false,\n\tenumerable: false,\n\tconfigurable: true\n})\n\nObject.defineProperties(FileSystemDirectoryHandle.prototype, {\n\tgetDirectoryHandle: { enumerable: true },\n\tentries: { enumerable: true },\n\tgetFileHandle: { enumerable: true },\n\tremoveEntry: { enumerable: true }\n})\n\nif (globalThis.FileSystemDirectoryHandle) {\n  const proto = globalThis.FileSystemDirectoryHandle.prototype\n\n  proto.resolve = async function resolve (possibleDescendant) {\n    if (await possibleDescendant.isSameEntry(this)) {\n      return []\n    }\n\n    const openSet = [{ handle: this, path: [] }]\n\n    while (openSet.length) {\n      let { handle: current, path } = openSet.pop()\n\n      for await (const entry of current.values()) {\n        if (await entry.isSameEntry(possibleDescendant)) {\n          return [...path, entry.name]\n        }\n        if (entry.kind === 'directory') {\n          openSet.push({ handle: entry, path: [...path, entry.name] })\n        }\n      }\n    }\n\n    return null\n  }\n\n  // Safari allows us operate on deleted files,\n  // so we need to check if they still exist.\n  // Hope to remove this one day.\n  async function ensureDoActuallyStillExist (handle) {\n    const root = await navigator.storage.getDirectory()\n    const path = await root.resolve(handle)\n    if (path === null) { throw new DOMException(...GONE) }\n  }\n\n  const entries = proto.entries\n  proto.entries = async function * () {\n    await ensureDoActuallyStillExist(this)\n    yield * entries.call(this)\n  }\n  proto[Symbol.asyncIterator] = async function * () {\n    yield * this.entries()\n  }\n\n  const removeEntry = proto.removeEntry\n  proto.removeEntry = async function (name, options = {}) {\n    return removeEntry.call(this, name, options).catch(async err => {\n      const unknown = err instanceof DOMException && err.name === 'UnknownError'\n      if (unknown && !options.recursive) {\n        const empty = (await entries.call(this).next()).done\n        if (!empty) { throw new DOMException(...MOD_ERR) }\n      }\n      throw err\n    })\n  }\n}\n\nexport default FileSystemDirectoryHandle\nexport { FileSystemDirectoryHandle }\n"
  },
  {
    "path": "public/apps/nfsadapter/FileSystemFileHandle.js",
    "content": "const kAdapter$1 = Symbol('adapter');\n\n/**\n * @typedef {Object} FileSystemHandlePermissionDescriptor\n * @property {('read'|'readwrite')} [mode='read']\n */\nclass FileSystemHandle {\n  /** @type {FileSystemHandle} */\n  [kAdapter$1]\n\n  /** @type {string} */\n  name\n  /** @type {('file'|'directory')} */\n  kind\n\n  /** @param {FileSystemHandle & {writable}} adapter */\n  constructor (adapter) {\n    this.kind = adapter.kind;\n    this.name = adapter.name;\n    this[kAdapter$1] = adapter;\n  }\n\n  /** @param {FileSystemHandlePermissionDescriptor} descriptor */\n  async queryPermission (descriptor = {}) {\n    const { mode = 'read' } = descriptor;\n    const handle = this[kAdapter$1];\n\n    if (handle.queryPermission) {\n      return handle.queryPermission({mode})\n    }\n\n    if (mode === 'read') {\n      return 'granted'\n    } else if (mode === 'readwrite') {\n      return handle.writable ? 'granted' : 'denied'\n    } else {\n      throw new TypeError(`Mode ${mode} must be 'read' or 'readwrite'`)\n    }\n  }\n\n  async requestPermission ({mode = 'read'} = {}) {\n    const handle = this[kAdapter$1];\n    if (handle.requestPermission) {\n      return handle.requestPermission({mode})\n    }\n\n    if (mode === 'read') {\n      return 'granted'\n    } else if (mode === 'readwrite') {\n      return handle.writable ? 'granted' : 'denied'\n    } else {\n      throw new TypeError(`Mode ${mode} must be 'read' or 'readwrite'`)\n    }\n  }\n\n  /**\n   * Attempts to remove the entry represented by handle from the underlying file system.\n   *\n   * @param {object} options\n   * @param {boolean} [options.recursive=false]\n   */\n  async remove (options = {}) {\n    await this[kAdapter$1].remove(options);\n  }\n\n  /**\n   * @param {FileSystemHandle} other\n   */\n  async isSameEntry (other) {\n    if (this === other) return true\n    if (\n      (!other) ||\n      (typeof other !== 'object') ||\n      (this.kind !== other.kind) ||\n      (!other[kAdapter$1])\n    ) return false\n    return this[kAdapter$1].isSameEntry(other[kAdapter$1])\n  }\n}\n\nObject.defineProperty(FileSystemHandle.prototype, Symbol.toStringTag, {\n  value: 'FileSystemHandle',\n  writable: false,\n  enumerable: false,\n  configurable: true\n});\n\n// Safari safari doesn't support writable streams yet.\nif (globalThis.FileSystemHandle) {\n  globalThis.FileSystemHandle.prototype.queryPermission ??= function (descriptor) {\n    return 'granted'\n  };\n}\n\nconst config = {\n  ReadableStream: globalThis.ReadableStream,\n  WritableStream: globalThis.WritableStream,\n  TransformStream: globalThis.TransformStream,\n  DOMException: globalThis.DOMException,\n  Blob: globalThis.Blob,\n  File: globalThis.File,\n};\n\nconst { WritableStream } = config;\n\nclass FileSystemWritableFileStream extends WritableStream {\n  #writer\n  constructor (writer) {\n    super(writer);\n    this.#writer = writer;\n    // Stupid Safari hack to extend native classes\n    // https://bugs.webkit.org/show_bug.cgi?id=226201\n    Object.setPrototypeOf(this, FileSystemWritableFileStream.prototype);\n\n    /** @private */\n    this._closed = false;\n  }\n\n  async close () {\n    this._closed = true;\n    const w = this.getWriter();\n    const p = w.close();\n    w.releaseLock();\n    return p\n    // return super.close ? super.close() : this.getWriter().close()\n  }\n\n  /** @param {number} position */\n  seek (position) {\n    return this.write({ type: 'seek', position })\n  }\n\n  /** @param {number} size */\n  truncate (size) {\n    return this.write({ type: 'truncate', size })\n  }\n\n  // The write(data) method steps are:\n  write (data) {\n    if (this._closed) {\n      return Promise.reject(new TypeError('Cannot write to a CLOSED writable stream'))\n    }\n\n    // 1. Let writer be the result of getting a writer for this.\n    const writer = this.getWriter();\n\n    // 2. Let result be the result of writing a chunk to writer given data.\n    const result = writer.write(data);\n\n    // 3. Release writer.\n    writer.releaseLock();\n\n    // 4. Return result.\n    return result\n  }\n}\n\nObject.defineProperty(FileSystemWritableFileStream.prototype, Symbol.toStringTag, {\n  value: 'FileSystemWritableFileStream',\n  writable: false,\n  enumerable: false,\n  configurable: true\n});\n\nObject.defineProperties(FileSystemWritableFileStream.prototype, {\n  close: { enumerable: true },\n  seek: { enumerable: true },\n  truncate: { enumerable: true },\n  write: { enumerable: true }\n});\n\n// Safari safari doesn't support writable streams yet.\nif (\n  globalThis.FileSystemFileHandle &&\n  !globalThis.FileSystemFileHandle.prototype.createWritable &&\n  !globalThis.FileSystemWritableFileStream\n) {\n  globalThis.FileSystemWritableFileStream = FileSystemWritableFileStream;\n}\n\nconst errors = {\n  INVALID: ['seeking position failed.', 'InvalidStateError'],\n  GONE: ['A requested file or directory could not be found at the time an operation was processed.', 'NotFoundError'],\n  MISMATCH: ['The path supplied exists, but was not an entry of requested type.', 'TypeMismatchError'],\n  MOD_ERR: ['The object can not be modified in this way.', 'InvalidModificationError'],\n  SYNTAX: m => [`Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. ${m}`, 'SyntaxError'],\n  SECURITY: ['It was determined that certain files are unsafe for access within a Web application, or that too many calls are being made on file resources.', 'SecurityError'],\n  DISALLOWED: ['The request is not allowed by the user agent or the platform in the current context.', 'NotAllowedError']\n};\n\nconst { INVALID, SYNTAX, GONE } = errors;\n\nconst kAdapter = Symbol('adapter');\n\nclass FileSystemFileHandle extends FileSystemHandle {\n  /** @type {FileSystemFileHandle} */\n  [kAdapter]\n\n  constructor (adapter) {\n    super(adapter);\n    this[kAdapter] = adapter;\n  }\n\n  /**\n   * @param  {Object} [options={}]\n   * @param  {boolean} [options.keepExistingData]\n   * @returns {Promise<FileSystemWritableFileStream>}\n   */\n  async createWritable (options = {}) {\n    return new FileSystemWritableFileStream(\n      await this[kAdapter].createWritable(options)\n    )\n  }\n\n  /**\n   * @returns {Promise<File>}\n   */\n  async getFile () {\n    return this[kAdapter].getFile()\n  }\n}\n\nObject.defineProperty(FileSystemFileHandle.prototype, Symbol.toStringTag, {\n  value: 'FileSystemFileHandle',\n  writable: false,\n  enumerable: false,\n  configurable: true\n});\n\nObject.defineProperties(FileSystemFileHandle.prototype, {\n  createWritable: { enumerable: true },\n  getFile: { enumerable: true }\n});\n\n// Safari doesn't support async createWritable streams yet.\nif (\n  globalThis.FileSystemFileHandle &&\n  !globalThis.FileSystemFileHandle.prototype.createWritable\n) {\n  const wm = new WeakMap();\n\n  let workerUrl;\n\n  // Worker code that should be inlined (can't use any external functions)\n  const code = () => {\n    let fileHandle, handle;\n\n    onmessage = async evt => {\n      const port = evt.ports[0];\n      const cmd = evt.data;\n      switch (cmd.type) {\n        case 'open':\n          const file = cmd.name;\n\n          let dir = await navigator.storage.getDirectory();\n\n          for (const folder of cmd.path) {\n            dir = await dir.getDirectoryHandle(folder);\n          }\n\n          fileHandle = await dir.getFileHandle(file);\n          handle = await fileHandle.createSyncAccessHandle();\n          break\n        case 'write':\n          handle.write(cmd.data, { at: cmd.position });\n          handle.flush();\n          break\n        case 'truncate':\n          handle.truncate(cmd.size);\n          break\n        case 'abort':\n        case 'close':\n          handle.close();\n          break\n      }\n\n      port.postMessage(0);\n    };\n  };\n\n\n  globalThis.FileSystemFileHandle.prototype.createWritable = async function (options) {\n    // Safari only support writing data in a worker with sync access handle.\n    if (!workerUrl) {\n      const stringCode = `(${code.toString()})()`;\n      const blob = new Blob([stringCode], {\n        type: 'text/javascript'\n      });\n      workerUrl = URL.createObjectURL(blob);\n    }\n    const worker = new Worker(workerUrl, { type: 'module' });\n\n    let position = 0;\n    const textEncoder = new TextEncoder();\n    let size = await this.getFile().then(file => file.size);\n\n    const send = message => new Promise((resolve, reject) => {\n      const mc = new MessageChannel();\n      mc.port1.onmessage = evt => {\n        if (evt.data instanceof Error) reject(evt.data);\n        else resolve(evt.data);\n        mc.port1.close();\n        mc.port2.close();\n        mc.port1.onmessage = null;\n      };\n      worker.postMessage(message, [mc.port2]);\n    });\n\n    // Safari also don't support transferable file system handles.\n    // So we need to pass the path to the worker. This is a bit hacky and ugly.\n    const root = await navigator.storage.getDirectory();\n    const parent = await wm.get(this);\n    const path = await root.resolve(parent);\n\n    // Should likely never happen, but just in case...\n    if (path === null) throw new DOMException(...GONE)\n    await send({ type: 'open', path, name: this.name });\n\n    if (options?.keepExistingData === false) {\n      await send({ type: 'truncate', size: 0 });\n      size = 0;\n    }\n\n    const ws = new FileSystemWritableFileStream({\n      start: ctrl => {\n      },\n      async write(chunk) {\n        const isPlainObject = chunk?.constructor === Object;\n\n        if (isPlainObject) {\n          chunk = { ...chunk };\n        } else {\n          chunk = { type: 'write', data: chunk, position };\n        }\n\n        if (chunk.type === 'write') {\n          if (!('data' in chunk)) {\n            await send({ type: 'close' });\n            throw new DOMException(...SYNTAX('write requires a data argument'))\n          }\n\n          chunk.position ??= position;\n\n          if (typeof chunk.data === 'string') {\n            chunk.data = textEncoder.encode(chunk.data);\n          }\n\n          else if (chunk.data instanceof ArrayBuffer) {\n            chunk.data = new Uint8Array(chunk.data);\n          }\n\n          else if (!(chunk.data instanceof Uint8Array) && ArrayBuffer.isView(chunk.data)) {\n            chunk.data = new Uint8Array(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength);\n          }\n\n          else if (!(chunk.data instanceof Uint8Array)) {\n            const ab = await new Response(chunk.data).arrayBuffer();\n            chunk.data = new Uint8Array(ab);\n          }\n\n          if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n            position = chunk.position;\n          }\n          position += chunk.data.byteLength;\n          size += chunk.data.byteLength;\n        } else if (chunk.type === 'seek') {\n          if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n            if (size < chunk.position) {\n              throw new DOMException(...INVALID)\n            }\n            console.log('seeking', chunk);\n            position = chunk.position;\n            return // Don't need to enqueue seek...\n          } else {\n            await send({ type: 'close' });\n            throw new DOMException(...SYNTAX('seek requires a position argument'))\n          }\n        } else if (chunk.type === 'truncate') {\n          if (Number.isInteger(chunk.size) && chunk.size >= 0) {\n            size = chunk.size;\n            if (position > size) { position = size; }\n          } else {\n            await send({ type: 'close' });\n            throw new DOMException(...SYNTAX('truncate requires a size argument'))\n          }\n        }\n\n        await send(chunk);\n      },\n      async close () {\n        await send({ type: 'close' });\n        worker.terminate();\n      },\n      async abort (reason) {\n        await send({ type: 'abort', reason });\n        worker.terminate();\n      },\n    });\n\n    return ws\n  };\n\n  const orig = FileSystemDirectoryHandle.prototype.getFileHandle;\n  FileSystemDirectoryHandle.prototype.getFileHandle = async function (...args) {\n    const handle = await orig.call(this, ...args);\n    wm.set(handle, this);\n    return handle\n  };\n}\n\nexport { FileSystemFileHandle, FileSystemFileHandle as default };\n"
  },
  {
    "path": "public/apps/nfsadapter/FileSystemHandle.js",
    "content": "const kAdapter = Symbol('adapter');\n\n/**\n * @typedef {Object} FileSystemHandlePermissionDescriptor\n * @property {('read'|'readwrite')} [mode='read']\n */\nclass FileSystemHandle {\n  /** @type {FileSystemHandle} */\n  [kAdapter]\n\n  /** @type {string} */\n  name\n  /** @type {('file'|'directory')} */\n  kind\n\n  /** @param {FileSystemHandle & {writable}} adapter */\n  constructor (adapter) {\n    this.kind = adapter.kind;\n    this.name = adapter.name;\n    this[kAdapter] = adapter;\n  }\n\n  /** @param {FileSystemHandlePermissionDescriptor} descriptor */\n  async queryPermission (descriptor = {}) {\n    const { mode = 'read' } = descriptor;\n    const handle = this[kAdapter];\n\n    if (handle.queryPermission) {\n      return handle.queryPermission({mode})\n    }\n\n    if (mode === 'read') {\n      return 'granted'\n    } else if (mode === 'readwrite') {\n      return handle.writable ? 'granted' : 'denied'\n    } else {\n      throw new TypeError(`Mode ${mode} must be 'read' or 'readwrite'`)\n    }\n  }\n\n  async requestPermission ({mode = 'read'} = {}) {\n    const handle = this[kAdapter];\n    if (handle.requestPermission) {\n      return handle.requestPermission({mode})\n    }\n\n    if (mode === 'read') {\n      return 'granted'\n    } else if (mode === 'readwrite') {\n      return handle.writable ? 'granted' : 'denied'\n    } else {\n      throw new TypeError(`Mode ${mode} must be 'read' or 'readwrite'`)\n    }\n  }\n\n  /**\n   * Attempts to remove the entry represented by handle from the underlying file system.\n   *\n   * @param {object} options\n   * @param {boolean} [options.recursive=false]\n   */\n  async remove (options = {}) {\n    await this[kAdapter].remove(options);\n  }\n\n  /**\n   * @param {FileSystemHandle} other\n   */\n  async isSameEntry (other) {\n    if (this === other) return true\n    if (\n      (!other) ||\n      (typeof other !== 'object') ||\n      (this.kind !== other.kind) ||\n      (!other[kAdapter])\n    ) return false\n    return this[kAdapter].isSameEntry(other[kAdapter])\n  }\n}\n\nObject.defineProperty(FileSystemHandle.prototype, Symbol.toStringTag, {\n  value: 'FileSystemHandle',\n  writable: false,\n  enumerable: false,\n  configurable: true\n});\n\n// Safari safari doesn't support writable streams yet.\nif (globalThis.FileSystemHandle) {\n  globalThis.FileSystemHandle.prototype.queryPermission ??= function (descriptor) {\n    return 'granted'\n  };\n}\n\nexport { FileSystemHandle, FileSystemHandle as default };\n"
  },
  {
    "path": "public/apps/nfsadapter/adapters/anuraadapter.js",
    "content": "import { errors } from '../util.js'\n\nimport config from '../config.js'\nconst join = window.Filer.path.join;\nconst fs = window.anura.fs.promises;\nconst Buffer = window.Filer.Buffer;\nconst cbfs = window.anura.fs; // This stands for callback fs but I like to pretend it stands for cock and ball fs.\n\nconst {\n  DOMException\n} = config\n\nconst { INVALID, GONE, MISMATCH, MOD_ERR, SYNTAX } = errors\n\n/**\n * @see https://github.com/node-fetch/fetch-blob/blob/0455796ede330ecffd9eb6b9fdf206cc15f90f3e/index.js#L232\n * @param {*} object\n * @returns {object is Blob}\n */\nfunction isBlob (object) {\n  return (\n    object &&\n    typeof object === 'object' &&\n    typeof object.constructor === 'function' &&\n    (\n      typeof object.stream === 'function' ||\n      typeof object.arrayBuffer === 'function'\n    ) &&\n    /^(Blob|File)$/.test(object[Symbol.toStringTag])\n  )\n}\n\nexport class Sink {\n  /**\n   * @param {fs.FileHandle} fileHandle\n   * @param {number} size\n   */\n  constructor (fileHandle, size) {\n    this._fileHandle = fileHandle\n    this._size = size\n    this._position = 0\n  }\n\n  async abort() {\n    const filehandle = this._fileHandle\n    await new Promise((res, rej) => {\n      cbfs.close(filehandle, (err) => {\n        if (!err) res() \n          else rej()\n      })\n    })\n  }\n\n  async write (chunk) {\n    if (typeof chunk === 'object') {\n      if (chunk.type === 'write') {\n        if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n          this._position = chunk.position\n        }\n        if (!('data' in chunk)) {\n          const filehandle = this._fileHandle\n          await new Promise((res, rej) => {\n            cbfs.close(filehandle, (err) => {\n              if (!err) res() \n                else rej()\n            })\n          })\n          throw new DOMException(...SYNTAX('write requires a data argument'))\n        }\n        chunk = chunk.data\n      } else if (chunk.type === 'seek') {\n        if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n          if (this._size < chunk.position) {\n            throw new DOMException(...INVALID)\n          }\n          this._position = chunk.position\n          return\n        } else {\n          const filehandle = this._fileHandle\n          await new Promise((res, rej) => {\n            cbfs.close(filehandle, (err) => {\n              if (!err) res() \n                else rej()\n            })\n          })\n          throw new DOMException(...SYNTAX('seek requires a position argument'))\n        }\n      } else if (chunk.type === 'truncate') {\n        if (Number.isInteger(chunk.size) && chunk.size >= 0) {\n          console.log(\"handle:\")\n          console.log(this._fileHandle)\n          const filehandle = this._fileHandle\n          await new Promise((res, rej) => {\n            cbfs.ftruncate(filehandle, chunk.size, (err) => {\n              if (!err) res() \n                else rej()\n            })\n          })\n          this._size = chunk.size\n          if (this._position > this._size) {\n            this._position = this._size\n          }\n          return\n        } else {\n          const filehandle = this._fileHandle\n          await new Promise((res, rej) => {\n            cbfs.close(filehandle, (err) => {\n              if (!err) res() \n                else rej()\n            })\n          })\n          throw new DOMException(...SYNTAX('truncate requires a size argument'))\n        }\n      }\n    }\n\n    if (chunk instanceof ArrayBuffer) {\n      chunk = new Uint8Array(chunk)\n    } else if (typeof chunk === 'string') {\n      chunk = Buffer.from(chunk)\n    } else if (isBlob(chunk)) {\n      for await (const data of chunk.stream()) {\n        // const res = await this._fileHandle.writev([data], this._position)\n        const res = await new Promise((res, rej) => { \n          cbfs.write(this._fileHandle, Filer.Buffer.from(data), 0, data.length, this._position, (err, nbytes) => {\n            if (err) rej(err)\n              else res(nbytes);\n        })})\n        this._position += res.bytesWritten\n        this._size += res.bytesWritten\n      }\n      return\n    }\n    \n    const res = await new Promise((res, rej) => { \n      cbfs.write(this._fileHandle, Filer.Buffer.from(chunk), 0, chunk.length, this._position, (err, nbytes) => {\n        if (err) rej(err)\n          else res(nbytes);\n    })})\n    // const res = await this._fileHandle.writev([chunk], this._position)\n    this._position += res.bytesWritten\n    this._size += res.bytesWritten\n  }\n\n  async close () {\n    // First make sure we close the handle\n    const filehandle = this._fileHandle\n    await new Promise((res, rej) => {\n      cbfs.close(filehandle, (err) => {\n        if (!err) res() \n          else rej()\n      })\n    })\n  }\n}\n\nexport class FileHandle {\n\n  /**\n   * @param {string} path\n   * @param {string} name\n   */\n  constructor (path, name) {\n    this._path = path\n    this.name = name\n    this.kind = 'file'\n  }\n\n  async getFile () {\n    await fs.stat(this._path).catch(err => {\n      if (err.code === 'ENOENT') throw new DOMException(...GONE)\n    })\n\n    // TODO: replace once https://github.com/nodejs/node/issues/37340 is fixed\n    return new File([await fs.readFile(this._path)], this.name);\n  }\n\n  async isSameEntry (other) {\n    return this._path === this._getPath.apply(other)\n  }\n\n  _getPath() {\n    return this._path\n  }\n\n  /** @param {{ keepExistingData: boolean; }} opts */\n  async createWritable (opts) {\n    const fileHandle = await fs.open(this._path, opts.keepExistingData ? 'r+' : 'w+').catch(err => {\n      if (err.code === 'ENOENT') throw new DOMException(...GONE)\n      throw err\n    })\n    const { size } = await fs.stat(this._path);\n    return new Sink(fileHandle, size)\n  }\n}\n\nexport class FolderHandle {\n  _path = ''\n\n  constructor (path = '', name = '') {\n    this.name = name\n    this.kind = 'directory'\n    this._path = path\n  }\n\n  /** @param {FolderHandle} other */\n  async isSameEntry (other) {\n    return this._path === other._path\n  }\n\n  /** @returns {AsyncGenerator<[string, FileHandle | FolderHandle]>} */\n  async * entries () {\n    const dir = this._path\n    const items = await fs.readdir(dir).catch(err => {\n      if (err.code === 'ENOENT') throw new DOMException(...GONE)\n      throw err\n    })\n    for (let name of items) {\n      const path = Filer.path.join(dir, name)\n\n      const stat = await fs.lstat(path)\n      if (stat.isFile()) {\n        yield [name, new FileHandle(path, name)]\n      } else if (stat.isDirectory()) {\n        yield [name, new FolderHandle(path, name)]\n      }\n    }\n  }\n\n  /**\n   * @param {string} name\n   * @param {{ create: boolean; }} opts\n   */\n  async getDirectoryHandle (name, opts) {\n    const path = join(this._path, name)\n    const stat = await fs.lstat(path).catch(err => {\n      if (err.code !== 'ENOENT') throw err\n    })\n    const isDirectory = stat?.isDirectory()\n    if (stat && isDirectory) return new FolderHandle(path, name)\n    if (stat && !isDirectory) throw new DOMException(...MISMATCH)\n    if (!opts.create) throw new DOMException(...GONE)\n    await fs.mkdir(path)\n    return new FolderHandle(path, name)\n  }\n\n  /**\n   * @param {string} name\n   * @param {{ create: boolean; }} opts\n   */\n  async getFileHandle (name, opts) {\n    const path = join(this._path, name)\n    const stat = await fs.lstat(path).catch(err => {\n      if (err.code !== 'ENOENT') throw err\n    })\n    const isFile = stat?.isFile()\n    if (stat && isFile) return new FileHandle(path, name)\n    if (stat && !isFile) throw new DOMException(...MISMATCH)\n    if (!opts.create) throw new DOMException(...GONE)\n    anura.fs.close(await fs.open(path, 'w'))\n    return new FileHandle(path, name)\n  }\n\n  async queryPermission () {\n    return 'granted'\n  } \n\n  /**\n   * @param {string} name\n   * @param {{ recursive: boolean; }} opts\n   */\n  async removeEntry (name, opts) {\n    const path = join(this._path, name)\n    const stat = await fs.lstat(path).catch(err => {\n      if (err.code === 'ENOENT') throw new DOMException(...GONE)\n      throw err\n    })\n    if (stat.isDirectory()) {\n      if (opts.recursive) {\n        await fs.rm(path, { recursive: true, }).catch(err => {\n          if (err.code === 'ENOTEMPTY') throw new DOMException(...MOD_ERR)\n          throw err\n        })\n      } else {\n        await fs.rmdir(path).catch(err => {\n          if (err.code === 'ENOTEMPTY') throw new DOMException(...MOD_ERR)\n          throw err\n        })\n      }\n    } else {\n      await fs.unlink(path)\n    }\n  }\n}\n\nexport default path => {\n  return new FolderHandle(\"/\")\n\n}\n"
  },
  {
    "path": "public/apps/nfsadapter/adapters/memory.js",
    "content": "const errors = {\n  INVALID: ['seeking position failed.', 'InvalidStateError'],\n  GONE: ['A requested file or directory could not be found at the time an operation was processed.', 'NotFoundError'],\n  MISMATCH: ['The path supplied exists, but was not an entry of requested type.', 'TypeMismatchError'],\n  MOD_ERR: ['The object can not be modified in this way.', 'InvalidModificationError'],\n  SYNTAX: m => [`Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. ${m}`, 'SyntaxError'],\n  SECURITY: ['It was determined that certain files are unsafe for access within a Web application, or that too many calls are being made on file resources.', 'SecurityError'],\n  DISALLOWED: ['The request is not allowed by the user agent or the platform in the current context.', 'NotAllowedError']\n};\n\nconst config = {\n  ReadableStream: globalThis.ReadableStream,\n  WritableStream: globalThis.WritableStream,\n  TransformStream: globalThis.TransformStream,\n  DOMException: globalThis.DOMException,\n  Blob: globalThis.Blob,\n  File: globalThis.File,\n};\n\nconst { File, Blob, DOMException } = config;\nconst { INVALID, GONE, MISMATCH, MOD_ERR, SYNTAX, SECURITY, DISALLOWED } = errors;\n\nclass Sink {\n\n  /**\n   * @param {FileHandle} fileHandle\n   * @param {File} file\n   */\n  constructor (fileHandle, file) {\n    this.fileHandle = fileHandle;\n    this.file = file;\n    this.size = file.size;\n    this.position = 0;\n  }\n\n  write (chunk) {\n    let file = this.file;\n\n    if (typeof chunk === 'object') {\n      if (chunk.type === 'write') {\n        if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n          this.position = chunk.position;\n          if (this.size < chunk.position) {\n            this.file = new File(\n              [this.file, new ArrayBuffer(chunk.position - this.size)],\n              this.file.name,\n              this.file\n            );\n          }\n        }\n        if (!('data' in chunk)) {\n          throw new DOMException(...SYNTAX('write requires a data argument'))\n        }\n        chunk = chunk.data;\n      } else if (chunk.type === 'seek') {\n        if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n          if (this.size < chunk.position) {\n            throw new DOMException(...INVALID)\n          }\n          this.position = chunk.position;\n          return\n        } else {\n          throw new DOMException(...SYNTAX('seek requires a position argument'))\n        }\n      } else if (chunk.type === 'truncate') {\n        if (Number.isInteger(chunk.size) && chunk.size >= 0) {\n          file = chunk.size < this.size\n            ? new File([file.slice(0, chunk.size)], file.name, file)\n            : new File([file, new Uint8Array(chunk.size - this.size)], file.name);\n\n          this.size = file.size;\n          if (this.position > file.size) {\n            this.position = file.size;\n          }\n          this.file = file;\n          return\n        } else {\n          throw new DOMException(...SYNTAX('truncate requires a size argument'))\n        }\n      }\n    }\n\n    chunk = new Blob([chunk]);\n\n    let blob = this.file;\n    // Calc the head and tail fragments\n    const head = blob.slice(0, this.position);\n    const tail = blob.slice(this.position + chunk.size);\n\n    // Calc the padding\n    let padding = this.position - head.size;\n    if (padding < 0) {\n      padding = 0;\n    }\n    blob = new File([\n      head,\n      new Uint8Array(padding),\n      chunk,\n      tail\n    ], blob.name);\n\n    this.size = blob.size;\n    this.position += chunk.size;\n\n    this.file = blob;\n  }\n  close () {\n    if (this.fileHandle._deleted) throw new DOMException(...GONE)\n    this.fileHandle._file = this.file;\n    this.file =\n    this.position =\n    this.size = null;\n    if (this.fileHandle.onclose) {\n      this.fileHandle.onclose(this.fileHandle);\n    }\n  }\n}\n\nclass FileHandle {\n  constructor (name = '', file = new File([], name), writable = true) {\n    this._file = file;\n    this.name = name;\n    this.kind = 'file';\n    this._deleted = false;\n    this.writable = writable;\n    this.readable = true;\n  }\n\n  async getFile () {\n    if (this._deleted) throw new DOMException(...GONE)\n    return this._file\n  }\n\n  async createWritable (opts) {\n    if (!this.writable) throw new DOMException(...DISALLOWED)\n    if (this._deleted) throw new DOMException(...GONE)\n\n    const file = opts.keepExistingData\n      ? await this.getFile()\n      : new File([], this.name);\n\n    return new Sink(this, file)\n  }\n\n  async isSameEntry (other) {\n    return this === other\n  }\n\n  async _destroy () {\n    this._deleted = true;\n    this._file = null;\n  }\n}\n\nclass FolderHandle {\n\n  /** @param {string} name */\n  constructor (name, writable = true) {\n    this.name = name;\n    this.kind = 'directory';\n    this._deleted = false;\n    /** @type {Object.<string, (FolderHandle|FileHandle)>} */\n    this._entries = {};\n    this.writable = writable;\n    this.readable = true;\n  }\n\n  /** @returns {AsyncGenerator<[string, FileHandle | FolderHandle]>} */\n  async * entries () {\n    if (this._deleted) throw new DOMException(...GONE)\n    yield* Object.entries(this._entries);\n  }\n\n  async isSameEntry (other) {\n    return this === other\n  }\n\n  /**\n   * @param {string} name\n   * @param {{ create: boolean; }} opts\n   */\n  async getDirectoryHandle (name, opts) {\n    if (this._deleted) throw new DOMException(...GONE)\n    const entry = this._entries[name];\n    if (entry) { // entry exist\n      if (entry instanceof FileHandle) {\n        throw new DOMException(...MISMATCH)\n      } else {\n        return entry\n      }\n    } else {\n      if (opts.create) {\n        return (this._entries[name] = new FolderHandle(name))\n      } else {\n        throw new DOMException(...GONE)\n      }\n    }\n  }\n\n  /**\n   * @param {string} name\n   * @param {{ create: boolean; }} opts\n   */\n  async getFileHandle (name, opts) {\n    const entry = this._entries[name];\n    const isFile = entry instanceof FileHandle;\n    if (entry && isFile) return entry\n    if (entry && !isFile) throw new DOMException(...MISMATCH)\n    if (!entry && !opts.create) throw new DOMException(...GONE)\n    if (!entry && opts.create) {\n      return (this._entries[name] = new FileHandle(name))\n    }\n  }\n\n  async removeEntry (name, opts) {\n    const entry = this._entries[name];\n    if (!entry) throw new DOMException(...GONE)\n    await entry._destroy(opts.recursive);\n    delete this._entries[name];\n  }\n\n  async _destroy (recursive) {\n    for (let x of Object.values(this._entries)) {\n      if (!recursive) throw new DOMException(...MOD_ERR)\n      await x._destroy(recursive);\n    }\n    this._entries = {};\n    this._deleted = true;\n  }\n}\n\nconst fs = new FolderHandle('');\n\nvar memory = () => fs;\n\nexport { FileHandle, FolderHandle, Sink, memory as default };\n"
  },
  {
    "path": "public/apps/nfsadapter/adapters/sandbox.js",
    "content": "const errors = {\n  INVALID: ['seeking position failed.', 'InvalidStateError'],\n  GONE: ['A requested file or directory could not be found at the time an operation was processed.', 'NotFoundError'],\n  MISMATCH: ['The path supplied exists, but was not an entry of requested type.', 'TypeMismatchError'],\n  MOD_ERR: ['The object can not be modified in this way.', 'InvalidModificationError'],\n  SYNTAX: m => [`Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. ${m}`, 'SyntaxError'],\n  SECURITY: ['It was determined that certain files are unsafe for access within a Web application, or that too many calls are being made on file resources.', 'SecurityError'],\n  DISALLOWED: ['The request is not allowed by the user agent or the platform in the current context.', 'NotAllowedError']\n};\n\n/* global Blob, DOMException */\n\nconst { DISALLOWED } = errors;\n\nclass Sink {\n  /**\n   * @param {FileWriter} writer\n   * @param {FileEntry} fileEntry\n   */\n  constructor (writer, fileEntry) {\n    this.writer = writer;\n    this.fileEntry = fileEntry;\n  }\n\n  /**\n   * @param {BlobPart | Object} chunk\n   */\n  async write (chunk) {\n    if (typeof chunk === 'object') {\n      if (chunk.type === 'write') {\n        if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n          this.writer.seek(chunk.position);\n          if (this.writer.position !== chunk.position) {\n            await new Promise((resolve, reject) => {\n              this.writer.onwriteend = resolve;\n              this.writer.onerror = reject;\n              this.writer.truncate(chunk.position);\n            });\n            this.writer.seek(chunk.position);\n          }\n        }\n        if (!('data' in chunk)) {\n          throw new DOMException('Failed to execute \\'write\\' on \\'UnderlyingSinkBase\\': Invalid params passed. write requires a data argument', 'SyntaxError')\n        }\n        chunk = chunk.data;\n      } else if (chunk.type === 'seek') {\n        if (Number.isInteger(chunk.position) && chunk.position >= 0) {\n          this.writer.seek(chunk.position);\n          if (this.writer.position !== chunk.position) {\n            throw new DOMException('seeking position failed', 'InvalidStateError')\n          }\n          return\n        } else {\n          throw new DOMException('Failed to execute \\'write\\' on \\'UnderlyingSinkBase\\': Invalid params passed. seek requires a position argument', 'SyntaxError')\n        }\n      } else if (chunk.type === 'truncate') {\n        return new Promise(resolve => {\n          if (Number.isInteger(chunk.size) && chunk.size >= 0) {\n            this.writer.onwriteend = evt => resolve();\n            this.writer.truncate(chunk.size);\n          } else {\n            throw new DOMException('Failed to execute \\'write\\' on \\'UnderlyingSinkBase\\': Invalid params passed. truncate requires a size argument', 'SyntaxError')\n          }\n        })\n      }\n    }\n    await new Promise((resolve, reject) => {\n      this.writer.onwriteend = resolve;\n      this.writer.onerror = reject;\n      this.writer.write(new Blob([chunk]));\n    });\n  }\n\n  close () {\n    return new Promise(this.fileEntry.file.bind(this.fileEntry))\n  }\n}\n\nclass FileHandle {\n  /** @param {FileEntry} file */\n  constructor (file, writable = true) {\n    this.file = file;\n    this.kind = 'file';\n    this.writable = writable;\n    this.readable = true;\n  }\n\n  get name () {\n    return this.file.name\n  }\n\n  /**\n   * @param {{ file: { toURL: () => string; }; }} other\n   */\n  isSameEntry (other) {\n    return this.file.toURL() === other.file.toURL()\n  }\n\n  /** @return {Promise<File>} */\n  getFile () {\n    return new Promise(this.file.file.bind(this.file))\n  }\n\n  /** @return {Promise<Sink>} */\n  createWritable (opts) {\n    if (!this.writable) throw new DOMException(...DISALLOWED)\n\n    return new Promise((resolve, reject) =>\n      this.file.createWriter(fileWriter => {\n        if (opts.keepExistingData === false) {\n          fileWriter.onwriteend = evt => resolve(new Sink(fileWriter, this.file));\n          fileWriter.truncate(0);\n        } else {\n          resolve(new Sink(fileWriter, this.file));\n        }\n      }, reject)\n    )\n  }\n}\n\nclass FolderHandle {\n  /** @param {DirectoryEntry} dir */\n  constructor (dir, writable = true) {\n    this.dir = dir;\n    this.writable = writable;\n    this.readable = true;\n    this.kind = 'directory';\n    this.name = dir.name;\n  }\n\n  /** @param {FolderHandle} other */\n  isSameEntry (other) {\n    return this.dir.fullPath === other.dir.fullPath\n  }\n\n  /** @returns {AsyncGenerator<[string, FileHandle | FolderHandle]>} */\n  async * entries () {\n    const reader = this.dir.createReader();\n    const entries = await new Promise(reader.readEntries.bind(reader));\n    for (const x of entries) {\n      yield [x.name, x.isFile ? new FileHandle(x, this.writable) : new FolderHandle(x, this.writable)];\n    }\n  }\n\n  /**\n   * @param {string} name\n   * @param {{ create: boolean; }} opts\n   * @returns {Promise<FolderHandle>}\n   */\n  getDirectoryHandle (name, opts) {\n    return new Promise((resolve, reject) => {\n      this.dir.getDirectory(name, opts, dir => {\n        resolve(new FolderHandle(dir));\n      }, reject);\n    })\n  }\n\n  /**\n   * @param {string} name\n   * @param {{ create: boolean; }} opts\n   * @returns {Promise<FileHandle>}\n   */\n  getFileHandle (name, opts) {\n    return new Promise((resolve, reject) =>\n      this.dir.getFile(name, opts, file => resolve(new FileHandle(file)), reject)\n    )\n  }\n\n  /**\n   * @param {string} name\n   * @param {{ recursive: boolean; }} opts\n   */\n  async removeEntry (name, opts) {\n    /** @type {Error|FolderHandle|FileHandle} */\n    const entry = await this.getDirectoryHandle(name, { create: false }).catch(err =>\n      err.name === 'TypeMismatchError' ? this.getFileHandle(name, { create: false }) : err\n    );\n\n    if (entry instanceof Error) throw entry\n\n    return new Promise((resolve, reject) => {\n      if (entry instanceof FolderHandle) {\n        opts.recursive\n          ? entry.dir.removeRecursively(() => resolve(), reject)\n          : entry.dir.remove(() => resolve(), reject);\n      } else if (entry.file) {\n        entry.file.remove(() => resolve(), reject);\n      }\n    })\n  }\n}\n\nvar sandbox = (opts = {}) => new Promise((resolve, reject) =>\n  window.webkitRequestFileSystem(\n    opts._persistent, 0,\n    e => resolve(new FolderHandle(e.root)),\n    reject\n  )\n);\n\nexport { FileHandle, FolderHandle, sandbox as default };\n"
  },
  {
    "path": "public/apps/nfsadapter/config.js",
    "content": "const config = {\n  ReadableStream: globalThis.ReadableStream,\n  WritableStream: globalThis.WritableStream,\n  TransformStream: globalThis.TransformStream,\n  DOMException: globalThis.DOMException,\n  Blob: globalThis.Blob,\n  File: globalThis.File,\n}\n\nexport default config\n"
  },
  {
    "path": "public/apps/nfsadapter/nfsadapter.js",
    "content": "const e=globalThis.showDirectoryPicker;async function t(t={}){if(e&&!t._preferPolyfill)return e(t);const i=document.createElement(\"input\");i.type=\"file\",i.webkitdirectory=!0,i.multiple=!0,i.style.position=\"fixed\",i.style.top=\"-100000px\",i.style.left=\"-100000px\",document.body.appendChild(i);const r=Promise.resolve().then((function(){return p}));return await new Promise((e=>{i.addEventListener(\"change\",e),i.click()})),r.then((e=>e.getDirHandlesFromInput(i)))}const i={accepts:[]},r=globalThis.showOpenFilePicker;async function n(e={}){const t={...i,...e};if(r&&!e._preferPolyfill)return r(t);const n=document.createElement(\"input\");n.type=\"file\",n.multiple=t.multiple,n.accept=(t.accepts||[]).map((e=>[...(e.extensions||[]).map((e=>\".\"+e)),...e.mimeTypes||[]])).flat().join(\",\"),Object.assign(n.style,{position:\"fixed\",top:\"-100000px\",left:\"-100000px\"}),document.body.appendChild(n);const s=Promise.resolve().then((function(){return p}));return await new Promise((e=>{n.addEventListener(\"change\",e,{once:!0}),n.click()})),n.remove(),s.then((e=>e.getFileHandlesFromInput(n)))}const s=globalThis.showSaveFilePicker;async function a(e={}){if(s&&!e._preferPolyfill)return s(e);e._name&&(console.warn(\"deprecated _name, spec now have `suggestedName`\"),e.suggestedName=e._name);const{FileSystemFileHandle:t}=await Promise.resolve().then((function(){return P})),{FileHandle:i}=await Promise.resolve().then((function(){return R}));return new t(new i(e.suggestedName))}async function o(e,t={}){if(!e)return globalThis.navigator?.storage?.getDirectory()||globalThis.getOriginPrivateDirectory();const{FileSystemDirectoryHandle:i}=await Promise.resolve().then((function(){return F})),r=await e;return new i(await(r.default?r.default(t):r(t)))}globalThis.DataTransferItem&&!DataTransferItem.prototype.getAsFileSystemHandle&&(DataTransferItem.prototype.getAsFileSystemHandle=async function(){const e=this.webkitGetAsEntry(),[{FileHandle:t,FolderHandle:i},{FileSystemDirectoryHandle:r},{FileSystemFileHandle:n}]=await Promise.all([Promise.resolve().then((function(){return L})),Promise.resolve().then((function(){return F})),Promise.resolve().then((function(){return P}))]);return e.isFile?new n(new t(e,!1)):new r(new i(e,!1))});const l={ReadableStream:globalThis.ReadableStream,WritableStream:globalThis.WritableStream,TransformStream:globalThis.TransformStream,DOMException:globalThis.DOMException,Blob:globalThis.Blob,File:globalThis.File},{WritableStream:c}=l;class d extends c{#e;constructor(e){super(e),this.#e=e,Object.setPrototypeOf(this,d.prototype),this._closed=!1}async close(){this._closed=!0;const e=this.getWriter(),t=e.close();return e.releaseLock(),t}seek(e){return this.write({type:\"seek\",position:e})}truncate(e){return this.write({type:\"truncate\",size:e})}write(e){if(this._closed)return Promise.reject(new TypeError(\"Cannot write to a CLOSED writable stream\"));const t=this.getWriter(),i=t.write(e);return t.releaseLock(),i}}Object.defineProperty(d.prototype,Symbol.toStringTag,{value:\"FileSystemWritableFileStream\",writable:!1,enumerable:!1,configurable:!0}),Object.defineProperties(d.prototype,{close:{enumerable:!0},seek:{enumerable:!0},truncate:{enumerable:!0},write:{enumerable:!0}}),!globalThis.FileSystemFileHandle||globalThis.FileSystemFileHandle.prototype.createWritable||globalThis.FileSystemWritableFileStream||(globalThis.FileSystemWritableFileStream=d);const h=Symbol(\"adapter\");class w{[h];name;kind;constructor(e){this.kind=e.kind,this.name=e.name,this[h]=e}async queryPermission(e={}){const{mode:t=\"read\"}=e,i=this[h];if(i.queryPermission)return i.queryPermission({mode:t});if(\"read\"===t)return\"granted\";if(\"readwrite\"===t)return i.writable?\"granted\":\"denied\";throw new TypeError(`Mode ${t} must be 'read' or 'readwrite'`)}async requestPermission({mode:e=\"read\"}={}){const t=this[h];if(t.requestPermission)return t.requestPermission({mode:e});if(\"read\"===e)return\"granted\";if(\"readwrite\"===e)return t.writable?\"granted\":\"denied\";throw new TypeError(`Mode ${e} must be 'read' or 'readwrite'`)}async remove(e={}){await this[h].remove(e)}async isSameEntry(e){return this===e||!(!e||\"object\"!=typeof e||this.kind!==e.kind||!e[h])&&this[h].isSameEntry(e[h])}}Object.defineProperty(w.prototype,Symbol.toStringTag,{value:\"FileSystemHandle\",writable:!1,enumerable:!1,configurable:!0}),globalThis.FileSystemHandle&&(globalThis.FileSystemHandle.prototype.queryPermission??=function(e){return\"granted\"});const u={INVALID:[\"seeking position failed.\",\"InvalidStateError\"],GONE:[\"A requested file or directory could not be found at the time an operation was processed.\",\"NotFoundError\"],MISMATCH:[\"The path supplied exists, but was not an entry of requested type.\",\"TypeMismatchError\"],MOD_ERR:[\"The object can not be modified in this way.\",\"InvalidModificationError\"],SYNTAX:e=>[`Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. ${e}`,\"SyntaxError\"],SECURITY:[\"It was determined that certain files are unsafe for access within a Web application, or that too many calls are being made on file resources.\",\"SecurityError\"],DISALLOWED:[\"The request is not allowed by the user agent or the platform in the current context.\",\"NotAllowedError\"]},y={writable:globalThis.WritableStream};var p=Object.freeze({__proto__:null,errors:u,config:y,fromDataTransfer:async function(e){console.warn(\"deprecated fromDataTransfer - use `dt.items[0].getAsFileSystemHandle()` instead\");const[t,i,r]=await Promise.all([Promise.resolve().then((function(){return te})),Promise.resolve().then((function(){return L})),Promise.resolve().then((function(){return F}))]),n=new t.FolderHandle(\"\",!1);return n._entries=e.map((e=>e.isFile?new i.FileHandle(e,!1):new i.FolderHandle(e,!1))),new r.FileSystemDirectoryHandle(n)},getDirHandlesFromInput:async function(e){const{FolderHandle:t,FileHandle:i}=await Promise.resolve().then((function(){return te})),{FileSystemDirectoryHandle:r}=await Promise.resolve().then((function(){return F})),n=Array.from(e.files),s=n[0].webkitRelativePath.split(\"/\",1)[0],a=new t(s,!1);return n.forEach((e=>{const r=e.webkitRelativePath.split(\"/\");r.shift();const n=r.pop();r.reduce(((e,i)=>(e._entries[i]||(e._entries[i]=new t(i,!1)),e._entries[i])),a)._entries[n]=new i(e.name,e,!1)})),new r(a)},getFileHandlesFromInput:async function(e){const{FileHandle:t}=await Promise.resolve().then((function(){return te})),{FileSystemFileHandle:i}=await Promise.resolve().then((function(){return P}));return Array.from(e.files).map((e=>new i(new t(e.name,e,!1))))}});const{GONE:m,MOD_ERR:f}=u,b=Symbol(\"adapter\");class g extends w{[b];constructor(e){super(e),this[b]=e}async getDirectoryHandle(e,t={}){if(\"\"===e)throw new TypeError(\"Name can't be an empty string.\");if(\".\"===e||\"..\"===e||e.includes(\"/\"))throw new TypeError(\"Name contains invalid characters.\");t.create=!!t.create;const i=await this[b].getDirectoryHandle(e,t);return new g(i)}async*entries(){const{FileSystemFileHandle:e}=await Promise.resolve().then((function(){return P}));for await(const[t,i]of this[b].entries())yield[i.name,\"file\"===i.kind?new e(i):new g(i)]}async*getEntries(){const{FileSystemFileHandle:e}=await Promise.resolve().then((function(){return P}));console.warn(\"deprecated, use .entries() instead\");for await(let t of this[b].entries())yield\"file\"===t.kind?new e(t):new g(t)}async getFileHandle(e,t={}){const{FileSystemFileHandle:i}=await Promise.resolve().then((function(){return P}));if(\"\"===e)throw new TypeError(\"Name can't be an empty string.\");if(\".\"===e||\"..\"===e||e.includes(\"/\"))throw new TypeError(\"Name contains invalid characters.\");t.create=!!t.create;return new i(await this[b].getFileHandle(e,t))}async removeEntry(e,t={}){if(\"\"===e)throw new TypeError(\"Name can't be an empty string.\");if(\".\"===e||\"..\"===e||e.includes(\"/\"))throw new TypeError(\"Name contains invalid characters.\");return t.recursive=!!t.recursive,this[b].removeEntry(e,t)}async resolve(e){if(await e.isSameEntry(this))return[];const t=[{handle:this,path:[]}];for(;t.length;){let{handle:i,path:r}=t.pop();for await(const n of i.values()){if(await n.isSameEntry(e))return[...r,n.name];\"directory\"===n.kind&&t.push({handle:n,path:[...r,n.name]})}}return null}async*keys(){for await(const[e]of this[b].entries())yield e}async*values(){for await(const[e,t]of this)yield t}[Symbol.asyncIterator](){return this.entries()}}if(Object.defineProperty(g.prototype,Symbol.toStringTag,{value:\"FileSystemDirectoryHandle\",writable:!1,enumerable:!1,configurable:!0}),Object.defineProperties(g.prototype,{getDirectoryHandle:{enumerable:!0},entries:{enumerable:!0},getFileHandle:{enumerable:!0},removeEntry:{enumerable:!0}}),globalThis.FileSystemDirectoryHandle){const e=globalThis.FileSystemDirectoryHandle.prototype;e.resolve=async function(e){if(await e.isSameEntry(this))return[];const t=[{handle:this,path:[]}];for(;t.length;){let{handle:i,path:r}=t.pop();for await(const n of i.values()){if(await n.isSameEntry(e))return[...r,n.name];\"directory\"===n.kind&&t.push({handle:n,path:[...r,n.name]})}}return null};const t=e.entries;e.entries=async function*(){await async function(e){const t=await navigator.storage.getDirectory();if(null===await t.resolve(e))throw new DOMException(...m)}(this),yield*t.call(this)},e[Symbol.asyncIterator]=async function*(){yield*this.entries()};const i=e.removeEntry;e.removeEntry=async function(e,r={}){return i.call(this,e,r).catch((async e=>{if(e instanceof DOMException&&\"UnknownError\"===e.name&&!r.recursive){if(!(await t.call(this).next()).done)throw new DOMException(...f)}throw e}))}}var F=Object.freeze({__proto__:null,default:g,FileSystemDirectoryHandle:g});const{INVALID:_,SYNTAX:S,GONE:E}=u,v=Symbol(\"adapter\");class H extends w{[v];constructor(e){super(e),this[v]=e}async createWritable(e={}){return new d(await this[v].createWritable(e))}async getFile(){return this[v].getFile()}}if(Object.defineProperty(H.prototype,Symbol.toStringTag,{value:\"FileSystemFileHandle\",writable:!1,enumerable:!1,configurable:!0}),Object.defineProperties(H.prototype,{createWritable:{enumerable:!0},getFile:{enumerable:!0}}),globalThis.FileSystemFileHandle&&!globalThis.FileSystemFileHandle.prototype.createWritable){const e=new WeakMap;let t;const i=()=>{let e,t;onmessage=async i=>{const r=i.ports[0],n=i.data;switch(n.type){case\"open\":const i=n.name;let r=await navigator.storage.getDirectory();for(const e of n.path)r=await r.getDirectoryHandle(e);e=await r.getFileHandle(i),t=await e.createSyncAccessHandle();break;case\"write\":t.write(n.data,{at:n.position}),t.flush();break;case\"truncate\":t.truncate(n.size);break;case\"abort\":case\"close\":t.close()}r.postMessage(0)}};globalThis.FileSystemFileHandle.prototype.createWritable=async function(r){if(!t){const e=`(${i.toString()})()`,r=new Blob([e],{type:\"text/javascript\"});t=URL.createObjectURL(r)}const n=new Worker(t,{type:\"module\"});let s=0;const a=new TextEncoder;let o=await this.getFile().then((e=>e.size));const l=e=>new Promise(((t,i)=>{const r=new MessageChannel;r.port1.onmessage=e=>{e.data instanceof Error?i(e.data):t(e.data),r.port1.close(),r.port2.close(),r.port1.onmessage=null},n.postMessage(e,[r.port2])})),c=await navigator.storage.getDirectory(),h=await e.get(this),w=await c.resolve(h);if(null===w)throw new DOMException(...E);await l({type:\"open\",path:w,name:this.name}),!1===r?.keepExistingData&&(await l({type:\"truncate\",size:0}),o=0);return new d({start:e=>{},async write(e){if(\"write\"===(e=e?.constructor===Object?{...e}:{type:\"write\",data:e,position:s}).type){if(!(\"data\"in e))throw await l({type:\"close\"}),new DOMException(...S(\"write requires a data argument\"));if(e.position??=s,\"string\"==typeof e.data)e.data=a.encode(e.data);else if(e.data instanceof ArrayBuffer)e.data=new Uint8Array(e.data);else if(e.data instanceof Uint8Array||!ArrayBuffer.isView(e.data)){if(!(e.data instanceof Uint8Array)){const t=await new Response(e.data).arrayBuffer();e.data=new Uint8Array(t)}}else e.data=new Uint8Array(e.data.buffer,e.data.byteOffset,e.data.byteLength);Number.isInteger(e.position)&&e.position>=0&&(s=e.position),s+=e.data.byteLength,o+=e.data.byteLength}else{if(\"seek\"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(o<e.position)throw new DOMException(..._);return console.log(\"seeking\",e),void(s=e.position)}throw await l({type:\"close\"}),new DOMException(...S(\"seek requires a position argument\"))}if(\"truncate\"===e.type){if(!(Number.isInteger(e.size)&&e.size>=0))throw await l({type:\"close\"}),new DOMException(...S(\"truncate requires a size argument\"));o=e.size,s>o&&(s=o)}}await l(e)},async close(){await l({type:\"close\"}),n.terminate()},async abort(e){await l({type:\"abort\",reason:e}),n.terminate()}})};const r=FileSystemDirectoryHandle.prototype.getFileHandle;FileSystemDirectoryHandle.prototype.getFileHandle=async function(...t){const i=await r.call(this,...t);return e.set(i,this),i}}var P=Object.freeze({__proto__:null,default:H,FileSystemFileHandle:H});const{WritableStream:T,TransformStream:D,DOMException:k,Blob:O}=l,{GONE:x}=u,z=/constructor/i.test(window.HTMLElement);class M{constructor(e){e.onmessage=e=>this._onMessage(e.data),this._port=e,this._resetReady()}start(e){return this._controller=e,this._readyPromise}write(e){const t={type:0,chunk:e};return this._port.postMessage(t,[e.buffer]),this._resetReady(),this._readyPromise}close(){this._port.postMessage({type:2}),this._port.close()}abort(e){this._port.postMessage({type:1,reason:e}),this._port.close()}_onMessage(e){0===e.type&&this._resolveReady(),1===e.type&&this._onError(e.reason)}_onError(e){this._controller.error(e),this._rejectReady(e),this._port.close()}_resetReady(){this._readyPromise=new Promise(((e,t)=>{this._readyResolve=e,this._readyReject=t})),this._readyPending=!0}_resolveReady(){this._readyResolve(),this._readyPending=!1}_rejectReady(e){this._readyPending||this._resetReady(),this._readyPromise.catch((()=>{})),this._readyReject(e),this._readyPending=!1}}class I{constructor(e){const t=new MessageChannel;this.readablePort=t.port1,this.writable=new e(new M(t.port2))}}var R=Object.freeze({__proto__:null,FileHandle:class{constructor(e=\"unkown\"){this.name=e,this.kind=\"file\"}async getFile(){throw new k(...x)}async isSameEntry(e){return this===e}async createWritable(e={}){const t=await(navigator.serviceWorker?.getRegistration()),i=document.createElement(\"a\"),r=new D,n=r.writable;if(i.download=this.name,z||!t){let e=[];r.readable.pipeTo(new T({write(t){e.push(new O([t]))},close(){const t=new O(e,{type:\"application/octet-stream; charset=utf-8\"});e=[],i.href=URL.createObjectURL(t),i.click(),setTimeout((()=>URL.revokeObjectURL(i.href)),1e4)}}))}else{const{writable:i,readablePort:n}=new I(T),s=encodeURIComponent(this.name).replace(/['()]/g,escape).replace(/\\*/g,\"%2A\"),a={\"content-disposition\":\"attachment; filename*=UTF-8''\"+s,\"content-type\":\"application/octet-stream; charset=utf-8\",...e.size?{\"content-length\":e.size}:{}},o=setTimeout((()=>t.active.postMessage(0)),1e4);r.readable.pipeThrough(new D({transform(e,t){if(e instanceof Uint8Array)return t.enqueue(e);const i=new Response(e).body.getReader(),r=e=>i.read().then((e=>e.done?0:r(t.enqueue(e.value))));return r()}})).pipeTo(i).finally((()=>{clearInterval(o)})),t.active.postMessage({url:t.scope+s,headers:a,readablePort:n},[n]);const l=document.createElement(\"iframe\");l.hidden=!0,l.src=t.scope+s,document.body.appendChild(l)}return n.getWriter()}}});const{DISALLOWED:j}=u;class A{constructor(e,t){this.writer=e,this.fileEntry=t}async write(e){if(\"object\"==typeof e)if(\"write\"===e.type){if(Number.isInteger(e.position)&&e.position>=0&&(this.writer.seek(e.position),this.writer.position!==e.position&&(await new Promise(((t,i)=>{this.writer.onwriteend=t,this.writer.onerror=i,this.writer.truncate(e.position)})),this.writer.seek(e.position))),!(\"data\"in e))throw new DOMException(\"Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. write requires a data argument\",\"SyntaxError\");e=e.data}else{if(\"seek\"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(this.writer.seek(e.position),this.writer.position!==e.position)throw new DOMException(\"seeking position failed\",\"InvalidStateError\");return}throw new DOMException(\"Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. seek requires a position argument\",\"SyntaxError\")}if(\"truncate\"===e.type)return new Promise((t=>{if(!(Number.isInteger(e.size)&&e.size>=0))throw new DOMException(\"Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. truncate requires a size argument\",\"SyntaxError\");this.writer.onwriteend=e=>t(),this.writer.truncate(e.size)}))}await new Promise(((t,i)=>{this.writer.onwriteend=t,this.writer.onerror=i,this.writer.write(new Blob([e]))}))}close(){return new Promise(this.fileEntry.file.bind(this.fileEntry))}}class N{constructor(e,t=!0){this.file=e,this.kind=\"file\",this.writable=t,this.readable=!0}get name(){return this.file.name}isSameEntry(e){return this.file.toURL()===e.file.toURL()}getFile(){return new Promise(this.file.file.bind(this.file))}createWritable(e){if(!this.writable)throw new DOMException(...j);return new Promise(((t,i)=>this.file.createWriter((i=>{!1===e.keepExistingData?(i.onwriteend=e=>t(new A(i,this.file)),i.truncate(0)):t(new A(i,this.file))}),i)))}}class W{constructor(e,t=!0){this.dir=e,this.writable=t,this.readable=!0,this.kind=\"directory\",this.name=e.name}isSameEntry(e){return this.dir.fullPath===e.dir.fullPath}async*entries(){const e=this.dir.createReader(),t=await new Promise(e.readEntries.bind(e));for(const e of t)yield[e.name,e.isFile?new N(e,this.writable):new W(e,this.writable)]}getDirectoryHandle(e,t){return new Promise(((i,r)=>{this.dir.getDirectory(e,t,(e=>{i(new W(e))}),r)}))}getFileHandle(e,t){return new Promise(((i,r)=>this.dir.getFile(e,t,(e=>i(new N(e))),r)))}async removeEntry(e,t){const i=await this.getDirectoryHandle(e,{create:!1}).catch((t=>\"TypeMismatchError\"===t.name?this.getFileHandle(e,{create:!1}):t));if(i instanceof Error)throw i;return new Promise(((e,r)=>{i instanceof W?t.recursive?i.dir.removeRecursively((()=>e()),r):i.dir.remove((()=>e()),r):i.file&&i.file.remove((()=>e()),r)}))}}var L=Object.freeze({__proto__:null,FileHandle:N,FolderHandle:W,default:(e={})=>new Promise(((t,i)=>window.webkitRequestFileSystem(e._persistent,0,(e=>t(new W(e.root))),i)))});const{File:U,Blob:q,DOMException:B}=l,{INVALID:C,GONE:G,MISMATCH:Y,MOD_ERR:V,SYNTAX:$,SECURITY:X,DISALLOWED:J}=u;class K{constructor(e,t){this.fileHandle=e,this.file=t,this.size=t.size,this.position=0}write(e){let t=this.file;if(\"object\"==typeof e)if(\"write\"===e.type){if(Number.isInteger(e.position)&&e.position>=0&&(this.position=e.position,this.size<e.position&&(this.file=new U([this.file,new ArrayBuffer(e.position-this.size)],this.file.name,this.file))),!(\"data\"in e))throw new B(...$(\"write requires a data argument\"));e=e.data}else{if(\"seek\"===e.type){if(Number.isInteger(e.position)&&e.position>=0){if(this.size<e.position)throw new B(...C);return void(this.position=e.position)}throw new B(...$(\"seek requires a position argument\"))}if(\"truncate\"===e.type){if(Number.isInteger(e.size)&&e.size>=0)return t=e.size<this.size?new U([t.slice(0,e.size)],t.name,t):new U([t,new Uint8Array(e.size-this.size)],t.name),this.size=t.size,this.position>t.size&&(this.position=t.size),void(this.file=t);throw new B(...$(\"truncate requires a size argument\"))}}e=new q([e]);let i=this.file;const r=i.slice(0,this.position),n=i.slice(this.position+e.size);let s=this.position-r.size;s<0&&(s=0),i=new U([r,new Uint8Array(s),e,n],i.name),this.size=i.size,this.position+=e.size,this.file=i}close(){if(this.fileHandle._deleted)throw new B(...G);this.fileHandle._file=this.file,this.file=this.position=this.size=null,this.fileHandle.onclose&&this.fileHandle.onclose(this.fileHandle)}}class Q{constructor(e=\"\",t=new U([],e),i=!0){this._file=t,this.name=e,this.kind=\"file\",this._deleted=!1,this.writable=i,this.readable=!0}async getFile(){if(this._deleted)throw new B(...G);return this._file}async createWritable(e){if(!this.writable)throw new B(...J);if(this._deleted)throw new B(...G);const t=e.keepExistingData?await this.getFile():new U([],this.name);return new K(this,t)}async isSameEntry(e){return this===e}async _destroy(){this._deleted=!0,this._file=null}}class Z{constructor(e,t=!0){this.name=e,this.kind=\"directory\",this._deleted=!1,this._entries={},this.writable=t,this.readable=!0}async*entries(){if(this._deleted)throw new B(...G);yield*Object.entries(this._entries)}async isSameEntry(e){return this===e}async getDirectoryHandle(e,t){if(this._deleted)throw new B(...G);const i=this._entries[e];if(i){if(i instanceof Q)throw new B(...Y);return i}if(t.create)return this._entries[e]=new Z(e);throw new B(...G)}async getFileHandle(e,t){const i=this._entries[e],r=i instanceof Q;if(i&&r)return i;if(i&&!r)throw new B(...Y);if(!i&&!t.create)throw new B(...G);return!i&&t.create?this._entries[e]=new Q(e):void 0}async removeEntry(e,t){const i=this._entries[e];if(!i)throw new B(...G);await i._destroy(t.recursive),delete this._entries[e]}async _destroy(e){for(let t of Object.values(this._entries)){if(!e)throw new B(...V);await t._destroy(e)}this._entries={},this._deleted=!0}}const ee=new Z(\"\");var te=Object.freeze({__proto__:null,Sink:K,FileHandle:Q,FolderHandle:Z,default:()=>ee});export{g as FileSystemDirectoryHandle,H as FileSystemFileHandle,w as FileSystemHandle,d as FileSystemWritableFileStream,o as getOriginPrivateDirectory,t as showDirectoryPicker,n as showOpenFilePicker,a as showSaveFilePicker};\n"
  },
  {
    "path": "public/apps/nfsadapter/util.js",
    "content": "export const errors = {\n  INVALID: ['seeking position failed.', 'InvalidStateError'],\n  GONE: ['A requested file or directory could not be found at the time an operation was processed.', 'NotFoundError'],\n  MISMATCH: ['The path supplied exists, but was not an entry of requested type.', 'TypeMismatchError'],\n  MOD_ERR: ['The object can not be modified in this way.', 'InvalidModificationError'],\n  SYNTAX: m => [`Failed to execute 'write' on 'UnderlyingSinkBase': Invalid params passed. ${m}`, 'SyntaxError'],\n  SECURITY: ['It was determined that certain files are unsafe for access within a Web application, or that too many calls are being made on file resources.', 'SecurityError'],\n  DISALLOWED: ['The request is not allowed by the user agent or the platform in the current context.', 'NotAllowedError']\n}\n\nexport const config = {\n  writable: globalThis.WritableStream\n}\n\nexport async function fromDataTransfer (entries) {\n  console.warn('deprecated fromDataTransfer - use `dt.items[0].getAsFileSystemHandle()` instead')\n  const [memory, sandbox, fs] = await Promise.all([\n    import('/public/apps/nfsadapter/adapters/memory.js'),\n    import('/public/apps/nfsadapter/adapters/sandbox.js'),\n    import('/public/apps/nfsadapter/FileSystemDirectoryHandle.js')\n  ])\n\n  const folder = new memory.FolderHandle('', false)\n  folder._entries = entries.map(entry => entry.isFile\n    ? new sandbox.FileHandle(entry, false)\n    : new sandbox.FolderHandle(entry, false)\n  )\n\n  return new fs.FileSystemDirectoryHandle(folder)\n}\n\nexport async function getDirHandlesFromInput (input) {\n  const { FolderHandle, FileHandle } = await import('/public/apps/nfsadapter/adapters/memory.js')\n  const { FileSystemDirectoryHandle } = await import('/public/apps/nfsadapter/FileSystemDirectoryHandle.js')\n\n  const files = Array.from(input.files)\n  const rootName = files[0].webkitRelativePath.split('/', 1)[0]\n  const root = new FolderHandle(rootName, false)\n\n  files.forEach(file => {\n    const path = file.webkitRelativePath.split('/')\n    path.shift()\n    const name = path.pop()\n\n    const dir = path.reduce((dir, path) => {\n      if (!dir._entries[path]) dir._entries[path] = new FolderHandle(path, false)\n      return dir._entries[path]\n    }, root)\n\n    dir._entries[name] = new FileHandle(file.name, file, false)\n  })\n\n  return new FileSystemDirectoryHandle(root)\n}\n\nexport async function getFileHandlesFromInput (input) {\n  const { FileHandle } = await import('/public/apps/nfsadapter/adapters/memory.js')\n  const { FileSystemFileHandle } = await import('/public/apps/nfsadapter/FileSystemFileHandle.js')\n\n  return Array.from(input.files).map(file =>\n    new FileSystemFileHandle(new FileHandle(file.name, file, false))\n  )\n}\n"
  },
  {
    "path": "public/apps/settings.tapp/accounts/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" class=\"h-full\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>Accounts</title>\n  <script src=\"/assets/libs/filer.min.js\"></script>\n  <script src=\"/assets/libs/tailwind.min.js\"></script>\n  <style>\n    @font-face {\n      font-family: Inter;\n      src: url(/fonts/Inter.ttf); }\n\n    * { font-family: Inter; }\n\n    ::-webkit-scrollbar { width: 8px; }\n    ::-webkit-scrollbar-thumb {\n      background: #ffffff30;\n      border-radius: 8px;\n      border: 2px solid transparent; }\n    ::-webkit-scrollbar-track { background: transparent; }\n\n    /* animations */\n    @keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }\n    @keyframes scaleUp { from { transform: scale(0.95); opacity: 0; } to { transform: scale(1); opacity: 1; } }\n    @keyframes float { 0%,100%{transform:translateY(0);}50%{transform:translateY(-4px);} }\n\n    /* page stuff */\n    .page-animate { animation: fadeUp 0.4s ease-out forwards; }\n    .accounts-animate { animation: fadeUp 0.35s ease-out 0.1s forwards; opacity:0; }\n\n    .current-accounts > div {\n      animation: scaleUp 0.3s ease-out forwards;\n      transition: transform 0.2s, box-shadow 0.3s; }\n    .current-accounts > div:hover {\n      transform: scale(1.05);\n      box-shadow: 0 0 10px rgba(255,255,255,0.7); }\n    .current-accounts svg[fill=\"#e74949\"] {\n      transition: transform 0.2s, fill 0.2s; }\n    .current-accounts svg[fill=\"#e74949\"]:hover {\n      transform: scale(1.2);\n      fill: #ff6969; }\n\n    /* button tingz */\n    button:hover { transform: scale(1.03); }\n\n    /* float first account automatically */\n    .current-accounts > div:first-child {\n      animation: float 5s ease-in-out infinite; }\n  </style>\n</head>\n\n<body class=\"text-white h-full\">\n  <div class=\"flex flex-col p-2 h-full gap-3 page-animate\">\n    <h2 class=\"text-2xl font-bold\">Account Manager</h2>\n\n    <div class=\"current-accounts flex flex-wrap gap-3 max-h-[200px] overflow-auto p-3 bg-[#ffffff18] rounded-lg accounts-animate\">\n    </div>\n\n    <button class=\"flex gap-1.5 w-max py-2 px-4 rounded-md bg-[#ffffff10] shadow-[0_0_6px_0_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20]\"\n      onmousedown=\"createAccount()\">\n      <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n        <path d=\"M5.25 6.375a4.125 4.125 0 1 1 8.25 0 4.125 4.125 0 0 1-8.25 0ZM2.25 19.125a7.125 7.125 0 0 1 14.25 0v.003l-.001.119a.75.75 0 0 1-.363.63 13.067 13.067 0 0 1-6.761 1.873c-2.472 0-4.786-.684-6.76-1.873a.75.75 0 0 1-.364-.63l-.001-.122ZM18.75 7.5a.75.75 0 0 0-1.5 0v2.25H15a.75.75 0 0 0 0 1.5h2.25v2.25a.75.75 0 0 0 1.5 0v-2.25H21a.75.75 0 0 0 0-1.5h-2.25V7.5Z\"/>\n      </svg>\n      <span class=\"font-semibold\">Create Account</span>\n    </button>\n  </div>\n\n  <script src=\"./index.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "public/apps/settings.tapp/accounts/index.js",
    "content": "const tb = parent.window.tb;\nconst currentAccountsEl = document.querySelector(\".current-accounts\");\n\nconst getAccounts = async () => {\n\tconst entries = await tb.fs.promises.readdir(\"/home/\");\n\tconst accounts = await Promise.all(\n\t\tentries.map(async entry => {\n\t\t\ttry {\n\t\t\t\tconst account = JSON.parse(await tb.fs.promises.readFile(`/home/${entry}/user.json`, \"utf8\"));\n\t\t\t\treturn {\n\t\t\t\t\tname: entry,\n\t\t\t\t\tid: account[\"id\"],\n\t\t\t\t\tusername: account[\"username\"],\n\t\t\t\t\tperm: account[\"perm\"],\n\t\t\t\t\tpfp: account[\"pfp\"],\n\t\t\t\t};\n\t\t\t} catch (e) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}),\n\t);\n\n\treturn accounts.filter(account => account !== null);\n};\n\nconst deleteAccount = async id => {\n\tconst sudoUsers = JSON.parse(await tb.fs.promises.readFile(\"/system/etc/terbium/sudousers.json\", \"utf8\"));\n\tlet sudoWithPassword = null;\n\tfor (const sudoUser of sudoUsers) {\n\t\tconst sudoUserData = JSON.parse(await tb.fs.promises.readFile(`/home/${sudoUser}/user.json`, \"utf8\"));\n\t\tif (sudoUserData.password !== false) {\n\t\t\tsudoWithPassword = sudoUser;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!sudoUsers.includes(sessionStorage.getItem(\"currAcc\"))) {\n\t\tif (!sudoWithPassword) {\n\t\t\ttb.system.users.remove(id);\n\t\t\tdocument.getElementById(id).remove();\n\t\t\treturn;\n\t\t}\n\t\ttb.dialog.Permissions({\n\t\t\ttitle: \"Permission Denied\",\n\t\t\tmessage: \"You do not have permission to delete accounts, would you like to request permission from sudo?\",\n\t\t\tonOk: async () => {\n\t\t\t\ttb.dialog.Auth({\n\t\t\t\t\ttitle: \"Request Permission\",\n\t\t\t\t\tdefaultUsername: sudoUsers[0],\n\t\t\t\t\tonOk: async (username, password) => {\n\t\t\t\t\t\tconst pass = await tb.crypto(password);\n\t\t\t\t\t\tif (pass === JSON.parse(await tb.fs.promises.readFile(`/home/${sudoUsers[0]}/user.json`, \"utf8\")).password) {\n\t\t\t\t\t\t\ttb.system.users.remove(id);\n\t\t\t\t\t\t\tdocument.getElementById(id).remove();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttb.dialog.Alert({\n\t\t\t\t\t\t\t\ttitle: \"Incorrect Password\",\n\t\t\t\t\t\t\t\tmessage: \"The password you entered is incorrect.\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t} else {\n\t\tconst pw = JSON.parse(await tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\")).password;\n\t\tif (pw === false) {\n\t\t\tawait tb.system.users.remove(id);\n\t\t\tdocument.getElementById(id).remove();\n\t\t} else {\n\t\t\tawait tb.dialog.Auth({\n\t\t\t\ttitle: \"Authenticate to Delete Account\",\n\t\t\t\tdefaultUsername: sessionStorage.getItem(\"currAcc\"),\n\t\t\t\tonOk: async (username, password) => {\n\t\t\t\t\tconst pass = await tb.crypto(password);\n\t\t\t\t\tif (pass === pw) {\n\t\t\t\t\t\tawait tb.system.users.remove(id);\n\t\t\t\t\t\tdocument.getElementById(id).remove();\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttb.dialog.Alert({\n\t\t\t\t\t\t\ttitle: \"Incorrect Password\",\n\t\t\t\t\t\t\tmessage: \"The password you entered is incorrect.\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n};\n\nconst changePerm = async () => {\n\tconst data = JSON.parse(await tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"));\n\tif (data[\"password\"] === false) {\n\t\tawait tb.dialog.Select({\n\t\t\ttitle: \"Enter the permission level you wish to set (Ex: Admin, User, Group, Public)\",\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\ttext: \"Admin\",\n\t\t\t\t\tvalue: \"admin\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"User\",\n\t\t\t\t\tvalue: \"user\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Group\",\n\t\t\t\t\tvalue: \"group\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Public\",\n\t\t\t\t\tvalue: \"public\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tonOk: async perm => {\n\t\t\t\tif (perm === data[\"perm\"]) return;\n\t\t\t\tdata[\"perm\"] = perm;\n\t\t\t\tpermEl.innerHTML = perm.charAt(0).toUpperCase() + perm.slice(1);\n\t\t\t\tawait tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, JSON.stringify(data));\n\t\t\t},\n\t\t});\n\t} else {\n\t\tawait tb.dialog.Auth({\n\t\t\tsudo: true,\n\t\t\ttitle: \"Authenticate to change your permissions\",\n\t\t\tdefaultUsername: sessionStorage.getItem(\"currAcc\"),\n\t\t\tonOk: async (username, password) => {\n\t\t\t\tconst pass = await tb.crypto(password);\n\t\t\t\tif (pass === data[\"password\"]) {\n\t\t\t\t\tawait tb.dialog.Select({\n\t\t\t\t\t\ttitle: \"Enter the permission level you wish to set (Ex: Admin, User, Group, Public)\",\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Admin\",\n\t\t\t\t\t\t\t\tvalue: \"admin\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"User\",\n\t\t\t\t\t\t\t\tvalue: \"user\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Group\",\n\t\t\t\t\t\t\t\tvalue: \"group\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Public\",\n\t\t\t\t\t\t\t\tvalue: \"public\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tonOk: async perm => {\n\t\t\t\t\t\t\tif (perm === data[\"perm\"]) return;\n\t\t\t\t\t\t\tdata[\"perm\"] = perm;\n\t\t\t\t\t\t\tpermEl.innerHTML = perm.charAt(0).toUpperCase() + perm.slice(1);\n\t\t\t\t\t\t\tawait tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, JSON.stringify(data));\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(\"Incorrect Password\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n};\n\nconst changePfp = async id => {\n\tconst data = JSON.parse(await tb.fs.promises.readFile(`/home/${id.id}/user.json`, \"utf8\"));\n\tconst pfpInp = document.createElement(\"input\");\n\tpfpInp.type = \"file\";\n\tpfpInp.accept = \"image/*\";\n\tpfpInp.click();\n\tpfpInp.onchange = async e => {\n\t\tif (e.target.files.length !== 0) {\n\t\t\tconst file = e.target.files[0];\n\t\t\tconst reader = new FileReader();\n\t\t\treader.onload = async e => {\n\t\t\t\tawait tb.dialog.Cropper({\n\t\t\t\t\ttitle: \"Crop Profile Picture\",\n\t\t\t\t\timg: e.target.result,\n\t\t\t\t\tonOk: async img => {\n\t\t\t\t\t\tdata[\"pfp\"] = img;\n\t\t\t\t\t\tawait tb.fs.promises.writeFile(`/home/${id.id}/user.json`, JSON.stringify(data));\n\t\t\t\t\t\tparent.window.dispatchEvent(new Event(\"accUpd\"));\n\t\t\t\t\t\trenderAccounts();\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\t\t\treader.readAsDataURL(file);\n\t\t}\n\t};\n};\n\nconst renderAccounts = async () => {\n\tconst accounts = await getAccounts();\n\tcurrentAccountsEl.innerHTML = accounts\n\t\t.map(\n\t\t\taccount => `\n        <div id=\"${account.id}\"\" class=\"relative p-3 rounded-lg w-max min-w-44 bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38]\">\n            <div class=\"flex gap-2 items-center\">\n                <div class=\"group relative size-10 rounded-full\">\n                    <img class=\"object-cover size-10 rounded-full bg-[#ffffff20]\" src=\"${account.pfp}\" />\n                    <div class=\"absolute flex justify-center items-center inset-0 bg-[#ffffff10] rounded-full p-1.5 backdrop-blur cursor-pointer opacity-0 group-hover:opacity-100 duration-150\" onmousedown=\"changePfp(${account.id})\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-3 pointer-events-none\">\n                            <path fill-rule=\"evenodd\" d=\"M20.599 1.5c-.376 0-.743.111-1.055.32l-5.08 3.385a18.747 18.747 0 0 0-3.471 2.987 10.04 10.04 0 0 1 4.815 4.815 18.748 18.748 0 0 0 2.987-3.472l3.386-5.079A1.902 1.902 0 0 0 20.599 1.5Zm-8.3 14.025a18.76 18.76 0 0 0 1.896-1.207 8.026 8.026 0 0 0-4.513-4.513A18.75 18.75 0 0 0 8.475 11.7l-.278.5a5.26 5.26 0 0 1 3.601 3.602l.502-.278ZM6.75 13.5A3.75 3.75 0 0 0 3 17.25a1.5 1.5 0 0 1-1.601 1.497.75.75 0 0 0-.7 1.123 5.25 5.25 0 0 0 9.8-2.62 3.75 3.75 0 0 0-3.75-3.75Z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    </div>\n                </div>\n                <div class=\"flex flex-col\">\n                    <input class=\"font-black text-lg text-white leading-none bg-transparent p-0 rounded-none focus-within:outline-none w-28\" value=\"${account.username}\" />\n                    <span class=\"w-max bg-[#ffffff28] py-1 px-1.5 rounded-md font-bold text-[#ffffff68] text-xs leading-none mt-1.5 select-none\" onClick=\"changePerm()\">${account.perm.charAt(0).toUpperCase() + account.perm.slice(1)}</span>\n                </div>\n            </div>\n            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"#e74949\" class=\"absolute top-1 right-1 size-4 cursor-pointer\" onmousedown=\"deleteAccount('${account.id}')\">\n                <path fill-rule=\"evenodd\" d=\"M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z\" clip-rule=\"evenodd\" />\n            </svg>\n        </div>\n    `,\n\t\t)\n\t\t.join(\"\");\n};\n\nrenderAccounts();\n\nconst createAccount = async () => {\n\tconst askNewAccountDetails = async () => {\n\t\tconst makeAccount = async () => {\n\t\t\tawait tb.dialog.Message({\n\t\t\t\ttitle: \"Create Username\",\n\t\t\t\tonOk: async username => {\n\t\t\t\t\tconst data = {};\n\t\t\t\t\tdata[\"id\"] = username;\n\t\t\t\t\tdata[\"username\"] = username;\n\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\ttitle: \"Create Password\",\n\t\t\t\t\t\tonOk: async password => {\n\t\t\t\t\t\t\tif (password !== \"\") {\n\t\t\t\t\t\t\t\tdata[\"password\"] = await tb.crypto(password);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdata[\"password\"] = false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tawait tb.dialog.Select({\n\t\t\t\t\t\t\t\ttitle: \"Do you want to set up a security question?\",\n\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Yes\",\n\t\t\t\t\t\t\t\t\t\tvalue: \"yes\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"No\",\n\t\t\t\t\t\t\t\t\t\tvalue: \"no\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tonOk: async securityChoice => {\n\t\t\t\t\t\t\t\t\tif (securityChoice === \"yes\") {\n\t\t\t\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\ttitle: \"Set Security Question\",\n\t\t\t\t\t\t\t\t\t\t\tonOk: async question => {\n\t\t\t\t\t\t\t\t\t\t\t\tawait tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Set Security Answer\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: async answer => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata[\"securityQuestion\"] = {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tquestion: question,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tanswer: await tb.crypto(answer),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t\t\taskProfilePicture(data);\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\taskProfilePicture(data);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\t\t};\n\t\tconst ping = await parent.tb.libcurl.fetch(\"https://auth.terbiumon.top/ping\");\n\t\tif (ping.ok) {\n\t\t\tawait tb.dialog.Select({\n\t\t\t\ttitle: \"Select Account Type\",\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttext: \"Local Account\",\n\t\t\t\t\t\tvalue: \"user\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttext: \"Terbium Cloud Account\",\n\t\t\t\t\t\tvalue: \"tacc\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tonOk: async accountType => {\n\t\t\t\t\tif (accountType === \"user\") {\n\t\t\t\t\t\tmakeAccount();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst run = async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst resp = await window.parent.tb.tauth.signIn();\n\t\t\t\t\t\t\t\tconst userDataConv = {\n\t\t\t\t\t\t\t\t\tid: resp.data.user.id,\n\t\t\t\t\t\t\t\t\tusername: resp.data.user.name,\n\t\t\t\t\t\t\t\t\temail: resp.data.user.email,\n\t\t\t\t\t\t\t\t\tpfp: resp.data.user.image,\n\t\t\t\t\t\t\t\t\tpassword: await tb.crypto(resp.data.user.password),\n\t\t\t\t\t\t\t\t\tperm: \"user\",\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tawait tb.system.users.add(userDataConv);\n\t\t\t\t\t\t\t\trenderAccounts();\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\trun();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\trun();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\t\t} else {\n\t\t\tmakeAccount();\n\t\t}\n\t};\n\n\tconst askProfilePicture = async data => {\n\t\tawait tb.dialog.Select({\n\t\t\ttitle: \"Do you want to set a profile picture?\",\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\ttext: \"Yes\",\n\t\t\t\t\tvalue: \"yes\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"No\",\n\t\t\t\t\tvalue: \"no\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tonOk: async perm => {\n\t\t\t\tif (perm === \"yes\") {\n\t\t\t\t\tconst pfpInp = document.createElement(\"input\");\n\t\t\t\t\tpfpInp.type = \"file\";\n\t\t\t\t\tpfpInp.accept = \"image/*\";\n\t\t\t\t\tpfpInp.click();\n\t\t\t\t\tpfpInp.onchange = async e => {\n\t\t\t\t\t\tif (e.target.files.length === 0) {\n\t\t\t\t\t\t\tconst randomColorStr = [\"blue\", \"green\", \"orange\", \"pink\", \"purple\", \"red\", \"yellow\"][Math.floor(Math.random() * 7)];\n\t\t\t\t\t\t\tdata[\"pfp\"] = `/assets/img/default - ${randomColorStr}.png`;\n\t\t\t\t\t\t\tdata[\"perm\"] = \"user\";\n\t\t\t\t\t\t\tawait tb.system.users.add(data);\n\t\t\t\t\t\t\trenderAccounts();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst file = e.target.files[0];\n\t\t\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\t\t\treader.onload = async e => {\n\t\t\t\t\t\t\t\tawait tb.dialog.Cropper({\n\t\t\t\t\t\t\t\t\ttitle: \"Crop Profile Picture\",\n\t\t\t\t\t\t\t\t\timg: e.target.result,\n\t\t\t\t\t\t\t\t\tonOk: async img => {\n\t\t\t\t\t\t\t\t\t\tdata[\"pfp\"] = img;\n\t\t\t\t\t\t\t\t\t\tdata[\"perm\"] = \"user\";\n\t\t\t\t\t\t\t\t\t\tawait tb.system.users.add(data);\n\t\t\t\t\t\t\t\t\t\trenderAccounts();\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\treader.readAsDataURL(file);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\tconst randomColorStr = [\"blue\", \"green\", \"orange\", \"pink\", \"purple\", \"red\", \"yellow\"][Math.floor(Math.random() * 7)];\n\t\t\t\t\tdata[\"pfp\"] = `/assets/img/default - ${randomColorStr}.png`;\n\t\t\t\t\tdata[\"perm\"] = \"user\";\n\t\t\t\t\tawait tb.system.users.add(data);\n\t\t\t\t\trenderAccounts();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n\n\tconst sudoUsers = JSON.parse(await tb.fs.promises.readFile(\"/system/etc/terbium/sudousers.json\", \"utf8\"));\n\tlet sudoWithPassword = null;\n\tfor (const sudoUser of sudoUsers) {\n\t\tconst sudoUserData = JSON.parse(await tb.fs.promises.readFile(`/home/${sudoUser}/user.json`, \"utf8\"));\n\t\tif (sudoUserData.password !== false) {\n\t\t\tsudoWithPassword = sudoUser;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!sudoUsers.includes(sessionStorage.getItem(\"currAcc\"))) {\n\t\tif (!sudoWithPassword) {\n\t\t\taskNewAccountDetails();\n\t\t\treturn;\n\t\t}\n\t\ttb.dialog.Permissions({\n\t\t\ttitle: \"Permission Denied\",\n\t\t\tmessage: \"You do not have permission to create accounts, would you like to request permission from sudo?\",\n\t\t\tonOk: async () => {\n\t\t\t\ttb.dialog.Auth({\n\t\t\t\t\ttitle: \"Request Permission\",\n\t\t\t\t\tdefaultUsername: sudoWithPassword,\n\t\t\t\t\tonOk: async (username, password) => {\n\t\t\t\t\t\tconst pass = await tb.crypto(password);\n\t\t\t\t\t\tif (pass === JSON.parse(await tb.fs.promises.readFile(`/home/${sudoWithPassword}/user.json`, \"utf8\")).password) {\n\t\t\t\t\t\t\taskNewAccountDetails();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttb.dialog.Alert({\n\t\t\t\t\t\t\t\ttitle: \"Incorrect Password\",\n\t\t\t\t\t\t\t\tmessage: \"The password you entered is incorrect.\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t} else {\n\t\tconst user = JSON.parse(await tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"));\n\t\tif (user[\"password\"] === false) {\n\t\t\taskNewAccountDetails();\n\t\t} else {\n\t\t\tawait tb.dialog.Auth({\n\t\t\t\ttitle: \"Authenticate to Create Account\",\n\t\t\t\tdefaultUsername: sessionStorage.getItem(\"currAcc\"),\n\t\t\t\tonOk: async (username, password) => {\n\t\t\t\t\tconst pass = await tb.crypto(password);\n\t\t\t\t\tif (pass === JSON.parse(await tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\")).password) {\n\t\t\t\t\t\taskNewAccountDetails();\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttb.dialog.Alert({\n\t\t\t\t\t\t\ttitle: \"Incorrect Password\",\n\t\t\t\t\t\t\tmessage: \"The password you entered is incorrect.\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\trenderAccounts();\n};\n"
  },
  {
    "path": "public/apps/settings.tapp/index.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(/fonts/Inter.ttf);\n}\n\n* {\n\tfont-family: Inter;\n}\n\nhtml,\nbody {\n\theight: 100%;\n\tmargin: 0;\n\tfont-family: Inter;\n\tposition: relative;\n\toverflow: hidden;\n}\n\nbody {\n\tdisplay: flex;\n\tcolor: #ffffff;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n\tmargin: 0;\n}\n\n/* big screen */\n@media screen and (max-width: 1920px) {\n\t.wallpaper-option {\n\t\twidth: 200px;\n\t\theight: 112px;\n\t}\n}\n\n/* small screen */\n@media screen and (max-width: 646px) {\n\t.cat-option {\n\t\tpadding-right: 0px;\n\t}\n\n\t.cat-option .text {\n\t\tposition: absolute;\n\t\topacity: 0;\n\t}\n\n\t.cat-option .icon:hover {\n\t\tbackground-color: #ffffff28;\n\t}\n\n\t.cat-option .icon svg {\n\t\twidth: 24px;\n\t\theight: auto;\n\t}\n\n\t.category-search {\n\t\twidth: 100px;\n\t}\n\n\t.wallpapers {\n\t\twidth: 400px;\n\t}\n\n\t.wallpaper-option {\n\t\twidth: 130px;\n\t\theight: 73px;\n\t}\n}\n\n.category-search {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 8px 10px;\n\tbackground-color: #ffffff20;\n\tfont-weight: 700;\n\tfont-family: Inter;\n\tfont-size: 16px;\n\tline-height: 16px;\n\tcolor: #dedede;\n\tborder: none;\n\tborder-radius: 6px;\n\ttransition: 150ms ease-in-out;\n}\n\n.category-search:focus {\n\toutline: none;\n}\n\n.cat-option .text {\n\tfont-size: 16px;\n\tfont-weight: 800;\n\tline-height: 16px;\n\ttransition-property: color;\n\ttransition-duration: 150ms;\n\ttransition-timing-function: ease-in-out;\n\tpointer-events: none;\n}\n\n.cat-option:hover {\n\tcolor: #ffffff98;\n}\n\n.cat-option {\n\tcolor: #ffffff52;\n}\n\n.cat-option:hover {\n\tcolor: #ffffff;\n}\n\n.cat-option {\n\tposition: relative;\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\theight: min-content;\n\tcursor: var(--cursor-pointer);\n}\n\n.cat-option.selected {\n\tcolor: #ffffff !important;\n}\n\n.cat-option .icon {\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\tpadding: 6px;\n\tborder-radius: 8px;\n\ttransition-property: color, background-color;\n\ttransition-duration: 150ms;\n\ttransition-timing-function: ease-in-out;\n\tpointer-events: none;\n}\n\n.cat-option .icon svg {\n\tpointer-events: none;\n}\n\n.cat-tooltip {\n\tbackground-color: #ffffff10;\n\tborder: 1px solid #ffffff28;\n\tborder-radius: 6px;\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tfont-family: Inter;\n\tposition: absolute;\n\tleft: calc(36px + 8px);\n\tbackdrop-filter: blur(100px) contrast(0.8) brightness(0.8);\n\tz-index: 99;\n\tpointer-events: none;\n\tpadding: 4px 8px;\n\topacity: 1;\n\ttransition: 150ms ease-in-out;\n}\n\n.cat-tooltip.hidden {\n\topacity: 0;\n\tleft: calc(36px);\n}\n\n.sidebar {\n\tposition: relative;\n\tdisplay: flex;\n\t/* background-color: #ffffff24; */\n\tborder-radius: 8px;\n}\n\n.settings-category::-webkit-scrollbar {\n\twidth: 8px;\n\theight: 8px;\n}\n\n.settings-category::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff28;\n\tborder-radius: 8px;\n}\n\n.settings-category::-webkit-scrollbar-track {\n\tbackground-color: #ffffff10;\n\tborder-radius: 8px;\n}\n\n.settings-category {\n\tscrollbar-color: #ffffff28 #ffffff10;\n\tscrollbar-width: thin;\n}\n\n.option-container {\n\tgap: 6px;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.wallpapers {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 6px;\n}\n\n.wallpaper-container {\n\tposition: relative;\n}\n\n.wallpaper-option {\n\tborder-radius: 6px;\n\tcursor: var(--cursor-pointer);\n\topacity: 0.7;\n\ttransition: 150ms ease-in-out;\n}\n\n.wallpaper-option:hover {\n\topacity: 1;\n}\n\n.delete-wallpaper {\n\tposition: absolute;\n\ttop: 6px;\n\tright: 6px;\n\twidth: 20px;\n\theight: 20px;\n\tpadding: 4px;\n\tbackground-color: #ffffff;\n\tborder-radius: 50%;\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\ttransition: 150ms ease-in-out;\n\tcursor: var(--cursor-pointer);\n}\n\n.buttons-flex {\n\tdisplay: flex;\n}\n\n#output div span:nth-child(2) {\n\tword-spacing: 0.25em;\n\tletter-spacing: 0.25em;\n\twhite-space: pre;\n}\n\n#wispSrvs .wisp-card:nth-child(odd) {\n\tbackground-color: #ffffff28;\n}\n\n.wisp-card {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 6px;\n\twidth: calc(100% - 16px);\n\tpadding: 6px;\n\tpadding-left: 10px;\n\tborder-radius: 6px;\n}\n\n.wisp-card .net {\n\tdisplay: flex;\n\tgap: 6px;\n\talign-items: center;\n}\n\n.wisp-card .net-info {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.wisp-card .net-info .info-text {\n\tfont-size: 14px;\n\tfont-weight: 650;\n\tfont-family: Inter;\n}\n\n.wisp-card .net-info .info-text:nth-child(odd) {\n\tuser-select: none;\n}\n\n.wisp-card .net-info .info-text:nth-child(even) {\n\tcolor: #ffffff93;\n\tfont-weight: normal;\n}\n\n.wisp-card .latency {\n\tfont-size: 14px;\n\tfont-weight: 800;\n\tfont-family: Inter;\n\tcolor: #ffffff93;\n\tuser-select: none;\n}\n\n.wisp-card .connection-info {\n\tdisplay: flex;\n\tgap: 6px;\n\talign-items: center;\n}\n\n.blur-slider input[type=\"range\"] {\n\t-webkit-appearance: none;\n\tappearance: none;\n\twidth: 100%;\n\theight: 10px;\n\tbackground: linear-gradient(90deg, #60a5fa 18%, rgba(255, 255, 255, 0.12) 18%);\n\tborder-radius: 999px;\n\toutline: none;\n}\n.blur-slider input[type=\"range\"]::-webkit-slider-runnable-track {\n\theight: 10px;\n\tborder-radius: 999px;\n\tbackground: transparent;\n}\n.blur-slider input[type=\"range\"]::-moz-range-track {\n\theight: 10px;\n\tborder-radius: 999px;\n\tbackground: transparent;\n}\n.blur-slider input[type=\"range\"]::-webkit-slider-thumb {\n\t-webkit-appearance: none;\n\tappearance: none;\n\tmargin-top: -5px;\n\twidth: 20px;\n\theight: 20px;\n\tborder-radius: 50%;\n\tbackground: #ffffff;\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n\tborder: 3px solid rgba(255, 255, 255, 0.12);\n\tcursor: pointer;\n}\n.blur-slider input[type=\"range\"]::-moz-range-thumb {\n\twidth: 20px;\n\theight: 20px;\n\tborder-radius: 50%;\n\tbackground: #ffffff;\n\tborder: 3px solid rgba(255, 255, 255, 0.12);\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n\tcursor: pointer;\n}\n.blur-hint {\n\tfont-size: 0.78rem;\n\tcolor: rgba(255, 255, 255, 0.55);\n}\n"
  },
  {
    "path": "public/apps/settings.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" theme=\"dark\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n        <title>Settings</title>\n        <link rel=\"stylesheet\" href=\"index.css\">\n        <link rel=\"stylesheet\" href=\"./select.css\">\n        <link rel=\"stylesheet\" href=\"./radio.css\">\n        <script src=\"/assets/libs/filer.min.js\"></script>\n        <script src=\"/assets/libs/tailwind.min.js\"></script>\n    </head>\n    <body>\n        <div class=\"flex flex-col sm:flex-row size-full p-3 gap-3\">\n            <div class=\"sidebar flex sm:w-50 sm:h-min sm:flex-col sm:items-start sm:gap-1.5 sm:p-2 sm:px-3.5 w-min h-max flex-row items-center gap-1 p-2 bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,inset_0_0_0_0.5px_#ffffff38]\">\n                <!-- <input type=\"text\" class=\"category-search\" placeholder=\"Search\" /> -->\n                <div class=\"cat-option selected sm:pr-3.5\" data-category=\"appearance\">\n                    <div class=\"icon\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"w-8 h-auto\">\n                            <path fill-rule=\"evenodd\" d=\"M1.5 7.125c0-1.036.84-1.875 1.875-1.875h6c1.036 0 1.875.84 1.875 1.875v3.75c0 1.036-.84 1.875-1.875 1.875h-6A1.875 1.875 0 011.5 10.875v-3.75zm12 1.5c0-1.036.84-1.875 1.875-1.875h5.25c1.035 0 1.875.84 1.875 1.875v8.25c0 1.035-.84 1.875-1.875 1.875h-5.25a1.875 1.875 0 01-1.875-1.875v-8.25zM3 16.125c0-1.036.84-1.875 1.875-1.875h5.25c1.036 0 1.875.84 1.875 1.875v2.25c0 1.035-.84 1.875-1.875 1.875h-5.25A1.875 1.875 0 013 18.375v-2.25z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    </div>\n                    <div class=\"text sm:opacity-100\">Appearance</div>\n                    <div class=\"cat-tooltip hidden\">Appearance</div>\n                </div>\n                <div class=\"cat-option\" data-category=\"window\">\n                    <div class=\"icon\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"w-8 h-auto\">\n                            <path fill-rule=\"evenodd\" d=\"M2.25 6a3 3 0 013-3h13.5a3 3 0 013 3v12a3 3 0 01-3 3H5.25a3 3 0 01-3-3V6zm18 3H3.75v9a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5V9zm-15-3.75A.75.75 0 004.5 6v.008c0 .414.336.75.75.75h.008a.75.75 0 00.75-.75V6a.75.75 0 00-.75-.75H5.25zm1.5.75a.75.75 0 01.75-.75h.008a.75.75 0 01.75.75v.008a.75.75 0 01-.75.75H7.5a.75.75 0 01-.75-.75V6zm3-.75A.75.75 0 009 6v.008c0 .414.336.75.75.75h.008a.75.75 0 00.75-.75V6a.75.75 0 00-.75-.75H9.75z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    </div>\n                    <div class=\"text\">Window</div>\n                    <div class=\"cat-tooltip hidden\">Window</div>\n                </div>\n                <div class=\"cat-option\" data-category=\"networking\">\n                    <div class=\"icon\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"w-8 h-auto\">\n                            <path fill-rule=\"evenodd\" d=\"M1.371 8.143c5.858-5.857 15.356-5.857 21.213 0a.75.75 0 010 1.061l-.53.53a.75.75 0 01-1.06 0c-4.98-4.979-13.053-4.979-18.032 0a.75.75 0 01-1.06 0l-.53-.53a.75.75 0 010-1.06zm3.182 3.182c4.1-4.1 10.749-4.1 14.85 0a.75.75 0 010 1.061l-.53.53a.75.75 0 01-1.062 0 8.25 8.25 0 00-11.667 0 .75.75 0 01-1.06 0l-.53-.53a.75.75 0 010-1.06zm3.204 3.182a6 6 0 018.486 0 .75.75 0 010 1.061l-.53.53a.75.75 0 01-1.061 0 3.75 3.75 0 00-5.304 0 .75.75 0 01-1.06 0l-.53-.53a.75.75 0 010-1.06zm3.182 3.182a1.5 1.5 0 012.122 0 .75.75 0 010 1.061l-.53.53a.75.75 0 01-1.061 0l-.53-.53a.75.75 0 010-1.06z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    </div>\n                    <div class=\"text\">Networking</div>\n                    <div class=\"cat-tooltip hidden\">Networking</div>\n                </div>\n                <div class=\"cat-option\" data-category=\"privacy\">\n                    <div class=\"icon\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"w-8 h-auto\">\n                            <path fill-rule=\"evenodd\" d=\"M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    </div>\n                    <div class=\"text\">Privacy</div>\n                    <div class=\"cat-tooltip hidden\">Privacy</div>\n                </div>\n                <div class=\"cat-option\" data-category=\"other\">\n                    <div class=\"icon\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" style=\"stroke-width: 5px;\" class=\"w-8 h-auto\">\n                            <path fill-rule=\"evenodd\" d=\"M12 5.25a.75.75 0 01.75.75v5.25H18a.75.75 0 010 1.5h-5.25V18a.75.75 0 01-1.5 0v-5.25H6a.75.75 0 010-1.5h5.25V6a.75.75 0 01.75-.75z\" clip-rule=\"evenodd\" />\n                        </svg>\n                    </div>\n                    <div class=\"text\">Other</div>\n                    <div class=\"cat-tooltip hidden\">Other</div>\n                </div>\n            </div>\n            <div class=\"relative size-full\">\n                <div class=\"settings-category absolute flex flex-col gap-3 overflow-y-auto overflow-x-hidden rounded-md size-full duration-75 ease-in\" category=\"appearance\" data-visible=\"true\">\n                    <div class=\"flex flex-col gap-1.5\">\n                        <h2 class=\"text-2xl font-bold\">Wallpaper</h2>\n                        <div class=\"wallpapers sm:max-w-153\">\n                            <img src=\"/assets/wallpapers/1.png\" class=\"wallpaper-option\"></img>\n                            <img src=\"/assets/wallpapers/2.png\" class=\"wallpaper-option\"></img>\n                            <img src=\"/assets/wallpapers/3.png\" class=\"wallpaper-option\"></img>\n                            <img src=\"/assets/wallpapers/4.png\" class=\"wallpaper-option\"></img>\n                            <img src=\"/assets/wallpapers/5.png\" class=\"wallpaper-option\"></img>\n                            <img src=\"/assets/wallpapers/6.png\" class=\"wallpaper-option\"></img>\n                            <img src=\"/assets/wallpapers/7.png\" class=\"wallpaper-option\"></img>\n                        </div>\n                        <h3>Wallpaper Fill Mode</h3>\n                        <div class=\"select\" action=\"fs\" action-for=\"wallpaper-fill\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">Cover</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" value=\"Cover\">Cover</div>\n                                <div class=\"option\" value=\"Contain\">Contain</div>\n                                <div class=\"option\" value=\"Stretch\">Stretch</div>\n                            </div>\n                        </div>\n                    </div>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <h3>Accent</h3>\n                        <div class=\"flex gap-2 items-center\">\n                            <div class=\"accent-preview flex justify-center items-center size-6 rounded-md inset-shadow-[0_0_0_0.5px_#ffffff38] bg-(--accent)\">\n                                <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"group-hover:opacity-100 group-hover:scale-100 size-4 opacity-0 scale-75 duration-150\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n                                </svg>\n                            </div>\n                            <button class=\"custom-accent w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\">Custom Color</button>\n                        </div>\n                    </div>\n                    <div class=\"battery\">\n                        <h2 class=\"text-2xl font-bold\">Battery</h2>\n                        <div class=\"battery-percentage flex items-center gap-1.5 group\">\n                            <h2 class=\"text-2xl font-bold pointer-events-none se'\">Show Percentage</h2>\n                            <div class=\"relative flex size-max rounded-md bg-[#ffffff18] p-1.5 text-white duration-150 group-hover:bg-[#ffffff28] pointer-events-none\">\n                                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"checkIcon size-2.5 stroke-current stroke-[4px] select-none duration-150\">\n                                    <path fill-rule=\"evenodd\" d=\"M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z\" clip-rule=\"evenodd\" />\n                                </svg>\n                                <input type=\"checkbox\" class=\"hidden pointer-events-none\" />\n                            </div>\n                        </div>\n                    </div>\n                    <h2 class=\"text-2xl font-bold\">Time</h2>\n                    <h3>24 hour clock</h3>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <div class=\"select\" action=\"fs\" action-for=\"24h-12h\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">No</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" value=\"No\">No</div>\n                                <div class=\"option\" value=\"Yes\">Yes</div>\n                            </div>\n                        </div>\n                    </div>\n                    <h3>Show seconds</h3>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <div class=\"select\" action=\"fs\" action-for=\"show-seconds\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">No</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" value=\"No\">No</div>\n                                <div class=\"option\" value=\"Yes\">Yes</div>\n                            </div>\n                        </div>\n                    </div>\n                    <h2 class=\"text-2xl font-bold\">Weather</h2>\n                    <h3>Temperature Unit</h3>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <div class=\"select\" action=\"fs\" action-for=\"temperature-unit\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">Celsius</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" value=\"Fahrenheit\">Fahrenheit</div>\n                                <div class=\"option\" value=\"Celsius\">Celsius</div>\n                                <div class=\"option\" value=\"Kelvin\">Kelvin</div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div class=\"settings-category absolute flex flex-col gap-3 overflow-y-auto overflow-x-hidden rounded-md size-full opacity-0 pointer-events-none scale-[1.01] duration-75 ease-in\" category=\"window\" data-visible=\"false\">\n                    <div class=\"window-optimizations-check flex items-center gap-2 group cursor-pointer\">\n                        <div class=\"flex flex-col\">\n                            <h3 class=\"text-xl font-bold pointer-events-none select-none\">Enable Window Optimizations</h3>\n                            <p class=\"text-sm text-[#ffffffaa] pointer-events-none select-none\">Improves rendering performance for smoother window interactions (drag, resize). Uses GPU acceleration and frame throttling.</p>\n                        </div>\n                        <div class=\"relative flex size-max rounded-md bg-[#ffffff18] p-1.5 text-white duration-150 group-hover:bg-[#ffffff28] pointer-events-none\">\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"checkIcon size-2.5 stroke-current stroke-[4px] select-none duration-150\">\n                                <path fill-rule=\"evenodd\" d=\"M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z\" clip-rule=\"evenodd\" />\n                            </svg>\n                            <input type=\"checkbox\" class=\"hidden pointer-events-none\" />\n                        </div>\n                    </div>\n                    <div class=\"fps-counter-check flex items-center gap-2 group cursor-pointer\">\n                        <div class=\"flex flex-col\">\n                            <h3 class=\"text-xl font-bold pointer-events-none select-none\">Show FPS Counter</h3>\n                            <p class=\"text-sm text-[#ffffffaa] pointer-events-none select-none\">Display real-time frames per second in the information island.</p>\n                        </div>\n                        <div class=\"relative flex size-max rounded-md bg-[#ffffff18] p-1.5 text-white duration-150 group-hover:bg-[#ffffff28] pointer-events-none\">\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"checkIcon size-2.5 stroke-current stroke-[4px] select-none duration-150 opacity-0 scale-85\">\n                                <path fill-rule=\"evenodd\" d=\"M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z\" clip-rule=\"evenodd\" />\n                            </svg>\n                            <input type=\"checkbox\" class=\"hidden pointer-events-none\" />\n                        </div>\n                    </div>\n                    <div class=\"p-3 rounded-lg bg-[#4acd6018] border border-[#4acd6038]\">\n                        <p class=\"text-sm text-[#ffffffcc]\">💡 <strong>Tip:</strong> If you notice Terbium lagging with this feature enabled, try disabling it to improve performance on older or potato devices.</p>\n                    </div>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <h3>Window Accent Color</h3>\n                        <div class=\"flex gap-2 items-center\">\n                            <div class=\"winaccent-preview flex justify-center items-center size-6 rounded-md inset-shadow-[0_0_0_0.5px_#ffffff38] bg-(--accent)\">\n                                <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"group-hover:opacity-100 group-hover:scale-100 size-4 opacity-0 scale-75 duration-150\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n                                </svg>\n                            </div>\n                            <button class=\"custom-waccent w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\">Custom Color</button>\n                        </div>\n                    </div>\n                    <h3>Force Windows to be Maximized</h3>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <div class=\"select\" action=\"fs\" action-for=\"wmx\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">No</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" value=\"No\">No</div>\n                                <div class=\"option\" value=\"Yes\">Yes</div>\n                            </div>\n                        </div>\n                    </div>\n                    <h3>Make Maximized windows in \"Full Screen\" mode</h3>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <div class=\"select\" action=\"fs\" action-for=\"wfs\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">No</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" value=\"No\">No</div>\n                                <div class=\"option\" value=\"Yes\">Yes</div>\n                            </div>\n                        </div>\n                    </div>\n                    <h3>Window Blur Intensity</h3>\n                    <div class=\"blur-slider flex flex-col gap-2 w-full max-w-md\">\n                        <div class=\"flex items-center justify-between\">\n                            <span class=\"text-sm text-[#ffffffb3]\">Window Blur Intensity</span>\n                            <span id=\"blurPercent\" class=\"text-sm font-semibold\">18%</span>\n                        </div>\n                        <input id=\"blurRange\" type=\"range\" min=\"0\" max=\"50\" step=\"1\" value=\"9\" aria-label=\"Window blur intensity\" />\n                    </div>\n                </div>\n                <div class=\"settings-category absolute flex flex-col gap-3 overflow-y-auto overflow-x-hidden rounded-md size-full opacity-0 pointer-events-none scale-[1.01] duration-75 ease-in\" category=\"networking\" data-visible=\"false\">\n                    <div class=\"flex flex-col gap-1.5\">\n                        <h2 class=\"text-2xl font-bold\">Proxy</h2>\n                        <div class=\"select\" action=\"fs\" action-for=\"proxy\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">Ultraviolet</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" onmousedown=\"window.parent.tb.proxy.set('Ultraviolet')\" value=\"Ultraviolet\">Ultraviolet</div>\n                                <div class=\"option\" onmousedown=\"window.parent.tb.proxy.set('Scramjet')\" value=\"Scramjet\">Scramjet</div>\n                            </div>\n                        </div>\n                        <h2 class=\"text-2xl font-bold\">Wisp Server</h2>\n                        <div id=\"wispSrvs\" class=\"flex flex-col gap-2 overflow-x-hidden overflow-y-auto\"></div>\n                        <button class=\"w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\" id=\"addWisp\">Add Wisp Server</button>\n                        <button class=\"w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\" id=\"rmWisp\">Remove Wisp Server</button>\n                        <h2 class=\"text-2xl font-bold\">Transport Type</h2>\n                        <div class=\"select\" action=\"fs\" action-for=\"transports\">\n                            <div class=\"select-title\">\n                                <div class=\"text\">Default (Epoxy)</div>\n                                <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n                                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\" />\n                                </svg>\n                            </div>\n                            <div class=\"options\">\n                                <div class=\"option\" onmousedown=\"updateTransport('Default (Epoxy)')\" value=\"Default (Epoxy)\">Default (Epoxy)</div>\n                                <div class=\"option\" onmousedown=\"updateTransport('Libcurl')\" value=\"Libcurl\">Libcurl</div>\n                                <div class=\"option\" onmousedown=\"updateTransport('Anura BCC')\" value=\"bcc\">Anura BCC</div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div class=\"settings-category absolute flex flex-col gap-3 overflow-y-auto overflow-x-hidden rounded-md size-full opacity-0 pointer-events-none scale-[1.01] duration-75 ease-in\" category=\"privacy\" data-visible=\"false\">\n                    <h2 class=\"text-2xl font-bold\">Account</h2>\n                    <div class=\"p-4 rounded-lg w-max min-w-64 bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38]\">\n                        <div class=\"flex gap-2 items-center\">\n                            <div class=\"relative group size-20 rounded-full overflow-hidden\">\n                                <img class=\"pfp object-cover size-full rounded-full bg-[#ffffff20]\">\n                                <div class=\"absolute inset-1 flex justify-center items-center rounded-full opacity-0 bg-[#00000058] pointer-events-none group-hover:inset-0 group-hover:opacity-100 backdrop-blur-lg duration-150\">\n                                    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                                        <path fill-rule=\"evenodd\" d=\"M20.599 1.5c-.376 0-.743.111-1.055.32l-5.08 3.385a18.747 18.747 0 0 0-3.471 2.987 10.04 10.04 0 0 1 4.815 4.815 18.748 18.748 0 0 0 2.987-3.472l3.386-5.079A1.902 1.902 0 0 0 20.599 1.5Zm-8.3 14.025a18.76 18.76 0 0 0 1.896-1.207 8.026 8.026 0 0 0-4.513-4.513A18.75 18.75 0 0 0 8.475 11.7l-.278.5a5.26 5.26 0 0 1 3.601 3.602l.502-.278ZM6.75 13.5A3.75 3.75 0 0 0 3 17.25a1.5 1.5 0 0 1-1.601 1.497.75.75 0 0 0-.7 1.123 5.25 5.25 0 0 0 9.8-2.62 3.75 3.75 0 0 0-3.75-3.75Z\" clip-rule=\"evenodd\" />\n                                    </svg>\n                                </div>\n                            </div>\n                            <div class=\"flex flex-col\">\n                                <input class=\"username font-black text-2xl text-white leading-none bg-transparent p-0 rounded-none focus-within:outline-none w-28\" />\n                                <div class=\"flex items-center\">\n                                    <span class=\"font-bold text-xl text-[#ffffff68] leading-none\">@</span>\n                                    <input class=\"hostname font-bold text-xl text-[#ffffff68] leading-none bg-transparent p-0 rounded-none focus-within:outline-none w-28\" />\n                                </div>\n                                <div class=\"flex flex-row gap-1.5\">\n                                    <span class=\"perm w-max bg-[#ffffff28] py-1 px-1.5 rounded-md font-bold text-[#ffffff68] text-sm leading-none mt-1.5 select-none\"></span>\n                                    <span class=\"actype w-max bg-[#ffffff28] py-1 px-1.5 rounded-md font-bold text-[#ffffff68] text-sm leading-none mt-1.5 select-none\"></span>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                    <button class=\"accounts flex gap-1.5 w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\">\n                        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                            <path fill-rule=\"evenodd\" d=\"M8.25 6.75a3.75 3.75 0 1 1 7.5 0 3.75 3.75 0 0 1-7.5 0ZM15.75 9.75a3 3 0 1 1 6 0 3 3 0 0 1-6 0ZM2.25 9.75a3 3 0 1 1 6 0 3 3 0 0 1-6 0ZM6.31 15.117A6.745 6.745 0 0 1 12 12a6.745 6.745 0 0 1 6.709 7.498.75.75 0 0 1-.372.568A12.696 12.696 0 0 1 12 21.75c-2.305 0-4.47-.612-6.337-1.684a.75.75 0 0 1-.372-.568 6.787 6.787 0 0 1 1.019-4.38Z\" clip-rule=\"evenodd\" />\n                            <path d=\"M5.082 14.254a8.287 8.287 0 0 0-1.308 5.135 9.687 9.687 0 0 1-1.764-.44l-.115-.04a.563.563 0 0 1-.373-.487l-.01-.121a3.75 3.75 0 0 1 3.57-4.047ZM20.226 19.389a8.287 8.287 0 0 0-1.308-5.135 3.75 3.75 0 0 1 3.57 4.047l-.01.121a.563.563 0 0 1-.373.486l-.115.04c-.567.2-1.156.349-1.764.441Z\" />\n                        </svg>\n                        <span>Accounts</span>\n                    </button>\n                    <div class=\"flex flex-col gap-1.5\">\n                        <span class=\"flex gap-2\">\n                            <span>Cords:</span>\n                            <div class=\"relative w-max\">\n                                <span class=\"cords pointer-events-none opacity-0 duration-150 select-none\"></span>\n                                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"showCords absolute top-1/2 -translate-y-1/2 size-6 duration-150\">\n                                    <path d=\"M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z\" />\n                                    <path fill-rule=\"evenodd\" d=\"M1.323 11.447C2.811 6.976 7.028 3.75 12.001 3.75c4.97 0 9.185 3.223 10.675 7.69.12.362.12.752 0 1.113-1.487 4.471-5.705 7.697-10.677 7.697-4.97 0-9.186-3.223-10.675-7.69a1.762 1.762 0 0 1 0-1.113ZM17.25 12a5.25 5.25 0 1 1-10.5 0 5.25 5.25 0 0 1 10.5 0Z\" clip-rule=\"evenodd\" />\n                                </svg>\n                            </div>\n                        </span>\n                        <button class=\"save-city w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\">Update Location</button>\n                    </div>\n                </div>\n                <div class=\"settings-category absolute flex flex-col gap-3 overflow-y-auto overflow-x-hidden rounded-md size-full opacity-0 pointer-events-none scale-[1.01] duration-75 ease-in\" category=\"other\" data-visible=\"false\">\n                    <h2 class=\"text-2xl font-bold\">Import/Export Settings</h2>\n                    <div class=\"flex gap-1.5\">\n                        <button class=\"w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\" onmousedown=\"const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async () => { let file = input.files[0]; let reader = new FileReader(); reader.onload = async () => { let settings = JSON.parse(reader.result); await window.parent.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings), 'utf8'); parent.window.location.reload() }; reader.readAsText(file); }; input.click();\">Import</button>\n                        <button class=\"w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\" onmousedown=\"exportSettings()\">Export</button>\n                    </div>\n                    <h2 class=\"text-2xl font-bold\">Import TBS File</h2>\n                    <div class=\"flex gap-1.5\">\n                        <button class=\"w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\" onmousedown=\"convertTBSIF()\">Import</button>\n                    </div>\n                    <!-- <div class=\"animations-check flex items-center gap-2 group\">\n                        <h2 class=\"text-2xl font-bold pointer-events-none select-none\">Animations</h2>\n                        <div class=\"relative flex size-max rounded-md bg-[#ffffff18] p-1.5 text-white duration-150 group-hover:bg-[#ffffff28] pointer-events-none\">\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"checkIcon size-2.5 stroke-current stroke-[4px] select-none duration-150\">\n                                <path fill-rule=\"evenodd\" d=\"M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z\" clip-rule=\"evenodd\" />\n                            </svg>\n                            <input type=\"checkbox\" class=\"hidden pointer-events-none\" />\n                        </div>\n                    </div> -->\n                    <div class=\"eruda-check flex items-center gap-2 group\">\n                        <h2 class=\"text-2xl font-bold pointer-events-none select-none\">Enable Eruda</h2>\n                        <div class=\"relative flex size-max rounded-md bg-[#ffffff18] p-1.5 text-white duration-150 group-hover:bg-[#ffffff28] pointer-events-none\">\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"checkIcon size-2.5 stroke-current stroke-[4px] select-none duration-150 opacity-0 scale-85\">\n                                <path fill-rule=\"evenodd\" d=\"M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.74a.75.75 0 0 1 1.04-.207Z\" clip-rule=\"evenodd\" />\n                            </svg>\n                            <input type=\"checkbox\" class=\"hidden pointer-events-none\" />\n                        </div>\n                    </div>\n                    <h2 class=\"text-2xl font-bold\">Invalidate Cache</h2>\n                    <div class=\"flex gap-1.5\">\n                        <button class=\"w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\" onmousedown=\"(async () => {await window.tb.fs.promises.writeFile('/system/etc/terbium/hash.cache', 'invalid'); window.parent.location.reload();})()\">Invalidate</button>\n                    </div>\n                    <h2 class=\"text-2xl font-bold\">Export FileSystem (tab may freeze momentarily)</h2>\n                    <div class=\"flex gap-1.5\">\n                        <button class=\"w-max py-2 px-4 rounded-md font-semibold bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff20] duration-150\" onmousedown=\"window.parent.tb.system.exportfs()\">Export</button>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <script src=\"./select.js\"></script>\n        <script src=\"./message.js\"></script>\n        <script src=\"index.js\"></script>\n        <script src=\"island.js\"></script>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/settings.tapp/index.js",
    "content": "const Filer = window.Filer;\nconst tb = parent.window.tb;\nconst tb_window = tb.window;\nconst tb_desktop = tb.desktop;\nconst tb_preferences = tb.desktop.preferences;\nconst tb_island = tb.window.island;\nconst tb_context_menu = tb.context_menu;\nconst tb_dialog = tb.dialog;\nconst tb_wallpaper = parent.window.tb.desktop.wallpaper;\nconst parent_body = parent.document.body;\nsetInterval(() => {\n\tif (parent_body.getAttribute(\"theme\")) document.body.setAttribute(\"theme\", parent_body.getAttribute(\"theme\"));\n});\n\nconst cat_options = document.querySelectorAll(\".cat-option\");\ncat_options.forEach(option => {\n\tfunction mouseleave() {\n\t\tlet tooltip = option.querySelector(\".cat-tooltip\");\n\t\ttooltip.classList.add(\"hidden\");\n\t\toption.removeEventListener(\"mouseleave\", mouseleave);\n\t\toption.addEventListener(\"mouseover\", mouseover);\n\t}\n\tfunction mouseover() {\n\t\tsetTimeout(() => {\n\t\t\tif (option.matches(\":hover\")) {\n\t\t\t\tif (option.offsetWidth === 36) {\n\t\t\t\t\tlet tooltip = option.querySelector(\".cat-tooltip\");\n\t\t\t\t\ttooltip.classList.remove(\"hidden\");\n\t\t\t\t\tdocument.querySelectorAll(\".cat-tooltip\").forEach(tooltip => {\n\t\t\t\t\t\tif (tooltip !== option.querySelector(\".cat-tooltip\")) tooltip.classList.add(\"hidden\");\n\t\t\t\t\t});\n\t\t\t\t\toption.addEventListener(\"mouseleave\", mouseleave);\n\t\t\t\t}\n\t\t\t}\n\t\t}, 1000);\n\t}\n\toption.addEventListener(\"click\", e => {\n\t\tconst cat = option.getAttribute(\"data-category\");\n\t\tconst current_cat = document.querySelector('.settings-category[data-visible=\"true\"]').getAttribute(\"category\");\n\t\tif (cat === current_cat) return;\n\t\tdocument.querySelectorAll(\".settings-category\").forEach(category => {\n\t\t\tcategory.dataset.visible = \"false\";\n\t\t\tcategory.classList.add(\"opacity-0\", \"pointer-events-none\", \"translate-y-6\");\n\t\t});\n\t\tdocument.querySelectorAll(\".cat-option\").forEach(opt => opt.classList.remove(\"selected\"));\n\t\tconst view = document.querySelector(`.settings-category[category=\"${cat}\"]`);\n\t\tif (view === null) return;\n\t\tview.dataset.visible = \"true\";\n\t\tview.classList.remove(\"opacity-0\", \"pointer-events-none\", \"translate-y-6\");\n\t\toption.classList.add(\"selected\");\n\t});\n\toption.addEventListener(\"mouseover\", mouseover);\n});\n\nconst wallpaper_options = document.querySelectorAll(\".wallpaper-option\");\nwallpaper_options.forEach(option => {\n\toption.addEventListener(\"click\", async e => {\n\t\tconst parent_origin = parent.parent.window.location.origin;\n\t\tconst wallpaper = option.src.toString().split(parent_origin)[1];\n\t\tconst color = option.getAttribute(\"color-type\");\n\t\tlet data = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\tdata[\"wallpaper\"] = wallpaper;\n\t\ttb_wallpaper.set(wallpaper);\n\t\tconst fillMode = parent.window.tb.desktop.wallpaper.fillMode();\n\t\tif (fillMode === null) parent.window.tb.desktop.wallpaper.cover();\n\t\tif (color !== parent.window.tb.desktop.preferences.theme()) {\n\t\t\t// parent.window.tb.desktop.preferences.setTheme(`${color`)\n\t\t\tdocument.body.setAttribute(\"theme\", color);\n\t\t}\n\t});\n});\n\nwindow.parent.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\tif (err) return console.log(err);\n\tdata = JSON.parse(data);\n\tconst fillMode = data[\"wallpaperMode\"];\n\tconst showSeconds = data[\"times\"][\"showSeconds\"];\n\tconst twentyFourHour = data[\"times\"][\"format\"];\n\tlet fillModeCapitalized = fillMode.charAt(0).toUpperCase() + fillMode.slice(1);\n\tdocument.querySelector(`[action-for=\"wallpaper-fill\"]`).querySelector(\".select-title .text\").innerText = fillModeCapitalized;\n\tdocument.querySelector(`[action-for=\"proxy\"]`).querySelector(\".select-title .text\").innerText = data[\"proxy\"];\n\tdocument.querySelector(`[action-for=\"transports\"]`).querySelector(\".select-title .text\").innerText = data[\"transport\"];\n\tdocument.querySelector(`[action-for=\"show-seconds\"]`).querySelector(\".select-title .text\").innerText = showSeconds ? \"Yes\" : \"No\";\n\tdocument.querySelector(`[action-for=\"24h-12h\"]`).querySelector(\".select-title .text\").innerText = twentyFourHour === \"24h\" ? \"Yes\" : \"No\";\n\tdocument.querySelector(`[action-for=\"wmx\"]`).querySelector(\".select-title .text\").innerText = data[\"window\"][\"alwaysMaximized\"] ? \"Yes\" : \"No\";\n\tdocument.querySelector(`[action-for=\"wfs\"]`).querySelector(\".select-title .text\").innerText = data[\"window\"][\"alwaysFullscreen\"] ? \"Yes\" : \"No\";\n\ttry {\n\t\tconst wallpaperVal = data[\"wallpaper\"];\n\t\tconst isb64 = val => {\n\t\t\tif (!val || typeof val !== \"string\") return false;\n\t\t\tif (val.startsWith(\"data:\")) return true;\n\t\t\tconst stripped = val.replace(/\\s+/g, \"\");\n\t\t\treturn /^[A-Za-z0-9+/=]+$/.test(stripped) && stripped.length > 200;\n\t\t};\n\t\tif (isb64(wallpaperVal)) {\n\t\t\tconst wallpaperContainer = document.querySelector(\".wallpapers\");\n\t\t\tif (wallpaperContainer) {\n\t\t\t\tconst container = document.createElement(\"div\");\n\t\t\t\tcontainer.classList.add(\"wallpaper-container\");\n\t\t\t\tcontainer.style.position = \"relative\";\n\t\t\t\tconst img = document.createElement(\"img\");\n\t\t\t\timg.classList.add(\"wallpaper-option\");\n\t\t\t\timg.src = wallpaperVal.startsWith(\"data:\") ? wallpaperVal : `data:image/png;base64,${wallpaperVal}`;\n\t\t\t\timg.alt = \"Synced Wallpaper\";\n\t\t\t\timg.style.objectFit = \"cover\";\n\t\t\t\timg.addEventListener(\"click\", async () => {\n\t\t\t\t\ttb_wallpaper.set(img.src);\n\t\t\t\t});\n\t\t\t\tconst label = document.createElement(\"div\");\n\t\t\t\tlabel.innerText = \"Synced Wallpaper\";\n\t\t\t\tlabel.style.position = \"absolute\";\n\t\t\t\tlabel.style.bottom = \"6px\";\n\t\t\t\tlabel.style.left = \"6px\";\n\t\t\t\tlabel.style.background = \"rgba(0,0,0,0.45)\";\n\t\t\t\tlabel.style.padding = \"2px 6px\";\n\t\t\t\tlabel.style.borderRadius = \"6px\";\n\t\t\t\tlabel.style.color = \"#ffffff\";\n\t\t\t\tlabel.style.fontSize = \"12px\";\n\t\t\t\tcontainer.appendChild(img);\n\t\t\t\tcontainer.appendChild(label);\n\t\t\t\twallpaperContainer.prepend(container);\n\t\t\t}\n\t\t}\n\t} catch (e) {\n\t\tconsole.warn(\"Unable to show synced wallpaper label:\", e);\n\t}\n});\n\nwindow.parent.tb.fs.readFile(\"/system/etc/terbium/settings.json\", \"utf8\", (err, data) => {\n\tif (err) return console.log(err);\n\tdata = JSON.parse(data);\n\tconst cords = data[\"location\"];\n\tdocument.querySelector(`.cords`).innerText = `${cords}`;\n\tconst tempunit = data[\"weather\"][\"unit\"];\n\tdocument.querySelector(`[action-for=\"temperature-unit\"]`).querySelector(\".select-title .text\").innerText = tempunit;\n});\n\nconst customWallpaper = () => {\n\twindow.parent.tb.dialog.Select({\n\t\ttitle: \"Where do you want to load the wallpaper from?\",\n\t\toptions: [\n\t\t\t{\n\t\t\t\ttext: \"System Storage\",\n\t\t\t\tvalue: \"sys\",\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Terbium File System\",\n\t\t\t\tvalue: \"fs\",\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Internet Url\",\n\t\t\t\tvalue: \"url\",\n\t\t\t},\n\t\t],\n\t\tonOk: async perm => {\n\t\t\tswitch (perm) {\n\t\t\t\tcase \"sys\":\n\t\t\t\t\tconst input = document.createElement(\"input\");\n\t\t\t\t\tinput.type = \"file\";\n\t\t\t\t\tinput.setAttribute(\"accept\", \"image/*\");\n\t\t\t\t\tinput.click();\n\t\t\t\t\tinput.addEventListener(\"change\", async e => {\n\t\t\t\t\t\tconst file = input.files[0];\n\t\t\t\t\t\tconst buffer = await file.arrayBuffer();\n\t\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\t\treader.readAsDataURL(file);\n\t\t\t\t\t\treader.onload = async () => {\n\t\t\t\t\t\t\tconst imgdata = reader.result;\n\t\t\t\t\t\t\tconst path = \"/system/etc/terbium/wallpapers/\" + file.name;\n\n\t\t\t\t\t\t\ttb_wallpaper.set(path);\n\t\t\t\t\t\t\tconst img_container = document.createElement(\"div\");\n\t\t\t\t\t\t\timg_container.classList.add(\"wallpaper-container\");\n\t\t\t\t\t\t\tconst wimg = document.createElement(\"img\");\n\t\t\t\t\t\t\twimg.src = imgdata;\n\t\t\t\t\t\t\twimg.classList.add(\"wallpaper-option\");\n\n\t\t\t\t\t\t\tconst delete_button = document.createElement(\"img\");\n\t\t\t\t\t\t\tdelete_button.src = \"/fs/apps/system/settings.tapp/delete.svg\";\n\t\t\t\t\t\t\tdelete_button.classList.add(\"delete-wallpaper\");\n\t\t\t\t\t\t\tdelete_button.addEventListener(\"click\", async e => {\n\t\t\t\t\t\t\t\tlet data = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\t\t\t\t\tif (data[\"wallpaper\"] === path) {\n\t\t\t\t\t\t\t\t\ttb_wallpaper.set(\"/assets/wallpapers/1.png\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.unlink(path);\n\t\t\t\t\t\t\t\timg_container.remove();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\twimg.addEventListener(\"click\", async e => {\n\t\t\t\t\t\t\t\ttb_wallpaper.set(path);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(path, window.parent.tb.buffer.from(buffer), \"arraybuffer\");\n\t\t\t\t\t\t\ttb_wallpaper.set(path);\n\t\t\t\t\t\t\timg_container.append(wimg);\n\t\t\t\t\t\t\timg_container.append(delete_button);\n\t\t\t\t\t\t\tdocument.querySelector(\".custom-wallpaper\").remove();\n\t\t\t\t\t\t\tdocument.querySelector(\".wallpapers\").append(img_container);\n\t\t\t\t\t\t\tappendCustomWallpaper();\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"fs\":\n\t\t\t\t\ttb.dialog.FileBrowser({\n\t\t\t\t\t\ttitle: \"Select a wallpaper from the file system\",\n\t\t\t\t\t\tlocal: true,\n\t\t\t\t\t\tonOk: async filePath => {\n\t\t\t\t\t\t\tconst imgdata = await window.parent.tb.fs.promises.readFile(filePath, \"arraybuffer\");\n\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/system/etc/terbium/wallpapers/\" + filePath.split(\"/\").pop(), imgdata, \"arraybuffer\");\n\t\t\t\t\t\t\ttb.desktop.wallpaper.set(\"/system/etc/terbium/wallpapers/\" + filePath.split(\"/\").pop());\n\t\t\t\t\t\t\tdocument.querySelector(\".wallpapers\").innerHTML = `\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/1.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/2.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/3.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/4.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/5.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/6.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/7.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t`;\n\t\t\t\t\t\t\tconst wallpaper_options = document.querySelectorAll(\".wallpaper-option\");\n\t\t\t\t\t\t\twallpaper_options.forEach(option => {\n\t\t\t\t\t\t\t\toption.addEventListener(\"click\", async e => {\n\t\t\t\t\t\t\t\t\tconst parent_origin = parent.parent.window.location.origin;\n\t\t\t\t\t\t\t\t\tconst wallpaper = option.src.toString().split(parent_origin)[1];\n\t\t\t\t\t\t\t\t\tconst color = option.getAttribute(\"color-type\");\n\t\t\t\t\t\t\t\t\tlet data = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\tdata[\"wallpaper\"] = wallpaper;\n\t\t\t\t\t\t\t\t\ttb_wallpaper.set(wallpaper);\n\t\t\t\t\t\t\t\t\tconst fillMode = parent.window.tb.desktop.wallpaper.fillMode();\n\t\t\t\t\t\t\t\t\tif (fillMode === null) parent.window.tb.desktop.wallpaper.cover();\n\t\t\t\t\t\t\t\t\tif (color !== parent.window.tb.desktop.preferences.theme()) {\n\t\t\t\t\t\t\t\t\t\t// parent.window.tb.desktop.preferences.setTheme(`${color`)\n\t\t\t\t\t\t\t\t\t\tdocument.body.setAttribute(\"theme\", color);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tgetWallpapers();\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"url\":\n\t\t\t\t\ttb.dialog.Message({\n\t\t\t\t\t\ttitle: \"Enter a URL of the wallpaper\",\n\t\t\t\t\t\tonOk: async value => {\n\t\t\t\t\t\t\tawait window.parent.tb.system.download(value, `/system/etc/terbium/wallpapers/${value.split(\"/\").pop()}`);\n\t\t\t\t\t\t\ttb.desktop.wallpaper.set(\"/system/etc/terbium/wallpapers/\" + value.split(\"/\").pop());\n\t\t\t\t\t\t\tdocument.querySelector(\".wallpapers\").innerHTML = `\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/1.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/2.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/3.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/4.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/5.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/6.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t\t<img src=\"/assets/wallpapers/7.png\" class=\"wallpaper-option\"></img>\n\t\t\t\t\t\t\t`;\n\t\t\t\t\t\t\tconst wallpaper_options = document.querySelectorAll(\".wallpaper-option\");\n\t\t\t\t\t\t\twallpaper_options.forEach(option => {\n\t\t\t\t\t\t\t\toption.addEventListener(\"click\", async e => {\n\t\t\t\t\t\t\t\t\tconst parent_origin = parent.parent.window.location.origin;\n\t\t\t\t\t\t\t\t\tconst wallpaper = option.src.toString().split(parent_origin)[1];\n\t\t\t\t\t\t\t\t\tconst color = option.getAttribute(\"color-type\");\n\t\t\t\t\t\t\t\t\tlet data = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\tdata[\"wallpaper\"] = wallpaper;\n\t\t\t\t\t\t\t\t\ttb_wallpaper.set(wallpaper);\n\t\t\t\t\t\t\t\t\tconst fillMode = parent.window.tb.desktop.wallpaper.fillMode();\n\t\t\t\t\t\t\t\t\tif (fillMode === null) parent.window.tb.desktop.wallpaper.cover();\n\t\t\t\t\t\t\t\t\tif (color !== parent.window.tb.desktop.preferences.theme()) {\n\t\t\t\t\t\t\t\t\t\t// parent.window.tb.desktop.preferences.setTheme(`${color`)\n\t\t\t\t\t\t\t\t\t\tdocument.body.setAttribute(\"theme\", color);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tgetWallpapers();\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t});\n};\n\nconst appendCustomWallpaper = () => {\n\tif (document.querySelector(\".custom-wallpaper\")) {\n\t\tconsole.log(document.querySelector(\".custom-wallpaper\"));\n\t}\n\n\tconst newButton = `\n        <button class=\"wallpaper-option flex justify-center items-center bg-[#ffffff10] hover:bg-[#ffffff20] duration-150 custom-wallpaper\" style=\"box-shadow: inset 0 0 0 0.5px #ffffff38;\">\n            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-8 pointer-events-none\">\n                <path style=\"stroke: currentColor; stroke-width: 1;\" fill-rule=\"evenodd\" d=\"M12 3.75a.75.75 0 0 1 .75.75v6.75h6.75a.75.75 0 0 1 0 1.5h-6.75v6.75a.75.75 0 0 1-1.5 0v-6.75H4.5a.75.75 0 0 1 0-1.5h6.75V4.5a.75.75 0 0 1 .75-.75Z\" clip-rule=\"evenodd\" />\n            </svg>\n        </button>\n    `;\n\n\tconst wallpaperContainer = document.querySelector(\".wallpapers\");\n\twallpaperContainer.insertAdjacentHTML(\"beforeend\", newButton);\n\tconst customWallpaperBtn = document.querySelector(\".custom-wallpaper\");\n\tcustomWallpaperBtn.addEventListener(\"click\", e => {\n\t\tcustomWallpaper();\n\t});\n};\n\nasync function getWispSrvs() {\n\tconst fileExists = await window.parent.tb.fs.promises\n\t\t.stat(\"//apps/system/settings.tapp/wisp-servers.json\")\n\t\t.then(() => true)\n\t\t.catch(() => false);\n\tif (!fileExists) {\n\t\tawait window.parent.tb.fs.promises.mkdir(\"//apps/settings.tapp/\", { recursive: true });\n\t\tconst stockDat = [\n\t\t\t{ id: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`, name: \"Backend\" },\n\t\t\t{ id: \"wss://wisp.terbiumon.top/wisp/\", name: \"TB Wisp Instance\" },\n\t\t];\n\t\tawait window.parent.tb.fs.promises.writeFile(\"//apps/system/settings.tapp/wisp-servers.json\", JSON.stringify(stockDat));\n\t}\n\tconst main = document.getElementById(\"wispSrvs\");\n\twindow.parent.window.dispatchEvent(new Event(\"update-wispsrvs\"));\n\tconst makeCard = async (name, id) => {\n\t\tlet settings = await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\");\n\t\tlet settdata = JSON.parse(settings);\n\n\t\tconst card = document.createElement(\"div\");\n\t\tcard.classList.add(\"flex\", \"justify-between\", \"w-full\", \"p-1.5\", \"rounded-lg\", \"duration-150\", `srv-${id.replace(/\\s/g, \"-\")}`);\n\t\tif (name === settdata.wispServer) {\n\t\t\tcard.classList.add(\"bg-[#4acd609c]\");\n\t\t} else {\n\t\t\tcard.classList.add(\"bg-[#ffffff18]\", \"hover:bg-[#ffffff38]\", \"cursor-pointer\");\n\t\t}\n\n\t\tconst html = `\n            <div class=\"flex gap-6 items-center justify-between text-[#ffffffd1] w-full pointer-events-none\">\n                <div class=\"flex gap-3 items-center\">\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-8\">\n                        <path fill-rule=\"evenodd\" d=\"M1.371 8.143c5.858-5.857 15.356-5.857 21.213 0a.75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.06 0c-4.98-4.979-13.053-4.979-18.032 0a.75.75 0 0 1-1.06 0l-.53-.53a.75.75 0 0 1 0-1.06Zm3.182 3.182c4.1-4.1 10.749-4.1 14.85 0a.75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.062 0 8.25 8.25 0 0 0-11.667 0 .75.75 0 0 1-1.06 0l-.53-.53a.75.75 0 0 1 0-1.06Zm3.204 3.182a6 6 0 0 1 8.486 0 .75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.061 0 3.75 3.75 0 0 0-5.304 0 .75.75 0 0 1-1.06 0l-.53-.53a.75.75 0 0 1 0-1.06Zm3.182 3.182a1.5 1.5 0 0 1 2.122 0 .75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.061 0l-.53-.53a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" />\n                    </svg>\n                    <div class=\"flex flex-col\">\n                        <h3 class=\"font-bold leading-tight\">${id}</h3>\n                        <span class=\"text-xs leading-tight\">${name}</span>\n                    </div>\n                </div>\n                <p latency-${id.replace(/\\s/g, \"-\")} class=\"text-sm p-2 bg-[#ffffff38] rounded-lg leading-none text-[#ffffffce] font-extrabold \">Pinging...</p>\n            </div>\n        `;\n\n\t\tcard.innerHTML = html;\n\t\tsetTimeout(async () => {\n\t\t\tconst res = await ping(name);\n\t\t\tdocument.querySelector(`[latency-${id.replace(/\\s/g, \"-\")}]`).innerHTML = res.latency + \"ms\";\n\t\t}, 1750);\n\n\t\tcard.addEventListener(\"click\", async () => {\n\t\t\tsettdata.wispServer = name;\n\t\t\tconst updSet = JSON.stringify(settdata, null, 2);\n\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, updSet);\n\t\t\twindow.parent.tb.proxy.updateSWs();\n\t\t\twindow.parent.window.dispatchEvent(new Event(\"update-wispsrvs\"));\n\t\t});\n\n\t\tmain.appendChild(card);\n\t};\n\tconst data = JSON.parse(await window.parent.tb.fs.promises.readFile(\"//apps/system/settings.tapp/wisp-servers.json\"));\n\tdata.forEach(item => {\n\t\tmakeCard(item.id, item.name);\n\t});\n\tconst ping = id => {\n\t\treturn new Promise(resolve => {\n\t\t\tconst websocket = new WebSocket(id);\n\t\t\tconst startTime = Date.now();\n\t\t\tconst onOpen = () => {\n\t\t\t\tconst latency = Date.now() - startTime;\n\t\t\t\twebsocket.close();\n\t\t\t\tresolve({ status: \"OK\", latency });\n\t\t\t};\n\t\t\tconst onMessage = () => {\n\t\t\t\tconst latency = Date.now() - startTime;\n\t\t\t\twebsocket.close();\n\t\t\t\tresolve({ status: \"OK\", latency });\n\t\t\t};\n\t\t\tconst onError = () => {\n\t\t\t\twebsocket.close();\n\t\t\t\tresolve({ status: \"Fail\", latency: \"N/A\" });\n\t\t\t};\n\t\t\twebsocket.addEventListener(\"open\", onOpen);\n\t\t\twebsocket.addEventListener(\"message\", onMessage);\n\t\t\twebsocket.addEventListener(\"error\", onError);\n\t\t\tsetTimeout(() => {\n\t\t\t\twebsocket.close();\n\t\t\t\tresolve({ status: \"Fail\", latency: \"N/A\" });\n\t\t\t}, 5000);\n\t\t});\n\t};\n\tconst addWispbtn = document.getElementById(\"addWisp\");\n\taddWispbtn.addEventListener(\"click\", () => {\n\t\twindow.parent.tb.dialog.Message({\n\t\t\ttitle: \"Enter a name for the Wisp server\",\n\t\t\tonOk: async val => {\n\t\t\t\tsessionStorage.setItem(\"wispSrv\", val);\n\t\t\t\twindow.parent.tb.dialog.Message({\n\t\t\t\t\ttitle: \"Enter the socket URL for the Wisp server\",\n\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\tconst ent = { id: val, name: sessionStorage.getItem(\"wispSrv\") };\n\t\t\t\t\t\tlet data = JSON.parse(await window.parent.tb.fs.promises.readFile(\"//apps/system/settings.tapp/wisp-servers.json\"));\n\t\t\t\t\t\tdata.push(ent);\n\t\t\t\t\t\twindow.parent.tb.fs.promises.writeFile(\"//apps/system/settings.tapp/wisp-servers.json\", JSON.stringify(data));\n\t\t\t\t\t\tmakeCard(val, sessionStorage.getItem(\"wispSrv\"));\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t});\n\tconst rmWispbtn = document.getElementById(\"rmWisp\");\n\trmWispbtn.addEventListener(\"click\", async () => {\n\t\tlet data = JSON.parse(await window.parent.tb.fs.promises.readFile(\"//apps/system/settings.tapp/wisp-servers.json\"));\n\t\tconst servers = data.map(item => ({\n\t\t\ttext: item.name,\n\t\t\tvalue: item.name,\n\t\t}));\n\t\twindow.parent.tb.dialog.Select({\n\t\t\ttitle: \"Select the Wisp server to remove\",\n\t\t\toptions: servers,\n\t\t\tonOk: async selectedName => {\n\t\t\t\tdata = data.filter(item => item.name !== selectedName);\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\"//apps/system/settings.tapp/wisp-servers.json\", JSON.stringify(data));\n\t\t\t\tdocument.querySelector(`.srv-${selectedName.replace(/\\s/g, \"-\")}`).remove();\n\t\t\t\twindow.parent.window.dispatchEvent(new Event(\"update-wispsrvs\"));\n\t\t\t},\n\t\t});\n\t});\n}\ngetWispSrvs();\n\nasync function updateTransport(transport) {\n\tconst st = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\tst[\"transport\"] = transport;\n\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(st), \"utf8\");\n}\n\nconst accentPreview = document.querySelector(\".accent-preview\");\nconst accentMousedown = async () => {\n\tconst defaultAccent = \"#32ae62\";\n\taccentPreview.classList.remove(\"group\", \"cursor-pointer\");\n\taccentPreview.style.setProperty(\"--accent\", defaultAccent);\n\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\tsettings[\"accent\"] = defaultAccent;\n\twindow.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\taccentPreview.removeEventListener(\"mousedown\", accentMousedown);\n};\n\nconst getAccent = async () => {\n\tconst settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\tvar accentColor = settings[\"accent\"];\n\tconst defaultAccent = \"#32ae62\";\n\tif (accentColor !== defaultAccent) {\n\t\taccentPreview.classList.add(\"group\", \"cursor-pointer\");\n\t\taccentPreview.addEventListener(\"mousedown\", accentMousedown);\n\t}\n\tif (accentColor) {\n\t\taccentPreview.style.setProperty(\"--accent\", accentColor);\n\t} else {\n\t\taccentPreview.style.setProperty(\"--accent\", \"#32ae62\");\n\t}\n};\ngetAccent();\n\nconst custom_accent = document.querySelector(\".custom-accent\");\ncustom_accent.addEventListener(\"click\", e => {\n\tconst color_picker = document.createElement(\"input\");\n\tcolor_picker.type = \"color\";\n\tcolor_picker.click();\n\tcolor_picker.addEventListener(\"change\", async e => {\n\t\tlet color = color_picker.value;\n\t\tif (color.charAt(0) !== \"#\") {\n\t\t\tconst rgb = color.match(/\\d+/g);\n\t\t\tconst r = rgb[0];\n\t\t\tconst g = rgb[1];\n\t\t\tconst b = rgb[2];\n\t\t\tcolor = \"#\" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);\n\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tsettings[\"accent\"] = color;\n\t\t\twindow.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\t\t} else {\n\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tsettings[\"accent\"] = color;\n\t\t\twindow.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\t\t}\n\t\taccentPreview.style.setProperty(\"--accent\", color);\n\t\taccentPreview.classList.add(\"group\", \"cursor-pointer\");\n\t\taccentPreview.addEventListener(\"mousedown\", accentMousedown);\n\t\twindow.parent.window.dispatchEvent(new Event(\"upd-accent\"));\n\t});\n});\n\nconst getWallpapers = async () => {\n\tconst files = await window.parent.tb.fs.promises.readdir(\"/system/etc/terbium/wallpapers\");\n\tfor (const file of files) {\n\t\tconst path = \"/system/etc/terbium/wallpapers/\" + file;\n\t\tconst data = URL.createObjectURL(new Blob([await window.parent.tb.fs.promises.readFile(path, \"utf8\")]));\n\t\tconst img_container = document.createElement(\"div\");\n\t\timg_container.classList.add(\"wallpaper-container\");\n\t\tconst img = document.createElement(\"img\");\n\t\timg.src = `/fs/${path}`;\n\t\timg.classList.add(\"wallpaper-option\");\n\t\tconst delete_button = document.createElement(\"img\");\n\t\tdelete_button.src = \"/fs/apps/system/settings.tapp/delete.svg\";\n\t\tdelete_button.classList.add(\"delete-wallpaper\");\n\t\tdelete_button.addEventListener(\"click\", e => {\n\t\t\twindow.parent.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\t\t\t\tif (err) return console.log(err);\n\t\t\t\tdata = JSON.parse(data);\n\t\t\t\tif (data[\"wallpaper\"] === path) {\n\t\t\t\t\ttb_wallpaper.set(\"/assets/wallpapers/1.png\");\n\t\t\t\t}\n\t\t\t});\n\t\t\twindow.parent.tb.fs.unlink(path, err => {\n\t\t\t\tif (err) return console.log(err);\n\t\t\t\timg_container.remove();\n\t\t\t});\n\t\t});\n\t\timg_container.append(img);\n\t\timg_container.append(delete_button);\n\t\tdocument.querySelector(\".wallpapers\").append(img_container);\n\t\timg.addEventListener(\"click\", e => {\n\t\t\ttb_wallpaper.set(path);\n\t\t});\n\t}\n\tappendCustomWallpaper();\n};\ngetWallpapers();\n\nconst pfpEl = document.querySelector(\".pfp\");\nwindow.parent.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\", (err, data) => {\n\tif (err) return console.log(err);\n\tdata = JSON.parse(data);\n\tpfpEl.src = data[\"pfp\"];\n});\n\npfpEl.addEventListener(\"click\", async e => {\n\tconst uploader = document.createElement(\"input\");\n\tuploader.type = \"file\";\n\tuploader.accept = \"img/*\";\n\tuploader.onchange = () => {\n\t\tconst files = uploader.files;\n\t\tconst file = files[0];\n\t\tconst reader = new FileReader();\n\t\treader.onload = () => {\n\t\t\ttb.dialog.Cropper({\n\t\t\t\ttitle: \"Resize your Profile picture\",\n\t\t\t\timg: reader.result,\n\t\t\t\tonOk: async img => {\n\t\t\t\t\tif (await window.parent.tb.tauth.isTACC()) {\n\t\t\t\t\t\ttb.dialog.Select({\n\t\t\t\t\t\t\ttitle: \"Do you want to upload this profile picture to your Terbium Account?\",\n\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"Yes\",\n\t\t\t\t\t\t\t\t\tvalue: \"yes\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttext: \"No\",\n\t\t\t\t\t\t\t\t\tvalue: \"no\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tonOk: async choice => {\n\t\t\t\t\t\t\t\tif (choice === \"yes\") {\n\t\t\t\t\t\t\t\t\tawait window.parent.tb.tauth.updateInfo({\n\t\t\t\t\t\t\t\t\t\tpfp: img,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst uSettings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"));\n\t\t\t\t\tuSettings[\"pfp\"] = img;\n\t\t\t\t\tpfpEl.src = img;\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, JSON.stringify(uSettings));\n\t\t\t\t\twindow.parent.dispatchEvent(new Event(\"accUpd\"));\n\t\t\t\t},\n\t\t\t});\n\t\t};\n\t\treader.readAsDataURL(file);\n\t};\n\tuploader.click();\n});\n\nconst usernameEl = document.querySelector(\".username\");\nusernameEl.addEventListener(\"input\", async e => {\n\tusernameEl.addEventListener(\"blur\", async () => {\n\t\tif (usernameEl.value !== JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"))[\"username\"]) {\n\t\t\tawait window.parent.tb.system.users.renameUser(sessionStorage.getItem(\"currAcc\"), usernameEl.value);\n\t\t}\n\t});\n});\n\nconst permEl = document.querySelector(\".perm\");\npermEl.addEventListener(\"click\", async () => {\n\tconst data = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"));\n\tif (data[\"password\"] === false) {\n\t\tawait tb.dialog.Select({\n\t\t\ttitle: \"Enter the permission level you wish to set (Ex: Admin, User, Group, Public)\",\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\ttext: \"Admin\",\n\t\t\t\t\tvalue: \"admin\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"User\",\n\t\t\t\t\tvalue: \"user\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Group\",\n\t\t\t\t\tvalue: \"group\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Public\",\n\t\t\t\t\tvalue: \"public\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tonOk: async perm => {\n\t\t\t\tif (perm === data[\"perm\"]) return;\n\t\t\t\tdata[\"perm\"] = perm;\n\t\t\t\tpermEl.innerHTML = perm.charAt(0).toUpperCase() + perm.slice(1);\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, JSON.stringify(data));\n\t\t\t},\n\t\t});\n\t} else {\n\t\tawait tb.dialog.Auth({\n\t\t\tsudo: true,\n\t\t\ttitle: \"Authenticate to change your permissions\",\n\t\t\tdefaultUsername: sessionStorage.getItem(\"currAcc\"),\n\t\t\tonOk: async (_username, password) => {\n\t\t\t\tconst pass = await tb.crypto(password);\n\t\t\t\tif (pass === data[\"password\"]) {\n\t\t\t\t\tawait tb.dialog.Select({\n\t\t\t\t\t\ttitle: \"Enter the permission level you wish to set (Ex: Admin, User, Group, Public)\",\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Admin\",\n\t\t\t\t\t\t\t\tvalue: \"admin\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"User\",\n\t\t\t\t\t\t\t\tvalue: \"user\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Group\",\n\t\t\t\t\t\t\t\tvalue: \"group\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Public\",\n\t\t\t\t\t\t\t\tvalue: \"public\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tonOk: async perm => {\n\t\t\t\t\t\t\tif (perm === data[\"perm\"]) return;\n\t\t\t\t\t\t\tdata[\"perm\"] = perm;\n\t\t\t\t\t\t\tpermEl.innerHTML = perm.charAt(0).toUpperCase() + perm.slice(1);\n\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, JSON.stringify(data));\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(\"Incorrect Password\");\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t}\n});\n\nconst actype = document.querySelector(\".actype\");\nactype.addEventListener(\"click\", async () => {\n\tawait tb.dialog.Select({\n\t\ttitle: \"Select Option\",\n\t\toptions: [\n\t\t\t{\n\t\t\t\ttext: \"Link Terbium Cloud™ Account\",\n\t\t\t\tvalue: \"cloud\",\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"Convert to Local Account\",\n\t\t\t\tvalue: \"local\",\n\t\t\t},\n\t\t],\n\t\tonOk: async choice => {\n\t\t\tswitch (choice) {\n\t\t\t\tcase \"cloud\":\n\t\t\t\t\tconst res = await window.parent.tb.tauth.signIn();\n\t\t\t\t\tactype.innerHTML = \"Terbium Cloud\\u2122 Account\";\n\t\t\t\t\tconst currAcc = sessionStorage.getItem(\"currAcc\");\n\t\t\t\t\tsessionStorage.setItem(\"currAcc\", res.data.user.name);\n\t\t\t\t\tawait window.parent.tb.tauth.sync.retreive();\n\t\t\t\t\tsessionStorage.setItem(\"currAcc\", currAcc);\n\t\t\t\t\tconst userinfo = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"));\n\t\t\t\t\tuserinfo[\"password\"] = await window.parent.tb.crypto(res.data.user.password);\n\t\t\t\t\tuserinfo[\"perm\"] = \"admin\";\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, JSON.stringify(userinfo));\n\t\t\t\t\tawait window.parent.tb.system.users.renameUser(sessionStorage.getItem(\"currAcc\"), res.data.user.name);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"local\":\n\t\t\t\t\twindow.parent.tb.dialog.Select({\n\t\t\t\t\t\ttitle: \"Save current settings to cloud before converting to local?\",\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"Yes\",\n\t\t\t\t\t\t\t\tvalue: \"yes\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: \"No\",\n\t\t\t\t\t\t\t\tvalue: \"no\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tonOk: async val => {\n\t\t\t\t\t\t\tif (val === \"yes\") {\n\t\t\t\t\t\t\t\tawait window.parent.tb.tauth.sync.upload();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tawait window.parent.tb.tauth.signOut();\n\t\t\t\t\tactype.innerHTML = \"Local Account\";\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t});\n});\n\nwindow.parent.tb.fs.readFile(`/system/etc/terbium/taccs.json`, \"utf8\", (err, data) => {\n\tif (err) actype.innerHTML = \"Local Account\";\n\tconst entries = JSON.parse(data);\n\tconst act = sessionStorage.getItem(\"currAcc\");\n\ttry {\n\t\tlet isCloud = false;\n\t\tif (Array.isArray(entries)) {\n\t\t\tisCloud = entries.some(e => e && e.username === act);\n\t\t} else if (entries && typeof entries === \"object\") {\n\t\t\tisCloud = Object.values(entries).some(e => e && e.username === act);\n\t\t}\n\t\tactype.innerHTML = isCloud ? \"Terbium Cloud\\u2122 Account\" : \"Local Account\";\n\t} catch (e) {\n\t\tactype.innerHTML = \"Local Account\";\n\t}\n});\n\nwindow.parent.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\", (err, data) => {\n\tif (err) return console.log(err);\n\tdata = JSON.parse(data);\n\tusernameEl.value = data[\"username\"];\n\tpermEl.innerHTML = data[\"perm\"].charAt(0).toUpperCase() + data[\"perm\"].slice(1);\n});\n\nconst hostnameEl = document.querySelector(\".hostname\");\nhostnameEl.addEventListener(\"input\", async e => {\n\thostnameEl.addEventListener(\"blur\", async () => {\n\t\tif (hostnameEl.value !== JSON.parse(await window.parent.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"host-name\"]) {\n\t\t\twindow.parent.tb.fs.readFile(\"/system/etc/terbium/settings.json\", \"utf8\", async (err, data) => {\n\t\t\t\tif (err) return console.log(err);\n\t\t\t\tdata = JSON.parse(data);\n\t\t\t\tdata[\"host-name\"] = hostnameEl.value;\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(data));\n\t\t\t});\n\t\t}\n\t});\n});\n\nwindow.parent.tb.fs.readFile(\"/system/etc/terbium/settings.json\", \"utf8\", (err, data) => {\n\tif (err) return console.log(err);\n\tdata = JSON.parse(data);\n\thostnameEl.value = data[\"host-name\"];\n});\n\nconst cords = document.querySelector(\".cords\");\nconst saveCity = document.querySelector(\".save-city\");\nsaveCity.addEventListener(\"click\", e => {\n\twindow.parent.tb.fs.readFile(\"/system/etc/terbium/settings.json\", \"utf8\", (err, data) => {\n\t\tif (err) return console.log(err);\n\t\tdata = JSON.parse(data);\n\t\tif (navigator.geolocation) {\n\t\t\tnavigator.geolocation.getCurrentPosition(\n\t\t\t\tfunction (position) {\n\t\t\t\t\tconst latitude = position.coords.latitude;\n\t\t\t\t\tconst longitude = position.coords.longitude;\n\t\t\t\t\tconsole.log(`${latitude},${longitude}`);\n\t\t\t\t\tdata[\"location\"] = `${latitude},${longitude}`;\n\t\t\t\t\twindow.parent.dispatchEvent(new Event(\"updWeather\"));\n\t\t\t\t\twindow.parent.tb.fs.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(data), err => {\n\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tfunction (error) {\n\t\t\t\t\tconsole.error(`Error Occured: ${error.code}`);\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tenableHighAccuracy: true,\n\t\t\t\t\ttimeout: 5000,\n\t\t\t\t\tmaximumAge: 0,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t});\n});\n\nconst accountsButton = document.querySelector(\".accounts\");\naccountsButton.addEventListener(\"mousedown\", e => {\n\ttb_window.create({\n\t\ttitle: \"Accounts\",\n\t\tsrc: \"/fs/apps/system/settings.tapp/accounts/index.html\",\n\t\ticon: \"/fs/apps/system/settings.tapp/accounts/icon.svg\",\n\t\tsize: {\n\t\t\twidth: 400,\n\t\t\theight: 500,\n\t\t},\n\t});\n});\n\nconst batteryPercentage = document.querySelector(\".battery-percentage\");\n(async () => {\n\tlet showBatteryPercentage = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"))[\"battery-percent\"];\n\tconst realCheckbox = batteryPercentage.querySelector(\"input[type='checkbox']\");\n\tif (showBatteryPercentage) {\n\t\trealCheckbox.checked = true;\n\t\tconst checkIcon = batteryPercentage.querySelector(\".checkIcon\");\n\t\tcheckIcon.classList.remove(\"opacity-0\", \"scale-85\");\n\t} else {\n\t\trealCheckbox.checked = false;\n\t\tconst checkIcon = batteryPercentage.querySelector(\".checkIcon\");\n\t\tcheckIcon.classList.add(\"opacity-0\", \"scale-85\");\n\t}\n})();\n\nbatteryPercentage.addEventListener(\"mousedown\", async e => {\n\tlet data = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\tconst realCheckbox = batteryPercentage.querySelector(\"input[type='checkbox']\");\n\trealCheckbox.checked = !realCheckbox.checked;\n\tconst checkIcon = batteryPercentage.querySelector(\".checkIcon\");\n\tif (realCheckbox.checked) {\n\t\tcheckIcon.classList.remove(\"opacity-0\", \"scale-85\");\n\t\ttb.battery.showPercentage();\n\t} else {\n\t\tcheckIcon.classList.add(\"opacity-0\", \"scale-85\");\n\t\ttb.battery.hidePercentage();\n\t}\n\tdata[\"battery-percent\"] = realCheckbox.checked;\n\twindow.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(data));\n});\n\nconst getBat = async () => {\n\tconst battery = await window.parent.tb.battery.canUse();\n\tif (!battery) {\n\t\tdocument.querySelector(\".battery\").remove();\n\t}\n};\ngetBat();\n\nconst showCords = document.querySelector(\".showCords\");\nshowCords.addEventListener(\"mousedown\", async e => {\n\tshowCords.classList.add(\"opacity-0\", \"pointer-events-none\");\n\tcords.classList.remove(\"opacity-0\");\n\tconst mouseup = () => {\n\t\tshowCords.classList.remove(\"opacity-0\", \"pointer-events-none\");\n\t\tcords.classList.add(\"opacity-0\");\n\t\tdocument.removeEventListener(\"mouseup\", mouseup);\n\t};\n\tdocument.addEventListener(\"mouseup\", mouseup);\n});\n\nasync function exportSettings() {\n\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\tlet data = JSON.stringify(settings);\n\tlet blob = new Blob([data], { type: \"application/json\" });\n\tlet url = URL.createObjectURL(blob);\n\tlet a = document.createElement(\"a\");\n\ta.href = url;\n\ta.download = \"settings.json\";\n\ta.click();\n}\n\nconst range = document.getElementById(\"blurRange\");\nconst pct = document.getElementById(\"blurPercent\");\nasync function render(initial) {\n\tconst settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\tconst v = initial ? settings.window.blurlevel : Number(range.value);\n\tif (initial) range.value = v;\n\tconst mapped = Math.round((v / 50) * 100);\n\tpct.textContent = mapped + \"%\";\n\tconst fill = (v / 50) * 100;\n\trange.style.background = `linear-gradient(90deg,#60a5fa ${fill}%, rgba(255,255,255,0.12) ${fill}%)`;\n\trange.dataset.mappedPercent = mapped;\n\tif (initial) return;\n\tsettings.window.blurlevel = v;\n\twindow.parent.tb.fs.promises.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, JSON.stringify(settings, null, 4), \"utf8\");\n\twindow.parent.dispatchEvent(new Event(\"upd-accent\"));\n}\n\nrender(true);\nrange.addEventListener(\"input\", () => render(false));\n\nconst winAccent = document.querySelector(\".winaccent-preview\");\nconst initWinAccent = async () => {\n\tconst defaultAccent = \"#ffffff\";\n\ttry {\n\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\tconst current = settings.window && settings.window.winAccent ? settings.window.winAccent : defaultAccent;\n\t\twinAccent.style.setProperty(\"--accent\", current);\n\t\tif (current !== defaultAccent) {\n\t\t\twinAccent.classList.add(\"group\", \"cursor-pointer\");\n\t\t\tconst resetHandler = async () => {\n\t\t\t\tlet s = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\ts.window = s.window || {};\n\t\t\t\ts.window.winAccent = defaultAccent;\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(s));\n\t\t\t\twinAccent.style.setProperty(\"--accent\", defaultAccent);\n\t\t\t\twinAccent.classList.remove(\"group\", \"cursor-pointer\");\n\t\t\t\twinAccent.removeEventListener(\"mousedown\", resetHandler);\n\t\t\t\twindow.parent.dispatchEvent(new Event(\"upd-accent\"));\n\t\t\t};\n\t\t\twinAccent.addEventListener(\"mousedown\", resetHandler);\n\t\t}\n\t} catch (err) {\n\t\twinAccent.style.setProperty(\"--accent\", defaultAccent);\n\t}\n};\ninitWinAccent();\n\nconst custom_waccent = document.querySelector(\".custom-waccent\");\ncustom_waccent.addEventListener(\"click\", e => {\n\tconst color_picker = document.createElement(\"input\");\n\tcolor_picker.type = \"color\";\n\tcolor_picker.click();\n\tcolor_picker.addEventListener(\"change\", async e => {\n\t\tlet color = color_picker.value;\n\t\tif (color.charAt(0) !== \"#\") {\n\t\t\tconst rgb = color.match(/\\d+/g);\n\t\t\tconst r = rgb[0];\n\t\t\tconst g = rgb[1];\n\t\t\tconst b = rgb[2];\n\t\t\tcolor = \"#\" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);\n\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tsettings[\"window\"][\"winAccent\"] = color;\n\t\t\twindow.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\t\t\twindow.parent.dispatchEvent(new Event(\"upd-accent\"));\n\t\t} else {\n\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tsettings[\"window\"][\"winAccent\"] = color;\n\t\t\twindow.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\t\t\twindow.parent.dispatchEvent(new Event(\"upd-accent\"));\n\t\t}\n\t\twinAccent.style.setProperty(\"--accent\", color);\n\t\twinAccent.classList.add(\"group\", \"cursor-pointer\");\n\t\twinAccent.addEventListener(\"mousedown\", winAccentPrev);\n\t});\n});\n\nasync function convertTBSIF() {\n\tconst input = document.createElement(\"input\");\n\tinput.type = \"file\";\n\tinput.accept = \".tbs\";\n\tinput.onchange = async () => {\n\t\tlet file = input.files[0];\n\t\tlet reader = new FileReader();\n\t\treader.onload = async () => {\n\t\t\tlet tbs_config = reader.result;\n\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tlet syssettings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.parent.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tif (tbs_config.theme && tbs_config.theme !== \"default\") {\n\t\t\t\tsyssettings.theme = tbs_config.theme;\n\t\t\t}\n\t\t\tif (tbs_config.wallpaper && tbs_config.wallpaper !== \"default\" && settings.wallpaper !== \"/assets/wallpapers/1.png\") {\n\t\t\t\tsettings.wallpaper = tbs_config.wallpaper;\n\t\t\t}\n\t\t\tif (tbs_config.wallpaperFill && tbs_config.wallpaperFill !== \"default\") {\n\t\t\t\tsettings.wallpaperMode = tbs_config.wallpaperFill === \"contain\" ? \"cover\" : \"contain\";\n\t\t\t}\n\t\t\tif (tbs_config.shadow === \"yes\") {\n\t\t\t\tsettings[\"system-blur\"] = true;\n\t\t\t}\n\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.parent.tb.user.username()}/settings.json`, JSON.stringify(settings, null, 2), \"utf8\");\n\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(syssettings, null, 2), \"utf8\");\n\t\t\tparent.window.location.reload();\n\t\t};\n\t\treader.readAsText(file);\n\t};\n\tinput.click();\n}\n\nconst animationCheckbox = document.querySelector(\".eruda-check\");\nconst eruda = () => {\n\tconst realCheckbox = animationCheckbox.querySelector(\"input[type='checkbox']\");\n\tconst checkIcon = animationCheckbox.querySelector(\".checkIcon\");\n\tconst setState = enabled => {\n\t\trealCheckbox.checked = enabled;\n\t\tif (enabled) {\n\t\t\tcheckIcon.classList.remove(\"opacity-0\", \"scale-85\");\n\t\t\tlocalStorage.setItem(\"eruda\", \"true\");\n\t\t} else {\n\t\t\tcheckIcon.classList.add(\"opacity-0\", \"scale-85\");\n\t\t\tlocalStorage.removeItem(\"eruda\");\n\t\t}\n\t};\n\tsetState(localStorage.getItem(\"eruda\") === \"true\");\n\tanimationCheckbox.addEventListener(\"mousedown\", () => {\n\t\tsetState(!realCheckbox.checked);\n\t});\n};\neruda();\n\nconst windowOptimizationsCheckbox = document.querySelector(\".window-optimizations-check\");\nconst setupWindowOptimizations = async () => {\n\tconst realCheckbox = windowOptimizationsCheckbox.querySelector(\"input[type='checkbox']\");\n\tconst checkIcon = windowOptimizationsCheckbox.querySelector(\".checkIcon\");\n\tconst setState = async (enabled, forceWrite = false) => {\n\t\trealCheckbox.checked = enabled;\n\t\tif (enabled) {\n\t\t\tcheckIcon.classList.remove(\"opacity-0\", \"scale-85\");\n\t\t} else {\n\t\t\tcheckIcon.classList.add(\"opacity-0\", \"scale-85\");\n\t\t}\n\t\ttry {\n\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tif (forceWrite || settings.windowOptimizations !== enabled) {\n\t\t\t\tsettings.windowOptimizations = enabled;\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings, null, 2), \"utf8\");\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (forceWrite) {\n\t\t\t\tconst base = { windowOptimizations: enabled };\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(base, null, 2), \"utf8\");\n\t\t\t}\n\t\t}\n\t};\n\ttry {\n\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\tsetState(settings.windowOptimizations ?? true, false);\n\t} catch (err) {\n\t\tconsole.error(\"Failed to load window optimization settings:\", err);\n\t\tsetState(true, false);\n\t}\n\twindowOptimizationsCheckbox.addEventListener(\"mousedown\", async () => {\n\t\tawait setState(!realCheckbox.checked, true);\n\t});\n};\nsetupWindowOptimizations();\n\nconst fpsCounterCheckbox = document.querySelector(\".fps-counter-check\");\nconst setupFPSCounter = async () => {\n\tconst realCheckbox = fpsCounterCheckbox.querySelector(\"input[type='checkbox']\");\n\tconst checkIcon = fpsCounterCheckbox.querySelector(\".checkIcon\");\n\tconst setState = async (enabled, forceWrite = false) => {\n\t\trealCheckbox.checked = enabled;\n\t\tif (enabled) {\n\t\t\tcheckIcon.classList.remove(\"opacity-0\", \"scale-85\");\n\t\t} else {\n\t\t\tcheckIcon.classList.add(\"opacity-0\", \"scale-85\");\n\t\t}\n\t\ttry {\n\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\tif (forceWrite || settings.showFPS !== enabled) {\n\t\t\t\tsettings.showFPS = enabled;\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings, null, 2), \"utf8\");\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (forceWrite) {\n\t\t\t\tconst base = { showFPS: enabled };\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(base, null, 2), \"utf8\");\n\t\t\t}\n\t\t}\n\t\twindow.parent.dispatchEvent(new CustomEvent(\"settings-changed\", { detail: { showFPS: enabled } }));\n\t};\n\n\ttry {\n\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\tsetState(settings.showFPS ?? false, false);\n\t} catch (err) {\n\t\tconsole.error(\"Failed to load FPS counter settings:\", err);\n\t\tsetState(false, false);\n\t}\n\n\tfpsCounterCheckbox.addEventListener(\"mousedown\", async () => {\n\t\tawait setState(!realCheckbox.checked, true);\n\t});\n};\nsetupFPSCounter();\n\nconst realCheckbox = animationCheckbox.querySelector(\"input[type='checkbox']\");\nrealCheckbox.checked = !realCheckbox.checked;\nconst checkIcon = animationCheckbox.querySelector(\".checkIcon\");\nif (realCheckbox.checked) {\n\tcheckIcon.classList.remove(\"opacity-0\", \"scale-85\");\n} else {\n\tcheckIcon.classList.add(\"opacity-0\", \"scale-85\");\n}\n"
  },
  {
    "path": "public/apps/settings.tapp/index.json",
    "content": "{\n\t\"name\": \"Settings\",\n\t\"version\": \"1.0.0\",\n\t\"config\": {\n\t\t\"title\": \"Settings\",\n\t\t\"src\": \"/fs/apps/system/settings.tapp/index.html\",\n\t\t\"icon\": \"/fs/apps/system/settings.tapp/icon.svg\",\n\t\t\"single\": true\n\t}\n}\n"
  },
  {
    "path": "public/apps/settings.tapp/island.js",
    "content": "const appisland = window.parent.document.querySelector(\".app_island\").clientHeight + 12;\n\ntb_island.addControl({\n\ttext: \"File\",\n\tappname: \"Settings\",\n\tid: \"settings_file\",\n\tclick: () => {\n\t\ttb.contextmenu.create({\n\t\t\tx: 6,\n\t\t\ty: appisland,\n\t\t\tiframe: false,\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\ttext: \"Import Settings\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tconst input = document.createElement(\"input\");\n\t\t\t\t\t\tinput.type = \"file\";\n\t\t\t\t\t\tinput.accept = \".json\";\n\t\t\t\t\t\tinput.onchange = async () => {\n\t\t\t\t\t\t\tlet file = input.files[0];\n\t\t\t\t\t\t\tlet reader = new FileReader();\n\t\t\t\t\t\t\treader.onload = async () => {\n\t\t\t\t\t\t\t\tlet settings = JSON.parse(reader.result);\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings), \"utf8\");\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\treader.readAsText(file);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tinput.click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Export Settings\",\n\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\t\t\tlet data = JSON.stringify(settings);\n\t\t\t\t\t\tlet blob = new Blob([data], { type: \"application/json\" });\n\t\t\t\t\t\tlet url = URL.createObjectURL(blob);\n\t\t\t\t\t\tlet a = document.createElement(\"a\");\n\t\t\t\t\t\ta.href = url;\n\t\t\t\t\t\ta.download = \"settings.json\";\n\t\t\t\t\t\ta.click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t},\n});\n\ntb_island.addControl({\n\ttext: \"View\",\n\tappname: \"Settings\",\n\tid: \"settings_view\",\n\tclick: () => {\n\t\ttb.contextmenu.create({\n\t\t\tx: 6,\n\t\t\ty: appisland,\n\t\t\tiframe: false,\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\ttext: \"Appearance\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tdocument.querySelector(`[data-category=\"appearance\"]`).click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Window\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tdocument.querySelector(`[data-category=\"window\"]`).click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Networking\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tdocument.querySelector(`[data-category=\"networking\"]`).click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Other\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tdocument.querySelector(`[data-category=\"other\"]`).click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t},\n});\n\ntb_island.addControl({\n\ttext: \"Help\",\n\tappname: \"Settings\",\n\tid: \"help\",\n\tclick: () => {\n\t\twindow.open(\"https://github.com/TerbiumOS/web-v2/blob/main/docs/README.md\", \"_blank\");\n\t},\n});\n"
  },
  {
    "path": "public/apps/settings.tapp/message.js",
    "content": "window.addEventListener(\"message\", function (event) {\n\tvar data;\n\tif (typeof event.data === \"object\") {\n\t\ttry {\n\t\t\tdata = event.data;\n\t\t} catch (e) {\n\t\t\tconsole.warn(e);\n\t\t}\n\t} else {\n\t\ttry {\n\t\t\tdata = JSON.parse(event.data);\n\t\t} catch {\n\t\t\tconsole.warn(\"No Message\");\n\t\t}\n\t}\n\tif (data && document.querySelector(`[data-category=\"${data.page}\"]`)) {\n\t\tlet button = document.querySelector(`[data-category=\"${data.page}\"]`);\n\t\tbutton.click();\n\t}\n});\n"
  },
  {
    "path": "public/apps/settings.tapp/radio.css",
    "content": "body[theme=\"light\"] button.radio {\n\tcolor: #ffffff88;\n}\n\nbody[theme=\"dark\"] button.radio {\n\tcolor: #ffffff88;\n}\n\nbutton.radio {\n\tdisplay: flex;\n\tgap: 6px;\n\tbackground-color: transparent;\n\tborder: none;\n\tfont-size: 16px;\n\tfont-family: Inter;\n\tfont-weight: 700;\n\tline-height: 16px;\n\tcursor: var(--cursor-pointer);\n\ttransition: 150ms ease-in-out;\n}\n\nbody[theme=\"light\"] button.radio span.radio {\n\tbackground-color: #ffffff78;\n}\n\nbutton.radio span.radio {\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 50%;\n\ttransition: 150ms ease-in-out;\n}\n\nbutton.radio.selected {\n\tcolor: #ffffff !important;\n}\n\nbutton.radio.selected span.radio {\n\tbackground-color: #ffffff !important;\n}\n\nbutton.radio.selected span.radio span.radio-check {\n\tbackground-color: #4e8bff;\n}\n\nbutton.radio span.radio span.radio-check {\n\twidth: 10px;\n\theight: 10px;\n\tborder-radius: 50%;\n\tbackground-color: #00000050;\n\ttransition: 150ms ease-in-out;\n}\n"
  },
  {
    "path": "public/apps/settings.tapp/select.css",
    "content": ".select {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\twidth: min-content;\n}\n\n.select .select-title {\n\tbackground-color: #ffffff10;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n}\n\n.select .select-title:hover {\n\tbackground-color: #ffffff36;\n}\n\n.select .select-title {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 20px;\n\tfont-size: 18px;\n\tfont-weight: 800;\n\tline-height: 18px;\n\tpadding: 6px 6px 6px 10px;\n\tcolor: #ffffff;\n\tborder-radius: 6px;\n\tbackdrop-filter: blur(100px);\n\tcursor: var(--cursor-pointer);\n\ttransition: 150ms ease-in-out;\n}\n\n.select .select-title .text {\n\twidth: max-content;\n}\n\n.select .select-title .icon {\n\twidth: 24px;\n\theight: auto;\n}\n\n.select .options {\n\tbackground-color: #ffffff10;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n}\n\n.select .options {\n\tposition: absolute;\n\ttop: calc(100% + 6px);\n\tz-index: 9;\n\tleft: 0;\n\twidth: 100%;\n\tmax-height: 160px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tborder-radius: 6px;\n\toverflow: auto;\n\ttransition: 150ms ease-in-out;\n}\n\n.select .options::before {\n\tcontent: \"\\00A0\";\n\tdisplay: block;\n\tposition: absolute;\n\ttop: 1px;\n\tbottom: 1px;\n\tleft: 1px;\n\tright: 1px;\n\tz-index: -1;\n\tborder-radius: 4px;\n\tbackdrop-filter: blur(14px);\n}\n\n.select .options .option {\n\tpadding: 10px 10px;\n\tcursor: var(--cursor-pointer);\n\tfont-weight: 650;\n\ttransition: 150ms ease-in-out;\n}\n\n.select .options .option:hover {\n\tbackground-color: #ffffff36;\n}\n\n.select .options:not(.open) {\n\topacity: 0;\n\tpointer-events: none;\n\theight: 0px;\n}\n"
  },
  {
    "path": "public/apps/settings.tapp/select.js",
    "content": "const selects = document.querySelectorAll(\".select\");\n\nselects.forEach(select => {\n\tconst intiator = select.querySelector(\".select-title\");\n\tconst select_options = select.querySelector(\".options\");\n\n\tintiator.addEventListener(\"click\", e => {\n\t\tdocument.querySelectorAll(\".options\").forEach(option => {\n\t\t\tif (option !== select_options) {\n\t\t\t\toption.classList.remove(\"open\");\n\t\t\t}\n\t\t});\n\t\tselect_options.classList.toggle(\"open\");\n\t\tconst options = select_options.querySelectorAll(\".option\");\n\t\toptions.forEach(option => {\n\t\t\toption.addEventListener(\"click\", () => {\n\t\t\t\tintiator.querySelector(\".text\").innerHTML = option.getAttribute(\"value\");\n\t\t\t\tselect_options.classList.remove(\"open\");\n\t\t\t\tif (select.getAttribute(\"action\") === \"fs\") {\n\t\t\t\t\tif (select.getAttribute(\"action-for\") === \"wallpaper-fill\") {\n\t\t\t\t\t\tswitch (option.getAttribute(\"value\").toLowerCase()) {\n\t\t\t\t\t\t\tcase \"cover\":\n\t\t\t\t\t\t\t\ttb.desktop.wallpaper.cover();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"contain\":\n\t\t\t\t\t\t\t\ttb.desktop.wallpaper.contain();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"stretch\":\n\t\t\t\t\t\t\t\ttb.desktop.wallpaper.stretch();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (select.getAttribute(\"action-for\") === \"proxy\") {\n\t\t\t\t\t\tswitch (option.getAttribute(\"value\").toLowerCase()) {\n\t\t\t\t\t\t\tcase \"Ultraviolet\":\n\t\t\t\t\t\t\t\ttb.proxy.set(\"Ultraviolet\");\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"Scramjet\":\n\t\t\t\t\t\t\t\ttb.proxy.set(\"Scramjet\");\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (select.getAttribute(\"action-for\") === \"show-seconds\") {\n\t\t\t\t\t\tswitch (option.getAttribute(\"value\").toLowerCase()) {\n\t\t\t\t\t\t\tcase \"no\":\n\t\t\t\t\t\t\t\twindow.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\t\t\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\t\t\tsettings[\"times\"][\"showSeconds\"] = false;\n\t\t\t\t\t\t\t\t\twindow.tb.fs.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, JSON.stringify(settings));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"yes\":\n\t\t\t\t\t\t\t\twindow.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\t\t\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\t\t\tsettings[\"times\"][\"showSeconds\"] = true;\n\t\t\t\t\t\t\t\t\twindow.tb.fs.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, JSON.stringify(settings));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (select.getAttribute(\"action-for\") === \"24h-12h\") {\n\t\t\t\t\t\tswitch (option.getAttribute(\"value\").toLowerCase()) {\n\t\t\t\t\t\t\tcase \"no\":\n\t\t\t\t\t\t\t\twindow.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\t\t\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\t\t\tsettings[\"times\"][\"format\"] = \"12h\";\n\t\t\t\t\t\t\t\t\twindow.tb.fs.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, JSON.stringify(settings));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase \"yes\":\n\t\t\t\t\t\t\t\twindow.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\t\t\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\t\t\tsettings[\"times\"][\"format\"] = \"24h\";\n\t\t\t\t\t\t\t\t\twindow.tb.fs.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, JSON.stringify(settings));\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (select.getAttribute(\"action-for\") === \"location-state\") {\n\t\t\t\t\t\twindow.tb.fs.readFile(\"/system/etc/terbium/settings.json\", \"utf8\", (err, data) => {\n\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\tsettings[\"location\"][\"state\"] = option.getAttribute(\"value\");\n\t\t\t\t\t\t\twindow.tb.fs.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(settings));\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (select.getAttribute(\"action-for\") === \"temperature-unit\") {\n\t\t\t\t\t\twindow.tb.fs.readFile(\"/system/etc/terbium/settings.json\", \"utf8\", (err, data) => {\n\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\tsettings[\"weather\"][\"unit\"] = option.getAttribute(\"value\");\n\t\t\t\t\t\t\twindow.tb.fs.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(settings));\n\t\t\t\t\t\t\twindow.parent.dispatchEvent(new Event(\"updWeather\"));\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (select.getAttribute(\"action-for\") === \"wmx\") {\n\t\t\t\t\t\twindow.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\tsettings[\"window\"][\"alwaysMaximized\"] = option.getAttribute(\"value\").toLowerCase() === \"yes\" ? true : false;\n\t\t\t\t\t\t\twindow.tb.fs.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, JSON.stringify(settings));\n\t\t\t\t\t\t\twindow.parent.dispatchEvent(new Event(\"upd-accent\"));\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (select.getAttribute(\"action-for\") === \"wfs\") {\n\t\t\t\t\t\twindow.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\", (err, data) => {\n\t\t\t\t\t\t\tif (err) return console.log(err);\n\t\t\t\t\t\t\tlet settings = JSON.parse(data);\n\t\t\t\t\t\t\tsettings[\"window\"][\"alwaysFullscreen\"] = option.getAttribute(\"value\").toLowerCase() === \"yes\" ? true : false;\n\t\t\t\t\t\t\twindow.tb.fs.writeFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, JSON.stringify(settings));\n\t\t\t\t\t\t\twindow.parent.dispatchEvent(new Event(\"upd-accent\"));\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "public/apps/task manager.tapp/index.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(/fonts/Inter.ttf);\n}\n\nh1 {\n\tfont-family: Inter;\n\tfont-weight: 700;\n}\n\nh4 {\n\tmargin-top: 4px;\n\tmargin-bottom: 4px;\n}\n\n.image-container {\n\twidth: 100px;\n}\n\n.centered-image {\n\tmax-width: 100%;\n\tmax-height: 100%;\n}\n\na {\n\tcolor: #5088ff;\n\ttext-decoration: none;\n}\n\na:hover {\n\ttext-decoration: underline;\n}\n\n.opt {\n\twidth: 100%;\n\theight: 100%;\n\tmargin-top: 35px;\n}\n\n.opt.disabled {\n\tdisplay: none;\n}\n\n.topnav {\n\tdisplay: flex;\n\tflex-direction: row;\n\tgap: 5px;\n}\n\n.topnav .sec {\n\tmargin-top: 5px;\n\tbackground-color: #ffffff24;\n\tborder-radius: 8px;\n\tdisplay: flex;\n\tflex-direction: row;\n\tjustify-content: center;\n\tpadding: 6px 16px;\n}\n\n.opt .topbr {\n\t/*display: flex;\n    position: relative;\n    flex-direction: row;*/\n\twidth: 100%;\n}\n\n.opt .topbr h2 {\n\tcolor: #c0bdc1;\n\tfont-size: 24px;\n}\n\n.opt .topbr h1 {\n\tfont-size: 32px;\n}\n\n.opt .topbr .m {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-self: flex-start;\n\tjustify-content: flex-start;\n\ttext-align: start;\n}\n\n.opt .topbr .sub {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-self: flex-end;\n\tjustify-content: flex-end;\n\ttext-align: end;\n\tmargin-top: -50px;\n\tgap: 50px;\n\tmargin-right: 25px;\n}\n\n.opt .apps {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.opt .apps .apl {\n\tdisplay: flex;\n\tgap: 25px;\n\talign-items: center;\n\twidth: 100%;\n}\n\n.opt .apps .apl .end-text {\n\tmargin-left: auto;\n\tdisplay: flex;\n\tgap: 25px;\n}\n\n.opt .apps .apl .end-text .btn {\n\tmargin-right: 10px;\n}\n\n.opt .apps .apl img {\n\twidth: 25px;\n\theight: 25px;\n}\n\n::-webkit-scrollbar {\n\twidth: 8px;\n\theight: 8px;\n}\n\n::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff28;\n\tborder-radius: 8px;\n}\n\n::-webkit-scrollbar-track {\n\tbackground-color: #ffffff10;\n\tborder-radius: 8px;\n}\n\n.navbtn span {\n\tbackdrop-filter: blur(100px) contrast(0.8) brightness(0.8);\n\tz-index: 9999;\n\tbackground-color: #ffffff28;\n}\n"
  },
  {
    "path": "public/apps/task manager.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html class=\"size-full\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Task Manager</title>\n    <link rel=\"stylesheet\" href=\"./index.css\">\n    <style>\n        @font-face {\n            font-family: Inter;\n            src: url(/fonts/Inter.ttf);\n        }\n    </style>\n</head>\n    <body class=\"p-4 m-0 text-white font-[Inter] relative flex flex-col size-full gap-4\">\n        <div class=\"flex p-2 bg-[rgba(255,255,255,0.16)] rounded-lg w-max gap-0.5\">\n            <button class=\"navbtn p-1.5 hover:bg-[#ffffff28] rounded-lg relative flex items-center select-none cursor-pointer\" onclick=\"loadPane('tasks')\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                    <path fill-rule=\"evenodd\" d=\"M12 6.75a5.25 5.25 0 0 1 6.775-5.025.75.75 0 0 1 .313 1.248l-3.32 3.319c.063.475.276.934.641 1.299.365.365.824.578 1.3.64l3.318-3.319a.75.75 0 0 1 1.248.313 5.25 5.25 0 0 1-5.472 6.756c-1.018-.086-1.87.1-2.309.634L7.344 21.3A3.298 3.298 0 1 1 2.7 16.657l8.684-7.151c.533-.44.72-1.291.634-2.309A5.342 5.342 0 0 1 12 6.75ZM4.117 19.125a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75h-.008a.75.75 0 0 1-.75-.75v-.008Z\" clip-rule=\"evenodd\" />\n                </svg>\n                <span class=\"absolute opacity-0 text-[14px] font-[600] w-max py-1 px-2 border border-[#ffffff28] rounded-md bg-[#ffffff10] duration-150 pointer-events-none\">Tasks</span>\n            </button>\n            <button class=\"navbtn p-1.5 hover:bg-[#ffffff28] rounded-lg relative flex items-center select-none cursor-pointer\" onclick=\"loadPane('sys')\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-6\">\n                    <polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\"/>\n                </svg>\n                <span class=\"absolute opacity-0 text-[14px] font-[600] w-max py-1 px-2 -left-1/2 border border-[#ffffff28] rounded-md bg-[#ffffff10] duration-150 pointer-events-none\">System Info</span>\n            </button>\n            <button class=\"navbtn p-1.5 hover:bg-[#ffffff28] rounded-lg relative flex items-center select-none cursor-pointer\" onclick=\"loadPane('startup')\">\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-6\">\n                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z\" />\n                </svg>\n                <span class=\"absolute opacity-0 text-[14px] font-[600] w-max py-1 px-2 -left-1/2 border border-[#ffffff28] rounded-md bg-[#ffffff10] duration-150 pointer-events-none\">Startup Processes</span>\n            </button>\n        </div>\n        <div class=\"relative size-full\">\n            <div class=\"absolute w-full duration-150\" id=\"appl\">\n                <table class=\"w-full bg-[#00000068] rounded-[10px] border-collapse overflow-hidden\">\n                    <thead class=\"font-[900] bg-[#ffffff28]\">\n                        <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">Name</th>\n                        <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">Memory</th>\n                        <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">PID</th>\n                        <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">State</th>\n                        <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\"></th>\n                    </thead>\n                    <tbody>\n                        <td class=\"text-sm\">Loading Tasks (This will take a few seconds)</td>\n                        <!-- <tr class=\"py-2.5 px-3.5\">\n                            <th class=\"text-left py-2.5 pl-3.5 pr-[100px]\">Settings</th>\n                            <td class=\"px-3.5\">2 MB</td>\n                            <td class=\"px-3.5\">ba11d83a-5372-4aae-8a83-bbab718fa10c</td>\n                            <td class=\"px-3.5\">1</td>\n                            <td class=\"px-3.5\">Active</td>\n                        </tr> -->\n                    </tbody>\n                </table>\n            </div>\n            <div class=\"absolute opacity-0 pointer-events-none duration-150\" id=\"sysinf\">\n                <h1>System Information (estimated)</h1>\n                <div class=\"flex gap-1 items-center\">\n                    <img class=\"w-[50px] h-auto\" src=\"./cpu.svg\" />\n                    <p class=\"font-semibold\" id=\"cpu\"></p>\n                </div>\n                <div class=\"flex gap-1 items-center\">\n                    <img class=\"w-[50px] h-auto\" src=\"./ram.svg\" />\n                    <p class=\"font-semibold\" id=\"ram\"></p>\n                </div>\n                <div class=\"flex gap-1 items-center\">\n                    <img class=\"w-[50px] h-auto\" src=\"./ssd.svg\" />\n                    <p class=\"font-semibold\" id=\"ssd\"></p>\n                </div>\n                <div class=\"flex gap-1 items-center\">\n                    <img class=\"w-[50px] h-auto\" src=\"./gpu.svg\" />\n                    <p class=\"font-semibold\" id=\"gpu\"></p>\n                </div>\n            </div>\n            <div class=\"absolute opacity-0 pointer-events-none duration-150 p-2 w-full h-full\" id=\"startup\">\n                <div class=\"flex items-center justify-between mb-2\">\n                    <h1 class=\"text-xl font-bold\">Startup Processes</h1>\n                    <div class=\"flex items-center gap-2\">\n                        <select id=\"startupScopeSelect\" class=\"rounded-md bg-[#ffffff0a] border border-[#ffffff22] text-[#ffffff] px-2 py-1\">\n                            <option value=\"all\">All</option>\n                            <option value=\"System\">System</option>\n                            <option id=\"userOption\">User</option>\n                        </select>\n                        <script>\n                            (function() {\n                                const opt = document.getElementById('userOption');\n                                if (opt) opt.value = sessionStorage.getItem('currAcc') || 'Guest';\n                            })();\n                        </script>\n                        <button id=\"refreshStartup\" class=\"w-max py-1 px-3 rounded-md font-semibold bg-[#ffffff10] hover:bg-[#ffffff20] duration-150\">Refresh</button>\n                        <button id=\"addStartupBtn\" class=\"w-max py-1 px-3 rounded-md font-semibold bg-[#ffffff10] hover:bg-[#ffffff20] duration-150\">Add</button>\n                    </div>\n                </div>\n                <div id=\"startupTableContainer\" class=\"overflow-auto\">\n                    <table class=\"w-full bg-[#00000068] rounded-[10px] border-collapse overflow-hidden\">\n                        <thead class=\"font-[900] bg-[#ffffff28]\">\n                            <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">Name</th>\n                            <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">Scope</th>\n                            <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">Command</th>\n                            <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">Installed By</th>\n                            <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\">Enabled</th>\n                            <th class=\"text-left py-2.5 px-3.5 cursor-default select-none\"></th>\n                        </thead>\n                        <tbody id=\"startupTbody\">\n                            <tr>\n                                <td class=\"text-sm\">Loading startup processes...</td>\n                            </tr>\n                        </tbody>\n                    </table>\n                </div>\n            </div>\n        </div>\n        <script src=\"/assets/libs/tailwind.min.js\"></script>\n        <script src=\"index.js\"></script>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/task manager.tapp/index.js",
    "content": "const navbuttons = document.querySelectorAll(\".navbtn\");\n\nnavbuttons.forEach(btn => {\n\tconst tooltip = btn.querySelector(\"span\");\n\tlet btnWidth = btn.getBoundingClientRect().width;\n\ttooltip.classList.add(`top-[${btnWidth + 4}px]`);\n\n\tbtn.addEventListener(\n\t\t\"mouseover\",\n\t\t() => {\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (btn.matches(\":hover\")) {\n\t\t\t\t\ttooltip.classList.remove(\"opacity-0\");\n\t\t\t\t\ttooltip.classList.remove(`top-[${btnWidth + 4}px]`);\n\t\t\t\t\ttooltip.classList.add(`top-[${btnWidth + 14}px]`);\n\t\t\t\t\tbtn.addEventListener(\"mouseleave\", () => {\n\t\t\t\t\t\ttooltip.classList.add(\"opacity-0\");\n\t\t\t\t\t\ttooltip.classList.remove(`top-[${btnWidth + 14}px]`);\n\t\t\t\t\t\ttooltip.classList.add(`top-[${btnWidth + 4}px]`);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t},\n\t\t\"once\",\n\t);\n});\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n\tgetTasks();\n\tsetInterval(getTasks, 2500);\n});\n\nfunction loadPane(val) {\n\tconst sys = document.getElementById(\"sysinf\");\n\tconst main = document.getElementById(\"appl\");\n\tconst startup = document.getElementById(\"startup\");\n\tif (val === \"sys\") {\n\t\tsys.classList.remove(\"opacity-0\", \"pointer-events-none\");\n\t\tmain.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t\tstartup.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t\tgetSpecs();\n\t} else if (val === \"startup\") {\n\t\tstartup.classList.remove(\"opacity-0\", \"pointer-events-none\");\n\t\tmain.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t\tsys.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t\tgetStartups();\n\t} else {\n\t\tmain.classList.remove(\"opacity-0\", \"pointer-events-none\");\n\t\tsys.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t\tstartup.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t}\n}\n\nfunction getSpecs() {\n\tconst cputxt = document.getElementById(\"cpu\");\n\tconst memtxt = document.getElementById(\"ram\");\n\tconst ssdtxt = document.getElementById(\"ssd\");\n\tconst gputxt = document.getElementById(\"gpu\");\n\tlet mem = navigator.deviceMemory ? navigator.deviceMemory + \"GB\" + \" of ram\" : \"Not Available\";\n\tlet canvas = document.createElement(\"canvas\");\n\tlet gl = canvas.getContext(\"webgl\") || canvas.getContext(\"experimental-webgl\");\n\tif (!gl) {\n\t\tconsole.error(\"%cGPU%c: Information not available\", `color: ${accent}`, \"color: #b6b6b6\");\n\t\treturn;\n\t}\n\tlet gpuName;\n\tlet dbgRenderInfo = gl.getExtension(\"WEBGL_debug_renderer_info\");\n\tif (dbgRenderInfo) {\n\t\tlet rndr = gl.getParameter(dbgRenderInfo.UNMASKED_RENDERER_WEBGL);\n\t\tlet regex = /ANGLE \\(.+?,\\s*(.+?) \\(/;\n\t\tlet match = rndr.match(regex);\n\t\tgpuName = match ? match[1] : \"Not Available\";\n\t}\n\tnavigator.storage.estimate().then(estimate => {\n\t\tconst totalSize = estimate.quota;\n\t\tconst usedSize = estimate.usage;\n\t\tconst usedPercentage = (usedSize / totalSize) * 100;\n\t\tlet formattedUsedSize, formattedTotalSize;\n\t\tif (usedSize >= 1024 * 1024 * 1024) {\n\t\t\tformattedUsedSize = `${(usedSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t\t} else {\n\t\t\tformattedUsedSize = `${(usedSize / (1024 * 1024)).toFixed(2)} MB`;\n\t\t}\n\t\tif (totalSize >= 1024 * 1024 * 1024) {\n\t\t\tformattedTotalSize = `${(totalSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t\t} else {\n\t\t\tformattedTotalSize = `${Math.round((totalSize / (1024 * 1024)).toFixed(2))} MB`;\n\t\t}\n\t\tssdtxt.textContent = `${formattedUsedSize} of ${formattedTotalSize}`;\n\t});\n\tlet cpuCors = navigator.hardwareConcurrency;\n\tcputxt.textContent = `${cpuCors} Logical Cores (${Math.floor(cpuCors / 2)} Cores ${cpuCors} threads)`;\n\tlet memoryUsed = window.tman_info && window.tman_info.bytes ? window.tman_info.bytes : 0;\n\tlet formattedMemoryUsed;\n\tif (memoryUsed >= 1024 * 1024 * 1024) {\n\t\tformattedMemoryUsed = `${(memoryUsed / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t} else {\n\t\tformattedMemoryUsed = `${(memoryUsed / (1024 * 1024)).toFixed(2)} MB`;\n\t}\n\tmemtxt.textContent = `${formattedMemoryUsed} of ${mem}`;\n\tgputxt.textContent = gpuName;\n}\n\nasync function getTasks() {\n\tlet mem;\n\tif (\"measureUserAgentSpecificMemory\" in window.parent.performance) {\n\t\tmem = await window.parent.performance.measureUserAgentSpecificMemory();\n\t} else {\n\t\tmem = { bytes: 0, breakdown: [] };\n\t}\n\twindow.tman_info = mem;\n\tconst windows = window.parent.tb.process.list();\n\tlet main = document.querySelector(\"tbody\");\n\tlet existingEntries = main.querySelectorAll(\"tr\");\n\tlet currentWinIds = Array.from(existingEntries).map(entry => entry.getAttribute(\"win-id\"));\n\tconst currentIdsSet = new Set(currentWinIds);\n\tObject.values(windows).forEach(win => {\n\t\tconst winID = win.id;\n\t\tif (currentIdsSet.has(winID)) {\n\t\t\tcurrentIdsSet.delete(winID);\n\t\t\treturn;\n\t\t}\n\t\tconst sysRegex = /^Terbium (Alexa Desktop Experience|Service Worker|Node\\.js Runtime)$/;\n\t\tconst tr = document.createElement(\"tr\");\n\t\ttr.classList.add(\"hover:bg-[#ffffff18]\", \"duration-150\", \"ease-in-out\", \"px-2.5\");\n\t\ttr.setAttribute(\"win-id\", winID);\n\n\t\tconst thName = document.createElement(\"th\");\n\t\tthName.textContent = typeof win.name === \"string\" ? win.name : win.name.text;\n\t\tthName.classList.add(\"text-left\", \"py-2.5\", \"pl-3.5\", \"pr-[100px]\");\n\t\tconst tdMemory = document.createElement(\"td\");\n\t\tlet memEntry = null;\n\t\tif (mem && Array.isArray(mem.breakdown)) {\n\t\t\tmemEntry = mem.breakdown.find(entry =>\n\t\t\t\tentry.attribution.some(attr => {\n\t\t\t\t\treturn attr.container && attr.container.src === win.src;\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (memEntry && typeof memEntry.bytes === \"number\") {\n\t\t\ttdMemory.textContent = `${(memEntry.bytes / (1024 * 1024)).toFixed(2)} MB`;\n\t\t} else if (sysRegex.test(win.name)) {\n\t\t\ttdMemory.textContent = \"System Process\";\n\t\t} else {\n\t\t\ttdMemory.textContent = \"N/A\";\n\t\t}\n\n\t\tconst tdPID = document.createElement(\"td\");\n\t\ttdPID.textContent = win.pid;\n\n\t\tconst tdState = document.createElement(\"td\");\n\t\tconst stateText = document.createElement(\"span\");\n\t\tif (win.pid === window.parent.tb.window.getId()) {\n\t\t\tstateText.textContent = \"Active\";\n\t\t} else if (sysRegex.test(win.name)) {\n\t\t\tstateText.textContent = \"Active\";\n\t\t} else {\n\t\t\tstateText.textContent = \"Idle\";\n\t\t}\n\t\ttdState.appendChild(stateText);\n\n\t\tconst tdActions = document.createElement(\"td\");\n\t\tconst btnEnd = document.createElement(\"button\");\n\t\tbtnEnd.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\"><path fill-rule=\"evenodd\" d=\"M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" /></svg>`;\n\t\tbtnEnd.onclick = () => {\n\t\t\twindow.parent.tb.process.kill(win.pid);\n\t\t};\n\n\t\ttr.appendChild(thName);\n\t\ttr.appendChild(tdMemory);\n\t\ttr.appendChild(tdPID);\n\t\ttr.appendChild(tdState);\n\t\ttr.appendChild(tdActions);\n\t\ttdActions.appendChild(btnEnd);\n\t\tmain.appendChild(tr);\n\t});\n\texistingEntries.forEach(entry => {\n\t\tconst winID = entry.getAttribute(\"win-id\");\n\t\tif (!Object.values(windows).some(win => win.id === winID)) {\n\t\t\tmain.removeChild(entry);\n\t\t}\n\t});\n}\n\nasync function getStartups() {\n\tconst tbody = document.getElementById(\"startupTbody\");\n\tif (!tbody) return;\n\ttbody.innerHTML = \"<tr><td class='text-sm'>Loading startup processes...</td></tr>\";\n\tlet data;\n\ttry {\n\t\tdata = await window.parent.tb.system.startup.list();\n\t} catch (err) {\n\t\tconsole.error(\"Failed to load startup list:\", err);\n\t\ttbody.innerHTML = \"<tr><td class='text-sm'>Failed to load startup processes</td></tr>\";\n\t\treturn;\n\t}\n\tconst scopeFilterEl = document.getElementById(\"startupScopeSelect\");\n\tconst scopeFilter = scopeFilterEl ? scopeFilterEl.value : \"all\";\n\tconst entries = [];\n\tfor (const scope of Object.keys(data || {})) {\n\t\tconst procs = data[scope] || {};\n\t\tfor (const name of Object.keys(procs)) {\n\t\t\tconst e = procs[name];\n\t\t\tentries.push({ name, scope, start: e.start, installedby: e.installedby || \"-\", enabled: !!e.enabled });\n\t\t}\n\t}\n\tif (entries.length === 0) {\n\t\ttbody.innerHTML = \"<tr><td class='text-sm'>No Startup processes found</td></tr>\";\n\t\treturn;\n\t}\n\tconst filtered = entries.filter(ent => (scopeFilter === \"all\" ? true : ent.scope === scopeFilter));\n\tif (filtered.length === 0) {\n\t\ttbody.innerHTML = \"<tr><td class='text-sm'>No Startup processes found for selected scope</td></tr>\";\n\t\treturn;\n\t}\n\ttbody.innerHTML = \"\";\n\tfor (const ent of filtered) {\n\t\tconst tr = document.createElement(\"tr\");\n\t\ttr.classList.add(\"hover:bg-[#ffffff18]\", \"duration-150\", \"ease-in-out\", \"px-2.5\");\n\t\tconst thName = document.createElement(\"th\");\n\t\tthName.textContent = ent.name;\n\t\tthName.classList.add(\"text-left\", \"py-2.5\", \"pl-3.5\", \"pr-[100px]\");\n\t\tconst tdScope = document.createElement(\"td\");\n\t\ttdScope.textContent = ent.scope;\n\t\ttdScope.classList.add(\"px-3.5\");\n\t\tconst tdCmd = document.createElement(\"td\");\n\t\ttdCmd.classList.add(\"px-3.5\", \"text-sm\", \"text-[#ffffffb3]\");\n\t\ttdCmd.textContent = ent.start;\n\t\tconst tdInstalled = document.createElement(\"td\");\n\t\ttdInstalled.classList.add(\"px-3.5\");\n\t\ttdInstalled.textContent = ent.installedby;\n\t\tconst tdEnabled = document.createElement(\"td\");\n\t\tconst enabledSpan = document.createElement(\"span\");\n\t\tenabledSpan.textContent = ent.enabled ? \"Yes\" : \"No\";\n\t\ttdEnabled.appendChild(enabledSpan);\n\t\tconst tdActions = document.createElement(\"td\");\n\t\tconst btnToggle = document.createElement(\"button\");\n\t\tbtnToggle.classList.add(\"mr-2\", \"w-max\", \"py-1\", \"px-2\", \"rounded-md\", \"bg-[#ffffff10]\");\n\t\tbtnToggle.textContent = ent.enabled ? \"Disable\" : \"Enable\";\n\t\tbtnToggle.onclick = async () => {\n\t\t\ttry {\n\t\t\t\tif (ent.enabled) {\n\t\t\t\t\tawait window.parent.tb.system.startup.disable(ent.name, ent.scope === \"System\" ? \"System\" : \"User\");\n\t\t\t\t} else {\n\t\t\t\t\tawait window.parent.tb.system.startup.enable(ent.name, ent.scope === \"System\" ? \"System\" : \"User\");\n\t\t\t\t}\n\t\t\t\tawait getStartups();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t}\n\t\t};\n\t\tconst btnRemove = document.createElement(\"button\");\n\t\tbtnRemove.classList.add(\"w-max\", \"py-1\", \"px-2\", \"rounded-md\", \"bg-[#ff000018]\");\n\t\tbtnRemove.textContent = \"Remove\";\n\t\tbtnRemove.onclick = async () => {\n\t\t\twindow.parent.tb.dialog.Select({\n\t\t\t\ttitle: `Remove startup entry '${ent.name}'?`,\n\t\t\t\toptions: [\n\t\t\t\t\t{ text: \"Yes\", value: \"yes\" },\n\t\t\t\t\t{ text: \"No\", value: \"no\" },\n\t\t\t\t],\n\t\t\t\tonOk: async choice => {\n\t\t\t\t\tif (choice === \"yes\") {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait window.parent.tb.system.startup.removeProc(ent.name, ent.scope === \"System\" ? \"System\" : \"User\");\n\t\t\t\t\t\t\tawait getStartups();\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\t\t};\n\t\ttdActions.appendChild(btnToggle);\n\t\ttdActions.appendChild(btnRemove);\n\t\ttr.appendChild(thName);\n\t\ttr.appendChild(tdScope);\n\t\ttr.appendChild(tdCmd);\n\t\ttr.appendChild(tdInstalled);\n\t\ttr.appendChild(tdEnabled);\n\t\ttr.appendChild(tdActions);\n\t\tdocument.getElementById(\"startupTbody\").appendChild(tr);\n\t}\n}\n\nconst refreshBtn = document.getElementById(\"refreshStartup\");\nif (refreshBtn) refreshBtn.addEventListener(\"click\", getStartups);\nconst scopeSelect = document.getElementById(\"startupScopeSelect\");\nif (scopeSelect) scopeSelect.addEventListener(\"change\", getStartups);\nconst addBtn = document.getElementById(\"addStartupBtn\");\nif (addBtn) {\n\taddBtn.addEventListener(\"click\", () => {\n\t\twindow.parent.tb.dialog.Message({\n\t\t\ttitle: \"Enter a name for the startup entry\",\n\t\t\tonOk: name => {\n\t\t\t\tif (!name) return;\n\t\t\t\twindow.parent.tb.dialog.Select({\n\t\t\t\t\ttitle: \"Select scope\",\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{ text: \"System\", value: \"System\" },\n\t\t\t\t\t\t{ text: \"User\", value: \"User\" },\n\t\t\t\t\t],\n\t\t\t\t\tonOk: scope => {\n\t\t\t\t\t\twindow.parent.tb.dialog.Message({\n\t\t\t\t\t\t\ttitle: \"Enter the start command (optional)\",\n\t\t\t\t\t\t\tonOk: async cmd => {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait window.parent.tb.system.startup.addProc(name, scope, cmd || undefined);\n\t\t\t\t\t\t\t\t\tgetStartups();\n\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t});\n}\n"
  },
  {
    "path": "public/apps/task manager.tapp/index.json",
    "content": "{\n\t\"name\": \"Task Manager\",\n\t\"config\": {\n\t\t\"title\": \"Task Manager\",\n\t\t\"icon\": \"/fs/apps/system/task manager.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/task manager.tapp/index.html\"\n\t}\n}\n"
  },
  {
    "path": "public/apps/terminal.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n        <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@xterm/xterm@latest/css/xterm.css\" />\n        <link rel=\"stylesheet\" href=\"./terminal.css\" />\n        <title>terminal</title>\n    </head>\n    <body>\n        <script src=\"https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js\" type=\"module\"></script>\n        <script src=\"./index.js\" type=\"module\"></script>\n        <script src=\"./ssh-util.js\" type=\"module\"></script>\n        <script src=\"./terminal_com.js\"></script>\n        <div id=\"term\">\n        </div>\n    </body>\n</html>"
  },
  {
    "path": "public/apps/terminal.tapp/index.js",
    "content": "import parser from \"https://unpkg.com/yargs-parser@22.0.0/browser.js\";\nimport http from \"https://cdn.jsdelivr.net/npm/isomorphic-git@latest/http/web/index.js\";\nimport git from \"https://cdn.jsdelivr.net/npm/isomorphic-git@latest/+esm\";\nimport { Terminal } from \"https://cdn.jsdelivr.net/npm/@xterm/xterm@latest/+esm\";\n\n/**\n * @typedef {import(\"yargs-parser\").Arguments} argv\n */\n/**\n * @typedef {function(string, argv)} commandHandler\n */\n/**\n * @typedef {Object} appInfo\n * @property {string} name The name of the app\n * @property {string} description The description of the app\n * @property {string} usage How to use the app from the CLI\n */\n\n// This is just to resove the terbium system api's\nconst tb = window.tb || window.parent.tb || {};\n\nwindow.http = http;\nwindow.gitfetch = git;\n\n/**\n * Converts a hex color to an RGB string\n * @param {string} hex The hex color to convert\n * @returns {{r: number, g: number, b: number} | null} The RGB object for use in accent, or null if invalid\n */\nfunction htorgb(hex) {\n\thex = hex.replace(\"#\", \"\");\n\tif (hex.length === 3) {\n\t\thex = hex\n\t\t\t.split(\"\")\n\t\t\t.map(h => h + h)\n\t\t\t.join(\"\");\n\t}\n\tif (hex.length !== 6) return null;\n\tconst bigint = parseInt(hex, 16);\n\treturn {\n\t\tr: (bigint >> 16) & 255,\n\t\tg: (bigint >> 8) & 255,\n\t\tb: bigint & 255,\n\t};\n}\n\n/**\n * Command that has been captured from the start of the other command prompt ending and after the newline carriage\n */\nlet accCommand = \"\";\n\n/**\n * Cursor position within the current command (for left/right arrow navigation)\n */\nlet cursorPos = 0;\n\ntb.setCommandProcessing = status => {\n\tsessions.forEach(s => {\n\t\ttry {\n\t\t\ts.isProcessingCommands = status;\n\t\t\ts.localEcho = status;\n\t\t} catch (e) {}\n\t});\n};\n/**\n * Last few commands that have been executed\n */\nlet commandHistory = [];\nlet historyIndex = -1;\nlet path = `/home/${sessionStorage.getItem(\"currAcc\")}/`;\nconst HISTORY_LIMIT = 1000;\nconst HISTORY_FILE = \".bash_history\";\n\nclass TerminalSession {\n\tconstructor(name = \"Terbium TSH\") {\n\t\tthis.id = `s-${Date.now()}-${Math.floor(Math.random() * 1000)}`;\n\t\tthis.name = name;\n\t\tthis.container = document.createElement(\"div\");\n\t\tthis.container.className = \"term-session\";\n\t\tthis.container.style.width = \"100%\";\n\t\tthis.container.style.height = \"100%\";\n\t\tthis.container.style.display = \"none\";\n\t\tdocument.getElementById(\"term\").appendChild(this.container);\n\n\t\tthis.term = new Terminal({ theme: { background: \"#000000\", cursor: \"#ffffff\", selection: \"#444444\" }, cursorBlink: true, allowTransparency: true, rightClickSelectsWord: true });\n\t\tthis.term.open(this.container);\n\n\t\tthis.accCommand = \"\";\n\t\tthis.cursorPos = 0;\n\t\tthis.isProcessingCommands = true;\n\t\tthis.localEcho = true;\n\t\tthis.commandHistory = [];\n\t\tthis.historyIndex = 0;\n\t\tthis.path = `/home/${sessionStorage.getItem(\"currAcc\")}/`;\n\n\t\tthis._bindEvents();\n\t\tthis.loadHistory();\n\t\tthis.writePowerline();\n\t\tthis.focus();\n\t}\n\n\t_bindEvents() {\n\t\tthis.term.element.addEventListener(\"contextmenu\", e => e.preventDefault());\n\t\tthis.term.attachCustomKeyEventHandler(event => {\n\t\t\tif (event.ctrlKey && event.key === \"c\") {\n\t\t\t\tif (this.term.hasSelection()) {\n\t\t\t\t\tnavigator.clipboard.writeText(this.term.getSelection()).catch(console.error);\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (event.ctrlKey && event.key === \"v\") {\n\t\t\t\tnavigator.clipboard\n\t\t\t\t\t.readText()\n\t\t\t\t\t.then(text => {\n\t\t\t\t\t\tfor (const c of text) this.handleChar(c);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(console.error);\n\t\t\t\tevent.preventDefault();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (event.altKey && event.key === \"t\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tcreateSession(\"Terbium TSH\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (event.altKey && event.key === \"w\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tcloseSession(this.id);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (event.altKey && event.key === \"Tab\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tswitchSessionNext();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\n\t\tthis.term.onData(async char => {\n\t\t\tif (!this.isProcessingCommands) {\n\t\t\t\tconst dataHandler = this.term._core._inputHandler;\n\t\t\t\tif (dataHandler && dataHandler.onData) dataHandler.onData(char);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (char.length > 1 && !char.startsWith(\"\\x1b\")) {\n\t\t\t\tfor (const c of char) await this.handleChar(c);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait this.handleChar(char);\n\t\t});\n\n\t\tthis.term.onLineFeed(() => {\n\t\t\tthis.accCommand = \"\";\n\t\t\tthis.cursorPos = 0;\n\t\t\tthis.historyIndex = this.commandHistory.length;\n\t\t});\n\t}\n\n\tasync handleChar(char) {\n\t\tif (char === \"\\x1b[A\") {\n\t\t\tif (this.historyIndex > 0 && this.commandHistory.length > 0) {\n\t\t\t\tthis.term.write(\"\\r\\x1b[K\");\n\t\t\t\tawait this.writePowerline();\n\t\t\t\tthis.historyIndex--;\n\t\t\t\tthis.accCommand = this.commandHistory[this.historyIndex];\n\t\t\t\tthis.cursorPos = this.accCommand.length;\n\t\t\t\tthis.term.write(this.accCommand);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\x1b[B\") {\n\t\t\tthis.term.write(\"\\r\\x1b[K\");\n\t\t\tawait this.writePowerline();\n\t\t\tif (this.historyIndex < this.commandHistory.length - 1) {\n\t\t\t\tthis.historyIndex++;\n\t\t\t\tthis.accCommand = this.commandHistory[this.historyIndex];\n\t\t\t\tthis.cursorPos = this.accCommand.length;\n\t\t\t\tthis.term.write(this.accCommand);\n\t\t\t} else {\n\t\t\t\tthis.historyIndex = this.commandHistory.length;\n\t\t\t\tthis.accCommand = \"\";\n\t\t\t\tthis.cursorPos = 0;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\x1b[D\") {\n\t\t\tif (this.cursorPos > 0) {\n\t\t\t\tthis.cursorPos--;\n\t\t\t\tthis.term.write(\"\\x1b[D\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\x1b[C\") {\n\t\t\tif (this.cursorPos < this.accCommand.length) {\n\t\t\t\tthis.cursorPos++;\n\t\t\t\tthis.term.write(\"\\x1b[C\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\x1b[H\" || char === \"\\x1b[1~\") {\n\t\t\tconst moveLeft = this.cursorPos;\n\t\t\tif (moveLeft > 0) {\n\t\t\t\tthis.term.write(`\\x1b[${moveLeft}D`);\n\t\t\t\tthis.cursorPos = 0;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\x1b[F\" || char === \"\\x1b[4~\") {\n\t\t\tconst moveRight = this.accCommand.length - this.cursorPos;\n\t\t\tif (moveRight > 0) {\n\t\t\t\tthis.term.write(`\\x1b[${moveRight}C`);\n\t\t\t\tthis.cursorPos = this.accCommand.length;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\x1b[3~\") {\n\t\t\tif (this.cursorPos < this.accCommand.length) {\n\t\t\t\tthis.accCommand = this.accCommand.slice(0, this.cursorPos) + this.accCommand.slice(this.cursorPos + 1);\n\t\t\t\tconst remaining = this.accCommand.slice(this.cursorPos);\n\t\t\t\tthis.term.write(remaining + \" \");\n\t\t\t\tthis.term.write(`\\x1b[${remaining.length + 1}D`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\x7f\") {\n\t\t\tif (this.cursorPos > 0) {\n\t\t\t\tthis.accCommand = this.accCommand.slice(0, this.cursorPos - 1) + this.accCommand.slice(this.cursorPos);\n\t\t\t\tthis.cursorPos--;\n\t\t\t\tthis.term.write(\"\\b\");\n\t\t\t\tconst remaining = this.accCommand.slice(this.cursorPos);\n\t\t\t\tthis.term.write(remaining + \" \");\n\t\t\t\tthis.term.write(`\\x1b[${remaining.length + 1}D`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (char === \"\\r\") {\n\t\t\tthis.term.writeln(\"\");\n\t\t\tconst input = this.accCommand.trim();\n\t\t\tif (input.length > 0) {\n\t\t\t\tawait this.saveToHistory(input);\n\t\t\t\tconst [cmd, ...rawArgs] = input.split(\" \");\n\t\t\t\tconst argv = parser(rawArgs);\n\t\t\t\targv._raw = rawArgs.join(\" \");\n\t\t\t\tawait this.handleCommand(cmd, argv);\n\t\t\t} else {\n\t\t\t\tawait this.writePowerline();\n\t\t\t}\n\t\t\tthis.accCommand = \"\";\n\t\t\tthis.cursorPos = 0;\n\t\t\treturn;\n\t\t}\n\t\tif (char >= \" \" && char <= \"~\") {\n\t\t\t// Only echo locally when localEcho is enabled. In passthrough mode the program will echo input itself.\n\t\t\tthis.accCommand = this.accCommand.slice(0, this.cursorPos) + char + this.accCommand.slice(this.cursorPos);\n\t\t\tthis.cursorPos++;\n\t\t\tif (this.localEcho) {\n\t\t\t\tif (this.cursorPos === this.accCommand.length) {\n\t\t\t\t\tthis.term.write(char);\n\t\t\t\t} else {\n\t\t\t\t\tconst remaining = this.accCommand.slice(this.cursorPos - 1);\n\t\t\t\t\tthis.term.write(remaining);\n\t\t\t\t\tthis.term.write(`\\x1b[${remaining.length - 1}D`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync writePowerline() {\n\t\tconst username = await tb.user.username();\n\t\tconst userSettings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${username}/settings.json`, \"utf8\"));\n\t\tconst accent = await htorgb(userSettings.accent);\n\t\tconst hostname = JSON.parse(await window.parent.tb.fs.promises.readFile(\"//system/etc/terbium/settings.json\"))[\"host-name\"];\n\t\tthis.term.write(`\\x1b[38;2;${accent.r};${accent.g};${accent.b}m${username}@${hostname}\\x1b[39m ~ ${this.path}\\x1b[0m: `);\n\t}\n\n\tasync createNewCommandInput() {\n\t\tthis.term.write(\"\\r\\n\");\n\t\tawait this.writePowerline();\n\t\tthis.historyIndex = this.commandHistory.length;\n\t}\n\tasync displayOutput(message, ...styles) {\n\t\t// If output indicates a shell/program exit, restore local echo / command processing\n\t\ttry {\n\t\t\tconst txt = String(message || \"\");\n\t\t\tif (/exited with code|shell exited|exit code/gi.test(txt)) {\n\t\t\t\tthis.exitPassthrough();\n\t\t\t}\n\t\t} catch (e) {}\n\n\t\tif (message.includes(\"%c\")) {\n\t\t\tconst parts = message.split(/(%c)/);\n\t\t\tlet styled = \"\";\n\t\t\tlet styleIndex = 0;\n\t\t\tfor (let i = 0; i < parts.length; i++) {\n\t\t\t\tif (parts[i] === \"%c\") {\n\t\t\t\t\tconst text = parts[++i] || \"\";\n\t\t\t\t\tconst style = styles[styleIndex++] || \"\";\n\t\t\t\t\tconst colorMatch = style.match(/color:\\s*(#[0-9a-fA-F]{3,6})/);\n\t\t\t\t\tif (colorMatch) {\n\t\t\t\t\t\tconst rgb = await htorgb(colorMatch[1]);\n\t\t\t\t\t\tif (rgb) {\n\t\t\t\t\t\t\tstyled += `\\x1b[38;2;${rgb.r};${rgb.g};${rgb.b}m${text}\\x1b[0m`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tstyled += text;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstyled += text;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tstyled += parts[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.term.writeln(styled);\n\t\t} else {\n\t\t\tthis.term.writeln(message);\n\t\t}\n\t}\n\tasync displayError(message) {\n\t\tthis.term.writeln(`\\x1b[31mERR: ${message}\\x1b[0m`);\n\t}\n\tasync loadHistory() {\n\t\ttry {\n\t\t\tconst username = await tb.user.username();\n\t\t\tconst historyPath = `/home/${username}/${HISTORY_FILE}`;\n\t\t\tconst data = await window.parent.tb.fs.promises.readFile(historyPath, \"utf8\");\n\t\t\tthis.commandHistory = data.split(\"\\n\").filter(cmd => cmd.trim() !== \"\");\n\t\t} catch {}\n\t\tthis.historyIndex = this.commandHistory.length;\n\t}\n\tasync saveToHistory(command) {\n\t\tif (!command.trim()) return;\n\t\tthis.commandHistory.push(command);\n\t\tif (this.commandHistory.length > HISTORY_LIMIT) this.commandHistory.shift();\n\t\tthis.historyIndex = this.commandHistory.length;\n\t\ttry {\n\t\t\tconst username = await tb.user.username();\n\t\t\tconst historyPath = `/home/${username}/${HISTORY_FILE}`;\n\t\t\tawait window.parent.tb.fs.promises.writeFile(historyPath, this.commandHistory.join(\"\\n\"));\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to save history\", error);\n\t\t}\n\t}\n\tasync handleCommand(name, args) {\n\t\tconst appInfo = await getAppInfo();\n\t\tif (name === \"exit\") {\n\t\t\tcloseSession(this.id);\n\t\t\treturn;\n\t\t}\n\t\tif (name === \"help\") {\n\t\t\tconst commands = appInfo ? appInfo.join(\", \") : \"No commands available\";\n\t\t\tthis.displayOutput(`Available commands: exit, help, ${commands}`);\n\t\t\tthis.createNewCommandInput();\n\t\t\treturn;\n\t\t}\n\n\t\t// If this command is an interactive shell-like command, enter passthrough mode\n\t\tconst INTERACTIVE_CMDS = new Set([\"node\", \"python\", \"bash\", \"sh\", \"nodejs\", \"jsh\", \"pwsh\", \"powershell\"]);\n\t\tif (INTERACTIVE_CMDS.has(name.toLowerCase())) {\n\t\t\tthis.enterPassthrough();\n\t\t}\n\n\t\tconst scriptPaths = [`/fs/apps/system/terminal.tapp/scripts/${name.toLowerCase()}.js`, `/apps/terminal.tapp/scripts/${name.toLowerCase()}.js`];\n\t\tif (appInfo === null) {\n\t\t\tthis.displayOutput(\"Failed to fetch app info, cannot execute command\");\n\t\t\tthis.createNewCommandInput();\n\t\t\tthis.exitPassthrough();\n\t\t\treturn;\n\t\t}\n\t\tif (!appInfo.includes(name)) {\n\t\t\tthis.displayOutput(`Command '${name}' not found! Type 'help' for a list of commands.`);\n\t\t\tthis.createNewCommandInput();\n\t\t\tthis.exitPassthrough();\n\t\t\treturn;\n\t\t}\n\t\tlet scriptRes;\n\t\ttry {\n\t\t\tscriptRes = await fetch(scriptPaths[0]);\n\t\t} catch {\n\t\t\ttry {\n\t\t\t\tscriptRes = await fetch(scriptPaths[1]);\n\t\t\t} catch (error) {\n\t\t\t\tthis.displayOutput(`Failed to fetch script: ${error.message}`);\n\t\t\t\tthis.createNewCommandInput();\n\t\t\t\tthis.exitPassthrough();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tconst script = await scriptRes.text();\n\t\t\tconst fn = new Function(\"args\", \"displayOutput\", \"createNewCommandInput\", \"displayError\", \"term\", \"path\", \"terbium\", \"buffer\", \"setTabTitle\", \"exitPassthrough\", script);\n\t\t\tfn(args, this.displayOutput.bind(this), this.createNewCommandInput.bind(this), this.displayError.bind(this), this.term, this.path, window.parent.tb, window.parent.tb.buffer, this.setName.bind(this), this.exitPassthrough.bind(this));\n\t\t} catch (error) {\n\t\t\tthis.displayOutput(`Failed to execute command '${name}': ${error.message}`);\n\t\t\tthis.createNewCommandInput();\n\t\t\tthis.exitPassthrough();\n\t\t\treturn;\n\t\t}\n\t}\n\tresize() {\n\t\ttry {\n\t\t\tconst charWidth = this.term._core._renderService.dimensions.css.cell.width;\n\t\t\tconst charHeight = this.term._core._renderService.dimensions.css.cell.height;\n\t\t\tconst cols = Math.floor(window.innerWidth / charWidth);\n\t\t\tconst rows = Math.floor(window.innerHeight / charHeight);\n\t\t\tthis.term.resize(cols, rows);\n\t\t} catch (e) {}\n\t}\n\tfocus() {\n\t\ttry {\n\t\t\tthis.term.focus();\n\t\t\twindow.term = this.term;\n\t\t} catch (e) {}\n\t}\n\tenterPassthrough() {\n\t\tthis.isProcessingCommands = false;\n\t\tthis.localEcho = false;\n\t}\n\texitPassthrough() {\n\t\tthis.isProcessingCommands = true;\n\t\tthis.localEcho = true;\n\t\t// show a new prompt after exiting passthrough\n\t\ttry {\n\t\t\tthis.createNewCommandInput();\n\t\t} catch (e) {}\n\t}\n\tsetName(newName) {\n\t\tthis.name = newName;\n\t\tconst win = getWinRoot();\n\t\tif (win) {\n\t\t\tconst tab = win.querySelector(`.term-tab[data-sid=\"${this.id}\"]`);\n\t\t\tif (tab) {\n\t\t\t\ttab.querySelector(\".label\").textContent = newName;\n\t\t\t}\n\t\t}\n\t}\n\tdestroy() {\n\t\ttry {\n\t\t\tthis.term.dispose();\n\t\t} catch {}\n\t\ttry {\n\t\t\tthis.container.remove();\n\t\t} catch {}\n\t}\n}\n\nconst sessions = [];\nlet activeSession = null;\n// Guard to prevent duplicate rapid-close actions (e.g., a single keypress being handled by two handlers)\nlet _lastCloseTime = 0;\nfunction getWinRoot() {\n\ttry {\n\t\treturn window.frameElement?.closest(\"[pid]\");\n\t} catch {\n\t\treturn null;\n\t}\n}\nfunction addTabToTitle(session) {\n\tconst win = getWinRoot();\n\tif (!win) return;\n\tconst tabList = win.querySelector(\".term-tab-list\");\n\tif (!tabList) return;\n\tconst btn = document.createElement(\"button\");\n\tbtn.className = \"term-tab\";\n\tbtn.dataset.sid = session.id;\n\tbtn.innerHTML = `<span class=\\\"label\\\">${session.name}</span><span class=\\\"close\\\">×</span>`;\n\ttabList.appendChild(btn);\n\tbtn.addEventListener(\"click\", e => {\n\t\tif (e.target && e.target.classList && e.target.classList.contains(\"close\")) {\n\t\t\tcloseSession(session.id);\n\t\t} else {\n\t\t\tswitchSession(session.id);\n\t\t}\n\t});\n\tconst add = win.querySelector(\".term-add\");\n\tif (add && !add.dataset.bound) {\n\t\tadd.dataset.bound = \"1\";\n\t\tadd.addEventListener(\"click\", () => createSession(\"Terbium TSH\"));\n\t}\n\tsetActiveTabInTitle();\n}\nfunction removeTabFromTitle(id) {\n\tconst win = getWinRoot();\n\tif (!win) return;\n\tconst tab = win.querySelector(`.term-tab[data-sid=\"${id}\"]`);\n\tif (tab) tab.remove();\n\tsetActiveTabInTitle();\n}\nfunction setActiveTabInTitle() {\n\tconst win = getWinRoot();\n\tif (!win) return;\n\twin.querySelectorAll(\".term-tab\").forEach(t => t.classList.remove(\"active\"));\n\tif (!activeSession) return;\n\tconst sel = win.querySelector(`.term-tab[data-sid=\"${activeSession.id}\"]`);\n\tif (sel) sel.classList.add(\"active\");\n}\nfunction createSession(name = \"Terbium TSH\") {\n\tconst isFirst = sessions.length === 0;\n\tconst s = new TerminalSession(name);\n\tsessions.push(s);\n\tif (isFirst) {\n\t\ttry {\n\t\t\ts.term.writeln(`TerbiumOS [Version: ${tb.system.version()}]`);\n\t\t\ts.term.writeln(`Type 'help' for a list of commands.`);\n\t\t} catch (e) {\n\t\t\tconsole.error(\"Failed to display welcome message\", e);\n\t\t}\n\t}\n\tsessions.forEach(se => {\n\t\tse.container.style.display = se === s ? \"flex\" : \"none\";\n\t});\n\tactiveSession = s;\n\taddTabToTitle(s);\n\tsetActiveTabInTitle();\n\treturn s;\n}\nfunction switchSession(id) {\n\tconst s = sessions.find(x => x.id === id);\n\tif (!s) return;\n\tsessions.forEach(se => (se.container.style.display = se === s ? \"flex\" : \"none\"));\n\tactiveSession = s;\n\ts.focus();\n\tsetActiveTabInTitle();\n}\nfunction switchSessionNext() {\n\tif (sessions.length <= 1) return;\n\tconst idx = sessions.indexOf(activeSession);\n\tconst next = sessions[(idx + 1) % sessions.length];\n\tswitchSession(next.id);\n}\nfunction closeSession(id) {\n\tconst now = Date.now();\n\tif (now - _lastCloseTime < 250) return;\n\t_lastCloseTime = now;\n\tconst idx = sessions.findIndex(s => s.id === id);\n\tif (idx < 0) return;\n\tconst wasActive = sessions[idx] === activeSession;\n\tsessions[idx].destroy();\n\tremoveTabFromTitle(id);\n\tsessions.splice(idx, 1);\n\tif (wasActive) {\n\t\tif (sessions.length) {\n\t\t\tswitchSession(sessions[Math.max(0, idx - 1)].id);\n\t\t} else {\n\t\t\tactiveSession = null;\n\t\t}\n\t}\n\tif (sessions.length === 0) {\n\t\twindow.parent.tb.window.close();\n\t}\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n\tcreateSession(\"Terbium TSH\");\n\twindow.addEventListener(\"resize\", () => {\n\t\tsessions.forEach(s => s.resize());\n\t});\n});\nwindow.handleCommand = (...args) => (activeSession ? activeSession.handleCommand(...args) : handleCommand(...args));\nwindow.addEventListener(\"updPath\", e => {\n\tif (activeSession) activeSession.path = e.detail;\n});\n\n/**\n * Resizes the active session terminal to fit the window\n * @returns {void}\n */\nfunction resizeTerm() {\n\ttry {\n\t\tif (activeSession && activeSession.term && activeSession.term._core) {\n\t\t\tconst charWidth = activeSession.term._core._renderService.dimensions.css.cell.width;\n\t\t\tconst charHeight = activeSession.term._core._renderService.dimensions.css.cell.height;\n\t\t\tconst cols = Math.floor(window.innerWidth / charWidth);\n\t\t\tconst rows = Math.floor(window.innerHeight / charHeight);\n\t\t\tactiveSession.term.resize(cols, rows);\n\t\t}\n\t} catch (e) {\n\t\t/* ignore */\n\t}\n}\nsetTimeout(resizeTerm, 50);\nwindow.addEventListener(\"resize\", resizeTerm);\n\n/**\n * The command handler, which executes the commands in `scripts/`\n * @param {string} name The command name\n * @param {argv} args The command's respective args (from yargs-parser)\n * @returns {Promise<void>}\n */\nasync function handleCommand(name, args) {\n\t// Prefer activeSession handling if available\n\tif (typeof activeSession !== \"undefined\" && activeSession) {\n\t\treturn activeSession.handleCommand(name, args);\n\t}\n\t// If no active session, attempt minimal fallback: try to run script but without a term\n\tconst scriptPaths = [`/fs/apps/system/terminal.tapp/scripts/${name.toLowerCase()}.js`, `/apps/terminal.tapp/scripts/${name.toLowerCase()}.js`];\n\tconst appInfo = await getAppInfo();\n\tif (appInfo === null) {\n\t\tconsole.error(\"Failed to fetch app info, cannot execute command\");\n\t\treturn;\n\t}\n\tif (!appInfo.includes(name)) {\n\t\tconsole.error(`Command '${name}' not found!`);\n\t\treturn;\n\t}\n\tlet scriptRes;\n\ttry {\n\t\tscriptRes = await fetch(scriptPaths[0]);\n\t} catch {\n\t\ttry {\n\t\t\tscriptRes = await fetch(scriptPaths[1]);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to fetch script: ${error.message}`);\n\t\t\treturn;\n\t\t}\n\t}\n\ttry {\n\t\tconst script = await scriptRes.text();\n\t\tconst fn = new Function(\"args\", \"displayOutput\", \"createNewCommandInput\", \"displayError\", \"term\", \"path\", \"terbium\", \"buffer\", script);\n\t\tfn(\n\t\t\targs,\n\t\t\t(m, ...s) => console.log(m),\n\t\t\t() => {},\n\t\t\te => console.error(e),\n\t\t\tundefined,\n\t\t\tpath,\n\t\t\twindow.parent.tb,\n\t\t\twindow.parent.tb.buffer,\n\t\t);\n\t} catch (error) {\n\t\tconsole.error(`Failed to execute command '${name}': ${error.message}`);\n\t\treturn;\n\t}\n}\n\n// Expose a delegating handler so other frames can always call it\nwindow.handleCommand = (...args) => {\n\tif (typeof activeSession !== \"undefined\" && activeSession) return activeSession.handleCommand(...args);\n\treturn handleCommand(...args);\n};\n\nwindow.addEventListener(\"updPath\", e => {\n\tpath = e.detail;\n});\n\n/**\n * Fetches the app info from the `info.json` file\n * @param {boolean} justNames Whether to return just the app names or the full app info\n * @returns {Promise<string[]|appInfo>} The app names or the full app info\n */\nasync function getAppInfo(justNames = true) {\n\t/**\n\t * @type {Response}\n\t */\n\tconst appInfoResUsr = await fetch(`/fs/apps/user/${await tb.user.username()}/terminal/info.json`);\n\t/**\n\t * @type {Response}\n\t */\n\tconst appInfoResSys = await fetch(`/fs/apps/system/terminal.tapp/scripts/info.json`);\n\n\t/**\n\t * @type {Response}\n\t */\n\tlet appInfo;\n\ttry {\n\t\tlet appInfoUsr = await appInfoResUsr.json();\n\t\tlet appInfoSys = await appInfoResSys.json();\n\t\tif (!Array.isArray(appInfoUsr)) appInfoUsr = appInfoUsr ? [appInfoUsr] : [];\n\t\tif (!Array.isArray(appInfoSys)) appInfoSys = appInfoSys ? [appInfoSys] : [];\n\t\tappInfo = [...appInfoUsr, ...appInfoSys];\n\t} catch (error) {\n\t\tdisplayError(`Failed to parse one or more info.json files: ${error.message}`);\n\t\tcreateNewCommandInput();\n\t\treturn null;\n\t}\n\n\tif (justNames) return appInfo.map(app => app.name);\n\treturn appInfo;\n}\n\n/**\n * Displays a styled message to the terminal\n * @param {string} message The message to display, can include %c for styling\n * @param {...string} styles CSS style strings for each %c in the message\n * @returns {Promise<void>}\n */\nasync function displayOutput(message, ...styles) {\n\tif (typeof activeSession !== \"undefined\" && activeSession) return activeSession.displayOutput(message, ...styles);\n\tif (message.includes(\"%c\")) console.log(message.replace(/%c/g, \"\"));\n\telse console.log(message);\n}\n/**\n * Writes the powerline prompt to the terminal\n * @returns {Promise<void>}\n */\nasync function writePowerline() {\n\tif (typeof activeSession !== \"undefined\" && activeSession) return activeSession.writePowerline();\n\t// fallback: no-op\n}\n/**\n * Creates new command line with a styled prompt\n * @returns {Promise<void>}\n */\nasync function createNewCommandInput() {\n\tif (typeof activeSession !== \"undefined\" && activeSession) return activeSession.createNewCommandInput();\n\t// fallback: no-op\n}\n\n/**\n * Logs an error message to terminal\n * @param {string} message The error message that will be displayed on the output\n */\nfunction displayError(message) {\n\tif (typeof activeSession !== \"undefined\" && activeSession) return activeSession.displayError(message);\n\tconsole.error(message);\n}\n\n/**\n * Load the current history from the bash history file\n * @returns {Promise<void>}\n */\nasync function loadHistory() {\n\tif (typeof activeSession !== \"undefined\" && activeSession) return activeSession.loadHistory();\n\t// fallback: no-op\n}\n/**\n * Saves a command to the bash history file\n * @param {string} command The command to save to history\n * @returns {Promise<void>}\n */\nasync function saveToHistory(command) {\n\tif (typeof activeSession !== \"undefined\" && activeSession) return activeSession.saveToHistory(command);\n\tif (!command.trim()) return;\n\tcommandHistory.push(command);\n\tif (commandHistory.length > HISTORY_LIMIT) commandHistory.shift();\n\thistoryIndex = commandHistory.length;\n\ttry {\n\t\tconst username = await tb.user.username();\n\t\tconst historyPath = `/home/${username}/${HISTORY_FILE}`;\n\t\tawait window.parent.tb.fs.promises.writeFile(historyPath, commandHistory.join(\"\\n\"));\n\t} catch (error) {\n\t\tconsole.error(\"Failed to save history\", error);\n\t}\n}\n"
  },
  {
    "path": "public/apps/terminal.tapp/index.json",
    "content": "{\n\t\"name\": \"Terminal\",\n\t\"config\": {\n\t\t\"title\": {\n\t\t\t\"text\": \"Terminal\",\n\t\t\t\"html\": \"<div class=\\\"term-tab-container\\\">\\n<style>.term-tabs{display:flex;align-items:center;gap:6px;width:100%;height:100%;}.term-tab-list{display:flex;gap:6px;align-items:center;overflow:hidden;white-space:nowrap}.term-tab{display:inline-flex;align-items:center;gap:8px;padding:4px 10px;border-radius:8px;background:transparent;color:#fff;font-weight:700;cursor:var(--cursor-pointer);user-select:none;border:1px solid transparent}.term-tab.active{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.08)}.term-tab .close{opacity:0.6;font-weight:600;margin-left:8px}.term-tab-controls{display:flex;gap:6px;align-items:center}.term-add{background:#ffffff0f;color:#fff;border-radius:6px;padding:2px 6px;border:none;font-weight:700;cursor:var(--cursor-pointer)}</style>\\n<div class=\\\"term-tabs\\\">\\n<div class=\\\"term-tab-list\\\" aria-hidden=\\\"false\\\"></div>\\n<div class=\\\"term-tab-controls\\\">\\n<button class=\\\"term-add\\\" title=\\\"New tab\\\">+</button>\\n</div>\\n</div>\\n</div>\"\n\t\t},\n\t\t\"icon\": \"/fs/apps/system/terminal.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/terminal.tapp/index.html\",\n\t\t\"size\": {\n\t\t\t\"width\": 612,\n\t\t\t\"height\": 415\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "public/apps/terminal.tapp/logo.txt",
    "content": "@@@@@@@@@@@@@@~ B@@@@@@@@#G?. \nB###&@@@@&####^ #@@@&PPPB@@@G.\n .. ~@@@@J ..  .#@@@P   ~&@@@^\n    ^@@@@?     .#@@@@###&@@&7 \n    ^@@@@?     .#@@@#555P&@@B7\n    ^@@@@?     .#@@@P    G@@@@\n    ^@@@@?     .#@@@&GGG#@@@@Y\n    ^&@@@?      B@@@@@@@@&B5~ "
  },
  {
    "path": "public/apps/terminal.tapp/scripts/cat.js",
    "content": "async function cat(args) {\n\tif (args._raw.length <= 0) {\n\t\tdisplayError(\"cat: missing operand\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\tdisplayOutput(`%cRight now cat only outputs the contents of a file.\\n`, \"color: #e39d34\");\n\tif (path.includes(\"/mnt/\")) {\n\t\ttry {\n\t\t\tconst match = path.match(/\\/mnt\\/([^\\/]+)\\//);\n\t\t\tconst davName = match ? match[1].toLowerCase() : \"\";\n\t\t\tconst text = await tb.vfs.servers.get(davName).connection.promises.readFile(`${path}/${args._raw}`, \"utf8\");\n\t\t\tdisplayOutput(text);\n\t\t\tcreateNewCommandInput();\n\t\t} catch (e) {\n\t\t\tdisplayError(`TNSM cat: ${e.message}`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\ttb.sh.cat(`${path}/${args._raw}`, (err, data) => {\n\t\t\tif (err) {\n\t\t\t\tdisplayError(`cat: ${err.message}`);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t} else {\n\t\t\t\tdisplayOutput(data);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t}\n\t\t});\n\t}\n}\ncat(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/cd.js",
    "content": "function cd(args) {\n\tlet destination = args._[0];\n\tconst raw_destination = destination;\n\n\tif (!destination || destination === \"~\") {\n\t\tconst homePath = `/home/${sessionStorage.getItem(\"currAcc\")}/`;\n\t\twindow.dispatchEvent(new CustomEvent(\"updPath\", { detail: homePath }));\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\n\tif (destination.startsWith(\"~/\")) {\n\t\tdestination = destination.replace(\"~\", `/home/${sessionStorage.getItem(\"currAcc\")}`);\n\t}\n\n\tlet newPath;\n\tif (destination.startsWith(\"/\")) {\n\t\tnewPath = destination;\n\t} else {\n\t\tnewPath = path + destination;\n\t}\n\n\tconst resolvedParts = [];\n\tfor (const part of newPath.split(\"/\").filter(p => p)) {\n\t\tif (part === \"..\") {\n\t\t\tresolvedParts.pop();\n\t\t} else if (part !== \".\") {\n\t\t\tresolvedParts.push(part);\n\t\t}\n\t}\n\n\tlet finalPath = \"/\" + resolvedParts.join(\"/\") + \"/\";\n\tif (finalPath === \"//\") {\n\t\tfinalPath = \"/\";\n\t}\n\n\tconst checkPath = finalPath.length > 2 ? finalPath.slice(0, -1) : finalPath;\n\twindow.parent.tb.fs.stat(checkPath, (err, stats) => {\n\t\tif (err) {\n\t\t\tif (destination.includes(\"/mnt/\") || checkPath.includes(\"/mnt/\")) {\n\t\t\t\twindow.dispatchEvent(\n\t\t\t\t\tnew CustomEvent(\"updPath\", {\n\t\t\t\t\t\tdetail: finalPath,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t} else {\n\t\t\t\tdisplayError(`cd: ${raw_destination}: No such file or directory`);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t}\n\t\t} else if (!stats.isDirectory()) {\n\t\t\tdisplayError(`cd: ${raw_destination}: Not a directory`);\n\t\t\tcreateNewCommandInput();\n\t\t} else {\n\t\t\twindow.dispatchEvent(\n\t\t\t\tnew CustomEvent(\"updPath\", {\n\t\t\t\t\tdetail: finalPath,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tcreateNewCommandInput();\n\t\t}\n\t});\n}\ncd(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/clear.js",
    "content": "function clear(term) {\n\tterm.clear();\n\tcreateNewCommandInput();\n}\n\nclear(term);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/curl.js",
    "content": "async function curl(args) {\n\tlet url = args._raw;\n\tif (!url) {\n\t\tdisplayOutput(\"Usage: curl <scriptURL>\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\n\tlet shouldSave = false;\n\tif (url.includes(\"-k\")) {\n\t\tshouldSave = true;\n\t\turl = url.replace(\"-k\", \"\").trim();\n\t}\n\n\tif (shouldSave) {\n\t\ttb.dialog.SaveFile({\n\t\t\ttitle: \"Save Script\",\n\t\t\tonOk: async loc => {\n\t\t\t\ttry {\n\t\t\t\t\tawait window.parent.tb.system.download(url, loc);\n\t\t\t\t\tdisplayOutput(`Saved to ${loc}`);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tdisplayError(\"Error saving script:\", e);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcreateNewCommandInput();\n\t\t\t},\n\t\t});\n\t} else {\n\t\tconst response = await window.parent.tb.libcurl.fetch(url);\n\t\tconst scriptContent = await response.text();\n\t\ttry {\n\t\t\teval(scriptContent);\n\t\t} catch (error) {\n\t\t\tdisplayError(\"Error executing script:\", error);\n\t\t}\n\t\tcreateNewCommandInput();\n\t}\n}\n\ncurl(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/echo.js",
    "content": "function echo(args) {\n\tdisplayOutput(args);\n\tcreateNewCommandInput();\n}\n\necho(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/exit.js",
    "content": "function exit() {\n\twindow.parent.tb.window.close();\n}\nexit();\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/git.js",
    "content": "async function git(args) {\n\tlet user = await window.parent.tb.user.username();\n\tlet currentPath = path;\n\tif (currentPath.startsWith(\"~\")) currentPath = currentPath.replace(\"~\", `/home/${window.parent.sessionStorage.getItem(\"currAcc\")}`);\n\tlet cmds = [\n\t\t\"\\ start a working area\",\n\t\t\"clone: Clone a repository into a new directory\",\n\t\t\"init: Create an empty Git repository or reinitialize an existing one\",\n\t\t\"\\ work on the current change\",\n\t\t\"add: Add file contents to the index\",\n\t\t\"rm: Remove files from the working tree and from the index\",\n\t\t\"\\ examine the history and state\",\n\t\t\"status: Show the working tree status\",\n\t\t\"\\ grow, mark and tweak your common history\",\n\t\t\"commit: Record changes to the repository (Make sure to run git add <filename> <directory> before commiting)\",\n\t\t\"\\ collaborate (Login requires your GitHub Token)\",\n\t\t\"fetch: Download objects and refs from another repository\",\n\t\t\"pull: Fetch from and integrate with another repository or a local branch\",\n\t\t\"push: Update remote refs along with associated objects\",\n\t];\n\ttry {\n\t\tif (args._raw.includes(\"clone\")) {\n\t\t\tif (!args._[2]) {\n\t\t\t\tpath = `/home/${sessionStorage.getItem(\"currAcc\")}/`;\n\t\t\t}\n\n\t\t\tif (path !== \"/\" && args._[2] === \"/\") {\n\t\t\t\tpath = args._[2];\n\t\t\t} else if (path !== \"/\") {\n\t\t\t\tpath = `${currentPath}/${args._[2]}`;\n\t\t\t}\n\n\t\t\tdisplayOutput(`Cloning into '${args._[1].split(/(\\\\|\\/)/g).pop()}'...`);\n\t\t\tconst targetDir = args._[2] ?? `${currentPath}/${args._[1].split(/(\\\\|\\/)/g).pop()}`;\n\t\t\tawait window.parent.tb.fs.promises.mkdir(targetDir, { recursive: true });\n\t\t\tawait gitfetch.clone({\n\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\thttp: http,\n\t\t\t\tdir: targetDir,\n\t\t\t\tcorsProxy: \"https://cors.isomorphic-git.org\",\n\t\t\t\turl: args._[1],\n\t\t\t\tnoCheckout: false,\n\t\t\t\tsingleBranch: true,\n\t\t\t\tdepth: 1,\n\t\t\t\tonAuth: async () => {\n\t\t\t\t\treturn new Promise(async resolve => {\n\t\t\t\t\t\tawait window.parent.tb.dialog.WebAuth({\n\t\t\t\t\t\t\ttitle: \"GitHub Authentication\",\n\t\t\t\t\t\t\tonOk: async (username, password) => {\n\t\t\t\t\t\t\t\tresolve({ username, password });\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonCancel: () => {\n\t\t\t\t\t\t\t\tdisplayError(\"Authentication was canceled\");\n\t\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tonMessage: e => {\n\t\t\t\t\tdisplayOutput(e);\n\t\t\t\t},\n\t\t\t});\n\t\t\tawait gitfetch.setConfig({\n\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\tdir: targetDir,\n\t\t\t\tpath: \"user.name\",\n\t\t\t\tvalue: await window.parent.tb.user.username(),\n\t\t\t});\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"init\")) {\n\t\t\tlet path = currentPath + args._[1];\n\t\t\tif (!args._[1]) {\n\t\t\t\tdisplayError(\"Error: Target directory must be specified for 'git init'.\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tdisplayOutput(`Initializing empty Git repository in ${path}/.git/...`);\n\t\t\tawait window.parent.tb.fs.promises.mkdir(`${path}/.git`, { recursive: true });\n\t\t\tawait gitfetch.init({\n\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\thttp: http,\n\t\t\t\tdir: path,\n\t\t\t\tbare: false,\n\t\t\t\tdefaultBranch: \"master\",\n\t\t\t\tgitdir: `${path}/.git`,\n\t\t\t});\n\t\t\tdisplayOutput(\"Initialized empty Git repository.\");\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"checkout\")) {\n\t\t\tif (!args._[1] || !args._[2]) {\n\t\t\t\tdisplayOutput(\"Usage: git checkout <branch> <directory>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst branchName = args._[1];\n\t\t\tconst targetDir = currentPath + args._[2];\n\t\t\ttry {\n\t\t\t\tawait gitfetch.checkout({\n\t\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\t\tdir: targetDir,\n\t\t\t\t\tref: branchName,\n\t\t\t\t\tonMessage: e => {\n\t\t\t\t\t\tdisplayOutput(e);\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tdisplayOutput(`Switched branch '${branchName}' in '${targetDir}'`);\n\t\t\t} catch (error) {\n\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t}\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"add\")) {\n\t\t\tif (!args._[1] || !args._[2]) {\n\t\t\t\tdisplayOutput(\"Usage: git add <file> <directory>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst filePath = args._[1];\n\t\t\tconst targetDir = currentPath + args._[2];\n\t\t\ttry {\n\t\t\t\tawait gitfetch.add({\n\t\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\t\tdir: targetDir,\n\t\t\t\t\tfilepath: filePath,\n\t\t\t\t});\n\n\t\t\t\tdisplayOutput(`Added '${filePath}' to staging area`);\n\t\t\t} catch (error) {\n\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t}\n\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"rm\")) {\n\t\t\tif (!args._[1] || !args._[2]) {\n\t\t\t\tdisplayOutput(\"Usage: git rm <file> <directory>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst filePath = args._[1];\n\t\t\tconst targetDir = currentPath + args._[2];\n\t\t\ttry {\n\t\t\t\tawait gitfetch.remove({\n\t\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\t\tdir: targetDir,\n\t\t\t\t\tfilepath: filePath,\n\t\t\t\t});\n\n\t\t\t\tdisplayOutput(`Removed '${filePath}' from the working tree and the index`);\n\t\t\t} catch (error) {\n\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t}\n\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"status\")) {\n\t\t\tdisplayError(\"Command is currently not implemented\");\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"pull\")) {\n\t\t\ttry {\n\t\t\t\tconst result = await gitfetch.pull({\n\t\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\t\thttp: http,\n\t\t\t\t\tdir: path,\n\t\t\t\t\tcorsProxy: \"https://cors.isomorphic-git.org\",\n\t\t\t\t\tauthor: {\n\t\t\t\t\t\tname: user,\n\t\t\t\t\t\temail: `${user}@terbiumon.top`,\n\t\t\t\t\t},\n\t\t\t\t\tonMessage: e => {\n\t\t\t\t\t\tdisplayOutput(e);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tdisplayOutput(JSON.stringify(result, null, 2));\n\t\t\t} catch (error) {\n\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t}\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"push\")) {\n\t\t\ttry {\n\t\t\t\tconst result = await window.parent.tb.dialog.WebAuth({\n\t\t\t\t\ttitle: \"GitHub Authentication\",\n\t\t\t\t\tonOk: async ({ username, password }) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst gitResult = await gitfetch.push({\n\t\t\t\t\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\t\t\t\t\thttp: http,\n\t\t\t\t\t\t\t\tdir: path,\n\t\t\t\t\t\t\t\tcorsProxy: \"https://cors.isomorphic-git.org\",\n\t\t\t\t\t\t\t\tremote: \"origin\",\n\t\t\t\t\t\t\t\tforce: false,\n\t\t\t\t\t\t\t\tonMessage: e => {\n\t\t\t\t\t\t\t\t\tdisplayOutput(e);\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tauthor: {\n\t\t\t\t\t\t\t\t\tname: user,\n\t\t\t\t\t\t\t\t\temail: `${user}@terbiumon.top`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tonAuth: () => {\n\t\t\t\t\t\t\t\t\treturn { username, password };\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tdisplayOutput(JSON.stringify(gitResult, null, 2));\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonCancel: () => {\n\t\t\t\t\t\tdisplayError(\"GitHub authentication canceled\");\n\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t}\n\t\t} else if (args._raw.includes(\"fetch\")) {\n\t\t\tif (!args._[1]) {\n\t\t\t\tdisplayOutput(\"Usage: git fetch <directory> <remote-url>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst dirName = args._[1];\n\t\t\tconst targetDir = `${currentPath}/${dirName}/.git`;\n\t\t\tif (!args._[2]) {\n\t\t\t\tdisplayError(\"Error: Remote URL must be provided.\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst remoteUrl = args._[2];\n\t\t\ttry {\n\t\t\t\tawait gitfetch.fetch({\n\t\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\t\thttp: http,\n\t\t\t\t\tdir: targetDir,\n\t\t\t\t\turl: remoteUrl,\n\t\t\t\t});\n\n\t\t\t\tdisplayOutput(\"Fetch successful.\");\n\t\t\t} catch (error) {\n\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t}\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"commit\")) {\n\t\t\tif (!args._[1]) {\n\t\t\t\tdisplayError(\"Error: Commit message must be provided.\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst commitMessage = args._[2] || \"Blank Commit\";\n\t\t\ttry {\n\t\t\t\tawait gitfetch.commit({\n\t\t\t\t\tfs: window.parent.tb.fs,\n\t\t\t\t\thttp: http,\n\t\t\t\t\tdir: path,\n\t\t\t\t\tcorsProxy: \"https://cors.isomorphic-git.org\",\n\t\t\t\t\tauthor: {\n\t\t\t\t\t\tname: user,\n\t\t\t\t\t\temail: `${user}@terbiumon.top`,\n\t\t\t\t\t},\n\t\t\t\t\tmessage: commitMessage,\n\t\t\t\t});\n\t\t\t\tdisplayOutput(\"Commit successful.\");\n\t\t\t} catch (error) {\n\t\t\t\tdisplayError(`Error: ${error.message}`);\n\t\t\t}\n\t\t\tcreateNewCommandInput();\n\t\t} else if (args._raw.includes(\"gui\")) {\n\t\t\tdisplayOutput(\"Opening GitGUI app...\");\n\t\t\ttry {\n\t\t\t\tawait tb.system.openApp(\"com.tb.gitgui\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t} catch (err) {\n\t\t\t\tdisplayError(`Error while opening GitGUI: ${err}`);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t}\n\t\t} else if (args._raw.includes(\"version\")) {\n\t\t\tdisplayOutput(`git version: ${gitfetch.version()}`);\n\t\t\tcreateNewCommandInput();\n\t\t} else {\n\t\t\tdisplayOutput(\"Usage: git [--version] [--help] <command> [<args>]\"), displayOutput(\"These are common Git commands used in various situations:\");\n\t\t\tfor (let command of cmds) {\n\t\t\t\tif (command.trim() === \"\") {\n\t\t\t\t\tdisplayOutput(\"\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (command.startsWith(\"\\ \")) {\n\t\t\t\t\tdisplayOutput(command.slice(1));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet [cmd, description] = command.split(\": \");\n\t\t\t\tdisplayOutput(`   ${cmd.padEnd(15)} ${description}`);\n\t\t\t}\n\t\t\tcreateNewCommandInput();\n\t\t}\n\t} catch (e) {\n\t\tdisplayError(e);\n\t\tcreateNewCommandInput();\n\t}\n}\n\ngit(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/help.js",
    "content": "async function help(args) {\n\tif (args.length > 0) {\n\t\tconst scriptName = args[0];\n\t\tconst scriptList = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/terminal/info.json`, \"utf8\"));\n\t\tconst script = scriptList.find(script => script.name === scriptName);\n\t\tif (script) {\n\t\t\tdisplayOutput(`${script.name}: ${script.usage ? `${script.usage}` : \"\"}`);\n\t\t} else {\n\t\t\tdisplayError(`help: ${scriptName}: No such script`);\n\t\t}\n\t\tcreateNewCommandInput();\n\t} else {\n\t\tfetch(\"./scripts/info.json\")\n\t\t\t.then(response => response.json())\n\t\t\t.then(scriptList => {\n\t\t\t\tfor (let script of scriptList) {\n\t\t\t\t\tdisplayOutput(`${script.usage.padEnd(30)} ${script.description}`);\n\t\t\t\t}\n\t\t\t\tcreateNewCommandInput();\n\t\t\t});\n\t}\n}\nhelp(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/info.json",
    "content": "[\n\t{\n\t\t\"name\": \"help\",\n\t\t\"description\": \"Display this help message\",\n\t\t\"usage\": \"help [command]\"\n\t},\n\t{\n\t\t\"name\": \"mkdir\",\n\t\t\"description\": \"Create a directory\",\n\t\t\"usage\": \"mkdir [directory]\"\n\t},\n\t{\n\t\t\"name\": \"nano\",\n\t\t\"description\": \"Simple text editor\",\n\t\t\"usage\": \"nano [file]\"\n\t},\n\t{\n\t\t\"name\": \"touch\",\n\t\t\"description\": \"Create a file\",\n\t\t\"usage\": \"touch [file]\"\n\t},\n\t{\n\t\t\"name\": \"rm\",\n\t\t\"description\": \"Remove a file or directory\",\n\t\t\"usage\": \"rm [file/directory]\",\n\t\t\"options\": {\n\t\t\t\"-r, --recursive\": \"Remove directories and their contents recursively.\",\n\t\t\t\"-f, --force\": \"Ignore nonexistent files and arguments, never prompt.\"\n\t\t}\n\t},\n\t{\n\t\t\"name\": \"rmdir\",\n\t\t\"description\": \"Remove a directory\",\n\t\t\"usage\": \"rmdir [directory]\"\n\t},\n\t{\n\t\t\"name\": \"cd\",\n\t\t\"description\": \"Change directory\",\n\t\t\"usage\": \"cd [directory]\"\n\t},\n\t{\n\t\t\"name\": \"ls\",\n\t\t\"description\": \"List files in current directory\",\n\t\t\"usage\": \"ls\"\n\t},\n\t{\n\t\t\"name\": \"pwd\",\n\t\t\"description\": \"Print the current directory\",\n\t\t\"usage\": \"pwd\"\n\t},\n\t{\n\t\t\"name\": \"pkg\",\n\t\t\"description\": \"Package manager for Terbium\",\n\t\t\"usage\": \"pkg <command> [package]\",\n\t\t\"options\": {\n\t\t\t\"install\": \"Install a package\",\n\t\t\t\"remove\": \"Remove a package\",\n\t\t\t\"update\": \"Update a package\",\n\t\t\t\"list\": \"List all packages\",\n\t\t\t\"search\": \"Search for a package\"\n\t\t}\n\t},\n\t{\n\t\t\"name\": \"git\",\n\t\t\"description\": \"Git for Terbium\",\n\t\t\"usage\": \"git [command]\",\n\t\t\"options\": {\n\t\t\t\"clone\": \"Clones a repository\",\n\t\t\t\"init\": \"Initiates a blank repository\",\n\t\t\t\"checkout\": \"Switches the remote branch\",\n\t\t\t\"add\": \"Adds Files to Push\",\n\t\t\t\"rm\": \"Removes Files to Push\",\n\t\t\t\"status\": \"Shows status of Pending Changes (Not implemented yet)\",\n\t\t\t\"pull\": \"Pulls Latest Commit from remote repository\",\n\t\t\t\"push\": \"Pushes Latest Commit to remote repository\",\n\t\t\t\"fetch\": \"Fetches Latest repository Information\"\n\t\t}\n\t},\n\t{\n\t\t\"name\": \"sysfetch\",\n\t\t\"description\": \"Display system information\",\n\t\t\"usage\": \"sysfetch\"\n\t},\n\t{\n\t\t\"name\": \"curl\",\n\t\t\"description\": \"Allows you to fetch a script from the internet and run it or save it to the fs\",\n\t\t\"usage\": \"curl [script url], -k [path]\"\n\t},\n\t{\n\t\t\"name\": \"node\",\n\t\t\"description\": \"Run JavaScript with Node.js runtime\",\n\t\t\"usage\": \"node [options] [script.js] [arguments]\",\n\t\t\"options\": {\n\t\t\t\"-j, --jsh\": \"Launch the WebContainer JavaScript shell (jsh) instead of Node.js. When this flag is used, no other arguments are passed to the shell.\",\n\t\t\t\"-c, --check\": \"Check the script's syntax without executing it. Exits with an error code if script is invalid.\",\n\t\t\t\"-e, --eval string\": \"Evaluate string as JavaScript.\",\n\t\t\t\"-h, --help\": \"Print command-line options. The output of this option is less detailed than this document.\",\n\t\t\t\"-i, --interactive\": \"Open the REPL even if stdin does not appear to be a terminal.\",\n\t\t\t\"-p, --print string\": \"Identical to -e, but prints the result.\",\n\t\t\t\"-r, --require module\": \"Preload the specified module at startup. Follows require() module resolution rules.\",\n\t\t\t\"-v, --version\": \"Print node's version.\",\n\t\t\t\"--abort-on-uncaught-exception\": \"Aborting instead of exiting causes a core file to be generated for post-mortem analysis using a debugger (such as lldb, gdb, and mdb).\",\n\t\t\t\"--cpu-prof\": \"Start the V8 CPU profiler on start up, and write the CPU profile to disk before exit.\",\n\t\t\t\"--diagnostic-dir=directory\": \"Set the directory to which all diagnostic output files are written. Defaults to current working directory.\",\n\t\t\t\"--disable-warning=code-or-type\": \"Silence specific process warnings by code or type.\",\n\t\t\t\"--dns-result-order=order\": \"Set the default value of order in dns.lookup() and dnsPromises.lookup(). The value could be: ipv4first, ipv6first, verbatim.\",\n\t\t\t\"--enable-fips\": \"Enable FIPS-compliant crypto at startup. (Requires Node.js to be built against FIPS-compatible OpenSSL.)\",\n\t\t\t\"--enable-network-family-autoselection\": \"Enable network family autoselection algorithm.\",\n\t\t\t\"--enable-source-maps\": \"Enable Source Map v3 support for stack traces.\",\n\t\t\t\"--experimental-default-type=type\": \"Set the default module system to use.\",\n\t\t\t\"--experimental-import-meta-resolve\": \"Enable experimental import.meta.resolve() parent URL support.\",\n\t\t\t\"--experimental-loader=module\": \"Use the specified module as a custom loader.\",\n\t\t\t\"--experimental-permission\": \"Enable the experimental Permission Model.\",\n\t\t\t\"--experimental-sea-config\": \"Use this flag to generate a blob that can be injected into the Node.js binary to produce a single executable application.\",\n\t\t\t\"--force-context-aware\": \"Disable loading native addons that are not context-aware.\",\n\t\t\t\"--frozen-intrinsics\": \"Enable experimental frozen intrinsics like Array and Object.\",\n\t\t\t\"--heap-prof\": \"Start the V8 heap profiler on start up, and write the heap profile to disk before exit.\",\n\t\t\t\"--icu-data-dir=file\": \"Specify ICU data load path. (Overrides NODE_ICU_DATA.)\",\n\t\t\t\"--input-type=type\": \"Set the module type for string input via --eval, --print, or STDIN.\",\n\t\t\t\"--inspect[=[host:]port]\": \"Activate inspector on host:port. Default is 127.0.0.1:9229.\",\n\t\t\t\"--inspect-brk[=[host:]port]\": \"Activate inspector on host:port and break at start of user script.\",\n\t\t\t\"--inspect-wait[=[host:]port]\": \"Activate inspector on host:port and wait for debugger to be attached.\",\n\t\t\t\"--jitless\": \"Disable runtime allocation of executable memory.\",\n\t\t\t\"--max-http-header-size=size\": \"Specify the maximum size of HTTP headers in bytes. Defaults to 16KiB.\",\n\t\t\t\"--napi-modules\": \"This option is a no-op. It is kept for compatibility.\",\n\t\t\t\"--no-addons\": \"Disable loading native addons.\",\n\t\t\t\"--no-deprecation\": \"Silence deprecation warnings.\",\n\t\t\t\"--no-experimental-fetch\": \"Disable experimental Fetch API.\",\n\t\t\t\"--no-experimental-global-customevent\": \"Disable exposing CustomEvent on the global scope.\",\n\t\t\t\"--no-experimental-global-navigator\": \"Disable experimental Navigator API.\",\n\t\t\t\"--no-experimental-global-webcrypto\": \"Disable experimental Web Crypto API on the global scope.\",\n\t\t\t\"--no-experimental-repl-await\": \"Disable top-level await keyword support in REPL.\",\n\t\t\t\"--no-extra-info-on-fatal-exception\": \"Hide extra information on fatal exception that causes exit.\",\n\t\t\t\"--no-force-async-hooks-checks\": \"Disable runtime checks for async_hooks.\",\n\t\t\t\"--no-network-family-autoselection\": \"Disable network family autoselection algorithm.\",\n\t\t\t\"--no-warnings\": \"Silence all process warnings (including deprecations).\",\n\t\t\t\"--openssl-config=file\": \"Load an OpenSSL configuration file on startup.\",\n\t\t\t\"--openssl-shared-config\": \"Enable OpenSSL default configuration section, openssl_conf to be read from the OpenSSL configuration file.\",\n\t\t\t\"--pending-deprecation\": \"Emit pending deprecation warnings.\",\n\t\t\t\"--preserve-symlinks\": \"Follow symlinks when resolving modules.\",\n\t\t\t\"--preserve-symlinks-main\": \"Follow symlinks when resolving the main module.\",\n\t\t\t\"--prof\": \"Generate V8 profiler output.\",\n\t\t\t\"--redirect-warnings=file\": \"Write process warnings to the given file instead of printing to stderr.\",\n\t\t\t\"--report-compact\": \"Write reports in a compact format, single-line JSON.\",\n\t\t\t\"--report-dir=directory\": \"Directory where the report is written.\",\n\t\t\t\"--report-filename=filename\": \"Name of the file to which the report will be written.\",\n\t\t\t\"--report-on-fatalerror\": \"Generate a report on fatal errors.\",\n\t\t\t\"--report-on-signal\": \"Generate a report upon receiving a signal.\",\n\t\t\t\"--report-signal=signal\": \"Set the signal upon which a report is generated.\",\n\t\t\t\"--report-uncaught-exception\": \"Generate a report on uncaught exceptions.\",\n\t\t\t\"--secure-heap=n\": \"Initialize an OpenSSL secure heap of n bytes.\",\n\t\t\t\"--secure-heap-min=n\": \"Minimum allocation from the OpenSSL secure heap.\",\n\t\t\t\"--test\": \"Starts the Node.js command line test runner.\",\n\t\t\t\"--test-concurrency\": \"Set the number of test files to run in parallel.\",\n\t\t\t\"--test-name-pattern\": \"Run tests whose name matches the provided pattern.\",\n\t\t\t\"--test-reporter\": \"Set test reporter format.\",\n\t\t\t\"--test-reporter-destination\": \"Set test reporter destination.\",\n\t\t\t\"--test-shard\": \"Configure test suite shard.\",\n\t\t\t\"--throw-deprecation\": \"Throw errors for deprecations.\",\n\t\t\t\"--title=title\": \"Set process.title on startup.\",\n\t\t\t\"--tls-cipher-list=list\": \"Specify an alternative default TLS cipher list.\",\n\t\t\t\"--tls-keylog=file\": \"Log TLS key material to a file.\",\n\t\t\t\"--tls-max-v1.2\": \"Set default maxVersion to 'TLSv1.2'.\",\n\t\t\t\"--tls-max-v1.3\": \"Set default maxVersion to 'TLSv1.3'.\",\n\t\t\t\"--tls-min-v1.0\": \"Set default minVersion to 'TLSv1'.\",\n\t\t\t\"--tls-min-v1.1\": \"Set default minVersion to 'TLSv1.1'.\",\n\t\t\t\"--tls-min-v1.2\": \"Set default minVersion to 'TLSv1.2'.\",\n\t\t\t\"--tls-min-v1.3\": \"Set default minVersion to 'TLSv1.3'.\",\n\t\t\t\"--trace-atomics-wait\": \"Print short summaries of calls to Atomics.wait().\",\n\t\t\t\"--trace-deprecation\": \"Print stack traces for deprecations.\",\n\t\t\t\"--trace-event-categories categories\": \"A comma separated list of categories that should be traced when trace event tracing is enabled.\",\n\t\t\t\"--trace-event-file-pattern pattern\": \"Template string specifying the filepath for the trace event data.\",\n\t\t\t\"--trace-events-enabled\": \"Enable the collection of trace event tracing information.\",\n\t\t\t\"--trace-exit\": \"Print a stack trace whenever an environment is exited proactively.\",\n\t\t\t\"--trace-sync-io\": \"Print a stack trace whenever synchronous I/O is detected after the first turn of the event loop.\",\n\t\t\t\"--trace-tls\": \"Print TLS packet trace information to stderr.\",\n\t\t\t\"--trace-uncaught\": \"Print stack traces for uncaught exceptions.\",\n\t\t\t\"--trace-warnings\": \"Print stack traces for process warnings (including deprecations).\",\n\t\t\t\"--track-heap-objects\": \"Track heap object allocations for heap snapshots.\",\n\t\t\t\"--unhandled-rejections=mode\": \"Define the behavior for unhandled rejections. Mode can be one of: throw, strict, warn, warn-with-error-code, none.\",\n\t\t\t\"--use-bundled-ca\": \"Use bundled Mozilla CA store as supplied by current Node.js version.\",\n\t\t\t\"--use-largepages=mode\": \"Re-map the Node.js static code to large memory pages at startup.\",\n\t\t\t\"--use-openssl-ca\": \"Use OpenSSL's default CA store.\",\n\t\t\t\"--watch\": \"Restart the process when the command is edited.\",\n\t\t\t\"--watch-path\": \"Specify what paths to watch.\",\n\t\t\t\"--watch-preserve-output\": \"Disable clearing the console when watch mode restarts the process.\",\n\t\t\t\"--zero-fill-buffers\": \"Automatically zero-fills all newly allocated Buffer and SlowBuffer instances.\",\n\t\t\t\"--allow-child-process\": \"Allow spawning process when using the permission model.\",\n\t\t\t\"--allow-fs-read\": \"Allow file system read access when using the permission model.\",\n\t\t\t\"--allow-fs-write\": \"Allow file system write access when using the permission model.\",\n\t\t\t\"--allow-wasi\": \"Allow WASI when using the permission model.\",\n\t\t\t\"--allow-worker\": \"Allow creating worker threads when using the permission model.\"\n\t\t}\n\t},\n\t{\n\t\t\"name\": \"ping\",\n\t\t\"description\": \"Allows you to ping a site\",\n\t\t\"usage\": \"ping [url], -t [pingtime]\"\n\t},\n\t{\n\t\t\"name\": \"echo\",\n\t\t\"description\": \"Print text\",\n\t\t\"usage\": \"echo [text]\"\n\t},\n\t{\n\t\t\"name\": \"cat\",\n\t\t\"description\": \"Display the contents of a file\",\n\t\t\"usage\": \"cat [file]\"\n\t},\n\t{\n\t\t\"name\": \"taskkill\",\n\t\t\"description\": \"Lets you kill a task\",\n\t\t\"usage\": \"taskkill [PID]\",\n\t\t\"options\": {\n\t\t\t\"list\": \"Lists all Processes\"\n\t\t}\n\t},\n\t{\n\t\t\"name\": \"pkill\",\n\t\t\"description\": \"Lets you kill a task\",\n\t\t\"usage\": \"pkill [PID]\",\n\t\t\"options\": {\n\t\t\t\"list\": \"Lists all Processes\"\n\t\t}\n\t},\n\t{\n\t\t\"name\": \"clear\",\n\t\t\"description\": \"Clear the console\",\n\t\t\"usage\": \"clear\"\n\t},\n\t{\n\t\t\"name\": \"tb\",\n\t\t\"description\": \"Command for debugging terbium and interacting directly with it's API's\",\n\t\t\"usage\": \"tb [subcmd] [subcmd] ... <args>\"\n\t},\n\t{\n\t\t\"name\": \"unzip\",\n\t\t\"description\": \"unzip a file to a directory\",\n\t\t\"usage\": \"unzip [file] [directory]\"\n\t},\n\t{\n\t\t\"name\": \"exit\",\n\t\t\"description\": \"Exit the terminal\",\n\t\t\"usage\": \"exit\"\n\t},\n\t{\n\t\t\"name\": \"ssh\",\n\t\t\"description\": \"Connect to a remote server via SSH (uses WebContainer or libcurl.js)\",\n\t\t\"usage\": \"ssh [user@]hostname [options]\",\n\t\t\"options\": {\n\t\t\t\"--host\": \"Remote host to connect to\",\n\t\t\t\"--port\": \"Port number (default: 22)\",\n\t\t\t\"--username\": \"Username for authentication\",\n\t\t\t\"--password\": \"Password for authentication\",\n\t\t\t\"-p\": \"Prompt for password (secure)\",\n\t\t\t\"--privateKey\": \"Private key content for authentication\",\n\t\t\t\"-i\": \"Path to private key file\",\n\t\t\t\"--disconnect\": \"Disconnect current SSH session\",\n\t\t\t\"--status\": \"Show current connection status\",\n\t\t\t\"--help, -h\": \"Show help message\"\n\t\t}\n\t},\n\t{\n\t\t\"name\": \"ssh-keygen\",\n\t\t\"description\": \"Generate, manage and convert SSH keys\",\n\t\t\"usage\": \"ssh-keygen [options]\",\n\t\t\"options\": {\n\t\t\t\"-t type\": \"Specifies the type of key to create.\",\n\t\t\t\"-b bits\": \"Specifies the number of bits in the key.\",\n\t\t\t\"-f filename\": \"Specifies the filename of the key file.\"\n\t\t}\n\t}\n]\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/info.schema.json",
    "content": "{\n\t\"$schema\": \"http://json-schema.org/draft-07/schema#\",\n\t\"type\": \"array\",\n\t\"items\": {\n\t\t\"$ref\": \"#/definitions/command\"\n\t},\n\t\"definitions\": {\n\t\t\"command\": {\n\t\t\t\"type\": \"object\",\n\t\t\t\"required\": [\"name\", \"description\", \"usage\"],\n\t\t\t\"properties\": {\n\t\t\t\t\"name\": {\n\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t},\n\t\t\t\t\"description\": {\n\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t},\n\t\t\t\t\"usage\": {\n\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t},\n\t\t\t\t\"options\": {\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"additionalProperties\": {\n\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"additionalProperties\": false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/ls.js",
    "content": "async function ls(args) {\n\tif (args._raw === \"/mnt/\" || path === \"/mnt/\") {\n\t\tfunction centerText(text, width) {\n\t\t\tconst pad = Math.max(0, width - text.length);\n\t\t\tconst padLeft = Math.floor(pad / 2);\n\t\t\tconst padRight = pad - padLeft;\n\t\t\treturn \" \".repeat(padLeft) + text + \" \".repeat(padRight);\n\t\t}\n\t\tconst columns = [\n\t\t\t{ name: \"Name\", width: 12 },\n\t\t\t{ name: \"URL\", width: 32 },\n\t\t\t{ name: \"Mounted\", width: 10 },\n\t\t\t{ name: \"Mounted Path\", width: 20 },\n\t\t];\n\t\tconst header = \"| \" + columns.map(col => centerText(col.name, col.width)).join(\" | \") + \" |\";\n\t\tconst separator = \"|\" + columns.map(col => \"-\".repeat(col.width + 2)).join(\"|\") + \"|\";\n\t\tdisplayOutput(centerText(`TerbiumOS Network Storage Manager v1.2.0`, header.length));\n\t\tdisplayOutput(header);\n\t\tdisplayOutput(separator);\n\t\tfor (const instance of window.parent.tb.vfs.servers) {\n\t\t\tconst dav = instance[1];\n\t\t\tconst row = [centerText(dav.name, columns[0].width), centerText(dav.url, columns[1].width), centerText(dav.connected ? \"Yes\" : \"No\", columns[2].width), centerText(`/mnt/${dav.name.toLowerCase()}`, columns[3].width)];\n\t\t\tdisplayOutput(\"| \" + row.join(\" | \") + \" |\");\n\t\t}\n\t\tcreateNewCommandInput();\n\t} else if ((args._raw.includes(\"/mnt/\") && args._raw !== \"/mnt/\") || (path.includes(\"/mnt/\") && path !== \"/mnt/\")) {\n\t\ttry {\n\t\t\tconst match = args._raw.match(/\\/mnt\\/([^\\/]+)\\//) || path.match(/\\/mnt\\/([^\\/]+)\\//);\n\t\t\tconst davName = match ? match[1].toLowerCase() : \"\";\n\t\t\tconst contents = await tb.vfs.servers.get(davName).connection.promises.readdir(`${path}/${args._raw}`);\n\t\t\tfor (const entry of contents) {\n\t\t\t\tif (entry.type === \"directory\") {\n\t\t\t\t\tdisplayOutput(`${entry.basename}/`);\n\t\t\t\t} else {\n\t\t\t\t\tdisplayOutput(entry.basename);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tdisplayError(`TNSM ls: Dav Drive is not mounted with error: ${e.message}`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t\tcreateNewCommandInput();\n\t} else if (args._raw) {\n\t\ttry {\n\t\t\tconst entries = await tb.sh.promises.ls(path + args._raw);\n\t\t\tentries.forEach(entry => {\n\t\t\t\tdisplayOutput(entry.name);\n\t\t\t});\n\t\t\tcreateNewCommandInput();\n\t\t} catch {\n\t\t\tconst entries = await tb.sh.promises.ls(args._raw);\n\t\t\tentries.forEach(entry => {\n\t\t\t\tdisplayOutput(entry.name);\n\t\t\t});\n\t\t\tcreateNewCommandInput();\n\t\t}\n\t} else {\n\t\tconst entries = await tb.sh.promises.ls(path);\n\t\tentries.forEach(entry => {\n\t\t\tdisplayOutput(entry.name);\n\t\t});\n\t\tcreateNewCommandInput();\n\t}\n}\nls(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/mkdir.js",
    "content": "async function mkdir(args) {\n\tif (!args._raw) {\n\t\tdisplayError(\"mkdir: Please provide a directory name\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\n\tif (path.includes(\"/mnt/\")) {\n\t\ttry {\n\t\t\tconst match = path.match(/\\/mnt\\/([^\\/]+)\\//);\n\t\t\tconst davName = match ? match[1].toLowerCase() : \"\";\n\t\t\tconst np = path.replace(`/mnt/${davName.toLowerCase()}/`, \"\");\n\t\t\tawait tb.vfs.servers.get(davName).connection.promises.mkdir(`${np}/${args._raw}`);\n\t\t\tcreateNewCommandInput();\n\t\t} catch (e) {\n\t\t\tdisplayError(`TNSM mkdir: ${e.message}`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\ttry {\n\t\t\tawait window.parent.tb.fs.promises.mkdir(path + args._raw);\n\t\t\tcreateNewCommandInput();\n\t\t} catch (error) {\n\t\t\tif (error.code === \"ENOENT\") {\n\t\t\t\terror = \"No such file or directory\";\n\t\t\t}\n\t\t\tdisplayError(`mkdir: cannot create directory \"${args._raw}': ${error}`);\n\t\t\tcreateNewCommandInput();\n\t\t}\n\t}\n}\nmkdir(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/nano.js",
    "content": "async function nano(args) {\n\tconst filename = args._[0];\n\tif (!filename) {\n\t\tdisplayError(\"nano: filename required\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\tconst filepath = path + filename;\n\tlet content = \"\";\n\tlet isNewFile = false;\n\ttry {\n\t\tcontent = await terbium.fs.promises.readFile(filepath, \"utf8\");\n\t} catch (e) {\n\t\tcontent = \"\";\n\t\tisNewFile = true;\n\t}\n\tlet lines = content.split(\"\\n\");\n\twhile (lines.length > 0 && lines[lines.length - 1] === \"\") {\n\t\tlines.pop();\n\t}\n\tif (lines.length === 0) {\n\t\tlines = [\"\"];\n\t}\n\tlet currentLine = 0;\n\tlet currentCol = 0;\n\tlet modified = false;\n\tlet viewStart = 0;\n\tlet cutBuffer = \"\";\n\tterbium.setCommandProcessing(false);\n\tterm.write(\"\\x1b[2J\\x1b[H\");\n\tdrawEditor();\n\tconst disposable = term.onData(handleInput);\n\tsetTabTitle(\"TSH Nano\");\n\tfunction handleInput(data) {\n\t\tif (data === \"\\x1b\") {\n\t\t\treturn;\n\t\t}\n\t\tif (data === \"\\r\" || data === \"\\n\") {\n\t\t\tlines.splice(currentLine + 1, 0, lines[currentLine].slice(currentCol));\n\t\t\tlines[currentLine] = lines[currentLine].slice(0, currentCol);\n\t\t\tcurrentLine++;\n\t\t\tcurrentCol = 0;\n\t\t\tmodified = true;\n\t\t\tdrawEditor();\n\t\t} else if (data === \"\\x7f\") {\n\t\t\tif (currentCol > 0) {\n\t\t\t\tlines[currentLine] = lines[currentLine].slice(0, currentCol - 1) + lines[currentLine].slice(currentCol);\n\t\t\t\tcurrentCol--;\n\t\t\t\tmodified = true;\n\t\t\t\tdrawEditor();\n\t\t\t} else if (currentLine > 0) {\n\t\t\t\tcurrentCol = lines[currentLine - 1].length;\n\t\t\t\tlines[currentLine - 1] += lines[currentLine];\n\t\t\t\tlines.splice(currentLine, 1);\n\t\t\t\tcurrentLine--;\n\t\t\t\tmodified = true;\n\t\t\t\tdrawEditor();\n\t\t\t}\n\t\t} else if (data === \"\\x1b[A\") {\n\t\t\tif (currentLine > 0) {\n\t\t\t\tcurrentLine--;\n\t\t\t\tcurrentCol = Math.min(currentCol, lines[currentLine].length);\n\t\t\t\tdrawEditor();\n\t\t\t}\n\t\t} else if (data === \"\\x1b[B\") {\n\t\t\tif (currentLine < lines.length - 1) {\n\t\t\t\tcurrentLine++;\n\t\t\t\tcurrentCol = Math.min(currentCol, lines[currentLine].length);\n\t\t\t\tdrawEditor();\n\t\t\t}\n\t\t} else if (data === \"\\x1b[D\") {\n\t\t\tif (currentCol > 0) {\n\t\t\t\tcurrentCol--;\n\t\t\t\tdrawEditor();\n\t\t\t}\n\t\t} else if (data === \"\\x1b[C\") {\n\t\t\tif (currentCol < lines[currentLine].length) {\n\t\t\t\tcurrentCol++;\n\t\t\t\tdrawEditor();\n\t\t\t}\n\t\t} else if (data === \"\\x1b[H\") {\n\t\t\tcurrentCol = 0;\n\t\t\tdrawEditor();\n\t\t} else if (data === \"\\x1b[F\") {\n\t\t\tcurrentCol = lines[currentLine].length;\n\t\t\tdrawEditor();\n\t\t} else if (data === \"\\x1b[3~\") {\n\t\t\tif (currentCol < lines[currentLine].length) {\n\t\t\t\tlines[currentLine] = lines[currentLine].slice(0, currentCol) + lines[currentLine].slice(currentCol + 1);\n\t\t\t\tmodified = true;\n\t\t\t\tdrawEditor();\n\t\t\t} else if (currentLine < lines.length - 1) {\n\t\t\t\tlines[currentLine] += lines[currentLine + 1];\n\t\t\t\tlines.splice(currentLine + 1, 1);\n\t\t\t\tmodified = true;\n\t\t\t\tdrawEditor();\n\t\t\t}\n\t\t} else if (data.length === 1 && data >= \" \" && data <= \"~\") {\n\t\t\tlines[currentLine] = lines[currentLine].slice(0, currentCol) + data + lines[currentLine].slice(currentCol);\n\t\t\tcurrentCol++;\n\t\t\tmodified = true;\n\t\t\tdrawEditor();\n\t\t} else if (data === \"\\x07\") {\n\t\t\t// ^G Help\n\t\t\tshowHelp();\n\t\t} else if (data === \"\\x0f\") {\n\t\t\t// ^O Write Out\n\t\t\tsaveFile();\n\t\t} else if (data === \"\\x0b\") {\n\t\t\t// ^K Cut\n\t\t\tcutBuffer = lines[currentLine];\n\t\t\tlines.splice(currentLine, 1);\n\t\t\tif (lines.length === 0) lines = [\"\"];\n\t\t\tif (currentLine >= lines.length) currentLine = lines.length - 1;\n\t\t\tcurrentCol = Math.min(currentCol, lines[currentLine].length);\n\t\t\tmodified = true;\n\t\t\tdrawEditor();\n\t\t} else if (data === \"\\x15\") {\n\t\t\t// ^U Paste\n\t\t\tif (cutBuffer !== \"\") {\n\t\t\t\tlines.splice(currentLine + 1, 0, cutBuffer);\n\t\t\t\tcurrentLine++;\n\t\t\t\tcurrentCol = 0;\n\t\t\t\tmodified = true;\n\t\t\t\tdrawEditor();\n\t\t\t}\n\t\t} else if (data === \"\\x03\") {\n\t\t\t// ^C Cur Pos\n\t\t\tshowPosition();\n\t\t} else if (data === \"\\x16\") {\n\t\t\t// ^V Next Page\n\t\t\tconst pageSize = term.rows - 3;\n\t\t\tcurrentLine = Math.min(lines.length - 1, currentLine + pageSize);\n\t\t\tcurrentCol = Math.min(currentCol, lines[currentLine].length);\n\t\t\tdrawEditor();\n\t\t} else if (data === \"\\x19\") {\n\t\t\t// ^Y Prev Page\n\t\t\tconst pageSize = term.rows - 3;\n\t\t\tcurrentLine = Math.max(0, currentLine - pageSize);\n\t\t\tcurrentCol = Math.min(currentCol, lines[currentLine].length);\n\t\t\tdrawEditor();\n\t\t} else if (data === \"\\x18\") {\n\t\t\t// ^X Exit\n\t\t\tif (modified) {\n\t\t\t\tpromptSaveOnExit();\n\t\t\t} else {\n\t\t\t\texitEditor();\n\t\t\t}\n\t\t}\n\t}\n\tfunction drawEditor() {\n\t\tterm.write(\"\\x1b[2J\\x1b[H\");\n\t\tconst title = `TSH nano ${modified ? \"[ Modified ] \" : \"\"}${filename}`;\n\t\tconst centerCol = Math.max(1, Math.floor((term.cols - title.length) / 2));\n\t\tterm.write(`\\x1b[1;${centerCol}H\\x1b[7m${title}\\x1b[0m`);\n\t\tconst maxLines = term.rows - 3;\n\t\tviewStart = Math.max(0, Math.min(viewStart, currentLine - maxLines + 1));\n\t\tviewStart = Math.max(0, Math.min(viewStart, lines.length - maxLines));\n\t\tfor (let i = viewStart; i < Math.min(lines.length, viewStart + maxLines); i++) {\n\t\t\tconst line = lines[i] || \"\";\n\t\t\tconst row = i - viewStart + 2;\n\t\t\tterm.write(`\\x1b[${row};1H${line}`);\n\t\t\tterm.write(`\\x1b[${row};${line.length + 1}H\\x1b[K`);\n\t\t}\n\t\tfor (let r = Math.min(lines.length, viewStart + maxLines) - viewStart + 2; r <= term.rows - 1; r++) {\n\t\t\tterm.write(`\\x1b[${r};1H\\x1b[K`);\n\t\t}\n\t\tterm.write(`\\x1b[${term.rows};1H^G Get Help  ^O Write Out  ^W Where Is  ^K Cut Text  ^J Justify  ^C Cur Pos  ^X Exit`);\n\t\tconst cursorRow = currentLine - viewStart + 2;\n\t\tconst cursorCol = Math.min(currentCol + 1, (lines[currentLine] || \"\").length + 1);\n\t\tterm.write(`\\x1b[${cursorRow};${cursorCol}H`);\n\t}\n\tasync function saveFile() {\n\t\ttry {\n\t\t\tawait terbium.fs.promises.writeFile(filepath, lines.join(\"\\n\"), \"utf8\");\n\t\t\tmodified = false;\n\t\t\tisNewFile = false;\n\t\t\tdrawEditor();\n\t\t} catch (e) {\n\t\t\tterm.write(`\\x1b[${term.rows};1HError: ${e.message}`);\n\t\t\tsetTimeout(() => drawEditor(), 2000);\n\t\t}\n\t}\n\tfunction showHelp() {\n\t\tterm.write(\"\\x1b[2J\\x1b[H\");\n\t\tterm.writeln(\"Nano Help\");\n\t\tterm.writeln(\"\");\n\t\tterm.writeln(\"^G Get Help    ^O Write Out   ^W Where Is\");\n\t\tterm.writeln(\"^K Cut Text    ^J Justify     ^C Cur Pos\");\n\t\tterm.writeln(\"^X Exit        ^U Paste       ^V Next Page\");\n\t\tterm.writeln(\"^Y Prev Page\");\n\t\tterm.writeln(\"\");\n\t\tterm.writeln(\"Press any key to return to editor\");\n\t\tterm.onData(() => {\n\t\t\tdrawEditor();\n\t\t});\n\t}\n\tfunction showPosition() {\n\t\tconst lineNum = currentLine + 1;\n\t\tconst colNum = currentCol + 1;\n\t\tconst totalLines = lines.length;\n\t\tterm.write(`\\x1b[${term.rows};1HLine ${lineNum}/${totalLines} Col ${colNum}`);\n\t\tsetTimeout(() => drawEditor(), 2000);\n\t}\n\tfunction promptSaveOnExit() {\n\t\tterm.write(\"\\x1b[2J\\x1b[H\");\n\t\tterm.writeln(`Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ?`);\n\t\tterm.writeln(\"\");\n\t\tterm.writeln(\" Y Yes\");\n\t\tterm.writeln(\" N No\");\n\t\tterm.writeln(\" ^C Cancel\");\n\t\tterm.writeln(\"\");\n\t\tterm.write(\"Save modified buffer? \");\n\t\tlet response = \"\";\n\t\tconst promptDisposable = term.onData(data => {\n\t\t\tif (data === \"\\r\" || data === \"\\n\") {\n\t\t\t\tif (response.toLowerCase() === \"y\" || response.toLowerCase() === \"yes\") {\n\t\t\t\t\tsaveFile().then(() => exitEditor());\n\t\t\t\t} else if (response.toLowerCase() === \"n\" || response.toLowerCase() === \"no\") {\n\t\t\t\t\texitEditor();\n\t\t\t\t} else {\n\t\t\t\t\tpromptSaveOnExit();\n\t\t\t\t}\n\t\t\t\tpromptDisposable.dispose();\n\t\t\t} else if (data === \"\\x03\") {\n\t\t\t\t// ^C Cancel\n\t\t\t\tdrawEditor();\n\t\t\t\tpromptDisposable.dispose();\n\t\t\t} else if (data === \"\\x7f\") {\n\t\t\t\t// Backspace\n\t\t\t\tif (response.length > 0) {\n\t\t\t\t\tresponse = response.slice(0, -1);\n\t\t\t\t\tterm.write(\"\\b \\b\");\n\t\t\t\t}\n\t\t\t} else if (data.length === 1 && data >= \" \" && data <= \"~\") {\n\t\t\t\tresponse += data;\n\t\t\t\tterm.write(data);\n\t\t\t}\n\t\t});\n\t}\n\tfunction exitEditor() {\n\t\tsetTabTitle(\"Terbium TSH\");\n\t\tterbium.setCommandProcessing(true);\n\t\tdisposable.dispose();\n\t\tterm.write(\"\\x1b[2J\\x1b[H\");\n\t\tcreateNewCommandInput();\n\t}\n}\n\nnano(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/node.js",
    "content": "/**\n * @typedef {import(\"yargs-parser\").Arguments} argv\n * @typedef {import(\"xterm\").Terminal} Terminal\n */\n\n/**\n * CLI for **Node.js** subsystem\n * @param {argv} args The arguments to pass into **Node.js**\n * @param {Terminal} term - **XTERM.js** terminal instance\n */\nasync function node(args, term) {\n\tconst webContainer = tb.node.webContainer;\n\n\ttb.setCommandProcessing(false);\n\tterm.focus();\n\n\t// Check for jsh mode\n\tconst isJshMode = args.j === true || args.jsh === true;\n\n\tlet command;\n\tlet commandArgs;\n\n\tif (tb.node.isReady === false) {\n\t\tdisplayOutput(`\\r\\nWebContainer has not booted yet. Please wait a few seconds and try again.`);\n\t\tcreateNewCommandInput();\n\t\ttb.setCommandProcessing(true);\n\t\treturn;\n\t}\n\n\tif (isJshMode) {\n\t\tdisplayOutput(\"Starting WebContainer JavaScript shell...\");\n\t\tsetTabTitle(\"JSH: Terminal\");\n\t\tcommand = \"jsh\";\n\t\tcommandArgs = [];\n\t} else {\n\t\tdisplayOutput(\"Starting Node.js...\");\n\t\tsetTabTitle(\"NodeJS\");\n\t\tcommand = \"node\";\n\t\tconst { _: positionalArguments, $0: commandName, j: shortJshFlag, jsh: longJshFlag, _raw: rawArgumentString, ...remainingFlags } = args;\n\t\tconst positionalArgs = positionalArguments || [];\n\t\tcommandArgs = [...positionalArgs];\n\n\t\t// Process the POSIX flags\n\t\tfor (const [key, value] of Object.entries(remainingFlags)) {\n\t\t\tif (value === false) continue;\n\n\t\t\tif (key.length === 1) {\n\t\t\t\t// Handle single letter flags\n\t\t\t\tcommandArgs.push(`-${key}`);\n\t\t\t\tif (value !== true) {\n\t\t\t\t\tcommandArgs.push(String(value));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Long flags\n\t\t\t\tif (value === true) {\n\t\t\t\t\tcommandArgs.push(`--${key}`);\n\t\t\t\t} else {\n\t\t\t\t\tcommandArgs.push(`--${key}`, String(value));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst shell = await webContainer.spawn(command, commandArgs, {\n\t\tterminal: {\n\t\t\tcols: term.cols,\n\t\t\trows: term.rows,\n\t\t},\n\t});\n\n\tshell.output.pipeTo(\n\t\tnew WritableStream({\n\t\t\twrite(data) {\n\t\t\t\tterm.write(data);\n\t\t\t},\n\t\t}),\n\t);\n\n\tconst writer = shell.input.getWriter();\n\n\tconst inputHandler = term.onData(async data => {\n\t\tawait writer.write(data);\n\t});\n\n\tconst resizeHandler = () => {\n\t\tshell.resize({\n\t\t\tcols: term.cols,\n\t\t\trows: term.rows,\n\t\t});\n\t};\n\twindow.addEventListener(\"resize\", resizeHandler);\n\n\t// Cleanup\n\tconst exitCode = await shell.exit;\n\t// Cleanup listeners and handlers\n\tinputHandler.dispose();\n\twindow.removeEventListener(\"resize\", resizeHandler);\n\ttb.setCommandProcessing(true);\n\tsetTabTitle(\"Terbium TSH\");\n\t// Display exit message\n\tdisplayOutput(`\\r\\nWebContainer shell exited with code ${exitCode}`);\n\t// Give the focus back to the terminal\n\tcreateNewCommandInput();\n}\nnode(args, term);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/ping.js",
    "content": "async function ping(args) {\n\tconst numPings = 5;\n\tlet url = args._raw;\n\tlet totalResponseTime = 0;\n\tlet packetsReceived = 0;\n\n\tif (!url) displayError(\"No Url was Provided\");\n\tif (!url.includes(\"http://\") && !url.includes(\"https://\")) {\n\t\turl = \"http://\" + url;\n\t}\n\n\tfor (let i = 0; i < numPings; i++) {\n\t\tconst startTime = Date.now();\n\t\tconsole.log(url);\n\t\ttry {\n\t\t\tconst response = await window.parent.tb.libcurl.fetch(url);\n\t\t\tconsole.log(response);\n\t\t\tif (response.ok) {\n\t\t\t\tpacketsReceived++;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tdisplayOutput(`Error Reaching Site: Site turned ${error.response?.status} Error when pinged`);\n\t\t}\n\n\t\tconst endTime = Date.now();\n\t\tconst responseTime = endTime - startTime;\n\t\ttotalResponseTime += responseTime;\n\t\tdisplayOutput(`Ping ${url} - Time: ${responseTime}ms`);\n\t}\n\n\tconst avgResponseTime = totalResponseTime / numPings;\n\tconst percentReceived = (packetsReceived / numPings) * 100;\n\tconst percentLost = 100 - percentReceived;\n\tdisplayOutput(`Pinged ${url} ${numPings} times: ${avgResponseTime.toFixed(2)}ms average, ${percentLost.toFixed(2)}% packet loss, ${percentReceived.toFixed(2)}% packets received`);\n\tcreateNewCommandInput();\n}\n\nping(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/pkg.js",
    "content": "async function pkg(args) {\n\tlet availableCommands = [\n\t\t\"pkg <command> -h: Display help for <command>.\",\n\t\t\"pkg install <package-name>: Install an app matching <package-name> from the repo.\",\n\t\t\"pkg remove <package-name>: Uninstall an app matching <package-name> from the repo.\",\n\t\t\"pkg update: <package-name>: Update a package to the latest version if available.\",\n\t\t\"pkg list: Shows a list of installed apps.\",\n\t\t\"pkg search <package-name>: Search for an app matching <package-name> in the repo.\",\n\t\t\"pkg repo: Changes the Package Managers Fetch repo (Use -r to remove the repo you added)\",\n\t];\n\tlet repo = sessionStorage.getItem(\"pkg-repo\") || JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, \"utf8\"))[0].url;\n\tlet rType = sessionStorage.getItem(\"pkg-type\") || \"terbium\";\n\tswitch (args._[0]) {\n\t\tcase \"install\":\n\t\t\tif (args._[1]) {\n\t\t\t\tconst response = await tb.libcurl.fetch(repo);\n\t\t\t\tlet repoData = rType === \"terbium\" ? (await response.json()).apps : (await (await tb.libcurl.fetch(repo.replace(\"manifest.json\", \"list.json\"))).json()).apps;\n\t\t\t\tconst packageName = args._[1];\n\t\t\t\tconst exactMatch = repoData.find(pkg => pkg.name.toLowerCase() === packageName.toLowerCase());\n\t\t\t\tif (exactMatch) {\n\t\t\t\t\tdisplayOutput(`Installing ${exactMatch.name}...`);\n\t\t\t\t\tif (exactMatch.requirements) {\n\t\t\t\t\t\tif (exactMatch.requirements.os && semverCompare(exactMatch.requirements.os, window.parent.tb.system.version()) > 0) {\n\t\t\t\t\t\t\tdisplayError(`This app requires terbium version: ${exactMatch.requirements.os} or later`);\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} else if (exactMatch.requirements.proxy && exactMatch.requirements.proxy !== (await window.parent.tb.proxy.get())) {\n\t\t\t\t\t\t\tdisplayError(`This app requires ${exactMatch.requirements.proxy} as the default proxy.`);\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst installed = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\t\t\tlet type;\n\t\t\t\t\tif (\"pkg-download\" in exactMatch) {\n\t\t\t\t\t\ttype = \"TAPP\";\n\t\t\t\t\t} else if (\"anura-pkg\" in exactMatch || rType === \"anura\") {\n\t\t\t\t\t\ttype = \"anura\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttype = \"web\";\n\t\t\t\t\t}\n\t\t\t\t\tif (installed.some(app => app.name.toLowerCase() === exactMatch.name.toLowerCase())) {\n\t\t\t\t\t\tdisplayOutput(`The app \"${exactMatch.name}\" is already installed.`);\n\t\t\t\t\t\tdisplayOutput(\"Do you want to reinstall it? (y/n)\");\n\t\t\t\t\t\tterm.write(\"\\r\\n> \");\n\t\t\t\t\t\tconst onData = async function (input) {\n\t\t\t\t\t\t\tconst userInput = input.trim().toLowerCase();\n\t\t\t\t\t\t\tif (userInput === \"y\") {\n\t\t\t\t\t\t\t\tdisplayOutput(\"\");\n\t\t\t\t\t\t\t\tdisplayOutput(`Reinstalling ${exactMatch.name}...`);\n\t\t\t\t\t\t\t\tawait tb.launcher.removeApp(exactMatch.name);\n\t\t\t\t\t\t\t\tawait installApp(exactMatch, type, installed);\n\t\t\t\t\t\t\t\tdisplayOutput(`${exactMatch.name} reinstalled successfully!`);\n\t\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisposable.dispose();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst disposable = term.onData(onData);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait installApp(exactMatch, type, installed);\n\t\t\t\t\t\tdisplayOutput(`${exactMatch.name} installed successfully!`);\n\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdisplayOutput(`No package found with the name \"${packageName}\".`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdisplayOutput(\"Usage: pkg install <package-name>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase \"remove\":\n\t\t\tif (args._[1]) {\n\t\t\t\tconst packageName = args._[1];\n\t\t\t\tlet installed = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\t\tconst appIndex = installed.findIndex(app => app.name.toLowerCase() === packageName.toLowerCase());\n\t\t\t\tif (appIndex !== -1) {\n\t\t\t\t\tconst app = installed[appIndex];\n\t\t\t\t\tinstalled.splice(appIndex, 1);\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/apps/installed.json\", JSON.stringify(installed, null, 2), \"utf8\");\n\t\t\t\t\tdisplayOutput(`Uninstalling ${app.name}...`);\n\t\t\t\t\tconst configPath = app.config;\n\t\t\t\t\tconsole.log(configPath);\n\t\t\t\t\tif (configPath.endsWith(\"index.json\")) {\n\t\t\t\t\t\tlet webApps = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/web_apps.json\", \"utf8\"));\n\t\t\t\t\t\tif (Array.isArray(webApps.apps)) {\n\t\t\t\t\t\t\tconst waIndex = webApps.apps.findIndex(webApp => webApp.name && webApp.name.toLowerCase() === app.name.toLowerCase());\n\t\t\t\t\t\t\tif (waIndex !== -1) {\n\t\t\t\t\t\t\t\twebApps.apps.splice(waIndex, 1);\n\t\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/apps/web_apps.json\", JSON.stringify(webApps, null, 2), \"utf8\");\n\t\t\t\t\t\t\t\tawait tb.launcher.removeApp(app.name);\n\t\t\t\t\t\t\t\tdisplayOutput(`${app.name} has been uninstalled.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (configPath.endsWith(\"manifest.json\")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait window.parent.tb.fs.promises.unlink(`/system/etc/anura/configs/${app.name}.json`);\n\t\t\t\t\t\t\tawait tb.sh.rm(configPath.replace(\"/manifest.json\", \"/\"), { recursive: true });\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\tawait tb.sh.rm(`/apps/anura/${app.name}`, { recursive: true });\n\t\t\t\t\t\tawait tb.launcher.removeApp(app.name);\n\t\t\t\t\t\tdelete window.parent.anura.apps[app.package];\n\t\t\t\t\t\tdisplayOutput(`${app.name} has been uninstalled.`);\n\t\t\t\t\t} else if (configPath.endsWith(\".tbconfig\")) {\n\t\t\t\t\t\tawait tb.sh.rm(configPath.replace(\"/.tbconfig\", \"/\"), { recursive: true });\n\t\t\t\t\t\tawait tb.launcher.removeApp(app.name);\n\t\t\t\t\t\tdisplayOutput(`${app.name} has been uninstalled.`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdisplayOutput(`No installed app found with the name \"${packageName}\".`);\n\t\t\t\t}\n\t\t\t\tcreateNewCommandInput();\n\t\t\t} else {\n\t\t\t\tdisplayOutput(\"Usage: pkg remove <package-name>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"update\":\n\t\t\tif (args._[1]) {\n\t\t\t\tdisplayOutput(\"Checking for updates...\");\n\t\t\t\tconst config = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/system/${args._[1].toLowerCase()}.tapp/.tbconfig`, \"utf8\"));\n\t\t\t\tconst response = await tb.libcurl.fetch(repo);\n\t\t\t\tlet repoData = rType === \"terbium\" ? (await response.json()).apps : (await (await tb.libcurl.fetch(repo.replace(\"manifest.json\", \"list.json\"))).json()).apps;\n\t\t\t\tconst packageName = args._[1];\n\t\t\t\tconst exactMatch = repoData.find(pkg => pkg.name.toLowerCase() === packageName.toLowerCase());\n\t\t\t\tif (exactMatch.requirements) {\n\t\t\t\t\tif (exactMatch.requirements.os && semverCompare(exactMatch.requirements.os, window.parent.tb.system.version()) > 0) {\n\t\t\t\t\t\tdisplayError(`This app requires terbium version: ${exactMatch.requirements.os} or later`);\n\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} else if (exactMatch.requirements.proxy && exactMatch.requirements.proxy !== (await window.parent.tb.proxy.get())) {\n\t\t\t\t\t\tdisplayError(`This app requires ${exactMatch.requirements.proxy} as the default proxy.`);\n\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (config.version !== exactMatch.version) {\n\t\t\t\t\tdisplayOutput(`Updating ${exactMatch.name} from version ${config.version} to ${exactMatch.version}...`);\n\t\t\t\t\tawait tb.sh.promises.rm(`/apps/system/${args._[1].toLowerCase()}.tapp/`, { recursive: true });\n\t\t\t\t\tawait installApp(exactMatch, \"TAPP\");\n\t\t\t\t\tdisplayOutput(`${exactMatch.name} updated successfully!`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdisplayOutput(\"Usage: pkg update <package-name>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"list\":\n\t\t\tdisplayOutput(\"Installed Packages for this system:\");\n\t\t\tconst installed = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\tfor (const app of installed) {\n\t\t\t\tdisplayOutput(`${app.name} - ${app.user}`);\n\t\t\t}\n\t\t\tdisplayOutput(\"\");\n\t\t\tdisplayOutput(`${installed.length} are installed.`);\n\t\t\tcreateNewCommandInput();\n\t\t\tbreak;\n\t\tcase \"search\":\n\t\t\tif (args._[1]) {\n\t\t\t\tconst response = await tb.libcurl.fetch(repo);\n\t\t\t\tlet repoData = rType === \"terbium\" ? (await response.json()).apps : (await (await tb.libcurl.fetch(repo.replace(\"manifest.json\", \"list.json\"))).json()).apps;\n\t\t\t\tconst searchTerm = args._[1].toLowerCase();\n\t\t\t\tconst exactMatch = repoData.find(pkg => pkg.name.toLowerCase() === searchTerm);\n\t\t\t\tif (exactMatch) {\n\t\t\t\t\tdisplayOutput(`Found package: ${exactMatch.name}`);\n\t\t\t\t} else {\n\t\t\t\t\tconst potentialMatches = repoData.filter(pkg => pkg.name.toLowerCase().includes(searchTerm));\n\t\t\t\t\tif (potentialMatches.length > 0) {\n\t\t\t\t\t\tdisplayOutput(`No exact match found for \"${searchTerm}\".`);\n\t\t\t\t\t\tdisplayOutput(\"Did you mean? Potential match(es):\");\n\t\t\t\t\t\tfor (const match of potentialMatches) {\n\t\t\t\t\t\t\tdisplayOutput(`  - ${match.name}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdisplayOutput(\"No matching packages found.\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcreateNewCommandInput();\n\t\t\t} else {\n\t\t\t\tdisplayOutput(\"Usage: pkg search <package-name>\");\n\t\t\t\tcreateNewCommandInput();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"repo\":\n\t\t\tswitch (args._[1]) {\n\t\t\t\tcase \"r\":\n\t\t\t\tcase \"remove\":\n\t\t\t\t\tlet r = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, \"utf8\"));\n\t\t\t\t\tr = r.filter(r => r.url !== args._[2]);\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, JSON.stringify(r));\n\t\t\t\t\tdisplayOutput(`Removed ${args._[2]} from the repo list`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"a\":\n\t\t\t\tcase \"add\":\n\t\t\t\t\tlet newrepo = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, \"utf8\"));\n\t\t\t\t\tnewrepo.push({ url: args._[2] });\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, JSON.stringify(newrepo));\n\t\t\t\t\tdisplayOutput(`Added ${args._[2]} to the repo list`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"l\":\n\t\t\t\tcase \"list\":\n\t\t\t\t\tlet repoList = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, \"utf8\"));\n\t\t\t\t\tdisplayOutput(\"Available Repositories:\");\n\t\t\t\t\trepoList.forEach(repo => {\n\t\t\t\t\t\tdisplayOutput(` - ${repo.url}`);\n\t\t\t\t\t});\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"s\":\n\t\t\t\tcase \"set\":\n\t\t\t\t\trepo = args._[2];\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst response = await fetch(repo);\n\t\t\t\t\t\tconst jsonData = await response.json();\n\t\t\t\t\t\tlet repoType;\n\t\t\t\t\t\tif (\"repo\" in jsonData) {\n\t\t\t\t\t\t\trepoType = \"terbium\";\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trepoType = \"anura\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsessionStorage.setItem(\"pkg-repo\", repo);\n\t\t\t\t\t\tsessionStorage.setItem(\"pkg-type\", repoType);\n\t\t\t\t\t\tdisplayOutput(`Set repo to ${repo} (type: ${repoType})`);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tdisplayError(`Failed to fetch or detect repo type: ${e.message}`);\n\t\t\t\t\t}\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tdisplayOutput(\"Usage: pkg repo <a/r/l/s> [repo-url]\");\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"help\":\n\t\tdefault:\n\t\t\tdisplayOutput(`TPKG v1.4.3 - February 2026`);\n\t\t\tdisplayOutput(`Usage: pkg <command>`);\n\t\t\tdisplayOutput(\" \");\n\t\t\tdisplayOutput(\"All commands:\");\n\t\t\tfor (let command in availableCommands) {\n\t\t\t\tcommand = availableCommands[command];\n\t\t\t\tlet [cmd, description] = command.split(\": \");\n\t\t\t\tdisplayOutput(`   ${cmd.padEnd(40)} ${description}`);\n\t\t\t}\n\t\t\tcreateNewCommandInput();\n\t\t\tbreak;\n\t}\n}\n\nasync function installApp(app, type) {\n\tlet repo = sessionStorage.getItem(\"pkg-repo\") || JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/app store/repos.json`, \"utf8\"))[0].url;\n\tswitch (type) {\n\t\tcase \"web\":\n\t\t\tlet appPath = `/apps/user/${await window.parent.tb.user.username()}/${app.name}`;\n\t\t\tlet appIndex = {\n\t\t\t\tname: app.name,\n\t\t\t\ticon: app.icon,\n\t\t\t\tdescription: app.description,\n\t\t\t\tauthors: app.authors,\n\t\t\t\t\"pkg-name\": app[\"pkg-name\"],\n\t\t\t\tversion: app.version,\n\t\t\t\timages: app.images,\n\t\t\t\twmArgs: app.wmArgs,\n\t\t\t};\n\t\t\tif (!(await dirExists(appPath))) {\n\t\t\t\tawait window.parent.tb.fs.promises.mkdir(appPath);\n\t\t\t}\n\t\t\tawait window.parent.tb.fs.promises.writeFile(`${appPath}/index.json`, JSON.stringify(appIndex));\n\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/web_apps.json\", \"utf8\"));\n\t\t\tapps[\"apps\"].push(app[\"pkg-name\"]);\n\t\t\tawait window.parent.tb.fs.promises.writeFile(\"/apps/web_apps.json\", JSON.stringify(apps));\n\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\ttitle: app[\"wmArgs\"][\"title\"],\n\t\t\t\tname: app.name,\n\t\t\t\ticon: app.icon,\n\t\t\t\tsrc: app[\"wmArgs\"][\"src\"],\n\t\t\t\tsize: {\n\t\t\t\t\twidth: app[\"wmArgs\"][\"size\"][\"width\"],\n\t\t\t\t\theight: app[\"wmArgs\"][\"size\"][\"height\"],\n\t\t\t\t},\n\t\t\t\tsingle: app[\"wmArgs\"][\"single\"],\n\t\t\t\tresizable: app[\"wmArgs\"][\"resizable\"],\n\t\t\t\tcontrols: app[\"wmArgs\"][\"controls\"],\n\t\t\t\tmessage: app[\"wmArgs\"][\"message\"],\n\t\t\t\tproxy: app[\"wmArgs\"][\"proxy\"],\n\t\t\t\tsnapable: app[\"wmArgs\"][\"snapable\"],\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\tapps.push({\n\t\t\t\t\tname: app.name,\n\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\tconfig: `/apps/user/${await window.parent.tb.user.username()}/${app.name}/index.json`,\n\t\t\t\t});\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t} catch {\n\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\tconfig: `/apps/user/${await window.parent.tb.user.username()}/${app.name}/index.json`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"TAPP\":\n\t\t\tconst appName = app.name.toLowerCase();\n\t\t\tconst DLPath = `/apps/${appName}`;\n\t\t\tconst downloadUrl = app[\"pkg-download\"];\n\t\t\ttry {\n\t\t\t\tawait tb.system.download(downloadUrl, `${DLPath}.zip`);\n\t\t\t\tconst targetDirectory = `/apps/system/${appName}.tapp/`;\n\t\t\t\tawait unzip(`/apps/${appName}.zip`, targetDirectory);\n\t\t\t\tconst appConf = await window.parent.tb.fs.promises.readFile(`/apps/system/${appName}.tapp/.tbconfig`, \"utf8\");\n\t\t\t\tconst appData = JSON.parse(appConf);\n\t\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\t\ttitle:\n\t\t\t\t\t\ttypeof appData.wmArgs.title === \"object\"\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\ttext: appData.wmArgs.title.text,\n\t\t\t\t\t\t\t\t\tweight: appData.wmArgs.title.weight,\n\t\t\t\t\t\t\t\t\thtml: appData.wmArgs.title.html,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: appData.wmArgs.title,\n\t\t\t\t\tname: appData.title,\n\t\t\t\t\ticon: `/fs/apps/system/${appName}.tapp/${appData.icon}`,\n\t\t\t\t\tsrc: `/fs/apps/system/${appName}.tapp/${appData.wmArgs.src}`,\n\t\t\t\t\tsize: {\n\t\t\t\t\t\twidth: appData.wmArgs.size.width,\n\t\t\t\t\t\theight: appData.wmArgs.size.height,\n\t\t\t\t\t},\n\t\t\t\t\tsingle: appData.wmArgs.single,\n\t\t\t\t\tresizable: appData.wmArgs.resizable,\n\t\t\t\t\tcontrols: appData.wmArgs.controls,\n\t\t\t\t\tmessage: appData.wmArgs.message,\n\t\t\t\t\tsnapable: appData.wmArgs.snapable,\n\t\t\t\t});\n\t\t\t\tawait window.parent.tb.fs.promises.unlink(`${DLPath}.zip`);\n\t\t\t\ttry {\n\t\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\tapps.push({\n\t\t\t\t\t\tname: appName,\n\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\tconfig: `/apps/system/${appName}.tapp/.tbconfig`,\n\t\t\t\t\t});\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t\t} catch {\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tname: appName,\n\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\tconfig: `/apps/system/${appName}.tapp/.tbconfig`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tdisplayError(`Failed to install ${appName} with reason: ${e.message}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"anura\":\n\t\t\tconsole.log(app);\n\t\t\tconst aName = app.name || app.package;\n\t\t\tconst APath = `/apps/anura/${aName}`;\n\t\t\tlet aDL;\n\t\t\tif (\"anura-pkg\" in app) {\n\t\t\t\taDL = app[\"anura-pkg\"];\n\t\t\t} else {\n\t\t\t\taDL = `${repo.replace(\"manifest.json\", \"\")}/apps/${app.package}/${app.data}`;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait tb.system.download(aDL, `${APath}.zip`);\n\t\t\t\tconst targetDirectory = `/apps/anura/${aName}/`;\n\t\t\t\tawait unzip(`/apps/anura/${aName}.zip`, targetDirectory);\n\t\t\t\tconst appConf = await window.parent.tb.fs.promises.readFile(`/apps/anura/${aName}/manifest.json`, \"utf8\");\n\t\t\t\tconst appData = JSON.parse(appConf);\n\t\t\t\tawait window.parent.tb.launcher.addApp({\n\t\t\t\t\tname: appData.name,\n\t\t\t\t\ttitle: appData.wininfo.title,\n\t\t\t\t\ticon: `/fs/apps/anura/${app.name}/${appData.icon}`,\n\t\t\t\t\tsrc: `/fs/apps/anura/${app.name}/${appData.index}`,\n\t\t\t\t\tsize: {\n\t\t\t\t\t\twidth: appData.wininfo.width,\n\t\t\t\t\t\theight: appData.wininfo.height,\n\t\t\t\t\t},\n\t\t\t\t\tsingle: appData.wininfo.allowMultipleInstance,\n\t\t\t\t});\n\t\t\t\twindow.parent.anura.apps[appData.package] = {\n\t\t\t\t\ttitle: appData.name,\n\t\t\t\t\ticon: appData.icon,\n\t\t\t\t\tid: appData.package,\n\t\t\t\t};\n\t\t\t\tawait window.parent.tb.fs.promises.unlink(`${APath}.zip`);\n\t\t\t\ttry {\n\t\t\t\t\tlet apps = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\tapps.push({\n\t\t\t\t\t\tname: appData.name,\n\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\tconfig: `/apps/anura/${aName}/manifest.json`,\n\t\t\t\t\t});\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(apps));\n\t\t\t\t} catch {\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`/apps/installed.json`,\n\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: appData.name,\n\t\t\t\t\t\t\t\tuser: await window.parent.tb.user.username(),\n\t\t\t\t\t\t\t\tconfig: `/apps/anura/${aName}/manifest.json`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tdisplayError(`Failed to install ${aName} with reason: ${e.message}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\nasync function unzip(path, target) {\n\tconst runUnzip = async () => {\n\t\tconst response = await fetch(\"/fs/\" + path);\n\t\tconst zipFileContent = await response.arrayBuffer();\n\t\tif (!(await dirExists(target))) {\n\t\t\tawait window.parent.tb.fs.promises.mkdir(target, { recursive: true });\n\t\t}\n\t\tconst compressedFiles = window.parent.tb.fflate.unzipSync(new Uint8Array(zipFileContent));\n\t\tfor (const [relativePath, content] of Object.entries(compressedFiles)) {\n\t\t\tconst fullPath = `${target}/${relativePath}`;\n\t\t\tconst pathParts = fullPath.split(\"/\");\n\t\t\tlet currentPath = \"\";\n\t\t\tfor (let i = 0; i < pathParts.length; i++) {\n\t\t\t\tcurrentPath += pathParts[i] + \"/\";\n\t\t\t\tif (i === pathParts.length - 1 && !relativePath.endsWith(\"/\")) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(`touch ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t\tdisplayOutput(`touch ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(currentPath.slice(0, -1), Filer.Buffer.from(content));\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tdisplayOutput(`Cant make ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t\tconsole.log(`Cant make ${currentPath.slice(0, -1)}`);\n\t\t\t\t\t}\n\t\t\t\t} else if (!(await dirExists(currentPath))) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(`mkdir ${currentPath}`);\n\t\t\t\t\t\tdisplayOutput(`mkdir ${currentPath}`);\n\t\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(currentPath);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tconsole.log(`Cant make ${currentPath}`);\n\t\t\t\t\t\tdisplayOutput(`Cant make ${currentPath}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (relativePath.endsWith(\"/\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconsole.log(`mkdir fp ${fullPath}`);\n\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(fullPath);\n\t\t\t\t} catch {\n\t\t\t\t\tconsole.log(`Cant make ${fullPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn \"Done!\";\n\t};\n\n\treturn window.parent.tb.notification.Installing(\n\t\t{\n\t\t\tmessage: \"Installing package files...\",\n\t\t\tapplication: \"Terminal\",\n\t\t\ticonSrc: \"/fs/apps/system/terminal.tapp/icon.svg\",\n\t\t},\n\t\trunUnzip(),\n\t\t{\n\t\t\tmessage: \"Finished extracting package\",\n\t\t\tapplication: \"Terminal\",\n\t\t\ticonSrc: \"/fs/apps/system/terminal.tapp/icon.svg\",\n\t\t\ttime: 3200,\n\t\t},\n\t\t{\n\t\t\tmessage: \"Failed to extract package\",\n\t\t\tapplication: \"Terminal\",\n\t\t\ticonSrc: \"/fs/apps/system/terminal.tapp/icon.svg\",\n\t\t\ttime: 2500,\n\t\t},\n\t);\n}\n\nconst dirExists = async path => {\n\treturn new Promise(resolve => {\n\t\twindow.parent.tb.fs.stat(path, (err, stats) => {\n\t\t\tif (err) {\n\t\t\t\tif (err.code === \"ENOENT\") {\n\t\t\t\t\tresolve(false);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t\tresolve(false);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst exists = stats.type === \"DIRECTORY\";\n\t\t\t\tresolve(exists);\n\t\t\t}\n\t\t});\n\t});\n};\n\n/**\n * Compares two semantic version strings.\n * @param {string} a - The first version string.\n * @param {string} b - The second version string.\n * @returns {number} - Returns 1 if a > b, -1 if a < b, 0 if they are equal.\n */\nconst semverCompare = (a, b) => {\n\tconst pa = a.split(/[-.]/);\n\tconst pb = b.split(/[-.]/);\n\tfor (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n\t\tconst na = pa[i] || \"0\";\n\t\tconst nb = pb[i] || \"0\";\n\t\tif (!isNaN(na) && !isNaN(nb)) {\n\t\t\tif (+na > +nb) return 1;\n\t\t\tif (+na < +nb) return -1;\n\t\t} else {\n\t\t\tif (na > nb) return 1;\n\t\t\tif (na < nb) return -1;\n\t\t}\n\t}\n\treturn 0;\n};\n\npkg(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/pkill.js",
    "content": "function pkill(args) {\n\tif (args._raw.includes(\"list\")) {\n\t\tconst windows = window.parent.tb.process.list();\n\t\tObject.values(windows).forEach(window => {\n\t\t\tdisplayOutput(`${window.name}, ${window.pid}`);\n\t\t});\n\t\tcreateNewCommandInput();\n\t} else {\n\t\ttry {\n\t\t\twindow.tb.process.kill(args._raw);\n\t\t\tdisplayOutput(`Successfully killed task with pid: ${args._raw}`);\n\t\t} catch {\n\t\t\tdisplayError(\"Not task found with that PID\");\n\t\t}\n\t\tcreateNewCommandInput();\n\t}\n}\n\npkill(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/pwd.js",
    "content": "function pwd(args) {\n\tdisplayOutput(path);\n\tcreateNewCommandInput();\n}\npwd(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/rm.js",
    "content": "async function rm(args) {\n\tlet availableOptions = [\n\t\t\"-f: ignore nonexistent files and arguments, never prompt.\",\n\t\t\"-r: remove directories and their contents recursively.; optionally you can also use -rf to remove directories and their contents recursively without prompt.\",\n\t\t\"-v: explain what is being done (not default).\",\n\t\t\"-d: remove empty directories.; you should use rmdir instead.\",\n\t];\n\n\tif (!args._raw || args._raw.includes(\"-h\")) {\n\t\tdisplayOutput(\"Usage: rm [OPTION] [FILE]\");\n\t\tdisplayOutput(\"Remove (unlink) the FILE(s).\");\n\t\tdisplayOutput(\" \");\n\t\tdisplayOutput(\"Options:\");\n\t\tfor (let option of availableOptions) {\n\t\t\tlet nspace = \" \";\n\t\t\tlet [opt, desc] = option.split(\": \");\n\t\t\tlet optionally = desc.split(\";\")[1];\n\t\t\tdesc = desc.replace(\";\", \"\").replace(optionally, \"\");\n\t\t\tdisplayOutput(`  ${opt.padEnd(10)} ${desc}`);\n\t\t\tif (optionally) {\n\t\t\t\tdisplayOutput(`  ${nspace.padEnd(10)}${optionally}`);\n\t\t\t}\n\t\t}\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\tconst user = sessionStorage.getItem(\"currAcc\");\n\tconst systemDirs = [\"/home\", `/home/${user}/documents`, `/home/${user}/videos`, `/home/${user}/pictures`, `/home/${user}/music`];\n\n\tfor (let sdir of systemDirs) {\n\t\tif (path === sdir) {\n\t\t\tdisplayOutput(`rm: cannot remove \"${path}\": Is a system directory`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tlet options = {\n\t\tforce: false,\n\t\trecursive: false,\n\t\tverbose: false,\n\t\tdirectory: false,\n\t};\n\n\tif (args._raw.includes(\"-rf\")) {\n\t\toptions.force = true;\n\t\toptions.recursive = true;\n\t}\n\tif (args._raw.includes(\"-r\")) {\n\t\toptions.recursive = true;\n\t}\n\tif (args._raw.includes(\"-f\")) {\n\t\toptions.force = true;\n\t}\n\tif (args._raw.includes(\"-v\")) {\n\t\toptions.verbose = true;\n\t}\n\tif (args._raw.includes(\"-d\")) {\n\t\toptions.directory = true;\n\t}\n\tconst toDel = `${path}/${args._raw.replace(/^-f|-rf|-r|-v|-d/g, \"\").trim()}`;\n\tconsole.log(toDel);\n\tif (path.includes(\"/mnt/\")) {\n\t\ttry {\n\t\t\tconst match = path.match(/\\/mnt\\/([^\\/]+)\\//);\n\t\t\tconst davName = match ? match[1].toLowerCase() : \"\";\n\t\t\tconst np = path.replace(`/mnt/${davName.toLowerCase()}/`, \"\");\n\t\t\tawait tb.vfs.servers.get(davName).connection.promises.unlink(`${np}/${args._raw}`);\n\t\t\tcreateNewCommandInput();\n\t\t} catch (e) {\n\t\t\tdisplayError(`TNSM rmdir: ${e.message}`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\twindow.parent.tb.fs.stat(toDel, (err, stats) => {\n\t\t\tif (err) return console.log(err);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tif (options.force || options.recursive) {\n\t\t\t\t\ttb.sh.rm(toDel, { recursive: options.recursive, force: options.force }, err => {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\tdisplayError(`rm: cannot remove \"${toDel}\": ${err.message}`);\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (options.verbose) {\n\t\t\t\t\t\t\t\tdisplayOutput(`removed directory \"${toDel}\"`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t} else if (options.directory) {\n\t\t\t\t\twindow.parent.tb.fs.rmdir(toDel, err => {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\tif (err.code === \"ENOTEMPTY\") {\n\t\t\t\t\t\t\t\tdisplayError(`rm: cannot remove \"${toDel}\": Directory not empty`);\n\t\t\t\t\t\t\t\tdisplayOutput(\"Use -r to remove non-empty directories. or -rf to remove non-empty directories without prompt.\");\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdisplayError(`rm: cannot remove \"${toDel}\": ${err.message}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (options.verbose) {\n\t\t\t\t\t\t\t\tdisplayOutput(`removed directory \"${toDel}\"`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tdisplayError(`rm: cannot remove \"${toDel}\": Is a directory`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttb.sh.rm(toDel, { recursive: options.recursive, force: options.force }, err => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tdisplayError(`rm: cannot remove \"${toDel}\": ${err.message}`);\n\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (options.verbose) {\n\t\t\t\t\t\t\tdisplayOutput(`removed \"${toDel}\"`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n}\nrm(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/rmdir.js",
    "content": "async function rmdir(args) {\n\tif (args._raw.length <= 0) {\n\t\tdisplayError(\"rmdir: missing operand\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\tif (path.includes(\"/mnt/\")) {\n\t\tdisplayError(\"TNSM rmdir: Removing directories from mounted drives is not supported by the webdav library at this time.\");\n\t} else {\n\t\twindow.parent.tb.sh.rm(`${path}/${args._raw}`, err => {\n\t\t\tif (err) {\n\t\t\t\tdisplayError(`rmdir: ${err.message}`);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t} else {\n\t\t\t\tcreateNewCommandInput();\n\t\t\t}\n\t\t});\n\t}\n}\nrmdir(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/ssh-keygen.js",
    "content": "function ssh_keygen(args) {\n\tif (args.help || args.h) {\n\t\tshowHelp();\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\tconst keyType = args.type || args.t || \"rsa\";\n\tconst bits = parseInt(args.bits || args.b) || (keyType === \"rsa\" ? 2048 : keyType === \"ed25519\" ? 256 : 2048);\n\tconst comment = args.comment || args.C || sessionStorage.getItem(\"currAcc\");\n\tconst filename = args.file || args.f || `/home/${sessionStorage.getItem(\"currAcc\")}/.ssh/id_${keyType}`;\n\tconst passphrase = args.passphrase || args.N || \"\";\n\tif (![\"rsa\", \"ed25519\"].includes(keyType)) {\n\t\tdisplayError(`ssh-keygen: unknown key type ${keyType}`);\n\t\tdisplayError(\"Supported types: rsa, ed25519\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\tconst sshDir = `/home/${sessionStorage.getItem(\"currAcc\")}/.ssh`;\n\tif (!window.parent.tb.fs.promises.exists(sshDir)) {\n\t\twindow.parent.tb.fs.promises.mkdir(sshDir);\n\t}\n\tdisplayOutput(`Generating public/private ${keyType} key pair.`);\n\tgenerateKeyPair(keyType, bits)\n\t\t.then(keyPair => {\n\t\t\treturn Promise.all([formatPrivateKey(keyPair.privateKey, keyType, passphrase), formatPublicKey(keyPair.publicKey, keyType, comment)]);\n\t\t})\n\t\t.then(([privateKeyPEM, publicKeySSH]) => {\n\t\t\twindow.parent.tb.fs.promises.writeFile(filename, privateKeyPEM);\n\t\t\tdisplayOutput(`Your identification has been saved in ${filename}`);\n\t\t\tconst publicFilename = `${filename}.pub`;\n\t\t\twindow.parent.tb.fs.promises.writeFile(publicFilename, publicKeySSH);\n\t\t\tdisplayOutput(`Your public key has been saved in ${publicFilename}`);\n\t\t\treturn calculateFingerprint(publicKeySSH);\n\t\t})\n\t\t.then(fingerprint => {\n\t\t\tdisplayOutput(`The key fingerprint is:`);\n\t\t\tdisplayOutput(`SHA256:${fingerprint}`);\n\t\t\tdisplayOutput(`The key's randomart image is:`);\n\t\t\tdisplayOutput(generateRandomArt(fingerprint));\n\t\t\tcreateNewCommandInput();\n\t\t})\n\t\t.catch(error => {\n\t\t\tdisplayError(`ssh-keygen: ${error.message}`);\n\t\t\tcreateNewCommandInput();\n\t\t});\n\n\tfunction showHelp() {\n\t\tdisplayOutput(\"usage: ssh-keygen [-t keytype] [-b bits] [-C comment] [-f output_keyfile] [-N new_passphrase]\");\n\t\tdisplayOutput(\"\");\n\t\tdisplayOutput(\"Options:\");\n\t\tdisplayOutput(\"  -t keytype    Specifies the type of key to create (rsa, ed25519)\");\n\t\tdisplayOutput(\"  -b bits       Number of bits in the key (default: 2048 for rsa, 256 for ed25519)\");\n\t\tdisplayOutput(\"  -C comment    Provides a new comment\");\n\t\tdisplayOutput(\"  -f filename   Specifies the filename of the key file\");\n\t\tdisplayOutput(\"  -N passphrase Provides the new passphrase\");\n\t\tdisplayOutput(\"  -h, --help    Show this help message\");\n\t}\n\n\tasync function generateKeyPair(keyType, bits) {\n\t\tif (keyType === \"rsa\") {\n\t\t\treturn await crypto.subtle.generateKey(\n\t\t\t\t{\n\t\t\t\t\tname: \"RSASSA-PKCS1-v1_5\",\n\t\t\t\t\tmodulusLength: bits,\n\t\t\t\t\tpublicExponent: new Uint8Array([0x01, 0x00, 0x01]),\n\t\t\t\t\thash: \"SHA-256\",\n\t\t\t\t},\n\t\t\t\ttrue,\n\t\t\t\t[\"sign\", \"verify\"],\n\t\t\t);\n\t\t} else if (keyType === \"ed25519\") {\n\t\t\treturn await crypto.subtle.generateKey(\n\t\t\t\t{\n\t\t\t\t\tname: \"ECDSA\",\n\t\t\t\t\tnamedCurve: \"P-256\",\n\t\t\t\t},\n\t\t\t\ttrue,\n\t\t\t\t[\"sign\", \"verify\"],\n\t\t\t);\n\t\t}\n\t}\n\n\tasync function formatPrivateKey(privateKey, keyType, passphrase) {\n\t\tconst exported = await crypto.subtle.exportKey(\"pkcs8\", privateKey);\n\t\tconst pemBody = base64Encode(new Uint8Array(exported));\n\t\tlet pem = \"-----BEGIN PRIVATE KEY-----\\n\";\n\t\tpem += pemBody.match(/.{1,64}/g).join(\"\\n\");\n\t\tpem += \"\\n-----END PRIVATE KEY-----\\n\";\n\t\tif (passphrase) {\n\t\t\tpem = \"-----BEGIN ENCRYPTED PRIVATE KEY-----\\n\" + \"Note: Passphrase encryption not fully implemented\\n\" + pemBody.match(/.{1,64}/g).join(\"\\n\") + \"\\n-----END ENCRYPTED PRIVATE KEY-----\\n\";\n\t\t}\n\n\t\treturn pem;\n\t}\n\n\tasync function formatPublicKey(publicKey, keyType, comment) {\n\t\tconst exported = await crypto.subtle.exportKey(\"spki\", publicKey);\n\t\tconst exportedArray = new Uint8Array(exported);\n\t\tlet keyData;\n\t\tlet keyTypeStr;\n\t\tif (keyType === \"rsa\") {\n\t\t\tkeyTypeStr = \"ssh-rsa\";\n\t\t\tkeyData = extractRSAPublicKey(exportedArray);\n\t\t} else {\n\t\t\tkeyTypeStr = \"ecdsa-sha2-nistp256\";\n\t\t\tkeyData = extractECDSAPublicKey(exportedArray);\n\t\t}\n\t\tconst sshKey = encodeSSHPublicKey(keyTypeStr, keyData);\n\t\treturn `${keyTypeStr} ${sshKey} ${comment}`;\n\t}\n\tfunction extractRSAPublicKey(spki) {\n\t\tlet offset = 0;\n\t\tfor (let i = 0; i < spki.length - 1; i++) {\n\t\t\tif (spki[i] === 0x03) {\n\t\t\t\toffset = i + 1;\n\t\t\t\tif (spki[offset] & 0x80) {\n\t\t\t\t\tconst lenBytes = spki[offset] & 0x7f;\n\t\t\t\t\toffset += lenBytes + 1;\n\t\t\t\t} else {\n\t\t\t\t\toffset += 1;\n\t\t\t\t}\n\t\t\t\toffset += 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn spki.slice(offset);\n\t}\n\tfunction extractECDSAPublicKey(spki) {\n\t\tlet offset = spki.length - 65;\n\t\treturn spki.slice(offset);\n\t}\n\tfunction encodeSSHPublicKey(keyType, keyData) {\n\t\tconst keyTypeBytes = stringToBytes(keyType);\n\t\tconst parts = [];\n\t\tparts.push(encodeUint32(keyTypeBytes.length));\n\t\tparts.push(keyTypeBytes);\n\t\tparts.push(encodeUint32(keyData.length));\n\t\tparts.push(keyData);\n\t\tconst totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n\t\tconst result = new Uint8Array(totalLength);\n\t\tlet offset = 0;\n\t\tfor (const part of parts) {\n\t\t\tresult.set(part, offset);\n\t\t\toffset += part.length;\n\t\t}\n\n\t\treturn base64Encode(result);\n\t}\n\tfunction encodeUint32(value) {\n\t\tconst buffer = new ArrayBuffer(4);\n\t\tconst view = new DataView(buffer);\n\t\tview.setUint32(0, value, false);\n\t\treturn new Uint8Array(buffer);\n\t}\n\tfunction stringToBytes(str) {\n\t\tconst encoder = new TextEncoder();\n\t\treturn encoder.encode(str);\n\t}\n\tfunction base64Encode(buffer) {\n\t\tlet binary = \"\";\n\t\tconst bytes = new Uint8Array(buffer);\n\t\tfor (let i = 0; i < bytes.length; i++) {\n\t\t\tbinary += String.fromCharCode(bytes[i]);\n\t\t}\n\t\treturn btoa(binary);\n\t}\n\n\tasync function calculateFingerprint(publicKey) {\n\t\tconst keyData = publicKey.split(\" \")[1];\n\t\tconst decoded = base64Decode(keyData);\n\t\tconst hashBuffer = await crypto.subtle.digest(\"SHA-256\", decoded);\n\t\tconst hashArray = new Uint8Array(hashBuffer);\n\t\treturn base64Encode(hashArray).replace(/=+$/, \"\");\n\t}\n\n\tfunction base64Decode(str) {\n\t\tconst binary = atob(str);\n\t\tconst bytes = new Uint8Array(binary.length);\n\t\tfor (let i = 0; i < binary.length; i++) {\n\t\t\tbytes[i] = binary.charCodeAt(i);\n\t\t}\n\t\treturn bytes;\n\t}\n\n\tfunction generateRandomArt(fingerprint) {\n\t\tconst width = 17;\n\t\tconst height = 9;\n\t\tconst field = Array(height)\n\t\t\t.fill(0)\n\t\t\t.map(() => Array(width).fill(0));\n\t\tlet x = Math.floor(width / 2);\n\t\tlet y = Math.floor(height / 2);\n\t\tconst fpBytes = base64Decode(fingerprint);\n\n\t\tfor (let i = 0; i < fpBytes.length; i++) {\n\t\t\tconst byte = fpBytes[i];\n\t\t\tfor (let j = 0; j < 4; j++) {\n\t\t\t\tconst move = (byte >> (j * 2)) & 0x03;\n\t\t\t\tconst dx = move & 0x01 ? 1 : -1;\n\t\t\t\tconst dy = move & 0x02 ? 1 : -1;\n\t\t\t\tx = Math.max(0, Math.min(width - 1, x + dx));\n\t\t\t\ty = Math.max(0, Math.min(height - 1, y + dy));\n\t\t\t\tfield[y][x]++;\n\t\t\t}\n\t\t}\n\t\tconst startX = Math.floor(width / 2);\n\t\tconst startY = Math.floor(height / 2);\n\t\tconst chars = \" .o+=*BOX@%&#/^SE\";\n\t\tlet art = \"+\" + \"-\".repeat(width) + \"+\\n\";\n\t\tfor (let row = 0; row < height; row++) {\n\t\t\tlet line = \"|\";\n\t\t\tfor (let col = 0; col < width; col++) {\n\t\t\t\tif (row === startY && col === startX) {\n\t\t\t\t\tline += \"S\";\n\t\t\t\t} else if (row === y && col === x) {\n\t\t\t\t\tline += \"E\";\n\t\t\t\t} else {\n\t\t\t\t\tconst val = Math.min(field[row][col], chars.length - 1);\n\t\t\t\t\tline += chars[val];\n\t\t\t\t}\n\t\t\t}\n\t\t\tline += \"|\";\n\t\t\tart += line + \"\\n\";\n\t\t}\n\t\tart += \"+\" + \"-\".repeat(width) + \"+\";\n\t\treturn art;\n\t}\n}\n\nssh_keygen(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/ssh.js",
    "content": "const tbSSH = window.tbSSH;\nif (!tbSSH) {\n\tthrow new Error(\"TB-SSH library not loaded!\");\n}\nif (tb.node.isReady === false) {\n\ttb.setCommandProcessing(true);\n\tthrow new Error(\"\\r\\nWebContainer has not booted yet. Please wait a few seconds and try again.\");\n}\nconst connectionString = args._[0];\nconst port = args.p || args.port;\nconst identityFile = args.i || args.identity;\nconst verbose = args.v || args.verbose;\nconst proxyUrl = args.proxy || \"wss://ssh-proxy.terbiumon.top/\";\nif (!connectionString) {\n\tdisplayOutput(\"Usage: ssh [user@]hostname [-p port] [-i identity_file] [-proxy proxy_url] [-v]\");\n\tdisplayOutput(\"Examples:\");\n\tdisplayOutput(\"  ssh user@example.com\");\n\tdisplayOutput(\"  ssh example.com\");\n\tdisplayOutput(\"  ssh -p 2222 user@example.com\");\n\tdisplayOutput(\"  ssh -i ~/.ssh/id_rsa user@example.com\");\n\tdisplayOutput(\"  ssh --proxy ws://localhost:3333 user@example.com\");\n\tthrow new Error(\"Invalid Usage or No connection string provided.\");\n}\nlet username, hostname;\nif (connectionString.includes(\"@\")) {\n\t[username, hostname] = connectionString.split(\"@\");\n} else {\n\thostname = connectionString;\n}\n(async () => {\n\tlet client = null;\n\ttry {\n\t\tdisplayOutput(`Connecting to ${hostname}...`);\n\t\tlet usedKey = null;\n\t\tdisplayOutput(`Using proxy URL: ${proxyUrl}`);\n\t\tconst configParser = await tbSSH.loadSSHConfig();\n\t\tif (configParser) {\n\t\t\tconst hostConfig = configParser.getHost(hostname);\n\t\t\tif (hostConfig && !username && !port && !identityFile) {\n\t\t\t\tdisplayOutput(`Using SSH config for host: ${hostname}`);\n\t\t\t\tif (hostConfig.IdentityFile) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst pk = await tbSSH.loadPrivateKey(hostConfig.IdentityFile);\n\t\t\t\t\t\tif (pk) {\n\t\t\t\t\t\t\tusedKey = hostConfig.IdentityFile;\n\t\t\t\t\t\t\tdisplayOutput(`SSH config IdentityFile found: ${hostConfig.IdentityFile} keyLen=${pk.length} startsWithBegin=${pk.trim().startsWith(\"-----BEGIN\")}`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdisplayOutput(`SSH config IdentityFile not found or unreadable: ${hostConfig.IdentityFile}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tdisplayOutput(`Error reading SSH config IdentityFile ${hostConfig.IdentityFile}: ${e.message}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tclient = await tbSSH.createSSHClientFromConfig(hostname);\n\t\t\t\t\tif (!client) {\n\t\t\t\t\t\tdisplayError(`createSSHClientFromConfig returned null or undefined for ${hostname}`);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tdisplayError(`Error creating SSH client from config for ${hostname}: ${err.message}`);\n\t\t\t\t\tif (verbose) displayError(`Stack: ${err.stack}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tasync function getClientInternal() {\n\t\t\tif (configParser) {\n\t\t\t\tconst hostConfig = configParser.getHost(hostname);\n\t\t\t\tif (hostConfig && !username && !port && !identityFile) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst c = await tbSSH.createSSHClientFromConfig(hostname);\n\t\t\t\t\t\tif (c) return c;\n\t\t\t\t\t\tdisplayError(`createSSHClientFromConfig returned null or undefined for ${hostname}`);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tdisplayError(`Error creating SSH client from config for ${hostname}: ${e.message}`);\n\t\t\t\t\t\tif (verbose) displayError(`Stack: ${e.stack}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst cfg = {\n\t\t\t\thost: hostname,\n\t\t\t\tport: port ? parseInt(port) : 22,\n\t\t\t\tusername: username || sessionStorage.getItem(\"currAcc\") || \"root\",\n\t\t\t\ttimeout: 60000,\n\t\t\t\tkeepaliveInterval: 60000,\n\t\t\t};\n\t\t\tif (identityFile) {\n\t\t\t\tconst pk = await tbSSH.loadPrivateKey(identityFile);\n\t\t\t\tif (pk) {\n\t\t\t\t\tcfg.privateKey = pk;\n\t\t\t\t\tusedKey = identityFile;\n\t\t\t\t\tdisplayOutput(`Using identity file: ${identityFile}`);\n\t\t\t\t} else {\n\t\t\t\t\tdisplayError(`Failed to load identity file: ${identityFile}`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst defaultKeys = [\"~/.ssh/id_ed25519\", \"~/.ssh/id_rsa\", \"~/.ssh/id_ecdsa\", \"~/.ssh/id_dsa\"];\n\t\t\t\tfor (const kp of defaultKeys) {\n\t\t\t\t\tconst kd = await tbSSH.loadPrivateKey(kp);\n\t\t\t\t\tif (kd) {\n\t\t\t\t\t\tcfg.privateKey = kd;\n\t\t\t\t\t\tusedKey = kp;\n\t\t\t\t\t\tdisplayOutput(`Using identity file: ${kp}`);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!cfg.privateKey) {\n\t\t\t\t\tconst password = await new Promise(resolve => {\n\t\t\t\t\t\tterm.write(\"Password: \");\n\t\t\t\t\t\tlet pwd = \"\";\n\t\t\t\t\t\tconst disposable = term.onData(data => {\n\t\t\t\t\t\t\tconst char = data;\n\t\t\t\t\t\t\tif (char === \"\\r\" || char === \"\\n\") {\n\t\t\t\t\t\t\t\tterm.writeln(\"\");\n\t\t\t\t\t\t\t\tdisposable.dispose();\n\t\t\t\t\t\t\t\tresolve(pwd);\n\t\t\t\t\t\t\t} else if (char === \"\\x7f\") {\n\t\t\t\t\t\t\t\tif (pwd.length > 0) {\n\t\t\t\t\t\t\t\t\tpwd = pwd.slice(0, -1);\n\t\t\t\t\t\t\t\t\tterm.write(\"\\b \\b\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (char >= \" \" && char <= \"~\") {\n\t\t\t\t\t\t\t\tpwd += char;\n\t\t\t\t\t\t\t\tterm.write(\"*\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\tcfg.password = password;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (cfg.privateKey) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdisplayOutput(`Auth: key (source=${usedKey || \"inline\"}) keyLen=${cfg.privateKey.length} startsWithBegin=${cfg.privateKey.trim().startsWith(\"-----BEGIN\")}`);\n\t\t\t\t\t} catch (e) {}\n\t\t\t\t} else if (cfg.password) {\n\t\t\t\t\tdisplayOutput(`Auth: password (provided)`);\n\t\t\t\t} else {\n\t\t\t\t\tdisplayOutput(`Auth: none`);\n\t\t\t\t}\n\t\t\t} catch (e) {}\n\t\t\tif (typeof tbSSH.createSSHClientWithProxy === \"function\") {\n\t\t\t\ttry {\n\t\t\t\t\tdisplayOutput(`Attempting WebSocket proxy connection via ${proxyUrl}...`);\n\t\t\t\t\tconst wsClient = tbSSH.createSSHClientWithProxy(cfg, proxyUrl, true);\n\t\t\t\t\tawait wsClient.connect();\n\t\t\t\t\tif (wsClient.isConnected()) return wsClient;\n\t\t\t\t} catch (e) {\n\t\t\t\t\tdisplayError(`WebSocket proxy failed: ${e.message}`);\n\t\t\t\t\tif (verbose) displayError(`Stack: ${e.stack}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst c = await tbSSH.createSSHClient(cfg);\n\t\t\t\tif (c && typeof c.connect === \"function\") return c;\n\t\t\t\tif (c) displayOutput(`createSSHClient returned non-standard object; Client shape: ${Object.getOwnPropertyNames(c).join(\", \")}`);\n\t\t\t} catch (e) {\n\t\t\t\tdisplayError(`Error creating SSH client for ${hostname}: ${e.message}`);\n\t\t\t\tif (verbose) displayError(`Stack: ${e.stack}`);\n\t\t\t}\n\t\t\tif (typeof tbSSH.connectToSSH === \"function\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst connected = await tbSSH.connectToSSH(cfg.host, cfg.port, cfg.username, cfg.password);\n\t\t\t\t\tif (connected) return connected;\n\t\t\t\t} catch (e) {\n\t\t\t\t\tdisplayError(`connectToSSH fallback failed: ${e.message}`);\n\t\t\t\t\tif (verbose) displayError(`Stack: ${e.stack}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\tclient = await getClientInternal();\n\t\tif (!client) {\n\t\t\tdisplayError(`Failed to create SSH client for ${hostname}`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t\tif (!client || typeof client.connect !== \"function\") {\n\t\t\tdisplayError(`Failed to create SSH client for ${hostname}`);\n\t\t\ttry {\n\t\t\t\tdisplayOutput(`Tried: host=${hostname} port=${port ? parseInt(port) : 22} username=${username || sessionStorage.getItem(\"currAcc\") || \"root\"} auth=${usedKey ? `key(source=${usedKey})` : \"password/none\"}`);\n\t\t\t\tif (client) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdisplayOutput(`Client shape: ${Object.getOwnPropertyNames(client).join(\", \")}`);\n\t\t\t\t\t} catch (e) {}\n\t\t\t\t}\n\t\t\t} catch (e) {}\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tif (typeof client.connect === \"function\") {\n\t\t\t\tif (!(typeof client.isConnected === \"function\" && client.isConnected())) {\n\t\t\t\t\tawait client.connect();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (!(typeof client.isConnected === \"function\" && client.isConnected())) {\n\t\t\t\t\tdisplayError(`SSH client returned but doesn't expose connect() or isConnected() for ${hostname}`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (client && typeof client.disconnect === \"function\") {\n\t\t\t\ttry {\n\t\t\t\t\tclient.disconnect();\n\t\t\t\t} catch (e) {}\n\t\t\t}\n\t\t\tdisplayError(`Failed to connect: ${err.message}`);\n\t\t\tif (verbose) displayError(`Stack: ${err.stack}`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t\tdisplayOutput(`Connected to ${hostname}`);\n\t\tconst isWebSocketClient = typeof client.setStream === \"function\" && typeof client.write === \"function\" && typeof client.shell !== \"function\";\n\t\tif (isWebSocketClient) {\n\t\t\tif (window.parent.tb?.setCommandProcessing) {\n\t\t\t\twindow.parent.tb.setCommandProcessing(false);\n\t\t\t}\n\t\t\tconst stream = {\n\t\t\t\tonData: data => {\n\t\t\t\t\tconst text = typeof data === \"string\" ? data : new TextDecoder().decode(data);\n\t\t\t\t\tterm.write(text);\n\t\t\t\t},\n\t\t\t\tonClose: () => {\n\t\t\t\t\tdisplayOutput(\"\\r\\nConnection closed.\");\n\t\t\t\t\tclient.disconnect();\n\t\t\t\t\tif (window.parent.tb?.setCommandProcessing) {\n\t\t\t\t\t\twindow.parent.tb.setCommandProcessing(true);\n\t\t\t\t\t}\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t},\n\t\t\t};\n\t\t\tclient.setStream(stream);\n\t\t\tterm.onData(data => {\n\t\t\t\tclient.write(data);\n\t\t\t});\n\t\t\tterm.onResize(({ cols, rows }) => {\n\t\t\t\tif (typeof client.resize === \"function\") {\n\t\t\t\t\tclient.resize(cols, rows);\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst terminal = new tbSSH.SSHTerminal(client);\n\t\tawait terminal.start();\n\t\tif (window.parent.tb?.setCommandProcessing) {\n\t\t\twindow.parent.tb.setCommandProcessing(false);\n\t\t}\n\t\tterminal.onData(data => {\n\t\t\tterm.write(data);\n\t\t});\n\t\tterminal.onClose(() => {\n\t\t\tdisplayOutput(\"\\r\\nConnection closed.\");\n\t\t\tif (client) client.disconnect();\n\t\t\ttry {\n\t\t\t\tif (typeof inputDisposable !== \"undefined\" && inputDisposable) inputDisposable.dispose();\n\t\t\t} catch (e) {}\n\t\t\tif (window.parent.tb?.setCommandProcessing) {\n\t\t\t\twindow.parent.tb.setCommandProcessing(true);\n\t\t\t}\n\t\t\tcreateNewCommandInput();\n\t\t});\n\t\tconst inputDisposable = term.onData(data => {\n\t\t\tterminal.write(data);\n\t\t});\n\t} catch (error) {\n\t\tif (client) client.disconnect();\n\t\tdisplayError(`Failed to connect: ${error.message}`);\n\t\tif (verbose) {\n\t\t\tdisplayError(`Stack: ${error.stack}`);\n\t\t}\n\t\tcreateNewCommandInput();\n\t}\n})();\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/sysfetch.js",
    "content": "async function sysfetch(args, term) {\n\tif (args._raw.includes(\"-v\")) {\n\t\tdisplayOutput(\"Sysfetch v1.0.0\");\n\t\tcreateNewCommandInput();\n\t} else {\n\t\tlet accent = \"#32ae62\";\n\t\tlet settings = JSON.parse(await window.parent.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\tif (settings[\"accent\"]) {\n\t\t\taccent = settings[\"accent\"];\n\t\t}\n\t\tdisplayOutput(\"                                 %cSystem Information\", \"color: #3cc3f0; font-weight: bold; text-decoration: underline;\");\n\t\tdisplayOutput(`%c@@@@@@@@@@@@@@~ B@@@@@@@@#G?.     OS%c: TerbiumOS ${tb.system.version()}`, `color: ${accent}`, \"color: #b6b6b6\");\n\t\tdisplayOutput(\"%cB###&@@@@&####^ #@@@&PPPB@@@G.    Kernel%c: Ayla v1.0.0\", `color: ${accent}`, \"color: #b6b6b6\");\n\t\tdisplayOutput(\"%c    ~@@@@J     .#@@@P   ~&@@@^    DE%c: Alexa\", `color: ${accent}`, \"color: #b6b6b6\");\n\t\tdisplayOutput(\"%c    ^@@@@?     .#@@@@###&@@&7     %c\", `color: ${accent}`, \"color: #b6b6b6\");\n\t\tdisplayOutput(\"%c    ^@@@@?     .#@@@#555P&@@B7\" + \"   %cHardware Information (estimated)\", `color: ${accent}`, \"color: #3cc3f0; font-weight: bold; text-decoration: underline;\");\n\t\tawait displayCPUInfo(accent);\n\t\tawait displayMemoryInfo(accent);\n\t\tawait getStorage(accent);\n\t\tawait displayGPUInfo(accent);\n\t\tcreateNewCommandInput();\n\t}\n}\n\nasync function displayCPUInfo(accent) {\n\tlet cpuCors = navigator.hardwareConcurrency;\n\tlet cpuInfo = \"%c    ^@@@@?     .#@@@P    G@@@@\" + \"    %cCPU%c: \" + cpuCors + \" Logical Cores \" + `(${Math.floor(cpuCors / 2)} Cores ${cpuCors} threads)`;\n\tdisplayOutput(cpuInfo, `color: ${accent}`, `color: ${accent}`, \"color: #b6b6b6\");\n\treturn true;\n}\n\nasync function displayMemoryInfo(accent) {\n\tlet mem = navigator.deviceMemory ? navigator.deviceMemory + \"GB\" : \"Unknown\";\n\tlet memoryInfo = \"%c    ^@@@@?     .#@@@&GGG#@@@@Y \" + \"   %cMemory%c: \" + mem;\n\tdisplayOutput(memoryInfo, `color: ${accent}`, `color: ${accent}`, \"color: #b6b6b6\");\n\treturn true;\n\t// Im confused tho cus I dont think memory is causing it but like idk\n\t// this is like the process list bug in the tb command where it just wouldnt work in that one specific spot\n}\n\nasync function displayGPUInfo(accent) {\n\tlet canvas = document.createElement(\"canvas\");\n\tlet gl = canvas.getContext(\"webgl\") || canvas.getContext(\"experimental-webgl\");\n\tif (!gl) {\n\t\tdisplayOutput(\"%cGPU%c: Information not available\", `color: ${accent}`, \"color: #b6b6b6\");\n\t\treturn;\n\t}\n\tlet dbgRenderInfo = gl.getExtension(\"WEBGL_debug_renderer_info\");\n\tif (dbgRenderInfo) {\n\t\tlet rndr = gl.getParameter(dbgRenderInfo.UNMASKED_RENDERER_WEBGL);\n\t\tlet regex = /ANGLE \\(.+?,\\s*(.+?) \\(/;\n\t\tlet match = rndr.match(regex);\n\t\tlet gpuName = match ? match[1] : \"\";\n\t\tdisplayOutput(`\t\t\t\t  %cGPU%c: ${gpuName}`, `color: ${accent}`, \"color: #b6b6b6\");\n\t} else {\n\t\tdisplayOutput(\"\t\t\t\t  %cGPU%c: Information not available\", `color: ${accent}`, \"color: #b6b6b6\");\n\t}\n\treturn true;\n}\n\nasync function getStorage(accent) {\n\tconst estimate = await navigator.storage.estimate();\n\tconst totalSize = estimate.quota;\n\tconst usedSize = estimate.usage;\n\tconst usedPercentage = (usedSize / totalSize) * 100;\n\tlet formattedUsedSize, formattedTotalSize;\n\tif (usedSize >= 1024 * 1024 * 1024) {\n\t\tformattedUsedSize = `${(usedSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t} else {\n\t\tformattedUsedSize = `${(usedSize / (1024 * 1024)).toFixed(2)} MB`;\n\t}\n\tif (totalSize >= 1024 * 1024 * 1024) {\n\t\tformattedTotalSize = `${(totalSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n\t} else {\n\t\tformattedTotalSize = `${Math.round((totalSize / (1024 * 1024)).toFixed(2))} MB`;\n\t}\n\tdisplayOutput(\"%c    ^&@@@?      B@@@@@@@@&B5~ \" + `    %cStorage%c: ${formattedUsedSize} of ${formattedTotalSize}`, `color: ${accent}`, `color: ${accent}`, \"color: #b6b6b6\");\n\treturn true;\n}\n\nsysfetch(args, term);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/taskkill.js",
    "content": "function taskkill(args) {\n\tif (args._raw.includes(\"list\")) {\n\t\tconst windows = tb.process.list();\n\t\tObject.values(windows).forEach(window => {\n\t\t\tdisplayOutput(`${window.name}, ${window.pid}`);\n\t\t});\n\t\tcreateNewCommandInput();\n\t} else {\n\t\ttry {\n\t\t\twindow.tb.process.kill(args._raw);\n\t\t\tdisplayOutput(`Successfully killed task with pid: ${args._raw}`);\n\t\t} catch {\n\t\t\tdisplayError(\"Not task found with that PID\");\n\t\t}\n\t\tcreateNewCommandInput();\n\t}\n}\n\ntaskkill(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/tb.js",
    "content": "const ver = \"1.0.0\";\n\nvar cmdData = {\n\thelp: {\n\t\tdesc: \"Shows information about a given subcommand\",\n\t\tusage: \"tb help <subcmd> ...\",\n\t\targs: {\n\t\t\tsubcmd: \"The subcommand to look up. I.e. tb help system version\",\n\t\t},\n\t},\n\trestart: {\n\t\tdesc: \"Restarts TerbiumOS\",\n\t\tusage: \"tb restart <args>\",\n\t\talias: \"reboot\",\n\t\targs: {\n\t\t\t\"-f/--force\": \"Clears the session cache upon reboot and reboots to bootloader\",\n\t\t\t\"-s/--skip-prompt\": \"Skips the confirm reboot prompt\",\n\t\t},\n\t},\n\tprocess: {\n\t\tdesc: \"Parent command for listing terbium processes.\",\n\t\tusage: \"tb process [subcmd] ... <args>\",\n\t\talias: \"proc\",\n\t\tsubcmds: {\n\t\t\tkill: {\n\t\t\t\tdesc: \"Kill/delete a given process\",\n\t\t\t\tusage: \"tb process kill [pid]\",\n\t\t\t\talias: \"delete\",\n\t\t\t\targs: {\n\t\t\t\t\tpid: \"The ID of the process to kill/delete\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tlist: {\n\t\t\t\tdesc: \"[ ! BROKEN ! ] Lists all active processes\",\n\t\t\t\tusage: \"tb process list\",\n\t\t\t},\n\t\t},\n\t},\n\tsystem: {\n\t\tdesc: \"Parent command for details about the terbium system.\",\n\t\tusage: \"tb system [subcmd] ... <args>\",\n\t\talias: \"sys\",\n\t\tsubcmds: {\n\t\t\tversion: {\n\t\t\t\tdesc: \"Display the currently installed Terbium version.\",\n\t\t\t\tusage: \"tb system version\",\n\t\t\t\talias: \"ver\",\n\t\t\t},\n\t\t\texportfs: {\n\t\t\t\tdesc: \"Export the terbium filesystem.\",\n\t\t\t\tusage: \"tb system exportfs\",\n\t\t\t},\n\t\t\trestartNode: {\n\t\t\t\tdesc: \"Restarts the NodeJS Container\",\n\t\t\t\tusage: \"tb system restartNode\",\n\t\t\t},\n\t\t},\n\t},\n\tapplication: {\n\t\tdesc: \"Parent command for running/modifying apps.\",\n\t\tusage: \"tb application [subcmd] ... <args>\",\n\t\talias: \"app\",\n\t\tsubcmds: {\n\t\t\trun: {\n\t\t\t\tdesc: \"Runs the app located at the specified package ID\",\n\t\t\t\tusage: \"tb application run [app] <args>\",\n\t\t\t\talias: \"open\",\n\t\t\t\targs: {\n\t\t\t\t\tapp: \"The name of the app to open. Replace any spaces with an underscore ( _ ). Not case sensitive.\",\n\t\t\t\t\t\"-l/--legacy\": \"Toggle if the old `com.tb.appname` format should be used or not.\",\n\t\t\t\t\t\"-j/--json-file\": \"Process the app argument as a path to an app's configuration file.\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tlist: {\n\t\t\t\tdesc: \"Lists the installed applications.\",\n\t\t\t\tusage: \"tb application list <args>\",\n\t\t\t\targs: {\n\t\t\t\t\t\"-d/--directory\": \"Shows the directory that the application is located\",\n\t\t\t\t\t\"-c/--config\": \"Shows the location of the app's config file\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\tnetwork: {\n\t\tdesc: \"Parent command for interacting with Terbium's networking system\",\n\t\tusage: \"tb network [subcmd] ... <args>\",\n\t\talias: \"net\",\n\t\tsubcmds: {\n\t\t\tproxy: {\n\t\t\t\tdesc: \"Parent command for modifying/reading info about the current proxy.\",\n\t\t\t\tusage: \"tb network proxy [subcmd] ... <args>\",\n\t\t\t\tsubcmds: {\n\t\t\t\t\tactive: {\n\t\t\t\t\t\tdesc: \"Prints the active proxy in use by Terbium.\",\n\t\t\t\t\t\tusage: \"tb network proxy active\",\n\t\t\t\t\t},\n\t\t\t\t\tset: {\n\t\t\t\t\t\tdesc: \"Change the proxy that Terbium will use\",\n\t\t\t\t\t\tusage: \"tb network proxy set [proxy]\",\n\t\t\t\t\t\targs: {\n\t\t\t\t\t\t\tproxy: \"The name of the proxy to switch to. CASE SENSITIVE!!!\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\tnode: {\n\t\tdesc: \"Parent command for interacting with Terbium's NodeJS container\",\n\t\tusage: \"tb node [subcmd] ... <args>\",\n\t\tsubcmds: {\n\t\t\trestart: {\n\t\t\t\tdesc: \"Restarts the NodeJS container\",\n\t\t\t\tusage: \"tb node restart\",\n\t\t\t},\n\t\t\tstart: {\n\t\t\t\tdesc: \"Starts the NodeJS container\",\n\t\t\t\tusage: \"tb node start\",\n\t\t\t},\n\t\t\tstop: {\n\t\t\t\tdesc: \"Stops the NodeJS container\",\n\t\t\t\tusage: \"tb node stop\",\n\t\t\t},\n\t\t},\n\t},\n};\n\nasync function tb(args) {\n\tfunction error(err) {\n\t\tdisplayError(`${err}\\n`);\n\t\tcreateNewCommandInput();\n\t}\n\tfunction help(args) {\n\t\tfunction resolveCommand(args) {\n\t\t\tlet current = cmdData;\n\t\t\tfor (let i = 1; i < args.length; i++) {\n\t\t\t\tconst input = args[i];\n\t\t\t\tconst scope = current.subcmds || current;\n\t\t\t\tconst match = Object.entries(scope).find(([key, val]) => key === input || val.alias === input);\n\t\t\t\tif (!match) {\n\t\t\t\t\tdisplayOutput(`Unknown command or alias: ${input}`);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tcurrent = match[1];\n\t\t\t}\n\t\t\tif (args.length === 1) {\n\t\t\t\tcurrent.v = true;\n\t\t\t}\n\t\t\treturn current;\n\t\t}\n\t\tfunction formatData(info) {\n\t\t\tif (typeof info.v === \"undefined\") {\n\t\t\t\tdisplayOutput(`Description: ${info.desc}\\n`);\n\t\t\t\tdisplayOutput(`USAGE: ${info.usage}`);\n\t\t\t\tif (info.alias) displayOutput(`ALIAS(ES): ${info.alias}\\n`);\n\t\t\t\tif (info.subcmds) {\n\t\t\t\t\tdisplayOutput(\"SUBCOMMANDS:\");\n\t\t\t\t\tconst subkeys = Object.keys(info.subcmds);\n\t\t\t\t\tfor (let i = 0; i < subkeys.length; i++) {\n\t\t\t\t\t\tdisplayOutput(`${`${subkeys[i]} ${info.subcmds[subkeys[i]].alias ? `(alias: ${info.subcmds[subkeys[i]].alias})` : \"\"}`.padEnd(40)}${info.subcmds[subkeys[i]].desc}\\n`);\n\t\t\t\t\t}\n\t\t\t\t} else if (info.args) {\n\t\t\t\t\tdisplayOutput(\"ARGUMENTS:\");\n\t\t\t\t\tconst subkeys = Object.keys(info.args);\n\t\t\t\t\tfor (let i = 0; i < subkeys.length; i++) {\n\t\t\t\t\t\tdisplayOutput(`${`${subkeys[i]}`.padEnd(40)}${info.args[subkeys[i]]}\\n`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdelete info.v;\n\t\t\t\tdisplayOutput('Any commands listed as \"parent\" commands have subcommands. Use `tb help <cmd>` to view it\\'s commands.');\n\t\t\t\tdisplayOutput(\"List of available commands:\\n\");\n\t\t\t\tconst cmdKeys = Object.keys(cmdData);\n\t\t\t\tfor (let i = 0; i < cmdKeys.length; i++) {\n\t\t\t\t\tdisplayOutput(`${`${cmdKeys[i]} ${cmdData[cmdKeys[i]].alias ? `(alias: ${cmdData[cmdKeys[i]].alias})` : \"\"}`.padEnd(40)}${cmdData[cmdKeys[i]].desc}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst data = resolveCommand(args);\n\t\tif (data != null) {\n\t\t\tformatData(data);\n\t\t}\n\t\tdisplayOutput(`Terbium System CLI v${ver}`);\n\t\tcreateNewCommandInput();\n\t}\n\tswitch (args._[0]) {\n\t\tcase undefined:\n\t\tcase null:\n\t\t\thelp([\"help\"]);\n\t\t\tbreak;\n\t\tcase \"help\":\n\t\t\thelp(args._);\n\t\t\tbreak;\n\t\tcase \"restart\":\n\t\tcase \"reboot\": {\n\t\t\tfunction handleReboot() {\n\t\t\t\tif (args.f || args.force) {\n\t\t\t\t\twindow.parent.sessionStorage.clear();\n\t\t\t\t\twindow.parent.location.reload();\n\t\t\t\t} else {\n\t\t\t\t\twindow.parent.sessionStorage.setItem(\"logged-in\", \"false\");\n\t\t\t\t\twindow.parent.location.reload();\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (args.s || args.skipPrompt) {\n\t\t\t\thandleReboot();\n\t\t\t} else {\n\t\t\t\tawait window.parent.tb.dialog.Permissions({\n\t\t\t\t\ttitle: \"Confirm restart\",\n\t\t\t\t\tmessage: \"Are you sure you want to restart Terbium?\",\n\t\t\t\t\tonOk: () => {\n\t\t\t\t\t\thandleReboot();\n\t\t\t\t\t},\n\t\t\t\t\tonCancel: () => {\n\t\t\t\t\t\terror(\"tb > restart > operation aborted by user\");\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase \"lock\":\n\t\t\twindow.parent.sessionStorage.setItem(\"logged-in\", false);\n\t\t\twindow.parent.location.reload();\n\t\t\tbreak;\n\t\tcase \"process\":\n\t\tcase \"proc\":\n\t\t\tswitch (args._[1]) {\n\t\t\t\tcase undefined:\n\t\t\t\tcase null:\n\t\t\t\t\terror(\"tb > process > expected an argument at pos 3, got nothing\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"kill\":\n\t\t\t\tcase \"delete\":\n\t\t\t\t\tif (args._[2] === undefined) {\n\t\t\t\t\t\terror(\"tb > process > kill > expected an argument at pos 4, got nothing\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\twindow.parent.tb.process.kill(args._[2]);\n\t\t\t\t\t}\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"list\": {\n\t\t\t\t\t/*\n\t\t\t\t\tconst proclist = window.parent.tb.process.list();\n\t\t\t\t\tconst proclistIDs = Object.keys(proclist);\n\t\t\t\t\tfor (let i = 0; i < proclist.length; i++) {\n\t\t\t\t\t\tdisplayOutput(`\"${typeof proclist[i].name === \"string\" ? proclist[i].name : proclist[i].name.text}\" [PID: ${proclistIDs[i]}]`);\n\t\t\t\t\t}\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t*/\n\t\t\t\t\terror(\"tb > process > list > This command is currently broken. If you need to see a list of process ID's, please use task manager for now\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\terror(`tb > process > unknown subcommand: ${args._[1]}`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"sys\":\n\t\tcase \"system\":\n\t\t\tswitch (args._[1]) {\n\t\t\t\tcase undefined:\n\t\t\t\tcase null:\n\t\t\t\t\terror(\"tb > system > expected an argument at pos 3, got nothing\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ver\":\n\t\t\t\tcase \"version\":\n\t\t\t\t\tdisplayOutput(`TerbiumOS version ${window.parent.tb.system.version()}`);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"exportfs\":\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(true);\n\t\t\t\t\tdisplayOutput(\"! WARNING !\");\n\t\t\t\t\tdisplayOutput(\"Using this command may cause the tab to freeze momentarily.\");\n\t\t\t\t\tdisplayOutput(\"DO NOT close this tab until the file finishes downloading.\");\n\t\t\t\t\tawait window.parent.tb.system.exportfs();\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(false);\n\t\t\t\t\tdisplayOutput(\"Success!\");\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\terror(`tb > system > unknown subcommand: ${args._[1]}`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"application\":\n\t\tcase \"app\":\n\t\t\tswitch (args._[1]) {\n\t\t\t\tcase undefined:\n\t\t\t\tcase null:\n\t\t\t\t\terror(\"tb > application > expected an argument at pos 3, got nothing\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"run\":\n\t\t\t\tcase \"open\": {\n\t\t\t\t\tif (args._[2] === undefined) {\n\t\t\t\t\t\terror(\"tb > application > run > expected an argument at pos 4, got nothing\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (args.l || args.legacy) {\n\t\t\t\t\t\t\t\tif (args._[3]) {\n\t\t\t\t\t\t\t\t\tawait window.parent.tb.system.openApp(args._[2], { rest: args._[3] });\n\t\t\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tawait window.parent.tb.system.openApp(args._[2]);\n\t\t\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// biome-ignore lint/correctness/noInnerDeclarations: variable isnt needed at the root, keep it at the current scope\n\t\t\t\t\t\t\t\tvar resolvedAppConfigFile = \"\";\n\t\t\t\t\t\t\t\tif (args.j || args.jsonFile) resolvedAppConfigFile = args._[2];\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tconst trueApp = args._[2].split(\"_\").join(\" \");\n\t\t\t\t\t\t\t\t\tconst apps = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\t\t\t\t\t\t\tconst app = apps.find(obj => obj.name.toLowerCase() === trueApp.toLowerCase());\n\t\t\t\t\t\t\t\t\tif (app === undefined) resolvedAppConfigFile = undefined;\n\t\t\t\t\t\t\t\t\telse resolvedAppConfigFile = app.config;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (resolvedAppConfigFile === undefined) error(\"tb > application > run > could not find that app\");\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tconst appConfig = JSON.parse(await window.parent.tb.fs.promises.readFile(resolvedAppConfigFile)).config;\n\t\t\t\t\t\t\t\t\twindow.parent.tb.window.create(appConfig);\n\t\t\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\terror(`tb > application > run > failed to open app: ${e.message}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"list\": {\n\t\t\t\t\tconst apps = JSON.parse(await window.parent.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\t\t\tfor (const app of apps) {\n\t\t\t\t\t\tdisplayOutput(`\"${app.name}\"${args.d || args.directory ? ` (Directory: ${app.config.replace(\"index.json\", \"\")})` : \"\"}${args.c || args.config ? ` (Configuration: ${app.config})` : \"\"}`);\n\t\t\t\t\t}\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\terror(`tb > application > unknown subcommand: ${args._[1]}`);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"network\":\n\t\tcase \"net\":\n\t\t\tswitch (args._[1]) {\n\t\t\t\tcase \"proxy\":\n\t\t\t\t\tswitch (args._[2]) {\n\t\t\t\t\t\tcase \"active\":\n\t\t\t\t\t\t\tdisplayOutput(`Active proxy: ${await window.parent.tb.proxy.get()}`);\n\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"set\":\n\t\t\t\t\t\t\tif (typeof args._[3] !== \"undefined\") {\n\t\t\t\t\t\t\t\tdisplayOutput((await window.parent.tb.proxy.set(args._[3])) ? `Successfully set the active proxy to ${args._[3]}` : `Could not set the active proxy to ${args._[3]}.`);\n\t\t\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\terror(\"tb > network > proxy > set > expected an argument at pos 5, got nothing\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\terror(`tb > network > proxy > unknown subcommand: ${args._[2]}`);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\terror(`tb > network > unknown subcommand: ${args._[1]}`);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase \"node\":\n\t\t\tswitch (args._[1]) {\n\t\t\t\tcase \"restart\":\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(true);\n\t\t\t\t\tdisplayOutput(\"Restarting NodeJS container...\");\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait window.parent.tb.node.stop();\n\t\t\t\t\t\tdisplayOutput(\"container stopped successfullty. starting...\");\n\t\t\t\t\t\tawait window.parent.tb.node.start();\n\t\t\t\t\t\tdisplayOutput(\"Container restarted.\");\n\t\t\t\t\t\twindow.parent.tb.setCommandProcessing(false);\n\t\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\terror(\"tb > node > restart > Could not restart the NodeJS container.\");\n\t\t\t\t\t}\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(false);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"start\":\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(true);\n\t\t\t\t\tif (window.parent.tb.node.isReady) {\n\t\t\t\t\t\tdisplayOutput(\"NodeJS container is already running.\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdisplayOutput(\"Starting NodeJS container...\");\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twindow.parent.tb.node.start();\n\t\t\t\t\t\t\tdisplayOutput(\"Successfully started the NodeJS container.\");\n\t\t\t\t\t\t} catch (_) {\n\t\t\t\t\t\t\terror(\"tb > node > start > An error occured while starting the NodeJS container.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(false);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"stop\":\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(true);\n\t\t\t\t\tif (window.parent.tb.node.isReady) {\n\t\t\t\t\t\tdisplayOutput(\"Stopping NodeJS container...\");\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twindow.parent.tb.node.start();\n\t\t\t\t\t\t\tdisplayOutput(\"Successfully stopped the NodeJS container.\");\n\t\t\t\t\t\t} catch (_) {\n\t\t\t\t\t\t\terror(\"tb > node > stop > An error occured while stopping the NodeJS container.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdisplayOutput(\"NodeJS container is already stopped.\");\n\t\t\t\t\t}\n\t\t\t\t\twindow.parent.tb.setCommandProcessing(false);\n\t\t\t\t\tcreateNewCommandInput();\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\terror(`tb > network > unknown subcommand: ${args._[1]}`);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\terror(`tb > unknown subcommand: ${args._[0]}`);\n\t\t\tbreak;\n\t}\n}\ntb(args, term);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/touch.js",
    "content": "async function touch(args) {\n\tif (args._raw.length <= 0) {\n\t\tdisplayError(\"touch: missing operand\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\n\tif (path.includes(\"/mnt/\")) {\n\t\ttry {\n\t\t\tconst match = path.match(/\\/mnt\\/([^\\/]+)\\//);\n\t\t\tconst davName = match ? match[1].toLowerCase() : \"\";\n\t\t\tconst np = path.replace(`/mnt/${davName.toLowerCase()}/`, \"\");\n\t\t\tawait tb.vfs.servers.get(davName).connection.promises.writeFile(`${np}/${args._raw}`, \"\", \"utf8\");\n\t\t\tcreateNewCommandInput();\n\t\t} catch (e) {\n\t\t\tdisplayError(`TNSM touch: ${e.message}`);\n\t\t\tcreateNewCommandInput();\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\ttb.sh.touch(`${path}/${args._raw}`, err => {\n\t\t\tif (err) {\n\t\t\t\tdisplayError(`touch: ${err.message}`);\n\t\t\t\tcreateNewCommandInput();\n\t\t\t} else {\n\t\t\t\tcreateNewCommandInput();\n\t\t\t}\n\t\t});\n\t}\n}\ntouch(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/scripts/unzip.js",
    "content": "async function uzip(path, target) {\n\tconst response = await fetch(\"/fs/\" + path);\n\tconst zipFileContent = await response.arrayBuffer();\n\tif (!(await dirExists(target))) {\n\t\tawait window.parent.tb.fs.promises.mkdir(target, { recursive: true });\n\t}\n\tconst compressedFiles = window.parent.tb.fflate.unzipSync(new Uint8Array(zipFileContent));\n\tfor (const [relativePath, content] of Object.entries(compressedFiles)) {\n\t\tconst fullPath = `${target}/${relativePath}`;\n\t\tconst pathParts = fullPath.split(\"/\");\n\t\tlet currentPath = \"\";\n\t\tfor (let i = 0; i < pathParts.length; i++) {\n\t\t\tcurrentPath += pathParts[i] + \"/\";\n\t\t\tif (i === pathParts.length - 1 && !relativePath.endsWith(\"/\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconsole.log(`touch ${currentPath.slice(0, -1)}`);\n\t\t\t\t\tdisplayOutput(`touch ${currentPath.slice(0, -1)}`);\n\t\t\t\t\tawait window.parent.tb.fs.promises.writeFile(currentPath.slice(0, -1), window.parent.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\t} catch {\n\t\t\t\t\tdisplayOutput(`Cant make ${currentPath.slice(0, -1)}`);\n\t\t\t\t\tconsole.log(`Cant make ${currentPath.slice(0, -1)}`);\n\t\t\t\t}\n\t\t\t} else if (!(await dirExists(currentPath))) {\n\t\t\t\ttry {\n\t\t\t\t\tconsole.log(`mkdir ${currentPath}`);\n\t\t\t\t\tdisplayOutput(`mkdir ${currentPath}`);\n\t\t\t\t\tawait window.parent.tb.fs.promises.mkdir(currentPath);\n\t\t\t\t} catch {\n\t\t\t\t\tconsole.log(`Cant make ${currentPath}`);\n\t\t\t\t\tdisplayOutput(`Cant make ${currentPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (relativePath.endsWith(\"/\")) {\n\t\t\ttry {\n\t\t\t\tconsole.log(`mkdir fp ${fullPath}`);\n\t\t\t\tawait window.parent.tb.fs.promises.mkdir(fullPath);\n\t\t\t} catch {\n\t\t\t\tconsole.log(`Cant make ${fullPath}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn \"Done!\";\n}\n\nconst dirExists = async path => {\n\treturn new Promise(resolve => {\n\t\twindow.parent.tb.fs.stat(path, (err, stats) => {\n\t\t\tif (err) {\n\t\t\t\tif (err.code === \"ENOENT\") {\n\t\t\t\t\tresolve(false);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t\tresolve(false);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst exists = stats.type === \"DIRECTORY\";\n\t\t\t\tresolve(exists);\n\t\t\t}\n\t\t});\n\t});\n};\n\nasync function unzip(args) {\n\tif (!args._raw || args._raw.length < 2) {\n\t\tdisplayOutput(\"Usage: unzip <zipfile> <target>\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t} else if (path.includes(\"/mnt/\")) {\n\t\tdisplayError(\"TNSM unzip: Unzipping files from mounted drives is not supported yet.\");\n\t\tcreateNewCommandInput();\n\t\treturn;\n\t}\n\ttry {\n\t\tawait uzip(`${path}/${args._[0]}`, `${path}/${args._[1]}`);\n\t\tdisplayOutput(`Successfully unzipped ${args._[0]} to ${args._[1]}`);\n\t} catch (e) {\n\t\tdisplayError(`Error unzipping file: ${e.message}`);\n\t}\n\tcreateNewCommandInput();\n\treturn;\n}\n\nunzip(args);\n"
  },
  {
    "path": "public/apps/terminal.tapp/ssh-util.js",
    "content": "(()=>{\"use strict\";var e={};e.d=(n,t)=>{for(var r in t)e.o(t,r)&&!e.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:t[r]})},e.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n);var n={};function t(e,n,t,r,o,s,i){try{var c=e[s](i),a=c.value}catch(e){t(e);return}c.done?n(a):Promise.resolve(a).then(r,o)}function r(e){return function(){var n=this,r=arguments;return new Promise(function(o,s){var i=e.apply(n,r);function c(e){t(i,o,s,c,a,\"next\",e)}function a(e){t(i,o,s,c,a,\"throw\",e)}c(void 0)})}}e.d(n,{default:()=>w});function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function s(e,n){var t,r,o,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create((\"function\"==typeof Iterator?Iterator:Object).prototype),c=Object.defineProperty;return c(i,\"next\",{value:a(0)}),c(i,\"throw\",{value:a(1)}),c(i,\"return\",{value:a(2)}),\"function\"==typeof Symbol&&c(i,Symbol.iterator,{value:function(){return this}}),i;function a(c){return function(a){var l=[c,a];if(t)throw TypeError(\"Generator is already executing.\");for(;i&&(i=0,l[0]&&(s=0)),s;)try{if(t=1,r&&(o=2&l[0]?r.return:l[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,l[1])).done)return o;switch(r=0,o&&(l=[2&l[0],o.value]),l[0]){case 0:case 1:o=l;break;case 4:return s.label++,{value:l[1],done:!1};case 5:s.label++,r=l[1],l=[0];continue;case 7:l=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===l[0]||2===l[0])){s=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]<o[3])){s.label=l[1];break}if(6===l[0]&&s.label<o[1]){s.label=o[1],o=l;break}if(o&&s.label<o[2]){s.label=o[2],s.ops.push(l);break}o[2]&&s.ops.pop(),s.trys.pop();continue}l=n.call(e,s)}catch(e){l=[6,e],r=0}finally{t=o=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}}}var i=function(){var e;function n(e){if(!(this instanceof n))throw TypeError(\"Cannot call a class as a function\");o(this,\"config\",void 0),o(this,\"webContainer\",null),o(this,\"sshProcess\",null),o(this,\"connected\",!1),o(this,\"authenticated\",!1),o(this,\"dataCallbacks\",[]),o(this,\"closeCallbacks\",[]),o(this,\"connectionPromise\",null),o(this,\"_didRetry\",!1),this.config=function(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{},r=Object.keys(t);\"function\"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(t).filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.forEach(function(n){o(e,n,t[n])})}return e}({port:22,timeout:6e4,keepaliveInterval:6e4},e)}return e=[{key:\"connect\",value:function(){return r(function(){return s(this,function(e){return this.connectionPromise||(this.connectionPromise=this.doConnect()),[2,this.connectionPromise]})}).call(this)}},{key:\"doConnect\",value:function(){return r(function(){var e,n,t,r,o,i,c,a,l,u,f,h;return s(this,function(s){switch(s.label){case 0:if(console.log(\"[SSH] Starting connection to\",this.config.host+\":\"+this.config.port),!(null==(t=window.tb||(null==(e=window.parent)?void 0:e.tb))||null==(n=t.node)?void 0:n.webContainer))throw console.error(\"[SSH] WebContainer not available\"),Error(\"WebContainer not available. Ensure WebContainer is initialized.\");if(console.log(\"[SSH] WebContainer found\"),this.webContainer=t.node.webContainer,t.node.isReady)return[3,2];return console.log(\"[SSH] Waiting for WebContainer to be ready...\"),[4,new Promise(function(e){var n=setInterval(function(){t.node.isReady&&(clearInterval(n),console.log(\"[SSH] WebContainer is ready\"),e())},100)})];case 1:return s.sent(),[3,3];case 2:console.log(\"[SSH] WebContainer already ready\"),s.label=3;case 3:return console.log(\"[SSH] Ensuring ssh2 is installed...\"),[4,this.ensureSSH2Installed()];case 4:return s.sent(),console.log(\"[SSH] ssh2 installation check complete\"),console.log(\"[SSH] Generating SSH client script...\"),console.log(\"[SSH] Script generated, length:\",(r=this.generateSSHScript()).length),console.log(\"[SSH] Writing script to\",o=\"ssh-client.mjs\"),[4,this.webContainer.fs.writeFile(o,r)];case 5:s.sent(),console.log(\"[SSH] Script written successfully\"),s.label=6;case 6:return s.trys.push([6,8,,9]),[4,this.webContainer.fs.readFile(o,\"utf-8\")];case 7:return console.log(\"[SSH] Verified script exists, length:\",s.sent().length),[3,9];case 8:return console.error(\"[SSH] Failed to verify script file:\",s.sent()),[3,9];case 9:return console.log(\"[SSH] Spawning Node.js process with script:\",o),[4,this.webContainer.spawn(\"node\",[o])];case 10:i=s.sent(),this.sshProcess=i,console.log(\"[SSH] Process spawned, starting to read output...\"),c=i.output.getReader(),this.readOutput(c),console.log(\"[SSH] Waiting for connection confirmation...\"),a=0,l=3,s.label=11;case 11:if(!(a<l))return[3,17];s.label=12;case 12:return s.trys.push([12,14,,16]),a++,console.log(\"[SSH] waitForConnection attempt \".concat(a,\"/\").concat(l,\" (timeout=\").concat(this.config.timeout,\"ms)\")),[4,this.waitForConnection()];case 13:return s.sent(),[3,17];case 14:if(console.error(\"[SSH] waitForConnection failed:\",u=s.sent()),a>=l)throw console.error(\"[SSH] All retry attempts failed\"),u;this._didRetry=!0,this.config.timeout,this.config.timeout=Math.max(2*this.config.timeout,12e4),console.log(\"[SSH] Retrying connection with increased timeout:\",this.config.timeout);try{this.sshProcess&&\"function\"==typeof this.sshProcess.kill&&this.sshProcess.kill()}catch(e){console.warn(\"[SSH] Error killing previous process:\",e)}return[4,this.webContainer.spawn(\"node\",[o])];case 15:return f=s.sent(),this.sshProcess=f,h=f.output.getReader(),this.readOutput(h),[3,16];case 16:return[3,11];case 17:return this.connected=!0,this.authenticated=!0,[2]}})}).call(this)}},{key:\"ensureSSH2Installed\",value:function(){return r(function(){var e,n,t;return s(this,function(r){switch(r.label){case 0:return r.trys.push([0,7,,11]),console.log(\"[SSH] Checking for existing package.json...\"),[4,this.webContainer.fs.readFile(\"/package.json\",\"utf-8\")];case 1:if(console.log(\"[SSH] Found package.json:\",t=JSON.parse(r.sent())),!(!(null==(e=t.dependencies)?void 0:e.ssh2)||!(null==(n=t.dependencies)?void 0:n.sshpk)))return[3,5];return console.log(\"[SSH] Adding ssh2 and sshpk to dependencies if missing...\"),t.dependencies=t.dependencies||{},t.dependencies.ssh2=t.dependencies.ssh2||\"^1.15.0\",t.dependencies.sshpk=t.dependencies.sshpk||\"^1.16.1\",[4,this.webContainer.fs.writeFile(\"/package.json\",JSON.stringify(t,null,2))];case 2:return r.sent(),console.log(\"[SSH] Running npm install...\"),[4,this.webContainer.spawn(\"npm\",[\"install\"])];case 3:return[4,r.sent().exit];case 4:return console.log(\"[SSH] npm install completed with exit code:\",r.sent()),[3,6];case 5:console.log(\"[SSH] ssh2 and sshpk already installed\"),r.label=6;case 6:return[3,11];case 7:return r.sent(),console.log(\"[SSH] No package.json found or error, creating new one...\"),[4,this.webContainer.fs.writeFile(\"/package.json\",JSON.stringify({name:\"ssh-client\",dependencies:{ssh2:\"^1.15.0\"}},null,2))];case 8:return r.sent(),console.log(\"[SSH] Running npm install for new package.json...\"),[4,this.webContainer.spawn(\"npm\",[\"install\"])];case 9:return[4,r.sent().exit];case 10:return console.log(\"[SSH] npm install completed with exit code:\",r.sent()),[3,11];case 11:return[2]}})}).call(this)}},{key:\"generateSSHScript\",value:function(){var e=this.config,n=e.host,t=e.port,r=e.username,o=e.password,s=e.privateKey,i=e.passphrase,c=[];c.push(\"host: \".concat(JSON.stringify(n))),c.push(\"port: \".concat(t)),c.push(\"username: \".concat(JSON.stringify(r))),o&&c.push(\"password: \".concat(JSON.stringify(o))),s&&c.push(\"privateKey: \".concat(JSON.stringify(s))),i&&c.push(\"passphrase: \".concat(JSON.stringify(i))),c.push(\"readyTimeout: \".concat(this.config.timeout)),c.push(\"keepaliveInterval: \".concat(this.config.keepaliveInterval)),c.push(\"algorithms: {\\n\t\t\tkex: [\\n\t\t\t\t'diffie-hellman-group14-sha1',\\n\t\t\t\t'diffie-hellman-group-exchange-sha256',\\n\t\t\t\t'ecdh-sha2-nistp256',\\n\t\t\t\t'ecdh-sha2-nistp384',\\n\t\t\t\t'ecdh-sha2-nistp521',\\n\t\t\t\t'curve25519-sha256',\\n\t\t\t\t'curve25519-sha256@libssh.org',\\n\t\t\t\t'diffie-hellman-group1-sha1',\\n\t\t\t\t'diffie-hellman-group-exchange-sha1'\\n\t\t\t],\\n\t\t\tcipher: [\\n\t\t\t\t'aes128-ctr','aes192-ctr','aes256-ctr','aes128-gcm','aes128-gcm@openssh.com','aes256-gcm','aes256-gcm@openssh.com','aes128-cbc','aes192-cbc','aes256-cbc','3des-cbc'\\n\t\t\t],\\n\t\t\thmac: ['hmac-sha2-256','hmac-sha2-512','hmac-sha1'],\\n\t\t\tserverHostKey: ['ssh-rsa','rsa-sha2-512','rsa-sha2-256','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521','ssh-dss']\\n\t\t}\"),c.push(\"debug: (msg) => console.log('[ssh2-debug]', msg)\"),c.push(\"hostVerifier: (hash) => { console.log('[ssh2] hostVerifier:', hash); return true; }\");var a=\"{\\n\t\t\t\".concat(c.join(\",\\n\t\t\"),\"\\n\t\t}\");return\"\\nconsole.log('[SSH Script] Starting...');\\nimport { Client } from 'ssh2';\\nimport net from 'net';\\nconsole.log('[SSH Script] ssh2 module loaded');\\n\\nconst conn = new Client();\\nlet shellStream = null;\\n\\nconsole.log('[SSH Script] Setting up event handlers...');\\n\\nconn.on('ready', () => {\\n\tconsole.log('[SSH Script] Connection ready event fired');\\n\tconsole.log('___SSH_CONNECTED___');\\n\\n\tconn.shell((err, stream) => {\\n\t\tif (err) {\\n\t\t\tconsole.error('[SSH Script] Shell error:', err.message);\\n\t\t\tconsole.error('___SSH_ERROR___', err.message);\\n\t\t\tprocess.exit(1);\\n\t\t}\\n\\n\t\tconsole.log('[SSH Script] Shell stream established');\\n\t\tshellStream = stream;\\n\\n\t\tstream.on('data', (data) => {\\n\t\t\tprocess.stdout.write(data);\\n\t\t});\\n\\n\t\tstream.on('close', () => {\\n\t\t\tconsole.log('___SSH_CLOSED___');\\n\t\t\tconn.end();\\n\t\t});\\n\\n\t\tstream.stderr.on('data', (data) => {\\n\t\t\tprocess.stderr.write(data);\\n\t\t});\\n\\n\t\tprocess.stdin.setEncoding('utf8');\\n\t\tprocess.stdin.on('data', (data) => {\\n\t\t\tif (shellStream) {\\n\t\t\t\tshellStream.write(data);\\n\t\t\t}\\n\t\t});\\n\t});\\n});\\n\\nconn.on('error', (err) => {\\n\tconsole.error('[SSH Script] Connection error:', err);\\n\tconsole.error('___SSH_ERROR___', err.message);\\n\t// dump helpful debug info\\n\ttry { console.error('[SSH Script] conn properties:', JSON.stringify({ localAddr: conn.localAddress, localPort: conn.localPort })); } catch(e) {}\\n\tprocess.exit(1);\\n});\\n\\nconn.on('banner', (msg) => {\\n\tconsole.log('[SSH Script] Server banner:', msg);\\n});\\n\\nconn.on('close', () => {\\n\tconsole.log('___SSH_CLOSED___');\\n});\\n\\nconsole.log('[SSH Script] Preparing connection config...');\\nconst config = \".concat(a,\";\\n\\n// expose the client identification (ident) to mimic OpenSSH for compatibility\\nif (!config.ident) {\\n\tconfig.ident = 'SSH-2.0-OpenSSH_8.9p1';\\n}\\n\\n// If OPENSSH private key format is present, try converting to PEM (sshpk must be installed in the container)\\ntry {\\n\tif (config.privateKey && typeof config.privateKey === 'string' && config.privateKey.includes('-----BEGIN OPENSSH PRIVATE KEY-----')) {\\n\t\ttry {\\n\t\t\tconst { default: sshpk } = await import('sshpk');\\n\t\t\tconst keyObj = sshpk.parsePrivateKey(config.privateKey, 'openssh');\\n\t\t\tconfig.privateKey = keyObj.toString('pem');\\n\t\t\tconsole.log('[SSH Script] Converted OPENSSH private key to PEM format');\\n\t\t} catch (e) {\\n\t\t\tconsole.log('[SSH Script] Key conversion failed:', e && e.message);\\n\t\t}\\n\t}\\n} catch(e) { console.log('[SSH Script] Key conversion check error:', e && e.message); }\\n\\nconsole.log('[SSH Script] Connecting with config:', JSON.stringify({...config, password: config.password ? '***' : undefined, privateKey: config.privateKey ? '***' : undefined, algorithms: config.algorithms}, null, 2));\\n\\n// TCP banner check to verify server sends SSH identification\\nasync function checkServerBanner(host, port, timeoutMs = 10000) {\\n\treturn new Promise((resolve) => {\\n\t\tconst sock = net.createConnection(port, host, () => {});\\n\t\tsock.setEncoding('utf8');\\n\t\tlet buffer = '';\\n\t\tlet done = false;\\n\t\tsock.on('data', (chunk) => {\\n\t\t\tbuffer += chunk;\\n\t\t\tif (buffer.indexOf('\\\\n') !== -1 && !done) {\\n\t\t\t\tdone = true;\\n\t\t\t\tconst line = buffer.split(/\\\\r?\\\\n/)[0];\\n\t\t\t\tconsole.log('[SSH Script] Server banner (raw):', line);\\n\t\t\t\tsock.destroy();\\n\t\t\t\tresolve(line);\\n\t\t\t}\\n\t\t});\\n\t\tsock.on('error', (err) => {\\n\t\t\tif (!done) { done = true; console.log('[SSH Script] Server banner check error:', err.message); resolve(null); }\\n\t\t});\\n\t\tsetTimeout(() => {\\n\t\t\tif (!done) { done = true; console.log('[SSH Script] Server banner check timeout'); try { sock.destroy(); } catch(e) {} resolve(null); }\\n\t\t}, timeoutMs);\\n\t});\\n}\\n\\ntry {\\n\tconst banner = await checkServerBanner(config.host, config.port, 10000);\\n\tif (!banner) {\\n\t\tconsole.log('[SSH Script] No server banner received before connect; proceeding anyway');\\n\t}\\n\\n\tconn.connect(config);\\n\tconsole.log('[SSH Script] connect() called, waiting for events...');\\n} catch (e) {\\n\tconsole.error('[SSH Script] Top-level error during connect flow:', e);\\n\tconsole.error('___SSH_ERROR___', e && e.message ? e.message : String(e));\\n\tprocess.exit(1);\\n}\\n\")}},{key:\"readOutput\",value:function(e){return r(function(){var n,t;return s(this,function(r){switch(r.label){case 0:console.log(\"[SSH] Starting output reader...\"),r.label=1;case 1:r.trys.push([1,5,,6]),t=function(){var t,r,o,i,c,a;return s(this,function(s){switch(s.label){case 0:return[4,e.read()];case 1:if(r=(t=s.sent()).done,o=t.value,r)return console.log(\"[SSH] Output stream ended\"),[2,\"break\"];if(i=void 0,c=void 0,\"string\"==typeof o)c=o,i=new TextEncoder().encode(o),console.log(\"[SSH] Received string output:\",c.substring(0,200));else{var l;if(null!=(l=Uint8Array)&&\"u\">typeof Symbol&&l[Symbol.hasInstance]?!l[Symbol.hasInstance](o):!(o instanceof l))return console.error(\"[SSH] Unexpected value type:\",void 0===o?\"undefined\":o&&\"u\">typeof Symbol&&o.constructor===Symbol?\"symbol\":typeof o),[2,\"continue\"];i=o,console.log(\"[SSH] Received bytes output:\",(c=new TextDecoder().decode(o)).substring(0,200))}if(c.includes(\"___SSH_CONNECTED___\"))return[2,\"continue\"];if(c.includes(\"___SSH_CLOSED___\"))return n.handleClose(),[2,\"continue\"];if(c.includes(\"___SSH_ERROR___\"))return console.error(\"SSH Error:\",null==(a=c.split(\"___SSH_ERROR___\")[1])?void 0:a.trim()),[2,\"continue\"];return n.dataCallbacks.forEach(function(e){return e(i)}),[2]}})},r.label=2;case 2:return n=this,[5,function(e){var n=\"function\"==typeof Symbol&&Symbol.iterator,t=n&&e[n],r=0;if(t)return t.call(e);if(e&&\"number\"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw TypeError(n?\"Object is not iterable.\":\"Symbol.iterator is not defined.\")}(t())];case 3:if(\"break\"===r.sent())return[3,4];return[3,2];case 4:return[3,6];case 5:return console.error(\"Error reading SSH output:\",r.sent()),this.handleClose(),[3,6];case 6:return[2]}})}).call(this)}},{key:\"waitForConnection\",value:function(){return r(function(){var e;return s(this,function(n){return e=this,[2,new Promise(function(n,t){console.log(\"[SSH] Setting up connection timeout:\",e.config.timeout+\"ms\");var r=setTimeout(function(){console.error(\"[SSH] Connection timeout! No SSH_CONNECTED marker received within\",e.config.timeout+\"ms\"),console.error(\"[SSH] Buffer contents:\",o),t(Error(\"SSH connection timeout\"))},e.config.timeout),o=\"\",s=function(t){var i=new TextDecoder().decode(t);if(o+=i,console.log(\"[SSH] Checking for connection marker in chunk:\",i.substring(0,100)),o.includes(\"___SSH_CONNECTED___\")){console.log(\"[SSH] Connection marker found!\"),clearTimeout(r);var c=e.dataCallbacks.indexOf(s);c>-1&&e.dataCallbacks.splice(c,1),n()}};console.log(\"[SSH] Registered connection check callback\"),e.dataCallbacks.push(s)})]})}).call(this)}},{key:\"handleClose\",value:function(){this.connected=!1,this.authenticated=!1,this.closeCallbacks.forEach(function(e){return e()})}},{key:\"exec\",value:function(e){return r(function(){var n,t,r,o,i,c,a,l,u,f,h;return s(this,function(s){switch(s.label){case 0:if(!this.authenticated)throw Error(\"Not authenticated\");return n=\"\\nconst { Client } = require('ssh2');\\nconst conn = new Client();\\n\\nconn.on('ready', () => {\\n\tconn.exec(\".concat(JSON.stringify(e),\", (err, stream) => {\\n\t\tif (err) {\\n\t\t\tconsole.error('ERROR:', err.message);\\n\t\t\tprocess.exit(1);\\n\t\t}\\n\t\t\\n\t\tlet stdout = '';\\n\t\tlet stderr = '';\\n\t\t\\n\t\tstream.on('data', (data) => {\\n\t\t\tstdout += data.toString();\\n\t\t});\\n\t\t\\n\t\tstream.stderr.on('data', (data) => {\\n\t\t\tstderr += data.toString();\\n\t\t});\\n\t\t\\n\t\tstream.on('close', (code) => {\\n\t\t\tconsole.log('___STDOUT___' + stdout + '___END_STDOUT___');\\n\t\t\tconsole.log('___STDERR___' + stderr + '___END_STDERR___');\\n\t\t\tconsole.log('___EXIT_CODE___' + code + '___END_EXIT_CODE___');\\n\t\t\tconn.end();\\n\t\t});\\n\t});\\n});\\n\\nconn.connect(\").concat(JSON.stringify({host:this.config.host,port:this.config.port,username:this.config.username,password:this.config.password,privateKey:this.config.privateKey,passphrase:this.config.passphrase}),\");\\n\"),t=\"ssh-exec.js\",[4,this.webContainer.fs.writeFile(t,n)];case 1:return s.sent(),[4,this.webContainer.spawn(\"node\",[t])];case 2:r=s.sent(),o=\"\",i=r.output.getReader(),s.label=3;case 3:return[4,i.read()];case 4:if(a=(c=s.sent()).done,l=c.value,a)return[3,5];return o+=new TextDecoder().decode(l),[3,3];case 5:return[4,r.exit];case 6:return s.sent(),u=o.match(RegExp(\"___STDOUT___(.*?)___END_STDOUT___\",\"s\")),f=o.match(RegExp(\"___STDERR___(.*?)___END_STDERR___\",\"s\")),h=o.match(/___EXIT_CODE___(\\d+)___END_EXIT_CODE___/),[2,{stdout:u?u[1]:\"\",stderr:f?f[1]:\"\",exitCode:h?parseInt(h[1]):0}]}})}).call(this)}},{key:\"shell\",value:function(){return r(function(){var e;return s(this,function(n){if(e=this,!this.authenticated)throw Error(\"Not authenticated\");return[2,{write:function(n){if(e.sshProcess)try{var t=\"string\"==typeof n?new TextEncoder().encode(n):n,r=e.sshProcess.input.getWriter();r.write(t),r.releaseLock()}catch(e){console.error(\"Error writing to SSH:\",e)}},onData:function(n){e.dataCallbacks.push(n)},onClose:function(n){e.closeCallbacks.push(n)},close:function(){e.sshProcess&&e.sshProcess.kill()}}]})}).call(this)}},{key:\"sftp\",value:function(){return r(function(){return s(this,function(e){throw Error(\"SFTP not yet implemented in WebContainer version\")})})()}},{key:\"disconnect\",value:function(){if(this.sshProcess)try{this.sshProcess.kill()}catch(e){console.error(\"Error killing SSH process:\",e)}this.connected=!1,this.authenticated=!1,this.connectionPromise=null}},{key:\"isConnected\",value:function(){return this.connected}},{key:\"isAuthenticated\",value:function(){return this.authenticated}}],function(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(n.prototype,e),n}();function c(e,n,t,r,o,s,i){try{var c=e[s](i),a=c.value}catch(e){t(e);return}c.done?n(a):Promise.resolve(a).then(r,o)}function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}var l=function(){var e;function n(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:\"ws://localhost:3333\",r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!(this instanceof n))throw TypeError(\"Cannot call a class as a function\");a(this,\"config\",void 0),a(this,\"ws\",null),a(this,\"stream\",null),a(this,\"connected\",!1),a(this,\"proxyUrl\",void 0),a(this,\"useLibcurl\",!1),this.config=function(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{},r=Object.keys(t);\"function\"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(t).filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.forEach(function(n){a(e,n,t[n])})}return e}({timeout:6e4,keepaliveInterval:6e4},e),this.proxyUrl=t,this.useLibcurl=r}return e=[{key:\"connect\",value:function(){var e;return(e=function(){var e;return function(e,n){var t,r,o,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create((\"function\"==typeof Iterator?Iterator:Object).prototype),c=Object.defineProperty;return c(i,\"next\",{value:a(0)}),c(i,\"throw\",{value:a(1)}),c(i,\"return\",{value:a(2)}),\"function\"==typeof Symbol&&c(i,Symbol.iterator,{value:function(){return this}}),i;function a(c){return function(a){var l=[c,a];if(t)throw TypeError(\"Generator is already executing.\");for(;i&&(i=0,l[0]&&(s=0)),s;)try{if(t=1,r&&(o=2&l[0]?r.return:l[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,l[1])).done)return o;switch(r=0,o&&(l=[2&l[0],o.value]),l[0]){case 0:case 1:o=l;break;case 4:return s.label++,{value:l[1],done:!1};case 5:s.label++,r=l[1],l=[0];continue;case 7:l=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===l[0]||2===l[0])){s=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]<o[3])){s.label=l[1];break}if(6===l[0]&&s.label<o[1]){s.label=o[1],o=l;break}if(o&&s.label<o[2]){s.label=o[2],s.ops.push(l);break}o[2]&&s.ops.pop(),s.trys.pop();continue}l=n.call(e,s)}catch(e){l=[6,e],r=0}finally{t=o=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}}}(this,function(n){return e=this,[2,new Promise(function(n,t){try{var r=\"\".concat(e.config.host,\":\").concat(e.config.port||22),o=\"\".concat(e.proxyUrl.replace(/\\/$/,\"\"),\"/\").concat(r);console.log(\"[SSH WS] Connecting to proxy:\",o),e.useLibcurl&&\"u\">typeof window&&window.parent.tb.libcurl.WebSocket?(console.log(\"[SSH WS] Using libcurl.js WebSocket\"),e.ws=new window.parent.tb.libcurl.WebSocket(o)):(console.log(\"[SSH WS] Using native WebSocket\"),e.ws=new WebSocket(o)),e.ws.binaryType=\"arraybuffer\";var s=setTimeout(function(){e.connected||t(Error(\"WebSocket connection timeout\"))},e.config.timeout);e.ws.onopen=function(){console.log(\"[SSH WS] WebSocket connected to proxy\"),clearTimeout(s);var n,t=JSON.stringify({type:\"ssh-auth\",username:e.config.username,password:e.config.password,privateKey:e.config.privateKey,passphrase:e.config.passphrase});null==(n=e.ws)||n.send(t)},e.ws.onmessage=function(r){if(\"string\"==typeof r.data)try{var o=JSON.parse(r.data);\"connected\"===o.type?(console.log(\"[SSH WS] SSH connection established\"),e.connected=!0,n()):\"error\"===o.type&&(console.error(\"[SSH WS] SSH error:\",o.message),t(Error(o.message)))}catch(e){console.error(\"[SSH WS] Failed to parse message:\",e)}else if(e.stream&&e.stream.onData){var s=new Uint8Array(r.data);e.stream.onData(s)}},e.ws.onerror=function(e){console.error(\"[SSH WS] WebSocket error:\",e),t(Error(\"WebSocket connection failed\"))},e.ws.onclose=function(){console.log(\"[SSH WS] WebSocket closed\"),e.connected=!1,e.stream&&e.stream.onClose&&e.stream.onClose()}}catch(e){t(e)}})]})},function(){var n=this,t=arguments;return new Promise(function(r,o){var s=e.apply(n,t);function i(e){c(s,r,o,i,a,\"next\",e)}function a(e){c(s,r,o,i,a,\"throw\",e)}i(void 0)})}).call(this)}},{key:\"write\",value:function(e){this.ws&&this.ws.readyState===WebSocket.OPEN?\"string\"==typeof e?this.ws.send(e):this.ws.send(e.buffer):console.error(\"[SSH WS] Cannot write - WebSocket not open\")}},{key:\"resize\",value:function(e,n){if(this.ws&&this.ws.readyState===WebSocket.OPEN){var t=JSON.stringify({type:\"resize\",cols:e,rows:n});this.ws.send(t)}}},{key:\"disconnect\",value:function(){console.log(\"[SSH WS] Disconnecting...\"),this.connected=!1,this.ws&&(this.ws.close(),this.ws=null)}},{key:\"getStream\",value:function(){return this.stream}},{key:\"setStream\",value:function(e){this.stream=e}},{key:\"isConnected\",value:function(){var e;return this.connected&&(null==(e=this.ws)?void 0:e.readyState)===WebSocket.OPEN}}],function(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(n.prototype,e),n}();function u(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function f(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{},r=Object.keys(t);\"function\"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(t).filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.forEach(function(n){u(e,n,t[n])})}return e}var h=function(){var e;function n(e){if(!(this instanceof n))throw TypeError(\"Cannot call a class as a function\");u(this,\"config\",[]),this.parse(e)}return e=[{key:\"parse\",value:function(e){var n=e.split(\"\\n\"),t=null,r=!0,o=!1,s=void 0;try{for(var i,c=n[Symbol.iterator]();!(r=(i=c.next()).done);r=!0){var a=i.value,l=a.indexOf(\"#\");if(-1!==l&&(a=a.substring(0,l)),a=a.trim()){var u=a.match(/^Host\\s+(.+)$/i);if(u){t&&this.config.push(t),t={Host:u[1].trim()};continue}if(t){var f=a.split(/\\s+/),h=f[0],p=f.slice(1).join(\" \");switch(h.toLowerCase()){case\"hostname\":t.HostName=p;break;case\"port\":t.Port=parseInt(p,10);break;case\"user\":t.User=p;break;case\"identityfile\":t.IdentityFile=p.replace(/^~/,\"/home/\".concat(sessionStorage.getItem(\"currAcc\")));break;case\"proxycommand\":t.ProxyCommand=p;break;case\"proxyjump\":t.ProxyJump=p;break;case\"forwardagent\":t.ForwardAgent=\"yes\"===p.toLowerCase();break;case\"serveraliveinterval\":t.ServerAliveInterval=parseInt(p,10);break;case\"serveralivecountmax\":t.ServerAliveCountMax=parseInt(p,10);break;case\"connecttimeout\":t.ConnectTimeout=parseInt(p,10);break;default:t[h]=p}}}}}catch(e){o=!0,s=e}finally{try{r||null==c.return||c.return()}finally{if(o)throw s}}t&&this.config.push(t)}},{key:\"getHost\",value:function(e){var n={Host:e,HostName:e,Port:22,User:sessionStorage.getItem(\"currAcc\")||\"root\"},t=!0,r=!1,o=void 0;try{for(var s,i=this.config[Symbol.iterator]();!(t=(s=i.next()).done);t=!0){var c=s.value;this.matchPattern(e,c.Host)&&(n=f({},n,c))}}catch(e){r=!0,o=e}finally{try{t||null==i.return||i.return()}finally{if(r)throw o}}var a=this.config.find(function(n){return n.Host===e});return a&&(n=f({},n,a)),n.HostName&&n.HostName===n.Host,n}},{key:\"matchPattern\",value:function(e,n){var t=n.replace(/\\./g,\"\\\\.\").replace(/\\*/g,\".*\").replace(/\\?/g,\".\");return new RegExp(\"^\".concat(t,\"$\")).test(e)}},{key:\"getAllHosts\",value:function(){return this.config}}],function(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(n.prototype,e),n}();function p(e,n,t,r,o,s,i){try{var c=e[s](i),a=c.value}catch(e){t(e);return}c.done?n(a):Promise.resolve(a).then(r,o)}function d(e){return function(){var n=this,t=arguments;return new Promise(function(r,o){var s=e.apply(n,t);function i(e){p(s,r,o,i,c,\"next\",e)}function c(e){p(s,r,o,i,c,\"throw\",e)}i(void 0)})}}function S(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function g(e,n){var t,r,o,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create((\"function\"==typeof Iterator?Iterator:Object).prototype),c=Object.defineProperty;return c(i,\"next\",{value:a(0)}),c(i,\"throw\",{value:a(1)}),c(i,\"return\",{value:a(2)}),\"function\"==typeof Symbol&&c(i,Symbol.iterator,{value:function(){return this}}),i;function a(c){return function(a){var l=[c,a];if(t)throw TypeError(\"Generator is already executing.\");for(;i&&(i=0,l[0]&&(s=0)),s;)try{if(t=1,r&&(o=2&l[0]?r.return:l[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,l[1])).done)return o;switch(r=0,o&&(l=[2&l[0],o.value]),l[0]){case 0:case 1:o=l;break;case 4:return s.label++,{value:l[1],done:!1};case 5:s.label++,r=l[1],l=[0];continue;case 7:l=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===l[0]||2===l[0])){s=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]<o[3])){s.label=l[1];break}if(6===l[0]&&s.label<o[1]){s.label=o[1],o=l;break}if(o&&s.label<o[2]){s.label=o[2],s.ops.push(l);break}o[2]&&s.ops.pop(),s.trys.pop();continue}l=n.call(e,s)}catch(e){l=[6,e],r=0}finally{t=o=0}if(5&l[0])throw l[1];return{value:l[0]?l[1]:void 0,done:!0}}}}function b(){return d(function(){var e,n,t;return g(this,function(r){switch(r.label){case 0:if(!(null==(t=window.tb||(null==(e=window.parent)?void 0:e.tb))||null==(n=t.node)?void 0:n.webContainer))throw Error(\"WebContainer not available\");if(t.node.isReady)return[3,2];return[4,new Promise(function(e){var n=setInterval(function(){t.node.isReady&&(clearInterval(n),e())},100)})];case 1:r.sent(),r.label=2;case 2:return[2]}})})()}function y(e){return d(function(){return g(this,function(n){switch(n.label){case 0:return[4,b()];case 1:return n.sent(),[2,new i(e)]}})})()}function v(){return d(function(){var e,n,t,r;return g(this,function(o){switch(o.label){case 0:if(o.trys.push([0,2,,3]),!(n=sessionStorage.getItem(\"currAcc\"))||(t=\"/home/\".concat(n,\"/.ssh/config\"),!(null==(r=window.tb||(null==(e=window.parent)?void 0:e.tb))?void 0:r.fs)))return[2,null];return[4,r.fs.promises.readFile(t,\"utf8\")];case 1:return[2,new h(o.sent())];case 2:return o.sent(),[2,null];case 3:return[2]}})})()}function m(e){return d(function(){var n,t,r,o,s;return g(this,function(i){switch(i.label){case 0:if(i.trys.push([0,2,,3]),!(t=sessionStorage.getItem(\"currAcc\"))||(r=e.replace(/^~/,\"/home/\".concat(t)),!(null==(o=window.tb||(null==(n=window.parent)?void 0:n.tb))?void 0:o.fs)))return[2,null];return[4,o.fs.promises.readFile(r,\"utf8\")];case 1:return[2,i.sent()];case 2:return s=i.sent(),console.error(\"Failed to load private key: \".concat(s)),[2,null];case 3:return[2]}})})()}let w={createSSHClient:y,createSSHClientWithProxy:function(e,n,t){return new l(e,n,t)},connectToSSH:function(e,n,t,r){return d(function(){var o;return g(this,function(s){switch(s.label){case 0:return[4,y({host:e,port:n,username:t,password:r,timeout:6e4,keepaliveInterval:6e4})];case 1:return[4,(o=s.sent()).connect()];case 2:return s.sent(),[2,o]}})})()},SSHTerminal:function(){var e;function n(e){if(!(this instanceof n))throw TypeError(\"Cannot call a class as a function\");S(this,\"client\",void 0),S(this,\"channel\",null),this.client=e}return e=[{key:\"start\",value:function(){return d(function(){var e;return g(this,function(n){switch(n.label){case 0:return e=this,[4,this.client.shell()];case 1:return e.channel=n.sent(),[2]}})}).call(this)}},{key:\"write\",value:function(e){if(!this.channel)throw Error(\"Terminal not started\");this.channel.write(e)}},{key:\"onData\",value:function(e){if(!this.channel)throw Error(\"Terminal not started\");this.channel.onData(function(n){e(new TextDecoder().decode(n))})}},{key:\"onClose\",value:function(e){if(!this.channel)throw Error(\"Terminal not started\");this.channel.onClose(e)}},{key:\"close\",value:function(){this.channel&&this.channel.close()}}],function(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(n.prototype,e),n}(),loadSSHConfig:v,loadPrivateKey:m,createSSHClientFromConfig:function(e,n){return d(function(){var t,r,o,s;return g(this,function(i){switch(i.label){case 0:return[4,v()];case 1:if(!(r=null==(t=i.sent())?void 0:t.getHost(e)))return[2,null];if(o=function(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{},r=Object.keys(t);\"function\"==typeof Object.getOwnPropertySymbols&&(r=r.concat(Object.getOwnPropertySymbols(t).filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.forEach(function(n){S(e,n,t[n])})}return e}({host:r.HostName||e,port:r.Port||22,username:r.User||sessionStorage.getItem(\"currAcc\")||\"root\",timeout:1e3*(r.ConnectTimeout||30),keepaliveInterval:1e3*(r.ServerAliveInterval||30)},n),!(r.IdentityFile&&!o.privateKey&&!o.password))return[3,3];return[4,m(r.IdentityFile)];case 2:(s=i.sent())&&(o.privateKey=s),i.label=3;case 3:return[4,y(o)];case 4:return[2,i.sent()]}})})()}};\"u\">typeof window&&(b().catch(console.error),console.log(\"TB-SSH initialized (WebContainer mode)\")),window.tbSSH=n.default})();\n//# sourceMappingURL=main.js.map"
  },
  {
    "path": "public/apps/terminal.tapp/terminal.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(/fonts/Inter.ttf);\n}\n\nbody {\n\tfont-family: Inter;\n\tmargin: 0;\n\tpadding: 0;\n\tcolor: #b6b6b6;\n\tdisplay: flex;\n\tflex-direction: column;\n\theight: 100vh;\n\tbackground-color: #000000b9;\n}\n\n#term {\n\tpadding: 0 2.5px 2.5px 2.5px;\n\tflex-grow: 1;\n\tmin-height: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.term-session {\n\tflex: 1 1 auto;\n\tmin-height: 0;\n\tdisplay: flex;\n\tposition: relative;\n\toverflow: hidden;\n}\n\n.xterm {\n\tflex: 1 1 auto;\n\theight: 100%;\n\twidth: 100%;\n}\n\n#output {\n\tmin-height: 200px;\n\toverflow-y: auto;\n\tfont-family: monospace;\n\tflex-grow: 1;\n\tscrollbar-color: #ffffff1f transparent;\n\tscrollbar-width: thin;\n}\n\n#output::-webkit-scrollbar {\n\twidth: 8px;\n}\n\n#output::-webkit-scrollbar-track {\n\tbackground-color: transparent;\n}\n\n#output::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff1f;\n\tborder-radius: 4px;\n}\n\n#output::-webkit-scrollbar-thumb:hover {\n\tbackground-color: #ffffff3c;\n}\n\n#output::-webkit-scrollbar-thumb:active {\n\tbackground-color: #ffffff5c;\n}\n\n#output div {\n\twhite-space: pre-wrap;\n}\n\n.user-input {\n\tdisplay: flex;\n\talign-items: center;\n}\n\n#input {\n\tborder: none;\n\toutline: none;\n\tbackground-color: transparent;\n\tcolor: #ffffff;\n\tpadding: 0;\n\tfont-family: monospace;\n\tcursor: default;\n\tmargin-left: -2px;\n}\n\n.error-text {\n\tcolor: #e64343;\n}\n\n.xterm-helpers {\n\tposition: absolute;\n\topacity: 0;\n}\n"
  },
  {
    "path": "public/apps/terminal.tapp/terminal_com.js",
    "content": "const tb = parent.window.tb;\nconst tb_island = tb.window.island;\nconst tb_window = tb.window;\nconst tb_context_menu = tb.context_menu;\nconst tb_dialog = tb.dialog;\n\ntb_island.addControl({\n\ttext: \"Help\",\n\tappname: \"Terminal\",\n\tid: \"terminal-help\",\n\tclick: async () => {\n\t\tterm.write(\"help\");\n\t\tawait window.handleCommand(\"help\");\n\t},\n});\n"
  },
  {
    "path": "public/apps/text editor.tapp/index.css",
    "content": "@font-face {\n\tfont-family: Inter;\n\tsrc: url(/fonts/Inter.ttf);\n}\n\nhtml,\nbody {\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbody {\n\tdisplay: flex;\n\tfont-family: Inter;\n\tcolor: #ffffff;\n\tscrollbar-width: thin;\n\tscrollbar-color: #ffffff44 transparent;\n\toverflow: hidden;\n}\n\n::-webkit-scrollbar {\n\twidth: 8px;\n\theight: 8px;\n}\n\n::-webkit-scrollbar-track {\n\tbackground: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff44;\n\tborder-radius: 20px;\n\tborder: transparent;\n}\n"
  },
  {
    "path": "public/apps/text editor.tapp/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" class=\"h-full\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Text Editor</title>\n    <link rel=\"stylesheet\" href=\"index.css\">\n</head>\n<body class=\"p-2 h-full\">\n    <!-- <div class=\"lines flex flex-col h-max max-h-full w-max p-2 bg-[#00000048] rounded-md items-center overflow-hidden\"></div> -->\n    <textarea class=\"size-full pl-2 pt-1.5 rounded-md bg-transparent leading-tight cursor-text focus-within:outline-none resize-none\" cols=\"30\" rows=\"10\" autocapitalize=\"false\" autocomplete=\"false\" spellcheck=\"false\"></textarea>\n    <script src=\"/assets/libs/tailwind.min.js\"></script>\n    <script src=\"/assets/libs/highlight.min.js\"></script>\n    <script src=\"text.com.js\" type=\"module\"></script>\n    <script src=\"index.js\" type=\"module\"></script>\n</body>\n</html>"
  },
  {
    "path": "public/apps/text editor.tapp/index.js",
    "content": "import * as webdav from \"/fs/apps/system/files.tapp/webdav.js\";\n\nwindow.webdav = webdav;\n\nfunction openFile(data) {\n\tconst textarea = document.querySelector(\"textarea\");\n\ttextarea.value = data;\n\tupdateLineNumbers();\n}\n\nasync function updateLineNumbers() {\n\tconst textarea = document.querySelector(\"textarea\");\n\t//const lines = textarea.value.split(\"\\n\");\n\t//const lineNumbers = document.querySelector(\".lines\");\n\tconst obj = await hljs.highlightAuto(document.querySelector(\"textarea\").value);\n\tconsole.log(obj.language);\n\t/*\n    lineNumbers.innerHTML = \"\";\n    lines.forEach((line, i) => {\n        console.log(i);\n        const span = document.createElement(\"span\");\n        const linesStyles = [\n            \"leading-tight\", \"font-extrabold\", \"cursor-pointer\"\n        ]\n        span.innerText = i + 1;\n        span.classList.add(...linesStyles);\n        lineNumbers.appendChild(span);\n    })\n    document.body.style.setProperty(\"--lines\", lines.length);\n    document.body.style.setProperty(\"--lines-width\", lineNumbers.offsetWidth + \"px\");\n    */\n}\n\nwindow.addEventListener(\"contextmenu\", e => {\n\te.preventDefault();\n\treturn false;\n});\n\n// window.addEventListener(\"load\", () => {\n//     updateLineNumbers();\n// })\n\nwindow.addEventListener(\"message\", async function load(e) {\n\tlet data;\n\ttry {\n\t\tdata = JSON.parse(e.data);\n\t} catch (err) {\n\t\tdata = e.data;\n\t}\n\tif (data && data.type === \"process\" && data.path) {\n\t\tif (!data.path.includes(\"http\")) {\n\t\t\tlet file = await window.parent.tb.fs.promises.readFile(data.path, \"utf8\");\n\t\t\tif (typeof file === \"object\") file = JSON.stringify(file);\n\t\t\tdocument.body.setAttribute(\"path\", data.path);\n\t\t\topenFile(file);\n\t\t\t// updateLineNumbers();\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tconst davInstances = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\t\t\tconst davUrl = data.path.split(\"/dav/\")[0] + \"/dav/\";\n\t\t\t\tconst dav = davInstances.find(d => d.url.toLowerCase().includes(davUrl));\n\t\t\t\tif (!dav) throw new Error(\"No matching dav instance found\");\n\t\t\t\tconst client = window.webdav.createClient(dav.url, {\n\t\t\t\t\tusername: dav.username,\n\t\t\t\t\tpassword: dav.password,\n\t\t\t\t\tauthType: window.webdav.AuthType.Password,\n\t\t\t\t});\n\t\t\t\tlet filePath;\n\t\t\t\tif (data.path.startsWith(\"http\")) {\n\t\t\t\t\tconst match = data.path.match(/^https?:\\/\\/[^\\/]+\\/dav\\/([^\\/]+\\/)?(.+)$/);\n\t\t\t\t\tfilePath = match ? \"/\" + match[2] : data.path;\n\t\t\t\t} else {\n\t\t\t\t\tfilePath = data.path.replace(davUrl, \"/\");\n\t\t\t\t}\n\t\t\t\tconst response = await client.getFileContents(filePath);\n\t\t\t\tdocument.body.setAttribute(\"path\", data.path);\n\t\t\t\tdocument.body.setAttribute(\"isDav\", \"true\");\n\t\t\t\tconst decoder = new TextDecoder(\"utf-8\");\n\t\t\t\tconst text = decoder.decode(response);\n\t\t\t\topenFile(text);\n\t\t\t} catch (err) {\n\t\t\t\twindow.tb.dialog.Alert({\n\t\t\t\t\ttitle: \"Failed to read dav file\",\n\t\t\t\t\tmessage: err,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\twindow.removeEventListener(\"message\", load);\n});\n\nfunction updateScroll(type, e) {\n\tconst textarea = document.querySelector(\"textarea\");\n\tif (type === \"key\") {\n\t\tconst scrollAmount = e === \"ArrowUp\" ? -20 : 20;\n\t\ttextarea.scrollTop += scrollAmount;\n\t} else if (type === \"mouse\") {\n\t\tconst scrollAmount = e.deltaY;\n\t\ttextarea.scrollTop += scrollAmount;\n\t}\n}\n\nconst textarea = document.querySelector(\"textarea\");\n// hljs.highlightElement(textarea);\ntextarea.addEventListener(\"keydown\", async e => {\n\tif ((e.ctrlKey || e.metaKey) && e.key === \"s\") {\n\t\te.preventDefault();\n\t\tconst textarea = document.querySelector(\"textarea\");\n\t\tlet ext;\n\t\tconst highlightResult = await hljs.highlightAuto(textarea.value);\n\t\tif (highlightResult.language) {\n\t\t\text = highlightResult.language;\n\t\t} else if (highlightResult._top?.aliases) {\n\t\t\text = highlightResult._top.aliases[0];\n\t\t} else {\n\t\t\text = \".txt\";\n\t\t}\n\t\tconst path = document.body.getAttribute(\"path\");\n\t\tif (path && path !== \"undefined\") {\n\t\t\tif (document.body.getAttribute(\"isDav\") === \"true\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst davInstances = JSON.parse(await window.parent.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\t\t\t\tconst davUrl = path.split(\"/dav/\")[0] + \"/dav/\";\n\t\t\t\t\tconst dav = davInstances.find(d => d.url.toLowerCase().includes(davUrl));\n\t\t\t\t\tif (!dav) throw new Error(\"No matching dav instance found\");\n\t\t\t\t\tconst client = window.webdav.createClient(dav.url, {\n\t\t\t\t\t\tusername: dav.username,\n\t\t\t\t\t\tpassword: dav.password,\n\t\t\t\t\t\tauthType: window.webdav.AuthType.Password,\n\t\t\t\t\t});\n\t\t\t\t\tlet filePath;\n\t\t\t\t\tif (path.startsWith(\"http\")) {\n\t\t\t\t\t\tconst match = path.match(/^https?:\\/\\/[^\\/]+\\/dav\\/([^\\/]+\\/)?(.+)$/);\n\t\t\t\t\t\tfilePath = match ? \"/\" + match[2] : path;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfilePath = path.replace(davUrl, \"/\");\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log(filePath);\n\t\t\t\t\tclient.putFileContents(filePath, textarea.value);\n\t\t\t\t} catch (err) {\n\t\t\t\t\twindow.tb.dialog.Alert({\n\t\t\t\t\t\ttitle: \"Failed to save dav file\",\n\t\t\t\t\t\tmessage: err.message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twindow.parent.tb.fs.promises.writeFile(path, textarea.value);\n\t\t\t}\n\t\t} else {\n\t\t\tawait tb.dialog.SaveFile({\n\t\t\t\ttitle: \"Save Text File\",\n\t\t\t\tfilename: `untitled.${ext}`,\n\t\t\t\tonOk: async txt => {\n\t\t\t\t\twindow.parent.tb.fs.writeFile(`${txt}`, textarea.value, err => {\n\t\t\t\t\t\tif (err) return alert(err);\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\t// else if(e.key === \"ArrowUp\" || e.key === \"ArrowDown\") {\n\t//     updateScroll(\"key\", e.key);\n\t// }\n});\n\n// textarea.addEventListener(\"input\", () => {\n//     updateLineNumbers();\n//     updateScroll(\"key\", \"ArrowUp\");\n// });\n\n// window.addEventListener(\"wheel\", (e) => {\n//     updateScroll(\"mouse\", e);\n// });\n"
  },
  {
    "path": "public/apps/text editor.tapp/index.json",
    "content": "{\n\t\"name\": \"Text Editor\",\n\t\"config\": {\n\t\t\"title\": \"Text Editor\",\n\t\t\"icon\": \"/fs/apps/system/text editor.tapp/icon.svg\",\n\t\t\"src\": \"/fs/apps/system/text editor.tapp/index.html\"\n\t}\n}\n"
  },
  {
    "path": "public/apps/text editor.tapp/text.com.js",
    "content": "const tb = parent.window.tb;\nconst tb_island = tb.window.island;\nconst tb_window = tb.window;\nconst tb_context_menu = tb.context_menu;\nconst tb_dialog = tb.dialog;\n\nconst appisland = window.parent.document.querySelector(\".app_island\").clientHeight + 12;\n\ntb_island.addControl({\n\ttext: \"File\",\n\tappname: \"Text Editor\",\n\tid: \"text-file\",\n\tclick: () => {\n\t\ttb.contextmenu.create({\n\t\t\tx: 112,\n\t\t\ty: appisland,\n\t\t\tiframe: false,\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\ttext: \"Open\",\n\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\tconst textarea = document.querySelector(\"textarea\");\n\t\t\t\t\t\tawait tb.dialog.FileBrowser({\n\t\t\t\t\t\t\ttitle: \"Open Text File\",\n\t\t\t\t\t\t\tfilename: \"untitled.txt\",\n\t\t\t\t\t\t\tonOk: async file => {\n\t\t\t\t\t\t\t\tdocument.body.setAttribute(\"path\", file);\n\t\t\t\t\t\t\t\ttextarea.value = await tb.vfs.whatFS(file).promises.readFile(file, \"utf8\");\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Save\",\n\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\tconst textarea = document.querySelector(\"textarea\");\n\t\t\t\t\t\tif (document.body.getAttribute(\"path\") && document.body.getAttribute(\"path\") !== \"undefined\") {\n\t\t\t\t\t\t\ttb.fs.promises.writeFile(document.body.getAttribute(\"path\"), textarea.value);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tawait tb.dialog.SaveFile({\n\t\t\t\t\t\t\t\ttitle: \"Save Text File\",\n\t\t\t\t\t\t\t\tfilename: \"untitled.txt\",\n\t\t\t\t\t\t\t\tonOk: async txt => {\n\t\t\t\t\t\t\t\t\ttb.vfs.whatFS(txt).writeFile(`${txt}`, textarea.value, err => {\n\t\t\t\t\t\t\t\t\t\tif (err) return alert(err);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t},\n});\n\ntb_island.addControl({\n\ttext: \"Computer\",\n\tappname: \"Text Editor\",\n\tid: \"text-computer\",\n\tclick: () => {\n\t\ttb.contextmenu.create({\n\t\t\tx: 156,\n\t\t\ty: appisland,\n\t\t\tiframe: false,\n\t\t\toptions: [\n\t\t\t\t{\n\t\t\t\t\ttext: \"Open\",\n\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\tconst file = document.createElement(\"input\");\n\t\t\t\t\t\tfile.type = \"file\";\n\t\t\t\t\t\tfile.onchange = async e => {\n\t\t\t\t\t\t\tlet blob = e.target.files[0];\n\t\t\t\t\t\t\tconst fileReader = new FileReader();\n\t\t\t\t\t\t\tfileReader.readAsText(blob);\n\t\t\t\t\t\t\tfileReader.onload = () => {\n\t\t\t\t\t\t\t\topenFile(fileReader.result);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t\tfile.click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttext: \"Save\",\n\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\tconst textarea = document.querySelector(\"textarea\");\n\t\t\t\t\t\tconst file = new Blob([textarea.value], { type: \"text/plain\" });\n\t\t\t\t\t\tconst url = URL.createObjectURL(file);\n\t\t\t\t\t\tconst a = document.createElement(\"a\");\n\t\t\t\t\t\ta.href = url;\n\t\t\t\t\t\ta.download = \"text.txt\";\n\t\t\t\t\t\ta.click();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t},\n});\n"
  },
  {
    "path": "public/assets/fs.ui/fs.css",
    "content": "@import 'tailwindcss';\n\n@custom-variant dark (&:where(.dark, .dark *));\n\n@media (prefers-color-scheme: light) {\n    :root {\n    --cursor-normal: url(\"/cursors/light/normal.svg\") 6 0, default;\n        --cursor-pointer: url(\"/cursors/light/pointer.svg\") 6 0, pointer;\n        --cursor-text: url(\"/cursors/light/text.svg\") 10 0, text;\n        --cursor-crosshair: url(\"/cursors/light/crosshair.svg\") 0 0, crosshair;\n        --cursor-wait: url(\"/cursors/light/wait.svg\") 0 0, wait;\n        --cursor-se-resize: url(\"/cursors/light/resize-l.svg\") 0 0, se-resize;\n        --cursor-sw-resize: url(\"/cursors/light/resize-r.svg\") 0 0, sw-resize;\n        --cursor-ne-resize: url(\"/cursors/light/resize-r.svg\") 0 0, ne-resize;\n        --cursor-n-resize: url(\"/cursors/light/resize-v.svg\") 0 0, n-resize;\n        --cursor-s-resize: url(\"/cursors/light/resize-v.svg\") 0 0, s-resize;\n        --cursor-e-resize: url(\"/cursors/light/resize-h.svg\") 0 0, e-resize;\n        --cursor-w-resize: url(\"/cursors/light/resize-h.svg\") 0 0, w-resize;\n    }\n\n    body {\n        background-color: #ffffff;\n        color: #000000c8;\n        cursor: var(--cursor-normal);\n    }\n}\n\n@media (prefers-color-scheme: dark) {\n    :root {\n        --cursor-normal: url(\"/cursors/dark/normal.svg\") 6 0, default;\n        --cursor-pointer: url(\"/cursors/dark/pointer.svg\") 6 0, pointer;\n        --cursor-text: url(\"/cursors/dark/text.svg\") 10 0, text;\n        --cursor-crosshair: url(\"/cursors/dark/crosshair.svg\") 0 0, crosshair;\n        --cursor-wait: url(\"/cursors/dark/wait.svg\") 0 0, wait;\n        --cursor-nw-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, nw-resize;\n\t--cursor-se-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, se-resize;\n\t--cursor-sw-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, sw-resize;\n\t--cursor-ne-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, ne-resize;\n\t--cursor-n-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, n-resize;\n\t--cursor-s-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, s-resize;\n\t--cursor-e-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, e-resize;\n\t--cursor-w-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, w-resize;\n    }\n\n    body {\n        background-color: #0e0e0e;\n        color: #ffffffde;\n        cursor: var(--cursor-normal);\n    }\n}\n\n@font-face {\n    font-family: Inter;\n    src: url(\"/fonts/Inter.ttf\") format(\"truetype\");\n}\n\nhtml, body {\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    font-family: Inter;\n}\n\nbody {\n    display: flex;\n    flex-direction: column;\n}\n\n::-webkit-scrollbar {\n    width: 8px;\n    height: 100%;\n}\n::-webkit-scrollbar-thumb {\n    background-color: #ffffff28;\n    border-radius: 8px;\n}\n::-webkit-scrollbar-track {\n    background-color: #ffffff10;\n    border-radius: 8px;\n}"
  },
  {
    "path": "public/assets/fs.ui/fs.js",
    "content": "const breadcrumbsEl = document.querySelector('.breadcrumbs');\nconst navBackEl = document.querySelector('.nav-back');\nconst navHomeEl = document.querySelector('.nav-home');\nconst url = new URL(window.location.href);\n\nvar breadcrumbs = []\nvar currentPath = url.pathname.replace(/\\/+/g, '/');\nvar currentDir = currentPath.split('/').pop();\nif (currentPath.endsWith('/')) {\n    currentPath = currentPath.slice(0, -1);\n}\n\nif (currentPath !== '/fs') {\n    breadcrumbs = currentPath.split('/').slice(2);\n    const fsIndex = breadcrumbs.indexOf('fs');\n    if (fsIndex !== -1) {\n        breadcrumbs.splice(fsIndex, 1);\n    }\n}\n\nconst updateBreadcrumbs = () => {\n    breadcrumbsEl.innerHTML = '';\n    if(currentPath === '/fs') {\n        const rootHolderEl = document.createElement('span');\n        rootHolderEl.classList = 'flex leading-none text-2xl text-[#ffffffff]';\n        rootHolderEl.textContent = 'root';\n        breadcrumbsEl.appendChild(rootHolderEl);\n        return\n    }\n    breadcrumbs.forEach((crumb, index) => {\n        if(index > 12) return;\n        if(index > 0) {\n            const separatorEl = document.createElement('span');\n            separatorEl.innerHTML = `\n                <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-4 stroke-2 stroke-current\">\n                    <path fill-rule=\"evenodd\" d=\"M16.28 11.47a.75.75 0 0 1 0 1.06l-7.5 7.5a.75.75 0 0 1-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 0 1 1.06-1.06l7.5 7.5Z\" clip-rule=\"evenodd\" />\n                </svg>\n            `\n            separatorEl.classList = 'flex leading-none justify-center items-center dark:text-[#ffffff68] text-[#00000088]';\n            breadcrumbsEl.appendChild(separatorEl);\n        }\n\n        const crumbEl = document.createElement('div');\n        crumbEl.textContent = crumb;\n        if (index !== breadcrumbs.length - 1) {\n            crumbEl.classList = 'flex leading-none p-1.5 text-lg dark:text-[#ffffffd5] dark:hover:text-[#fffffff8] dark:bg-[#ffffff18] dark:inset-shadow-[0_0_0_0.5px_#ffffff38] text-[#00000098] hover:text-[#000000] bg-[#00000025] inset-shadow-[0_0_0_1px_#00000068] rounded-md duration-150 cursor-(--cursor-pointer)';\n            crumbEl.onmousedown = (e) => {\n                e.preventDefault();\n                if (e.button === 0) {\n                    const newPath = breadcrumbs.slice(0, index + 1).join('/');\n                    window.location.href = `/fs/${newPath}`;\n                }\n            }\n        } else {\n            crumbEl.classList = 'flex leading-none p-1.5 text-lg dark:text-[#ffffffd5] dark:bg-[#ffffff18] dark:inset-shadow-[0_0_0_0.5px_#ffffff38] text-[#00000098] bg-[#00000025] inset-shadow-[0_0_0_1px_#00000068] rounded-md duration-150';\n        }\n        crumbEl.classList.add(\"select-none\");\n        breadcrumbsEl.appendChild(crumbEl);\n    });\n}\nupdateBreadcrumbs();\n\nnavBackEl.onmousedown = (e) => {\n    e.preventDefault();\n    if(currentPath === '/fs') return;\n    if (e.button === 0) {\n        if(breadcrumbs.length > 0) {\n            breadcrumbs.pop();\n            const newPath = breadcrumbs.join('/');\n            window.location.href = `/fs/${newPath}`;\n        } else {\n            window.location.href = '/fs/';\n        }\n    }\n}\n\nnavHomeEl.onmousedown = (e) => {\n    e.preventDefault();\n    if(currentPath === '/fs') return;\n    if (e.button === 0) {\n        window.location.href = '/fs/';\n    }\n}"
  },
  {
    "path": "public/assets/libs/comlink.min.umd.js",
    "content": "!function(e,t){\"object\"==typeof exports&&\"undefined\"!=typeof module?t(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],t):t((e=\"undefined\"!=typeof globalThis?globalThis:e||self).Comlink={})}(this,(function(e){\"use strict\";\n    /**\n         * @license\n         * Copyright 2019 Google LLC\n         * SPDX-License-Identifier: Apache-2.0\n         */const t=Symbol(\"Comlink.proxy\"),n=Symbol(\"Comlink.endpoint\"),r=Symbol(\"Comlink.releaseProxy\"),a=Symbol(\"Comlink.finalizer\"),o=Symbol(\"Comlink.thrown\"),s=e=>\"object\"==typeof e&&null!==e||\"function\"==typeof e,i=new Map([[\"proxy\",{canHandle:e=>s(e)&&e[t],serialize(e){const{port1:t,port2:n}=new MessageChannel;return c(e,t),[n,[n]]},deserialize:e=>(e.start(),l(e))}],[\"throw\",{canHandle:e=>s(e)&&o in e,serialize({value:e}){let t;return t=e instanceof Error?{isError:!0,value:{message:e.message,name:e.name,stack:e.stack}}:{isError:!1,value:e},[t,[]]},deserialize(e){if(e.isError)throw Object.assign(new Error(e.value.message),e.value);throw e.value}}]]);function c(e,t=globalThis,n=[\"*\"]){t.addEventListener(\"message\",(function r(s){if(!s||!s.data)return;if(!function(e,t){for(const n of e){if(t===n||\"*\"===n)return!0;if(n instanceof RegExp&&n.test(t))return!0}return!1}(n,s.origin))return void console.warn(`Invalid origin '${s.origin}' for comlink proxy`);const{id:i,type:l,path:p}=Object.assign({path:[]},s.data),f=(s.data.argumentList||[]).map(w);let d;try{const t=p.slice(0,-1).reduce(((e,t)=>e[t]),e),n=p.reduce(((e,t)=>e[t]),e);switch(l){case\"GET\":d=n;break;case\"SET\":t[p.slice(-1)[0]]=w(s.data.value),d=!0;break;case\"APPLY\":d=n.apply(t,f);break;case\"CONSTRUCT\":d=b(new n(...f));break;case\"ENDPOINT\":{const{port1:t,port2:n}=new MessageChannel;c(e,n),d=E(t,[t])}break;case\"RELEASE\":d=void 0;break;default:return}}catch(e){d={value:e,[o]:0}}Promise.resolve(d).catch((e=>({value:e,[o]:0}))).then((n=>{const[o,s]=v(n);t.postMessage(Object.assign(Object.assign({},o),{id:i}),s),\"RELEASE\"===l&&(t.removeEventListener(\"message\",r),u(t),a in e&&\"function\"==typeof e[a]&&e[a]())})).catch((e=>{const[n,r]=v({value:new TypeError(\"Unserializable return value\"),[o]:0});t.postMessage(Object.assign(Object.assign({},n),{id:i}),r)}))})),t.start&&t.start()}function u(e){(function(e){return\"MessagePort\"===e.constructor.name})(e)&&e.close()}function l(e,t){const n=new Map;return e.addEventListener(\"message\",(function(e){const{data:t}=e;if(!t||!t.id)return;const r=n.get(t.id);if(r)try{r(t)}finally{n.delete(t.id)}})),m(e,n,[],t)}function p(e){if(e)throw new Error(\"Proxy has been released and is not useable\")}function f(e){return k(e,new Map,{type:\"RELEASE\"}).then((()=>{u(e)}))}const d=new WeakMap,g=\"FinalizationRegistry\"in globalThis&&new FinalizationRegistry((e=>{const t=(d.get(e)||0)-1;d.set(e,t),0===t&&f(e)}));function m(e,t,a=[],o=function(){}){let s=!1;const i=new Proxy(o,{get(n,o){if(p(s),o===r)return()=>{!function(e){g&&g.unregister(e)}(i),f(e),t.clear(),s=!0};if(\"then\"===o){if(0===a.length)return{then:()=>i};const n=k(e,t,{type:\"GET\",path:a.map((e=>e.toString()))}).then(w);return n.then.bind(n)}return m(e,t,[...a,o])},set(n,r,o){p(s);const[i,c]=v(o);return k(e,t,{type:\"SET\",path:[...a,r].map((e=>e.toString())),value:i},c).then(w)},apply(r,o,i){p(s);const c=a[a.length-1];if(c===n)return k(e,t,{type:\"ENDPOINT\"}).then(w);if(\"bind\"===c)return m(e,t,a.slice(0,-1));const[u,l]=y(i);return k(e,t,{type:\"APPLY\",path:a.map((e=>e.toString())),argumentList:u},l).then(w)},construct(n,r){p(s);const[o,i]=y(r);return k(e,t,{type:\"CONSTRUCT\",path:a.map((e=>e.toString())),argumentList:o},i).then(w)}});return function(e,t){const n=(d.get(t)||0)+1;d.set(t,n),g&&g.register(e,t,e)}(i,e),i}function y(e){const t=e.map(v);return[t.map((e=>e[0])),(n=t.map((e=>e[1])),Array.prototype.concat.apply([],n))];var n}const h=new WeakMap;function E(e,t){return h.set(e,t),e}function b(e){return Object.assign(e,{[t]:!0})}function v(e){for(const[t,n]of i)if(n.canHandle(e)){const[r,a]=n.serialize(e);return[{type:\"HANDLER\",name:t,value:r},a]}return[{type:\"RAW\",value:e},h.get(e)||[]]}function w(e){switch(e.type){case\"HANDLER\":return i.get(e.name).deserialize(e.value);case\"RAW\":return e.value}}function k(e,t,n,r){return new Promise((a=>{const o=new Array(4).fill(0).map((()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16))).join(\"-\");t.set(o,a),e.start&&e.start(),e.postMessage(Object.assign({id:o},n),r)}))}e.createEndpoint=n,e.expose=c,e.finalizer=a,e.proxy=b,e.proxyMarker=t,e.releaseProxy=r,e.transfer=E,e.transferHandlers=i,e.windowEndpoint=function(e,t=globalThis,n=\"*\"){return{postMessage:(t,r)=>e.postMessage(t,n,r),addEventListener:t.addEventListener.bind(t),removeEventListener:t.removeEventListener.bind(t)}},e.wrap=l}));\n    //# sourceMappingURL=comlink.min.umd.js.map"
  },
  {
    "path": "public/assets/libs/idb-keyval.js",
    "content": "function _slicedToArray(t,n){return _arrayWithHoles(t)||_iterableToArrayLimit(t,n)||_unsupportedIterableToArray(t,n)||_nonIterableRest()}function _nonIterableRest(){throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}function _unsupportedIterableToArray(t,n){if(t){if(\"string\"==typeof t)return _arrayLikeToArray(t,n);var r=Object.prototype.toString.call(t).slice(8,-1);return\"Object\"===r&&t.constructor&&(r=t.constructor.name),\"Map\"===r||\"Set\"===r?Array.from(t):\"Arguments\"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(t,n):void 0}}function _arrayLikeToArray(t,n){(null==n||n>t.length)&&(n=t.length);for(var r=0,e=new Array(n);r<n;r++)e[r]=t[r];return e}function _iterableToArrayLimit(t,n){var r=null==t?null:\"undefined\"!=typeof Symbol&&t[Symbol.iterator]||t[\"@@iterator\"];if(null!=r){var e,o,u=[],i=!0,a=!1;try{for(r=r.call(t);!(i=(e=r.next()).done)&&(u.push(e.value),!n||u.length!==n);i=!0);}catch(t){a=!0,o=t}finally{try{i||null==r.return||r.return()}finally{if(a)throw o}}return u}}function _arrayWithHoles(t){if(Array.isArray(t))return t}function _typeof(t){return _typeof=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t},_typeof(t)}!function(t,n){\"object\"===(\"undefined\"==typeof exports?\"undefined\":_typeof(exports))&&\"undefined\"!=typeof module?n(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],n):n((t=\"undefined\"!=typeof globalThis?globalThis:t||self).idbKeyval={})}(this,(function(t){\"use strict\";function n(t){return new Promise((function(n,r){t.oncomplete=t.onsuccess=function(){return n(t.result)},t.onabort=t.onerror=function(){return r(t.error)}}))}function r(t,r){var e=indexedDB.open(t);e.onupgradeneeded=function(){return e.result.createObjectStore(r)};var o=n(e);return function(t,n){return o.then((function(e){return n(e.transaction(r,t).objectStore(r))}))}}var e;function o(){return e||(e=r(\"keyval-store\",\"keyval\")),e}function u(t,r){return t.openCursor().onsuccess=function(){this.result&&(r(this.result),this.result.continue())},n(t.transaction)}t.clear=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o();return t(\"readwrite\",(function(t){return t.clear(),n(t.transaction)}))},t.createStore=r,t.del=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o();return r(\"readwrite\",(function(r){return r.delete(t),n(r.transaction)}))},t.delMany=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o();return r(\"readwrite\",(function(r){return t.forEach((function(t){return r.delete(t)})),n(r.transaction)}))},t.entries=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o();return t(\"readonly\",(function(r){if(r.getAll&&r.getAllKeys)return Promise.all([n(r.getAllKeys()),n(r.getAll())]).then((function(t){var n=_slicedToArray(t,2),r=n[0],e=n[1];return r.map((function(t,n){return[t,e[n]]}))}));var e=[];return t(\"readonly\",(function(t){return u(t,(function(t){return e.push([t.key,t.value])})).then((function(){return e}))}))}))},t.get=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o();return r(\"readonly\",(function(r){return n(r.get(t))}))},t.getMany=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o();return r(\"readonly\",(function(r){return Promise.all(t.map((function(t){return n(r.get(t))})))}))},t.keys=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o();return t(\"readonly\",(function(t){if(t.getAllKeys)return n(t.getAllKeys());var r=[];return u(t,(function(t){return r.push(t.key)})).then((function(){return r}))}))},t.promisifyRequest=n,t.set=function(t,r){var e=arguments.length>2&&void 0!==arguments[2]?arguments[2]:o();return e(\"readwrite\",(function(e){return e.put(r,t),n(e.transaction)}))},t.setMany=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o();return r(\"readwrite\",(function(r){return t.forEach((function(t){return r.put(t[1],t[0])})),n(r.transaction)}))},t.update=function(t,r){var e=arguments.length>2&&void 0!==arguments[2]?arguments[2]:o();return e(\"readwrite\",(function(e){return new Promise((function(o,u){e.get(t).onsuccess=function(){try{e.put(r(this.result),t),o(n(e.transaction))}catch(t){u(t)}}}))}))},t.values=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o();return t(\"readonly\",(function(t){if(t.getAll)return n(t.getAll());var r=[];return u(t,(function(t){return r.push(t.value)})).then((function(){return r}))}))},Object.defineProperty(t,\"__esModule\",{value:!0})}));"
  },
  {
    "path": "public/assets/libs/mime.iife.js",
    "content": "var mime = (function (exports) {\n    'use strict';\n\n    const types$1 = {\n        'application/prs.cww': ['cww'],\n        'application/prs.xsf+xml': ['xsf'],\n        'application/vnd.1000minds.decision-model+xml': ['1km'],\n        'application/vnd.3gpp.pic-bw-large': ['plb'],\n        'application/vnd.3gpp.pic-bw-small': ['psb'],\n        'application/vnd.3gpp.pic-bw-var': ['pvb'],\n        'application/vnd.3gpp2.tcap': ['tcap'],\n        'application/vnd.3m.post-it-notes': ['pwn'],\n        'application/vnd.accpac.simply.aso': ['aso'],\n        'application/vnd.accpac.simply.imp': ['imp'],\n        'application/vnd.acucobol': ['acu'],\n        'application/vnd.acucorp': ['atc', 'acutc'],\n        'application/vnd.adobe.air-application-installer-package+zip': ['air'],\n        'application/vnd.adobe.formscentral.fcdt': ['fcdt'],\n        'application/vnd.adobe.fxp': ['fxp', 'fxpl'],\n        'application/vnd.adobe.xdp+xml': ['xdp'],\n        'application/vnd.adobe.xfdf': ['*xfdf'],\n        'application/vnd.age': ['age'],\n        'application/vnd.ahead.space': ['ahead'],\n        'application/vnd.airzip.filesecure.azf': ['azf'],\n        'application/vnd.airzip.filesecure.azs': ['azs'],\n        'application/vnd.amazon.ebook': ['azw'],\n        'application/vnd.americandynamics.acc': ['acc'],\n        'application/vnd.amiga.ami': ['ami'],\n        'application/vnd.android.package-archive': ['apk'],\n        'application/vnd.anser-web-certificate-issue-initiation': ['cii'],\n        'application/vnd.anser-web-funds-transfer-initiation': ['fti'],\n        'application/vnd.antix.game-component': ['atx'],\n        'application/vnd.apple.installer+xml': ['mpkg'],\n        'application/vnd.apple.keynote': ['key'],\n        'application/vnd.apple.mpegurl': ['m3u8'],\n        'application/vnd.apple.numbers': ['numbers'],\n        'application/vnd.apple.pages': ['pages'],\n        'application/vnd.apple.pkpass': ['pkpass'],\n        'application/vnd.aristanetworks.swi': ['swi'],\n        'application/vnd.astraea-software.iota': ['iota'],\n        'application/vnd.audiograph': ['aep'],\n        'application/vnd.balsamiq.bmml+xml': ['bmml'],\n        'application/vnd.blueice.multipass': ['mpm'],\n        'application/vnd.bmi': ['bmi'],\n        'application/vnd.businessobjects': ['rep'],\n        'application/vnd.chemdraw+xml': ['cdxml'],\n        'application/vnd.chipnuts.karaoke-mmd': ['mmd'],\n        'application/vnd.cinderella': ['cdy'],\n        'application/vnd.citationstyles.style+xml': ['csl'],\n        'application/vnd.claymore': ['cla'],\n        'application/vnd.cloanto.rp9': ['rp9'],\n        'application/vnd.clonk.c4group': ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'],\n        'application/vnd.cluetrust.cartomobile-config': ['c11amc'],\n        'application/vnd.cluetrust.cartomobile-config-pkg': ['c11amz'],\n        'application/vnd.commonspace': ['csp'],\n        'application/vnd.contact.cmsg': ['cdbcmsg'],\n        'application/vnd.cosmocaller': ['cmc'],\n        'application/vnd.crick.clicker': ['clkx'],\n        'application/vnd.crick.clicker.keyboard': ['clkk'],\n        'application/vnd.crick.clicker.palette': ['clkp'],\n        'application/vnd.crick.clicker.template': ['clkt'],\n        'application/vnd.crick.clicker.wordbank': ['clkw'],\n        'application/vnd.criticaltools.wbs+xml': ['wbs'],\n        'application/vnd.ctc-posml': ['pml'],\n        'application/vnd.cups-ppd': ['ppd'],\n        'application/vnd.curl.car': ['car'],\n        'application/vnd.curl.pcurl': ['pcurl'],\n        'application/vnd.dart': ['dart'],\n        'application/vnd.data-vision.rdz': ['rdz'],\n        'application/vnd.dbf': ['dbf'],\n        'application/vnd.dece.data': ['uvf', 'uvvf', 'uvd', 'uvvd'],\n        'application/vnd.dece.ttml+xml': ['uvt', 'uvvt'],\n        'application/vnd.dece.unspecified': ['uvx', 'uvvx'],\n        'application/vnd.dece.zip': ['uvz', 'uvvz'],\n        'application/vnd.denovo.fcselayout-link': ['fe_launch'],\n        'application/vnd.dna': ['dna'],\n        'application/vnd.dolby.mlp': ['mlp'],\n        'application/vnd.dpgraph': ['dpg'],\n        'application/vnd.dreamfactory': ['dfac'],\n        'application/vnd.ds-keypoint': ['kpxx'],\n        'application/vnd.dvb.ait': ['ait'],\n        'application/vnd.dvb.service': ['svc'],\n        'application/vnd.dynageo': ['geo'],\n        'application/vnd.ecowin.chart': ['mag'],\n        'application/vnd.enliven': ['nml'],\n        'application/vnd.epson.esf': ['esf'],\n        'application/vnd.epson.msf': ['msf'],\n        'application/vnd.epson.quickanime': ['qam'],\n        'application/vnd.epson.salt': ['slt'],\n        'application/vnd.epson.ssf': ['ssf'],\n        'application/vnd.eszigno3+xml': ['es3', 'et3'],\n        'application/vnd.ezpix-album': ['ez2'],\n        'application/vnd.ezpix-package': ['ez3'],\n        'application/vnd.fdf': ['*fdf'],\n        'application/vnd.fdsn.mseed': ['mseed'],\n        'application/vnd.fdsn.seed': ['seed', 'dataless'],\n        'application/vnd.flographit': ['gph'],\n        'application/vnd.fluxtime.clip': ['ftc'],\n        'application/vnd.framemaker': ['fm', 'frame', 'maker', 'book'],\n        'application/vnd.frogans.fnc': ['fnc'],\n        'application/vnd.frogans.ltf': ['ltf'],\n        'application/vnd.fsc.weblaunch': ['fsc'],\n        'application/vnd.fujitsu.oasys': ['oas'],\n        'application/vnd.fujitsu.oasys2': ['oa2'],\n        'application/vnd.fujitsu.oasys3': ['oa3'],\n        'application/vnd.fujitsu.oasysgp': ['fg5'],\n        'application/vnd.fujitsu.oasysprs': ['bh2'],\n        'application/vnd.fujixerox.ddd': ['ddd'],\n        'application/vnd.fujixerox.docuworks': ['xdw'],\n        'application/vnd.fujixerox.docuworks.binder': ['xbd'],\n        'application/vnd.fuzzysheet': ['fzs'],\n        'application/vnd.genomatix.tuxedo': ['txd'],\n        'application/vnd.geogebra.file': ['ggb'],\n        'application/vnd.geogebra.slides': ['ggs'],\n        'application/vnd.geogebra.tool': ['ggt'],\n        'application/vnd.geometry-explorer': ['gex', 'gre'],\n        'application/vnd.geonext': ['gxt'],\n        'application/vnd.geoplan': ['g2w'],\n        'application/vnd.geospace': ['g3w'],\n        'application/vnd.gmx': ['gmx'],\n        'application/vnd.google-apps.document': ['gdoc'],\n        'application/vnd.google-apps.presentation': ['gslides'],\n        'application/vnd.google-apps.spreadsheet': ['gsheet'],\n        'application/vnd.google-earth.kml+xml': ['kml'],\n        'application/vnd.google-earth.kmz': ['kmz'],\n        'application/vnd.gov.sk.xmldatacontainer+xml': ['xdcf'],\n        'application/vnd.grafeq': ['gqf', 'gqs'],\n        'application/vnd.groove-account': ['gac'],\n        'application/vnd.groove-help': ['ghf'],\n        'application/vnd.groove-identity-message': ['gim'],\n        'application/vnd.groove-injector': ['grv'],\n        'application/vnd.groove-tool-message': ['gtm'],\n        'application/vnd.groove-tool-template': ['tpl'],\n        'application/vnd.groove-vcard': ['vcg'],\n        'application/vnd.hal+xml': ['hal'],\n        'application/vnd.handheld-entertainment+xml': ['zmm'],\n        'application/vnd.hbci': ['hbci'],\n        'application/vnd.hhe.lesson-player': ['les'],\n        'application/vnd.hp-hpgl': ['hpgl'],\n        'application/vnd.hp-hpid': ['hpid'],\n        'application/vnd.hp-hps': ['hps'],\n        'application/vnd.hp-jlyt': ['jlt'],\n        'application/vnd.hp-pcl': ['pcl'],\n        'application/vnd.hp-pclxl': ['pclxl'],\n        'application/vnd.hydrostatix.sof-data': ['sfd-hdstx'],\n        'application/vnd.ibm.minipay': ['mpy'],\n        'application/vnd.ibm.modcap': ['afp', 'listafp', 'list3820'],\n        'application/vnd.ibm.rights-management': ['irm'],\n        'application/vnd.ibm.secure-container': ['sc'],\n        'application/vnd.iccprofile': ['icc', 'icm'],\n        'application/vnd.igloader': ['igl'],\n        'application/vnd.immervision-ivp': ['ivp'],\n        'application/vnd.immervision-ivu': ['ivu'],\n        'application/vnd.insors.igm': ['igm'],\n        'application/vnd.intercon.formnet': ['xpw', 'xpx'],\n        'application/vnd.intergeo': ['i2g'],\n        'application/vnd.intu.qbo': ['qbo'],\n        'application/vnd.intu.qfx': ['qfx'],\n        'application/vnd.ipunplugged.rcprofile': ['rcprofile'],\n        'application/vnd.irepository.package+xml': ['irp'],\n        'application/vnd.is-xpr': ['xpr'],\n        'application/vnd.isac.fcs': ['fcs'],\n        'application/vnd.jam': ['jam'],\n        'application/vnd.jcp.javame.midlet-rms': ['rms'],\n        'application/vnd.jisp': ['jisp'],\n        'application/vnd.joost.joda-archive': ['joda'],\n        'application/vnd.kahootz': ['ktz', 'ktr'],\n        'application/vnd.kde.karbon': ['karbon'],\n        'application/vnd.kde.kchart': ['chrt'],\n        'application/vnd.kde.kformula': ['kfo'],\n        'application/vnd.kde.kivio': ['flw'],\n        'application/vnd.kde.kontour': ['kon'],\n        'application/vnd.kde.kpresenter': ['kpr', 'kpt'],\n        'application/vnd.kde.kspread': ['ksp'],\n        'application/vnd.kde.kword': ['kwd', 'kwt'],\n        'application/vnd.kenameaapp': ['htke'],\n        'application/vnd.kidspiration': ['kia'],\n        'application/vnd.kinar': ['kne', 'knp'],\n        'application/vnd.koan': ['skp', 'skd', 'skt', 'skm'],\n        'application/vnd.kodak-descriptor': ['sse'],\n        'application/vnd.las.las+xml': ['lasxml'],\n        'application/vnd.llamagraphics.life-balance.desktop': ['lbd'],\n        'application/vnd.llamagraphics.life-balance.exchange+xml': ['lbe'],\n        'application/vnd.lotus-1-2-3': ['123'],\n        'application/vnd.lotus-approach': ['apr'],\n        'application/vnd.lotus-freelance': ['pre'],\n        'application/vnd.lotus-notes': ['nsf'],\n        'application/vnd.lotus-organizer': ['org'],\n        'application/vnd.lotus-screencam': ['scm'],\n        'application/vnd.lotus-wordpro': ['lwp'],\n        'application/vnd.macports.portpkg': ['portpkg'],\n        'application/vnd.mapbox-vector-tile': ['mvt'],\n        'application/vnd.mcd': ['mcd'],\n        'application/vnd.medcalcdata': ['mc1'],\n        'application/vnd.mediastation.cdkey': ['cdkey'],\n        'application/vnd.mfer': ['mwf'],\n        'application/vnd.mfmp': ['mfm'],\n        'application/vnd.micrografx.flo': ['flo'],\n        'application/vnd.micrografx.igx': ['igx'],\n        'application/vnd.mif': ['mif'],\n        'application/vnd.mobius.daf': ['daf'],\n        'application/vnd.mobius.dis': ['dis'],\n        'application/vnd.mobius.mbk': ['mbk'],\n        'application/vnd.mobius.mqy': ['mqy'],\n        'application/vnd.mobius.msl': ['msl'],\n        'application/vnd.mobius.plc': ['plc'],\n        'application/vnd.mobius.txf': ['txf'],\n        'application/vnd.mophun.application': ['mpn'],\n        'application/vnd.mophun.certificate': ['mpc'],\n        'application/vnd.mozilla.xul+xml': ['xul'],\n        'application/vnd.ms-artgalry': ['cil'],\n        'application/vnd.ms-cab-compressed': ['cab'],\n        'application/vnd.ms-excel': ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw'],\n        'application/vnd.ms-excel.addin.macroenabled.12': ['xlam'],\n        'application/vnd.ms-excel.sheet.binary.macroenabled.12': ['xlsb'],\n        'application/vnd.ms-excel.sheet.macroenabled.12': ['xlsm'],\n        'application/vnd.ms-excel.template.macroenabled.12': ['xltm'],\n        'application/vnd.ms-fontobject': ['eot'],\n        'application/vnd.ms-htmlhelp': ['chm'],\n        'application/vnd.ms-ims': ['ims'],\n        'application/vnd.ms-lrm': ['lrm'],\n        'application/vnd.ms-officetheme': ['thmx'],\n        'application/vnd.ms-outlook': ['msg'],\n        'application/vnd.ms-pki.seccat': ['cat'],\n        'application/vnd.ms-pki.stl': ['*stl'],\n        'application/vnd.ms-powerpoint': ['ppt', 'pps', 'pot'],\n        'application/vnd.ms-powerpoint.addin.macroenabled.12': ['ppam'],\n        'application/vnd.ms-powerpoint.presentation.macroenabled.12': ['pptm'],\n        'application/vnd.ms-powerpoint.slide.macroenabled.12': ['sldm'],\n        'application/vnd.ms-powerpoint.slideshow.macroenabled.12': ['ppsm'],\n        'application/vnd.ms-powerpoint.template.macroenabled.12': ['potm'],\n        'application/vnd.ms-project': ['*mpp', 'mpt'],\n        'application/vnd.ms-word.document.macroenabled.12': ['docm'],\n        'application/vnd.ms-word.template.macroenabled.12': ['dotm'],\n        'application/vnd.ms-works': ['wps', 'wks', 'wcm', 'wdb'],\n        'application/vnd.ms-wpl': ['wpl'],\n        'application/vnd.ms-xpsdocument': ['xps'],\n        'application/vnd.mseq': ['mseq'],\n        'application/vnd.musician': ['mus'],\n        'application/vnd.muvee.style': ['msty'],\n        'application/vnd.mynfc': ['taglet'],\n        'application/vnd.nato.bindingdataobject+xml': ['bdo'],\n        'application/vnd.neurolanguage.nlu': ['nlu'],\n        'application/vnd.nitf': ['ntf', 'nitf'],\n        'application/vnd.noblenet-directory': ['nnd'],\n        'application/vnd.noblenet-sealer': ['nns'],\n        'application/vnd.noblenet-web': ['nnw'],\n        'application/vnd.nokia.n-gage.ac+xml': ['*ac'],\n        'application/vnd.nokia.n-gage.data': ['ngdat'],\n        'application/vnd.nokia.n-gage.symbian.install': ['n-gage'],\n        'application/vnd.nokia.radio-preset': ['rpst'],\n        'application/vnd.nokia.radio-presets': ['rpss'],\n        'application/vnd.novadigm.edm': ['edm'],\n        'application/vnd.novadigm.edx': ['edx'],\n        'application/vnd.novadigm.ext': ['ext'],\n        'application/vnd.oasis.opendocument.chart': ['odc'],\n        'application/vnd.oasis.opendocument.chart-template': ['otc'],\n        'application/vnd.oasis.opendocument.database': ['odb'],\n        'application/vnd.oasis.opendocument.formula': ['odf'],\n        'application/vnd.oasis.opendocument.formula-template': ['odft'],\n        'application/vnd.oasis.opendocument.graphics': ['odg'],\n        'application/vnd.oasis.opendocument.graphics-template': ['otg'],\n        'application/vnd.oasis.opendocument.image': ['odi'],\n        'application/vnd.oasis.opendocument.image-template': ['oti'],\n        'application/vnd.oasis.opendocument.presentation': ['odp'],\n        'application/vnd.oasis.opendocument.presentation-template': ['otp'],\n        'application/vnd.oasis.opendocument.spreadsheet': ['ods'],\n        'application/vnd.oasis.opendocument.spreadsheet-template': ['ots'],\n        'application/vnd.oasis.opendocument.text': ['odt'],\n        'application/vnd.oasis.opendocument.text-master': ['odm'],\n        'application/vnd.oasis.opendocument.text-template': ['ott'],\n        'application/vnd.oasis.opendocument.text-web': ['oth'],\n        'application/vnd.olpc-sugar': ['xo'],\n        'application/vnd.oma.dd2+xml': ['dd2'],\n        'application/vnd.openblox.game+xml': ['obgx'],\n        'application/vnd.openofficeorg.extension': ['oxt'],\n        'application/vnd.openstreetmap.data+xml': ['osm'],\n        'application/vnd.openxmlformats-officedocument.presentationml.presentation': [\n            'pptx',\n        ],\n        'application/vnd.openxmlformats-officedocument.presentationml.slide': [\n            'sldx',\n        ],\n        'application/vnd.openxmlformats-officedocument.presentationml.slideshow': [\n            'ppsx',\n        ],\n        'application/vnd.openxmlformats-officedocument.presentationml.template': [\n            'potx',\n        ],\n        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['xlsx'],\n        'application/vnd.openxmlformats-officedocument.spreadsheetml.template': [\n            'xltx',\n        ],\n        'application/vnd.openxmlformats-officedocument.wordprocessingml.document': [\n            'docx',\n        ],\n        'application/vnd.openxmlformats-officedocument.wordprocessingml.template': [\n            'dotx',\n        ],\n        'application/vnd.osgeo.mapguide.package': ['mgp'],\n        'application/vnd.osgi.dp': ['dp'],\n        'application/vnd.osgi.subsystem': ['esa'],\n        'application/vnd.palm': ['pdb', 'pqa', 'oprc'],\n        'application/vnd.pawaafile': ['paw'],\n        'application/vnd.pg.format': ['str'],\n        'application/vnd.pg.osasli': ['ei6'],\n        'application/vnd.picsel': ['efif'],\n        'application/vnd.pmi.widget': ['wg'],\n        'application/vnd.pocketlearn': ['plf'],\n        'application/vnd.powerbuilder6': ['pbd'],\n        'application/vnd.previewsystems.box': ['box'],\n        'application/vnd.proteus.magazine': ['mgz'],\n        'application/vnd.publishare-delta-tree': ['qps'],\n        'application/vnd.pvi.ptid1': ['ptid'],\n        'application/vnd.pwg-xhtml-print+xml': ['xhtm'],\n        'application/vnd.quark.quarkxpress': [\n            'qxd',\n            'qxt',\n            'qwd',\n            'qwt',\n            'qxl',\n            'qxb',\n        ],\n        'application/vnd.rar': ['rar'],\n        'application/vnd.realvnc.bed': ['bed'],\n        'application/vnd.recordare.musicxml': ['mxl'],\n        'application/vnd.recordare.musicxml+xml': ['musicxml'],\n        'application/vnd.rig.cryptonote': ['cryptonote'],\n        'application/vnd.rim.cod': ['cod'],\n        'application/vnd.rn-realmedia': ['rm'],\n        'application/vnd.rn-realmedia-vbr': ['rmvb'],\n        'application/vnd.route66.link66+xml': ['link66'],\n        'application/vnd.sailingtracker.track': ['st'],\n        'application/vnd.seemail': ['see'],\n        'application/vnd.sema': ['sema'],\n        'application/vnd.semd': ['semd'],\n        'application/vnd.semf': ['semf'],\n        'application/vnd.shana.informed.formdata': ['ifm'],\n        'application/vnd.shana.informed.formtemplate': ['itp'],\n        'application/vnd.shana.informed.interchange': ['iif'],\n        'application/vnd.shana.informed.package': ['ipk'],\n        'application/vnd.simtech-mindmapper': ['twd', 'twds'],\n        'application/vnd.smaf': ['mmf'],\n        'application/vnd.smart.teacher': ['teacher'],\n        'application/vnd.software602.filler.form+xml': ['fo'],\n        'application/vnd.solent.sdkm+xml': ['sdkm', 'sdkd'],\n        'application/vnd.spotfire.dxp': ['dxp'],\n        'application/vnd.spotfire.sfs': ['sfs'],\n        'application/vnd.stardivision.calc': ['sdc'],\n        'application/vnd.stardivision.draw': ['sda'],\n        'application/vnd.stardivision.impress': ['sdd'],\n        'application/vnd.stardivision.math': ['smf'],\n        'application/vnd.stardivision.writer': ['sdw', 'vor'],\n        'application/vnd.stardivision.writer-global': ['sgl'],\n        'application/vnd.stepmania.package': ['smzip'],\n        'application/vnd.stepmania.stepchart': ['sm'],\n        'application/vnd.sun.wadl+xml': ['wadl'],\n        'application/vnd.sun.xml.calc': ['sxc'],\n        'application/vnd.sun.xml.calc.template': ['stc'],\n        'application/vnd.sun.xml.draw': ['sxd'],\n        'application/vnd.sun.xml.draw.template': ['std'],\n        'application/vnd.sun.xml.impress': ['sxi'],\n        'application/vnd.sun.xml.impress.template': ['sti'],\n        'application/vnd.sun.xml.math': ['sxm'],\n        'application/vnd.sun.xml.writer': ['sxw'],\n        'application/vnd.sun.xml.writer.global': ['sxg'],\n        'application/vnd.sun.xml.writer.template': ['stw'],\n        'application/vnd.sus-calendar': ['sus', 'susp'],\n        'application/vnd.svd': ['svd'],\n        'application/vnd.symbian.install': ['sis', 'sisx'],\n        'application/vnd.syncml+xml': ['xsm'],\n        'application/vnd.syncml.dm+wbxml': ['bdm'],\n        'application/vnd.syncml.dm+xml': ['xdm'],\n        'application/vnd.syncml.dmddf+xml': ['ddf'],\n        'application/vnd.tao.intent-module-archive': ['tao'],\n        'application/vnd.tcpdump.pcap': ['pcap', 'cap', 'dmp'],\n        'application/vnd.tmobile-livetv': ['tmo'],\n        'application/vnd.trid.tpt': ['tpt'],\n        'application/vnd.triscape.mxs': ['mxs'],\n        'application/vnd.trueapp': ['tra'],\n        'application/vnd.ufdl': ['ufd', 'ufdl'],\n        'application/vnd.uiq.theme': ['utz'],\n        'application/vnd.umajin': ['umj'],\n        'application/vnd.unity': ['unityweb'],\n        'application/vnd.uoml+xml': ['uoml', 'uo'],\n        'application/vnd.vcx': ['vcx'],\n        'application/vnd.visio': ['vsd', 'vst', 'vss', 'vsw'],\n        'application/vnd.visionary': ['vis'],\n        'application/vnd.vsf': ['vsf'],\n        'application/vnd.wap.wbxml': ['wbxml'],\n        'application/vnd.wap.wmlc': ['wmlc'],\n        'application/vnd.wap.wmlscriptc': ['wmlsc'],\n        'application/vnd.webturbo': ['wtb'],\n        'application/vnd.wolfram.player': ['nbp'],\n        'application/vnd.wordperfect': ['wpd'],\n        'application/vnd.wqd': ['wqd'],\n        'application/vnd.wt.stf': ['stf'],\n        'application/vnd.xara': ['xar'],\n        'application/vnd.xfdl': ['xfdl'],\n        'application/vnd.yamaha.hv-dic': ['hvd'],\n        'application/vnd.yamaha.hv-script': ['hvs'],\n        'application/vnd.yamaha.hv-voice': ['hvp'],\n        'application/vnd.yamaha.openscoreformat': ['osf'],\n        'application/vnd.yamaha.openscoreformat.osfpvg+xml': ['osfpvg'],\n        'application/vnd.yamaha.smaf-audio': ['saf'],\n        'application/vnd.yamaha.smaf-phrase': ['spf'],\n        'application/vnd.yellowriver-custom-menu': ['cmp'],\n        'application/vnd.zul': ['zir', 'zirz'],\n        'application/vnd.zzazz.deck+xml': ['zaz'],\n        'application/x-7z-compressed': ['7z'],\n        'application/x-abiword': ['abw'],\n        'application/x-ace-compressed': ['ace'],\n        'application/x-apple-diskimage': ['*dmg'],\n        'application/x-arj': ['arj'],\n        'application/x-authorware-bin': ['aab', 'x32', 'u32', 'vox'],\n        'application/x-authorware-map': ['aam'],\n        'application/x-authorware-seg': ['aas'],\n        'application/x-bcpio': ['bcpio'],\n        'application/x-bdoc': ['*bdoc'],\n        'application/x-bittorrent': ['torrent'],\n        'application/x-blorb': ['blb', 'blorb'],\n        'application/x-bzip': ['bz'],\n        'application/x-bzip2': ['bz2', 'boz'],\n        'application/x-cbr': ['cbr', 'cba', 'cbt', 'cbz', 'cb7'],\n        'application/x-cdlink': ['vcd'],\n        'application/x-cfs-compressed': ['cfs'],\n        'application/x-chat': ['chat'],\n        'application/x-chess-pgn': ['pgn'],\n        'application/x-chrome-extension': ['crx'],\n        'application/x-cocoa': ['cco'],\n        'application/x-conference': ['nsc'],\n        'application/x-cpio': ['cpio'],\n        'application/x-csh': ['csh'],\n        'application/x-debian-package': ['*deb', 'udeb'],\n        'application/x-dgc-compressed': ['dgc'],\n        'application/x-director': [\n            'dir',\n            'dcr',\n            'dxr',\n            'cst',\n            'cct',\n            'cxt',\n            'w3d',\n            'fgd',\n            'swa',\n        ],\n        'application/x-doom': ['wad'],\n        'application/x-dtbncx+xml': ['ncx'],\n        'application/x-dtbook+xml': ['dtb'],\n        'application/x-dtbresource+xml': ['res'],\n        'application/x-dvi': ['dvi'],\n        'application/x-envoy': ['evy'],\n        'application/x-eva': ['eva'],\n        'application/x-font-bdf': ['bdf'],\n        'application/x-font-ghostscript': ['gsf'],\n        'application/x-font-linux-psf': ['psf'],\n        'application/x-font-pcf': ['pcf'],\n        'application/x-font-snf': ['snf'],\n        'application/x-font-type1': ['pfa', 'pfb', 'pfm', 'afm'],\n        'application/x-freearc': ['arc'],\n        'application/x-futuresplash': ['spl'],\n        'application/x-gca-compressed': ['gca'],\n        'application/x-glulx': ['ulx'],\n        'application/x-gnumeric': ['gnumeric'],\n        'application/x-gramps-xml': ['gramps'],\n        'application/x-gtar': ['gtar'],\n        'application/x-hdf': ['hdf'],\n        'application/x-httpd-php': ['php'],\n        'application/x-install-instructions': ['install'],\n        'application/x-iso9660-image': ['*iso'],\n        'application/x-iwork-keynote-sffkey': ['*key'],\n        'application/x-iwork-numbers-sffnumbers': ['*numbers'],\n        'application/x-iwork-pages-sffpages': ['*pages'],\n        'application/x-java-archive-diff': ['jardiff'],\n        'application/x-java-jnlp-file': ['jnlp'],\n        'application/x-keepass2': ['kdbx'],\n        'application/x-latex': ['latex'],\n        'application/x-lua-bytecode': ['luac'],\n        'application/x-lzh-compressed': ['lzh', 'lha'],\n        'application/x-makeself': ['run'],\n        'application/x-mie': ['mie'],\n        'application/x-mobipocket-ebook': ['*prc', 'mobi'],\n        'application/x-ms-application': ['application'],\n        'application/x-ms-shortcut': ['lnk'],\n        'application/x-ms-wmd': ['wmd'],\n        'application/x-ms-wmz': ['wmz'],\n        'application/x-ms-xbap': ['xbap'],\n        'application/x-msaccess': ['mdb'],\n        'application/x-msbinder': ['obd'],\n        'application/x-mscardfile': ['crd'],\n        'application/x-msclip': ['clp'],\n        'application/x-msdos-program': ['*exe'],\n        'application/x-msdownload': ['*exe', '*dll', 'com', 'bat', '*msi'],\n        'application/x-msmediaview': ['mvb', 'm13', 'm14'],\n        'application/x-msmetafile': ['*wmf', '*wmz', '*emf', 'emz'],\n        'application/x-msmoney': ['mny'],\n        'application/x-mspublisher': ['pub'],\n        'application/x-msschedule': ['scd'],\n        'application/x-msterminal': ['trm'],\n        'application/x-mswrite': ['wri'],\n        'application/x-netcdf': ['nc', 'cdf'],\n        'application/x-ns-proxy-autoconfig': ['pac'],\n        'application/x-nzb': ['nzb'],\n        'application/x-perl': ['pl', 'pm'],\n        'application/x-pilot': ['*prc', '*pdb'],\n        'application/x-pkcs12': ['p12', 'pfx'],\n        'application/x-pkcs7-certificates': ['p7b', 'spc'],\n        'application/x-pkcs7-certreqresp': ['p7r'],\n        'application/x-rar-compressed': ['*rar'],\n        'application/x-redhat-package-manager': ['rpm'],\n        'application/x-research-info-systems': ['ris'],\n        'application/x-sea': ['sea'],\n        'application/x-sh': ['sh'],\n        'application/x-shar': ['shar'],\n        'application/x-shockwave-flash': ['swf'],\n        'application/x-silverlight-app': ['xap'],\n        'application/x-sql': ['*sql'],\n        'application/x-stuffit': ['sit'],\n        'application/x-stuffitx': ['sitx'],\n        'application/x-subrip': ['srt'],\n        'application/x-sv4cpio': ['sv4cpio'],\n        'application/x-sv4crc': ['sv4crc'],\n        'application/x-t3vm-image': ['t3'],\n        'application/x-tads': ['gam'],\n        'application/x-tar': ['tar'],\n        'application/x-tcl': ['tcl', 'tk'],\n        'application/x-tex': ['tex'],\n        'application/x-tex-tfm': ['tfm'],\n        'application/x-texinfo': ['texinfo', 'texi'],\n        'application/x-tgif': ['*obj'],\n        'application/x-ustar': ['ustar'],\n        'application/x-virtualbox-hdd': ['hdd'],\n        'application/x-virtualbox-ova': ['ova'],\n        'application/x-virtualbox-ovf': ['ovf'],\n        'application/x-virtualbox-vbox': ['vbox'],\n        'application/x-virtualbox-vbox-extpack': ['vbox-extpack'],\n        'application/x-virtualbox-vdi': ['vdi'],\n        'application/x-virtualbox-vhd': ['vhd'],\n        'application/x-virtualbox-vmdk': ['vmdk'],\n        'application/x-wais-source': ['src'],\n        'application/x-web-app-manifest+json': ['webapp'],\n        'application/x-x509-ca-cert': ['der', 'crt', 'pem'],\n        'application/x-xfig': ['fig'],\n        'application/x-xliff+xml': ['*xlf'],\n        'application/x-xpinstall': ['xpi'],\n        'application/x-xz': ['xz'],\n        'application/x-zmachine': ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'],\n        'audio/vnd.dece.audio': ['uva', 'uvva'],\n        'audio/vnd.digital-winds': ['eol'],\n        'audio/vnd.dra': ['dra'],\n        'audio/vnd.dts': ['dts'],\n        'audio/vnd.dts.hd': ['dtshd'],\n        'audio/vnd.lucent.voice': ['lvp'],\n        'audio/vnd.ms-playready.media.pya': ['pya'],\n        'audio/vnd.nuera.ecelp4800': ['ecelp4800'],\n        'audio/vnd.nuera.ecelp7470': ['ecelp7470'],\n        'audio/vnd.nuera.ecelp9600': ['ecelp9600'],\n        'audio/vnd.rip': ['rip'],\n        'audio/x-aac': ['*aac'],\n        'audio/x-aiff': ['aif', 'aiff', 'aifc'],\n        'audio/x-caf': ['caf'],\n        'audio/x-flac': ['flac'],\n        'audio/x-m4a': ['*m4a'],\n        'audio/x-matroska': ['mka'],\n        'audio/x-mpegurl': ['m3u'],\n        'audio/x-ms-wax': ['wax'],\n        'audio/x-ms-wma': ['wma'],\n        'audio/x-pn-realaudio': ['ram', 'ra'],\n        'audio/x-pn-realaudio-plugin': ['rmp'],\n        'audio/x-realaudio': ['*ra'],\n        'audio/x-wav': ['*wav'],\n        'chemical/x-cdx': ['cdx'],\n        'chemical/x-cif': ['cif'],\n        'chemical/x-cmdf': ['cmdf'],\n        'chemical/x-cml': ['cml'],\n        'chemical/x-csml': ['csml'],\n        'chemical/x-xyz': ['xyz'],\n        'image/prs.btif': ['btif', 'btf'],\n        'image/prs.pti': ['pti'],\n        'image/vnd.adobe.photoshop': ['psd'],\n        'image/vnd.airzip.accelerator.azv': ['azv'],\n        'image/vnd.dece.graphic': ['uvi', 'uvvi', 'uvg', 'uvvg'],\n        'image/vnd.djvu': ['djvu', 'djv'],\n        'image/vnd.dvb.subtitle': ['*sub'],\n        'image/vnd.dwg': ['dwg'],\n        'image/vnd.dxf': ['dxf'],\n        'image/vnd.fastbidsheet': ['fbs'],\n        'image/vnd.fpx': ['fpx'],\n        'image/vnd.fst': ['fst'],\n        'image/vnd.fujixerox.edmics-mmr': ['mmr'],\n        'image/vnd.fujixerox.edmics-rlc': ['rlc'],\n        'image/vnd.microsoft.icon': ['ico'],\n        'image/vnd.ms-dds': ['dds'],\n        'image/vnd.ms-modi': ['mdi'],\n        'image/vnd.ms-photo': ['wdp'],\n        'image/vnd.net-fpx': ['npx'],\n        'image/vnd.pco.b16': ['b16'],\n        'image/vnd.tencent.tap': ['tap'],\n        'image/vnd.valve.source.texture': ['vtf'],\n        'image/vnd.wap.wbmp': ['wbmp'],\n        'image/vnd.xiff': ['xif'],\n        'image/vnd.zbrush.pcx': ['pcx'],\n        'image/x-3ds': ['3ds'],\n        'image/x-cmu-raster': ['ras'],\n        'image/x-cmx': ['cmx'],\n        'image/x-freehand': ['fh', 'fhc', 'fh4', 'fh5', 'fh7'],\n        'image/x-icon': ['*ico'],\n        'image/x-jng': ['jng'],\n        'image/x-mrsid-image': ['sid'],\n        'image/x-ms-bmp': ['*bmp'],\n        'image/x-pcx': ['*pcx'],\n        'image/x-pict': ['pic', 'pct'],\n        'image/x-portable-anymap': ['pnm'],\n        'image/x-portable-bitmap': ['pbm'],\n        'image/x-portable-graymap': ['pgm'],\n        'image/x-portable-pixmap': ['ppm'],\n        'image/x-rgb': ['rgb'],\n        'image/x-tga': ['tga'],\n        'image/x-xbitmap': ['xbm'],\n        'image/x-xpixmap': ['xpm'],\n        'image/x-xwindowdump': ['xwd'],\n        'message/vnd.wfa.wsc': ['wsc'],\n        'model/vnd.bary': ['bary'],\n        'model/vnd.cld': ['cld'],\n        'model/vnd.collada+xml': ['dae'],\n        'model/vnd.dwf': ['dwf'],\n        'model/vnd.gdl': ['gdl'],\n        'model/vnd.gtw': ['gtw'],\n        'model/vnd.mts': ['*mts'],\n        'model/vnd.opengex': ['ogex'],\n        'model/vnd.parasolid.transmit.binary': ['x_b'],\n        'model/vnd.parasolid.transmit.text': ['x_t'],\n        'model/vnd.pytha.pyox': ['pyo', 'pyox'],\n        'model/vnd.sap.vds': ['vds'],\n        'model/vnd.usda': ['usda'],\n        'model/vnd.usdz+zip': ['usdz'],\n        'model/vnd.valve.source.compiled-map': ['bsp'],\n        'model/vnd.vtu': ['vtu'],\n        'text/prs.lines.tag': ['dsc'],\n        'text/vnd.curl': ['curl'],\n        'text/vnd.curl.dcurl': ['dcurl'],\n        'text/vnd.curl.mcurl': ['mcurl'],\n        'text/vnd.curl.scurl': ['scurl'],\n        'text/vnd.dvb.subtitle': ['sub'],\n        'text/vnd.familysearch.gedcom': ['ged'],\n        'text/vnd.fly': ['fly'],\n        'text/vnd.fmi.flexstor': ['flx'],\n        'text/vnd.graphviz': ['gv'],\n        'text/vnd.in3d.3dml': ['3dml'],\n        'text/vnd.in3d.spot': ['spot'],\n        'text/vnd.sun.j2me.app-descriptor': ['jad'],\n        'text/vnd.wap.wml': ['wml'],\n        'text/vnd.wap.wmlscript': ['wmls'],\n        'text/x-asm': ['s', 'asm'],\n        'text/x-c': ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'],\n        'text/x-component': ['htc'],\n        'text/x-fortran': ['f', 'for', 'f77', 'f90'],\n        'text/x-handlebars-template': ['hbs'],\n        'text/x-java-source': ['java'],\n        'text/x-lua': ['lua'],\n        'text/x-markdown': ['mkd'],\n        'text/x-nfo': ['nfo'],\n        'text/x-opml': ['opml'],\n        'text/x-org': ['*org'],\n        'text/x-pascal': ['p', 'pas'],\n        'text/x-processing': ['pde'],\n        'text/x-sass': ['sass'],\n        'text/x-scss': ['scss'],\n        'text/x-setext': ['etx'],\n        'text/x-sfv': ['sfv'],\n        'text/x-suse-ymp': ['ymp'],\n        'text/x-uuencode': ['uu'],\n        'text/x-vcalendar': ['vcs'],\n        'text/x-vcard': ['vcf'],\n        'video/vnd.dece.hd': ['uvh', 'uvvh'],\n        'video/vnd.dece.mobile': ['uvm', 'uvvm'],\n        'video/vnd.dece.pd': ['uvp', 'uvvp'],\n        'video/vnd.dece.sd': ['uvs', 'uvvs'],\n        'video/vnd.dece.video': ['uvv', 'uvvv'],\n        'video/vnd.dvb.file': ['dvb'],\n        'video/vnd.fvt': ['fvt'],\n        'video/vnd.mpegurl': ['mxu', 'm4u'],\n        'video/vnd.ms-playready.media.pyv': ['pyv'],\n        'video/vnd.uvvu.mp4': ['uvu', 'uvvu'],\n        'video/vnd.vivo': ['viv'],\n        'video/x-f4v': ['f4v'],\n        'video/x-fli': ['fli'],\n        'video/x-flv': ['flv'],\n        'video/x-m4v': ['m4v'],\n        'video/x-matroska': ['mkv', 'mk3d', 'mks'],\n        'video/x-mng': ['mng'],\n        'video/x-ms-asf': ['asf', 'asx'],\n        'video/x-ms-vob': ['vob'],\n        'video/x-ms-wm': ['wm'],\n        'video/x-ms-wmv': ['wmv'],\n        'video/x-ms-wmx': ['wmx'],\n        'video/x-ms-wvx': ['wvx'],\n        'video/x-msvideo': ['avi'],\n        'video/x-sgi-movie': ['movie'],\n        'video/x-smv': ['smv'],\n        'x-conference/x-cooltalk': ['ice'],\n    };\n    Object.freeze(types$1);\n\n    const types = {\n        'application/andrew-inset': ['ez'],\n        'application/appinstaller': ['appinstaller'],\n        'application/applixware': ['aw'],\n        'application/appx': ['appx'],\n        'application/appxbundle': ['appxbundle'],\n        'application/atom+xml': ['atom'],\n        'application/atomcat+xml': ['atomcat'],\n        'application/atomdeleted+xml': ['atomdeleted'],\n        'application/atomsvc+xml': ['atomsvc'],\n        'application/atsc-dwd+xml': ['dwd'],\n        'application/atsc-held+xml': ['held'],\n        'application/atsc-rsat+xml': ['rsat'],\n        'application/automationml-aml+xml': ['aml'],\n        'application/automationml-amlx+zip': ['amlx'],\n        'application/bdoc': ['bdoc'],\n        'application/calendar+xml': ['xcs'],\n        'application/ccxml+xml': ['ccxml'],\n        'application/cdfx+xml': ['cdfx'],\n        'application/cdmi-capability': ['cdmia'],\n        'application/cdmi-container': ['cdmic'],\n        'application/cdmi-domain': ['cdmid'],\n        'application/cdmi-object': ['cdmio'],\n        'application/cdmi-queue': ['cdmiq'],\n        'application/cpl+xml': ['cpl'],\n        'application/cu-seeme': ['cu'],\n        'application/cwl': ['cwl'],\n        'application/dash+xml': ['mpd'],\n        'application/dash-patch+xml': ['mpp'],\n        'application/davmount+xml': ['davmount'],\n        'application/docbook+xml': ['dbk'],\n        'application/dssc+der': ['dssc'],\n        'application/dssc+xml': ['xdssc'],\n        'application/ecmascript': ['ecma'],\n        'application/emma+xml': ['emma'],\n        'application/emotionml+xml': ['emotionml'],\n        'application/epub+zip': ['epub'],\n        'application/exi': ['exi'],\n        'application/express': ['exp'],\n        'application/fdf': ['fdf'],\n        'application/fdt+xml': ['fdt'],\n        'application/font-tdpfr': ['pfr'],\n        'application/geo+json': ['geojson'],\n        'application/gml+xml': ['gml'],\n        'application/gpx+xml': ['gpx'],\n        'application/gxf': ['gxf'],\n        'application/gzip': ['gz'],\n        'application/hjson': ['hjson'],\n        'application/hyperstudio': ['stk'],\n        'application/inkml+xml': ['ink', 'inkml'],\n        'application/ipfix': ['ipfix'],\n        'application/its+xml': ['its'],\n        'application/java-archive': ['jar', 'war', 'ear'],\n        'application/java-serialized-object': ['ser'],\n        'application/java-vm': ['class'],\n        'application/javascript': ['*js'],\n        'application/json': ['json', 'map'],\n        'application/json5': ['json5'],\n        'application/jsonml+json': ['jsonml'],\n        'application/ld+json': ['jsonld'],\n        'application/lgr+xml': ['lgr'],\n        'application/lost+xml': ['lostxml'],\n        'application/mac-binhex40': ['hqx'],\n        'application/mac-compactpro': ['cpt'],\n        'application/mads+xml': ['mads'],\n        'application/manifest+json': ['webmanifest'],\n        'application/marc': ['mrc'],\n        'application/marcxml+xml': ['mrcx'],\n        'application/mathematica': ['ma', 'nb', 'mb'],\n        'application/mathml+xml': ['mathml'],\n        'application/mbox': ['mbox'],\n        'application/media-policy-dataset+xml': ['mpf'],\n        'application/mediaservercontrol+xml': ['mscml'],\n        'application/metalink+xml': ['metalink'],\n        'application/metalink4+xml': ['meta4'],\n        'application/mets+xml': ['mets'],\n        'application/mmt-aei+xml': ['maei'],\n        'application/mmt-usd+xml': ['musd'],\n        'application/mods+xml': ['mods'],\n        'application/mp21': ['m21', 'mp21'],\n        'application/mp4': ['*mp4', '*mpg4', 'mp4s', 'm4p'],\n        'application/msix': ['msix'],\n        'application/msixbundle': ['msixbundle'],\n        'application/msword': ['doc', 'dot'],\n        'application/mxf': ['mxf'],\n        'application/n-quads': ['nq'],\n        'application/n-triples': ['nt'],\n        'application/node': ['cjs'],\n        'application/octet-stream': [\n            'bin',\n            'dms',\n            'lrf',\n            'mar',\n            'so',\n            'dist',\n            'distz',\n            'pkg',\n            'bpk',\n            'dump',\n            'elc',\n            'deploy',\n            'exe',\n            'dll',\n            'deb',\n            'dmg',\n            'iso',\n            'img',\n            'msi',\n            'msp',\n            'msm',\n            'buffer',\n        ],\n        'application/oda': ['oda'],\n        'application/oebps-package+xml': ['opf'],\n        'application/ogg': ['ogx'],\n        'application/omdoc+xml': ['omdoc'],\n        'application/onenote': ['onetoc', 'onetoc2', 'onetmp', 'onepkg'],\n        'application/oxps': ['oxps'],\n        'application/p2p-overlay+xml': ['relo'],\n        'application/patch-ops-error+xml': ['xer'],\n        'application/pdf': ['pdf'],\n        'application/pgp-encrypted': ['pgp'],\n        'application/pgp-keys': ['asc'],\n        'application/pgp-signature': ['sig', '*asc'],\n        'application/pics-rules': ['prf'],\n        'application/pkcs10': ['p10'],\n        'application/pkcs7-mime': ['p7m', 'p7c'],\n        'application/pkcs7-signature': ['p7s'],\n        'application/pkcs8': ['p8'],\n        'application/pkix-attr-cert': ['ac'],\n        'application/pkix-cert': ['cer'],\n        'application/pkix-crl': ['crl'],\n        'application/pkix-pkipath': ['pkipath'],\n        'application/pkixcmp': ['pki'],\n        'application/pls+xml': ['pls'],\n        'application/postscript': ['ai', 'eps', 'ps'],\n        'application/provenance+xml': ['provx'],\n        'application/pskc+xml': ['pskcxml'],\n        'application/raml+yaml': ['raml'],\n        'application/rdf+xml': ['rdf', 'owl'],\n        'application/reginfo+xml': ['rif'],\n        'application/relax-ng-compact-syntax': ['rnc'],\n        'application/resource-lists+xml': ['rl'],\n        'application/resource-lists-diff+xml': ['rld'],\n        'application/rls-services+xml': ['rs'],\n        'application/route-apd+xml': ['rapd'],\n        'application/route-s-tsid+xml': ['sls'],\n        'application/route-usd+xml': ['rusd'],\n        'application/rpki-ghostbusters': ['gbr'],\n        'application/rpki-manifest': ['mft'],\n        'application/rpki-roa': ['roa'],\n        'application/rsd+xml': ['rsd'],\n        'application/rss+xml': ['rss'],\n        'application/rtf': ['rtf'],\n        'application/sbml+xml': ['sbml'],\n        'application/scvp-cv-request': ['scq'],\n        'application/scvp-cv-response': ['scs'],\n        'application/scvp-vp-request': ['spq'],\n        'application/scvp-vp-response': ['spp'],\n        'application/sdp': ['sdp'],\n        'application/senml+xml': ['senmlx'],\n        'application/sensml+xml': ['sensmlx'],\n        'application/set-payment-initiation': ['setpay'],\n        'application/set-registration-initiation': ['setreg'],\n        'application/shf+xml': ['shf'],\n        'application/sieve': ['siv', 'sieve'],\n        'application/smil+xml': ['smi', 'smil'],\n        'application/sparql-query': ['rq'],\n        'application/sparql-results+xml': ['srx'],\n        'application/sql': ['sql'],\n        'application/srgs': ['gram'],\n        'application/srgs+xml': ['grxml'],\n        'application/sru+xml': ['sru'],\n        'application/ssdl+xml': ['ssdl'],\n        'application/ssml+xml': ['ssml'],\n        'application/swid+xml': ['swidtag'],\n        'application/tei+xml': ['tei', 'teicorpus'],\n        'application/thraud+xml': ['tfi'],\n        'application/timestamped-data': ['tsd'],\n        'application/toml': ['toml'],\n        'application/trig': ['trig'],\n        'application/ttml+xml': ['ttml'],\n        'application/ubjson': ['ubj'],\n        'application/urc-ressheet+xml': ['rsheet'],\n        'application/urc-targetdesc+xml': ['td'],\n        'application/voicexml+xml': ['vxml'],\n        'application/wasm': ['wasm'],\n        'application/watcherinfo+xml': ['wif'],\n        'application/widget': ['wgt'],\n        'application/winhlp': ['hlp'],\n        'application/wsdl+xml': ['wsdl'],\n        'application/wspolicy+xml': ['wspolicy'],\n        'application/xaml+xml': ['xaml'],\n        'application/xcap-att+xml': ['xav'],\n        'application/xcap-caps+xml': ['xca'],\n        'application/xcap-diff+xml': ['xdf'],\n        'application/xcap-el+xml': ['xel'],\n        'application/xcap-ns+xml': ['xns'],\n        'application/xenc+xml': ['xenc'],\n        'application/xfdf': ['xfdf'],\n        'application/xhtml+xml': ['xhtml', 'xht'],\n        'application/xliff+xml': ['xlf'],\n        'application/xml': ['xml', 'xsl', 'xsd', 'rng'],\n        'application/xml-dtd': ['dtd'],\n        'application/xop+xml': ['xop'],\n        'application/xproc+xml': ['xpl'],\n        'application/xslt+xml': ['*xsl', 'xslt'],\n        'application/xspf+xml': ['xspf'],\n        'application/xv+xml': ['mxml', 'xhvml', 'xvml', 'xvm'],\n        'application/yang': ['yang'],\n        'application/yin+xml': ['yin'],\n        'application/zip': ['zip'],\n        'audio/3gpp': ['*3gpp'],\n        'audio/aac': ['adts', 'aac'],\n        'audio/adpcm': ['adp'],\n        'audio/amr': ['amr'],\n        'audio/basic': ['au', 'snd'],\n        'audio/midi': ['mid', 'midi', 'kar', 'rmi'],\n        'audio/mobile-xmf': ['mxmf'],\n        'audio/mp3': ['*mp3'],\n        'audio/mp4': ['m4a', 'mp4a'],\n        'audio/mpeg': ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],\n        'audio/ogg': ['oga', 'ogg', 'spx', 'opus'],\n        'audio/s3m': ['s3m'],\n        'audio/silk': ['sil'],\n        'audio/wav': ['wav'],\n        'audio/wave': ['*wav'],\n        'audio/webm': ['weba'],\n        'audio/xm': ['xm'],\n        'font/collection': ['ttc'],\n        'font/otf': ['otf'],\n        'font/ttf': ['ttf'],\n        'font/woff': ['woff'],\n        'font/woff2': ['woff2'],\n        'image/aces': ['exr'],\n        'image/apng': ['apng'],\n        'image/avci': ['avci'],\n        'image/avcs': ['avcs'],\n        'image/avif': ['avif'],\n        'image/bmp': ['bmp', 'dib'],\n        'image/cgm': ['cgm'],\n        'image/dicom-rle': ['drle'],\n        'image/dpx': ['dpx'],\n        'image/emf': ['emf'],\n        'image/fits': ['fits'],\n        'image/g3fax': ['g3'],\n        'image/gif': ['gif'],\n        'image/heic': ['heic'],\n        'image/heic-sequence': ['heics'],\n        'image/heif': ['heif'],\n        'image/heif-sequence': ['heifs'],\n        'image/hej2k': ['hej2'],\n        'image/hsj2': ['hsj2'],\n        'image/ief': ['ief'],\n        'image/jls': ['jls'],\n        'image/jp2': ['jp2', 'jpg2'],\n        'image/jpeg': ['jpeg', 'jpg', 'jpe'],\n        'image/jph': ['jph'],\n        'image/jphc': ['jhc'],\n        'image/jpm': ['jpm', 'jpgm'],\n        'image/jpx': ['jpx', 'jpf'],\n        'image/jxl': ['jxl'],\n        'image/jxr': ['jxr'],\n        'image/jxra': ['jxra'],\n        'image/jxrs': ['jxrs'],\n        'image/jxs': ['jxs'],\n        'image/jxsc': ['jxsc'],\n        'image/jxsi': ['jxsi'],\n        'image/jxss': ['jxss'],\n        'image/ktx': ['ktx'],\n        'image/ktx2': ['ktx2'],\n        'image/png': ['png'],\n        'image/sgi': ['sgi'],\n        'image/svg+xml': ['svg', 'svgz'],\n        'image/t38': ['t38'],\n        'image/tiff': ['tif', 'tiff'],\n        'image/tiff-fx': ['tfx'],\n        'image/webp': ['webp'],\n        'image/wmf': ['wmf'],\n        'message/disposition-notification': ['disposition-notification'],\n        'message/global': ['u8msg'],\n        'message/global-delivery-status': ['u8dsn'],\n        'message/global-disposition-notification': ['u8mdn'],\n        'message/global-headers': ['u8hdr'],\n        'message/rfc822': ['eml', 'mime'],\n        'model/3mf': ['3mf'],\n        'model/gltf+json': ['gltf'],\n        'model/gltf-binary': ['glb'],\n        'model/iges': ['igs', 'iges'],\n        'model/jt': ['jt'],\n        'model/mesh': ['msh', 'mesh', 'silo'],\n        'model/mtl': ['mtl'],\n        'model/obj': ['obj'],\n        'model/prc': ['prc'],\n        'model/step+xml': ['stpx'],\n        'model/step+zip': ['stpz'],\n        'model/step-xml+zip': ['stpxz'],\n        'model/stl': ['stl'],\n        'model/u3d': ['u3d'],\n        'model/vrml': ['wrl', 'vrml'],\n        'model/x3d+binary': ['*x3db', 'x3dbz'],\n        'model/x3d+fastinfoset': ['x3db'],\n        'model/x3d+vrml': ['*x3dv', 'x3dvz'],\n        'model/x3d+xml': ['x3d', 'x3dz'],\n        'model/x3d-vrml': ['x3dv'],\n        'text/cache-manifest': ['appcache', 'manifest'],\n        'text/calendar': ['ics', 'ifb'],\n        'text/coffeescript': ['coffee', 'litcoffee'],\n        'text/css': ['css'],\n        'text/csv': ['csv'],\n        'text/html': ['html', 'htm', 'shtml'],\n        'text/jade': ['jade'],\n        'text/javascript': ['js', 'mjs'],\n        'text/jsx': ['jsx'],\n        'text/less': ['less'],\n        'text/markdown': ['md', 'markdown'],\n        'text/mathml': ['mml'],\n        'text/mdx': ['mdx'],\n        'text/n3': ['n3'],\n        'text/plain': ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini'],\n        'text/richtext': ['rtx'],\n        'text/rtf': ['*rtf'],\n        'text/sgml': ['sgml', 'sgm'],\n        'text/shex': ['shex'],\n        'text/slim': ['slim', 'slm'],\n        'text/spdx': ['spdx'],\n        'text/stylus': ['stylus', 'styl'],\n        'text/tab-separated-values': ['tsv'],\n        'text/troff': ['t', 'tr', 'roff', 'man', 'me', 'ms'],\n        'text/turtle': ['ttl'],\n        'text/uri-list': ['uri', 'uris', 'urls'],\n        'text/vcard': ['vcard'],\n        'text/vtt': ['vtt'],\n        'text/wgsl': ['wgsl'],\n        'text/xml': ['*xml'],\n        'text/yaml': ['yaml', 'yml'],\n        'video/3gpp': ['3gp', '3gpp'],\n        'video/3gpp2': ['3g2'],\n        'video/h261': ['h261'],\n        'video/h263': ['h263'],\n        'video/h264': ['h264'],\n        'video/iso.segment': ['m4s'],\n        'video/jpeg': ['jpgv'],\n        'video/jpm': ['*jpm', '*jpgm'],\n        'video/mj2': ['mj2', 'mjp2'],\n        'video/mp2t': ['ts', 'm2t', 'm2ts', 'mts'],\n        'video/mp4': ['mp4', 'mp4v', 'mpg4'],\n        'video/mpeg': ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'],\n        'video/ogg': ['ogv'],\n        'video/quicktime': ['qt', 'mov'],\n        'video/webm': ['webm'],\n    };\n    Object.freeze(types);\n\n    var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\n        if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n        if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n        return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n    };\n    var _Mime_extensionToType, _Mime_typeToExtension, _Mime_typeToExtensions;\n    class Mime {\n        constructor(...args) {\n            _Mime_extensionToType.set(this, new Map());\n            _Mime_typeToExtension.set(this, new Map());\n            _Mime_typeToExtensions.set(this, new Map());\n            for (const arg of args) {\n                this.define(arg);\n            }\n        }\n        define(typeMap, force = false) {\n            for (let [type, extensions] of Object.entries(typeMap)) {\n                type = type.toLowerCase();\n                extensions = extensions.map((ext) => ext.toLowerCase());\n                if (!__classPrivateFieldGet(this, _Mime_typeToExtensions, \"f\").has(type)) {\n                    __classPrivateFieldGet(this, _Mime_typeToExtensions, \"f\").set(type, new Set());\n                }\n                const allExtensions = __classPrivateFieldGet(this, _Mime_typeToExtensions, \"f\").get(type);\n                let first = true;\n                for (let extension of extensions) {\n                    const starred = extension.startsWith('*');\n                    extension = starred ? extension.slice(1) : extension;\n                    allExtensions?.add(extension);\n                    if (first) {\n                        __classPrivateFieldGet(this, _Mime_typeToExtension, \"f\").set(type, extension);\n                    }\n                    first = false;\n                    if (starred)\n                        continue;\n                    const currentType = __classPrivateFieldGet(this, _Mime_extensionToType, \"f\").get(extension);\n                    if (currentType && currentType != type && !force) {\n                        throw new Error(`\"${type} -> ${extension}\" conflicts with \"${currentType} -> ${extension}\". Pass \\`force=true\\` to override this definition.`);\n                    }\n                    __classPrivateFieldGet(this, _Mime_extensionToType, \"f\").set(extension, type);\n                }\n            }\n            return this;\n        }\n        getType(path) {\n            if (typeof path !== 'string')\n                return null;\n            const last = path.replace(/^.*[/\\\\]/, '').toLowerCase();\n            const ext = last.replace(/^.*\\./, '').toLowerCase();\n            const hasPath = last.length < path.length;\n            const hasDot = ext.length < last.length - 1;\n            if (!hasDot && hasPath)\n                return null;\n            return __classPrivateFieldGet(this, _Mime_extensionToType, \"f\").get(ext) ?? null;\n        }\n        getExtension(type) {\n            if (typeof type !== 'string')\n                return null;\n            type = type?.split?.(';')[0];\n            return ((type && __classPrivateFieldGet(this, _Mime_typeToExtension, \"f\").get(type.trim().toLowerCase())) ?? null);\n        }\n        getAllExtensions(type) {\n            if (typeof type !== 'string')\n                return null;\n            return __classPrivateFieldGet(this, _Mime_typeToExtensions, \"f\").get(type.toLowerCase()) ?? null;\n        }\n        _freeze() {\n            this.define = () => {\n                throw new Error('define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances');\n            };\n            Object.freeze(this);\n            for (const extensions of __classPrivateFieldGet(this, _Mime_typeToExtensions, \"f\").values()) {\n                Object.freeze(extensions);\n            }\n            return this;\n        }\n        _getTestState() {\n            return {\n                types: __classPrivateFieldGet(this, _Mime_extensionToType, \"f\"),\n                extensions: __classPrivateFieldGet(this, _Mime_typeToExtension, \"f\"),\n            };\n        }\n    }\n    _Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();\n\n    var index = new Mime(types, types$1)._freeze();\n\n    exports.Mime = Mime;\n    exports.default = index;\n\n    Object.defineProperty(exports, '__esModule', { value: true });\n\n    return exports;\n\n})({});"
  },
  {
    "path": "public/assets/libs/workbox/workbox-background-sync.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.backgroundSync = (function (exports, WorkboxError_mjs, logger_mjs, assert_mjs, getFriendlyURL_mjs, DBWrapper_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:background-sync:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const DB_VERSION = 3;\n  const DB_NAME = 'workbox-background-sync';\n  const OBJECT_STORE_NAME = 'requests';\n  const INDEXED_PROP = 'queueName';\n  /**\n   * A class to manage storing requests from a Queue in IndexedbDB,\n   * indexed by their queue name for easier access.\n   *\n   * @private\n   */\n\n  class QueueStore {\n    /**\n     * Associates this instance with a Queue instance, so entries added can be\n     * identified by their queue name.\n     *\n     * @param {string} queueName\n     * @private\n     */\n    constructor(queueName) {\n      this._queueName = queueName;\n      this._db = new DBWrapper_mjs.DBWrapper(DB_NAME, DB_VERSION, {\n        onupgradeneeded: evt => this._upgradeDb(evt)\n      });\n    }\n    /**\n     * Append an entry last in the queue.\n     *\n     * @param {Object} entry\n     * @param {Object} entry.requestData\n     * @param {number} [entry.timestamp]\n     * @param {Object} [entry.metadata]\n     */\n\n\n    async pushEntry(entry) {\n      {\n        assert_mjs.assert.isType(entry, 'object', {\n          moduleName: 'workbox-background-sync',\n          className: 'QueueStore',\n          funcName: 'pushEntry',\n          paramName: 'entry'\n        });\n        assert_mjs.assert.isType(entry.requestData, 'object', {\n          moduleName: 'workbox-background-sync',\n          className: 'QueueStore',\n          funcName: 'pushEntry',\n          paramName: 'entry.requestData'\n        });\n      } // Don't specify an ID since one is automatically generated.\n\n\n      delete entry.id;\n      entry.queueName = this._queueName;\n      await this._db.add(OBJECT_STORE_NAME, entry);\n    }\n    /**\n     * Preppend an entry first in the queue.\n     *\n     * @param {Object} entry\n     * @param {Object} entry.requestData\n     * @param {number} [entry.timestamp]\n     * @param {Object} [entry.metadata]\n     */\n\n\n    async unshiftEntry(entry) {\n      {\n        assert_mjs.assert.isType(entry, 'object', {\n          moduleName: 'workbox-background-sync',\n          className: 'QueueStore',\n          funcName: 'unshiftEntry',\n          paramName: 'entry'\n        });\n        assert_mjs.assert.isType(entry.requestData, 'object', {\n          moduleName: 'workbox-background-sync',\n          className: 'QueueStore',\n          funcName: 'unshiftEntry',\n          paramName: 'entry.requestData'\n        });\n      }\n\n      const [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {\n        count: 1\n      });\n\n      if (firstEntry) {\n        // Pick an ID one less than the lowest ID in the object store.\n        entry.id = firstEntry.id - 1;\n      } else {\n        delete entry.id;\n      }\n\n      entry.queueName = this._queueName;\n      await this._db.add(OBJECT_STORE_NAME, entry);\n    }\n    /**\n     * Removes and returns the last entry in the queue matching the `queueName`.\n     *\n     * @return {Promise<Object>}\n     */\n\n\n    async popEntry() {\n      return this._removeEntry({\n        direction: 'prev'\n      });\n    }\n    /**\n     * Removes and returns the first entry in the queue matching the `queueName`.\n     *\n     * @return {Promise<Object>}\n     */\n\n\n    async shiftEntry() {\n      return this._removeEntry({\n        direction: 'next'\n      });\n    }\n    /**\n     * Removes and returns the first or last entry in the queue (based on the\n     * `direction` argument) matching the `queueName`.\n     *\n     * @return {Promise<Object>}\n     */\n\n\n    async _removeEntry({\n      direction\n    }) {\n      const [entry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {\n        direction,\n        index: INDEXED_PROP,\n        query: IDBKeyRange.only(this._queueName),\n        count: 1\n      });\n\n      if (entry) {\n        await this._db.delete(OBJECT_STORE_NAME, entry.id); // Dont' expose the ID or queueName;\n\n        delete entry.id;\n        delete entry.queueName;\n        return entry;\n      }\n    }\n    /**\n     * Upgrades the database given an `upgradeneeded` event.\n     *\n     * @param {Event} event\n     */\n\n\n    _upgradeDb(event) {\n      const db = event.target.result;\n\n      if (event.oldVersion > 0 && event.oldVersion < DB_VERSION) {\n        db.deleteObjectStore(OBJECT_STORE_NAME);\n      }\n\n      const objStore = db.createObjectStore(OBJECT_STORE_NAME, {\n        autoIncrement: true,\n        keyPath: 'id'\n      });\n      objStore.createIndex(INDEXED_PROP, INDEXED_PROP, {\n        unique: false\n      });\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const serializableProperties = ['method', 'referrer', 'referrerPolicy', 'mode', 'credentials', 'cache', 'redirect', 'integrity', 'keepalive'];\n  /**\n   * A class to make it easier to serialize and de-serialize requests so they\n   * can be stored in IndexedDB.\n   *\n   * @private\n   */\n\n  class StorableRequest {\n    /**\n     * Converts a Request object to a plain object that can be structured\n     * cloned or JSON-stringified.\n     *\n     * @param {Request} request\n     * @return {Promise<StorableRequest>}\n     *\n     * @private\n     */\n    static async fromRequest(request) {\n      const requestData = {\n        url: request.url,\n        headers: {}\n      }; // Set the body if present.\n\n      if (request.method !== 'GET') {\n        // Use ArrayBuffer to support non-text request bodies.\n        // NOTE: we can't use Blobs becuse Safari doesn't support storing\n        // Blobs in IndexedDB in some cases:\n        // https://github.com/dfahlander/Dexie.js/issues/618#issuecomment-398348457\n        requestData.body = await request.clone().arrayBuffer();\n      } // Convert the headers from an iterable to an object.\n\n\n      for (const [key, value] of request.headers.entries()) {\n        requestData.headers[key] = value;\n      } // Add all other serializable request properties\n\n\n      for (const prop of serializableProperties) {\n        if (request[prop] !== undefined) {\n          requestData[prop] = request[prop];\n        }\n      }\n\n      return new StorableRequest(requestData);\n    }\n    /**\n     * Accepts an object of request data that can be used to construct a\n     * `Request` but can also be stored in IndexedDB.\n     *\n     * @param {Object} requestData An object of request data that includes the\n     *     `url` plus any relevant properties of\n     *     [requestInit]{@link https://fetch.spec.whatwg.org/#requestinit}.\n     * @private\n     */\n\n\n    constructor(requestData) {\n      {\n        assert_mjs.assert.isType(requestData, 'object', {\n          moduleName: 'workbox-background-sync',\n          className: 'StorableRequest',\n          funcName: 'constructor',\n          paramName: 'requestData'\n        });\n        assert_mjs.assert.isType(requestData.url, 'string', {\n          moduleName: 'workbox-background-sync',\n          className: 'StorableRequest',\n          funcName: 'constructor',\n          paramName: 'requestData.url'\n        });\n      }\n\n      this._requestData = requestData;\n    }\n    /**\n     * Returns a deep clone of the instances `_requestData` object.\n     *\n     * @return {Object}\n     *\n     * @private\n     */\n\n\n    toObject() {\n      const requestData = Object.assign({}, this._requestData);\n      requestData.headers = Object.assign({}, this._requestData.headers);\n\n      if (requestData.body) {\n        requestData.body = requestData.body.slice(0);\n      }\n\n      return requestData;\n    }\n    /**\n     * Converts this instance to a Request.\n     *\n     * @return {Request}\n     *\n     * @private\n     */\n\n\n    toRequest() {\n      return new Request(this._requestData.url, this._requestData);\n    }\n    /**\n     * Creates and returns a deep clone of the instance.\n     *\n     * @return {StorableRequest}\n     *\n     * @private\n     */\n\n\n    clone() {\n      return new StorableRequest(this.toObject());\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const TAG_PREFIX = 'workbox-background-sync';\n  const MAX_RETENTION_TIME = 60 * 24 * 7; // 7 days in minutes\n\n  const queueNames = new Set();\n  /**\n   * A class to manage storing failed requests in IndexedDB and retrying them\n   * later. All parts of the storing and replaying process are observable via\n   * callbacks.\n   *\n   * @memberof workbox.backgroundSync\n   */\n\n  class Queue {\n    /**\n     * Creates an instance of Queue with the given options\n     *\n     * @param {string} name The unique name for this queue. This name must be\n     *     unique as it's used to register sync events and store requests\n     *     in IndexedDB specific to this instance. An error will be thrown if\n     *     a duplicate name is detected.\n     * @param {Object} [options]\n     * @param {Function} [options.onSync] A function that gets invoked whenever\n     *     the 'sync' event fires. The function is invoked with an object\n     *     containing the `queue` property (referencing this instance), and you\n     *     can use the callback to customize the replay behavior of the queue.\n     *     When not set the `replayRequests()` method is called.\n     *     Note: if the replay fails after a sync event, make sure you throw an\n     *     error, so the browser knows to retry the sync event later.\n     * @param {number} [options.maxRetentionTime=7 days] The amount of time (in\n     *     minutes) a request may be retried. After this amount of time has\n     *     passed, the request will be deleted from the queue.\n     */\n    constructor(name, {\n      onSync,\n      maxRetentionTime\n    } = {}) {\n      // Ensure the store name is not already being used\n      if (queueNames.has(name)) {\n        throw new WorkboxError_mjs.WorkboxError('duplicate-queue-name', {\n          name\n        });\n      } else {\n        queueNames.add(name);\n      }\n\n      this._name = name;\n      this._onSync = onSync || this.replayRequests;\n      this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;\n      this._queueStore = new QueueStore(this._name);\n\n      this._addSyncListener();\n    }\n    /**\n     * @return {string}\n     */\n\n\n    get name() {\n      return this._name;\n    }\n    /**\n     * Stores the passed request in IndexedDB (with its timestamp and any\n     * metadata) at the end of the queue.\n     *\n     * @param {Object} entry\n     * @param {Request} entry.request The request to store in the queue.\n     * @param {Object} [entry.metadata] Any metadata you want associated with the\n     *     stored request. When requests are replayed you'll have access to this\n     *     metadata object in case you need to modify the request beforehand.\n     * @param {number} [entry.timestamp] The timestamp (Epoch time in\n     *     milliseconds) when the request was first added to the queue. This is\n     *     used along with `maxRetentionTime` to remove outdated requests. In\n     *     general you don't need to set this value, as it's automatically set\n     *     for you (defaulting to `Date.now()`), but you can update it if you\n     *     don't want particular requests to expire.\n     */\n\n\n    async pushRequest(entry) {\n      {\n        assert_mjs.assert.isType(entry, 'object', {\n          moduleName: 'workbox-background-sync',\n          className: 'Queue',\n          funcName: 'pushRequest',\n          paramName: 'entry'\n        });\n        assert_mjs.assert.isInstance(entry.request, Request, {\n          moduleName: 'workbox-background-sync',\n          className: 'Queue',\n          funcName: 'pushRequest',\n          paramName: 'entry.request'\n        });\n      }\n\n      await this._addRequest(entry, 'push');\n    }\n    /**\n     * Stores the passed request in IndexedDB (with its timestamp and any\n     * metadata) at the beginning of the queue.\n     *\n     * @param {Object} entry\n     * @param {Request} entry.request The request to store in the queue.\n     * @param {Object} [entry.metadata] Any metadata you want associated with the\n     *     stored request. When requests are replayed you'll have access to this\n     *     metadata object in case you need to modify the request beforehand.\n     * @param {number} [entry.timestamp] The timestamp (Epoch time in\n     *     milliseconds) when the request was first added to the queue. This is\n     *     used along with `maxRetentionTime` to remove outdated requests. In\n     *     general you don't need to set this value, as it's automatically set\n     *     for you (defaulting to `Date.now()`), but you can update it if you\n     *     don't want particular requests to expire.\n     */\n\n\n    async unshiftRequest(entry) {\n      {\n        assert_mjs.assert.isType(entry, 'object', {\n          moduleName: 'workbox-background-sync',\n          className: 'Queue',\n          funcName: 'unshiftRequest',\n          paramName: 'entry'\n        });\n        assert_mjs.assert.isInstance(entry.request, Request, {\n          moduleName: 'workbox-background-sync',\n          className: 'Queue',\n          funcName: 'unshiftRequest',\n          paramName: 'entry.request'\n        });\n      }\n\n      await this._addRequest(entry, 'unshift');\n    }\n    /**\n     * Removes and returns the last request in the queue (along with its\n     * timestamp and any metadata). The returned object takes the form:\n     * `{request, timestamp, metadata}`.\n     *\n     * @return {Promise<Object>}\n     */\n\n\n    async popRequest() {\n      return this._removeRequest('pop');\n    }\n    /**\n     * Removes and returns the first request in the queue (along with its\n     * timestamp and any metadata). The returned object takes the form:\n     * `{request, timestamp, metadata}`.\n     *\n     * @return {Promise<Object>}\n     */\n\n\n    async shiftRequest() {\n      return this._removeRequest('shift');\n    }\n    /**\n     * Adds the entry to the QueueStore and registers for a sync event.\n     *\n     * @param {Object} entry\n     * @param {Request} entry.request\n     * @param {Object} [entry.metadata]\n     * @param {number} [entry.timestamp=Date.now()]\n     * @param {string} operation ('push' or 'unshift')\n     */\n\n\n    async _addRequest({\n      request,\n      metadata,\n      timestamp = Date.now()\n    }, operation) {\n      const storableRequest = await StorableRequest.fromRequest(request.clone());\n      const entry = {\n        requestData: storableRequest.toObject(),\n        timestamp\n      }; // Only include metadata if it's present.\n\n      if (metadata) {\n        entry.metadata = metadata;\n      }\n\n      await this._queueStore[`${operation}Entry`](entry);\n\n      {\n        logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);\n      } // Don't register for a sync if we're in the middle of a sync. Instead,\n      // we wait until the sync is complete and call register if\n      // `this._requestsAddedDuringSync` is true.\n\n\n      if (this._syncInProgress) {\n        this._requestsAddedDuringSync = true;\n      } else {\n        await this.registerSync();\n      }\n    }\n    /**\n     * Removes and returns the first or last (depending on `operation`) entry\n     * form the QueueStore that's not older than the `maxRetentionTime`.\n     *\n     * @param {string} operation ('pop' or 'shift')\n     * @return {Object|undefined}\n     */\n\n\n    async _removeRequest(operation) {\n      const now = Date.now();\n      const entry = await this._queueStore[`${operation}Entry`]();\n\n      if (entry) {\n        // Ignore requests older than maxRetentionTime. Call this function\n        // recursively until an unexpired request is found.\n        const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;\n\n        if (now - entry.timestamp > maxRetentionTimeInMs) {\n          return this._removeRequest(operation);\n        }\n\n        entry.request = new StorableRequest(entry.requestData).toRequest();\n        delete entry.requestData;\n        return entry;\n      }\n    }\n    /**\n     * Loops through each request in the queue and attempts to re-fetch it.\n     * If any request fails to re-fetch, it's put back in the same position in\n     * the queue (which registers a retry for the next sync event).\n     */\n\n\n    async replayRequests() {\n      let entry;\n\n      while (entry = await this.shiftRequest()) {\n        try {\n          await fetch(entry.request);\n\n          {\n            logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(entry.request.url)}'` + `has been replayed in queue '${this._name}'`);\n          }\n        } catch (error) {\n          await this.unshiftRequest(entry);\n\n          {\n            logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(entry.request.url)}'` + `failed to replay, putting it back in queue '${this._name}'`);\n          }\n\n          throw new WorkboxError_mjs.WorkboxError('queue-replay-failed', {\n            name: this._name\n          });\n        }\n      }\n\n      {\n        logger_mjs.logger.log(`All requests in queue '${this.name}' have successfully ` + `replayed; the queue is now empty!`);\n      }\n    }\n    /**\n     * Registers a sync event with a tag unique to this instance.\n     */\n\n\n    async registerSync() {\n      if ('sync' in registration) {\n        try {\n          await registration.sync.register(`${TAG_PREFIX}:${this._name}`);\n        } catch (err) {\n          // This means the registration failed for some reason, possibly due to\n          // the user disabling it.\n          {\n            logger_mjs.logger.warn(`Unable to register sync event for '${this._name}'.`, err);\n          }\n        }\n      }\n    }\n    /**\n     * In sync-supporting browsers, this adds a listener for the sync event.\n     * In non-sync-supporting browsers, this will retry the queue on service\n     * worker startup.\n     *\n     * @private\n     */\n\n\n    _addSyncListener() {\n      if ('sync' in registration) {\n        self.addEventListener('sync', event => {\n          if (event.tag === `${TAG_PREFIX}:${this._name}`) {\n            {\n              logger_mjs.logger.log(`Background sync for tag '${event.tag}'` + `has been received`);\n            }\n\n            const syncComplete = async () => {\n              this._syncInProgress = true;\n              let syncError;\n\n              try {\n                await this._onSync({\n                  queue: this\n                });\n              } catch (error) {\n                syncError = error; // Rethrow the error. Note: the logic in the finally clause\n                // will run before this gets rethrown.\n\n                throw syncError;\n              } finally {\n                // New items may have been added to the queue during the sync,\n                // so we need to register for a new sync if that's happened...\n                // Unless there was an error during the sync, in which\n                // case the browser will automatically retry later, as long\n                // as `event.lastChance` is not true.\n                if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {\n                  await this.registerSync();\n                }\n\n                this._syncInProgress = false;\n                this._requestsAddedDuringSync = false;\n              }\n            };\n\n            event.waitUntil(syncComplete());\n          }\n        });\n      } else {\n        {\n          logger_mjs.logger.log(`Background sync replaying without background sync event`);\n        } // If the browser doesn't support background sync, retry\n        // every time the service worker starts up as a fallback.\n\n\n        this._onSync({\n          queue: this\n        });\n      }\n    }\n    /**\n     * Returns the set of queue names. This is primarily used to reset the list\n     * of queue names in tests.\n     *\n     * @return {Set}\n     *\n     * @private\n     */\n\n\n    static get _queueNames() {\n      return queueNames;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * A class implementing the `fetchDidFail` lifecycle callback. This makes it\n   * easier to add failed requests to a background sync Queue.\n   *\n   * @memberof workbox.backgroundSync\n   */\n\n  class Plugin {\n    /**\n     * @param {...*} queueArgs Args to forward to the composed Queue instance.\n     *    See the [Queue]{@link workbox.backgroundSync.Queue} documentation for\n     *    parameter details.\n     */\n    constructor(...queueArgs) {\n      this._queue = new Queue(...queueArgs);\n      this.fetchDidFail = this.fetchDidFail.bind(this);\n    }\n    /**\n     * @param {Object} options\n     * @param {Request} options.request\n     * @private\n     */\n\n\n    async fetchDidFail({\n      request\n    }) {\n      await this._queue.pushRequest({\n        request\n      });\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.Queue = Queue;\n  exports.Plugin = Plugin;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-background-sync.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-background-sync.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.backgroundSync=function(t,e,s){\"use strict\";try{self[\"workbox:background-sync:4.1.1\"]&&_()}catch(t){}const i=3,n=\"workbox-background-sync\",a=\"requests\",r=\"queueName\";class c{constructor(t){this.t=t,this.s=new s.DBWrapper(n,i,{onupgradeneeded:t=>this.i(t)})}async pushEntry(t){delete t.id,t.queueName=this.t,await this.s.add(a,t)}async unshiftEntry(t){const[e]=await this.s.getAllMatching(a,{count:1});e?t.id=e.id-1:delete t.id,t.queueName=this.t,await this.s.add(a,t)}async popEntry(){return this.h({direction:\"prev\"})}async shiftEntry(){return this.h({direction:\"next\"})}async h({direction:t}){const[e]=await this.s.getAllMatching(a,{direction:t,index:r,query:IDBKeyRange.only(this.t),count:1});if(e)return await this.s.delete(a,e.id),delete e.id,delete e.queueName,e}i(t){const e=t.target.result;t.oldVersion>0&&t.oldVersion<i&&e.deleteObjectStore(a),e.createObjectStore(a,{autoIncrement:!0,keyPath:\"id\"}).createIndex(r,r,{unique:!1})}}const h=[\"method\",\"referrer\",\"referrerPolicy\",\"mode\",\"credentials\",\"cache\",\"redirect\",\"integrity\",\"keepalive\"];class o{static async fromRequest(t){const e={url:t.url,headers:{}};\"GET\"!==t.method&&(e.body=await t.clone().arrayBuffer());for(const[s,i]of t.headers.entries())e.headers[s]=i;for(const s of h)void 0!==t[s]&&(e[s]=t[s]);return new o(e)}constructor(t){this.o=t}toObject(){const t=Object.assign({},this.o);return t.headers=Object.assign({},this.o.headers),t.body&&(t.body=t.body.slice(0)),t}toRequest(){return new Request(this.o.url,this.o)}clone(){return new o(this.toObject())}}const u=\"workbox-background-sync\",y=10080,w=new Set;class d{constructor(t,{onSync:s,maxRetentionTime:i}={}){if(w.has(t))throw new e.WorkboxError(\"duplicate-queue-name\",{name:t});w.add(t),this.u=t,this.l=s||this.replayRequests,this.q=i||y,this.m=new c(this.u),this.p()}get name(){return this.u}async pushRequest(t){await this.g(t,\"push\")}async unshiftRequest(t){await this.g(t,\"unshift\")}async popRequest(){return this.R(\"pop\")}async shiftRequest(){return this.R(\"shift\")}async g({request:t,metadata:e,timestamp:s=Date.now()},i){const n={requestData:(await o.fromRequest(t.clone())).toObject(),timestamp:s};e&&(n.metadata=e),await this.m[`${i}Entry`](n),this.k?this._=!0:await this.registerSync()}async R(t){const e=Date.now(),s=await this.m[`${t}Entry`]();if(s){const i=60*this.q*1e3;return e-s.timestamp>i?this.R(t):(s.request=new o(s.requestData).toRequest(),delete s.requestData,s)}}async replayRequests(){let t;for(;t=await this.shiftRequest();)try{await fetch(t.request)}catch(s){throw await this.unshiftRequest(t),new e.WorkboxError(\"queue-replay-failed\",{name:this.u})}}async registerSync(){if(\"sync\"in registration)try{await registration.sync.register(`${u}:${this.u}`)}catch(t){}}p(){\"sync\"in registration?self.addEventListener(\"sync\",t=>{if(t.tag===`${u}:${this.u}`){const e=async()=>{let e;this.k=!0;try{await this.l({queue:this})}catch(t){throw e=t}finally{!this._||e&&!t.lastChance||await this.registerSync(),this.k=!1,this._=!1}};t.waitUntil(e())}}):this.l({queue:this})}static get D(){return w}}return t.Queue=d,t.Plugin=class{constructor(...t){this.$=new d(...t),this.fetchDidFail=this.fetchDidFail.bind(this)}async fetchDidFail({request:t}){await this.$.pushRequest({request:t})}},t}({},workbox.core._private,workbox.core._private);\n//# sourceMappingURL=workbox-background-sync.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-broadcast-update.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.broadcastUpdate = (function (exports, assert_mjs, getFriendlyURL_mjs, logger_mjs, Deferred_mjs, WorkboxError_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:broadcast-update:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Given two `Response's`, compares several header values to see if they are\n   * the same or not.\n   *\n   * @param {Response} firstResponse\n   * @param {Response} secondResponse\n   * @param {Array<string>} headersToCheck\n   * @return {boolean}\n   *\n   * @memberof workbox.broadcastUpdate\n   * @private\n   */\n\n  const responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {\n    {\n      if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {\n        throw new WorkboxError_mjs.WorkboxError('invalid-responses-are-same-args');\n      }\n    }\n\n    const atLeastOneHeaderAvailable = headersToCheck.some(header => {\n      return firstResponse.headers.has(header) && secondResponse.headers.has(header);\n    });\n\n    if (!atLeastOneHeaderAvailable) {\n      {\n        logger_mjs.logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);\n        logger_mjs.logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);\n      } // Just return true, indicating the that responses are the same, since we\n      // can't determine otherwise.\n\n\n      return true;\n    }\n\n    return headersToCheck.every(header => {\n      const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);\n      const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);\n      return headerStateComparison && headerValueComparison;\n    });\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';\n  const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';\n  const DEFAULT_BROADCAST_CHANNEL_NAME = 'workbox';\n  const DEFAULT_DEFER_NOTIFICATION_TIMEOUT = 10000;\n  const DEFAULT_HEADERS_TO_CHECK = ['content-length', 'etag', 'last-modified'];\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * You would not normally call this method directly; it's called automatically\n   * by an instance of the {@link BroadcastCacheUpdate} class. It's exposed here\n   * for the benefit of developers who would rather not use the full\n   * `BroadcastCacheUpdate` implementation.\n   *\n   * Calling this will dispatch a message on the provided\n   * {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel}\n   * to notify interested subscribers about a change to a cached resource.\n   *\n   * The message that's posted has a formation inspired by the\n   * [Flux standard action](https://github.com/acdlite/flux-standard-action#introduction)\n   * format like so:\n   *\n   * ```\n   * {\n   *   type: 'CACHE_UPDATED',\n   *   meta: 'workbox-broadcast-update',\n   *   payload: {\n   *     cacheName: 'the-cache-name',\n   *     updatedURL: 'https://example.com/'\n   *   }\n   * }\n   * ```\n   *\n   * (Usage of [Flux](https://facebook.github.io/flux/) itself is not at\n   * all required.)\n   *\n   * @param {Object} options\n   * @param {string} options.cacheName The name of the cache in which the updated\n   *     `Response` was stored.\n   * @param {string} options.url The URL associated with the updated `Response`.\n   * @param {BroadcastChannel} [options.channel] The `BroadcastChannel` to use.\n   *     If no channel is set or the browser doesn't support the BroadcastChannel\n   *     api, then an attempt will be made to `postMessage` each window client.\n   *\n   * @memberof workbox.broadcastUpdate\n   */\n\n  const broadcastUpdate = async ({\n    channel,\n    cacheName,\n    url\n  }) => {\n    {\n      assert_mjs.assert.isType(cacheName, 'string', {\n        moduleName: 'workbox-broadcast-update',\n        className: '~',\n        funcName: 'broadcastUpdate',\n        paramName: 'cacheName'\n      });\n      assert_mjs.assert.isType(url, 'string', {\n        moduleName: 'workbox-broadcast-update',\n        className: '~',\n        funcName: 'broadcastUpdate',\n        paramName: 'url'\n      });\n    }\n\n    const data = {\n      type: CACHE_UPDATED_MESSAGE_TYPE,\n      meta: CACHE_UPDATED_MESSAGE_META,\n      payload: {\n        cacheName: cacheName,\n        updatedURL: url\n      }\n    };\n\n    if (channel) {\n      channel.postMessage(data);\n    } else {\n      const windows = await clients.matchAll({\n        type: 'window'\n      });\n\n      for (const win of windows) {\n        win.postMessage(data);\n      }\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Uses the [Broadcast Channel API]{@link https://developers.google.com/web/updates/2016/09/broadcastchannel}\n   * to notify interested parties when a cached response has been updated.\n   * In browsers that do not support the Broadcast Channel API, the instance\n   * falls back to sending the update via `postMessage()` to all window clients.\n   *\n   * For efficiency's sake, the underlying response bodies are not compared;\n   * only specific response headers are checked.\n   *\n   * @memberof workbox.broadcastUpdate\n   */\n\n  class BroadcastCacheUpdate {\n    /**\n     * Construct a BroadcastCacheUpdate instance with a specific `channelName` to\n     * broadcast messages on\n     *\n     * @param {Object} options\n     * @param {Array<string>}\n     *     [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n     *     A list of headers that will be used to determine whether the responses\n     *     differ.\n     * @param {string} [options.channelName='workbox'] The name that will be used\n     *.    when creating the `BroadcastChannel`, which defaults to 'workbox' (the\n     *     channel name used by the `workbox-window` package).\n     * @param {string} [options.deferNoticationTimeout=10000] The amount of time\n     *     to wait for a ready message from the window on navigation requests\n     *     before sending the update.\n     */\n    constructor({\n      headersToCheck,\n      channelName,\n      deferNoticationTimeout\n    } = {}) {\n      this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;\n      this._channelName = channelName || DEFAULT_BROADCAST_CHANNEL_NAME;\n      this._deferNoticationTimeout = deferNoticationTimeout || DEFAULT_DEFER_NOTIFICATION_TIMEOUT;\n\n      {\n        assert_mjs.assert.isType(this._channelName, 'string', {\n          moduleName: 'workbox-broadcast-update',\n          className: 'BroadcastCacheUpdate',\n          funcName: 'constructor',\n          paramName: 'channelName'\n        });\n        assert_mjs.assert.isArray(this._headersToCheck, {\n          moduleName: 'workbox-broadcast-update',\n          className: 'BroadcastCacheUpdate',\n          funcName: 'constructor',\n          paramName: 'headersToCheck'\n        });\n      }\n\n      this._initWindowReadyDeferreds();\n    }\n    /**\n     * Compare two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n     * and send a message via the\n     * {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel API}\n     * if they differ.\n     *\n     * Neither of the Responses can be {@link http://stackoverflow.com/questions/39109789|opaque}.\n     *\n     * @param {Object} options\n     * @param {Response} options.oldResponse Cached response to compare.\n     * @param {Response} options.newResponse Possibly updated response to compare.\n     * @param {string} options.url The URL of the request.\n     * @param {string} options.cacheName Name of the cache the responses belong\n     *     to. This is included in the broadcast message.\n     * @param {Event} [options.event] event An optional event that triggered\n     *     this possible cache update.\n     * @return {Promise} Resolves once the update is sent.\n     */\n\n\n    notifyIfUpdated({\n      oldResponse,\n      newResponse,\n      url,\n      cacheName,\n      event\n    }) {\n      if (!responsesAreSame(oldResponse, newResponse, this._headersToCheck)) {\n        {\n          logger_mjs.logger.log(`Newer response found (and cached) for:`, url);\n        }\n\n        const sendUpdate = async () => {\n          // In the case of a navigation request, the requesting page will likely\n          // not have loaded its JavaScript in time to recevied the update\n          // notification, so we defer it until ready (or we timeout waiting).\n          if (event && event.request && event.request.mode === 'navigate') {\n            {\n              logger_mjs.logger.debug(`Original request was a navigation request, ` + `waiting for a ready message from the window`, event.request);\n            }\n\n            await this._windowReadyOrTimeout(event);\n          }\n\n          await broadcastUpdate({\n            channel: this._getChannel(),\n            cacheName,\n            url\n          });\n        }; // Send the update and ensure the SW stays alive until it's sent.\n\n\n        const done = sendUpdate();\n\n        if (event) {\n          try {\n            event.waitUntil(done);\n          } catch (error) {\n            {\n              logger_mjs.logger.warn(`Unable to ensure service worker stays alive ` + `when broadcasting cache update for ` + `${getFriendlyURL_mjs.getFriendlyURL(event.request.url)}'.`);\n            }\n          }\n        }\n\n        return done;\n      }\n    }\n    /**\n     * @return {BroadcastChannel|undefined} The BroadcastChannel instance used for\n     * broadcasting updates, or undefined if the browser doesn't support the\n     * Broadcast Channel API.\n     *\n     * @private\n     */\n\n\n    _getChannel() {\n      if ('BroadcastChannel' in self && !this._channel) {\n        this._channel = new BroadcastChannel(this._channelName);\n      }\n\n      return this._channel;\n    }\n    /**\n     * Waits for a message from the window indicating that it's capable of\n     * receiving broadcasts. By default, this will only wait for the amount of\n     * time specified via the `deferNoticationTimeout` option.\n     *\n     * @param {Event} event The navigation fetch event.\n     * @return {Promise}\n     * @private\n     */\n\n\n    _windowReadyOrTimeout(event) {\n      if (!this._navigationEventsDeferreds.has(event)) {\n        const deferred = new Deferred_mjs.Deferred(); // Set the deferred on the `_navigationEventsDeferreds` map so it will\n        // be resolved when the next ready message event comes.\n\n        this._navigationEventsDeferreds.set(event, deferred); // But don't wait too long for the message since it may never come.\n\n\n        const timeout = setTimeout(() => {\n          {\n            logger_mjs.logger.debug(`Timed out after ${this._deferNoticationTimeout}` + `ms waiting for message from window`);\n          }\n\n          deferred.resolve();\n        }, this._deferNoticationTimeout); // Ensure the timeout is cleared if the deferred promise is resolved.\n\n        deferred.promise.then(() => clearTimeout(timeout));\n      }\n\n      return this._navigationEventsDeferreds.get(event).promise;\n    }\n    /**\n     * Creates a mapping between navigation fetch events and deferreds, and adds\n     * a listener for message events from the window. When message events arrive,\n     * all deferreds in the mapping are resolved.\n     *\n     * Note: it would be easier if we could only resolve the deferred of\n     * navigation fetch event whose client ID matched the source ID of the\n     * message event, but currently client IDs are not exposed on navigation\n     * fetch events: https://www.chromestatus.com/feature/4846038800138240\n     */\n\n\n    _initWindowReadyDeferreds() {\n      // A mapping between navigation events and their deferreds.\n      this._navigationEventsDeferreds = new Map(); // The message listener needs to be added in the initial run of the\n      // service worker, but since we don't actually need to be listening for\n      // messages until the cache updates, we only invoke the callback if set.\n\n      self.addEventListener('message', event => {\n        if (event.data.type === 'WINDOW_READY' && event.data.meta === 'workbox-window' && this._navigationEventsDeferreds.size > 0) {\n          {\n            logger_mjs.logger.debug(`Received WINDOW_READY event: `, event);\n          } // Resolve any pending deferreds.\n\n\n          for (const deferred of this._navigationEventsDeferreds.values()) {\n            deferred.resolve();\n          }\n\n          this._navigationEventsDeferreds.clear();\n        }\n      });\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * This plugin will automatically broadcast a message whenever a cached response\n   * is updated.\n   *\n   * @memberof workbox.broadcastUpdate\n   */\n\n  class Plugin {\n    /**\n     * Construct a BroadcastCacheUpdate instance with the passed options and\n     * calls its `notifyIfUpdated()` method whenever the plugin's\n     * `cacheDidUpdate` callback is invoked.\n     *\n     * @param {Object} options\n     * @param {Array<string>}\n     *     [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n     *     A list of headers that will be used to determine whether the responses\n     *     differ.\n     * @param {string} [options.channelName='workbox'] The name that will be used\n     *.    when creating the `BroadcastChannel`, which defaults to 'workbox' (the\n     *     channel name used by the `workbox-window` package).\n     * @param {string} [options.deferNoticationTimeout=10000] The amount of time\n     *     to wait for a ready message from the window on navigation requests\n     *     before sending the update.\n     */\n    constructor(options) {\n      this._broadcastUpdate = new BroadcastCacheUpdate(options);\n    }\n    /**\n     * A \"lifecycle\" callback that will be triggered automatically by the\n     * `workbox-sw` and `workbox-runtime-caching` handlers when an entry is\n     * added to a cache.\n     *\n     * @private\n     * @param {Object} options The input object to this function.\n     * @param {string} options.cacheName Name of the cache being updated.\n     * @param {Response} [options.oldResponse] The previous cached value, if any.\n     * @param {Response} options.newResponse The new value in the cache.\n     * @param {Request} options.request The request that triggered the udpate.\n     * @param {Request} [options.event] The event that triggered the update.\n     */\n\n\n    cacheDidUpdate({\n      cacheName,\n      oldResponse,\n      newResponse,\n      request,\n      event\n    }) {\n      {\n        assert_mjs.assert.isType(cacheName, 'string', {\n          moduleName: 'workbox-broadcast-update',\n          className: 'Plugin',\n          funcName: 'cacheDidUpdate',\n          paramName: 'cacheName'\n        });\n        assert_mjs.assert.isInstance(newResponse, Response, {\n          moduleName: 'workbox-broadcast-update',\n          className: 'Plugin',\n          funcName: 'cacheDidUpdate',\n          paramName: 'newResponse'\n        });\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-broadcast-update',\n          className: 'Plugin',\n          funcName: 'cacheDidUpdate',\n          paramName: 'request'\n        });\n      }\n\n      if (!oldResponse) {\n        // Without a two responses there is nothing to compare.\n        return;\n      }\n\n      this._broadcastUpdate.notifyIfUpdated({\n        cacheName,\n        oldResponse,\n        newResponse,\n        event,\n        url: request.url\n      });\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.BroadcastCacheUpdate = BroadcastCacheUpdate;\n  exports.Plugin = Plugin;\n  exports.broadcastUpdate = broadcastUpdate;\n  exports.responsesAreSame = responsesAreSame;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-broadcast-update.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-broadcast-update.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(e,t){\"use strict\";try{self[\"workbox:broadcast-update:4.1.1\"]&&_()}catch(e){}const s=(e,t,s)=>{return!s.some(s=>e.headers.has(s)&&t.headers.has(s))||s.every(s=>{const n=e.headers.has(s)===t.headers.has(s),a=e.headers.get(s)===t.headers.get(s);return n&&a})},n=\"workbox\",a=1e4,i=[\"content-length\",\"etag\",\"last-modified\"],o=async({channel:e,cacheName:t,url:s})=>{const n={type:\"CACHE_UPDATED\",meta:\"workbox-broadcast-update\",payload:{cacheName:t,updatedURL:s}};if(e)e.postMessage(n);else{const e=await clients.matchAll({type:\"window\"});for(const t of e)t.postMessage(n)}};class c{constructor({headersToCheck:e,channelName:t,deferNoticationTimeout:s}={}){this.t=e||i,this.s=t||n,this.i=s||a,this.o()}notifyIfUpdated({oldResponse:e,newResponse:t,url:n,cacheName:a,event:i}){if(!s(e,t,this.t)){const e=(async()=>{i&&i.request&&\"navigate\"===i.request.mode&&await this.h(i),await o({channel:this.l(),cacheName:a,url:n})})();if(i)try{i.waitUntil(e)}catch(e){}return e}}l(){return\"BroadcastChannel\"in self&&!this.u&&(this.u=new BroadcastChannel(this.s)),this.u}h(e){if(!this.m.has(e)){const s=new t.Deferred;this.m.set(e,s);const n=setTimeout(()=>{s.resolve()},this.i);s.promise.then(()=>clearTimeout(n))}return this.m.get(e).promise}o(){this.m=new Map,self.addEventListener(\"message\",e=>{if(\"WINDOW_READY\"===e.data.type&&\"workbox-window\"===e.data.meta&&this.m.size>0){for(const e of this.m.values())e.resolve();this.m.clear()}})}}return e.BroadcastCacheUpdate=c,e.Plugin=class{constructor(e){this.p=new c(e)}cacheDidUpdate({cacheName:e,oldResponse:t,newResponse:s,request:n,event:a}){t&&this.p.notifyIfUpdated({cacheName:e,oldResponse:t,newResponse:s,event:a,url:n.url})}},e.broadcastUpdate=o,e.responsesAreSame=s,e}({},workbox.core._private);\n//# sourceMappingURL=workbox-broadcast-update.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-cacheable-response.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.cacheableResponse = (function (exports, WorkboxError_mjs, assert_mjs, getFriendlyURL_mjs, logger_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:cacheable-response:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * This class allows you to set up rules determining what\n   * status codes and/or headers need to be present in order for a\n   * [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n   * to be considered cacheable.\n   *\n   * @memberof workbox.cacheableResponse\n   */\n\n  class CacheableResponse {\n    /**\n     * To construct a new CacheableResponse instance you must provide at least\n     * one of the `config` properties.\n     *\n     * If both `statuses` and `headers` are specified, then both conditions must\n     * be met for the `Response` to be considered cacheable.\n     *\n     * @param {Object} config\n     * @param {Array<number>} [config.statuses] One or more status codes that a\n     * `Response` can have and be considered cacheable.\n     * @param {Object<string,string>} [config.headers] A mapping of header names\n     * and expected values that a `Response` can have and be considered cacheable.\n     * If multiple headers are provided, only one needs to be present.\n     */\n    constructor(config = {}) {\n      {\n        if (!(config.statuses || config.headers)) {\n          throw new WorkboxError_mjs.WorkboxError('statuses-or-headers-required', {\n            moduleName: 'workbox-cacheable-response',\n            className: 'CacheableResponse',\n            funcName: 'constructor'\n          });\n        }\n\n        if (config.statuses) {\n          assert_mjs.assert.isArray(config.statuses, {\n            moduleName: 'workbox-cacheable-response',\n            className: 'CacheableResponse',\n            funcName: 'constructor',\n            paramName: 'config.statuses'\n          });\n        }\n\n        if (config.headers) {\n          assert_mjs.assert.isType(config.headers, 'object', {\n            moduleName: 'workbox-cacheable-response',\n            className: 'CacheableResponse',\n            funcName: 'constructor',\n            paramName: 'config.headers'\n          });\n        }\n      }\n\n      this._statuses = config.statuses;\n      this._headers = config.headers;\n    }\n    /**\n     * Checks a response to see whether it's cacheable or not, based on this\n     * object's configuration.\n     *\n     * @param {Response} response The response whose cacheability is being\n     * checked.\n     * @return {boolean} `true` if the `Response` is cacheable, and `false`\n     * otherwise.\n     */\n\n\n    isResponseCacheable(response) {\n      {\n        assert_mjs.assert.isInstance(response, Response, {\n          moduleName: 'workbox-cacheable-response',\n          className: 'CacheableResponse',\n          funcName: 'isResponseCacheable',\n          paramName: 'response'\n        });\n      }\n\n      let cacheable = true;\n\n      if (this._statuses) {\n        cacheable = this._statuses.includes(response.status);\n      }\n\n      if (this._headers && cacheable) {\n        cacheable = Object.keys(this._headers).some(headerName => {\n          return response.headers.get(headerName) === this._headers[headerName];\n        });\n      }\n\n      {\n        if (!cacheable) {\n          logger_mjs.logger.groupCollapsed(`The request for ` + `'${getFriendlyURL_mjs.getFriendlyURL(response.url)}' returned a response that does ` + `not meet the criteria for being cached.`);\n          logger_mjs.logger.groupCollapsed(`View cacheability criteria here.`);\n          logger_mjs.logger.log(`Cacheable statuses: ` + JSON.stringify(this._statuses));\n          logger_mjs.logger.log(`Cacheable headers: ` + JSON.stringify(this._headers, null, 2));\n          logger_mjs.logger.groupEnd();\n          const logFriendlyHeaders = {};\n          response.headers.forEach((value, key) => {\n            logFriendlyHeaders[key] = value;\n          });\n          logger_mjs.logger.groupCollapsed(`View response status and headers here.`);\n          logger_mjs.logger.log(`Response status: ` + response.status);\n          logger_mjs.logger.log(`Response headers: ` + JSON.stringify(logFriendlyHeaders, null, 2));\n          logger_mjs.logger.groupEnd();\n          logger_mjs.logger.groupCollapsed(`View full response details here.`);\n          logger_mjs.logger.log(response.headers);\n          logger_mjs.logger.log(response);\n          logger_mjs.logger.groupEnd();\n          logger_mjs.logger.groupEnd();\n        }\n      }\n\n      return cacheable;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * A class implementing the `cacheWillUpdate` lifecycle callback. This makes it\n   * easier to add in cacheability checks to requests made via Workbox's built-in\n   * strategies.\n   *\n   * @memberof workbox.cacheableResponse\n   */\n\n  class Plugin {\n    /**\n     * To construct a new cacheable response Plugin instance you must provide at\n     * least one of the `config` properties.\n     *\n     * If both `statuses` and `headers` are specified, then both conditions must\n     * be met for the `Response` to be considered cacheable.\n     *\n     * @param {Object} config\n     * @param {Array<number>} [config.statuses] One or more status codes that a\n     * `Response` can have and be considered cacheable.\n     * @param {Object<string,string>} [config.headers] A mapping of header names\n     * and expected values that a `Response` can have and be considered cacheable.\n     * If multiple headers are provided, only one needs to be present.\n     */\n    constructor(config) {\n      this._cacheableResponse = new CacheableResponse(config);\n    }\n    /**\n     * @param {Object} options\n     * @param {Response} options.response\n     * @return {boolean}\n     * @private\n     */\n\n\n    cacheWillUpdate({\n      response\n    }) {\n      if (this._cacheableResponse.isResponseCacheable(response)) {\n        return response;\n      }\n\n      return null;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.CacheableResponse = CacheableResponse;\n  exports.Plugin = Plugin;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-cacheable-response.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-cacheable-response.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.cacheableResponse=function(t){\"use strict\";try{self[\"workbox:cacheable-response:4.1.1\"]&&_()}catch(t){}class s{constructor(t={}){this.t=t.statuses,this.s=t.headers}isResponseCacheable(t){let s=!0;return this.t&&(s=this.t.includes(t.status)),this.s&&s&&(s=Object.keys(this.s).some(s=>t.headers.get(s)===this.s[s])),s}}return t.CacheableResponse=s,t.Plugin=class{constructor(t){this.i=new s(t)}cacheWillUpdate({response:t}){return this.i.isResponseCacheable(t)?t:null}},t}({});\n//# sourceMappingURL=workbox-cacheable-response.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-core.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.core = (function (exports) {\n  'use strict';\n\n  try {\n    self['workbox:core:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2019 Google LLC\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const logger = (() => {\n    let inGroup = false;\n    const methodToColorMap = {\n      debug: `#7f8c8d`,\n      // Gray\n      log: `#2ecc71`,\n      // Green\n      warn: `#f39c12`,\n      // Yellow\n      error: `#c0392b`,\n      // Red\n      groupCollapsed: `#3498db`,\n      // Blue\n      groupEnd: null // No colored prefix on groupEnd\n\n    };\n\n    const print = function (method, args) {\n      if (method === 'groupCollapsed') {\n        // Safari doesn't print all console.groupCollapsed() arguments:\n        // https://bugs.webkit.org/show_bug.cgi?id=182754\n        if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n          console[method](...args);\n          return;\n        }\n      }\n\n      const styles = [`background: ${methodToColorMap[method]}`, `border-radius: 0.5em`, `color: white`, `font-weight: bold`, `padding: 2px 0.5em`]; // When in a group, the workbox prefix is not displayed.\n\n      const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n      console[method](...logPrefix, ...args);\n\n      if (method === 'groupCollapsed') {\n        inGroup = true;\n      }\n\n      if (method === 'groupEnd') {\n        inGroup = false;\n      }\n    };\n\n    const api = {};\n\n    for (const method of Object.keys(methodToColorMap)) {\n      api[method] = (...args) => {\n        print(method, args);\n      };\n    }\n\n    return api;\n  })();\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const messages = {\n    'invalid-value': ({\n      paramName,\n      validValueDescription,\n      value\n    }) => {\n      if (!paramName || !validValueDescription) {\n        throw new Error(`Unexpected input to 'invalid-value' error.`);\n      }\n\n      return `The '${paramName}' parameter was given a value with an ` + `unexpected value. ${validValueDescription} Received a value of ` + `${JSON.stringify(value)}.`;\n    },\n    'not-in-sw': ({\n      moduleName\n    }) => {\n      if (!moduleName) {\n        throw new Error(`Unexpected input to 'not-in-sw' error.`);\n      }\n\n      return `The '${moduleName}' must be used in a service worker.`;\n    },\n    'not-an-array': ({\n      moduleName,\n      className,\n      funcName,\n      paramName\n    }) => {\n      if (!moduleName || !className || !funcName || !paramName) {\n        throw new Error(`Unexpected input to 'not-an-array' error.`);\n      }\n\n      return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className}.${funcName}()' must be an array.`;\n    },\n    'incorrect-type': ({\n      expectedType,\n      paramName,\n      moduleName,\n      className,\n      funcName\n    }) => {\n      if (!expectedType || !paramName || !moduleName || !funcName) {\n        throw new Error(`Unexpected input to 'incorrect-type' error.`);\n      }\n\n      return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className ? className + '.' : ''}` + `${funcName}()' must be of type ${expectedType}.`;\n    },\n    'incorrect-class': ({\n      expectedClass,\n      paramName,\n      moduleName,\n      className,\n      funcName,\n      isReturnValueProblem\n    }) => {\n      if (!expectedClass || !moduleName || !funcName) {\n        throw new Error(`Unexpected input to 'incorrect-class' error.`);\n      }\n\n      if (isReturnValueProblem) {\n        return `The return value from ` + `'${moduleName}.${className ? className + '.' : ''}${funcName}()' ` + `must be an instance of class ${expectedClass.name}.`;\n      }\n\n      return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className ? className + '.' : ''}${funcName}()' ` + `must be an instance of class ${expectedClass.name}.`;\n    },\n    'missing-a-method': ({\n      expectedMethod,\n      paramName,\n      moduleName,\n      className,\n      funcName\n    }) => {\n      if (!expectedMethod || !paramName || !moduleName || !className || !funcName) {\n        throw new Error(`Unexpected input to 'missing-a-method' error.`);\n      }\n\n      return `${moduleName}.${className}.${funcName}() expected the ` + `'${paramName}' parameter to expose a '${expectedMethod}' method.`;\n    },\n    'add-to-cache-list-unexpected-type': ({\n      entry\n    }) => {\n      return `An unexpected entry was passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' The entry ` + `'${JSON.stringify(entry)}' isn't supported. You must supply an array of ` + `strings with one or more characters, objects with a url property or ` + `Request objects.`;\n    },\n    'add-to-cache-list-conflicting-entries': ({\n      firstEntry,\n      secondEntry\n    }) => {\n      if (!firstEntry || !secondEntry) {\n        throw new Error(`Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`);\n      }\n\n      return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${firstEntry._entryId} but different revision details. Workbox is ` + `is unable to cache and version the asset correctly. Please remove one ` + `of the entries.`;\n    },\n    'plugin-error-request-will-fetch': ({\n      thrownError\n    }) => {\n      if (!thrownError) {\n        throw new Error(`Unexpected input to ` + `'plugin-error-request-will-fetch', error.`);\n      }\n\n      return `An error was thrown by a plugins 'requestWillFetch()' method. ` + `The thrown error message was: '${thrownError.message}'.`;\n    },\n    'invalid-cache-name': ({\n      cacheNameId,\n      value\n    }) => {\n      if (!cacheNameId) {\n        throw new Error(`Expected a 'cacheNameId' for error 'invalid-cache-name'`);\n      }\n\n      return `You must provide a name containing at least one character for ` + `setCacheDeatils({${cacheNameId}: '...'}). Received a value of ` + `'${JSON.stringify(value)}'`;\n    },\n    'unregister-route-but-not-found-with-method': ({\n      method\n    }) => {\n      if (!method) {\n        throw new Error(`Unexpected input to ` + `'unregister-route-but-not-found-with-method' error.`);\n      }\n\n      return `The route you're trying to unregister was not  previously ` + `registered for the method type '${method}'.`;\n    },\n    'unregister-route-route-not-registered': () => {\n      return `The route you're trying to unregister was not previously ` + `registered.`;\n    },\n    'queue-replay-failed': ({\n      name\n    }) => {\n      return `Replaying the background sync queue '${name}' failed.`;\n    },\n    'duplicate-queue-name': ({\n      name\n    }) => {\n      return `The Queue name '${name}' is already being used. ` + `All instances of backgroundSync.Queue must be given unique names.`;\n    },\n    'expired-test-without-max-age': ({\n      methodName,\n      paramName\n    }) => {\n      return `The '${methodName}()' method can only be used when the ` + `'${paramName}' is used in the constructor.`;\n    },\n    'unsupported-route-type': ({\n      moduleName,\n      className,\n      funcName,\n      paramName\n    }) => {\n      return `The supplied '${paramName}' parameter was an unsupported type. ` + `Please check the docs for ${moduleName}.${className}.${funcName} for ` + `valid input types.`;\n    },\n    'not-array-of-class': ({\n      value,\n      expectedClass,\n      moduleName,\n      className,\n      funcName,\n      paramName\n    }) => {\n      return `The supplied '${paramName}' parameter must be an array of ` + `'${expectedClass}' objects. Received '${JSON.stringify(value)},'. ` + `Please check the call to ${moduleName}.${className}.${funcName}() ` + `to fix the issue.`;\n    },\n    'max-entries-or-age-required': ({\n      moduleName,\n      className,\n      funcName\n    }) => {\n      return `You must define either config.maxEntries or config.maxAgeSeconds` + `in ${moduleName}.${className}.${funcName}`;\n    },\n    'statuses-or-headers-required': ({\n      moduleName,\n      className,\n      funcName\n    }) => {\n      return `You must define either config.statuses or config.headers` + `in ${moduleName}.${className}.${funcName}`;\n    },\n    'invalid-string': ({\n      moduleName,\n      className,\n      funcName,\n      paramName\n    }) => {\n      if (!paramName || !moduleName || !funcName) {\n        throw new Error(`Unexpected input to 'invalid-string' error.`);\n      }\n\n      return `When using strings, the '${paramName}' parameter must start with ` + `'http' (for cross-origin matches) or '/' (for same-origin matches). ` + `Please see the docs for ${moduleName}.${funcName}() for ` + `more info.`;\n    },\n    'channel-name-required': () => {\n      return `You must provide a channelName to construct a ` + `BroadcastCacheUpdate instance.`;\n    },\n    'invalid-responses-are-same-args': () => {\n      return `The arguments passed into responsesAreSame() appear to be ` + `invalid. Please ensure valid Responses are used.`;\n    },\n    'expire-custom-caches-only': () => {\n      return `You must provide a 'cacheName' property when using the ` + `expiration plugin with a runtime caching strategy.`;\n    },\n    'unit-must-be-bytes': ({\n      normalizedRangeHeader\n    }) => {\n      if (!normalizedRangeHeader) {\n        throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`);\n      }\n\n      return `The 'unit' portion of the Range header must be set to 'bytes'. ` + `The Range header provided was \"${normalizedRangeHeader}\"`;\n    },\n    'single-range-only': ({\n      normalizedRangeHeader\n    }) => {\n      if (!normalizedRangeHeader) {\n        throw new Error(`Unexpected input to 'single-range-only' error.`);\n      }\n\n      return `Multiple ranges are not supported. Please use a  single start ` + `value, and optional end value. The Range header provided was ` + `\"${normalizedRangeHeader}\"`;\n    },\n    'invalid-range-values': ({\n      normalizedRangeHeader\n    }) => {\n      if (!normalizedRangeHeader) {\n        throw new Error(`Unexpected input to 'invalid-range-values' error.`);\n      }\n\n      return `The Range header is missing both start and end values. At least ` + `one of those values is needed. The Range header provided was ` + `\"${normalizedRangeHeader}\"`;\n    },\n    'no-range-header': () => {\n      return `No Range header was found in the Request provided.`;\n    },\n    'range-not-satisfiable': ({\n      size,\n      start,\n      end\n    }) => {\n      return `The start (${start}) and end (${end}) values in the Range are ` + `not satisfiable by the cached response, which is ${size} bytes.`;\n    },\n    'attempt-to-cache-non-get-request': ({\n      url,\n      method\n    }) => {\n      return `Unable to cache '${url}' because it is a '${method}' request and ` + `only 'GET' requests can be cached.`;\n    },\n    'cache-put-with-no-response': ({\n      url\n    }) => {\n      return `There was an attempt to cache '${url}' but the response was not ` + `defined.`;\n    },\n    'no-response': ({\n      url,\n      error\n    }) => {\n      let message = `The strategy could not generate a response for '${url}'.`;\n\n      if (error) {\n        message += ` The underlying error is ${error}.`;\n      }\n\n      return message;\n    },\n    'bad-precaching-response': ({\n      url,\n      status\n    }) => {\n      return `The precaching request for '${url}' failed with an HTTP ` + `status of ${status}.`;\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  const generatorFunction = (code, ...args) => {\n    const message = messages[code];\n\n    if (!message) {\n      throw new Error(`Unable to find message for code '${code}'.`);\n    }\n\n    return message(...args);\n  };\n\n  const messageGenerator = generatorFunction;\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Workbox errors should be thrown with this class.\n   * This allows use to ensure the type easily in tests,\n   * helps developers identify errors from workbox\n   * easily and allows use to optimise error\n   * messages correctly.\n   *\n   * @private\n   */\n\n  class WorkboxError extends Error {\n    /**\n     *\n     * @param {string} errorCode The error code that\n     * identifies this particular error.\n     * @param {Object=} details Any relevant arguments\n     * that will help developers identify issues should\n     * be added as a key on the context object.\n     */\n    constructor(errorCode, details) {\n      let message = messageGenerator(errorCode, details);\n      super(message);\n      this.name = errorCode;\n      this.details = details;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /*\n   * This method returns true if the current context is a service worker.\n   */\n\n  const isSWEnv = moduleName => {\n    if (!('ServiceWorkerGlobalScope' in self)) {\n      throw new WorkboxError('not-in-sw', {\n        moduleName\n      });\n    }\n  };\n  /*\n   * This method throws if the supplied value is not an array.\n   * The destructed values are required to produce a meaningful error for users.\n   * The destructed and restructured object is so it's clear what is\n   * needed.\n   */\n\n\n  const isArray = (value, {\n    moduleName,\n    className,\n    funcName,\n    paramName\n  }) => {\n    if (!Array.isArray(value)) {\n      throw new WorkboxError('not-an-array', {\n        moduleName,\n        className,\n        funcName,\n        paramName\n      });\n    }\n  };\n\n  const hasMethod = (object, expectedMethod, {\n    moduleName,\n    className,\n    funcName,\n    paramName\n  }) => {\n    const type = typeof object[expectedMethod];\n\n    if (type !== 'function') {\n      throw new WorkboxError('missing-a-method', {\n        paramName,\n        expectedMethod,\n        moduleName,\n        className,\n        funcName\n      });\n    }\n  };\n\n  const isType = (object, expectedType, {\n    moduleName,\n    className,\n    funcName,\n    paramName\n  }) => {\n    if (typeof object !== expectedType) {\n      throw new WorkboxError('incorrect-type', {\n        paramName,\n        expectedType,\n        moduleName,\n        className,\n        funcName\n      });\n    }\n  };\n\n  const isInstance = (object, expectedClass, {\n    moduleName,\n    className,\n    funcName,\n    paramName,\n    isReturnValueProblem\n  }) => {\n    if (!(object instanceof expectedClass)) {\n      throw new WorkboxError('incorrect-class', {\n        paramName,\n        expectedClass,\n        moduleName,\n        className,\n        funcName,\n        isReturnValueProblem\n      });\n    }\n  };\n\n  const isOneOf = (value, validValues, {\n    paramName\n  }) => {\n    if (!validValues.includes(value)) {\n      throw new WorkboxError('invalid-value', {\n        paramName,\n        value,\n        validValueDescription: `Valid values are ${JSON.stringify(validValues)}.`\n      });\n    }\n  };\n\n  const isArrayOfClass = (value, expectedClass, {\n    moduleName,\n    className,\n    funcName,\n    paramName\n  }) => {\n    const error = new WorkboxError('not-array-of-class', {\n      value,\n      expectedClass,\n      moduleName,\n      className,\n      funcName,\n      paramName\n    });\n\n    if (!Array.isArray(value)) {\n      throw error;\n    }\n\n    for (let item of value) {\n      if (!(item instanceof expectedClass)) {\n        throw error;\n      }\n    }\n  };\n\n  const finalAssertExports = {\n    hasMethod,\n    isArray,\n    isInstance,\n    isOneOf,\n    isSWEnv,\n    isType,\n    isArrayOfClass\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const callbacks = new Set();\n  /**\n   * Adds a function to the set of callbacks that will be executed when there's\n   * a quota error.\n   *\n   * @param {Function} callback\n   * @memberof workbox.core\n   */\n\n  function registerQuotaErrorCallback(callback) {\n    {\n      finalAssertExports.isType(callback, 'function', {\n        moduleName: 'workbox-core',\n        funcName: 'register',\n        paramName: 'callback'\n      });\n    }\n\n    callbacks.add(callback);\n\n    {\n      logger.log('Registered a callback to respond to quota errors.', callback);\n    }\n  }\n  /**\n   * Runs all of the callback functions, one at a time sequentially, in the order\n   * in which they were registered.\n   *\n   * @memberof workbox.core\n   * @private\n   */\n\n\n  async function executeQuotaErrorCallbacks() {\n    {\n      logger.log(`About to run ${callbacks.size} callbacks to clean up caches.`);\n    }\n\n    for (const callback of callbacks) {\n      await callback();\n\n      {\n        logger.log(callback, 'is complete.');\n      }\n    }\n\n    {\n      logger.log('Finished running callbacks.');\n    }\n  }\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * A class that wraps common IndexedDB functionality in a promise-based API.\n   * It exposes all the underlying power and functionality of IndexedDB, but\n   * wraps the most commonly used features in a way that's much simpler to use.\n   *\n   * @private\n   */\n\n  class DBWrapper {\n    /**\n     * @param {string} name\n     * @param {number} version\n     * @param {Object=} [callback]\n     * @param {!Function} [callbacks.onupgradeneeded]\n     * @param {!Function} [callbacks.onversionchange] Defaults to\n     *     DBWrapper.prototype._onversionchange when not specified.\n     */\n    constructor(name, version, {\n      onupgradeneeded,\n      onversionchange = this._onversionchange\n    } = {}) {\n      this._name = name;\n      this._version = version;\n      this._onupgradeneeded = onupgradeneeded;\n      this._onversionchange = onversionchange; // If this is null, it means the database isn't open.\n\n      this._db = null;\n    }\n    /**\n     * Returns the IDBDatabase instance (not normally needed).\n     */\n\n\n    get db() {\n      return this._db;\n    }\n    /**\n     * Opens a connected to an IDBDatabase, invokes any onupgradedneeded\n     * callback, and added an onversionchange callback to the database.\n     *\n     * @return {IDBDatabase}\n     */\n\n\n    async open() {\n      if (this._db) return;\n      this._db = await new Promise((resolve, reject) => {\n        // This flag is flipped to true if the timeout callback runs prior\n        // to the request failing or succeeding. Note: we use a timeout instead\n        // of an onblocked handler since there are cases where onblocked will\n        // never never run. A timeout better handles all possible scenarios:\n        // https://github.com/w3c/IndexedDB/issues/223\n        let openRequestTimedOut = false;\n        setTimeout(() => {\n          openRequestTimedOut = true;\n          reject(new Error('The open request was blocked and timed out'));\n        }, this.OPEN_TIMEOUT);\n        const openRequest = indexedDB.open(this._name, this._version);\n\n        openRequest.onerror = () => reject(openRequest.error);\n\n        openRequest.onupgradeneeded = evt => {\n          if (openRequestTimedOut) {\n            openRequest.transaction.abort();\n            evt.target.result.close();\n          } else if (this._onupgradeneeded) {\n            this._onupgradeneeded(evt);\n          }\n        };\n\n        openRequest.onsuccess = ({\n          target\n        }) => {\n          const db = target.result;\n\n          if (openRequestTimedOut) {\n            db.close();\n          } else {\n            db.onversionchange = this._onversionchange.bind(this);\n            resolve(db);\n          }\n        };\n      });\n      return this;\n    }\n    /**\n     * Polyfills the native `getKey()` method. Note, this is overridden at\n     * runtime if the browser supports the native method.\n     *\n     * @param {string} storeName\n     * @param {*} query\n     * @return {Array}\n     */\n\n\n    async getKey(storeName, query) {\n      return (await this.getAllKeys(storeName, query, 1))[0];\n    }\n    /**\n     * Polyfills the native `getAll()` method. Note, this is overridden at\n     * runtime if the browser supports the native method.\n     *\n     * @param {string} storeName\n     * @param {*} query\n     * @param {number} count\n     * @return {Array}\n     */\n\n\n    async getAll(storeName, query, count) {\n      return await this.getAllMatching(storeName, {\n        query,\n        count\n      });\n    }\n    /**\n     * Polyfills the native `getAllKeys()` method. Note, this is overridden at\n     * runtime if the browser supports the native method.\n     *\n     * @param {string} storeName\n     * @param {*} query\n     * @param {number} count\n     * @return {Array}\n     */\n\n\n    async getAllKeys(storeName, query, count) {\n      return (await this.getAllMatching(storeName, {\n        query,\n        count,\n        includeKeys: true\n      })).map(({\n        key\n      }) => key);\n    }\n    /**\n     * Supports flexible lookup in an object store by specifying an index,\n     * query, direction, and count. This method returns an array of objects\n     * with the signature .\n     *\n     * @param {string} storeName\n     * @param {Object} [opts]\n     * @param {string} [opts.index] The index to use (if specified).\n     * @param {*} [opts.query]\n     * @param {IDBCursorDirection} [opts.direction]\n     * @param {number} [opts.count] The max number of results to return.\n     * @param {boolean} [opts.includeKeys] When true, the structure of the\n     *     returned objects is changed from an array of values to an array of\n     *     objects in the form {key, primaryKey, value}.\n     * @return {Array}\n     */\n\n\n    async getAllMatching(storeName, {\n      index,\n      query = null,\n      // IE errors if query === `undefined`.\n      direction = 'next',\n      count,\n      includeKeys\n    } = {}) {\n      return await this.transaction([storeName], 'readonly', (txn, done) => {\n        const store = txn.objectStore(storeName);\n        const target = index ? store.index(index) : store;\n        const results = [];\n\n        target.openCursor(query, direction).onsuccess = ({\n          target\n        }) => {\n          const cursor = target.result;\n\n          if (cursor) {\n            const {\n              primaryKey,\n              key,\n              value\n            } = cursor;\n            results.push(includeKeys ? {\n              primaryKey,\n              key,\n              value\n            } : value);\n\n            if (count && results.length >= count) {\n              done(results);\n            } else {\n              cursor.continue();\n            }\n          } else {\n            done(results);\n          }\n        };\n      });\n    }\n    /**\n     * Accepts a list of stores, a transaction type, and a callback and\n     * performs a transaction. A promise is returned that resolves to whatever\n     * value the callback chooses. The callback holds all the transaction logic\n     * and is invoked with two arguments:\n     *   1. The IDBTransaction object\n     *   2. A `done` function, that's used to resolve the promise when\n     *      when the transaction is done, if passed a value, the promise is\n     *      resolved to that value.\n     *\n     * @param {Array<string>} storeNames An array of object store names\n     *     involved in the transaction.\n     * @param {string} type Can be `readonly` or `readwrite`.\n     * @param {!Function} callback\n     * @return {*} The result of the transaction ran by the callback.\n     */\n\n\n    async transaction(storeNames, type, callback) {\n      await this.open();\n      return await new Promise((resolve, reject) => {\n        const txn = this._db.transaction(storeNames, type);\n\n        txn.onabort = ({\n          target\n        }) => reject(target.error);\n\n        txn.oncomplete = () => resolve();\n\n        callback(txn, value => resolve(value));\n      });\n    }\n    /**\n     * Delegates async to a native IDBObjectStore method.\n     *\n     * @param {string} method The method name.\n     * @param {string} storeName The object store name.\n     * @param {string} type Can be `readonly` or `readwrite`.\n     * @param {...*} args The list of args to pass to the native method.\n     * @return {*} The result of the transaction.\n     */\n\n\n    async _call(method, storeName, type, ...args) {\n      const callback = (txn, done) => {\n        txn.objectStore(storeName)[method](...args).onsuccess = ({\n          target\n        }) => {\n          done(target.result);\n        };\n      };\n\n      return await this.transaction([storeName], type, callback);\n    }\n    /**\n     * The default onversionchange handler, which closes the database so other\n     * connections can open without being blocked.\n     */\n\n\n    _onversionchange() {\n      this.close();\n    }\n    /**\n     * Closes the connection opened by `DBWrapper.open()`. Generally this method\n     * doesn't need to be called since:\n     *   1. It's usually better to keep a connection open since opening\n     *      a new connection is somewhat slow.\n     *   2. Connections are automatically closed when the reference is\n     *      garbage collected.\n     * The primary use case for needing to close a connection is when another\n     * reference (typically in another tab) needs to upgrade it and would be\n     * blocked by the current, open connection.\n     */\n\n\n    close() {\n      if (this._db) {\n        this._db.close();\n\n        this._db = null;\n      }\n    }\n\n  } // Exposed to let users modify the default timeout on a per-instance\n  // or global basis.\n\n  DBWrapper.prototype.OPEN_TIMEOUT = 2000; // Wrap native IDBObjectStore methods according to their mode.\n\n  const methodsToWrap = {\n    'readonly': ['get', 'count', 'getKey', 'getAll', 'getAllKeys'],\n    'readwrite': ['add', 'put', 'clear', 'delete']\n  };\n\n  for (const [mode, methods] of Object.entries(methodsToWrap)) {\n    for (const method of methods) {\n      if (method in IDBObjectStore.prototype) {\n        // Don't use arrow functions here since we're outside of the class.\n        DBWrapper.prototype[method] = async function (storeName, ...args) {\n          return await this._call(method, storeName, mode, ...args);\n        };\n      }\n    }\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Deletes the database.\n   * Note: this is exported separately from the DBWrapper module because most\n   * usages of IndexedDB in workbox dont need deleting, and this way it can be\n   * reused in tests to delete databases without creating DBWrapper instances.\n   *\n   * @param {string} name The database name.\n   * @private\n   */\n\n  const deleteDatabase = async name => {\n    await new Promise((resolve, reject) => {\n      const request = indexedDB.deleteDatabase(name);\n\n      request.onerror = ({\n        target\n      }) => {\n        reject(target.error);\n      };\n\n      request.onblocked = () => {\n        reject(new Error('Delete blocked'));\n      };\n\n      request.onsuccess = () => {\n        resolve();\n      };\n    });\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const _cacheNameDetails = {\n    googleAnalytics: 'googleAnalytics',\n    precache: 'precache-v2',\n    prefix: 'workbox',\n    runtime: 'runtime',\n    suffix: self.registration.scope\n  };\n\n  const _createCacheName = cacheName => {\n    return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix].filter(value => value.length > 0).join('-');\n  };\n\n  const cacheNames = {\n    updateDetails: details => {\n      Object.keys(_cacheNameDetails).forEach(key => {\n        if (typeof details[key] !== 'undefined') {\n          _cacheNameDetails[key] = details[key];\n        }\n      });\n    },\n    getGoogleAnalyticsName: userCacheName => {\n      return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);\n    },\n    getPrecacheName: userCacheName => {\n      return userCacheName || _createCacheName(_cacheNameDetails.precache);\n    },\n    getRuntimeName: userCacheName => {\n      return userCacheName || _createCacheName(_cacheNameDetails.runtime);\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const pluginEvents = {\n    CACHE_DID_UPDATE: 'cacheDidUpdate',\n    CACHE_WILL_UPDATE: 'cacheWillUpdate',\n    CACHED_RESPONSE_WILL_BE_USED: 'cachedResponseWillBeUsed',\n    FETCH_DID_FAIL: 'fetchDidFail',\n    FETCH_DID_SUCCEED: 'fetchDidSucceed',\n    REQUEST_WILL_FETCH: 'requestWillFetch'\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const pluginUtils = {\n    filter: (plugins, callbackName) => {\n      return plugins.filter(plugin => callbackName in plugin);\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  const getFriendlyURL = url => {\n    const urlObj = new URL(url, location);\n\n    if (urlObj.origin === location.origin) {\n      return urlObj.pathname;\n    }\n\n    return urlObj.href;\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Wrapper around cache.put().\n   *\n   * Will call `cacheDidUpdate` on plugins if the cache was updated, using\n   * `matchOptions` when determining what the old entry is.\n   *\n   * @param {Object} options\n   * @param {string} options.cacheName\n   * @param {Request} options.request\n   * @param {Response} options.response\n   * @param {Event} [options.event]\n   * @param {Array<Object>} [options.plugins=[]]\n   * @param {Object} [options.matchOptions]\n   *\n   * @private\n   * @memberof module:workbox-core\n   */\n\n  const putWrapper = async ({\n    cacheName,\n    request,\n    response,\n    event,\n    plugins = [],\n    matchOptions\n  } = {}) => {\n    if (!response) {\n      {\n        logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL(request.url)}'.`);\n      }\n\n      throw new WorkboxError('cache-put-with-no-response', {\n        url: getFriendlyURL(request.url)\n      });\n    }\n\n    let responseToCache = await _isResponseSafeToCache({\n      request,\n      response,\n      event,\n      plugins\n    });\n\n    if (!responseToCache) {\n      {\n        logger.debug(`Response '${getFriendlyURL(request.url)}' will not be ` + `cached.`, responseToCache);\n      }\n\n      return;\n    }\n\n    {\n      if (responseToCache.method && responseToCache.method !== 'GET') {\n        throw new WorkboxError('attempt-to-cache-non-get-request', {\n          url: getFriendlyURL(request.url),\n          method: responseToCache.method\n        });\n      }\n    }\n\n    const cache = await caches.open(cacheName);\n    const updatePlugins = pluginUtils.filter(plugins, pluginEvents.CACHE_DID_UPDATE);\n    let oldResponse = updatePlugins.length > 0 ? await matchWrapper({\n      cacheName,\n      request,\n      matchOptions\n    }) : null;\n\n    {\n      logger.debug(`Updating the '${cacheName}' cache with a new Response for ` + `${getFriendlyURL(request.url)}.`);\n    }\n\n    try {\n      await cache.put(request, responseToCache);\n    } catch (error) {\n      // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError\n      if (error.name === 'QuotaExceededError') {\n        await executeQuotaErrorCallbacks();\n      }\n\n      throw error;\n    }\n\n    for (let plugin of updatePlugins) {\n      await plugin[pluginEvents.CACHE_DID_UPDATE].call(plugin, {\n        cacheName,\n        request,\n        event,\n        oldResponse,\n        newResponse: responseToCache\n      });\n    }\n  };\n  /**\n   * This is a wrapper around cache.match().\n   *\n   * @param {Object} options\n   * @param {string} options.cacheName Name of the cache to match against.\n   * @param {Request} options.request The Request that will be used to look up\n   *     cache entries.\n   * @param {Event} [options.event] The event that propted the action.\n   * @param {Object} [options.matchOptions] Options passed to cache.match().\n   * @param {Array<Object>} [options.plugins=[]] Array of plugins.\n   * @return {Response} A cached response if available.\n   *\n   * @private\n   * @memberof module:workbox-core\n   */\n\n\n  const matchWrapper = async ({\n    cacheName,\n    request,\n    event,\n    matchOptions,\n    plugins = []\n  }) => {\n    const cache = await caches.open(cacheName);\n    let cachedResponse = await cache.match(request, matchOptions);\n\n    {\n      if (cachedResponse) {\n        logger.debug(`Found a cached response in '${cacheName}'.`);\n      } else {\n        logger.debug(`No cached response found in '${cacheName}'.`);\n      }\n    }\n\n    for (const plugin of plugins) {\n      if (pluginEvents.CACHED_RESPONSE_WILL_BE_USED in plugin) {\n        cachedResponse = await plugin[pluginEvents.CACHED_RESPONSE_WILL_BE_USED].call(plugin, {\n          cacheName,\n          request,\n          event,\n          matchOptions,\n          cachedResponse\n        });\n\n        {\n          if (cachedResponse) {\n            finalAssertExports.isInstance(cachedResponse, Response, {\n              moduleName: 'Plugin',\n              funcName: pluginEvents.CACHED_RESPONSE_WILL_BE_USED,\n              isReturnValueProblem: true\n            });\n          }\n        }\n      }\n    }\n\n    return cachedResponse;\n  };\n  /**\n   * This method will call cacheWillUpdate on the available plugins (or use\n   * status === 200) to determine if the Response is safe and valid to cache.\n   *\n   * @param {Object} options\n   * @param {Request} options.request\n   * @param {Response} options.response\n   * @param {Event} [options.event]\n   * @param {Array<Object>} [options.plugins=[]]\n   * @return {Promise<Response>}\n   *\n   * @private\n   * @memberof module:workbox-core\n   */\n\n\n  const _isResponseSafeToCache = async ({\n    request,\n    response,\n    event,\n    plugins\n  }) => {\n    let responseToCache = response;\n    let pluginsUsed = false;\n\n    for (let plugin of plugins) {\n      if (pluginEvents.CACHE_WILL_UPDATE in plugin) {\n        pluginsUsed = true;\n        responseToCache = await plugin[pluginEvents.CACHE_WILL_UPDATE].call(plugin, {\n          request,\n          response: responseToCache,\n          event\n        });\n\n        {\n          if (responseToCache) {\n            finalAssertExports.isInstance(responseToCache, Response, {\n              moduleName: 'Plugin',\n              funcName: pluginEvents.CACHE_WILL_UPDATE,\n              isReturnValueProblem: true\n            });\n          }\n        }\n\n        if (!responseToCache) {\n          break;\n        }\n      }\n    }\n\n    if (!pluginsUsed) {\n      {\n        if (!responseToCache.status === 200) {\n          if (responseToCache.status === 0) {\n            logger.warn(`The response for '${request.url}' is an opaque ` + `response. The caching strategy that you're using will not ` + `cache opaque responses by default.`);\n          } else {\n            logger.debug(`The response for '${request.url}' returned ` + `a status code of '${response.status}' and won't be cached as a ` + `result.`);\n          }\n        }\n      }\n\n      responseToCache = responseToCache.status === 200 ? responseToCache : null;\n    }\n\n    return responseToCache ? responseToCache : null;\n  };\n\n  const cacheWrapper = {\n    put: putWrapper,\n    match: matchWrapper\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Wrapper around the fetch API.\n   *\n   * Will call requestWillFetch on available plugins.\n   *\n   * @param {Object} options\n   * @param {Request|string} options.request\n   * @param {Object} [options.fetchOptions]\n   * @param {Event} [options.event]\n   * @param {Array<Object>} [options.plugins=[]]\n   * @return {Promise<Response>}\n   *\n   * @private\n   * @memberof module:workbox-core\n   */\n\n  const wrappedFetch = async ({\n    request,\n    fetchOptions,\n    event,\n    plugins = []\n  }) => {\n    // We *should* be able to call `await event.preloadResponse` even if it's\n    // undefined, but for some reason, doing so leads to errors in our Node unit\n    // tests. To work around that, explicitly check preloadResponse's value first.\n    if (event && event.preloadResponse) {\n      const possiblePreloadResponse = await event.preloadResponse;\n\n      if (possiblePreloadResponse) {\n        {\n          logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL(request.url)}'`);\n        }\n\n        return possiblePreloadResponse;\n      }\n    }\n\n    if (typeof request === 'string') {\n      request = new Request(request);\n    }\n\n    {\n      finalAssertExports.isInstance(request, Request, {\n        paramName: request,\n        expectedClass: 'Request',\n        moduleName: 'workbox-core',\n        className: 'fetchWrapper',\n        funcName: 'wrappedFetch'\n      });\n    }\n\n    const failedFetchPlugins = pluginUtils.filter(plugins, pluginEvents.FETCH_DID_FAIL); // If there is a fetchDidFail plugin, we need to save a clone of the\n    // original request before it's either modified by a requestWillFetch\n    // plugin or before the original request's body is consumed via fetch().\n\n    const originalRequest = failedFetchPlugins.length > 0 ? request.clone() : null;\n\n    try {\n      for (let plugin of plugins) {\n        if (pluginEvents.REQUEST_WILL_FETCH in plugin) {\n          request = await plugin[pluginEvents.REQUEST_WILL_FETCH].call(plugin, {\n            request: request.clone(),\n            event\n          });\n\n          {\n            if (request) {\n              finalAssertExports.isInstance(request, Request, {\n                moduleName: 'Plugin',\n                funcName: pluginEvents.CACHED_RESPONSE_WILL_BE_USED,\n                isReturnValueProblem: true\n              });\n            }\n          }\n        }\n      }\n    } catch (err) {\n      throw new WorkboxError('plugin-error-request-will-fetch', {\n        thrownError: err\n      });\n    } // The request can be altered by plugins with `requestWillFetch` making\n    // the original request (Most likely from a `fetch` event) to be different\n    // to the Request we make. Pass both to `fetchDidFail` to aid debugging.\n\n\n    let pluginFilteredRequest = request.clone();\n\n    try {\n      let fetchResponse; // See https://github.com/GoogleChrome/workbox/issues/1796\n\n      if (request.mode === 'navigate') {\n        fetchResponse = await fetch(request);\n      } else {\n        fetchResponse = await fetch(request, fetchOptions);\n      }\n\n      {\n        logger.debug(`Network request for ` + `'${getFriendlyURL(request.url)}' returned a response with ` + `status '${fetchResponse.status}'.`);\n      }\n\n      for (const plugin of plugins) {\n        if (pluginEvents.FETCH_DID_SUCCEED in plugin) {\n          fetchResponse = await plugin[pluginEvents.FETCH_DID_SUCCEED].call(plugin, {\n            event,\n            request: pluginFilteredRequest,\n            response: fetchResponse\n          });\n\n          {\n            if (fetchResponse) {\n              finalAssertExports.isInstance(fetchResponse, Response, {\n                moduleName: 'Plugin',\n                funcName: pluginEvents.FETCH_DID_SUCCEED,\n                isReturnValueProblem: true\n              });\n            }\n          }\n        }\n      }\n\n      return fetchResponse;\n    } catch (error) {\n      {\n        logger.error(`Network request for ` + `'${getFriendlyURL(request.url)}' threw an error.`, error);\n      }\n\n      for (const plugin of failedFetchPlugins) {\n        await plugin[pluginEvents.FETCH_DID_FAIL].call(plugin, {\n          error,\n          event,\n          originalRequest: originalRequest.clone(),\n          request: pluginFilteredRequest.clone()\n        });\n      }\n\n      throw error;\n    }\n  };\n\n  const fetchWrapper = {\n    fetch: wrappedFetch\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  var _private = /*#__PURE__*/Object.freeze({\n    DBWrapper: DBWrapper,\n    deleteDatabase: deleteDatabase,\n    WorkboxError: WorkboxError,\n    assert: finalAssertExports,\n    cacheNames: cacheNames,\n    cacheWrapper: cacheWrapper,\n    fetchWrapper: fetchWrapper,\n    getFriendlyURL: getFriendlyURL,\n    logger: logger\n  });\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Claim any currently available clients once the service worker\n   * becomes active. This is normally used in conjunction with `skipWaiting()`.\n   *\n   * @alias workbox.core.clientsClaim\n   */\n\n  const clientsClaim = () => {\n    addEventListener('activate', () => clients.claim());\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Get the current cache names used by Workbox.\n   *\n   * `cacheNames.precache` is used for precached assets,\n   * `cacheNames.googleAnalytics` is used by `workbox-google-analytics` to\n   * store `analytics.js`, and `cacheNames.runtime` is used for everything else.\n   *\n   * @return {Object} An object with `precache`, `runtime`, and\n   *     `googleAnalytics` cache names.\n   *\n   * @alias workbox.core.cacheNames\n   */\n\n  const cacheNames$1 = {\n    get googleAnalytics() {\n      return cacheNames.getGoogleAnalyticsName();\n    },\n\n    get precache() {\n      return cacheNames.getPrecacheName();\n    },\n\n    get runtime() {\n      return cacheNames.getRuntimeName();\n    }\n\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Modifies the default cache names used by the Workbox packages.\n   * Cache names are generated as `<prefix>-<Cache Name>-<suffix>`.\n   *\n   * @param {Object} details\n   * @param {Object} [details.prefix] The string to add to the beginning of\n   *     the precache and runtime cache names.\n   * @param {Object} [details.suffix] The string to add to the end of\n   *     the precache and runtime cache names.\n   * @param {Object} [details.precache] The cache name to use for precache\n   *     caching.\n   * @param {Object} [details.runtime] The cache name to use for runtime caching.\n   * @param {Object} [details.googleAnalytics] The cache name to use for\n   *     `workbox-google-analytics` caching.\n   *\n   * @alias workbox.core.setCacheNameDetails\n   */\n\n  const setCacheNameDetails = details => {\n    {\n      Object.keys(details).forEach(key => {\n        finalAssertExports.isType(details[key], 'string', {\n          moduleName: 'workbox-core',\n          funcName: 'setCacheNameDetails',\n          paramName: `details.${key}`\n        });\n      });\n\n      if ('precache' in details && details.precache.length === 0) {\n        throw new WorkboxError('invalid-cache-name', {\n          cacheNameId: 'precache',\n          value: details.precache\n        });\n      }\n\n      if ('runtime' in details && details.runtime.length === 0) {\n        throw new WorkboxError('invalid-cache-name', {\n          cacheNameId: 'runtime',\n          value: details.runtime\n        });\n      }\n\n      if ('googleAnalytics' in details && details.googleAnalytics.length === 0) {\n        throw new WorkboxError('invalid-cache-name', {\n          cacheNameId: 'googleAnalytics',\n          value: details.googleAnalytics\n        });\n      }\n    }\n\n    cacheNames.updateDetails(details);\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Force a service worker to become active, instead of waiting. This is\n   * normally used in conjunction with `clientsClaim()`.\n   *\n   * @alias workbox.core.skipWaiting\n   */\n\n  const skipWaiting = () => {\n    // We need to explicitly call `self.skipWaiting()` here because we're\n    // shadowing `skipWaiting` with this local function.\n    addEventListener('install', () => self.skipWaiting());\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  try {\n    self.workbox.v = self.workbox.v || {};\n  } catch (errer) {} // NOOP\n\n  exports._private = _private;\n  exports.clientsClaim = clientsClaim;\n  exports.cacheNames = cacheNames$1;\n  exports.registerQuotaErrorCallback = registerQuotaErrorCallback;\n  exports.setCacheNameDetails = setCacheNameDetails;\n  exports.skipWaiting = skipWaiting;\n\n  return exports;\n\n}({}));\n//# sourceMappingURL=workbox-core.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-core.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.core=function(e){\"use strict\";try{self[\"workbox:core:4.1.1\"]&&_()}catch(e){}const t=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n};class n extends Error{constructor(e,n){super(t(e,n)),this.name=e,this.details=n}}const s=new Set;class r{constructor(e,t,{onupgradeneeded:n,onversionchange:s=this.t}={}){this.s=e,this.i=t,this.o=n,this.t=s,this.l=null}get db(){return this.l}async open(){if(!this.l)return this.l=await new Promise((e,t)=>{let n=!1;setTimeout(()=>{n=!0,t(new Error(\"The open request was blocked and timed out\"))},this.OPEN_TIMEOUT);const s=indexedDB.open(this.s,this.i);s.onerror=(()=>t(s.error)),s.onupgradeneeded=(e=>{n?(s.transaction.abort(),e.target.result.close()):this.o&&this.o(e)}),s.onsuccess=(({target:t})=>{const s=t.result;n?s.close():(s.onversionchange=this.t.bind(this),e(s))})}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,n){return await this.getAllMatching(e,{query:t,count:n})}async getAllKeys(e,t,n){return(await this.getAllMatching(e,{query:t,count:n,includeKeys:!0})).map(({key:e})=>e)}async getAllMatching(e,{index:t,query:n=null,direction:s=\"next\",count:r,includeKeys:a}={}){return await this.transaction([e],\"readonly\",(i,c)=>{const o=i.objectStore(e),l=t?o.index(t):o,u=[];l.openCursor(n,s).onsuccess=(({target:e})=>{const t=e.result;if(t){const{primaryKey:e,key:n,value:s}=t;u.push(a?{primaryKey:e,key:n,value:s}:s),r&&u.length>=r?c(u):t.continue()}else c(u)})})}async transaction(e,t,n){return await this.open(),await new Promise((s,r)=>{const a=this.l.transaction(e,t);a.onabort=(({target:e})=>r(e.error)),a.oncomplete=(()=>s()),n(a,e=>s(e))})}async u(e,t,n,...s){return await this.transaction([t],n,(n,r)=>{n.objectStore(t)[e](...s).onsuccess=(({target:e})=>{r(e.result)})})}t(){this.close()}close(){this.l&&(this.l.close(),this.l=null)}}r.prototype.OPEN_TIMEOUT=2e3;const a={readonly:[\"get\",\"count\",\"getKey\",\"getAll\",\"getAllKeys\"],readwrite:[\"add\",\"put\",\"clear\",\"delete\"]};for(const[e,t]of Object.entries(a))for(const n of t)n in IDBObjectStore.prototype&&(r.prototype[n]=async function(t,...s){return await this.u(n,t,e,...s)});const i={googleAnalytics:\"googleAnalytics\",precache:\"precache-v2\",prefix:\"workbox\",runtime:\"runtime\",suffix:self.registration.scope},c=e=>[i.prefix,e,i.suffix].filter(e=>e.length>0).join(\"-\"),o={updateDetails:e=>{Object.keys(i).forEach(t=>{void 0!==e[t]&&(i[t]=e[t])})},getGoogleAnalyticsName:e=>e||c(i.googleAnalytics),getPrecacheName:e=>e||c(i.precache),getRuntimeName:e=>e||c(i.runtime)},l=\"cacheDidUpdate\",u=\"cacheWillUpdate\",h=\"cachedResponseWillBeUsed\",w=\"fetchDidFail\",f=\"fetchDidSucceed\",p=\"requestWillFetch\",d=(e,t)=>e.filter(e=>t in e),g=e=>{const t=new URL(e,location);return t.origin===location.origin?t.pathname:t.href},y=async({cacheName:e,request:t,event:n,matchOptions:s,plugins:r=[]})=>{const a=await caches.open(e);let i=await a.match(t,s);for(const a of r)h in a&&(i=await a[h].call(a,{cacheName:e,request:t,event:n,matchOptions:s,cachedResponse:i}));return i},m=async({request:e,response:t,event:n,plugins:s})=>{let r=t,a=!1;for(let t of s)if(u in t&&(a=!0,!(r=await t[u].call(t,{request:e,response:r,event:n}))))break;return a||(r=200===r.status?r:null),r||null},v={put:async({cacheName:e,request:t,response:r,event:a,plugins:i=[],matchOptions:c}={})=>{if(!r)throw new n(\"cache-put-with-no-response\",{url:g(t.url)});let o=await m({request:t,response:r,event:a,plugins:i});if(!o)return;const u=await caches.open(e),h=d(i,l);let w=h.length>0?await y({cacheName:e,request:t,matchOptions:c}):null;try{await u.put(t,o)}catch(e){throw\"QuotaExceededError\"===e.name&&await async function(){for(const e of s)await e()}(),e}for(let n of h)await n[l].call(n,{cacheName:e,request:t,event:a,oldResponse:w,newResponse:o})},match:y},q={fetch:async({request:e,fetchOptions:t,event:s,plugins:r=[]})=>{if(s&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}\"string\"==typeof e&&(e=new Request(e));const a=d(r,w),i=a.length>0?e.clone():null;try{for(let t of r)p in t&&(e=await t[p].call(t,{request:e.clone(),event:s}))}catch(e){throw new n(\"plugin-error-request-will-fetch\",{thrownError:e})}let c=e.clone();try{let n;n=\"navigate\"===e.mode?await fetch(e):await fetch(e,t);for(const e of r)f in e&&(n=await e[f].call(e,{event:s,request:c,response:n}));return n}catch(e){for(const t of a)await t[w].call(t,{error:e,event:s,originalRequest:i.clone(),request:c.clone()});throw e}}};var b=Object.freeze({DBWrapper:r,deleteDatabase:async e=>{await new Promise((t,n)=>{const s=indexedDB.deleteDatabase(e);s.onerror=(({target:e})=>{n(e.error)}),s.onblocked=(()=>{n(new Error(\"Delete blocked\"))}),s.onsuccess=(()=>{t()})})},WorkboxError:n,assert:null,cacheNames:o,cacheWrapper:v,fetchWrapper:q,getFriendlyURL:g,logger:null});const x={get googleAnalytics(){return o.getGoogleAnalyticsName()},get precache(){return o.getPrecacheName()},get runtime(){return o.getRuntimeName()}};try{self.workbox.v=self.workbox.v||{}}catch(e){}return e._private=b,e.clientsClaim=(()=>{addEventListener(\"activate\",()=>clients.claim())}),e.cacheNames=x,e.registerQuotaErrorCallback=function(e){s.add(e)},e.setCacheNameDetails=(e=>{o.updateDetails(e)}),e.skipWaiting=(()=>{addEventListener(\"install\",()=>self.skipWaiting())}),e}({});\n//# sourceMappingURL=workbox-core.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-expiration.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.expiration = (function (exports, DBWrapper_mjs, deleteDatabase_mjs, WorkboxError_mjs, assert_mjs, logger_mjs, cacheNames_mjs, getFriendlyURL_mjs, registerQuotaErrorCallback_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:expiration:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const DB_NAME = 'workbox-expiration';\n  const OBJECT_STORE_NAME = 'cache-entries';\n\n  const normalizeURL = unNormalizedUrl => {\n    const url = new URL(unNormalizedUrl, location);\n    url.hash = '';\n    return url.href;\n  };\n  /**\n   * Returns the timestamp model.\n   *\n   * @private\n   */\n\n\n  class CacheTimestampsModel {\n    /**\n     *\n     * @param {string} cacheName\n     *\n     * @private\n     */\n    constructor(cacheName) {\n      this._cacheName = cacheName;\n      this._db = new DBWrapper_mjs.DBWrapper(DB_NAME, 1, {\n        onupgradeneeded: event => this._handleUpgrade(event)\n      });\n    }\n    /**\n     * Should perform an upgrade of indexedDB.\n     *\n     * @param {Event} event\n     *\n     * @private\n     */\n\n\n    _handleUpgrade(event) {\n      const db = event.target.result; // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we\n      // have to use the `id` keyPath here and create our own values (a\n      // concatenation of `url + cacheName`) instead of simply using\n      // `keyPath: ['url', 'cacheName']`, which is supported in other browsers.\n\n      const objStore = db.createObjectStore(OBJECT_STORE_NAME, {\n        keyPath: 'id'\n      }); // TODO(philipwalton): once we don't have to support EdgeHTML, we can\n      // create a single index with the keyPath `['cacheName', 'timestamp']`\n      // instead of doing both these indexes.\n\n      objStore.createIndex('cacheName', 'cacheName', {\n        unique: false\n      });\n      objStore.createIndex('timestamp', 'timestamp', {\n        unique: false\n      }); // Previous versions of `workbox-expiration` used `this._cacheName`\n      // as the IDBDatabase name.\n\n      deleteDatabase_mjs.deleteDatabase(this._cacheName);\n    }\n    /**\n     * @param {string} url\n     * @param {number} timestamp\n     *\n     * @private\n     */\n\n\n    async setTimestamp(url, timestamp) {\n      url = normalizeURL(url);\n      await this._db.put(OBJECT_STORE_NAME, {\n        url,\n        timestamp,\n        cacheName: this._cacheName,\n        // Creating an ID from the URL and cache name won't be necessary once\n        // Edge switches to Chromium and all browsers we support work with\n        // array keyPaths.\n        id: this._getId(url)\n      });\n    }\n    /**\n     * Returns the timestamp stored for a given URL.\n     *\n     * @param {string} url\n     * @return {number}\n     *\n     * @private\n     */\n\n\n    async getTimestamp(url) {\n      const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));\n      return entry.timestamp;\n    }\n    /**\n     * Iterates through all the entries in the object store (from newest to\n     * oldest) and removes entries once either `maxCount` is reached or the\n     * entry's timestamp is less than `minTimestamp`.\n     *\n     * @param {number} minTimestamp\n     * @param {number} maxCount\n     *\n     * @private\n     */\n\n\n    async expireEntries(minTimestamp, maxCount) {\n      return await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {\n        const store = txn.objectStore(OBJECT_STORE_NAME);\n        const entriesDeleted = [];\n        let entriesNotDeletedCount = 0;\n\n        store.index('timestamp').openCursor(null, 'prev').onsuccess = ({\n          target\n        }) => {\n          const cursor = target.result;\n\n          if (cursor) {\n            const result = cursor.value; // TODO(philipwalton): once we can use a multi-key index, we\n            // won't have to check `cacheName` here.\n\n            if (result.cacheName === this._cacheName) {\n              // Delete an entry if it's older than the max age or\n              // if we already have the max number allowed.\n              if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {\n                cursor.delete(); // We only need to return the URL, not the whole entry.\n\n                entriesDeleted.push(cursor.value.url);\n              } else {\n                entriesNotDeletedCount++;\n              }\n            }\n\n            cursor.continue();\n          } else {\n            done(entriesDeleted);\n          }\n        };\n      });\n    }\n    /**\n     * Takes a URL and returns an ID that will be unique in the object store.\n     *\n     * @param {string} url\n     * @return {string}\n     */\n\n\n    _getId(url) {\n      // Creating an ID from the URL and cache name won't be necessary once\n      // Edge switches to Chromium and all browsers we support work with\n      // array keyPaths.\n      return this._cacheName + '|' + normalizeURL(url);\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * The `CacheExpiration` class allows you define an expiration and / or\n   * limit on the number of responses stored in a\n   * [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).\n   *\n   * @memberof workbox.expiration\n   */\n\n  class CacheExpiration {\n    /**\n     * To construct a new CacheExpiration instance you must provide at least\n     * one of the `config` properties.\n     *\n     * @param {string} cacheName Name of the cache to apply restrictions to.\n     * @param {Object} config\n     * @param {number} [config.maxEntries] The maximum number of entries to cache.\n     * Entries used the least will be removed as the maximum is reached.\n     * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n     * it's treated as stale and removed.\n     */\n    constructor(cacheName, config = {}) {\n      {\n        assert_mjs.assert.isType(cacheName, 'string', {\n          moduleName: 'workbox-expiration',\n          className: 'CacheExpiration',\n          funcName: 'constructor',\n          paramName: 'cacheName'\n        });\n\n        if (!(config.maxEntries || config.maxAgeSeconds)) {\n          throw new WorkboxError_mjs.WorkboxError('max-entries-or-age-required', {\n            moduleName: 'workbox-expiration',\n            className: 'CacheExpiration',\n            funcName: 'constructor'\n          });\n        }\n\n        if (config.maxEntries) {\n          assert_mjs.assert.isType(config.maxEntries, 'number', {\n            moduleName: 'workbox-expiration',\n            className: 'CacheExpiration',\n            funcName: 'constructor',\n            paramName: 'config.maxEntries'\n          }); // TODO: Assert is positive\n        }\n\n        if (config.maxAgeSeconds) {\n          assert_mjs.assert.isType(config.maxAgeSeconds, 'number', {\n            moduleName: 'workbox-expiration',\n            className: 'CacheExpiration',\n            funcName: 'constructor',\n            paramName: 'config.maxAgeSeconds'\n          }); // TODO: Assert is positive\n        }\n      }\n\n      this._isRunning = false;\n      this._rerunRequested = false;\n      this._maxEntries = config.maxEntries;\n      this._maxAgeSeconds = config.maxAgeSeconds;\n      this._cacheName = cacheName;\n      this._timestampModel = new CacheTimestampsModel(cacheName);\n    }\n    /**\n     * Expires entries for the given cache and given criteria.\n     */\n\n\n    async expireEntries() {\n      if (this._isRunning) {\n        this._rerunRequested = true;\n        return;\n      }\n\n      this._isRunning = true;\n      const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : undefined;\n      const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries); // Delete URLs from the cache\n\n      const cache = await caches.open(this._cacheName);\n\n      for (const url of urlsExpired) {\n        await cache.delete(url);\n      }\n\n      {\n        if (urlsExpired.length > 0) {\n          logger_mjs.logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` + `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` + `'${this._cacheName}' cache.`);\n          logger_mjs.logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);\n          urlsExpired.forEach(url => logger_mjs.logger.log(`    ${url}`));\n          logger_mjs.logger.groupEnd();\n        } else {\n          logger_mjs.logger.debug(`Cache expiration ran and found no entries to remove.`);\n        }\n      }\n\n      this._isRunning = false;\n\n      if (this._rerunRequested) {\n        this._rerunRequested = false;\n        this.expireEntries();\n      }\n    }\n    /**\n     * Update the timestamp for the given URL. This ensures the when\n     * removing entries based on maximum entries, most recently used\n     * is accurate or when expiring, the timestamp is up-to-date.\n     *\n     * @param {string} url\n     */\n\n\n    async updateTimestamp(url) {\n      {\n        assert_mjs.assert.isType(url, 'string', {\n          moduleName: 'workbox-expiration',\n          className: 'CacheExpiration',\n          funcName: 'updateTimestamp',\n          paramName: 'url'\n        });\n      }\n\n      await this._timestampModel.setTimestamp(url, Date.now());\n    }\n    /**\n     * Can be used to check if a URL has expired or not before it's used.\n     *\n     * This requires a look up from IndexedDB, so can be slow.\n     *\n     * Note: This method will not remove the cached entry, call\n     * `expireEntries()` to remove indexedDB and Cache entries.\n     *\n     * @param {string} url\n     * @return {boolean}\n     */\n\n\n    async isURLExpired(url) {\n      {\n        if (!this._maxAgeSeconds) {\n          throw new WorkboxError_mjs.WorkboxError(`expired-test-without-max-age`, {\n            methodName: 'isURLExpired',\n            paramName: 'maxAgeSeconds'\n          });\n        }\n      }\n\n      const timestamp = await this._timestampModel.getTimestamp(url);\n      const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;\n      return timestamp < expireOlderThan;\n    }\n    /**\n     * Removes the IndexedDB object store used to keep track of cache expiration\n     * metadata.\n     */\n\n\n    async delete() {\n      // Make sure we don't attempt another rerun if we're called in the middle of\n      // a cache expiration.\n      this._rerunRequested = false;\n      await this._timestampModel.expireEntries(Infinity); // Expires all.\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * This plugin can be used in the Workbox APIs to regularly enforce a\n   * limit on the age and / or the number of cached requests.\n   *\n   * Whenever a cached request is used or updated, this plugin will look\n   * at the used Cache and remove any old or extra requests.\n   *\n   * When using `maxAgeSeconds`, requests may be used *once* after expiring\n   * because the expiration clean up will not have occurred until *after* the\n   * cached request has been used. If the request has a \"Date\" header, then\n   * a light weight expiration check is performed and the request will not be\n   * used immediately.\n   *\n   * When using `maxEntries`, the last request to be used will be the request\n   * that is removed from the Cache.\n   *\n   * @memberof workbox.expiration\n   */\n\n  class Plugin {\n    /**\n     * @param {Object} config\n     * @param {number} [config.maxEntries] The maximum number of entries to cache.\n     * Entries used the least will be removed as the maximum is reached.\n     * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n     * it's treated as stale and removed.\n     * @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to\n     * automatic deletion if the available storage quota has been exceeded.\n     */\n    constructor(config = {}) {\n      {\n        if (!(config.maxEntries || config.maxAgeSeconds)) {\n          throw new WorkboxError_mjs.WorkboxError('max-entries-or-age-required', {\n            moduleName: 'workbox-expiration',\n            className: 'Plugin',\n            funcName: 'constructor'\n          });\n        }\n\n        if (config.maxEntries) {\n          assert_mjs.assert.isType(config.maxEntries, 'number', {\n            moduleName: 'workbox-expiration',\n            className: 'Plugin',\n            funcName: 'constructor',\n            paramName: 'config.maxEntries'\n          });\n        }\n\n        if (config.maxAgeSeconds) {\n          assert_mjs.assert.isType(config.maxAgeSeconds, 'number', {\n            moduleName: 'workbox-expiration',\n            className: 'Plugin',\n            funcName: 'constructor',\n            paramName: 'config.maxAgeSeconds'\n          });\n        }\n      }\n\n      this._config = config;\n      this._maxAgeSeconds = config.maxAgeSeconds;\n      this._cacheExpirations = new Map();\n\n      if (config.purgeOnQuotaError) {\n        registerQuotaErrorCallback_mjs.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());\n      }\n    }\n    /**\n     * A simple helper method to return a CacheExpiration instance for a given\n     * cache name.\n     *\n     * @param {string} cacheName\n     * @return {CacheExpiration}\n     *\n     * @private\n     */\n\n\n    _getCacheExpiration(cacheName) {\n      if (cacheName === cacheNames_mjs.cacheNames.getRuntimeName()) {\n        throw new WorkboxError_mjs.WorkboxError('expire-custom-caches-only');\n      }\n\n      let cacheExpiration = this._cacheExpirations.get(cacheName);\n\n      if (!cacheExpiration) {\n        cacheExpiration = new CacheExpiration(cacheName, this._config);\n\n        this._cacheExpirations.set(cacheName, cacheExpiration);\n      }\n\n      return cacheExpiration;\n    }\n    /**\n     * A \"lifecycle\" callback that will be triggered automatically by the\n     * `workbox.strategies` handlers when a `Response` is about to be returned\n     * from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to\n     * the handler. It allows the `Response` to be inspected for freshness and\n     * prevents it from being used if the `Response`'s `Date` header value is\n     * older than the configured `maxAgeSeconds`.\n     *\n     * @param {Object} options\n     * @param {string} options.cacheName Name of the cache the response is in.\n     * @param {Response} options.cachedResponse The `Response` object that's been\n     *     read from a cache and whose freshness should be checked.\n     * @return {Response} Either the `cachedResponse`, if it's\n     *     fresh, or `null` if the `Response` is older than `maxAgeSeconds`.\n     *\n     * @private\n     */\n\n\n    cachedResponseWillBeUsed({\n      event,\n      request,\n      cacheName,\n      cachedResponse\n    }) {\n      if (!cachedResponse) {\n        return null;\n      }\n\n      let isFresh = this._isResponseDateFresh(cachedResponse); // Expire entries to ensure that even if the expiration date has\n      // expired, it'll only be used once.\n\n\n      const cacheExpiration = this._getCacheExpiration(cacheName);\n\n      cacheExpiration.expireEntries(); // Update the metadata for the request URL to the current timestamp,\n      // but don't `await` it as we don't want to block the response.\n\n      const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);\n\n      if (event) {\n        try {\n          event.waitUntil(updateTimestampDone);\n        } catch (error) {\n          {\n            logger_mjs.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache entry for '${getFriendlyURL_mjs.getFriendlyURL(event.request.url)}'.`);\n          }\n        }\n      }\n\n      return isFresh ? cachedResponse : null;\n    }\n    /**\n     * @param {Response} cachedResponse\n     * @return {boolean}\n     *\n     * @private\n     */\n\n\n    _isResponseDateFresh(cachedResponse) {\n      if (!this._maxAgeSeconds) {\n        // We aren't expiring by age, so return true, it's fresh\n        return true;\n      } // Check if the 'date' header will suffice a quick expiration check.\n      // See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for\n      // discussion.\n\n\n      const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);\n\n      if (dateHeaderTimestamp === null) {\n        // Unable to parse date, so assume it's fresh.\n        return true;\n      } // If we have a valid headerTime, then our response is fresh iff the\n      // headerTime plus maxAgeSeconds is greater than the current time.\n\n\n      const now = Date.now();\n      return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;\n    }\n    /**\n     * This method will extract the data header and parse it into a useful\n     * value.\n     *\n     * @param {Response} cachedResponse\n     * @return {number}\n     *\n     * @private\n     */\n\n\n    _getDateHeaderTimestamp(cachedResponse) {\n      if (!cachedResponse.headers.has('date')) {\n        return null;\n      }\n\n      const dateHeader = cachedResponse.headers.get('date');\n      const parsedDate = new Date(dateHeader);\n      const headerTime = parsedDate.getTime(); // If the Date header was invalid for some reason, parsedDate.getTime()\n      // will return NaN.\n\n      if (isNaN(headerTime)) {\n        return null;\n      }\n\n      return headerTime;\n    }\n    /**\n     * A \"lifecycle\" callback that will be triggered automatically by the\n     * `workbox.strategies` handlers when an entry is added to a cache.\n     *\n     * @param {Object} options\n     * @param {string} options.cacheName Name of the cache that was updated.\n     * @param {string} options.request The Request for the cached entry.\n     *\n     * @private\n     */\n\n\n    async cacheDidUpdate({\n      cacheName,\n      request\n    }) {\n      {\n        assert_mjs.assert.isType(cacheName, 'string', {\n          moduleName: 'workbox-expiration',\n          className: 'Plugin',\n          funcName: 'cacheDidUpdate',\n          paramName: 'cacheName'\n        });\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-expiration',\n          className: 'Plugin',\n          funcName: 'cacheDidUpdate',\n          paramName: 'request'\n        });\n      }\n\n      const cacheExpiration = this._getCacheExpiration(cacheName);\n\n      await cacheExpiration.updateTimestamp(request.url);\n      await cacheExpiration.expireEntries();\n    }\n    /**\n     * This is a helper method that performs two operations:\n     *\n     * - Deletes *all* the underlying Cache instances associated with this plugin\n     * instance, by calling caches.delete() on your behalf.\n     * - Deletes the metadata from IndexedDB used to keep track of expiration\n     * details for each Cache instance.\n     *\n     * When using cache expiration, calling this method is preferable to calling\n     * `caches.delete()` directly, since this will ensure that the IndexedDB\n     * metadata is also cleanly removed and open IndexedDB instances are deleted.\n     *\n     * Note that if you're *not* using cache expiration for a given cache, calling\n     * `caches.delete()` and passing in the cache's name should be sufficient.\n     * There is no Workbox-specific method needed for cleanup in that case.\n     */\n\n\n    async deleteCacheAndMetadata() {\n      // Do this one at a time instead of all at once via `Promise.all()` to\n      // reduce the chance of inconsistency if a promise rejects.\n      for (const [cacheName, cacheExpiration] of this._cacheExpirations) {\n        await caches.delete(cacheName);\n        await cacheExpiration.delete();\n      } // Reset this._cacheExpirations to its initial state.\n\n\n      this._cacheExpirations = new Map();\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.CacheExpiration = CacheExpiration;\n  exports.Plugin = Plugin;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core));\n//# sourceMappingURL=workbox-expiration.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-expiration.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.expiration=function(t,e,s,i,a,h){\"use strict\";try{self[\"workbox:expiration:4.1.1\"]&&_()}catch(t){}const n=\"workbox-expiration\",c=\"cache-entries\",r=t=>{const e=new URL(t,location);return e.hash=\"\",e.href};class o{constructor(t){this.t=t,this.s=new e.DBWrapper(n,1,{onupgradeneeded:t=>this.i(t)})}i(t){const e=t.target.result.createObjectStore(c,{keyPath:\"id\"});e.createIndex(\"cacheName\",\"cacheName\",{unique:!1}),e.createIndex(\"timestamp\",\"timestamp\",{unique:!1}),s.deleteDatabase(this.t)}async setTimestamp(t,e){t=r(t),await this.s.put(c,{url:t,timestamp:e,cacheName:this.t,id:this.h(t)})}async getTimestamp(t){return(await this.s.get(c,this.h(t))).timestamp}async expireEntries(t,e){return await this.s.transaction(c,\"readwrite\",(s,i)=>{const a=s.objectStore(c),h=[];let n=0;a.index(\"timestamp\").openCursor(null,\"prev\").onsuccess=(({target:s})=>{const a=s.result;if(a){const s=a.value;s.cacheName===this.t&&(t&&s.timestamp<t||e&&n>=e?(a.delete(),h.push(a.value.url)):n++),a.continue()}else i(h)})})}h(t){return this.t+\"|\"+r(t)}}class u{constructor(t,e={}){this.o=!1,this.u=!1,this.l=e.maxEntries,this.p=e.maxAgeSeconds,this.t=t,this.m=new o(t)}async expireEntries(){if(this.o)return void(this.u=!0);this.o=!0;const t=this.p?Date.now()-1e3*this.p:void 0,e=await this.m.expireEntries(t,this.l),s=await caches.open(this.t);for(const t of e)await s.delete(t);this.o=!1,this.u&&(this.u=!1,this.expireEntries())}async updateTimestamp(t){await this.m.setTimestamp(t,Date.now())}async isURLExpired(t){return await this.m.getTimestamp(t)<Date.now()-1e3*this.p}async delete(){this.u=!1,await this.m.expireEntries(1/0)}}return t.CacheExpiration=u,t.Plugin=class{constructor(t={}){this.D=t,this.p=t.maxAgeSeconds,this.g=new Map,t.purgeOnQuotaError&&h.registerQuotaErrorCallback(()=>this.deleteCacheAndMetadata())}k(t){if(t===a.cacheNames.getRuntimeName())throw new i.WorkboxError(\"expire-custom-caches-only\");let e=this.g.get(t);return e||(e=new u(t,this.D),this.g.set(t,e)),e}cachedResponseWillBeUsed({event:t,request:e,cacheName:s,cachedResponse:i}){if(!i)return null;let a=this.N(i);const h=this.k(s);h.expireEntries();const n=h.updateTimestamp(e.url);if(t)try{t.waitUntil(n)}catch(t){}return a?i:null}N(t){if(!this.p)return!0;const e=this._(t);return null===e||e>=Date.now()-1e3*this.p}_(t){if(!t.headers.has(\"date\"))return null;const e=t.headers.get(\"date\"),s=new Date(e).getTime();return isNaN(s)?null:s}async cacheDidUpdate({cacheName:t,request:e}){const s=this.k(t);await s.updateTimestamp(e.url),await s.expireEntries()}async deleteCacheAndMetadata(){for(const[t,e]of this.g)await caches.delete(t),await e.delete();this.g=new Map}},t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core);\n//# sourceMappingURL=workbox-expiration.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-navigation-preload.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.navigationPreload = (function (exports, logger_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:navigation-preload:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * @return {boolean} Whether or not the current browser supports enabling\n   * navigation preload.\n   *\n   * @memberof workbox.navigationPreload\n   */\n\n  function isSupported() {\n    return Boolean(self.registration && self.registration.navigationPreload);\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * If the browser supports Navigation Preload, then this will disable it.\n   *\n   * @memberof workbox.navigationPreload\n   */\n\n  function disable() {\n    if (isSupported()) {\n      self.addEventListener('activate', event => {\n        event.waitUntil(self.registration.navigationPreload.disable().then(() => {\n          {\n            logger_mjs.logger.log(`Navigation preload is disabled.`);\n          }\n        }));\n      });\n    } else {\n      {\n        logger_mjs.logger.log(`Navigation preload is not supported in this browser.`);\n      }\n    }\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * If the browser supports Navigation Preload, then this will enable it.\n   *\n   * @param {string} [headerValue] Optionally, allows developers to\n   * [override](https://developers.google.com/web/updates/2017/02/navigation-preload#changing_the_header)\n   * the value of the `Service-Worker-Navigation-Preload` header which will be\n   * sent to the server when making the navigation request.\n   *\n   * @memberof workbox.navigationPreload\n   */\n\n  function enable(headerValue) {\n    if (isSupported()) {\n      self.addEventListener('activate', event => {\n        event.waitUntil(self.registration.navigationPreload.enable().then(() => {\n          // Defaults to Service-Worker-Navigation-Preload: true if not set.\n          if (headerValue) {\n            self.registration.navigationPreload.setHeaderValue(headerValue);\n          }\n\n          {\n            logger_mjs.logger.log(`Navigation preload is enabled.`);\n          }\n        }));\n      });\n    } else {\n      {\n        logger_mjs.logger.log(`Navigation preload is not supported in this browser.`);\n      }\n    }\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.disable = disable;\n  exports.enable = enable;\n  exports.isSupported = isSupported;\n\n  return exports;\n\n}({}, workbox.core._private));\n//# sourceMappingURL=workbox-navigation-preload.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-navigation-preload.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.navigationPreload=function(t){\"use strict\";try{self[\"workbox:navigation-preload:4.1.1\"]&&_()}catch(t){}function e(){return Boolean(self.registration&&self.registration.navigationPreload)}return t.disable=function(){e()&&self.addEventListener(\"activate\",t=>{t.waitUntil(self.registration.navigationPreload.disable().then(()=>{}))})},t.enable=function(t){e()&&self.addEventListener(\"activate\",e=>{e.waitUntil(self.registration.navigationPreload.enable().then(()=>{t&&self.registration.navigationPreload.setHeaderValue(t)}))})},t.isSupported=e,t}({});\n//# sourceMappingURL=workbox-navigation-preload.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-offline-ga.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.googleAnalytics = (function (exports, Plugin_mjs, cacheNames_mjs, getFriendlyURL_mjs, logger_mjs, Route_mjs, Router_mjs, NetworkFirst_mjs, NetworkOnly_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:google-analytics:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const QUEUE_NAME = 'workbox-google-analytics';\n  const MAX_RETENTION_TIME = 60 * 48; // Two days in minutes\n\n  const GOOGLE_ANALYTICS_HOST = 'www.google-analytics.com';\n  const GTM_HOST = 'www.googletagmanager.com';\n  const ANALYTICS_JS_PATH = '/analytics.js';\n  const GTAG_JS_PATH = '/gtag/js';\n  const GTM_JS_PATH = '/gtm.js';\n  // endpoints. Most of the time the default path (/collect) is used, but\n  // occasionally an experimental endpoint is used when testing new features,\n  // (e.g. /r/collect or /j/collect)\n\n  const COLLECT_PATHS_REGEX = /^\\/(\\w+\\/)?collect/;\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Creates the requestWillDequeue callback to be used with the background\n   * sync queue plugin. The callback takes the failed request and adds the\n   * `qt` param based on the current time, as well as applies any other\n   * user-defined hit modifications.\n   *\n   * @param {Object} config See workbox.googleAnalytics.initialize.\n   * @return {Function} The requestWillDequeu callback function.\n   *\n   * @private\n   */\n\n  const createOnSyncCallback = config => {\n    return async ({\n      queue\n    }) => {\n      let entry;\n\n      while (entry = await queue.shiftRequest()) {\n        const {\n          request,\n          timestamp\n        } = entry;\n        const url = new URL(request.url);\n\n        try {\n          // Measurement protocol requests can set their payload parameters in\n          // either the URL query string (for GET requests) or the POST body.\n          const params = request.method === 'POST' ? new URLSearchParams((await request.clone().text())) : url.searchParams; // Calculate the qt param, accounting for the fact that an existing\n          // qt param may be present and should be updated rather than replaced.\n\n          const originalHitTime = timestamp - (Number(params.get('qt')) || 0);\n          const queueTime = Date.now() - originalHitTime; // Set the qt param prior to applying hitFilter or parameterOverrides.\n\n          params.set('qt', queueTime); // Apply `paramterOverrideds`, if set.\n\n          if (config.parameterOverrides) {\n            for (const param of Object.keys(config.parameterOverrides)) {\n              const value = config.parameterOverrides[param];\n              params.set(param, value);\n            }\n          } // Apply `hitFilter`, if set.\n\n\n          if (typeof config.hitFilter === 'function') {\n            config.hitFilter.call(null, params);\n          } // Retry the fetch. Ignore URL search params from the URL as they're\n          // now in the post body.\n\n\n          await fetch(new Request(url.origin + url.pathname, {\n            body: params.toString(),\n            method: 'POST',\n            mode: 'cors',\n            credentials: 'omit',\n            headers: {\n              'Content-Type': 'text/plain'\n            }\n          }));\n\n          {\n            logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(url.href)}'` + `has been replayed`);\n          }\n        } catch (err) {\n          await queue.unshiftRequest(entry);\n\n          {\n            logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(url.href)}'` + `failed to replay, putting it back in the queue.`);\n          }\n\n          throw err;\n        }\n      }\n\n      {\n        logger_mjs.logger.log(`All Google Analytics request successfully replayed; ` + `the queue is now empty!`);\n      }\n    };\n  };\n  /**\n   * Creates GET and POST routes to catch failed Measurement Protocol hits.\n   *\n   * @param {Plugin} queuePlugin\n   * @return {Array<Route>} The created routes.\n   *\n   * @private\n   */\n\n\n  const createCollectRoutes = queuePlugin => {\n    const match = ({\n      url\n    }) => url.hostname === GOOGLE_ANALYTICS_HOST && COLLECT_PATHS_REGEX.test(url.pathname);\n\n    const handler = new NetworkOnly_mjs.NetworkOnly({\n      plugins: [queuePlugin]\n    });\n    return [new Route_mjs.Route(match, handler, 'GET'), new Route_mjs.Route(match, handler, 'POST')];\n  };\n  /**\n   * Creates a route with a network first strategy for the analytics.js script.\n   *\n   * @param {string} cacheName\n   * @return {Route} The created route.\n   *\n   * @private\n   */\n\n\n  const createAnalyticsJsRoute = cacheName => {\n    const match = ({\n      url\n    }) => url.hostname === GOOGLE_ANALYTICS_HOST && url.pathname === ANALYTICS_JS_PATH;\n\n    const handler = new NetworkFirst_mjs.NetworkFirst({\n      cacheName\n    });\n    return new Route_mjs.Route(match, handler, 'GET');\n  };\n  /**\n   * Creates a route with a network first strategy for the gtag.js script.\n   *\n   * @param {string} cacheName\n   * @return {Route} The created route.\n   *\n   * @private\n   */\n\n\n  const createGtagJsRoute = cacheName => {\n    const match = ({\n      url\n    }) => url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;\n\n    const handler = new NetworkFirst_mjs.NetworkFirst({\n      cacheName\n    });\n    return new Route_mjs.Route(match, handler, 'GET');\n  };\n  /**\n   * Creates a route with a network first strategy for the gtm.js script.\n   *\n   * @param {string} cacheName\n   * @return {Route} The created route.\n   *\n   * @private\n   */\n\n\n  const createGtmJsRoute = cacheName => {\n    const match = ({\n      url\n    }) => url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;\n\n    const handler = new NetworkFirst_mjs.NetworkFirst({\n      cacheName\n    });\n    return new Route_mjs.Route(match, handler, 'GET');\n  };\n  /**\n   * @param {Object=} [options]\n   * @param {Object} [options.cacheName] The cache name to store and retrieve\n   *     analytics.js. Defaults to the cache names provided by `workbox-core`.\n   * @param {Object} [options.parameterOverrides]\n   *     [Measurement Protocol parameters](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters),\n   *     expressed as key/value pairs, to be added to replayed Google Analytics\n   *     requests. This can be used to, e.g., set a custom dimension indicating\n   *     that the request was replayed.\n   * @param {Function} [options.hitFilter] A function that allows you to modify\n   *     the hit parameters prior to replaying\n   *     the hit. The function is invoked with the original hit's URLSearchParams\n   *     object as its only argument.\n   *\n   * @memberof workbox.googleAnalytics\n   */\n\n\n  const initialize = (options = {}) => {\n    const cacheName = cacheNames_mjs.cacheNames.getGoogleAnalyticsName(options.cacheName);\n    const queuePlugin = new Plugin_mjs.Plugin(QUEUE_NAME, {\n      maxRetentionTime: MAX_RETENTION_TIME,\n      onSync: createOnSyncCallback(options)\n    });\n    const routes = [createGtmJsRoute(cacheName), createAnalyticsJsRoute(cacheName), createGtagJsRoute(cacheName), ...createCollectRoutes(queuePlugin)];\n    const router = new Router_mjs.Router();\n\n    for (const route of routes) {\n      router.registerRoute(route);\n    }\n\n    router.addFetchListener();\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.initialize = initialize;\n\n  return exports;\n\n}({}, workbox.backgroundSync, workbox.core._private, workbox.core._private, workbox.core._private, workbox.routing, workbox.routing, workbox.strategies, workbox.strategies));\n//# sourceMappingURL=workbox-offline-ga.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-offline-ga.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.googleAnalytics=function(e,t,o,n,a,c,w){\"use strict\";try{self[\"workbox:google-analytics:4.1.1\"]&&_()}catch(e){}const r=/^\\/(\\w+\\/)?collect/,s=e=>async({queue:t})=>{let o;for(;o=await t.shiftRequest();){const{request:n,timestamp:a}=o,c=new URL(n.url);try{const w=\"POST\"===n.method?new URLSearchParams(await n.clone().text()):c.searchParams,r=a-(Number(w.get(\"qt\"))||0),s=Date.now()-r;if(w.set(\"qt\",s),e.parameterOverrides)for(const t of Object.keys(e.parameterOverrides)){const o=e.parameterOverrides[t];w.set(t,o)}\"function\"==typeof e.hitFilter&&e.hitFilter.call(null,w),await fetch(new Request(c.origin+c.pathname,{body:w.toString(),method:\"POST\",mode:\"cors\",credentials:\"omit\",headers:{\"Content-Type\":\"text/plain\"}}))}catch(e){throw await t.unshiftRequest(o),e}}},i=e=>{const t=({url:e})=>\"www.google-analytics.com\"===e.hostname&&r.test(e.pathname),o=new w.NetworkOnly({plugins:[e]});return[new n.Route(t,o,\"GET\"),new n.Route(t,o,\"POST\")]},l=e=>{const t=new c.NetworkFirst({cacheName:e});return new n.Route(({url:e})=>\"www.google-analytics.com\"===e.hostname&&\"/analytics.js\"===e.pathname,t,\"GET\")},m=e=>{const t=new c.NetworkFirst({cacheName:e});return new n.Route(({url:e})=>\"www.googletagmanager.com\"===e.hostname&&\"/gtag/js\"===e.pathname,t,\"GET\")},u=e=>{const t=new c.NetworkFirst({cacheName:e});return new n.Route(({url:e})=>\"www.googletagmanager.com\"===e.hostname&&\"/gtm.js\"===e.pathname,t,\"GET\")};return e.initialize=((e={})=>{const n=o.cacheNames.getGoogleAnalyticsName(e.cacheName),c=new t.Plugin(\"workbox-google-analytics\",{maxRetentionTime:2880,onSync:s(e)}),w=[u(n),l(n),m(n),...i(c)],r=new a.Router;for(const e of w)r.registerRoute(e);r.addFetchListener()}),e}({},workbox.backgroundSync,workbox.core._private,workbox.routing,workbox.routing,workbox.strategies,workbox.strategies);\n//# sourceMappingURL=workbox-offline-ga.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-precaching.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.precaching = (function (exports, assert_mjs, cacheNames_mjs, getFriendlyURL_mjs, logger_mjs, cacheWrapper_mjs, fetchWrapper_mjs, WorkboxError_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:precaching:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const plugins = [];\n  const precachePlugins = {\n    /*\n     * @return {Array}\n     * @private\n     */\n    get() {\n      return plugins;\n    },\n\n    /*\n     * @param {Array} newPlugins\n     * @private\n     */\n    add(newPlugins) {\n      plugins.push(...newPlugins);\n    }\n\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Adds plugins to precaching.\n   *\n   * @param {Array<Object>} newPlugins\n   *\n   * @alias workbox.precaching.addPlugins\n   */\n\n  const addPlugins = newPlugins => {\n    precachePlugins.add(newPlugins);\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * @param {Response} response\n   * @return {Response}\n   *\n   * @private\n   * @memberof module:workbox-precaching\n   */\n\n  async function cleanRedirect(response) {\n    const clonedResponse = response.clone(); // Not all browsers support the Response.body stream, so fall back\n    // to reading the entire body into memory as a blob.\n\n    const bodyPromise = 'body' in clonedResponse ? Promise.resolve(clonedResponse.body) : clonedResponse.blob();\n    const body = await bodyPromise; // new Response() is happy when passed either a stream or a Blob.\n\n    return new Response(body, {\n      headers: clonedResponse.headers,\n      status: clonedResponse.status,\n      statusText: clonedResponse.statusText\n    });\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  const REVISION_SEARCH_PARAM = '__WB_REVISION__';\n  /**\n   * Converts a manifest entry into a versioned URL suitable for precaching.\n   *\n   * @param {Object} entry\n   * @return {string} A URL with versioning info.\n   *\n   * @private\n   * @memberof module:workbox-precaching\n   */\n\n  function createCacheKey(entry) {\n    if (!entry) {\n      throw new WorkboxError_mjs.WorkboxError('add-to-cache-list-unexpected-type', {\n        entry\n      });\n    } // If a precache manifest entry is a string, it's assumed to be a versioned\n    // URL, like '/app.abcd1234.js'. Return as-is.\n\n\n    if (typeof entry === 'string') {\n      const urlObject = new URL(entry, location);\n      return {\n        cacheKey: urlObject.href,\n        url: urlObject.href\n      };\n    }\n\n    const {\n      revision,\n      url\n    } = entry;\n\n    if (!url) {\n      throw new WorkboxError_mjs.WorkboxError('add-to-cache-list-unexpected-type', {\n        entry\n      });\n    } // If there's just a URL and no revision, then it's also assumed to be a\n    // versioned URL.\n\n\n    if (!revision) {\n      const urlObject = new URL(url, location);\n      return {\n        cacheKey: urlObject.href,\n        url: urlObject.href\n      };\n    } // Otherwise, construct a properly versioned URL using the custom Workbox\n    // search parameter along with the revision info.\n\n\n    const originalURL = new URL(url, location);\n    const cacheKeyURL = new URL(url, location);\n    cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);\n    return {\n      cacheKey: cacheKeyURL.href,\n      url: originalURL.href\n    };\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  const logGroup = (groupTitle, deletedURLs) => {\n    logger_mjs.logger.groupCollapsed(groupTitle);\n\n    for (const url of deletedURLs) {\n      logger_mjs.logger.log(url);\n    }\n\n    logger_mjs.logger.groupEnd();\n  };\n  /**\n   * @param {Array<string>} deletedURLs\n   *\n   * @private\n   * @memberof module:workbox-precaching\n   */\n\n\n  function printCleanupDetails(deletedURLs) {\n    const deletionCount = deletedURLs.length;\n\n    if (deletionCount > 0) {\n      logger_mjs.logger.groupCollapsed(`During precaching cleanup, ` + `${deletionCount} cached ` + `request${deletionCount === 1 ? ' was' : 's were'} deleted.`);\n      logGroup('Deleted Cache Requests', deletedURLs);\n      logger_mjs.logger.groupEnd();\n    }\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * @param {string} groupTitle\n   * @param {Array<string>} urls\n   *\n   * @private\n   */\n\n  function _nestedGroup(groupTitle, urls) {\n    if (urls.length === 0) {\n      return;\n    }\n\n    logger_mjs.logger.groupCollapsed(groupTitle);\n\n    for (const url of urls) {\n      logger_mjs.logger.log(url);\n    }\n\n    logger_mjs.logger.groupEnd();\n  }\n  /**\n   * @param {Array<string>} urlsToPrecache\n   * @param {Array<string>} urlsAlreadyPrecached\n   *\n   * @private\n   * @memberof module:workbox-precaching\n   */\n\n\n  function printInstallDetails(urlsToPrecache, urlsAlreadyPrecached) {\n    const precachedCount = urlsToPrecache.length;\n    const alreadyPrecachedCount = urlsAlreadyPrecached.length;\n\n    if (precachedCount || alreadyPrecachedCount) {\n      let message = `Precaching ${precachedCount} file${precachedCount === 1 ? '' : 's'}.`;\n\n      if (alreadyPrecachedCount > 0) {\n        message += ` ${alreadyPrecachedCount} ` + `file${alreadyPrecachedCount === 1 ? ' is' : 's are'} already cached.`;\n      }\n\n      logger_mjs.logger.groupCollapsed(message);\n\n      _nestedGroup(`View newly precached URLs.`, urlsToPrecache);\n\n      _nestedGroup(`View previously precached URLs.`, urlsAlreadyPrecached);\n\n      logger_mjs.logger.groupEnd();\n    }\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Performs efficient precaching of assets.\n   *\n   * @memberof module:workbox-precaching\n   */\n\n  class PrecacheController {\n    /**\n     * Create a new PrecacheController.\n     *\n     * @param {string} [cacheName] An optional name for the cache, to override\n     * the default precache name.\n     */\n    constructor(cacheName) {\n      this._cacheName = cacheNames_mjs.cacheNames.getPrecacheName(cacheName);\n      this._urlsToCacheKeys = new Map();\n    }\n    /**\n     * This method will add items to the precache list, removing duplicates\n     * and ensuring the information is valid.\n     *\n     * @param {\n     * Array<module:workbox-precaching.PrecacheController.PrecacheEntry|string>\n     * } entries Array of entries to precache.\n     */\n\n\n    addToCacheList(entries) {\n      {\n        assert_mjs.assert.isArray(entries, {\n          moduleName: 'workbox-precaching',\n          className: 'PrecacheController',\n          funcName: 'addToCacheList',\n          paramName: 'entries'\n        });\n      }\n\n      for (const entry of entries) {\n        const {\n          cacheKey,\n          url\n        } = createCacheKey(entry);\n\n        if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) {\n          throw new WorkboxError_mjs.WorkboxError('add-to-cache-list-conflicting-entries', {\n            firstEntry: this._urlsToCacheKeys.get(url),\n            secondEntry: cacheKey\n          });\n        }\n\n        this._urlsToCacheKeys.set(url, cacheKey);\n      }\n    }\n    /**\n     * Precaches new and updated assets. Call this method from the service worker\n     * install event.\n     *\n     * @param {Object} options\n     * @param {Event} [options.event] The install event (if needed).\n     * @param {Array<Object>} [options.plugins] Plugins to be used for fetching\n     * and caching during install.\n     * @return {Promise<workbox.precaching.InstallResult>}\n     */\n\n\n    async install({\n      event,\n      plugins\n    } = {}) {\n      {\n        if (plugins) {\n          assert_mjs.assert.isArray(plugins, {\n            moduleName: 'workbox-precaching',\n            className: 'PrecacheController',\n            funcName: 'install',\n            paramName: 'plugins'\n          });\n        }\n      }\n\n      const urlsToPrecache = [];\n      const urlsAlreadyPrecached = [];\n      const cache = await caches.open(this._cacheName);\n      const alreadyCachedRequests = await cache.keys();\n      const alreadyCachedURLs = new Set(alreadyCachedRequests.map(request => request.url));\n\n      for (const cacheKey of this._urlsToCacheKeys.values()) {\n        if (alreadyCachedURLs.has(cacheKey)) {\n          urlsAlreadyPrecached.push(cacheKey);\n        } else {\n          urlsToPrecache.push(cacheKey);\n        }\n      }\n\n      const precacheRequests = urlsToPrecache.map(url => {\n        return this._addURLToCache({\n          event,\n          plugins,\n          url\n        });\n      });\n      await Promise.all(precacheRequests);\n\n      {\n        printInstallDetails(urlsToPrecache, urlsAlreadyPrecached);\n      }\n\n      return {\n        updatedURLs: urlsToPrecache,\n        notUpdatedURLs: urlsAlreadyPrecached\n      };\n    }\n    /**\n     * Deletes assets that are no longer present in the current precache manifest.\n     * Call this method from the service worker activate event.\n     *\n     * @return {Promise<workbox.precaching.CleanupResult>}\n     */\n\n\n    async activate() {\n      const cache = await caches.open(this._cacheName);\n      const currentlyCachedRequests = await cache.keys();\n      const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());\n      const deletedURLs = [];\n\n      for (const request of currentlyCachedRequests) {\n        if (!expectedCacheKeys.has(request.url)) {\n          await cache.delete(request);\n          deletedURLs.push(request.url);\n        }\n      }\n\n      {\n        printCleanupDetails(deletedURLs);\n      }\n\n      return {\n        deletedURLs\n      };\n    }\n    /**\n     * Requests the entry and saves it to the cache if the response is valid.\n     * By default, any response with a status code of less than 400 (including\n     * opaque responses) is considered valid.\n     *\n     * If you need to use custom criteria to determine what's valid and what\n     * isn't, then pass in an item in `options.plugins` that implements the\n     * `cacheWillUpdate()` lifecycle event.\n     *\n     * @private\n     * @param {Object} options\n     * @param {string} options.url The URL to fetch and cache.\n     * @param {Event} [options.event] The install event (if passed).\n     * @param {Array<Object>} [options.plugins] An array of plugins to apply to\n     * fetch and caching.\n     */\n\n\n    async _addURLToCache({\n      url,\n      event,\n      plugins\n    }) {\n      const request = new Request(url, {\n        credentials: 'same-origin'\n      });\n      let response = await fetchWrapper_mjs.fetchWrapper.fetch({\n        event,\n        plugins,\n        request\n      }); // Allow developers to override the default logic about what is and isn't\n      // valid by passing in a plugin implementing cacheWillUpdate(), e.g.\n      // a workbox.cacheableResponse.Plugin instance.\n\n      let cacheWillUpdateCallback;\n\n      for (const plugin of plugins || []) {\n        if ('cacheWillUpdate' in plugin) {\n          cacheWillUpdateCallback = plugin.cacheWillUpdate.bind(plugin);\n        }\n      }\n\n      const isValidResponse = cacheWillUpdateCallback ? // Use a callback if provided. It returns a truthy value if valid.\n      cacheWillUpdateCallback({\n        event,\n        request,\n        response\n      }) : // Otherwise, default to considering any response status under 400 valid.\n      // This includes, by default, considering opaque responses valid.\n      response.status < 400; // Consider this a failure, leading to the `install` handler failing, if\n      // we get back an invalid response.\n\n      if (!isValidResponse) {\n        throw new WorkboxError_mjs.WorkboxError('bad-precaching-response', {\n          url,\n          status: response.status\n        });\n      }\n\n      if (response.redirected) {\n        response = await cleanRedirect(response);\n      }\n\n      await cacheWrapper_mjs.cacheWrapper.put({\n        event,\n        plugins,\n        request,\n        response,\n        cacheName: this._cacheName,\n        matchOptions: {\n          ignoreSearch: true\n        }\n      });\n    }\n    /**\n     * Returns a mapping of a precached URL to the corresponding cache key, taking\n     * into account the revision information for the URL.\n     *\n     * @return {Map<string, string>} A URL to cache key mapping.\n     */\n\n\n    getURLsToCacheKeys() {\n      return this._urlsToCacheKeys;\n    }\n    /**\n     * Returns a list of all the URLs that have been precached by the current\n     * service worker.\n     *\n     * @return {Array<string>} The precached URLs.\n     */\n\n\n    getCachedURLs() {\n      return [...this._urlsToCacheKeys.keys()];\n    }\n    /**\n     * Returns the cache key used for storing a given URL. If that URL is\n     * unversioned, like `/index.html', then the cache key will be the original\n     * URL with a search parameter appended to it.\n     *\n     * @param {string} url A URL whose cache key you want to look up.\n     * @return {string} The versioned URL that corresponds to a cache key\n     * for the original URL, or undefined if that URL isn't precached.\n     */\n\n\n    getCacheKeyForURL(url) {\n      const urlObject = new URL(url, location);\n      return this._urlsToCacheKeys.get(urlObject.href);\n    }\n\n  }\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  let precacheController;\n  /**\n   * @return {PrecacheController}\n   * @private\n   */\n\n  const getOrCreatePrecacheController = () => {\n    if (!precacheController) {\n      precacheController = new PrecacheController();\n    }\n\n    return precacheController;\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Removes any URL search parameters that should be ignored.\n   *\n   * @param {URL} urlObject The original URL.\n   * @param {Array<RegExp>} ignoreURLParametersMatching RegExps to test against\n   * each search parameter name. Matches mean that the search parameter should be\n   * ignored.\n   * @return {URL} The URL with any ignored search parameters removed.\n   *\n   * @private\n   * @memberof module:workbox-precaching\n   */\n\n  function removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching) {\n    // Convert the iterable into an array at the start of the loop to make sure\n    // deletion doesn't mess up iteration.\n    for (const paramName of [...urlObject.searchParams.keys()]) {\n      if (ignoreURLParametersMatching.some(regExp => regExp.test(paramName))) {\n        urlObject.searchParams.delete(paramName);\n      }\n    }\n\n    return urlObject;\n  }\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Generator function that yields possible variations on the original URL to\n   * check, one at a time.\n   *\n   * @param {string} url\n   * @param {Object} options\n   *\n   * @private\n   * @memberof module:workbox-precaching\n   */\n\n  function* generateURLVariations(url, {\n    ignoreURLParametersMatching,\n    directoryIndex,\n    cleanURLs,\n    urlManipulation\n  } = {}) {\n    const urlObject = new URL(url, location);\n    urlObject.hash = '';\n    yield urlObject.href;\n    const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);\n    yield urlWithoutIgnoredParams.href;\n\n    if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith('/')) {\n      const directoryURL = new URL(urlWithoutIgnoredParams);\n      directoryURL.pathname += directoryIndex;\n      yield directoryURL.href;\n    }\n\n    if (cleanURLs) {\n      const cleanURL = new URL(urlWithoutIgnoredParams);\n      cleanURL.pathname += '.html';\n      yield cleanURL.href;\n    }\n\n    if (urlManipulation) {\n      const additionalURLs = urlManipulation({\n        url: urlObject\n      });\n\n      for (const urlToAttempt of additionalURLs) {\n        yield urlToAttempt.href;\n      }\n    }\n  }\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * This function will take the request URL and manipulate it based on the\n   * configuration options.\n   *\n   * @param {string} url\n   * @param {Object} options\n   * @return {string} Returns the URL in the cache that matches the request,\n   * if possible.\n   *\n   * @private\n   */\n\n  const getCacheKeyForURL = (url, options) => {\n    const precacheController = getOrCreatePrecacheController();\n    const urlsToCacheKeys = precacheController.getURLsToCacheKeys();\n\n    for (const possibleURL of generateURLVariations(url, options)) {\n      const possibleCacheKey = urlsToCacheKeys.get(possibleURL);\n\n      if (possibleCacheKey) {\n        return possibleCacheKey;\n      }\n    }\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  let listenerAdded = false;\n  /**\n   * Add a `fetch` listener to the service worker that will\n   * respond to\n   * [network requests]{@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests}\n   * with precached assets.\n   *\n   * Requests for assets that aren't precached, the `FetchEvent` will not be\n   * responded to, allowing the event to fall through to other `fetch` event\n   * listeners.\n   *\n   * @param {Object} options\n   * @param {string} [options.directoryIndex=index.html] The `directoryIndex` will\n   * check cache entries for a URLs ending with '/' to see if there is a hit when\n   * appending the `directoryIndex` value.\n   * @param {Array<RegExp>} [options.ignoreURLParametersMatching=[/^utm_/]] An\n   * array of regex's to remove search params when looking for a cache match.\n   * @param {boolean} [options.cleanURLs=true] The `cleanURLs` option will\n   * check the cache for the URL with a `.html` added to the end of the end.\n   * @param {workbox.precaching~urlManipulation} [options.urlManipulation]\n   * This is a function that should take a URL and return an array of\n   * alternative URL's that should be checked for precache matches.\n   *\n   * @alias workbox.precaching.addRoute\n   */\n\n  const addRoute = ({\n    ignoreURLParametersMatching = [/^utm_/],\n    directoryIndex = 'index.html',\n    cleanURLs = true,\n    urlManipulation = null\n  } = {}) => {\n    if (!listenerAdded) {\n      const cacheName = cacheNames_mjs.cacheNames.getPrecacheName();\n      addEventListener('fetch', event => {\n        const precachedURL = getCacheKeyForURL(event.request.url, {\n          cleanURLs,\n          directoryIndex,\n          ignoreURLParametersMatching,\n          urlManipulation\n        });\n\n        if (!precachedURL) {\n          {\n            logger_mjs.logger.debug(`Precaching did not find a match for ` + getFriendlyURL_mjs.getFriendlyURL(event.request.url));\n          }\n\n          return;\n        }\n\n        let responsePromise = caches.open(cacheName).then(cache => {\n          return cache.match(precachedURL);\n        }).then(cachedResponse => {\n          if (cachedResponse) {\n            return cachedResponse;\n          } // Fall back to the network if we don't have a cached response\n          // (perhaps due to manual cache cleanup).\n\n\n          {\n            logger_mjs.logger.warn(`The precached response for ` + `${getFriendlyURL_mjs.getFriendlyURL(precachedURL)} in ${cacheName} was not found. ` + `Falling back to the network instead.`);\n          }\n\n          return fetch(precachedURL);\n        });\n\n        {\n          responsePromise = responsePromise.then(response => {\n            // Workbox is going to handle the route.\n            // print the routing details to the console.\n            logger_mjs.logger.groupCollapsed(`Precaching is responding to: ` + getFriendlyURL_mjs.getFriendlyURL(event.request.url));\n            logger_mjs.logger.log(`Serving the precached url: ${precachedURL}`);\n            logger_mjs.logger.groupCollapsed(`View request details here.`);\n            logger_mjs.logger.log(event.request);\n            logger_mjs.logger.groupEnd();\n            logger_mjs.logger.groupCollapsed(`View response details here.`);\n            logger_mjs.logger.log(response);\n            logger_mjs.logger.groupEnd();\n            logger_mjs.logger.groupEnd();\n            return response;\n          });\n        }\n\n        event.respondWith(responsePromise);\n      });\n      listenerAdded = true;\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const SUBSTRING_TO_FIND = '-precache-';\n  /**\n   * Cleans up incompatible precaches that were created by older versions of\n   * Workbox, by a service worker registered under the current scope.\n   *\n   * This is meant to be called as part of the `activate` event.\n   *\n   * This should be safe to use as long as you don't include `substringToFind`\n   * (defaulting to `-precache-`) in your non-precache cache names.\n   *\n   * @param {string} currentPrecacheName The cache name currently in use for\n   * precaching. This cache won't be deleted.\n   * @param {string} [substringToFind='-precache-'] Cache names which include this\n   * substring will be deleted (excluding `currentPrecacheName`).\n   * @return {Array<string>} A list of all the cache names that were deleted.\n   *\n   * @private\n   * @memberof module:workbox-precaching\n   */\n\n  const deleteOutdatedCaches = async (currentPrecacheName, substringToFind = SUBSTRING_TO_FIND) => {\n    const cacheNames = await caches.keys();\n    const cacheNamesToDelete = cacheNames.filter(cacheName => {\n      return cacheName.includes(substringToFind) && cacheName.includes(self.registration.scope) && cacheName !== currentPrecacheName;\n    });\n    await Promise.all(cacheNamesToDelete.map(cacheName => caches.delete(cacheName)));\n    return cacheNamesToDelete;\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Adds an `activate` event listener which will clean up incompatible\n   * precaches that were created by older versions of Workbox.\n   *\n   * @alias workbox.precaching.cleanupOutdatedCaches\n   */\n\n  const cleanupOutdatedCaches = () => {\n    addEventListener('activate', event => {\n      const cacheName = cacheNames_mjs.cacheNames.getPrecacheName();\n      event.waitUntil(deleteOutdatedCaches(cacheName).then(cachesDeleted => {\n        {\n          if (cachesDeleted.length > 0) {\n            logger_mjs.logger.log(`The following out-of-date precaches were cleaned up ` + `automatically:`, cachesDeleted);\n          }\n        }\n      }));\n    });\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Takes in a URL, and returns the corresponding URL that could be used to\n   * lookup the entry in the precache.\n   *\n   * If a relative URL is provided, the location of the service worker file will\n   * be used as the base.\n   *\n   * For precached entries without revision information, the cache key will be the\n   * same as the original URL.\n   *\n   * For precached entries with revision information, the cache key will be the\n   * original URL with the addition of a query parameter used for keeping track of\n   * the revision info.\n   *\n   * @param {string} url The URL whose cache key to look up.\n   * @return {string} The cache key that corresponds to that URL.\n   *\n   * @alias workbox.precaching.getCacheKeyForURL\n   */\n\n  const getCacheKeyForURL$1 = url => {\n    const precacheController = getOrCreatePrecacheController();\n    return precacheController.getCacheKeyForURL(url);\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  let listenersAdded = false;\n  /**\n   * Adds items to the precache list, removing any duplicates and\n   * stores the files in the\n   * [\"precache cache\"]{@link module:workbox-core.cacheNames} when the service\n   * worker installs.\n   *\n   * This method can be called multiple times.\n   *\n   * Please note: This method **will not** serve any of the cached files for you.\n   * It only precaches files. To respond to a network request you call\n   * [addRoute()]{@link module:workbox-precaching.addRoute}.\n   *\n   * If you have a single array of files to precache, you can just call\n   * [precacheAndRoute()]{@link module:workbox-precaching.precacheAndRoute}.\n   *\n   * @param {Array<Object|string>} entries Array of entries to precache.\n   *\n   * @alias workbox.precaching.precache\n   */\n\n  const precache = entries => {\n    const precacheController = getOrCreatePrecacheController();\n    precacheController.addToCacheList(entries);\n\n    if (!listenersAdded && entries.length > 0) {\n      const plugins = precachePlugins.get();\n      self.addEventListener('install', event => {\n        event.waitUntil(precacheController.install({\n          event,\n          plugins\n        }).catch(error => {\n          {\n            logger_mjs.logger.error(`Service worker installation failed. It will ` + `be retried automatically during the next navigation.`);\n          } // Re-throw the error to ensure installation fails.\n\n\n          throw error;\n        }));\n      });\n      self.addEventListener('activate', event => {\n        event.waitUntil(precacheController.activate({\n          event,\n          plugins\n        }));\n      });\n      listenersAdded = true;\n    }\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * This method will add entries to the precache list and add a route to\n   * respond to fetch events.\n   *\n   * This is a convenience method that will call\n   * [precache()]{@link module:workbox-precaching.precache} and\n   * [addRoute()]{@link module:workbox-precaching.addRoute} in a single call.\n   *\n   * @param {Array<Object|string>} entries Array of entries to precache.\n   * @param {Object} options See\n   * [addRoute() options]{@link module:workbox-precaching.addRoute}.\n   *\n   * @alias workbox.precaching.precacheAndRoute\n   */\n\n  const precacheAndRoute = (entries, options) => {\n    precache(entries);\n    addRoute(options);\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  {\n    assert_mjs.assert.isSWEnv('workbox-precaching');\n  }\n\n  exports.addPlugins = addPlugins;\n  exports.addRoute = addRoute;\n  exports.cleanupOutdatedCaches = cleanupOutdatedCaches;\n  exports.getCacheKeyForURL = getCacheKeyForURL$1;\n  exports.precache = precache;\n  exports.precacheAndRoute = precacheAndRoute;\n  exports.PrecacheController = PrecacheController;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-precaching.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-precaching.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.precaching=function(t,e,n,s,c){\"use strict\";try{self[\"workbox:precaching:4.1.1\"]&&_()}catch(t){}const o=[],i={get:()=>o,add(t){o.push(...t)}};const a=\"__WB_REVISION__\";function r(t){if(!t)throw new c.WorkboxError(\"add-to-cache-list-unexpected-type\",{entry:t});if(\"string\"==typeof t){const e=new URL(t,location);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new c.WorkboxError(\"add-to-cache-list-unexpected-type\",{entry:t});if(!e){const t=new URL(n,location);return{cacheKey:t.href,url:t.href}}const s=new URL(n,location),o=new URL(n,location);return o.searchParams.set(a,e),{cacheKey:o.href,url:s.href}}class l{constructor(t){this.t=e.cacheNames.getPrecacheName(t),this.s=new Map}addToCacheList(t){for(const e of t){const{cacheKey:t,url:n}=r(e);if(this.s.has(n)&&this.s.get(n)!==t)throw new c.WorkboxError(\"add-to-cache-list-conflicting-entries\",{firstEntry:this.s.get(n),secondEntry:t});this.s.set(n,t)}}async install({event:t,plugins:e}={}){const n=[],s=[],c=await caches.open(this.t),o=await c.keys(),i=new Set(o.map(t=>t.url));for(const t of this.s.values())i.has(t)?s.push(t):n.push(t);const a=n.map(n=>this.o({event:t,plugins:e,url:n}));return await Promise.all(a),{updatedURLs:n,notUpdatedURLs:s}}async activate(){const t=await caches.open(this.t),e=await t.keys(),n=new Set(this.s.values()),s=[];for(const c of e)n.has(c.url)||(await t.delete(c),s.push(c.url));return{deletedURLs:s}}async o({url:t,event:e,plugins:o}){const i=new Request(t,{credentials:\"same-origin\"});let a,r=await s.fetchWrapper.fetch({event:e,plugins:o,request:i});for(const t of o||[])\"cacheWillUpdate\"in t&&(a=t.cacheWillUpdate.bind(t));if(!(a?a({event:e,request:i,response:r}):r.status<400))throw new c.WorkboxError(\"bad-precaching-response\",{url:t,status:r.status});r.redirected&&(r=await async function(t){const e=t.clone(),n=\"body\"in e?Promise.resolve(e.body):e.blob(),s=await n;return new Response(s,{headers:e.headers,status:e.status,statusText:e.statusText})}(r)),await n.cacheWrapper.put({event:e,plugins:o,request:i,response:r,cacheName:this.t,matchOptions:{ignoreSearch:!0}})}getURLsToCacheKeys(){return this.s}getCachedURLs(){return[...this.s.keys()]}getCacheKeyForURL(t){const e=new URL(t,location);return this.s.get(e.href)}}let u;const h=()=>(u||(u=new l),u);const d=(t,e)=>{const n=h().getURLsToCacheKeys();for(const s of function*(t,{ignoreURLParametersMatching:e,directoryIndex:n,cleanURLs:s,urlManipulation:c}={}){const o=new URL(t,location);o.hash=\"\",yield o.href;const i=function(t,e){for(const n of[...t.searchParams.keys()])e.some(t=>t.test(n))&&t.searchParams.delete(n);return t}(o,e);if(yield i.href,n&&i.pathname.endsWith(\"/\")){const t=new URL(i);t.pathname+=n,yield t.href}if(s){const t=new URL(i);t.pathname+=\".html\",yield t.href}if(c){const t=c({url:o});for(const e of t)yield e.href}}(t,e)){const t=n.get(s);if(t)return t}};let f=!1;const w=({ignoreURLParametersMatching:t=[/^utm_/],directoryIndex:n=\"index.html\",cleanURLs:s=!0,urlManipulation:c=null}={})=>{if(!f){const o=e.cacheNames.getPrecacheName();addEventListener(\"fetch\",e=>{const i=d(e.request.url,{cleanURLs:s,directoryIndex:n,ignoreURLParametersMatching:t,urlManipulation:c});if(!i)return;let a=caches.open(o).then(t=>t.match(i)).then(t=>t||fetch(i));e.respondWith(a)}),f=!0}};let y=!1;const p=t=>{const e=h();if(e.addToCacheList(t),!y&&t.length>0){const t=i.get();self.addEventListener(\"install\",n=>{n.waitUntil(e.install({event:n,plugins:t}).catch(t=>{throw t}))}),self.addEventListener(\"activate\",n=>{n.waitUntil(e.activate({event:n,plugins:t}))}),y=!0}};return t.addPlugins=(t=>{i.add(t)}),t.addRoute=w,t.cleanupOutdatedCaches=(()=>{addEventListener(\"activate\",t=>{const n=e.cacheNames.getPrecacheName();t.waitUntil((async(t,e=\"-precache-\")=>{const n=(await caches.keys()).filter(n=>n.includes(e)&&n.includes(self.registration.scope)&&n!==t);return await Promise.all(n.map(t=>caches.delete(t))),n})(n).then(t=>{}))})}),t.getCacheKeyForURL=(t=>{return h().getCacheKeyForURL(t)}),t.precache=p,t.precacheAndRoute=((t,e)=>{p(t),w(e)}),t.PrecacheController=l,t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);\n//# sourceMappingURL=workbox-precaching.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-range-requests.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.rangeRequests = (function (exports, WorkboxError_mjs, assert_mjs, logger_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:range-requests:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * @param {Blob} blob A source blob.\n   * @param {number|null} start The offset to use as the start of the\n   * slice.\n   * @param {number|null} end The offset to use as the end of the slice.\n   * @return {Object} An object with `start` and `end` properties, reflecting\n   * the effective boundaries to use given the size of the blob.\n   *\n   * @private\n   */\n\n  function calculateEffectiveBoundaries(blob, start, end) {\n    {\n      assert_mjs.assert.isInstance(blob, Blob, {\n        moduleName: 'workbox-range-requests',\n        funcName: 'calculateEffectiveBoundaries',\n        paramName: 'blob'\n      });\n    }\n\n    const blobSize = blob.size;\n\n    if (end > blobSize || start < 0) {\n      throw new WorkboxError_mjs.WorkboxError('range-not-satisfiable', {\n        size: blobSize,\n        end,\n        start\n      });\n    }\n\n    let effectiveStart;\n    let effectiveEnd;\n\n    if (start === null) {\n      effectiveStart = blobSize - end;\n      effectiveEnd = blobSize;\n    } else if (end === null) {\n      effectiveStart = start;\n      effectiveEnd = blobSize;\n    } else {\n      effectiveStart = start; // Range values are inclusive, so add 1 to the value.\n\n      effectiveEnd = end + 1;\n    }\n\n    return {\n      start: effectiveStart,\n      end: effectiveEnd\n    };\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * @param {string} rangeHeader A Range: header value.\n   * @return {Object} An object with `start` and `end` properties, reflecting\n   * the parsed value of the Range: header. If either the `start` or `end` are\n   * omitted, then `null` will be returned.\n   *\n   * @private\n   */\n\n  function parseRangeHeader(rangeHeader) {\n    {\n      assert_mjs.assert.isType(rangeHeader, 'string', {\n        moduleName: 'workbox-range-requests',\n        funcName: 'parseRangeHeader',\n        paramName: 'rangeHeader'\n      });\n    }\n\n    const normalizedRangeHeader = rangeHeader.trim().toLowerCase();\n\n    if (!normalizedRangeHeader.startsWith('bytes=')) {\n      throw new WorkboxError_mjs.WorkboxError('unit-must-be-bytes', {\n        normalizedRangeHeader\n      });\n    } // Specifying multiple ranges separate by commas is valid syntax, but this\n    // library only attempts to handle a single, contiguous sequence of bytes.\n    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#Syntax\n\n\n    if (normalizedRangeHeader.includes(',')) {\n      throw new WorkboxError_mjs.WorkboxError('single-range-only', {\n        normalizedRangeHeader\n      });\n    }\n\n    const rangeParts = /(\\d*)-(\\d*)/.exec(normalizedRangeHeader); // We need either at least one of the start or end values.\n\n    if (rangeParts === null || !(rangeParts[1] || rangeParts[2])) {\n      throw new WorkboxError_mjs.WorkboxError('invalid-range-values', {\n        normalizedRangeHeader\n      });\n    }\n\n    return {\n      start: rangeParts[1] === '' ? null : Number(rangeParts[1]),\n      end: rangeParts[2] === '' ? null : Number(rangeParts[2])\n    };\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Given a `Request` and `Response` objects as input, this will return a\n   * promise for a new `Response`.\n   *\n   * If the original `Response` already contains partial content (i.e. it has\n   * a status of 206), then this assumes it already fulfills the `Range:`\n   * requirements, and will return it as-is.\n   *\n   * @param {Request} request A request, which should contain a Range:\n   * header.\n   * @param {Response} originalResponse A response.\n   * @return {Promise<Response>} Either a `206 Partial Content` response, with\n   * the response body set to the slice of content specified by the request's\n   * `Range:` header, or a `416 Range Not Satisfiable` response if the\n   * conditions of the `Range:` header can't be met.\n   *\n   * @memberof workbox.rangeRequests\n   */\n\n  async function createPartialResponse(request, originalResponse) {\n    try {\n      {\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-range-requests',\n          funcName: 'createPartialResponse',\n          paramName: 'request'\n        });\n        assert_mjs.assert.isInstance(originalResponse, Response, {\n          moduleName: 'workbox-range-requests',\n          funcName: 'createPartialResponse',\n          paramName: 'originalResponse'\n        });\n      }\n\n      if (originalResponse.status === 206) {\n        // If we already have a 206, then just pass it through as-is;\n        // see https://github.com/GoogleChrome/workbox/issues/1720\n        return originalResponse;\n      }\n\n      const rangeHeader = request.headers.get('range');\n\n      if (!rangeHeader) {\n        throw new WorkboxError_mjs.WorkboxError('no-range-header');\n      }\n\n      const boundaries = parseRangeHeader(rangeHeader);\n      const originalBlob = await originalResponse.blob();\n      const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);\n      const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);\n      const slicedBlobSize = slicedBlob.size;\n      const slicedResponse = new Response(slicedBlob, {\n        // Status code 206 is for a Partial Content response.\n        // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206\n        status: 206,\n        statusText: 'Partial Content',\n        headers: originalResponse.headers\n      });\n      slicedResponse.headers.set('Content-Length', slicedBlobSize);\n      slicedResponse.headers.set('Content-Range', `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` + originalBlob.size);\n      return slicedResponse;\n    } catch (error) {\n      {\n        logger_mjs.logger.warn(`Unable to construct a partial response; returning a ` + `416 Range Not Satisfiable response instead.`);\n        logger_mjs.logger.groupCollapsed(`View details here.`);\n        logger_mjs.logger.log(error);\n        logger_mjs.logger.log(request);\n        logger_mjs.logger.log(originalResponse);\n        logger_mjs.logger.groupEnd();\n      }\n\n      return new Response('', {\n        status: 416,\n        statusText: 'Range Not Satisfiable'\n      });\n    }\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * The range request plugin makes it easy for a request with a 'Range' header to\n   * be fulfilled by a cached response.\n   *\n   * It does this by intercepting the `cachedResponseWillBeUsed` plugin callback\n   * and returning the appropriate subset of the cached response body.\n   *\n   * @memberof workbox.rangeRequests\n   */\n\n  class Plugin {\n    /**\n     * @param {Object} options\n     * @param {Request} options.request The original request, which may or may not\n     * contain a Range: header.\n     * @param {Response} options.cachedResponse The complete cached response.\n     * @return {Promise<Response>} If request contains a 'Range' header, then a\n     * new response with status 206 whose body is a subset of `cachedResponse` is\n     * returned. Otherwise, `cachedResponse` is returned as-is.\n     *\n     * @private\n     */\n    async cachedResponseWillBeUsed({\n      request,\n      cachedResponse\n    }) {\n      // Only return a sliced response if there's something valid in the cache,\n      // and there's a Range: header in the request.\n      if (cachedResponse && request.headers.has('range')) {\n        return await createPartialResponse(request, cachedResponse);\n      } // If there was no Range: header, or if cachedResponse wasn't valid, just\n      // pass it through as-is.\n\n\n      return cachedResponse;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.createPartialResponse = createPartialResponse;\n  exports.Plugin = Plugin;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-range-requests.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-range-requests.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.rangeRequests=function(e,n){\"use strict\";try{self[\"workbox:range-requests:4.1.1\"]&&_()}catch(e){}async function t(e,t){try{if(206===t.status)return t;const s=e.headers.get(\"range\");if(!s)throw new n.WorkboxError(\"no-range-header\");const a=function(e){const t=e.trim().toLowerCase();if(!t.startsWith(\"bytes=\"))throw new n.WorkboxError(\"unit-must-be-bytes\",{normalizedRangeHeader:t});if(t.includes(\",\"))throw new n.WorkboxError(\"single-range-only\",{normalizedRangeHeader:t});const s=/(\\d*)-(\\d*)/.exec(t);if(null===s||!s[1]&&!s[2])throw new n.WorkboxError(\"invalid-range-values\",{normalizedRangeHeader:t});return{start:\"\"===s[1]?null:Number(s[1]),end:\"\"===s[2]?null:Number(s[2])}}(s),r=await t.blob(),i=function(e,t,s){const a=e.size;if(s>a||t<0)throw new n.WorkboxError(\"range-not-satisfiable\",{size:a,end:s,start:t});let r,i;return null===t?(r=a-s,i=a):null===s?(r=t,i=a):(r=t,i=s+1),{start:r,end:i}}(r,a.start,a.end),o=r.slice(i.start,i.end),u=o.size,l=new Response(o,{status:206,statusText:\"Partial Content\",headers:t.headers});return l.headers.set(\"Content-Length\",u),l.headers.set(\"Content-Range\",`bytes ${i.start}-${i.end-1}/`+r.size),l}catch(e){return new Response(\"\",{status:416,statusText:\"Range Not Satisfiable\"})}}return e.createPartialResponse=t,e.Plugin=class{async cachedResponseWillBeUsed({request:e,cachedResponse:n}){return n&&e.headers.has(\"range\")?await t(e,n):n}},e}({},workbox.core._private);\n//# sourceMappingURL=workbox-range-requests.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-routing.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.routing = (function (exports, assert_mjs, logger_mjs, cacheNames_mjs, WorkboxError_mjs, getFriendlyURL_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:routing:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * The default HTTP method, 'GET', used when there's no specific method\n   * configured for a route.\n   *\n   * @type {string}\n   *\n   * @private\n   */\n\n  const defaultMethod = 'GET';\n  /**\n   * The list of valid HTTP methods associated with requests that could be routed.\n   *\n   * @type {Array<string>}\n   *\n   * @private\n   */\n\n  const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'];\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * @param {function()|Object} handler Either a function, or an object with a\n   * 'handle' method.\n   * @return {Object} An object with a handle method.\n   *\n   * @private\n   */\n\n  const normalizeHandler = handler => {\n    if (handler && typeof handler === 'object') {\n      {\n        assert_mjs.assert.hasMethod(handler, 'handle', {\n          moduleName: 'workbox-routing',\n          className: 'Route',\n          funcName: 'constructor',\n          paramName: 'handler'\n        });\n      }\n\n      return handler;\n    } else {\n      {\n        assert_mjs.assert.isType(handler, 'function', {\n          moduleName: 'workbox-routing',\n          className: 'Route',\n          funcName: 'constructor',\n          paramName: 'handler'\n        });\n      }\n\n      return {\n        handle: handler\n      };\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * A `Route` consists of a pair of callback functions, \"match\" and \"handler\".\n   * The \"match\" callback determine if a route should be used to \"handle\" a\n   * request by returning a non-falsy value if it can. The \"handler\" callback\n   * is called when there is a match and should return a Promise that resolves\n   * to a `Response`.\n   *\n   * @memberof workbox.routing\n   */\n\n  class Route {\n    /**\n     * Constructor for Route class.\n     *\n     * @param {workbox.routing.Route~matchCallback} match\n     * A callback function that determines whether the route matches a given\n     * `fetch` event by returning a non-falsy value.\n     * @param {workbox.routing.Route~handlerCallback} handler A callback\n     * function that returns a Promise resolving to a Response.\n     * @param {string} [method='GET'] The HTTP method to match the Route\n     * against.\n     */\n    constructor(match, handler, method) {\n      {\n        assert_mjs.assert.isType(match, 'function', {\n          moduleName: 'workbox-routing',\n          className: 'Route',\n          funcName: 'constructor',\n          paramName: 'match'\n        });\n\n        if (method) {\n          assert_mjs.assert.isOneOf(method, validMethods, {\n            paramName: 'method'\n          });\n        }\n      } // These values are referenced directly by Router so cannot be\n      // altered by minifification.\n\n\n      this.handler = normalizeHandler(handler);\n      this.match = match;\n      this.method = method || defaultMethod;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * NavigationRoute makes it easy to create a [Route]{@link\n   * workbox.routing.Route} that matches for browser\n   * [navigation requests]{@link https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests}.\n   *\n   * It will only match incoming Requests whose\n   * [`mode`]{@link https://fetch.spec.whatwg.org/#concept-request-mode}\n   * is set to `navigate`.\n   *\n   * You can optionally only apply this route to a subset of navigation requests\n   * by using one or both of the `blacklist` and `whitelist` parameters.\n   *\n   * @memberof workbox.routing\n   * @extends workbox.routing.Route\n   */\n\n  class NavigationRoute extends Route {\n    /**\n     * If both `blacklist` and `whiltelist` are provided, the `blacklist` will\n     * take precedence and the request will not match this route.\n     *\n     * The regular expressions in `whitelist` and `blacklist`\n     * are matched against the concatenated\n     * [`pathname`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname}\n     * and [`search`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search}\n     * portions of the requested URL.\n     *\n     * @param {workbox.routing.Route~handlerCallback} handler A callback\n     * function that returns a Promise resulting in a Response.\n     * @param {Object} options\n     * @param {Array<RegExp>} [options.blacklist] If any of these patterns match,\n     * the route will not handle the request (even if a whitelist RegExp matches).\n     * @param {Array<RegExp>} [options.whitelist=[/./]] If any of these patterns\n     * match the URL's pathname and search parameter, the route will handle the\n     * request (assuming the blacklist doesn't match).\n     */\n    constructor(handler, {\n      whitelist = [/./],\n      blacklist = []\n    } = {}) {\n      {\n        assert_mjs.assert.isArrayOfClass(whitelist, RegExp, {\n          moduleName: 'workbox-routing',\n          className: 'NavigationRoute',\n          funcName: 'constructor',\n          paramName: 'options.whitelist'\n        });\n        assert_mjs.assert.isArrayOfClass(blacklist, RegExp, {\n          moduleName: 'workbox-routing',\n          className: 'NavigationRoute',\n          funcName: 'constructor',\n          paramName: 'options.blacklist'\n        });\n      }\n\n      super(options => this._match(options), handler);\n      this._whitelist = whitelist;\n      this._blacklist = blacklist;\n    }\n    /**\n     * Routes match handler.\n     *\n     * @param {Object} options\n     * @param {URL} options.url\n     * @param {Request} options.request\n     * @return {boolean}\n     *\n     * @private\n     */\n\n\n    _match({\n      url,\n      request\n    }) {\n      if (request.mode !== 'navigate') {\n        return false;\n      }\n\n      const pathnameAndSearch = url.pathname + url.search;\n\n      for (const regExp of this._blacklist) {\n        if (regExp.test(pathnameAndSearch)) {\n          {\n            logger_mjs.logger.log(`The navigation route is not being used, since the ` + `URL matches this blacklist pattern: ${regExp}`);\n          }\n\n          return false;\n        }\n      }\n\n      if (this._whitelist.some(regExp => regExp.test(pathnameAndSearch))) {\n        {\n          logger_mjs.logger.debug(`The navigation route is being used.`);\n        }\n\n        return true;\n      }\n\n      {\n        logger_mjs.logger.log(`The navigation route is not being used, since the URL ` + `being navigated to doesn't match the whitelist.`);\n      }\n\n      return false;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * RegExpRoute makes it easy to create a regular expression based\n   * [Route]{@link workbox.routing.Route}.\n   *\n   * For same-origin requests the RegExp only needs to match part of the URL. For\n   * requests against third-party servers, you must define a RegExp that matches\n   * the start of the URL.\n   *\n   * [See the module docs for info.]{@link https://developers.google.com/web/tools/workbox/modules/workbox-routing}\n   *\n   * @memberof workbox.routing\n   * @extends workbox.routing.Route\n   */\n\n  class RegExpRoute extends Route {\n    /**\n     * If the regulard expression contains\n     * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},\n     * th ecaptured values will be passed to the\n     * [handler's]{@link workbox.routing.Route~handlerCallback} `params`\n     * argument.\n     *\n     * @param {RegExp} regExp The regular expression to match against URLs.\n     * @param {workbox.routing.Route~handlerCallback} handler A callback\n     * function that returns a Promise resulting in a Response.\n     * @param {string} [method='GET'] The HTTP method to match the Route\n     * against.\n     */\n    constructor(regExp, handler, method) {\n      {\n        assert_mjs.assert.isInstance(regExp, RegExp, {\n          moduleName: 'workbox-routing',\n          className: 'RegExpRoute',\n          funcName: 'constructor',\n          paramName: 'pattern'\n        });\n      }\n\n      const match = ({\n        url\n      }) => {\n        const result = regExp.exec(url.href); // Return null immediately if there's no match.\n\n        if (!result) {\n          return null;\n        } // Require that the match start at the first character in the URL string\n        // if it's a cross-origin request.\n        // See https://github.com/GoogleChrome/workbox/issues/281 for the context\n        // behind this behavior.\n\n\n        if (url.origin !== location.origin && result.index !== 0) {\n          {\n            logger_mjs.logger.debug(`The regular expression '${regExp}' only partially matched ` + `against the cross-origin URL '${url}'. RegExpRoute's will only ` + `handle cross-origin requests if they match the entire URL.`);\n          }\n\n          return null;\n        } // If the route matches, but there aren't any capture groups defined, then\n        // this will return [], which is truthy and therefore sufficient to\n        // indicate a match.\n        // If there are capture groups, then it will return their values.\n\n\n        return result.slice(1);\n      };\n\n      super(match, handler, method);\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * The Router can be used to process a FetchEvent through one or more\n   * [Routes]{@link workbox.routing.Route} responding  with a Request if\n   * a matching route exists.\n   *\n   * If no route matches a given a request, the Router will use a \"default\"\n   * handler if one is defined.\n   *\n   * Should the matching Route throw an error, the Router will use a \"catch\"\n   * handler if one is defined to gracefully deal with issues and respond with a\n   * Request.\n   *\n   * If a request matches multiple routes, the **earliest** registered route will\n   * be used to respond to the request.\n   *\n   * @memberof workbox.routing\n   */\n\n  class Router {\n    /**\n     * Initializes a new Router.\n     */\n    constructor() {\n      this._routes = new Map();\n    }\n    /**\n     * @return {Map<string, Array<workbox.routing.Route>>} routes A `Map` of HTTP\n     * method name ('GET', etc.) to an array of all the corresponding `Route`\n     * instances that are registered.\n     */\n\n\n    get routes() {\n      return this._routes;\n    }\n    /**\n     * Adds a fetch event listener to respond to events when a route matches\n     * the event's request.\n     */\n\n\n    addFetchListener() {\n      self.addEventListener('fetch', event => {\n        const {\n          request\n        } = event;\n        const responsePromise = this.handleRequest({\n          request,\n          event\n        });\n\n        if (responsePromise) {\n          event.respondWith(responsePromise);\n        }\n      });\n    }\n    /**\n     * Adds a message event listener for URLs to cache from the window.\n     * This is useful to cache resources loaded on the page prior to when the\n     * service worker started controlling it.\n     *\n     * The format of the message data sent from the window should be as follows.\n     * Where the `urlsToCache` array may consist of URL strings or an array of\n     * URL string + `requestInit` object (the same as you'd pass to `fetch()`).\n     *\n     * ```\n     * {\n     *   type: 'CACHE_URLS',\n     *   payload: {\n     *     urlsToCache: [\n     *       './script1.js',\n     *       './script2.js',\n     *       ['./script3.js', {mode: 'no-cors'}],\n     *     ],\n     *   },\n     * }\n     * ```\n     */\n\n\n    addCacheListener() {\n      self.addEventListener('message', async event => {\n        if (event.data && event.data.type === 'CACHE_URLS') {\n          const {\n            payload\n          } = event.data;\n\n          {\n            logger_mjs.logger.debug(`Caching URLs from the window`, payload.urlsToCache);\n          }\n\n          const requestPromises = Promise.all(payload.urlsToCache.map(entry => {\n            if (typeof entry === 'string') {\n              entry = [entry];\n            }\n\n            const request = new Request(...entry);\n            return this.handleRequest({\n              request\n            });\n          }));\n          event.waitUntil(requestPromises); // If a MessageChannel was used, reply to the message on success.\n\n          if (event.ports) {\n            await requestPromises;\n            event.ports[0].postMessage(true);\n          }\n        }\n      });\n    }\n    /**\n     * Apply the routing rules to a FetchEvent object to get a Response from an\n     * appropriate Route's handler.\n     *\n     * @param {Object} options\n     * @param {Request} options.request The request to handle (this is usually\n     *     from a fetch event, but it does not have to be).\n     * @param {FetchEvent} [options.event] The event that triggered the request,\n     *     if applicable.\n     * @return {Promise<Response>|undefined} A promise is returned if a\n     *     registered route can handle the request. If there is no matching\n     *     route and there's no `defaultHandler`, `undefined` is returned.\n     */\n\n\n    handleRequest({\n      request,\n      event\n    }) {\n      {\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'handleRequest',\n          paramName: 'options.request'\n        });\n      }\n\n      const url = new URL(request.url, location);\n\n      if (!url.protocol.startsWith('http')) {\n        {\n          logger_mjs.logger.debug(`Workbox Router only supports URLs that start with 'http'.`);\n        }\n\n        return;\n      }\n\n      let {\n        params,\n        route\n      } = this.findMatchingRoute({\n        url,\n        request,\n        event\n      });\n      let handler = route && route.handler;\n      let debugMessages = [];\n\n      {\n        if (handler) {\n          debugMessages.push([`Found a route to handle this request:`, route]);\n\n          if (params) {\n            debugMessages.push([`Passing the following params to the route's handler:`, params]);\n          }\n        }\n      } // If we don't have a handler because there was no matching route, then\n      // fall back to defaultHandler if that's defined.\n\n\n      if (!handler && this._defaultHandler) {\n        {\n          debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler.`); // This is used for debugging in logs in the case of an error.\n\n          route = '[Default Handler]';\n        }\n\n        handler = this._defaultHandler;\n      }\n\n      if (!handler) {\n        {\n          // No handler so Workbox will do nothing. If logs is set of debug\n          // i.e. verbose, we should print out this information.\n          logger_mjs.logger.debug(`No route found for: ${getFriendlyURL_mjs.getFriendlyURL(url)}`);\n        }\n\n        return;\n      }\n\n      {\n        // We have a handler, meaning Workbox is going to handle the route.\n        // print the routing details to the console.\n        logger_mjs.logger.groupCollapsed(`Router is responding to: ${getFriendlyURL_mjs.getFriendlyURL(url)}`);\n        debugMessages.forEach(msg => {\n          if (Array.isArray(msg)) {\n            logger_mjs.logger.log(...msg);\n          } else {\n            logger_mjs.logger.log(msg);\n          }\n        }); // The Request and Response objects contains a great deal of information,\n        // hide it under a group in case developers want to see it.\n\n        logger_mjs.logger.groupCollapsed(`View request details here.`);\n        logger_mjs.logger.log(request);\n        logger_mjs.logger.groupEnd();\n        logger_mjs.logger.groupEnd();\n      } // Wrap in try and catch in case the handle method throws a synchronous\n      // error. It should still callback to the catch handler.\n\n\n      let responsePromise;\n\n      try {\n        responsePromise = handler.handle({\n          url,\n          request,\n          event,\n          params\n        });\n      } catch (err) {\n        responsePromise = Promise.reject(err);\n      }\n\n      if (responsePromise && this._catchHandler) {\n        responsePromise = responsePromise.catch(err => {\n          {\n            // Still include URL here as it will be async from the console group\n            // and may not make sense without the URL\n            logger_mjs.logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL_mjs.getFriendlyURL(url)}. Falling back to Catch Handler.`);\n            logger_mjs.logger.error(`Error thrown by:`, route);\n            logger_mjs.logger.error(err);\n            logger_mjs.logger.groupEnd();\n          }\n\n          return this._catchHandler.handle({\n            url,\n            event,\n            err\n          });\n        });\n      }\n\n      return responsePromise;\n    }\n    /**\n     * Checks a request and URL (and optionally an event) against the list of\n     * registered routes, and if there's a match, returns the corresponding\n     * route along with any params generated by the match.\n     *\n     * @param {Object} options\n     * @param {URL} options.url\n     * @param {Request} options.request The request to match.\n     * @param {FetchEvent} [options.event] The corresponding event (unless N/A).\n     * @return {Object} An object with `route` and `params` properties.\n     *     They are populated if a matching route was found or `undefined`\n     *     otherwise.\n     */\n\n\n    findMatchingRoute({\n      url,\n      request,\n      event\n    }) {\n      {\n        assert_mjs.assert.isInstance(url, URL, {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'findMatchingRoute',\n          paramName: 'options.url'\n        });\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'findMatchingRoute',\n          paramName: 'options.request'\n        });\n      }\n\n      const routes = this._routes.get(request.method) || [];\n\n      for (const route of routes) {\n        let params;\n        let matchResult = route.match({\n          url,\n          request,\n          event\n        });\n\n        if (matchResult) {\n          if (Array.isArray(matchResult) && matchResult.length > 0) {\n            // Instead of passing an empty array in as params, use undefined.\n            params = matchResult;\n          } else if (matchResult.constructor === Object && Object.keys(matchResult).length > 0) {\n            // Instead of passing an empty object in as params, use undefined.\n            params = matchResult;\n          } // Return early if have a match.\n\n\n          return {\n            route,\n            params\n          };\n        }\n      } // If no match was found above, return and empty object.\n\n\n      return {};\n    }\n    /**\n     * Define a default `handler` that's called when no routes explicitly\n     * match the incoming request.\n     *\n     * Without a default handler, unmatched requests will go against the\n     * network as if there were no service worker present.\n     *\n     * @param {workbox.routing.Route~handlerCallback} handler A callback\n     * function that returns a Promise resulting in a Response.\n     */\n\n\n    setDefaultHandler(handler) {\n      this._defaultHandler = normalizeHandler(handler);\n    }\n    /**\n     * If a Route throws an error while handling a request, this `handler`\n     * will be called and given a chance to provide a response.\n     *\n     * @param {workbox.routing.Route~handlerCallback} handler A callback\n     * function that returns a Promise resulting in a Response.\n     */\n\n\n    setCatchHandler(handler) {\n      this._catchHandler = normalizeHandler(handler);\n    }\n    /**\n     * Registers a route with the router.\n     *\n     * @param {workbox.routing.Route} route The route to register.\n     */\n\n\n    registerRoute(route) {\n      {\n        assert_mjs.assert.isType(route, 'object', {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'registerRoute',\n          paramName: 'route'\n        });\n        assert_mjs.assert.hasMethod(route, 'match', {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'registerRoute',\n          paramName: 'route'\n        });\n        assert_mjs.assert.isType(route.handler, 'object', {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'registerRoute',\n          paramName: 'route'\n        });\n        assert_mjs.assert.hasMethod(route.handler, 'handle', {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'registerRoute',\n          paramName: 'route.handler'\n        });\n        assert_mjs.assert.isType(route.method, 'string', {\n          moduleName: 'workbox-routing',\n          className: 'Router',\n          funcName: 'registerRoute',\n          paramName: 'route.method'\n        });\n      }\n\n      if (!this._routes.has(route.method)) {\n        this._routes.set(route.method, []);\n      } // Give precedence to all of the earlier routes by adding this additional\n      // route to the end of the array.\n\n\n      this._routes.get(route.method).push(route);\n    }\n    /**\n     * Unregisters a route with the router.\n     *\n     * @param {workbox.routing.Route} route The route to unregister.\n     */\n\n\n    unregisterRoute(route) {\n      if (!this._routes.has(route.method)) {\n        throw new WorkboxError_mjs.WorkboxError('unregister-route-but-not-found-with-method', {\n          method: route.method\n        });\n      }\n\n      const routeIndex = this._routes.get(route.method).indexOf(route);\n\n      if (routeIndex > -1) {\n        this._routes.get(route.method).splice(routeIndex, 1);\n      } else {\n        throw new WorkboxError_mjs.WorkboxError('unregister-route-route-not-registered');\n      }\n    }\n\n  }\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  let defaultRouter;\n  /**\n   * Creates a new, singleton Router instance if one does not exist. If one\n   * does already exist, that instance is returned.\n   *\n   * @private\n   * @return {Router}\n   */\n\n  const getOrCreateDefaultRouter = () => {\n    if (!defaultRouter) {\n      defaultRouter = new Router(); // The helpers that use the default Router assume these listeners exist.\n\n      defaultRouter.addFetchListener();\n      defaultRouter.addCacheListener();\n    }\n\n    return defaultRouter;\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Registers a route that will return a precached file for a navigation\n   * request. This is useful for the\n   * [application shell pattern]{@link https://developers.google.com/web/fundamentals/architecture/app-shell}.\n   *\n   * When determining the URL of the precached HTML document, you will likely need\n   * to call `workbox.precaching.getCacheKeyForURL(originalUrl)`, to account for\n   * the fact that Workbox's precaching naming conventions often results in URL\n   * cache keys that contain extra revisioning info.\n   *\n   * This method will generate a\n   * [NavigationRoute]{@link workbox.routing.NavigationRoute}\n   * and call\n   * [Router.registerRoute()]{@link workbox.routing.Router#registerRoute} on a\n   * singleton Router instance.\n   *\n   * @param {string} cachedAssetUrl The cache key to use for the HTML file.\n   * @param {Object} [options]\n   * @param {string} [options.cacheName] Cache name to store and retrieve\n   * requests. Defaults to precache cache name provided by\n   * [workbox-core.cacheNames]{@link workbox.core.cacheNames}.\n   * @param {Array<RegExp>} [options.blacklist=[]] If any of these patterns\n   * match, the route will not handle the request (even if a whitelist entry\n   * matches).\n   * @param {Array<RegExp>} [options.whitelist=[/./]] If any of these patterns\n   * match the URL's pathname and search parameter, the route will handle the\n   * request (assuming the blacklist doesn't match).\n   * @return {workbox.routing.NavigationRoute} Returns the generated\n   * Route.\n   *\n   * @alias workbox.routing.registerNavigationRoute\n   */\n\n  const registerNavigationRoute = (cachedAssetUrl, options = {}) => {\n    {\n      assert_mjs.assert.isType(cachedAssetUrl, 'string', {\n        moduleName: 'workbox-routing',\n        funcName: 'registerNavigationRoute',\n        paramName: 'cachedAssetUrl'\n      });\n    }\n\n    const cacheName = cacheNames_mjs.cacheNames.getPrecacheName(options.cacheName);\n\n    const handler = async () => {\n      try {\n        const response = await caches.match(cachedAssetUrl, {\n          cacheName\n        });\n\n        if (response) {\n          return response;\n        } // This shouldn't normally happen, but there are edge cases:\n        // https://github.com/GoogleChrome/workbox/issues/1441\n\n\n        throw new Error(`The cache ${cacheName} did not have an entry for ` + `${cachedAssetUrl}.`);\n      } catch (error) {\n        // If there's either a cache miss, or the caches.match() call threw\n        // an exception, then attempt to fulfill the navigation request with\n        // a response from the network rather than leaving the user with a\n        // failed navigation.\n        {\n          logger_mjs.logger.debug(`Unable to respond to navigation request with ` + `cached response. Falling back to network.`, error);\n        } // This might still fail if the browser is offline...\n\n\n        return fetch(cachedAssetUrl);\n      }\n    };\n\n    const route = new NavigationRoute(handler, {\n      whitelist: options.whitelist,\n      blacklist: options.blacklist\n    });\n    const defaultRouter = getOrCreateDefaultRouter();\n    defaultRouter.registerRoute(route);\n    return route;\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Easily register a RegExp, string, or function with a caching\n   * strategy to a singleton Router instance.\n   *\n   * This method will generate a Route for you if needed and\n   * call [Router.registerRoute()]{@link\n   * workbox.routing.Router#registerRoute}.\n   *\n   * @param {\n   * RegExp|\n   * string|\n   * workbox.routing.Route~matchCallback|\n   * workbox.routing.Route\n   * } capture\n   * If the capture param is a `Route`, all other arguments will be ignored.\n   * @param {workbox.routing.Route~handlerCallback} handler A callback\n   * function that returns a Promise resulting in a Response.\n   * @param {string} [method='GET'] The HTTP method to match the Route\n   * against.\n   * @return {workbox.routing.Route} The generated `Route`(Useful for\n   * unregistering).\n   *\n   * @alias workbox.routing.registerRoute\n   */\n\n  const registerRoute = (capture, handler, method = 'GET') => {\n    let route;\n\n    if (typeof capture === 'string') {\n      const captureUrl = new URL(capture, location);\n\n      {\n        if (!(capture.startsWith('/') || capture.startsWith('http'))) {\n          throw new WorkboxError_mjs.WorkboxError('invalid-string', {\n            moduleName: 'workbox-routing',\n            funcName: 'registerRoute',\n            paramName: 'capture'\n          });\n        } // We want to check if Express-style wildcards are in the pathname only.\n        // TODO: Remove this log message in v4.\n\n\n        const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; // See https://github.com/pillarjs/path-to-regexp#parameters\n\n        const wildcards = '[*:?+]';\n\n        if (valueToCheck.match(new RegExp(`${wildcards}`))) {\n          logger_mjs.logger.debug(`The '$capture' parameter contains an Express-style wildcard ` + `character (${wildcards}). Strings are now always interpreted as ` + `exact matches; use a RegExp for partial or wildcard matches.`);\n        }\n      }\n\n      const matchCallback = ({\n        url\n      }) => {\n        {\n          if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {\n            logger_mjs.logger.debug(`${capture} only partially matches the cross-origin URL ` + `${url}. This route will only handle cross-origin requests ` + `if they match the entire URL.`);\n          }\n        }\n\n        return url.href === captureUrl.href;\n      };\n\n      route = new Route(matchCallback, handler, method);\n    } else if (capture instanceof RegExp) {\n      route = new RegExpRoute(capture, handler, method);\n    } else if (typeof capture === 'function') {\n      route = new Route(capture, handler, method);\n    } else if (capture instanceof Route) {\n      route = capture;\n    } else {\n      throw new WorkboxError_mjs.WorkboxError('unsupported-route-type', {\n        moduleName: 'workbox-routing',\n        funcName: 'registerRoute',\n        paramName: 'capture'\n      });\n    }\n\n    const defaultRouter = getOrCreateDefaultRouter();\n    defaultRouter.registerRoute(route);\n    return route;\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * If a Route throws an error while handling a request, this `handler`\n   * will be called and given a chance to provide a response.\n   *\n   * @param {workbox.routing.Route~handlerCallback} handler A callback\n   * function that returns a Promise resulting in a Response.\n   *\n   * @alias workbox.routing.setCatchHandler\n   */\n\n  const setCatchHandler = handler => {\n    const defaultRouter = getOrCreateDefaultRouter();\n    defaultRouter.setCatchHandler(handler);\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Define a default `handler` that's called when no routes explicitly\n   * match the incoming request.\n   *\n   * Without a default handler, unmatched requests will go against the\n   * network as if there were no service worker present.\n   *\n   * @param {workbox.routing.Route~handlerCallback} handler A callback\n   * function that returns a Promise resulting in a Response.\n   *\n   * @alias workbox.routing.setDefaultHandler\n   */\n\n  const setDefaultHandler = handler => {\n    const defaultRouter = getOrCreateDefaultRouter();\n    defaultRouter.setDefaultHandler(handler);\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  {\n    assert_mjs.assert.isSWEnv('workbox-routing');\n  }\n\n  exports.NavigationRoute = NavigationRoute;\n  exports.RegExpRoute = RegExpRoute;\n  exports.registerNavigationRoute = registerNavigationRoute;\n  exports.registerRoute = registerRoute;\n  exports.Route = Route;\n  exports.Router = Router;\n  exports.setCatchHandler = setCatchHandler;\n  exports.setDefaultHandler = setDefaultHandler;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-routing.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-routing.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.routing=function(t,e,r){\"use strict\";try{self[\"workbox:routing:4.1.1\"]&&_()}catch(t){}const s=\"GET\",n=t=>t&&\"object\"==typeof t?t:{handle:t};class o{constructor(t,e,r){this.handler=n(e),this.match=t,this.method=r||s}}class i extends o{constructor(t,{whitelist:e=[/./],blacklist:r=[]}={}){super(t=>this.t(t),t),this.s=e,this.o=r}t({url:t,request:e}){if(\"navigate\"!==e.mode)return!1;const r=t.pathname+t.search;for(const t of this.o)if(t.test(r))return!1;return!!this.s.some(t=>t.test(r))}}class u extends o{constructor(t,e,r){super(({url:e})=>{const r=t.exec(e.href);return r?e.origin!==location.origin&&0!==r.index?null:r.slice(1):null},e,r)}}class c{constructor(){this.i=new Map}get routes(){return this.i}addFetchListener(){self.addEventListener(\"fetch\",t=>{const{request:e}=t,r=this.handleRequest({request:e,event:t});r&&t.respondWith(r)})}addCacheListener(){self.addEventListener(\"message\",async t=>{if(t.data&&\"CACHE_URLS\"===t.data.type){const{payload:e}=t.data,r=Promise.all(e.urlsToCache.map(t=>{\"string\"==typeof t&&(t=[t]);const e=new Request(...t);return this.handleRequest({request:e})}));t.waitUntil(r),t.ports&&(await r,t.ports[0].postMessage(!0))}})}handleRequest({request:t,event:e}){const r=new URL(t.url,location);if(!r.protocol.startsWith(\"http\"))return;let s,{params:n,route:o}=this.findMatchingRoute({url:r,request:t,event:e}),i=o&&o.handler;if(!i&&this.u&&(i=this.u),i){try{s=i.handle({url:r,request:t,event:e,params:n})}catch(t){s=Promise.reject(t)}return s&&this.h&&(s=s.catch(t=>this.h.handle({url:r,event:e,err:t}))),s}}findMatchingRoute({url:t,request:e,event:r}){const s=this.i.get(e.method)||[];for(const n of s){let s,o=n.match({url:t,request:e,event:r});if(o)return Array.isArray(o)&&o.length>0?s=o:o.constructor===Object&&Object.keys(o).length>0&&(s=o),{route:n,params:s}}return{}}setDefaultHandler(t){this.u=n(t)}setCatchHandler(t){this.h=n(t)}registerRoute(t){this.i.has(t.method)||this.i.set(t.method,[]),this.i.get(t.method).push(t)}unregisterRoute(t){if(!this.i.has(t.method))throw new r.WorkboxError(\"unregister-route-but-not-found-with-method\",{method:t.method});const e=this.i.get(t.method).indexOf(t);if(!(e>-1))throw new r.WorkboxError(\"unregister-route-route-not-registered\");this.i.get(t.method).splice(e,1)}}let a;const h=()=>(a||((a=new c).addFetchListener(),a.addCacheListener()),a);return t.NavigationRoute=i,t.RegExpRoute=u,t.registerNavigationRoute=((t,r={})=>{const s=e.cacheNames.getPrecacheName(r.cacheName),n=new i(async()=>{try{const e=await caches.match(t,{cacheName:s});if(e)return e;throw new Error(`The cache ${s} did not have an entry for `+`${t}.`)}catch(e){return fetch(t)}},{whitelist:r.whitelist,blacklist:r.blacklist});return h().registerRoute(n),n}),t.registerRoute=((t,e,s=\"GET\")=>{let n;if(\"string\"==typeof t){const r=new URL(t,location);n=new o(({url:t})=>t.href===r.href,e,s)}else if(t instanceof RegExp)n=new u(t,e,s);else if(\"function\"==typeof t)n=new o(t,e,s);else{if(!(t instanceof o))throw new r.WorkboxError(\"unsupported-route-type\",{moduleName:\"workbox-routing\",funcName:\"registerRoute\",paramName:\"capture\"});n=t}return h().registerRoute(n),n}),t.Route=o,t.Router=c,t.setCatchHandler=(t=>{h().setCatchHandler(t)}),t.setDefaultHandler=(t=>{h().setDefaultHandler(t)}),t}({},workbox.core._private,workbox.core._private);\n//# sourceMappingURL=workbox-routing.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-strategies.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.strategies = (function (exports, logger_mjs, assert_mjs, cacheNames_mjs, cacheWrapper_mjs, fetchWrapper_mjs, getFriendlyURL_mjs, WorkboxError_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:strategies:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  const getFriendlyURL = url => {\n    const urlObj = new URL(url, location);\n\n    if (urlObj.origin === location.origin) {\n      return urlObj.pathname;\n    }\n\n    return urlObj.href;\n  };\n\n  const messages = {\n    strategyStart: (strategyName, request) => `Using ${strategyName} to ` + `respond to '${getFriendlyURL(request.url)}'`,\n    printFinalResponse: response => {\n      if (response) {\n        logger_mjs.logger.groupCollapsed(`View the final response here.`);\n        logger_mjs.logger.log(response);\n        logger_mjs.logger.groupEnd();\n      }\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * An implementation of a [cache-first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network}\n   * request strategy.\n   *\n   * A cache first strategy is useful for assets that have been revisioned,\n   * such as URLs like `/styles/example.a8f5f1.css`, since they\n   * can be cached for long periods of time.\n   *\n   * If the network request fails, and there is no cache match, this will throw\n   * a `WorkboxError` exception.\n   *\n   * @memberof workbox.strategies\n   */\n\n  class CacheFirst {\n    /**\n     * @param {Object} options\n     * @param {string} options.cacheName Cache name to store and retrieve\n     * requests. Defaults to cache names provided by\n     * [workbox-core]{@link workbox.core.cacheNames}.\n     * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n     * to use in conjunction with this caching strategy.\n     * @param {Object} options.fetchOptions Values passed along to the\n     * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n     * of all fetch() requests made by this strategy.\n     * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n     */\n    constructor(options = {}) {\n      this._cacheName = cacheNames_mjs.cacheNames.getRuntimeName(options.cacheName);\n      this._plugins = options.plugins || [];\n      this._fetchOptions = options.fetchOptions || null;\n      this._matchOptions = options.matchOptions || null;\n    }\n    /**\n     * This method will perform a request strategy and follows an API that\n     * will work with the\n     * [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * @param {Object} options\n     * @param {Request} options.request The request to run this strategy for.\n     * @param {Event} [options.event] The event that triggered the request.\n     * @return {Promise<Response>}\n     */\n\n\n    async handle({\n      event,\n      request\n    }) {\n      return this.makeRequest({\n        event,\n        request: request || event.request\n      });\n    }\n    /**\n     * This method can be used to perform a make a standalone request outside the\n     * context of the [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * See \"[Advanced Recipes](https://developers.google.com/web/tools/workbox/guides/advanced-recipes#make-requests)\"\n     * for more usage information.\n     *\n     * @param {Object} options\n     * @param {Request|string} options.request Either a\n     *     [`Request`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Request}\n     *     object, or a string URL, corresponding to the request to be made.\n     * @param {FetchEvent} [options.event] If provided, `event.waitUntil()` will\n           be called automatically to extend the service worker's lifetime.\n     * @return {Promise<Response>}\n     */\n\n\n    async makeRequest({\n      event,\n      request\n    }) {\n      const logs = [];\n\n      if (typeof request === 'string') {\n        request = new Request(request);\n      }\n\n      {\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-strategies',\n          className: 'CacheFirst',\n          funcName: 'makeRequest',\n          paramName: 'request'\n        });\n      }\n\n      let response = await cacheWrapper_mjs.cacheWrapper.match({\n        cacheName: this._cacheName,\n        request,\n        event,\n        matchOptions: this._matchOptions,\n        plugins: this._plugins\n      });\n      let error;\n\n      if (!response) {\n        {\n          logs.push(`No response found in the '${this._cacheName}' cache. ` + `Will respond with a network request.`);\n        }\n\n        try {\n          response = await this._getFromNetwork(request, event);\n        } catch (err) {\n          error = err;\n        }\n\n        {\n          if (response) {\n            logs.push(`Got response from network.`);\n          } else {\n            logs.push(`Unable to get a response from the network.`);\n          }\n        }\n      } else {\n        {\n          logs.push(`Found a cached response in the '${this._cacheName}' cache.`);\n        }\n      }\n\n      {\n        logger_mjs.logger.groupCollapsed(messages.strategyStart('CacheFirst', request));\n\n        for (let log of logs) {\n          logger_mjs.logger.log(log);\n        }\n\n        messages.printFinalResponse(response);\n        logger_mjs.logger.groupEnd();\n      }\n\n      if (!response) {\n        throw new WorkboxError_mjs.WorkboxError('no-response', {\n          url: request.url,\n          error\n        });\n      }\n\n      return response;\n    }\n    /**\n     * Handles the network and cache part of CacheFirst.\n     *\n     * @param {Request} request\n     * @param {FetchEvent} [event]\n     * @return {Promise<Response>}\n     *\n     * @private\n     */\n\n\n    async _getFromNetwork(request, event) {\n      const response = await fetchWrapper_mjs.fetchWrapper.fetch({\n        request,\n        event,\n        fetchOptions: this._fetchOptions,\n        plugins: this._plugins\n      }); // Keep the service worker while we put the request to the cache\n\n      const responseClone = response.clone();\n      const cachePutPromise = cacheWrapper_mjs.cacheWrapper.put({\n        cacheName: this._cacheName,\n        request,\n        response: responseClone,\n        event,\n        plugins: this._plugins\n      });\n\n      if (event) {\n        try {\n          event.waitUntil(cachePutPromise);\n        } catch (error) {\n          {\n            logger_mjs.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_mjs.getFriendlyURL(request.url)}'.`);\n          }\n        }\n      }\n\n      return response;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * An implementation of a\n   * [cache-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-only}\n   * request strategy.\n   *\n   * This class is useful if you want to take advantage of any\n   * [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}.\n   *\n   * If there is no cache match, this will throw a `WorkboxError` exception.\n   *\n   * @memberof workbox.strategies\n   */\n\n  class CacheOnly {\n    /**\n     * @param {Object} options\n     * @param {string} options.cacheName Cache name to store and retrieve\n     * requests. Defaults to cache names provided by\n     * [workbox-core]{@link workbox.core.cacheNames}.\n     * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n     * to use in conjunction with this caching strategy.\n     * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n     */\n    constructor(options = {}) {\n      this._cacheName = cacheNames_mjs.cacheNames.getRuntimeName(options.cacheName);\n      this._plugins = options.plugins || [];\n      this._matchOptions = options.matchOptions || null;\n    }\n    /**\n     * This method will perform a request strategy and follows an API that\n     * will work with the\n     * [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * @param {Object} options\n     * @param {Request} options.request The request to run this strategy for.\n     * @param {Event} [options.event] The event that triggered the request.\n     * @return {Promise<Response>}\n     */\n\n\n    async handle({\n      event,\n      request\n    }) {\n      return this.makeRequest({\n        event,\n        request: request || event.request\n      });\n    }\n    /**\n     * This method can be used to perform a make a standalone request outside the\n     * context of the [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * See \"[Advanced Recipes](https://developers.google.com/web/tools/workbox/guides/advanced-recipes#make-requests)\"\n     * for more usage information.\n     *\n     * @param {Object} options\n     * @param {Request|string} options.request Either a\n     *     [`Request`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Request}\n     *     object, or a string URL, corresponding to the request to be made.\n     * @param {FetchEvent} [options.event] If provided, `event.waitUntil()` will\n     *     be called automatically to extend the service worker's lifetime.\n     * @return {Promise<Response>}\n     */\n\n\n    async makeRequest({\n      event,\n      request\n    }) {\n      if (typeof request === 'string') {\n        request = new Request(request);\n      }\n\n      {\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-strategies',\n          className: 'CacheOnly',\n          funcName: 'makeRequest',\n          paramName: 'request'\n        });\n      }\n\n      const response = await cacheWrapper_mjs.cacheWrapper.match({\n        cacheName: this._cacheName,\n        request,\n        event,\n        matchOptions: this._matchOptions,\n        plugins: this._plugins\n      });\n\n      {\n        logger_mjs.logger.groupCollapsed(messages.strategyStart('CacheOnly', request));\n\n        if (response) {\n          logger_mjs.logger.log(`Found a cached response in the '${this._cacheName}'` + ` cache.`);\n          messages.printFinalResponse(response);\n        } else {\n          logger_mjs.logger.log(`No response found in the '${this._cacheName}' cache.`);\n        }\n\n        logger_mjs.logger.groupEnd();\n      }\n\n      if (!response) {\n        throw new WorkboxError_mjs.WorkboxError('no-response', {\n          url: request.url\n        });\n      }\n\n      return response;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const cacheOkAndOpaquePlugin = {\n    /**\n     * Returns a valid response (to allow caching) if the status is 200 (OK) or\n     * 0 (opaque).\n     *\n     * @param {Object} options\n     * @param {Response} options.response\n     * @return {Response|null}\n     *\n     * @private\n     */\n    cacheWillUpdate: ({\n      response\n    }) => {\n      if (response.status === 200 || response.status === 0) {\n        return response;\n      }\n\n      return null;\n    }\n  };\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * An implementation of a\n   * [network first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-falling-back-to-cache}\n   * request strategy.\n   *\n   * By default, this strategy will cache responses with a 200 status code as\n   * well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}.\n   * Opaque responses are are cross-origin requests where the response doesn't\n   * support [CORS]{@link https://enable-cors.org/}.\n   *\n   * If the network request fails, and there is no cache match, this will throw\n   * a `WorkboxError` exception.\n   *\n   * @memberof workbox.strategies\n   */\n\n  class NetworkFirst {\n    /**\n     * @param {Object} options\n     * @param {string} options.cacheName Cache name to store and retrieve\n     * requests. Defaults to cache names provided by\n     * [workbox-core]{@link workbox.core.cacheNames}.\n     * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n     * to use in conjunction with this caching strategy.\n     * @param {Object} options.fetchOptions Values passed along to the\n     * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n     * of all fetch() requests made by this strategy.\n     * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n     * @param {number} options.networkTimeoutSeconds If set, any network requests\n     * that fail to respond within the timeout will fallback to the cache.\n     *\n     * This option can be used to combat\n     * \"[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}\"\n     * scenarios.\n     */\n    constructor(options = {}) {\n      this._cacheName = cacheNames_mjs.cacheNames.getRuntimeName(options.cacheName);\n\n      if (options.plugins) {\n        let isUsingCacheWillUpdate = options.plugins.some(plugin => !!plugin.cacheWillUpdate);\n        this._plugins = isUsingCacheWillUpdate ? options.plugins : [cacheOkAndOpaquePlugin, ...options.plugins];\n      } else {\n        // No plugins passed in, use the default plugin.\n        this._plugins = [cacheOkAndOpaquePlugin];\n      }\n\n      this._networkTimeoutSeconds = options.networkTimeoutSeconds;\n\n      {\n        if (this._networkTimeoutSeconds) {\n          assert_mjs.assert.isType(this._networkTimeoutSeconds, 'number', {\n            moduleName: 'workbox-strategies',\n            className: 'NetworkFirst',\n            funcName: 'constructor',\n            paramName: 'networkTimeoutSeconds'\n          });\n        }\n      }\n\n      this._fetchOptions = options.fetchOptions || null;\n      this._matchOptions = options.matchOptions || null;\n    }\n    /**\n     * This method will perform a request strategy and follows an API that\n     * will work with the\n     * [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * @param {Object} options\n     * @param {Request} options.request The request to run this strategy for.\n     * @param {Event} [options.event] The event that triggered the request.\n     * @return {Promise<Response>}\n     */\n\n\n    async handle({\n      event,\n      request\n    }) {\n      return this.makeRequest({\n        event,\n        request: request || event.request\n      });\n    }\n    /**\n     * This method can be used to perform a make a standalone request outside the\n     * context of the [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * See \"[Advanced Recipes](https://developers.google.com/web/tools/workbox/guides/advanced-recipes#make-requests)\"\n     * for more usage information.\n     *\n     * @param {Object} options\n     * @param {Request|string} options.request Either a\n     *     [`Request`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Request}\n     *     object, or a string URL, corresponding to the request to be made.\n     * @param {FetchEvent} [options.event] If provided, `event.waitUntil()` will\n     *     be called automatically to extend the service worker's lifetime.\n     * @return {Promise<Response>}\n     */\n\n\n    async makeRequest({\n      event,\n      request\n    }) {\n      const logs = [];\n\n      if (typeof request === 'string') {\n        request = new Request(request);\n      }\n\n      {\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-strategies',\n          className: 'NetworkFirst',\n          funcName: 'handle',\n          paramName: 'makeRequest'\n        });\n      }\n\n      const promises = [];\n      let timeoutId;\n\n      if (this._networkTimeoutSeconds) {\n        const {\n          id,\n          promise\n        } = this._getTimeoutPromise({\n          request,\n          event,\n          logs\n        });\n\n        timeoutId = id;\n        promises.push(promise);\n      }\n\n      const networkPromise = this._getNetworkPromise({\n        timeoutId,\n        request,\n        event,\n        logs\n      });\n\n      promises.push(networkPromise); // Promise.race() will resolve as soon as the first promise resolves.\n\n      let response = await Promise.race(promises); // If Promise.race() resolved with null, it might be due to a network\n      // timeout + a cache miss. If that were to happen, we'd rather wait until\n      // the networkPromise resolves instead of returning null.\n      // Note that it's fine to await an already-resolved promise, so we don't\n      // have to check to see if it's still \"in flight\".\n\n      if (!response) {\n        response = await networkPromise;\n      }\n\n      {\n        logger_mjs.logger.groupCollapsed(messages.strategyStart('NetworkFirst', request));\n\n        for (let log of logs) {\n          logger_mjs.logger.log(log);\n        }\n\n        messages.printFinalResponse(response);\n        logger_mjs.logger.groupEnd();\n      }\n\n      if (!response) {\n        throw new WorkboxError_mjs.WorkboxError('no-response', {\n          url: request.url\n        });\n      }\n\n      return response;\n    }\n    /**\n     * @param {Object} options\n     * @param {Request} options.request\n     * @param {Array} options.logs A reference to the logs array\n     * @param {Event} [options.event]\n     * @return {Promise<Response>}\n     *\n     * @private\n     */\n\n\n    _getTimeoutPromise({\n      request,\n      logs,\n      event\n    }) {\n      let timeoutId;\n      const timeoutPromise = new Promise(resolve => {\n        const onNetworkTimeout = async () => {\n          {\n            logs.push(`Timing out the network response at ` + `${this._networkTimeoutSeconds} seconds.`);\n          }\n\n          resolve((await this._respondFromCache({\n            request,\n            event\n          })));\n        };\n\n        timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);\n      });\n      return {\n        promise: timeoutPromise,\n        id: timeoutId\n      };\n    }\n    /**\n     * @param {Object} options\n     * @param {number|undefined} options.timeoutId\n     * @param {Request} options.request\n     * @param {Array} options.logs A reference to the logs Array.\n     * @param {Event} [options.event]\n     * @return {Promise<Response>}\n     *\n     * @private\n     */\n\n\n    async _getNetworkPromise({\n      timeoutId,\n      request,\n      logs,\n      event\n    }) {\n      let error;\n      let response;\n\n      try {\n        response = await fetchWrapper_mjs.fetchWrapper.fetch({\n          request,\n          event,\n          fetchOptions: this._fetchOptions,\n          plugins: this._plugins\n        });\n      } catch (err) {\n        error = err;\n      }\n\n      if (timeoutId) {\n        clearTimeout(timeoutId);\n      }\n\n      {\n        if (response) {\n          logs.push(`Got response from network.`);\n        } else {\n          logs.push(`Unable to get a response from the network. Will respond ` + `with a cached response.`);\n        }\n      }\n\n      if (error || !response) {\n        response = await this._respondFromCache({\n          request,\n          event\n        });\n\n        {\n          if (response) {\n            logs.push(`Found a cached response in the '${this._cacheName}'` + ` cache.`);\n          } else {\n            logs.push(`No response found in the '${this._cacheName}' cache.`);\n          }\n        }\n      } else {\n        // Keep the service worker alive while we put the request in the cache\n        const responseClone = response.clone();\n        const cachePut = cacheWrapper_mjs.cacheWrapper.put({\n          cacheName: this._cacheName,\n          request,\n          response: responseClone,\n          event,\n          plugins: this._plugins\n        });\n\n        if (event) {\n          try {\n            // The event has been responded to so we can keep the SW alive to\n            // respond to the request\n            event.waitUntil(cachePut);\n          } catch (err) {\n            {\n              logger_mjs.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_mjs.getFriendlyURL(request.url)}'.`);\n            }\n          }\n        }\n      }\n\n      return response;\n    }\n    /**\n     * Used if the network timeouts or fails to make the request.\n     *\n     * @param {Object} options\n     * @param {Request} request The request to match in the cache\n     * @param {Event} [options.event]\n     * @return {Promise<Object>}\n     *\n     * @private\n     */\n\n\n    _respondFromCache({\n      event,\n      request\n    }) {\n      return cacheWrapper_mjs.cacheWrapper.match({\n        cacheName: this._cacheName,\n        request,\n        event,\n        matchOptions: this._matchOptions,\n        plugins: this._plugins\n      });\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * An implementation of a\n   * [network-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-only}\n   * request strategy.\n   *\n   * This class is useful if you want to take advantage of any\n   * [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}.\n   *\n   * If the network request fails, this will throw a `WorkboxError` exception.\n   *\n   * @memberof workbox.strategies\n   */\n\n  class NetworkOnly {\n    /**\n     * @param {Object} options\n     * @param {string} options.cacheName Cache name to store and retrieve\n     * requests. Defaults to cache names provided by\n     * [workbox-core]{@link workbox.core.cacheNames}.\n     * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n     * to use in conjunction with this caching strategy.\n     * @param {Object} options.fetchOptions Values passed along to the\n     * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n     * of all fetch() requests made by this strategy.\n     */\n    constructor(options = {}) {\n      this._cacheName = cacheNames_mjs.cacheNames.getRuntimeName(options.cacheName);\n      this._plugins = options.plugins || [];\n      this._fetchOptions = options.fetchOptions || null;\n    }\n    /**\n     * This method will perform a request strategy and follows an API that\n     * will work with the\n     * [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * @param {Object} options\n     * @param {Request} options.request The request to run this strategy for.\n     * @param {Event} [options.event] The event that triggered the request.\n     * @return {Promise<Response>}\n     */\n\n\n    async handle({\n      event,\n      request\n    }) {\n      return this.makeRequest({\n        event,\n        request: request || event.request\n      });\n    }\n    /**\n     * This method can be used to perform a make a standalone request outside the\n     * context of the [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * See \"[Advanced Recipes](https://developers.google.com/web/tools/workbox/guides/advanced-recipes#make-requests)\"\n     * for more usage information.\n     *\n     * @param {Object} options\n     * @param {Request|string} options.request Either a\n     *     [`Request`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Request}\n     *     object, or a string URL, corresponding to the request to be made.\n     * @param {FetchEvent} [options.event] If provided, `event.waitUntil()` will\n     *     be called automatically to extend the service worker's lifetime.\n     * @return {Promise<Response>}\n     */\n\n\n    async makeRequest({\n      event,\n      request\n    }) {\n      if (typeof request === 'string') {\n        request = new Request(request);\n      }\n\n      {\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-strategies',\n          className: 'NetworkOnly',\n          funcName: 'handle',\n          paramName: 'request'\n        });\n      }\n\n      let error;\n      let response;\n\n      try {\n        response = await fetchWrapper_mjs.fetchWrapper.fetch({\n          request,\n          event,\n          fetchOptions: this._fetchOptions,\n          plugins: this._plugins\n        });\n      } catch (err) {\n        error = err;\n      }\n\n      {\n        logger_mjs.logger.groupCollapsed(messages.strategyStart('NetworkOnly', request));\n\n        if (response) {\n          logger_mjs.logger.log(`Got response from network.`);\n        } else {\n          logger_mjs.logger.log(`Unable to get a response from the network.`);\n        }\n\n        messages.printFinalResponse(response);\n        logger_mjs.logger.groupEnd();\n      }\n\n      if (!response) {\n        throw new WorkboxError_mjs.WorkboxError('no-response', {\n          url: request.url,\n          error\n        });\n      }\n\n      return response;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * An implementation of a\n   * [stale-while-revalidate]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate}\n   * request strategy.\n   *\n   * Resources are requested from both the cache and the network in parallel.\n   * The strategy will respond with the cached version if available, otherwise\n   * wait for the network response. The cache is updated with the network response\n   * with each successful request.\n   *\n   * By default, this strategy will cache responses with a 200 status code as\n   * well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}.\n   * Opaque responses are are cross-origin requests where the response doesn't\n   * support [CORS]{@link https://enable-cors.org/}.\n   *\n   * If the network request fails, and there is no cache match, this will throw\n   * a `WorkboxError` exception.\n   *\n   * @memberof workbox.strategies\n   */\n\n  class StaleWhileRevalidate {\n    /**\n     * @param {Object} options\n     * @param {string} options.cacheName Cache name to store and retrieve\n     * requests. Defaults to cache names provided by\n     * [workbox-core]{@link workbox.core.cacheNames}.\n     * @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n     * to use in conjunction with this caching strategy.\n     * @param {Object} options.fetchOptions Values passed along to the\n     * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n     * of all fetch() requests made by this strategy.\n     * @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n     */\n    constructor(options = {}) {\n      this._cacheName = cacheNames_mjs.cacheNames.getRuntimeName(options.cacheName);\n      this._plugins = options.plugins || [];\n\n      if (options.plugins) {\n        let isUsingCacheWillUpdate = options.plugins.some(plugin => !!plugin.cacheWillUpdate);\n        this._plugins = isUsingCacheWillUpdate ? options.plugins : [cacheOkAndOpaquePlugin, ...options.plugins];\n      } else {\n        // No plugins passed in, use the default plugin.\n        this._plugins = [cacheOkAndOpaquePlugin];\n      }\n\n      this._fetchOptions = options.fetchOptions || null;\n      this._matchOptions = options.matchOptions || null;\n    }\n    /**\n     * This method will perform a request strategy and follows an API that\n     * will work with the\n     * [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * @param {Object} options\n     * @param {Request} options.request The request to run this strategy for.\n     * @param {Event} [options.event] The event that triggered the request.\n     * @return {Promise<Response>}\n     */\n\n\n    async handle({\n      event,\n      request\n    }) {\n      return this.makeRequest({\n        event,\n        request: request || event.request\n      });\n    }\n    /**\n     * This method can be used to perform a make a standalone request outside the\n     * context of the [Workbox Router]{@link workbox.routing.Router}.\n     *\n     * See \"[Advanced Recipes](https://developers.google.com/web/tools/workbox/guides/advanced-recipes#make-requests)\"\n     * for more usage information.\n     *\n     * @param {Object} options\n     * @param {Request|string} options.request Either a\n     *     [`Request`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Request}\n     *     object, or a string URL, corresponding to the request to be made.\n     * @param {FetchEvent} [options.event] If provided, `event.waitUntil()` will\n     *     be called automatically to extend the service worker's lifetime.\n     * @return {Promise<Response>}\n     */\n\n\n    async makeRequest({\n      event,\n      request\n    }) {\n      const logs = [];\n\n      if (typeof request === 'string') {\n        request = new Request(request);\n      }\n\n      {\n        assert_mjs.assert.isInstance(request, Request, {\n          moduleName: 'workbox-strategies',\n          className: 'StaleWhileRevalidate',\n          funcName: 'handle',\n          paramName: 'request'\n        });\n      }\n\n      const fetchAndCachePromise = this._getFromNetwork({\n        request,\n        event\n      });\n\n      let response = await cacheWrapper_mjs.cacheWrapper.match({\n        cacheName: this._cacheName,\n        request,\n        event,\n        matchOptions: this._matchOptions,\n        plugins: this._plugins\n      });\n      let error;\n\n      if (response) {\n        {\n          logs.push(`Found a cached response in the '${this._cacheName}'` + ` cache. Will update with the network response in the background.`);\n        }\n\n        if (event) {\n          try {\n            event.waitUntil(fetchAndCachePromise);\n          } catch (error) {\n            {\n              logger_mjs.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_mjs.getFriendlyURL(request.url)}'.`);\n            }\n          }\n        }\n      } else {\n        {\n          logs.push(`No response found in the '${this._cacheName}' cache. ` + `Will wait for the network response.`);\n        }\n\n        try {\n          response = await fetchAndCachePromise;\n        } catch (err) {\n          error = err;\n        }\n      }\n\n      {\n        logger_mjs.logger.groupCollapsed(messages.strategyStart('StaleWhileRevalidate', request));\n\n        for (let log of logs) {\n          logger_mjs.logger.log(log);\n        }\n\n        messages.printFinalResponse(response);\n        logger_mjs.logger.groupEnd();\n      }\n\n      if (!response) {\n        throw new WorkboxError_mjs.WorkboxError('no-response', {\n          url: request.url,\n          error\n        });\n      }\n\n      return response;\n    }\n    /**\n     * @param {Object} options\n     * @param {Request} options.request\n     * @param {Event} [options.event]\n     * @return {Promise<Response>}\n     *\n     * @private\n     */\n\n\n    async _getFromNetwork({\n      request,\n      event\n    }) {\n      const response = await fetchWrapper_mjs.fetchWrapper.fetch({\n        request,\n        event,\n        fetchOptions: this._fetchOptions,\n        plugins: this._plugins\n      });\n      const cachePutPromise = cacheWrapper_mjs.cacheWrapper.put({\n        cacheName: this._cacheName,\n        request,\n        response: response.clone(),\n        event,\n        plugins: this._plugins\n      });\n\n      if (event) {\n        try {\n          event.waitUntil(cachePutPromise);\n        } catch (error) {\n          {\n            logger_mjs.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_mjs.getFriendlyURL(request.url)}'.`);\n          }\n        }\n      }\n\n      return response;\n    }\n\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  const mapping = {\n    cacheFirst: CacheFirst,\n    cacheOnly: CacheOnly,\n    networkFirst: NetworkFirst,\n    networkOnly: NetworkOnly,\n    staleWhileRevalidate: StaleWhileRevalidate\n  };\n\n  const deprecate = strategy => {\n    const StrategyCtr = mapping[strategy];\n    return options => {\n      {\n        const strategyCtrName = strategy[0].toUpperCase() + strategy.slice(1);\n        logger_mjs.logger.warn(`The 'workbox.strategies.${strategy}()' function has been ` + `deprecated and will be removed in a future version of Workbox.\\n` + `Please use 'new workbox.strategies.${strategyCtrName}()' instead.`);\n      }\n\n      return new StrategyCtr(options);\n    };\n  };\n  /**\n   * @function workbox.strategies.cacheFirst\n   * @param {Object} options See the {@link workbox.strategies.CacheFirst}\n   * constructor for more info.\n   * @deprecated since v4.0.0\n   */\n\n\n  const cacheFirst = deprecate('cacheFirst');\n  /**\n   * @function workbox.strategies.cacheOnly\n   * @param {Object} options See the {@link workbox.strategies.CacheOnly}\n   * constructor for more info.\n   * @deprecated since v4.0.0\n   */\n\n  const cacheOnly = deprecate('cacheOnly');\n  /**\n   * @function workbox.strategies.networkFirst\n   * @param {Object} options See the {@link workbox.strategies.NetworkFirst}\n   * constructor for more info.\n   * @deprecated since v4.0.0\n   */\n\n  const networkFirst = deprecate('networkFirst');\n  /**\n   * @function workbox.strategies.networkOnly\n   * @param {Object} options See the {@link workbox.strategies.NetworkOnly}\n   * constructor for more info.\n   * @deprecated since v4.0.0\n   */\n\n  const networkOnly = deprecate('networkOnly');\n  /**\n   * @function workbox.strategies.staleWhileRevalidate\n   * @param {Object} options See the\n   * {@link workbox.strategies.StaleWhileRevalidate} constructor for more info.\n   * @deprecated since v4.0.0\n   */\n\n  const staleWhileRevalidate = deprecate('staleWhileRevalidate');\n\n  exports.CacheFirst = CacheFirst;\n  exports.CacheOnly = CacheOnly;\n  exports.NetworkFirst = NetworkFirst;\n  exports.NetworkOnly = NetworkOnly;\n  exports.StaleWhileRevalidate = StaleWhileRevalidate;\n  exports.cacheFirst = cacheFirst;\n  exports.cacheOnly = cacheOnly;\n  exports.networkFirst = networkFirst;\n  exports.networkOnly = networkOnly;\n  exports.staleWhileRevalidate = staleWhileRevalidate;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-strategies.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-strategies.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.strategies=function(e,t,s,n,r){\"use strict\";try{self[\"workbox:strategies:4.1.1\"]&&_()}catch(e){}class i{constructor(e={}){this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],this.i=e.fetchOptions||null,this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){\"string\"==typeof t&&(t=new Request(t));let n,i=await s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s});if(!i)try{i=await this.u(t,e)}catch(e){n=e}if(!i)throw new r.WorkboxError(\"no-response\",{url:t.url,error:n});return i}async u(e,t){const r=await n.fetchWrapper.fetch({request:e,event:t,fetchOptions:this.i,plugins:this.s}),i=r.clone(),h=s.cacheWrapper.put({cacheName:this.t,request:e,response:i,event:t,plugins:this.s});if(t)try{t.waitUntil(h)}catch(e){}return r}}class h{constructor(e={}){this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){\"string\"==typeof t&&(t=new Request(t));const n=await s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s});if(!n)throw new r.WorkboxError(\"no-response\",{url:t.url});return n}}const u={cacheWillUpdate:({response:e})=>200===e.status||0===e.status?e:null};class a{constructor(e={}){if(this.t=t.cacheNames.getRuntimeName(e.cacheName),e.plugins){let t=e.plugins.some(e=>!!e.cacheWillUpdate);this.s=t?e.plugins:[u,...e.plugins]}else this.s=[u];this.o=e.networkTimeoutSeconds,this.i=e.fetchOptions||null,this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){const s=[];\"string\"==typeof t&&(t=new Request(t));const n=[];let i;if(this.o){const{id:r,promise:h}=this.l({request:t,event:e,logs:s});i=r,n.push(h)}const h=this.q({timeoutId:i,request:t,event:e,logs:s});n.push(h);let u=await Promise.race(n);if(u||(u=await h),!u)throw new r.WorkboxError(\"no-response\",{url:t.url});return u}l({request:e,logs:t,event:s}){let n;return{promise:new Promise(t=>{n=setTimeout(async()=>{t(await this.p({request:e,event:s}))},1e3*this.o)}),id:n}}async q({timeoutId:e,request:t,logs:r,event:i}){let h,u;try{u=await n.fetchWrapper.fetch({request:t,event:i,fetchOptions:this.i,plugins:this.s})}catch(e){h=e}if(e&&clearTimeout(e),h||!u)u=await this.p({request:t,event:i});else{const e=u.clone(),n=s.cacheWrapper.put({cacheName:this.t,request:t,response:e,event:i,plugins:this.s});if(i)try{i.waitUntil(n)}catch(e){}}return u}p({event:e,request:t}){return s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s})}}class c{constructor(e={}){this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],this.i=e.fetchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){let s,i;\"string\"==typeof t&&(t=new Request(t));try{i=await n.fetchWrapper.fetch({request:t,event:e,fetchOptions:this.i,plugins:this.s})}catch(e){s=e}if(!i)throw new r.WorkboxError(\"no-response\",{url:t.url,error:s});return i}}class o{constructor(e={}){if(this.t=t.cacheNames.getRuntimeName(e.cacheName),this.s=e.plugins||[],e.plugins){let t=e.plugins.some(e=>!!e.cacheWillUpdate);this.s=t?e.plugins:[u,...e.plugins]}else this.s=[u];this.i=e.fetchOptions||null,this.h=e.matchOptions||null}async handle({event:e,request:t}){return this.makeRequest({event:e,request:t||e.request})}async makeRequest({event:e,request:t}){\"string\"==typeof t&&(t=new Request(t));const n=this.u({request:t,event:e});let i,h=await s.cacheWrapper.match({cacheName:this.t,request:t,event:e,matchOptions:this.h,plugins:this.s});if(h){if(e)try{e.waitUntil(n)}catch(i){}}else try{h=await n}catch(e){i=e}if(!h)throw new r.WorkboxError(\"no-response\",{url:t.url,error:i});return h}async u({request:e,event:t}){const r=await n.fetchWrapper.fetch({request:e,event:t,fetchOptions:this.i,plugins:this.s}),i=s.cacheWrapper.put({cacheName:this.t,request:e,response:r.clone(),event:t,plugins:this.s});if(t)try{t.waitUntil(i)}catch(e){}return r}}const l={cacheFirst:i,cacheOnly:h,networkFirst:a,networkOnly:c,staleWhileRevalidate:o},q=e=>{const t=l[e];return e=>new t(e)},w=q(\"cacheFirst\"),p=q(\"cacheOnly\"),v=q(\"networkFirst\"),y=q(\"networkOnly\"),m=q(\"staleWhileRevalidate\");return e.CacheFirst=i,e.CacheOnly=h,e.NetworkFirst=a,e.NetworkOnly=c,e.StaleWhileRevalidate=o,e.cacheFirst=w,e.cacheOnly=p,e.networkFirst=v,e.networkOnly=y,e.staleWhileRevalidate=m,e}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);\n//# sourceMappingURL=workbox-strategies.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-streams.dev.js",
    "content": "this.workbox = this.workbox || {};\nthis.workbox.streams = (function (exports, logger_mjs, assert_mjs) {\n  'use strict';\n\n  try {\n    self['workbox:streams:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Takes either a Response, a ReadableStream, or a\n   * [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the\n   * ReadableStreamReader object associated with it.\n   *\n   * @param {workbox.streams.StreamSource} source\n   * @return {ReadableStreamReader}\n   * @private\n   */\n\n  function _getReaderFromSource(source) {\n    if (source.body && source.body.getReader) {\n      return source.body.getReader();\n    }\n\n    if (source.getReader) {\n      return source.getReader();\n    } // TODO: This should be possible to do by constructing a ReadableStream, but\n    // I can't get it to work. As a hack, construct a new Response, and use the\n    // reader associated with its body.\n\n\n    return new Response(source).body.getReader();\n  }\n  /**\n   * Takes multiple source Promises, each of which could resolve to a Response, a\n   * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).\n   *\n   * Returns an object exposing a ReadableStream with each individual stream's\n   * data returned in sequence, along with a Promise which signals when the\n   * stream is finished (useful for passing to a FetchEvent's waitUntil()).\n   *\n   * @param {Array<Promise<workbox.streams.StreamSource>>} sourcePromises\n   * @return {Object<{done: Promise, stream: ReadableStream}>}\n   *\n   * @memberof workbox.streams\n   */\n\n\n  function concatenate(sourcePromises) {\n    {\n      assert_mjs.assert.isArray(sourcePromises, {\n        moduleName: 'workbox-streams',\n        funcName: 'concatenate',\n        paramName: 'sourcePromises'\n      });\n    }\n\n    const readerPromises = sourcePromises.map(sourcePromise => {\n      return Promise.resolve(sourcePromise).then(source => {\n        return _getReaderFromSource(source);\n      });\n    });\n    let fullyStreamedResolve;\n    let fullyStreamedReject;\n    const done = new Promise((resolve, reject) => {\n      fullyStreamedResolve = resolve;\n      fullyStreamedReject = reject;\n    });\n    let i = 0;\n    const logMessages = [];\n    const stream = new ReadableStream({\n      pull(controller) {\n        return readerPromises[i].then(reader => reader.read()).then(result => {\n          if (result.done) {\n            {\n              logMessages.push(['Reached the end of source:', sourcePromises[i]]);\n            }\n\n            i++;\n\n            if (i >= readerPromises.length) {\n              // Log all the messages in the group at once in a single group.\n              {\n                logger_mjs.logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);\n\n                for (const message of logMessages) {\n                  if (Array.isArray(message)) {\n                    logger_mjs.logger.log(...message);\n                  } else {\n                    logger_mjs.logger.log(message);\n                  }\n                }\n\n                logger_mjs.logger.log('Finished reading all sources.');\n                logger_mjs.logger.groupEnd();\n              }\n\n              controller.close();\n              fullyStreamedResolve();\n              return;\n            }\n\n            return this.pull(controller);\n          } else {\n            controller.enqueue(result.value);\n          }\n        }).catch(error => {\n          {\n            logger_mjs.logger.error('An error occurred:', error);\n          }\n\n          fullyStreamedReject(error);\n          throw error;\n        });\n      },\n\n      cancel() {\n        {\n          logger_mjs.logger.warn('The ReadableStream was cancelled.');\n        }\n\n        fullyStreamedResolve();\n      }\n\n    });\n    return {\n      done,\n      stream\n    };\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * This is a utility method that determines whether the current browser supports\n   * the features required to create streamed responses. Currently, it checks if\n   * [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)\n   * is available.\n   *\n   * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,\n   * `'text/html'` will be used by default.\n   * @return {boolean} `true`, if the current browser meets the requirements for\n   * streaming responses, and `false` otherwise.\n   *\n   * @memberof workbox.streams\n   */\n\n  function createHeaders(headersInit = {}) {\n    // See https://github.com/GoogleChrome/workbox/issues/1461\n    const headers = new Headers(headersInit);\n\n    if (!headers.has('content-type')) {\n      headers.set('content-type', 'text/html');\n    }\n\n    return headers;\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Takes multiple source Promises, each of which could resolve to a Response, a\n   * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),\n   * along with a\n   * [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).\n   *\n   * Returns an object exposing a Response whose body consists of each individual\n   * stream's data returned in sequence, along with a Promise which signals when\n   * the stream is finished (useful for passing to a FetchEvent's waitUntil()).\n   *\n   * @param {Array<Promise<workbox.streams.StreamSource>>} sourcePromises\n   * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,\n   * `'text/html'` will be used by default.\n   * @return {Object<{done: Promise, response: Response}>}\n   *\n   * @memberof workbox.streams\n   */\n\n  function concatenateToResponse(sourcePromises, headersInit) {\n    const {\n      done,\n      stream\n    } = concatenate(sourcePromises);\n    const headers = createHeaders(headersInit);\n    const response = new Response(stream, {\n      headers\n    });\n    return {\n      done,\n      response\n    };\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  let cachedIsSupported = undefined;\n  /**\n   * This is a utility method that determines whether the current browser supports\n   * the features required to create streamed responses. Currently, it checks if\n   * [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)\n   * can be created.\n   *\n   * @return {boolean} `true`, if the current browser meets the requirements for\n   * streaming responses, and `false` otherwise.\n   *\n   * @memberof workbox.streams\n   */\n\n  function isSupported() {\n    if (cachedIsSupported === undefined) {\n      // See https://github.com/GoogleChrome/workbox/issues/1473\n      try {\n        new ReadableStream({\n          start() {}\n\n        });\n        cachedIsSupported = true;\n      } catch (error) {\n        cachedIsSupported = false;\n      }\n    }\n\n    return cachedIsSupported;\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * A shortcut to create a strategy that could be dropped-in to Workbox's router.\n   *\n   * On browsers that do not support constructing new `ReadableStream`s, this\n   * strategy will automatically wait for all the `sourceFunctions` to complete,\n   * and create a final response that concatenates their values together.\n   *\n   * @param {\n   *   Array<function(workbox.routing.Route~handlerCallback)>} sourceFunctions\n   * Each function should return a {@link workbox.streams.StreamSource} (or a\n   * Promise which resolves to one).\n   * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,\n   * `'text/html'` will be used by default.\n   * @return {workbox.routing.Route~handlerCallback}\n   *\n   * @memberof workbox.streams\n   */\n\n  function strategy(sourceFunctions, headersInit) {\n    return async ({\n      event,\n      url,\n      params\n    }) => {\n      if (isSupported()) {\n        const {\n          done,\n          response\n        } = concatenateToResponse(sourceFunctions.map(fn => fn({\n          event,\n          url,\n          params\n        })), headersInit);\n        event.waitUntil(done);\n        return response;\n      }\n\n      {\n        logger_mjs.logger.log(`The current browser doesn't support creating response ` + `streams. Falling back to non-streaming response instead.`);\n      } // Fallback to waiting for everything to finish, and concatenating the\n      // responses.\n\n\n      const parts = await Promise.all(sourceFunctions.map(sourceFunction => sourceFunction({\n        event,\n        url,\n        params\n      })).map(async responsePromise => {\n        const response = await responsePromise;\n\n        if (response instanceof Response) {\n          return response.blob();\n        } // Otherwise, assume it's something like a string which can be used\n        // as-is when constructing the final composite blob.\n\n\n        return response;\n      }));\n      const headers = createHeaders(headersInit); // Constructing a new Response from a Blob source is well-supported.\n      // So is constructing a new Blob from multiple source Blobs or strings.\n\n      return new Response(new Blob(parts), {\n        headers\n      });\n    };\n  }\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.concatenate = concatenate;\n  exports.concatenateToResponse = concatenateToResponse;\n  exports.isSupported = isSupported;\n  exports.strategy = strategy;\n\n  return exports;\n\n}({}, workbox.core._private, workbox.core._private));\n//# sourceMappingURL=workbox-streams.dev.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-streams.prod.js",
    "content": "this.workbox=this.workbox||{},this.workbox.streams=function(e){\"use strict\";try{self[\"workbox:streams:4.1.1\"]&&_()}catch(e){}function n(e){const n=e.map(e=>Promise.resolve(e).then(e=>(function(e){return e.body&&e.body.getReader?e.body.getReader():e.getReader?e.getReader():new Response(e).body.getReader()})(e)));let t,r;const s=new Promise((e,n)=>{t=e,r=n});let o=0;return{done:s,stream:new ReadableStream({pull(e){return n[o].then(e=>e.read()).then(r=>{if(r.done)return++o>=n.length?(e.close(),void t()):this.pull(e);e.enqueue(r.value)}).catch(e=>{throw r(e),e})},cancel(){t()}})}}function t(e={}){const n=new Headers(e);return n.has(\"content-type\")||n.set(\"content-type\",\"text/html\"),n}function r(e,r){const{done:s,stream:o}=n(e),a=t(r);return{done:s,response:new Response(o,{headers:a})}}let s=void 0;function o(){if(void 0===s)try{new ReadableStream({start(){}}),s=!0}catch(e){s=!1}return s}return e.concatenate=n,e.concatenateToResponse=r,e.isSupported=o,e.strategy=function(e,n){return async({event:s,url:a,params:c})=>{if(o()){const{done:t,response:o}=r(e.map(e=>e({event:s,url:a,params:c})),n);return s.waitUntil(t),o}const i=await Promise.all(e.map(e=>e({event:s,url:a,params:c})).map(async e=>{const n=await e;return n instanceof Response?n.blob():n})),u=t(n);return new Response(new Blob(i),{headers:u})}},e}({});\n//# sourceMappingURL=workbox-streams.prod.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-sw.js",
    "content": "!function(){\"use strict\";try{self[\"workbox:sw:4.1.1\"]&&_()}catch(t){}const t=\"https://storage.googleapis.com/workbox-cdn/releases/4.1.1\",e={backgroundSync:\"background-sync\",broadcastUpdate:\"broadcast-update\",cacheableResponse:\"cacheable-response\",core:\"core\",expiration:\"expiration\",googleAnalytics:\"offline-ga\",navigationPreload:\"navigation-preload\",precaching:\"precaching\",rangeRequests:\"range-requests\",routing:\"routing\",strategies:\"strategies\",streams:\"streams\"};self.workbox=new class{constructor(){return this.v={},this.t={debug:\"localhost\"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.s=this.t.debug?\"dev\":\"prod\",this.o=!1,new Proxy(this,{get(t,s){if(t[s])return t[s];const o=e[s];return o&&t.loadModule(`workbox-${o}`),t[s]}})}setConfig(t={}){if(this.o)throw new Error(\"Config must be set before accessing workbox.* modules\");Object.assign(this.t,t),this.s=this.t.debug?\"dev\":\"prod\"}loadModule(t){const e=this.i(t);try{importScripts(e),this.o=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}i(e){if(this.t.modulePathCb)return this.t.modulePathCb(e,this.t.debug);let s=[t];const o=`${e}.${this.s}.js`,r=this.t.modulePathPrefix;return r&&\"\"===(s=r.split(\"/\"))[s.length-1]&&s.splice(s.length-1,1),s.push(o),s.join(\"/\")}}}();\n//# sourceMappingURL=workbox-sw.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-window.dev.es5.mjs",
    "content": "try {\n  self['workbox:window:4.1.1'] && _();\n} catch (e) {} // eslint-disable-line\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * Sends a data object to a service worker via `postMessage` and resolves with\n * a response (if any).\n *\n * A response can be set in a message handler in the service worker by\n * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n * returned by `messageSW()`. If no response is set, the promise will not\n * resolve.\n *\n * @param {ServiceWorker} sw The service worker to send the message to.\n * @param {Object} data An object to send to the service worker.\n * @return {Promise<Object|undefined>}\n *\n * @memberof module:workbox-window\n */\n\nvar messageSW = function messageSW(sw, data) {\n  return new Promise(function (resolve) {\n    var messageChannel = new MessageChannel();\n\n    messageChannel.port1.onmessage = function (evt) {\n      return resolve(evt.data);\n    };\n\n    sw.postMessage(data, [messageChannel.port2]);\n  });\n};\n\nfunction _defineProperties(target, props) {\n  for (var i = 0; i < props.length; i++) {\n    var descriptor = props[i];\n    descriptor.enumerable = descriptor.enumerable || false;\n    descriptor.configurable = true;\n    if (\"value\" in descriptor) descriptor.writable = true;\n    Object.defineProperty(target, descriptor.key, descriptor);\n  }\n}\n\nfunction _createClass(Constructor, protoProps, staticProps) {\n  if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n  if (staticProps) _defineProperties(Constructor, staticProps);\n  return Constructor;\n}\n\nfunction _inheritsLoose(subClass, superClass) {\n  subClass.prototype = Object.create(superClass.prototype);\n  subClass.prototype.constructor = subClass;\n  subClass.__proto__ = superClass;\n}\n\nfunction _assertThisInitialized(self) {\n  if (self === void 0) {\n    throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");\n  }\n\n  return self;\n}\n\ntry {\n  self['workbox:core:4.1.1'] && _();\n} catch (e) {} // eslint-disable-line\n\n/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * The Deferred class composes Promises in a way that allows for them to be\n * resolved or rejected from outside the constructor. In most cases promises\n * should be used directly, but Deferreds can be necessary when the logic to\n * resolve a promise must be separate.\n *\n * @private\n */\n\nvar Deferred =\n/**\n * Creates a promise and exposes its resolve and reject functions as methods.\n */\nfunction Deferred() {\n  var _this = this;\n\n  this.promise = new Promise(function (resolve, reject) {\n    _this.resolve = resolve;\n    _this.reject = reject;\n  });\n};\n\n/*\n  Copyright 2019 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nvar logger = function () {\n  var inGroup = false;\n  var methodToColorMap = {\n    debug: \"#7f8c8d\",\n    // Gray\n    log: \"#2ecc71\",\n    // Green\n    warn: \"#f39c12\",\n    // Yellow\n    error: \"#c0392b\",\n    // Red\n    groupCollapsed: \"#3498db\",\n    // Blue\n    groupEnd: null // No colored prefix on groupEnd\n\n  };\n\n  var print = function print(method, args) {\n    var _console2;\n\n    if (method === 'groupCollapsed') {\n      // Safari doesn't print all console.groupCollapsed() arguments:\n      // https://bugs.webkit.org/show_bug.cgi?id=182754\n      if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n        var _console;\n\n        (_console = console)[method].apply(_console, args);\n\n        return;\n      }\n    }\n\n    var styles = [\"background: \" + methodToColorMap[method], \"border-radius: 0.5em\", \"color: white\", \"font-weight: bold\", \"padding: 2px 0.5em\"]; // When in a group, the workbox prefix is not displayed.\n\n    var logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n\n    (_console2 = console)[method].apply(_console2, logPrefix.concat(args));\n\n    if (method === 'groupCollapsed') {\n      inGroup = true;\n    }\n\n    if (method === 'groupEnd') {\n      inGroup = false;\n    }\n  };\n\n  var api = {};\n\n  var _arr = Object.keys(methodToColorMap);\n\n  var _loop = function _loop() {\n    var method = _arr[_i];\n\n    api[method] = function () {\n      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n        args[_key] = arguments[_key];\n      }\n\n      print(method, args);\n    };\n  };\n\n  for (var _i = 0; _i < _arr.length; _i++) {\n    _loop();\n  }\n\n  return api;\n}();\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * A minimal `EventTarget` shim.\n * This is necessary because not all browsers support constructable\n * `EventTarget`, so using a real `EventTarget` will error.\n * @private\n */\n\nvar EventTargetShim =\n/*#__PURE__*/\nfunction () {\n  /**\n   * Creates an event listener registry\n   */\n  function EventTargetShim() {\n    // A registry of event types to listeners.\n    this._eventListenerRegistry = {};\n  }\n  /**\n   * @param {string} type\n   * @param {Function} listener\n   */\n\n\n  var _proto = EventTargetShim.prototype;\n\n  _proto.addEventListener = function addEventListener(type, listener) {\n    this._getEventListenersByType(type).add(listener);\n  };\n  /**\n   * @param {string} type\n   * @param {Function} listener\n   */\n\n\n  _proto.removeEventListener = function removeEventListener(type, listener) {\n    this._getEventListenersByType(type).delete(listener);\n  };\n  /**\n   * @param {Event} event\n   */\n\n\n  _proto.dispatchEvent = function dispatchEvent(event) {\n    event.target = this;\n\n    this._getEventListenersByType(event.type).forEach(function (listener) {\n      return listener(event);\n    });\n  };\n  /**\n   * Returns a Set of listeners associated with the passed event type.\n   * If no handlers have been registered, an empty Set is returned.\n   *\n   * @param {string} type The event type.\n   * @return {Set} An array of handler functions.\n   */\n\n\n  _proto._getEventListenersByType = function _getEventListenersByType(type) {\n    return this._eventListenerRegistry[type] = this._eventListenerRegistry[type] || new Set();\n  };\n\n  return EventTargetShim;\n}();\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * Returns true if two URLs have the same `.href` property. The URLS can be\n * relative, and if they are the current location href is used to resolve URLs.\n *\n * @private\n * @param {string} url1\n * @param {string} url2\n * @return {boolean}\n */\n\nvar urlsMatch = function urlsMatch(url1, url2) {\n  return new URL(url1, location).href === new URL(url2, location).href;\n};\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * A minimal `Event` subclass shim.\n * This doesn't *actually* subclass `Event` because not all browsers support\n * constructable `EventTarget`, and using a real `Event` will error.\n * @private\n */\n\nvar WorkboxEvent =\n/**\n * @param {string} type\n * @param {Object} props\n */\nfunction WorkboxEvent(type, props) {\n  Object.assign(this, props, {\n    type: type\n  });\n};\n\nfunction _catch(body, recover) {\n  try {\n    var result = body();\n  } catch (e) {\n    return recover(e);\n  }\n\n  if (result && result.then) {\n    return result.then(void 0, recover);\n  }\n\n  return result;\n}\n\nfunction _async(f) {\n  return function () {\n    for (var args = [], i = 0; i < arguments.length; i++) {\n      args[i] = arguments[i];\n    }\n\n    try {\n      return Promise.resolve(f.apply(this, args));\n    } catch (e) {\n      return Promise.reject(e);\n    }\n  };\n}\n\nfunction _invoke(body, then) {\n  var result = body();\n\n  if (result && result.then) {\n    return result.then(then);\n  }\n\n  return then(result);\n}\n\nfunction _await(value, then, direct) {\n  if (direct) {\n    return then ? then(value) : value;\n  }\n\n  if (!value || !value.then) {\n    value = Promise.resolve(value);\n  }\n\n  return then ? value.then(then) : value;\n}\n\nfunction _awaitIgnored(value, direct) {\n  if (!direct) {\n    return value && value.then ? value.then(_empty) : Promise.resolve();\n  }\n}\n\nfunction _empty() {}\n// `skipWaiting()` wasn't called. This 200 amount wasn't scientifically\n// chosen, but it seems to avoid false positives in my testing.\n\nvar WAITING_TIMEOUT_DURATION = 200; // The amount of time after a registration that we can reasonably conclude\n// that the registration didn't trigger an update.\n\nvar REGISTRATION_TIMEOUT_DURATION = 60000;\n/**\n * A class to aid in handling service worker registration, updates, and\n * reacting to service worker lifecycle events.\n *\n * @fires [message]{@link module:workbox-window.Workbox#message}\n * @fires [installed]{@link module:workbox-window.Workbox#installed}\n * @fires [waiting]{@link module:workbox-window.Workbox#waiting}\n * @fires [controlling]{@link module:workbox-window.Workbox#controlling}\n * @fires [activated]{@link module:workbox-window.Workbox#activated}\n * @fires [redundant]{@link module:workbox-window.Workbox#redundant}\n * @fires [externalinstalled]{@link module:workbox-window.Workbox#externalinstalled}\n * @fires [externalwaiting]{@link module:workbox-window.Workbox#externalwaiting}\n * @fires [externalactivated]{@link module:workbox-window.Workbox#externalactivated}\n *\n * @memberof module:workbox-window\n */\n\nvar Workbox =\n/*#__PURE__*/\nfunction (_EventTargetShim) {\n  _inheritsLoose(Workbox, _EventTargetShim);\n\n  /**\n   * Creates a new Workbox instance with a script URL and service worker\n   * options. The script URL and options are the same as those used when\n   * calling `navigator.serviceWorker.register(scriptURL, options)`. See:\n   * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register\n   *\n   * @param {string} scriptURL The service worker script associated with this\n   *     instance.\n   * @param {Object} [registerOptions] The service worker options associated\n   *     with this instance.\n   */\n  function Workbox(scriptURL, registerOptions) {\n    var _this;\n\n    if (registerOptions === void 0) {\n      registerOptions = {};\n    }\n\n    _this = _EventTargetShim.call(this) || this;\n    _this._scriptURL = scriptURL;\n    _this._registerOptions = registerOptions;\n    _this._updateFoundCount = 0; // Deferreds we can resolve later.\n\n    _this._swDeferred = new Deferred();\n    _this._activeDeferred = new Deferred();\n    _this._controllingDeferred = new Deferred(); // Bind event handler callbacks.\n\n    _this._onMessage = _this._onMessage.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n    _this._onStateChange = _this._onStateChange.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n    _this._onUpdateFound = _this._onUpdateFound.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n    _this._onControllerChange = _this._onControllerChange.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n    return _this;\n  }\n  /**\n   * Registers a service worker for this instances script URL and service\n   * worker options. By default this method delays registration until after\n   * the window has loaded.\n   *\n   * @param {Object} [options]\n   * @param {Function} [options.immediate=false] Setting this to true will\n   *     register the service worker immediately, even if the window has\n   *     not loaded (not recommended).\n   */\n\n\n  var _proto = Workbox.prototype;\n  _proto.register = _async(function (_temp) {\n    var _this2 = this;\n\n    var _ref = _temp === void 0 ? {} : _temp,\n        _ref$immediate = _ref.immediate,\n        immediate = _ref$immediate === void 0 ? false : _ref$immediate;\n\n    {\n      if (_this2._registrationTime) {\n        logger.error('Cannot re-register a Workbox instance after it has ' + 'been registered. Create a new instance instead.');\n        return;\n      }\n    }\n\n    return _invoke(function () {\n      if (!immediate && document.readyState !== 'complete') {\n        return _awaitIgnored(new Promise(function (res) {\n          return addEventListener('load', res);\n        }));\n      }\n    }, function () {\n      // Set this flag to true if any service worker was controlling the page\n      // at registration time.\n      _this2._isUpdate = Boolean(navigator.serviceWorker.controller); // Before registering, attempt to determine if a SW is already controlling\n      // the page, and if that SW script (and version, if specified) matches this\n      // instance's script.\n\n      _this2._compatibleControllingSW = _this2._getControllingSWIfCompatible();\n      return _await(_this2._registerScript(), function (_this2$_registerScrip) {\n        _this2._registration = _this2$_registerScrip;\n\n        // If we have a compatible controller, store the controller as the \"own\"\n        // SW, resolve active/controlling deferreds and add necessary listeners.\n        if (_this2._compatibleControllingSW) {\n          _this2._sw = _this2._compatibleControllingSW;\n\n          _this2._activeDeferred.resolve(_this2._compatibleControllingSW);\n\n          _this2._controllingDeferred.resolve(_this2._compatibleControllingSW);\n\n          _this2._reportWindowReady(_this2._compatibleControllingSW);\n\n          _this2._compatibleControllingSW.addEventListener('statechange', _this2._onStateChange, {\n            once: true\n          });\n        } // If there's a waiting service worker with a matching URL before the\n        // `updatefound` event fires, it likely means that this site is open\n        // in another tab, or the user refreshed the page (and thus the prevoius\n        // page wasn't fully unloaded before this page started loading).\n        // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting\n\n\n        var waitingSW = _this2._registration.waiting;\n\n        if (waitingSW && urlsMatch(waitingSW.scriptURL, _this2._scriptURL)) {\n          // Store the waiting SW as the \"own\" Sw, even if it means overwriting\n          // a compatible controller.\n          _this2._sw = waitingSW; // Run this in the next microtask, so any code that adds an event\n          // listener after awaiting `register()` will get this event.\n\n          Promise.resolve().then(function () {\n            _this2.dispatchEvent(new WorkboxEvent('waiting', {\n              sw: waitingSW,\n              wasWaitingBeforeRegister: true\n            }));\n\n            {\n              logger.warn('A service worker was already waiting to activate ' + 'before this script was registered...');\n            }\n          });\n        } // If an \"own\" SW is already set, resolve the deferred.\n\n\n        if (_this2._sw) {\n          _this2._swDeferred.resolve(_this2._sw);\n        }\n\n        {\n          logger.log('Successfully registered service worker.', _this2._scriptURL);\n\n          if (navigator.serviceWorker.controller) {\n            if (_this2._compatibleControllingSW) {\n              logger.debug('A service worker with the same script URL ' + 'is already controlling this page.');\n            } else {\n              logger.debug('A service worker with a different script URL is ' + 'currently controlling the page. The browser is now fetching ' + 'the new script now...');\n            }\n          }\n\n          var currentPageIsOutOfScope = function currentPageIsOutOfScope() {\n            var scopeURL = new URL(_this2._registerOptions.scope || _this2._scriptURL, document.baseURI);\n            var scopeURLBasePath = new URL('./', scopeURL.href).pathname;\n            return !location.pathname.startsWith(scopeURLBasePath);\n          };\n\n          if (currentPageIsOutOfScope()) {\n            logger.warn('The current page is not in scope for the registered ' + 'service worker. Was this a mistake?');\n          }\n        }\n\n        _this2._registration.addEventListener('updatefound', _this2._onUpdateFound);\n\n        navigator.serviceWorker.addEventListener('controllerchange', _this2._onControllerChange, {\n          once: true\n        }); // Add message listeners.\n\n        if ('BroadcastChannel' in self) {\n          _this2._broadcastChannel = new BroadcastChannel('workbox');\n\n          _this2._broadcastChannel.addEventListener('message', _this2._onMessage);\n        }\n\n        navigator.serviceWorker.addEventListener('message', _this2._onMessage);\n        return _this2._registration;\n      });\n    });\n  });\n  /**\n   * Resolves to the service worker registered by this instance as soon as it\n   * is active. If a service worker was already controlling at registration\n   * time then it will resolve to that if the script URLs (and optionally\n   * script versions) match, otherwise it will wait until an update is found\n   * and activates.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n\n  /**\n   * Resolves with a reference to a service worker that matches the script URL\n   * of this instance, as soon as it's available.\n   *\n   * If, at registration time, there's already an active or waiting service\n   * worker with a matching script URL, it will be used (with the waiting\n   * service worker taking precedence over the active service worker if both\n   * match, since the waiting service worker would have been registered more\n   * recently).\n   * If there's no matching active or waiting service worker at registration\n   * time then the promise will not resolve until an update is found and starts\n   * installing, at which point the installing service worker is used.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n  _proto.getSW = _async(function () {\n    var _this3 = this;\n\n    // If `this._sw` is set, resolve with that as we want `getSW()` to\n    // return the correct (new) service worker if an update is found.\n    return _this3._sw || _this3._swDeferred.promise;\n  });\n  /**\n   * Sends the passed data object to the service worker registered by this\n   * instance (via [`getSW()`]{@link module:workbox-window.Workbox#getSW}) and resolves\n   * with a response (if any).\n   *\n   * A response can be set in a message handler in the service worker by\n   * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n   * returned by `messageSW()`. If no response is set, the promise will never\n   * resolve.\n   *\n   * @param {Object} data An object to send to the service worker\n   * @return {Promise<Object>}\n   */\n\n  _proto.messageSW = _async(function (data) {\n    var _this4 = this;\n\n    return _await(_this4.getSW(), function (sw) {\n      return messageSW(sw, data);\n    });\n  });\n  /**\n   * Checks for a service worker already controlling the page and returns\n   * it if its script URL matchs.\n   *\n   * @private\n   * @return {ServiceWorker|undefined}\n   */\n\n  _proto._getControllingSWIfCompatible = function _getControllingSWIfCompatible() {\n    var controller = navigator.serviceWorker.controller;\n\n    if (controller && urlsMatch(controller.scriptURL, this._scriptURL)) {\n      return controller;\n    }\n  };\n  /**\n   * Registers a service worker for this instances script URL and register\n   * options and tracks the time registration was complete.\n   *\n   * @private\n   */\n\n\n  _proto._registerScript = _async(function () {\n    var _this5 = this;\n\n    return _catch(function () {\n      return _await(navigator.serviceWorker.register(_this5._scriptURL, _this5._registerOptions), function (reg) {\n        // Keep track of when registration happened, so it can be used in the\n        // `this._onUpdateFound` heuristic. Also use the presence of this\n        // property as a way to see if `.register()` has been called.\n        _this5._registrationTime = performance.now();\n        return reg;\n      });\n    }, function (error) {\n      {\n        logger.error(error);\n      } // Re-throw the error.\n\n\n      throw error;\n    });\n  });\n  /**\n   * Sends a message to the passed service worker that the window is ready.\n   *\n   * @param {ServiceWorker} sw\n   * @private\n   */\n\n  _proto._reportWindowReady = function _reportWindowReady(sw) {\n    messageSW(sw, {\n      type: 'WINDOW_READY',\n      meta: 'workbox-window'\n    });\n  };\n  /**\n   * @private\n   */\n\n\n  _proto._onUpdateFound = function _onUpdateFound() {\n    var installingSW = this._registration.installing; // If the script URL passed to `navigator.serviceWorker.register()` is\n    // different from the current controlling SW's script URL, we know any\n    // successful registration calls will trigger an `updatefound` event.\n    // But if the registered script URL is the same as the current controlling\n    // SW's script URL, we'll only get an `updatefound` event if the file\n    // changed since it was last registered. This can be a problem if the user\n    // opens up the same page in a different tab, and that page registers\n    // a SW that triggers an update. It's a problem because this page has no\n    // good way of knowing whether the `updatefound` event came from the SW\n    // script it registered or from a registration attempt made by a newer\n    // version of the page running in another tab.\n    // To minimize the possibility of a false positive, we use the logic here:\n\n    var updateLikelyTriggeredExternally = // Since we enforce only calling `register()` once, and since we don't\n    // add the `updatefound` event listener until the `register()` call, if\n    // `_updateFoundCount` is > 0 then it means this method has already\n    // been called, thus this SW must be external\n    this._updateFoundCount > 0 || // If the script URL of the installing SW is different from this\n    // instance's script URL, we know it's definitely not from our\n    // registration.\n    !urlsMatch(installingSW.scriptURL, this._scriptURL) || // If all of the above are false, then we use a time-based heuristic:\n    // Any `updatefound` event that occurs long after our registration is\n    // assumed to be external.\n    performance.now() > this._registrationTime + REGISTRATION_TIMEOUT_DURATION ? // If any of the above are not true, we assume the update was\n    // triggered by this instance.\n    true : false;\n\n    if (updateLikelyTriggeredExternally) {\n      this._externalSW = installingSW;\n\n      this._registration.removeEventListener('updatefound', this._onUpdateFound);\n    } else {\n      // If the update was not triggered externally we know the installing\n      // SW is the one we registered, so we set it.\n      this._sw = installingSW;\n\n      this._swDeferred.resolve(installingSW); // The `installing` state isn't something we have a dedicated\n      // callback for, but we do log messages for it in development.\n\n\n      {\n        if (navigator.serviceWorker.controller) {\n          logger.log('Updated service worker found. Installing now...');\n        } else {\n          logger.log('Service worker is installing...');\n        }\n      }\n    } // Increment the `updatefound` count, so future invocations of this\n    // method can be sure they were triggered externally.\n\n\n    ++this._updateFoundCount; // Add a `statechange` listener regardless of whether this update was\n    // triggered externally, since we have callbacks for both.\n\n    installingSW.addEventListener('statechange', this._onStateChange);\n  };\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n\n\n  _proto._onStateChange = function _onStateChange(originalEvent) {\n    var _this6 = this;\n\n    var sw = originalEvent.target;\n    var state = sw.state;\n    var isExternal = sw === this._externalSW;\n    var eventPrefix = isExternal ? 'external' : '';\n    var eventProps = {\n      sw: sw,\n      originalEvent: originalEvent\n    };\n\n    if (!isExternal && this._isUpdate) {\n      eventProps.isUpdate = true;\n    }\n\n    this.dispatchEvent(new WorkboxEvent(eventPrefix + state, eventProps));\n\n    if (state === 'installed') {\n      // This timeout is used to ignore cases where the service worker calls\n      // `skipWaiting()` in the install event, thus moving it directly in the\n      // activating state. (Since all service workers *must* go through the\n      // waiting phase, the only way to detect `skipWaiting()` called in the\n      // install event is to observe that the time spent in the waiting phase\n      // is very short.)\n      // NOTE: we don't need separate timeouts for the own and external SWs\n      // since they can't go through these phases at the same time.\n      this._waitingTimeout = setTimeout(function () {\n        // Ensure the SW is still waiting (it may now be redundant).\n        if (state === 'installed' && _this6._registration.waiting === sw) {\n          _this6.dispatchEvent(new WorkboxEvent(eventPrefix + 'waiting', eventProps));\n\n          {\n            if (isExternal) {\n              logger.warn('An external service worker has installed but is ' + 'waiting for this client to close before activating...');\n            } else {\n              logger.warn('The service worker has installed but is waiting ' + 'for existing clients to close before activating...');\n            }\n          }\n        }\n      }, WAITING_TIMEOUT_DURATION);\n    } else if (state === 'activating') {\n      clearTimeout(this._waitingTimeout);\n\n      if (!isExternal) {\n        this._activeDeferred.resolve(sw);\n      }\n    }\n\n    {\n      switch (state) {\n        case 'installed':\n          if (isExternal) {\n            logger.warn('An external service worker has installed. ' + 'You may want to suggest users reload this page.');\n          } else {\n            logger.log('Registered service worker installed.');\n          }\n\n          break;\n\n        case 'activated':\n          if (isExternal) {\n            logger.warn('An external service worker has activated.');\n          } else {\n            logger.log('Registered service worker activated.');\n\n            if (sw !== navigator.serviceWorker.controller) {\n              logger.warn('The registered service worker is active but ' + 'not yet controlling the page. Reload or run ' + '`clients.claim()` in the service worker.');\n            }\n          }\n\n          break;\n\n        case 'redundant':\n          if (sw === this._compatibleControllingSW) {\n            logger.log('Previously controlling service worker now redundant!');\n          } else if (!isExternal) {\n            logger.log('Registered service worker now redundant!');\n          }\n\n          break;\n      }\n    }\n  };\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n\n\n  _proto._onControllerChange = function _onControllerChange(originalEvent) {\n    var sw = this._sw;\n\n    if (sw === navigator.serviceWorker.controller) {\n      this.dispatchEvent(new WorkboxEvent('controlling', {\n        sw: sw,\n        originalEvent: originalEvent\n      }));\n\n      {\n        logger.log('Registered service worker now controlling this page.');\n      }\n\n      this._controllingDeferred.resolve(sw);\n    }\n  };\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n\n\n  _proto._onMessage = function _onMessage(originalEvent) {\n    var data = originalEvent.data;\n    this.dispatchEvent(new WorkboxEvent('message', {\n      data: data,\n      originalEvent: originalEvent\n    }));\n  };\n\n  _createClass(Workbox, [{\n    key: \"active\",\n    get: function get() {\n      return this._activeDeferred.promise;\n    }\n    /**\n     * Resolves to the service worker registered by this instance as soon as it\n     * is controlling the page. If a service worker was already controlling at\n     * registration time then it will resolve to that if the script URLs (and\n     * optionally script versions) match, otherwise it will wait until an update\n     * is found and starts controlling the page.\n     * Note: the first time a service worker is installed it will active but\n     * not start controlling the page unless `clients.claim()` is called in the\n     * service worker.\n     *\n     * @return {Promise<ServiceWorker>}\n     */\n\n  }, {\n    key: \"controlling\",\n    get: function get() {\n      return this._controllingDeferred.promise;\n    }\n  }]);\n\n  return Workbox;\n}(EventTargetShim); // The jsdoc comments below outline the events this instance may dispatch:\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nexport { Workbox, messageSW };\n//# sourceMappingURL=workbox-window.dev.es5.mjs.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-window.dev.mjs",
    "content": "try {\n  self['workbox:window:4.1.1'] && _();\n} catch (e) {} // eslint-disable-line\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * Sends a data object to a service worker via `postMessage` and resolves with\n * a response (if any).\n *\n * A response can be set in a message handler in the service worker by\n * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n * returned by `messageSW()`. If no response is set, the promise will not\n * resolve.\n *\n * @param {ServiceWorker} sw The service worker to send the message to.\n * @param {Object} data An object to send to the service worker.\n * @return {Promise<Object|undefined>}\n *\n * @memberof module:workbox-window\n */\n\nconst messageSW = (sw, data) => {\n  return new Promise(resolve => {\n    let messageChannel = new MessageChannel();\n\n    messageChannel.port1.onmessage = evt => resolve(evt.data);\n\n    sw.postMessage(data, [messageChannel.port2]);\n  });\n};\n\ntry {\n  self['workbox:core:4.1.1'] && _();\n} catch (e) {} // eslint-disable-line\n\n/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * The Deferred class composes Promises in a way that allows for them to be\n * resolved or rejected from outside the constructor. In most cases promises\n * should be used directly, but Deferreds can be necessary when the logic to\n * resolve a promise must be separate.\n *\n * @private\n */\n\nclass Deferred {\n  /**\n   * Creates a promise and exposes its resolve and reject functions as methods.\n   */\n  constructor() {\n    this.promise = new Promise((resolve, reject) => {\n      this.resolve = resolve;\n      this.reject = reject;\n    });\n  }\n\n}\n\n/*\n  Copyright 2019 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nconst logger = (() => {\n  let inGroup = false;\n  const methodToColorMap = {\n    debug: `#7f8c8d`,\n    // Gray\n    log: `#2ecc71`,\n    // Green\n    warn: `#f39c12`,\n    // Yellow\n    error: `#c0392b`,\n    // Red\n    groupCollapsed: `#3498db`,\n    // Blue\n    groupEnd: null // No colored prefix on groupEnd\n\n  };\n\n  const print = function (method, args) {\n    if (method === 'groupCollapsed') {\n      // Safari doesn't print all console.groupCollapsed() arguments:\n      // https://bugs.webkit.org/show_bug.cgi?id=182754\n      if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n        console[method](...args);\n        return;\n      }\n    }\n\n    const styles = [`background: ${methodToColorMap[method]}`, `border-radius: 0.5em`, `color: white`, `font-weight: bold`, `padding: 2px 0.5em`]; // When in a group, the workbox prefix is not displayed.\n\n    const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n    console[method](...logPrefix, ...args);\n\n    if (method === 'groupCollapsed') {\n      inGroup = true;\n    }\n\n    if (method === 'groupEnd') {\n      inGroup = false;\n    }\n  };\n\n  const api = {};\n\n  for (const method of Object.keys(methodToColorMap)) {\n    api[method] = (...args) => {\n      print(method, args);\n    };\n  }\n\n  return api;\n})();\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * A minimal `EventTarget` shim.\n * This is necessary because not all browsers support constructable\n * `EventTarget`, so using a real `EventTarget` will error.\n * @private\n */\n\nclass EventTargetShim {\n  /**\n   * Creates an event listener registry\n   */\n  constructor() {\n    // A registry of event types to listeners.\n    this._eventListenerRegistry = {};\n  }\n  /**\n   * @param {string} type\n   * @param {Function} listener\n   */\n\n\n  addEventListener(type, listener) {\n    this._getEventListenersByType(type).add(listener);\n  }\n  /**\n   * @param {string} type\n   * @param {Function} listener\n   */\n\n\n  removeEventListener(type, listener) {\n    this._getEventListenersByType(type).delete(listener);\n  }\n  /**\n   * @param {Event} event\n   */\n\n\n  dispatchEvent(event) {\n    event.target = this;\n\n    this._getEventListenersByType(event.type).forEach(listener => listener(event));\n  }\n  /**\n   * Returns a Set of listeners associated with the passed event type.\n   * If no handlers have been registered, an empty Set is returned.\n   *\n   * @param {string} type The event type.\n   * @return {Set} An array of handler functions.\n   */\n\n\n  _getEventListenersByType(type) {\n    return this._eventListenerRegistry[type] = this._eventListenerRegistry[type] || new Set();\n  }\n\n}\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * Returns true if two URLs have the same `.href` property. The URLS can be\n * relative, and if they are the current location href is used to resolve URLs.\n *\n * @private\n * @param {string} url1\n * @param {string} url2\n * @return {boolean}\n */\n\nconst urlsMatch = (url1, url2) => {\n  return new URL(url1, location).href === new URL(url2, location).href;\n};\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n/**\n * A minimal `Event` subclass shim.\n * This doesn't *actually* subclass `Event` because not all browsers support\n * constructable `EventTarget`, and using a real `Event` will error.\n * @private\n */\n\nclass WorkboxEvent {\n  /**\n   * @param {string} type\n   * @param {Object} props\n   */\n  constructor(type, props) {\n    Object.assign(this, props, {\n      type\n    });\n  }\n\n}\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n// `skipWaiting()` wasn't called. This 200 amount wasn't scientifically\n// chosen, but it seems to avoid false positives in my testing.\n\nconst WAITING_TIMEOUT_DURATION = 200; // The amount of time after a registration that we can reasonably conclude\n// that the registration didn't trigger an update.\n\nconst REGISTRATION_TIMEOUT_DURATION = 60000;\n/**\n * A class to aid in handling service worker registration, updates, and\n * reacting to service worker lifecycle events.\n *\n * @fires [message]{@link module:workbox-window.Workbox#message}\n * @fires [installed]{@link module:workbox-window.Workbox#installed}\n * @fires [waiting]{@link module:workbox-window.Workbox#waiting}\n * @fires [controlling]{@link module:workbox-window.Workbox#controlling}\n * @fires [activated]{@link module:workbox-window.Workbox#activated}\n * @fires [redundant]{@link module:workbox-window.Workbox#redundant}\n * @fires [externalinstalled]{@link module:workbox-window.Workbox#externalinstalled}\n * @fires [externalwaiting]{@link module:workbox-window.Workbox#externalwaiting}\n * @fires [externalactivated]{@link module:workbox-window.Workbox#externalactivated}\n *\n * @memberof module:workbox-window\n */\n\nclass Workbox extends EventTargetShim {\n  /**\n   * Creates a new Workbox instance with a script URL and service worker\n   * options. The script URL and options are the same as those used when\n   * calling `navigator.serviceWorker.register(scriptURL, options)`. See:\n   * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register\n   *\n   * @param {string} scriptURL The service worker script associated with this\n   *     instance.\n   * @param {Object} [registerOptions] The service worker options associated\n   *     with this instance.\n   */\n  constructor(scriptURL, registerOptions = {}) {\n    super();\n    this._scriptURL = scriptURL;\n    this._registerOptions = registerOptions;\n    this._updateFoundCount = 0; // Deferreds we can resolve later.\n\n    this._swDeferred = new Deferred();\n    this._activeDeferred = new Deferred();\n    this._controllingDeferred = new Deferred(); // Bind event handler callbacks.\n\n    this._onMessage = this._onMessage.bind(this);\n    this._onStateChange = this._onStateChange.bind(this);\n    this._onUpdateFound = this._onUpdateFound.bind(this);\n    this._onControllerChange = this._onControllerChange.bind(this);\n  }\n  /**\n   * Registers a service worker for this instances script URL and service\n   * worker options. By default this method delays registration until after\n   * the window has loaded.\n   *\n   * @param {Object} [options]\n   * @param {Function} [options.immediate=false] Setting this to true will\n   *     register the service worker immediately, even if the window has\n   *     not loaded (not recommended).\n   */\n\n\n  async register({\n    immediate = false\n  } = {}) {\n    {\n      if (this._registrationTime) {\n        logger.error('Cannot re-register a Workbox instance after it has ' + 'been registered. Create a new instance instead.');\n        return;\n      }\n    }\n\n    if (!immediate && document.readyState !== 'complete') {\n      await new Promise(res => addEventListener('load', res));\n    } // Set this flag to true if any service worker was controlling the page\n    // at registration time.\n\n\n    this._isUpdate = Boolean(navigator.serviceWorker.controller); // Before registering, attempt to determine if a SW is already controlling\n    // the page, and if that SW script (and version, if specified) matches this\n    // instance's script.\n\n    this._compatibleControllingSW = this._getControllingSWIfCompatible();\n    this._registration = await this._registerScript(); // If we have a compatible controller, store the controller as the \"own\"\n    // SW, resolve active/controlling deferreds and add necessary listeners.\n\n    if (this._compatibleControllingSW) {\n      this._sw = this._compatibleControllingSW;\n\n      this._activeDeferred.resolve(this._compatibleControllingSW);\n\n      this._controllingDeferred.resolve(this._compatibleControllingSW);\n\n      this._reportWindowReady(this._compatibleControllingSW);\n\n      this._compatibleControllingSW.addEventListener('statechange', this._onStateChange, {\n        once: true\n      });\n    } // If there's a waiting service worker with a matching URL before the\n    // `updatefound` event fires, it likely means that this site is open\n    // in another tab, or the user refreshed the page (and thus the prevoius\n    // page wasn't fully unloaded before this page started loading).\n    // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting\n\n\n    const waitingSW = this._registration.waiting;\n\n    if (waitingSW && urlsMatch(waitingSW.scriptURL, this._scriptURL)) {\n      // Store the waiting SW as the \"own\" Sw, even if it means overwriting\n      // a compatible controller.\n      this._sw = waitingSW; // Run this in the next microtask, so any code that adds an event\n      // listener after awaiting `register()` will get this event.\n\n      Promise.resolve().then(() => {\n        this.dispatchEvent(new WorkboxEvent('waiting', {\n          sw: waitingSW,\n          wasWaitingBeforeRegister: true\n        }));\n\n        {\n          logger.warn('A service worker was already waiting to activate ' + 'before this script was registered...');\n        }\n      });\n    } // If an \"own\" SW is already set, resolve the deferred.\n\n\n    if (this._sw) {\n      this._swDeferred.resolve(this._sw);\n    }\n\n    {\n      logger.log('Successfully registered service worker.', this._scriptURL);\n\n      if (navigator.serviceWorker.controller) {\n        if (this._compatibleControllingSW) {\n          logger.debug('A service worker with the same script URL ' + 'is already controlling this page.');\n        } else {\n          logger.debug('A service worker with a different script URL is ' + 'currently controlling the page. The browser is now fetching ' + 'the new script now...');\n        }\n      }\n\n      const currentPageIsOutOfScope = () => {\n        const scopeURL = new URL(this._registerOptions.scope || this._scriptURL, document.baseURI);\n        const scopeURLBasePath = new URL('./', scopeURL.href).pathname;\n        return !location.pathname.startsWith(scopeURLBasePath);\n      };\n\n      if (currentPageIsOutOfScope()) {\n        logger.warn('The current page is not in scope for the registered ' + 'service worker. Was this a mistake?');\n      }\n    }\n\n    this._registration.addEventListener('updatefound', this._onUpdateFound);\n\n    navigator.serviceWorker.addEventListener('controllerchange', this._onControllerChange, {\n      once: true\n    }); // Add message listeners.\n\n    if ('BroadcastChannel' in self) {\n      this._broadcastChannel = new BroadcastChannel('workbox');\n\n      this._broadcastChannel.addEventListener('message', this._onMessage);\n    }\n\n    navigator.serviceWorker.addEventListener('message', this._onMessage);\n    return this._registration;\n  }\n  /**\n   * Resolves to the service worker registered by this instance as soon as it\n   * is active. If a service worker was already controlling at registration\n   * time then it will resolve to that if the script URLs (and optionally\n   * script versions) match, otherwise it will wait until an update is found\n   * and activates.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n\n\n  get active() {\n    return this._activeDeferred.promise;\n  }\n  /**\n   * Resolves to the service worker registered by this instance as soon as it\n   * is controlling the page. If a service worker was already controlling at\n   * registration time then it will resolve to that if the script URLs (and\n   * optionally script versions) match, otherwise it will wait until an update\n   * is found and starts controlling the page.\n   * Note: the first time a service worker is installed it will active but\n   * not start controlling the page unless `clients.claim()` is called in the\n   * service worker.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n\n\n  get controlling() {\n    return this._controllingDeferred.promise;\n  }\n  /**\n   * Resolves with a reference to a service worker that matches the script URL\n   * of this instance, as soon as it's available.\n   *\n   * If, at registration time, there's already an active or waiting service\n   * worker with a matching script URL, it will be used (with the waiting\n   * service worker taking precedence over the active service worker if both\n   * match, since the waiting service worker would have been registered more\n   * recently).\n   * If there's no matching active or waiting service worker at registration\n   * time then the promise will not resolve until an update is found and starts\n   * installing, at which point the installing service worker is used.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n\n\n  async getSW() {\n    // If `this._sw` is set, resolve with that as we want `getSW()` to\n    // return the correct (new) service worker if an update is found.\n    return this._sw || this._swDeferred.promise;\n  }\n  /**\n   * Sends the passed data object to the service worker registered by this\n   * instance (via [`getSW()`]{@link module:workbox-window.Workbox#getSW}) and resolves\n   * with a response (if any).\n   *\n   * A response can be set in a message handler in the service worker by\n   * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n   * returned by `messageSW()`. If no response is set, the promise will never\n   * resolve.\n   *\n   * @param {Object} data An object to send to the service worker\n   * @return {Promise<Object>}\n   */\n\n\n  async messageSW(data) {\n    const sw = await this.getSW();\n    return messageSW(sw, data);\n  }\n  /**\n   * Checks for a service worker already controlling the page and returns\n   * it if its script URL matchs.\n   *\n   * @private\n   * @return {ServiceWorker|undefined}\n   */\n\n\n  _getControllingSWIfCompatible() {\n    const controller = navigator.serviceWorker.controller;\n\n    if (controller && urlsMatch(controller.scriptURL, this._scriptURL)) {\n      return controller;\n    }\n  }\n  /**\n   * Registers a service worker for this instances script URL and register\n   * options and tracks the time registration was complete.\n   *\n   * @private\n   */\n\n\n  async _registerScript() {\n    try {\n      const reg = await navigator.serviceWorker.register(this._scriptURL, this._registerOptions); // Keep track of when registration happened, so it can be used in the\n      // `this._onUpdateFound` heuristic. Also use the presence of this\n      // property as a way to see if `.register()` has been called.\n\n      this._registrationTime = performance.now();\n      return reg;\n    } catch (error) {\n      {\n        logger.error(error);\n      } // Re-throw the error.\n\n\n      throw error;\n    }\n  }\n  /**\n   * Sends a message to the passed service worker that the window is ready.\n   *\n   * @param {ServiceWorker} sw\n   * @private\n   */\n\n\n  _reportWindowReady(sw) {\n    messageSW(sw, {\n      type: 'WINDOW_READY',\n      meta: 'workbox-window'\n    });\n  }\n  /**\n   * @private\n   */\n\n\n  _onUpdateFound() {\n    const installingSW = this._registration.installing; // If the script URL passed to `navigator.serviceWorker.register()` is\n    // different from the current controlling SW's script URL, we know any\n    // successful registration calls will trigger an `updatefound` event.\n    // But if the registered script URL is the same as the current controlling\n    // SW's script URL, we'll only get an `updatefound` event if the file\n    // changed since it was last registered. This can be a problem if the user\n    // opens up the same page in a different tab, and that page registers\n    // a SW that triggers an update. It's a problem because this page has no\n    // good way of knowing whether the `updatefound` event came from the SW\n    // script it registered or from a registration attempt made by a newer\n    // version of the page running in another tab.\n    // To minimize the possibility of a false positive, we use the logic here:\n\n    let updateLikelyTriggeredExternally = // Since we enforce only calling `register()` once, and since we don't\n    // add the `updatefound` event listener until the `register()` call, if\n    // `_updateFoundCount` is > 0 then it means this method has already\n    // been called, thus this SW must be external\n    this._updateFoundCount > 0 || // If the script URL of the installing SW is different from this\n    // instance's script URL, we know it's definitely not from our\n    // registration.\n    !urlsMatch(installingSW.scriptURL, this._scriptURL) || // If all of the above are false, then we use a time-based heuristic:\n    // Any `updatefound` event that occurs long after our registration is\n    // assumed to be external.\n    performance.now() > this._registrationTime + REGISTRATION_TIMEOUT_DURATION ? // If any of the above are not true, we assume the update was\n    // triggered by this instance.\n    true : false;\n\n    if (updateLikelyTriggeredExternally) {\n      this._externalSW = installingSW;\n\n      this._registration.removeEventListener('updatefound', this._onUpdateFound);\n    } else {\n      // If the update was not triggered externally we know the installing\n      // SW is the one we registered, so we set it.\n      this._sw = installingSW;\n\n      this._swDeferred.resolve(installingSW); // The `installing` state isn't something we have a dedicated\n      // callback for, but we do log messages for it in development.\n\n\n      {\n        if (navigator.serviceWorker.controller) {\n          logger.log('Updated service worker found. Installing now...');\n        } else {\n          logger.log('Service worker is installing...');\n        }\n      }\n    } // Increment the `updatefound` count, so future invocations of this\n    // method can be sure they were triggered externally.\n\n\n    ++this._updateFoundCount; // Add a `statechange` listener regardless of whether this update was\n    // triggered externally, since we have callbacks for both.\n\n    installingSW.addEventListener('statechange', this._onStateChange);\n  }\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n\n\n  _onStateChange(originalEvent) {\n    const sw = originalEvent.target;\n    const {\n      state\n    } = sw;\n    const isExternal = sw === this._externalSW;\n    const eventPrefix = isExternal ? 'external' : '';\n    const eventProps = {\n      sw,\n      originalEvent\n    };\n\n    if (!isExternal && this._isUpdate) {\n      eventProps.isUpdate = true;\n    }\n\n    this.dispatchEvent(new WorkboxEvent(eventPrefix + state, eventProps));\n\n    if (state === 'installed') {\n      // This timeout is used to ignore cases where the service worker calls\n      // `skipWaiting()` in the install event, thus moving it directly in the\n      // activating state. (Since all service workers *must* go through the\n      // waiting phase, the only way to detect `skipWaiting()` called in the\n      // install event is to observe that the time spent in the waiting phase\n      // is very short.)\n      // NOTE: we don't need separate timeouts for the own and external SWs\n      // since they can't go through these phases at the same time.\n      this._waitingTimeout = setTimeout(() => {\n        // Ensure the SW is still waiting (it may now be redundant).\n        if (state === 'installed' && this._registration.waiting === sw) {\n          this.dispatchEvent(new WorkboxEvent(eventPrefix + 'waiting', eventProps));\n\n          {\n            if (isExternal) {\n              logger.warn('An external service worker has installed but is ' + 'waiting for this client to close before activating...');\n            } else {\n              logger.warn('The service worker has installed but is waiting ' + 'for existing clients to close before activating...');\n            }\n          }\n        }\n      }, WAITING_TIMEOUT_DURATION);\n    } else if (state === 'activating') {\n      clearTimeout(this._waitingTimeout);\n\n      if (!isExternal) {\n        this._activeDeferred.resolve(sw);\n      }\n    }\n\n    {\n      switch (state) {\n        case 'installed':\n          if (isExternal) {\n            logger.warn('An external service worker has installed. ' + 'You may want to suggest users reload this page.');\n          } else {\n            logger.log('Registered service worker installed.');\n          }\n\n          break;\n\n        case 'activated':\n          if (isExternal) {\n            logger.warn('An external service worker has activated.');\n          } else {\n            logger.log('Registered service worker activated.');\n\n            if (sw !== navigator.serviceWorker.controller) {\n              logger.warn('The registered service worker is active but ' + 'not yet controlling the page. Reload or run ' + '`clients.claim()` in the service worker.');\n            }\n          }\n\n          break;\n\n        case 'redundant':\n          if (sw === this._compatibleControllingSW) {\n            logger.log('Previously controlling service worker now redundant!');\n          } else if (!isExternal) {\n            logger.log('Registered service worker now redundant!');\n          }\n\n          break;\n      }\n    }\n  }\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n\n\n  _onControllerChange(originalEvent) {\n    const sw = this._sw;\n\n    if (sw === navigator.serviceWorker.controller) {\n      this.dispatchEvent(new WorkboxEvent('controlling', {\n        sw,\n        originalEvent\n      }));\n\n      {\n        logger.log('Registered service worker now controlling this page.');\n      }\n\n      this._controllingDeferred.resolve(sw);\n    }\n  }\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n\n\n  _onMessage(originalEvent) {\n    const {\n      data\n    } = originalEvent;\n    this.dispatchEvent(new WorkboxEvent('message', {\n      data,\n      originalEvent\n    }));\n  }\n\n} // The jsdoc comments below outline the events this instance may dispatch:\n\n/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nexport { Workbox, messageSW };\n//# sourceMappingURL=workbox-window.dev.mjs.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-window.dev.umd.js",
    "content": "(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n  typeof define === 'function' && define.amd ? define(['exports'], factory) :\n  (global = global || self, factory(global.workbox = {}));\n}(this, function (exports) { 'use strict';\n\n  try {\n    self['workbox:window:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Sends a data object to a service worker via `postMessage` and resolves with\n   * a response (if any).\n   *\n   * A response can be set in a message handler in the service worker by\n   * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n   * returned by `messageSW()`. If no response is set, the promise will not\n   * resolve.\n   *\n   * @param {ServiceWorker} sw The service worker to send the message to.\n   * @param {Object} data An object to send to the service worker.\n   * @return {Promise<Object|undefined>}\n   *\n   * @memberof module:workbox-window\n   */\n\n  var messageSW = function messageSW(sw, data) {\n    return new Promise(function (resolve) {\n      var messageChannel = new MessageChannel();\n\n      messageChannel.port1.onmessage = function (evt) {\n        return resolve(evt.data);\n      };\n\n      sw.postMessage(data, [messageChannel.port2]);\n    });\n  };\n\n  function _defineProperties(target, props) {\n    for (var i = 0; i < props.length; i++) {\n      var descriptor = props[i];\n      descriptor.enumerable = descriptor.enumerable || false;\n      descriptor.configurable = true;\n      if (\"value\" in descriptor) descriptor.writable = true;\n      Object.defineProperty(target, descriptor.key, descriptor);\n    }\n  }\n\n  function _createClass(Constructor, protoProps, staticProps) {\n    if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n    if (staticProps) _defineProperties(Constructor, staticProps);\n    return Constructor;\n  }\n\n  function _inheritsLoose(subClass, superClass) {\n    subClass.prototype = Object.create(superClass.prototype);\n    subClass.prototype.constructor = subClass;\n    subClass.__proto__ = superClass;\n  }\n\n  function _assertThisInitialized(self) {\n    if (self === void 0) {\n      throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");\n    }\n\n    return self;\n  }\n\n  try {\n    self['workbox:core:4.1.1'] && _();\n  } catch (e) {} // eslint-disable-line\n\n  /*\n    Copyright 2018 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * The Deferred class composes Promises in a way that allows for them to be\n   * resolved or rejected from outside the constructor. In most cases promises\n   * should be used directly, but Deferreds can be necessary when the logic to\n   * resolve a promise must be separate.\n   *\n   * @private\n   */\n\n  var Deferred =\n  /**\n   * Creates a promise and exposes its resolve and reject functions as methods.\n   */\n  function Deferred() {\n    var _this = this;\n\n    this.promise = new Promise(function (resolve, reject) {\n      _this.resolve = resolve;\n      _this.reject = reject;\n    });\n  };\n\n  /*\n    Copyright 2019 Google LLC\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  var logger = function () {\n    var inGroup = false;\n    var methodToColorMap = {\n      debug: \"#7f8c8d\",\n      // Gray\n      log: \"#2ecc71\",\n      // Green\n      warn: \"#f39c12\",\n      // Yellow\n      error: \"#c0392b\",\n      // Red\n      groupCollapsed: \"#3498db\",\n      // Blue\n      groupEnd: null // No colored prefix on groupEnd\n\n    };\n\n    var print = function print(method, args) {\n      var _console2;\n\n      if (method === 'groupCollapsed') {\n        // Safari doesn't print all console.groupCollapsed() arguments:\n        // https://bugs.webkit.org/show_bug.cgi?id=182754\n        if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n          var _console;\n\n          (_console = console)[method].apply(_console, args);\n\n          return;\n        }\n      }\n\n      var styles = [\"background: \" + methodToColorMap[method], \"border-radius: 0.5em\", \"color: white\", \"font-weight: bold\", \"padding: 2px 0.5em\"]; // When in a group, the workbox prefix is not displayed.\n\n      var logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n\n      (_console2 = console)[method].apply(_console2, logPrefix.concat(args));\n\n      if (method === 'groupCollapsed') {\n        inGroup = true;\n      }\n\n      if (method === 'groupEnd') {\n        inGroup = false;\n      }\n    };\n\n    var api = {};\n\n    var _arr = Object.keys(methodToColorMap);\n\n    var _loop = function _loop() {\n      var method = _arr[_i];\n\n      api[method] = function () {\n        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n          args[_key] = arguments[_key];\n        }\n\n        print(method, args);\n      };\n    };\n\n    for (var _i = 0; _i < _arr.length; _i++) {\n      _loop();\n    }\n\n    return api;\n  }();\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * A minimal `EventTarget` shim.\n   * This is necessary because not all browsers support constructable\n   * `EventTarget`, so using a real `EventTarget` will error.\n   * @private\n   */\n\n  var EventTargetShim =\n  /*#__PURE__*/\n  function () {\n    /**\n     * Creates an event listener registry\n     */\n    function EventTargetShim() {\n      // A registry of event types to listeners.\n      this._eventListenerRegistry = {};\n    }\n    /**\n     * @param {string} type\n     * @param {Function} listener\n     */\n\n\n    var _proto = EventTargetShim.prototype;\n\n    _proto.addEventListener = function addEventListener(type, listener) {\n      this._getEventListenersByType(type).add(listener);\n    };\n    /**\n     * @param {string} type\n     * @param {Function} listener\n     */\n\n\n    _proto.removeEventListener = function removeEventListener(type, listener) {\n      this._getEventListenersByType(type).delete(listener);\n    };\n    /**\n     * @param {Event} event\n     */\n\n\n    _proto.dispatchEvent = function dispatchEvent(event) {\n      event.target = this;\n\n      this._getEventListenersByType(event.type).forEach(function (listener) {\n        return listener(event);\n      });\n    };\n    /**\n     * Returns a Set of listeners associated with the passed event type.\n     * If no handlers have been registered, an empty Set is returned.\n     *\n     * @param {string} type The event type.\n     * @return {Set} An array of handler functions.\n     */\n\n\n    _proto._getEventListenersByType = function _getEventListenersByType(type) {\n      return this._eventListenerRegistry[type] = this._eventListenerRegistry[type] || new Set();\n    };\n\n    return EventTargetShim;\n  }();\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * Returns true if two URLs have the same `.href` property. The URLS can be\n   * relative, and if they are the current location href is used to resolve URLs.\n   *\n   * @private\n   * @param {string} url1\n   * @param {string} url2\n   * @return {boolean}\n   */\n\n  var urlsMatch = function urlsMatch(url1, url2) {\n    return new URL(url1, location).href === new URL(url2, location).href;\n  };\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n  /**\n   * A minimal `Event` subclass shim.\n   * This doesn't *actually* subclass `Event` because not all browsers support\n   * constructable `EventTarget`, and using a real `Event` will error.\n   * @private\n   */\n\n  var WorkboxEvent =\n  /**\n   * @param {string} type\n   * @param {Object} props\n   */\n  function WorkboxEvent(type, props) {\n    Object.assign(this, props, {\n      type: type\n    });\n  };\n\n  function _catch(body, recover) {\n    try {\n      var result = body();\n    } catch (e) {\n      return recover(e);\n    }\n\n    if (result && result.then) {\n      return result.then(void 0, recover);\n    }\n\n    return result;\n  }\n\n  function _async(f) {\n    return function () {\n      for (var args = [], i = 0; i < arguments.length; i++) {\n        args[i] = arguments[i];\n      }\n\n      try {\n        return Promise.resolve(f.apply(this, args));\n      } catch (e) {\n        return Promise.reject(e);\n      }\n    };\n  }\n\n  function _invoke(body, then) {\n    var result = body();\n\n    if (result && result.then) {\n      return result.then(then);\n    }\n\n    return then(result);\n  }\n\n  function _await(value, then, direct) {\n    if (direct) {\n      return then ? then(value) : value;\n    }\n\n    if (!value || !value.then) {\n      value = Promise.resolve(value);\n    }\n\n    return then ? value.then(then) : value;\n  }\n\n  function _awaitIgnored(value, direct) {\n    if (!direct) {\n      return value && value.then ? value.then(_empty) : Promise.resolve();\n    }\n  }\n\n  function _empty() {}\n  // `skipWaiting()` wasn't called. This 200 amount wasn't scientifically\n  // chosen, but it seems to avoid false positives in my testing.\n\n  var WAITING_TIMEOUT_DURATION = 200; // The amount of time after a registration that we can reasonably conclude\n  // that the registration didn't trigger an update.\n\n  var REGISTRATION_TIMEOUT_DURATION = 60000;\n  /**\n   * A class to aid in handling service worker registration, updates, and\n   * reacting to service worker lifecycle events.\n   *\n   * @fires [message]{@link module:workbox-window.Workbox#message}\n   * @fires [installed]{@link module:workbox-window.Workbox#installed}\n   * @fires [waiting]{@link module:workbox-window.Workbox#waiting}\n   * @fires [controlling]{@link module:workbox-window.Workbox#controlling}\n   * @fires [activated]{@link module:workbox-window.Workbox#activated}\n   * @fires [redundant]{@link module:workbox-window.Workbox#redundant}\n   * @fires [externalinstalled]{@link module:workbox-window.Workbox#externalinstalled}\n   * @fires [externalwaiting]{@link module:workbox-window.Workbox#externalwaiting}\n   * @fires [externalactivated]{@link module:workbox-window.Workbox#externalactivated}\n   *\n   * @memberof module:workbox-window\n   */\n\n  var Workbox =\n  /*#__PURE__*/\n  function (_EventTargetShim) {\n    _inheritsLoose(Workbox, _EventTargetShim);\n\n    /**\n     * Creates a new Workbox instance with a script URL and service worker\n     * options. The script URL and options are the same as those used when\n     * calling `navigator.serviceWorker.register(scriptURL, options)`. See:\n     * https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register\n     *\n     * @param {string} scriptURL The service worker script associated with this\n     *     instance.\n     * @param {Object} [registerOptions] The service worker options associated\n     *     with this instance.\n     */\n    function Workbox(scriptURL, registerOptions) {\n      var _this;\n\n      if (registerOptions === void 0) {\n        registerOptions = {};\n      }\n\n      _this = _EventTargetShim.call(this) || this;\n      _this._scriptURL = scriptURL;\n      _this._registerOptions = registerOptions;\n      _this._updateFoundCount = 0; // Deferreds we can resolve later.\n\n      _this._swDeferred = new Deferred();\n      _this._activeDeferred = new Deferred();\n      _this._controllingDeferred = new Deferred(); // Bind event handler callbacks.\n\n      _this._onMessage = _this._onMessage.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n      _this._onStateChange = _this._onStateChange.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n      _this._onUpdateFound = _this._onUpdateFound.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n      _this._onControllerChange = _this._onControllerChange.bind(_assertThisInitialized(_assertThisInitialized(_this)));\n      return _this;\n    }\n    /**\n     * Registers a service worker for this instances script URL and service\n     * worker options. By default this method delays registration until after\n     * the window has loaded.\n     *\n     * @param {Object} [options]\n     * @param {Function} [options.immediate=false] Setting this to true will\n     *     register the service worker immediately, even if the window has\n     *     not loaded (not recommended).\n     */\n\n\n    var _proto = Workbox.prototype;\n    _proto.register = _async(function (_temp) {\n      var _this2 = this;\n\n      var _ref = _temp === void 0 ? {} : _temp,\n          _ref$immediate = _ref.immediate,\n          immediate = _ref$immediate === void 0 ? false : _ref$immediate;\n\n      {\n        if (_this2._registrationTime) {\n          logger.error('Cannot re-register a Workbox instance after it has ' + 'been registered. Create a new instance instead.');\n          return;\n        }\n      }\n\n      return _invoke(function () {\n        if (!immediate && document.readyState !== 'complete') {\n          return _awaitIgnored(new Promise(function (res) {\n            return addEventListener('load', res);\n          }));\n        }\n      }, function () {\n        // Set this flag to true if any service worker was controlling the page\n        // at registration time.\n        _this2._isUpdate = Boolean(navigator.serviceWorker.controller); // Before registering, attempt to determine if a SW is already controlling\n        // the page, and if that SW script (and version, if specified) matches this\n        // instance's script.\n\n        _this2._compatibleControllingSW = _this2._getControllingSWIfCompatible();\n        return _await(_this2._registerScript(), function (_this2$_registerScrip) {\n          _this2._registration = _this2$_registerScrip;\n\n          // If we have a compatible controller, store the controller as the \"own\"\n          // SW, resolve active/controlling deferreds and add necessary listeners.\n          if (_this2._compatibleControllingSW) {\n            _this2._sw = _this2._compatibleControllingSW;\n\n            _this2._activeDeferred.resolve(_this2._compatibleControllingSW);\n\n            _this2._controllingDeferred.resolve(_this2._compatibleControllingSW);\n\n            _this2._reportWindowReady(_this2._compatibleControllingSW);\n\n            _this2._compatibleControllingSW.addEventListener('statechange', _this2._onStateChange, {\n              once: true\n            });\n          } // If there's a waiting service worker with a matching URL before the\n          // `updatefound` event fires, it likely means that this site is open\n          // in another tab, or the user refreshed the page (and thus the prevoius\n          // page wasn't fully unloaded before this page started loading).\n          // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting\n\n\n          var waitingSW = _this2._registration.waiting;\n\n          if (waitingSW && urlsMatch(waitingSW.scriptURL, _this2._scriptURL)) {\n            // Store the waiting SW as the \"own\" Sw, even if it means overwriting\n            // a compatible controller.\n            _this2._sw = waitingSW; // Run this in the next microtask, so any code that adds an event\n            // listener after awaiting `register()` will get this event.\n\n            Promise.resolve().then(function () {\n              _this2.dispatchEvent(new WorkboxEvent('waiting', {\n                sw: waitingSW,\n                wasWaitingBeforeRegister: true\n              }));\n\n              {\n                logger.warn('A service worker was already waiting to activate ' + 'before this script was registered...');\n              }\n            });\n          } // If an \"own\" SW is already set, resolve the deferred.\n\n\n          if (_this2._sw) {\n            _this2._swDeferred.resolve(_this2._sw);\n          }\n\n          {\n            logger.log('Successfully registered service worker.', _this2._scriptURL);\n\n            if (navigator.serviceWorker.controller) {\n              if (_this2._compatibleControllingSW) {\n                logger.debug('A service worker with the same script URL ' + 'is already controlling this page.');\n              } else {\n                logger.debug('A service worker with a different script URL is ' + 'currently controlling the page. The browser is now fetching ' + 'the new script now...');\n              }\n            }\n\n            var currentPageIsOutOfScope = function currentPageIsOutOfScope() {\n              var scopeURL = new URL(_this2._registerOptions.scope || _this2._scriptURL, document.baseURI);\n              var scopeURLBasePath = new URL('./', scopeURL.href).pathname;\n              return !location.pathname.startsWith(scopeURLBasePath);\n            };\n\n            if (currentPageIsOutOfScope()) {\n              logger.warn('The current page is not in scope for the registered ' + 'service worker. Was this a mistake?');\n            }\n          }\n\n          _this2._registration.addEventListener('updatefound', _this2._onUpdateFound);\n\n          navigator.serviceWorker.addEventListener('controllerchange', _this2._onControllerChange, {\n            once: true\n          }); // Add message listeners.\n\n          if ('BroadcastChannel' in self) {\n            _this2._broadcastChannel = new BroadcastChannel('workbox');\n\n            _this2._broadcastChannel.addEventListener('message', _this2._onMessage);\n          }\n\n          navigator.serviceWorker.addEventListener('message', _this2._onMessage);\n          return _this2._registration;\n        });\n      });\n    });\n    /**\n     * Resolves to the service worker registered by this instance as soon as it\n     * is active. If a service worker was already controlling at registration\n     * time then it will resolve to that if the script URLs (and optionally\n     * script versions) match, otherwise it will wait until an update is found\n     * and activates.\n     *\n     * @return {Promise<ServiceWorker>}\n     */\n\n    /**\n     * Resolves with a reference to a service worker that matches the script URL\n     * of this instance, as soon as it's available.\n     *\n     * If, at registration time, there's already an active or waiting service\n     * worker with a matching script URL, it will be used (with the waiting\n     * service worker taking precedence over the active service worker if both\n     * match, since the waiting service worker would have been registered more\n     * recently).\n     * If there's no matching active or waiting service worker at registration\n     * time then the promise will not resolve until an update is found and starts\n     * installing, at which point the installing service worker is used.\n     *\n     * @return {Promise<ServiceWorker>}\n     */\n    _proto.getSW = _async(function () {\n      var _this3 = this;\n\n      // If `this._sw` is set, resolve with that as we want `getSW()` to\n      // return the correct (new) service worker if an update is found.\n      return _this3._sw || _this3._swDeferred.promise;\n    });\n    /**\n     * Sends the passed data object to the service worker registered by this\n     * instance (via [`getSW()`]{@link module:workbox-window.Workbox#getSW}) and resolves\n     * with a response (if any).\n     *\n     * A response can be set in a message handler in the service worker by\n     * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n     * returned by `messageSW()`. If no response is set, the promise will never\n     * resolve.\n     *\n     * @param {Object} data An object to send to the service worker\n     * @return {Promise<Object>}\n     */\n\n    _proto.messageSW = _async(function (data) {\n      var _this4 = this;\n\n      return _await(_this4.getSW(), function (sw) {\n        return messageSW(sw, data);\n      });\n    });\n    /**\n     * Checks for a service worker already controlling the page and returns\n     * it if its script URL matchs.\n     *\n     * @private\n     * @return {ServiceWorker|undefined}\n     */\n\n    _proto._getControllingSWIfCompatible = function _getControllingSWIfCompatible() {\n      var controller = navigator.serviceWorker.controller;\n\n      if (controller && urlsMatch(controller.scriptURL, this._scriptURL)) {\n        return controller;\n      }\n    };\n    /**\n     * Registers a service worker for this instances script URL and register\n     * options and tracks the time registration was complete.\n     *\n     * @private\n     */\n\n\n    _proto._registerScript = _async(function () {\n      var _this5 = this;\n\n      return _catch(function () {\n        return _await(navigator.serviceWorker.register(_this5._scriptURL, _this5._registerOptions), function (reg) {\n          // Keep track of when registration happened, so it can be used in the\n          // `this._onUpdateFound` heuristic. Also use the presence of this\n          // property as a way to see if `.register()` has been called.\n          _this5._registrationTime = performance.now();\n          return reg;\n        });\n      }, function (error) {\n        {\n          logger.error(error);\n        } // Re-throw the error.\n\n\n        throw error;\n      });\n    });\n    /**\n     * Sends a message to the passed service worker that the window is ready.\n     *\n     * @param {ServiceWorker} sw\n     * @private\n     */\n\n    _proto._reportWindowReady = function _reportWindowReady(sw) {\n      messageSW(sw, {\n        type: 'WINDOW_READY',\n        meta: 'workbox-window'\n      });\n    };\n    /**\n     * @private\n     */\n\n\n    _proto._onUpdateFound = function _onUpdateFound() {\n      var installingSW = this._registration.installing; // If the script URL passed to `navigator.serviceWorker.register()` is\n      // different from the current controlling SW's script URL, we know any\n      // successful registration calls will trigger an `updatefound` event.\n      // But if the registered script URL is the same as the current controlling\n      // SW's script URL, we'll only get an `updatefound` event if the file\n      // changed since it was last registered. This can be a problem if the user\n      // opens up the same page in a different tab, and that page registers\n      // a SW that triggers an update. It's a problem because this page has no\n      // good way of knowing whether the `updatefound` event came from the SW\n      // script it registered or from a registration attempt made by a newer\n      // version of the page running in another tab.\n      // To minimize the possibility of a false positive, we use the logic here:\n\n      var updateLikelyTriggeredExternally = // Since we enforce only calling `register()` once, and since we don't\n      // add the `updatefound` event listener until the `register()` call, if\n      // `_updateFoundCount` is > 0 then it means this method has already\n      // been called, thus this SW must be external\n      this._updateFoundCount > 0 || // If the script URL of the installing SW is different from this\n      // instance's script URL, we know it's definitely not from our\n      // registration.\n      !urlsMatch(installingSW.scriptURL, this._scriptURL) || // If all of the above are false, then we use a time-based heuristic:\n      // Any `updatefound` event that occurs long after our registration is\n      // assumed to be external.\n      performance.now() > this._registrationTime + REGISTRATION_TIMEOUT_DURATION ? // If any of the above are not true, we assume the update was\n      // triggered by this instance.\n      true : false;\n\n      if (updateLikelyTriggeredExternally) {\n        this._externalSW = installingSW;\n\n        this._registration.removeEventListener('updatefound', this._onUpdateFound);\n      } else {\n        // If the update was not triggered externally we know the installing\n        // SW is the one we registered, so we set it.\n        this._sw = installingSW;\n\n        this._swDeferred.resolve(installingSW); // The `installing` state isn't something we have a dedicated\n        // callback for, but we do log messages for it in development.\n\n\n        {\n          if (navigator.serviceWorker.controller) {\n            logger.log('Updated service worker found. Installing now...');\n          } else {\n            logger.log('Service worker is installing...');\n          }\n        }\n      } // Increment the `updatefound` count, so future invocations of this\n      // method can be sure they were triggered externally.\n\n\n      ++this._updateFoundCount; // Add a `statechange` listener regardless of whether this update was\n      // triggered externally, since we have callbacks for both.\n\n      installingSW.addEventListener('statechange', this._onStateChange);\n    };\n    /**\n     * @private\n     * @param {Event} originalEvent\n     */\n\n\n    _proto._onStateChange = function _onStateChange(originalEvent) {\n      var _this6 = this;\n\n      var sw = originalEvent.target;\n      var state = sw.state;\n      var isExternal = sw === this._externalSW;\n      var eventPrefix = isExternal ? 'external' : '';\n      var eventProps = {\n        sw: sw,\n        originalEvent: originalEvent\n      };\n\n      if (!isExternal && this._isUpdate) {\n        eventProps.isUpdate = true;\n      }\n\n      this.dispatchEvent(new WorkboxEvent(eventPrefix + state, eventProps));\n\n      if (state === 'installed') {\n        // This timeout is used to ignore cases where the service worker calls\n        // `skipWaiting()` in the install event, thus moving it directly in the\n        // activating state. (Since all service workers *must* go through the\n        // waiting phase, the only way to detect `skipWaiting()` called in the\n        // install event is to observe that the time spent in the waiting phase\n        // is very short.)\n        // NOTE: we don't need separate timeouts for the own and external SWs\n        // since they can't go through these phases at the same time.\n        this._waitingTimeout = setTimeout(function () {\n          // Ensure the SW is still waiting (it may now be redundant).\n          if (state === 'installed' && _this6._registration.waiting === sw) {\n            _this6.dispatchEvent(new WorkboxEvent(eventPrefix + 'waiting', eventProps));\n\n            {\n              if (isExternal) {\n                logger.warn('An external service worker has installed but is ' + 'waiting for this client to close before activating...');\n              } else {\n                logger.warn('The service worker has installed but is waiting ' + 'for existing clients to close before activating...');\n              }\n            }\n          }\n        }, WAITING_TIMEOUT_DURATION);\n      } else if (state === 'activating') {\n        clearTimeout(this._waitingTimeout);\n\n        if (!isExternal) {\n          this._activeDeferred.resolve(sw);\n        }\n      }\n\n      {\n        switch (state) {\n          case 'installed':\n            if (isExternal) {\n              logger.warn('An external service worker has installed. ' + 'You may want to suggest users reload this page.');\n            } else {\n              logger.log('Registered service worker installed.');\n            }\n\n            break;\n\n          case 'activated':\n            if (isExternal) {\n              logger.warn('An external service worker has activated.');\n            } else {\n              logger.log('Registered service worker activated.');\n\n              if (sw !== navigator.serviceWorker.controller) {\n                logger.warn('The registered service worker is active but ' + 'not yet controlling the page. Reload or run ' + '`clients.claim()` in the service worker.');\n              }\n            }\n\n            break;\n\n          case 'redundant':\n            if (sw === this._compatibleControllingSW) {\n              logger.log('Previously controlling service worker now redundant!');\n            } else if (!isExternal) {\n              logger.log('Registered service worker now redundant!');\n            }\n\n            break;\n        }\n      }\n    };\n    /**\n     * @private\n     * @param {Event} originalEvent\n     */\n\n\n    _proto._onControllerChange = function _onControllerChange(originalEvent) {\n      var sw = this._sw;\n\n      if (sw === navigator.serviceWorker.controller) {\n        this.dispatchEvent(new WorkboxEvent('controlling', {\n          sw: sw,\n          originalEvent: originalEvent\n        }));\n\n        {\n          logger.log('Registered service worker now controlling this page.');\n        }\n\n        this._controllingDeferred.resolve(sw);\n      }\n    };\n    /**\n     * @private\n     * @param {Event} originalEvent\n     */\n\n\n    _proto._onMessage = function _onMessage(originalEvent) {\n      var data = originalEvent.data;\n      this.dispatchEvent(new WorkboxEvent('message', {\n        data: data,\n        originalEvent: originalEvent\n      }));\n    };\n\n    _createClass(Workbox, [{\n      key: \"active\",\n      get: function get() {\n        return this._activeDeferred.promise;\n      }\n      /**\n       * Resolves to the service worker registered by this instance as soon as it\n       * is controlling the page. If a service worker was already controlling at\n       * registration time then it will resolve to that if the script URLs (and\n       * optionally script versions) match, otherwise it will wait until an update\n       * is found and starts controlling the page.\n       * Note: the first time a service worker is installed it will active but\n       * not start controlling the page unless `clients.claim()` is called in the\n       * service worker.\n       *\n       * @return {Promise<ServiceWorker>}\n       */\n\n    }, {\n      key: \"controlling\",\n      get: function get() {\n        return this._controllingDeferred.promise;\n      }\n    }]);\n\n    return Workbox;\n  }(EventTargetShim); // The jsdoc comments below outline the events this instance may dispatch:\n\n  /*\n    Copyright 2019 Google LLC\n\n    Use of this source code is governed by an MIT-style\n    license that can be found in the LICENSE file or at\n    https://opensource.org/licenses/MIT.\n  */\n\n  exports.Workbox = Workbox;\n  exports.messageSW = messageSW;\n\n  Object.defineProperty(exports, '__esModule', { value: true });\n\n}));\n//# sourceMappingURL=workbox-window.dev.umd.js.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-window.prod.es5.mjs",
    "content": "try{self[\"workbox:window:4.1.1\"]&&_()}catch(n){}var n=function(n,t){return new Promise(function(i){var e=new MessageChannel;e.port1.onmessage=function(n){return i(n.data)},n.postMessage(t,[e.port2])})};function t(n,t){for(var i=0;i<t.length;i++){var e=t[i];e.enumerable=e.enumerable||!1,e.configurable=!0,\"value\"in e&&(e.writable=!0),Object.defineProperty(n,e.key,e)}}function i(n){if(void 0===n)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return n}try{self[\"workbox:core:4.1.1\"]&&_()}catch(n){}var e=function(){var n=this;this.promise=new Promise(function(t,i){n.resolve=t,n.reject=i})},r=function(n,t){return new URL(n,location).href===new URL(t,location).href},o=function(n,t){Object.assign(this,t,{type:n})};function u(n){return function(){for(var t=[],i=0;i<arguments.length;i++)t[i]=arguments[i];try{return Promise.resolve(n.apply(this,t))}catch(n){return Promise.reject(n)}}}function a(n,t,i){return i?t?t(n):n:(n&&n.then||(n=Promise.resolve(n)),t?n.then(t):n)}function s(){}var c=function(c){var f,h;function v(n,t){var r;return void 0===t&&(t={}),(r=c.call(this)||this).t=n,r.i=t,r.o=0,r.u=new e,r.s=new e,r.h=new e,r.v=r.v.bind(i(i(r))),r.l=r.l.bind(i(i(r))),r.g=r.g.bind(i(i(r))),r.m=r.m.bind(i(i(r))),r}h=c,(f=v).prototype=Object.create(h.prototype),f.prototype.constructor=f,f.__proto__=h;var l,w,g,d=v.prototype;return d.register=u(function(n){var t,i,e=this,u=(void 0===n?{}:n).immediate,c=void 0!==u&&u;return t=function(){return e.p=Boolean(navigator.serviceWorker.controller),e.P=e.R(),a(e.k(),function(n){e.B=n,e.P&&(e.O=e.P,e.s.resolve(e.P),e.h.resolve(e.P),e.j(e.P),e.P.addEventListener(\"statechange\",e.l,{once:!0}));var t=e.B.waiting;return t&&r(t.scriptURL,e.t)&&(e.O=t,Promise.resolve().then(function(){e.dispatchEvent(new o(\"waiting\",{sw:t,wasWaitingBeforeRegister:!0}))})),e.O&&e.u.resolve(e.O),e.B.addEventListener(\"updatefound\",e.g),navigator.serviceWorker.addEventListener(\"controllerchange\",e.m,{once:!0}),\"BroadcastChannel\"in self&&(e.C=new BroadcastChannel(\"workbox\"),e.C.addEventListener(\"message\",e.v)),navigator.serviceWorker.addEventListener(\"message\",e.v),e.B})},(i=function(){if(!c&&\"complete\"!==document.readyState)return function(n,t){if(!t)return n&&n.then?n.then(s):Promise.resolve()}(new Promise(function(n){return addEventListener(\"load\",n)}))}())&&i.then?i.then(t):t(i)}),d.getSW=u(function(){return this.O||this.u.promise}),d.messageSW=u(function(t){return a(this.getSW(),function(i){return n(i,t)})}),d.R=function(){var n=navigator.serviceWorker.controller;if(n&&r(n.scriptURL,this.t))return n},d.k=u(function(){var n=this;return function(n,t){try{var i=n()}catch(n){return t(n)}return i&&i.then?i.then(void 0,t):i}(function(){return a(navigator.serviceWorker.register(n.t,n.i),function(t){return n.L=performance.now(),t})},function(n){throw n})}),d.j=function(t){n(t,{type:\"WINDOW_READY\",meta:\"workbox-window\"})},d.g=function(){var n=this.B.installing;this.o>0||!r(n.scriptURL,this.t)||performance.now()>this.L+6e4?(this.W=n,this.B.removeEventListener(\"updatefound\",this.g)):(this.O=n,this.u.resolve(n)),++this.o,n.addEventListener(\"statechange\",this.l)},d.l=function(n){var t=this,i=n.target,e=i.state,r=i===this.W,u=r?\"external\":\"\",a={sw:i,originalEvent:n};!r&&this.p&&(a.isUpdate=!0),this.dispatchEvent(new o(u+e,a)),\"installed\"===e?this._=setTimeout(function(){\"installed\"===e&&t.B.waiting===i&&t.dispatchEvent(new o(u+\"waiting\",a))},200):\"activating\"===e&&(clearTimeout(this._),r||this.s.resolve(i))},d.m=function(n){var t=this.O;t===navigator.serviceWorker.controller&&(this.dispatchEvent(new o(\"controlling\",{sw:t,originalEvent:n})),this.h.resolve(t))},d.v=function(n){var t=n.data;this.dispatchEvent(new o(\"message\",{data:t,originalEvent:n}))},l=v,(w=[{key:\"active\",get:function(){return this.s.promise}},{key:\"controlling\",get:function(){return this.h.promise}}])&&t(l.prototype,w),g&&t(l,g),v}(function(){function n(){this.D={}}var t=n.prototype;return t.addEventListener=function(n,t){this.T(n).add(t)},t.removeEventListener=function(n,t){this.T(n).delete(t)},t.dispatchEvent=function(n){n.target=this,this.T(n.type).forEach(function(t){return t(n)})},t.T=function(n){return this.D[n]=this.D[n]||new Set},n}());export{c as Workbox,n as messageSW};\n//# sourceMappingURL=workbox-window.prod.es5.mjs.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-window.prod.mjs",
    "content": "try{self[\"workbox:window:4.1.1\"]&&_()}catch(t){}const t=(t,s)=>new Promise(i=>{let e=new MessageChannel;e.port1.onmessage=(t=>i(t.data)),t.postMessage(s,[e.port2])});try{self[\"workbox:core:4.1.1\"]&&_()}catch(t){}class s{constructor(){this.promise=new Promise((t,s)=>{this.resolve=t,this.reject=s})}}class i{constructor(){this.t={}}addEventListener(t,s){this.s(t).add(s)}removeEventListener(t,s){this.s(t).delete(s)}dispatchEvent(t){t.target=this,this.s(t.type).forEach(s=>s(t))}s(t){return this.t[t]=this.t[t]||new Set}}const e=(t,s)=>new URL(t,location).href===new URL(s,location).href;class n{constructor(t,s){Object.assign(this,s,{type:t})}}const h=200,a=6e4;class o extends i{constructor(t,i={}){super(),this.i=t,this.h=i,this.o=0,this.l=new s,this.g=new s,this.u=new s,this.m=this.m.bind(this),this.v=this.v.bind(this),this.p=this.p.bind(this),this._=this._.bind(this)}async register({immediate:t=!1}={}){t||\"complete\"===document.readyState||await new Promise(t=>addEventListener(\"load\",t)),this.C=Boolean(navigator.serviceWorker.controller),this.W=this.L(),this.S=await this.B(),this.W&&(this.R=this.W,this.g.resolve(this.W),this.u.resolve(this.W),this.P(this.W),this.W.addEventListener(\"statechange\",this.v,{once:!0}));const s=this.S.waiting;return s&&e(s.scriptURL,this.i)&&(this.R=s,Promise.resolve().then(()=>{this.dispatchEvent(new n(\"waiting\",{sw:s,wasWaitingBeforeRegister:!0}))})),this.R&&this.l.resolve(this.R),this.S.addEventListener(\"updatefound\",this.p),navigator.serviceWorker.addEventListener(\"controllerchange\",this._,{once:!0}),\"BroadcastChannel\"in self&&(this.T=new BroadcastChannel(\"workbox\"),this.T.addEventListener(\"message\",this.m)),navigator.serviceWorker.addEventListener(\"message\",this.m),this.S}get active(){return this.g.promise}get controlling(){return this.u.promise}async getSW(){return this.R||this.l.promise}async messageSW(s){const i=await this.getSW();return t(i,s)}L(){const t=navigator.serviceWorker.controller;if(t&&e(t.scriptURL,this.i))return t}async B(){try{const t=await navigator.serviceWorker.register(this.i,this.h);return this.U=performance.now(),t}catch(t){throw t}}P(s){t(s,{type:\"WINDOW_READY\",meta:\"workbox-window\"})}p(){const t=this.S.installing;this.o>0||!e(t.scriptURL,this.i)||performance.now()>this.U+a?(this.k=t,this.S.removeEventListener(\"updatefound\",this.p)):(this.R=t,this.l.resolve(t)),++this.o,t.addEventListener(\"statechange\",this.v)}v(t){const s=t.target,{state:i}=s,e=s===this.k,a=e?\"external\":\"\",o={sw:s,originalEvent:t};!e&&this.C&&(o.isUpdate=!0),this.dispatchEvent(new n(a+i,o)),\"installed\"===i?this.D=setTimeout(()=>{\"installed\"===i&&this.S.waiting===s&&this.dispatchEvent(new n(a+\"waiting\",o))},h):\"activating\"===i&&(clearTimeout(this.D),e||this.g.resolve(s))}_(t){const s=this.R;s===navigator.serviceWorker.controller&&(this.dispatchEvent(new n(\"controlling\",{sw:s,originalEvent:t})),this.u.resolve(s))}m(t){const{data:s}=t;this.dispatchEvent(new n(\"message\",{data:s,originalEvent:t}))}}export{o as Workbox,t as messageSW};\n//# sourceMappingURL=workbox-window.prod.mjs.map\n"
  },
  {
    "path": "public/assets/libs/workbox/workbox-window.prod.umd.js",
    "content": "!function(n,t){\"object\"==typeof exports&&\"undefined\"!=typeof module?t(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],t):t((n=n||self).workbox={})}(this,function(n){\"use strict\";try{self[\"workbox:window:4.1.1\"]&&_()}catch(n){}var t=function(n,t){return new Promise(function(i){var e=new MessageChannel;e.port1.onmessage=function(n){return i(n.data)},n.postMessage(t,[e.port2])})};function i(n,t){for(var i=0;i<t.length;i++){var e=t[i];e.enumerable=e.enumerable||!1,e.configurable=!0,\"value\"in e&&(e.writable=!0),Object.defineProperty(n,e.key,e)}}function e(n){if(void 0===n)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return n}try{self[\"workbox:core:4.1.1\"]&&_()}catch(n){}var r=function(){var n=this;this.promise=new Promise(function(t,i){n.resolve=t,n.reject=i})},o=function(n,t){return new URL(n,location).href===new URL(t,location).href},u=function(n,t){Object.assign(this,t,{type:n})};function s(n){return function(){for(var t=[],i=0;i<arguments.length;i++)t[i]=arguments[i];try{return Promise.resolve(n.apply(this,t))}catch(n){return Promise.reject(n)}}}function a(n,t,i){return i?t?t(n):n:(n&&n.then||(n=Promise.resolve(n)),t?n.then(t):n)}function c(){}var f=function(n){var f,h;function v(t,i){var o;return void 0===i&&(i={}),(o=n.call(this)||this).t=t,o.i=i,o.o=0,o.u=new r,o.s=new r,o.h=new r,o.v=o.v.bind(e(e(o))),o.l=o.l.bind(e(e(o))),o.g=o.g.bind(e(e(o))),o.m=o.m.bind(e(e(o))),o}h=n,(f=v).prototype=Object.create(h.prototype),f.prototype.constructor=f,f.__proto__=h;var l,w,d,g=v.prototype;return g.register=s(function(n){var t,i,e=this,r=(void 0===n?{}:n).immediate,s=void 0!==r&&r;return t=function(){return e.p=Boolean(navigator.serviceWorker.controller),e.P=e.j(),a(e.O(),function(n){e.R=n,e.P&&(e._=e.P,e.s.resolve(e.P),e.h.resolve(e.P),e.k(e.P),e.P.addEventListener(\"statechange\",e.l,{once:!0}));var t=e.R.waiting;return t&&o(t.scriptURL,e.t)&&(e._=t,Promise.resolve().then(function(){e.dispatchEvent(new u(\"waiting\",{sw:t,wasWaitingBeforeRegister:!0}))})),e._&&e.u.resolve(e._),e.R.addEventListener(\"updatefound\",e.g),navigator.serviceWorker.addEventListener(\"controllerchange\",e.m,{once:!0}),\"BroadcastChannel\"in self&&(e.B=new BroadcastChannel(\"workbox\"),e.B.addEventListener(\"message\",e.v)),navigator.serviceWorker.addEventListener(\"message\",e.v),e.R})},(i=function(){if(!s&&\"complete\"!==document.readyState)return function(n,t){if(!t)return n&&n.then?n.then(c):Promise.resolve()}(new Promise(function(n){return addEventListener(\"load\",n)}))}())&&i.then?i.then(t):t(i)}),g.getSW=s(function(){return this._||this.u.promise}),g.messageSW=s(function(n){return a(this.getSW(),function(i){return t(i,n)})}),g.j=function(){var n=navigator.serviceWorker.controller;if(n&&o(n.scriptURL,this.t))return n},g.O=s(function(){var n=this;return function(n,t){try{var i=n()}catch(n){return t(n)}return i&&i.then?i.then(void 0,t):i}(function(){return a(navigator.serviceWorker.register(n.t,n.i),function(t){return n.C=performance.now(),t})},function(n){throw n})}),g.k=function(n){t(n,{type:\"WINDOW_READY\",meta:\"workbox-window\"})},g.g=function(){var n=this.R.installing;this.o>0||!o(n.scriptURL,this.t)||performance.now()>this.C+6e4?(this.L=n,this.R.removeEventListener(\"updatefound\",this.g)):(this._=n,this.u.resolve(n)),++this.o,n.addEventListener(\"statechange\",this.l)},g.l=function(n){var t=this,i=n.target,e=i.state,r=i===this.L,o=r?\"external\":\"\",s={sw:i,originalEvent:n};!r&&this.p&&(s.isUpdate=!0),this.dispatchEvent(new u(o+e,s)),\"installed\"===e?this.W=setTimeout(function(){\"installed\"===e&&t.R.waiting===i&&t.dispatchEvent(new u(o+\"waiting\",s))},200):\"activating\"===e&&(clearTimeout(this.W),r||this.s.resolve(i))},g.m=function(n){var t=this._;t===navigator.serviceWorker.controller&&(this.dispatchEvent(new u(\"controlling\",{sw:t,originalEvent:n})),this.h.resolve(t))},g.v=function(n){var t=n.data;this.dispatchEvent(new u(\"message\",{data:t,originalEvent:n}))},l=v,(w=[{key:\"active\",get:function(){return this.s.promise}},{key:\"controlling\",get:function(){return this.h.promise}}])&&i(l.prototype,w),d&&i(l,d),v}(function(){function n(){this.D={}}var t=n.prototype;return t.addEventListener=function(n,t){this.M(n).add(t)},t.removeEventListener=function(n,t){this.M(n).delete(t)},t.dispatchEvent=function(n){n.target=this,this.M(n.type).forEach(function(t){return t(n)})},t.M=function(n){return this.D[n]=this.D[n]||new Set},n}());n.Workbox=f,n.messageSW=t,Object.defineProperty(n,\"__esModule\",{value:!0})});\n//# sourceMappingURL=workbox-window.prod.umd.js.map\n"
  },
  {
    "path": "public/assets/materialsymbols.css",
    "content": "/* fallback */\n@font-face {\n    font-family: \"Material Symbols Outlined\";\n    font-style: normal;\n    font-weight: 100 700;\n    src: url(/assets/materialsymbols.woff2) format(\"woff2\");\n}\n\n.material-symbols-outlined {\n    font-family: \"Material Symbols Outlined\";\n    font-weight: normal;\n    font-style: normal;\n    font-size: 24px;\n    line-height: 1;\n    letter-spacing: normal;\n    text-transform: none;\n    display: inline-block;\n    white-space: nowrap;\n    word-wrap: normal;\n    direction: ltr;\n    text-rendering: optimizeLegibility;\n    -webkit-font-smoothing: antialiased;\n}\n"
  },
  {
    "path": "public/assets/matter.css",
    "content": "/* Matter 0.2.2 */\n\n/* Components */\n/* Button Contained */\n.matter-button-contained {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    --matter-helper-ontheme: var(--matter-ontheme-rgb, var(--matter-onprimary-rgb, 255, 255, 255));\n    position: relative;\n    display: inline-block;\n    box-sizing: border-box;\n    border: none;\n    border-radius: 4px;\n    padding: 0 16px;\n    min-width: 64px;\n    height: 36px;\n    vertical-align: middle;\n    text-align: center;\n    text-overflow: ellipsis;\n    color: rgb(var(--matter-helper-ontheme));\n    background-color: rgb(var(--matter-helper-theme));\n    box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 14px;\n    font-weight: 500;\n    line-height: 36px;\n    outline: none;\n    cursor: var(--cursor-pointer);\n    transition: box-shadow 0.2s;\n}\n.matter-button-contained::-moz-focus-inner {\n    border: none;\n}\n/* Highlight, Ripple */\n.matter-button-contained::before,\n.matter-button-contained::after {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    border-radius: inherit;\n    opacity: 0;\n}\n.matter-button-contained::before {\n    background-color: rgb(var(--matter-helper-ontheme));\n    transition: opacity 0.2s;\n}\n.matter-button-contained::after {\n    background: radial-gradient(circle at center, currentColor 1%, transparent 1%) center/10000% 10000% no-repeat;\n    transition: opacity 1s, background-size 0.5s;\n}\n/* Hover, Focus */\n.matter-button-contained:hover,\n.matter-button-contained:focus {\n    box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);\n}\n.matter-button-contained:hover::before {\n    opacity: 0.08;\n}\n.matter-button-contained:focus::before {\n    opacity: 0.24;\n}\n.matter-button-contained:hover:focus::before {\n    opacity: 0.32;\n}\n/* Active */\n.matter-button-contained:active {\n    box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);\n}\n.matter-button-contained:active::after {\n    opacity: 0.32;\n    background-size: 100% 100%;\n    transition: background-size 0s;\n}\n/* Disabled */\n.matter-button-contained:disabled {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.12);\n    box-shadow: none;\n    cursor: initial;\n}\n.matter-button-contained:disabled::before,\n.matter-button-contained:disabled::after {\n    opacity: 0;\n}\n/* Button Unelevated */\n.matter-button-unelevated {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    --matter-helper-ontheme: var(--matter-ontheme-rgb, var(--matter-onprimary-rgb, 255, 255, 255));\n    position: relative;\n    display: inline-block;\n    box-sizing: border-box;\n    border: none;\n    border-radius: 4px;\n    padding: 0 16px;\n    min-width: 64px;\n    height: 36px;\n    vertical-align: middle;\n    text-align: center;\n    text-overflow: ellipsis;\n    color: rgb(var(--matter-helper-ontheme));\n    background-color: rgb(var(--matter-helper-theme));\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 14px;\n    font-weight: 500;\n    line-height: 36px;\n    outline: none;\n    cursor: var(--cursor-pointer);\n}\n.matter-button-unelevated::-moz-focus-inner {\n    border: none;\n}\n/* Highlight, Ripple */\n.matter-button-unelevated::before,\n.matter-button-unelevated::after {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    border-radius: inherit;\n    opacity: 0;\n}\n.matter-button-unelevated::before {\n    background-color: rgb(var(--matter-helper-ontheme));\n    transition: opacity 0.2s;\n}\n.matter-button-unelevated::after {\n    background: radial-gradient(circle at center, currentColor 1%, transparent 1%) center/10000% 10000% no-repeat;\n    transition: opacity 1s, background-size 0.5s;\n}\n/* Hover, Focus */\n.matter-button-unelevated:hover::before {\n    opacity: 0.08;\n}\n.matter-button-unelevated:focus::before {\n    opacity: 0.24;\n}\n.matter-button-unelevated:hover:focus::before {\n    opacity: 0.32;\n}\n/* Active */\n.matter-button-unelevated:active::after {\n    opacity: 0.32;\n    background-size: 100% 100%;\n    transition: background-size 0s;\n}\n/* Disabled */\n.matter-button-unelevated:disabled {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.12);\n    cursor: initial;\n}\n.matter-button-unelevated:disabled::before,\n.matter-button-unelevated:disabled::after {\n    opacity: 0;\n}\n/* Button Outlined */\n.matter-button-outlined {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    position: relative;\n    display: inline-block;\n    box-sizing: border-box;\n    margin: 0;\n    border: solid 1px; /* Safari */\n    border-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.24);\n    border-radius: 4px;\n    padding: 0 16px;\n    min-width: 64px;\n    height: 36px;\n    vertical-align: middle;\n    text-align: center;\n    text-overflow: ellipsis;\n    color: rgb(var(--matter-helper-theme));\n    background-color: transparent;\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 14px;\n    font-weight: 500;\n    line-height: 34px;\n    outline: none;\n    cursor: var(--cursor-pointer);\n}\n.matter-button-outlined::-moz-focus-inner {\n    border: none;\n}\n/* Highlight, Ripple */\n.matter-button-outlined::before,\n.matter-button-outlined::after {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    border-radius: 3px;\n    opacity: 0;\n}\n.matter-button-outlined::before {\n    background-color: rgb(var(--matter-helper-theme));\n    transition: opacity 0.2s;\n}\n.matter-button-outlined::after {\n    background: radial-gradient(circle at center, currentColor 1%, transparent 1%) center/10000% 10000% no-repeat;\n    transition: opacity 1s, background-size 0.5s;\n}\n/* Hover, Focus */\n.matter-button-outlined:hover::before {\n    opacity: 0.04;\n}\n.matter-button-outlined:focus::before {\n    opacity: 0.12;\n}\n.matter-button-outlined:hover:focus::before {\n    opacity: 0.16;\n}\n/* Active */\n.matter-button-outlined:active::after {\n    opacity: 0.16;\n    background-size: 100% 100%;\n    transition: background-size 0s;\n}\n/* Disabled */\n.matter-button-outlined:disabled {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    background-color: transparent;\n    cursor: initial;\n}\n.matter-button-outlined:disabled::before,\n.matter-button-outlined:disabled::after {\n    opacity: 0;\n}\n/* Button Text */\n.matter-button-text {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    position: relative;\n    display: inline-block;\n    box-sizing: border-box;\n    margin: 0;\n    border: none;\n    border-radius: 4px;\n    padding: 0 8px;\n    min-width: 64px;\n    height: 36px;\n    vertical-align: middle;\n    text-align: center;\n    text-overflow: ellipsis;\n    color: rgb(var(--matter-helper-theme));\n    background-color: transparent;\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 14px;\n    font-weight: 500;\n    line-height: 36px;\n    outline: none;\n    cursor: var(--cursor-pointer);\n}\n.matter-button-text::-moz-focus-inner {\n    border: none;\n}\n/* Highlight, Ripple */\n.matter-button-text::before,\n.matter-button-text::after {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    border-radius: inherit;\n    opacity: 0;\n}\n.matter-button-text::before {\n    background-color: rgb(var(--matter-helper-theme));\n    transition: opacity 0.2s;\n}\n.matter-button-text::after {\n    background: radial-gradient(circle at center, currentColor 1%, transparent 1%) center/10000% 10000% no-repeat;\n    transition: opacity 1s, background-size 0.5s;\n}\n/* Hover, Focus */\n.matter-button-text:hover::before {\n    opacity: 0.04;\n}\n.matter-button-text:focus::before {\n    opacity: 0.12;\n}\n.matter-button-text:hover:focus::before {\n    opacity: 0.16;\n}\n/* Active */\n.matter-button-text:active::after {\n    opacity: 0.16;\n    background-size: 100% 100%;\n    transition: background-size 0s;\n}\n/* Disabled */\n.matter-button-text:disabled {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    background-color: transparent;\n    cursor: initial;\n}\n.matter-button-text:disabled::before,\n.matter-button-text:disabled::after {\n    opacity: 0;\n}\n/* Link */\n.matter-link {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    --matter-helper-safari1: rgba(var(--matter-helper-theme), 0.12);\n    border-radius: 4px;\n    color: rgb(var(--matter-helper-theme));\n    text-decoration: none;\n    transition: background-color 0.2s, box-shadow 0.2s;\n}\n/* Hover */\n.matter-link:hover {\n    text-decoration: underline;\n}\n/* Focus */\n.matter-link:focus {\n    background-color: var(--matter-helper-safari1);\n    box-shadow: 0 0 0 0.16em var(--matter-helper-safari1);\n    outline: none;\n}\n/* Active */\n.matter-link:active {\n    background-color: transparent;\n    box-shadow: none;\n}\n/* Progress Circular */\n.matter-progress-circular {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    -webkit-appearance: none;\n    -moz-appearance: none;\n    appearance: none;\n    box-sizing: border-box;\n    border: none;\n    border-radius: 50%;\n    padding: 0.25em;\n    width: 3em;\n    height: 3em;\n    color: rgb(var(--matter-helper-theme));\n    background-color: transparent;\n    font-size: 16px;\n    overflow: hidden;\n}\n.matter-progress-circular::-webkit-progress-bar {\n    background-color: transparent;\n}\n/* Indeterminate */\n.matter-progress-circular:indeterminate {\n    animation: matter-progress-circular 6s infinite cubic-bezier(0.3, 0.6, 1, 1);\n}\n:-ms-lang(x), .matter-progress-circular:indeterminate {\n    animation: none;\n}\n.matter-progress-circular:indeterminate::before,\n.matter-progress-circular:indeterminate::-webkit-progress-value {\n    content: \"\";\n    display: block;\n    box-sizing: border-box;\n    margin-bottom: 0.25em;\n    border: solid 0.25em currentColor;\n    border-radius: 50%;\n    width: 100% !important;\n    height: 100%;\n    background-color: transparent;\n    -webkit-clip-path: polygon(50% 50%, 37% 0, 50% 0, 50% 0, 50% 0, 50% 0);\n    clip-path: polygon(50% 50%, 37% 0, 50% 0, 50% 0, 50% 0, 50% 0);\n    animation: matter-progress-circular-pseudo 0.75s infinite linear alternate;\n    animation-play-state: inherit;\n    animation-delay: inherit;\n}\n.matter-progress-circular:indeterminate::-moz-progress-bar {\n    box-sizing: border-box;\n    border: solid 0.25em currentColor;\n    border-radius: 50%;\n    width: 100%;\n    height: 100%;\n    background-color: transparent;\n    clip-path: polygon(50% 50%, 37% 0, 50% 0, 50% 0, 50% 0, 50% 0);\n    animation: matter-progress-circular-pseudo 0.75s infinite linear alternate;\n    animation-play-state: inherit;\n    animation-delay: inherit;\n}\n.matter-progress-circular:indeterminate::-ms-fill {\n    animation-name: -ms-ring;\n}\n@keyframes matter-progress-circular {\n    0% {\n        transform: rotate(0deg);\n    }\n    12.5% {\n        transform: rotate(180deg);\n        animation-timing-function: linear;\n    }\n    25% {\n        transform: rotate(630deg);\n    }\n    37.5% {\n        transform: rotate(810deg);\n        animation-timing-function: linear;\n    }\n    50% {\n        transform: rotate(1260deg);\n    }\n    62.5% {\n        transform: rotate(1440deg);\n        animation-timing-function: linear;\n    }\n    75% {\n        transform: rotate(1890deg);\n    }\n    87.5% {\n        transform: rotate(2070deg);\n        animation-timing-function: linear;\n    }\n    100% {\n        transform: rotate(2520deg);\n    }\n}\n@keyframes matter-progress-circular-pseudo {\n    0% {\n        -webkit-clip-path: polygon(50% 50%, 37% 0, 50% 0, 50% 0, 50% 0, 50% 0);\n        clip-path: polygon(50% 50%, 37% 0, 50% 0, 50% 0, 50% 0, 50% 0);\n    }\n    18% {\n        -webkit-clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 0, 100% 0, 100% 0);\n        clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 0, 100% 0, 100% 0);\n    }\n    53% {\n        -webkit-clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 100%, 100% 100%, 100% 100%);\n        clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 100%, 100% 100%, 100% 100%);\n    }\n    88% {\n        -webkit-clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 100%, 0 100%, 0 100%);\n        clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 100%, 0 100%, 0 100%);\n    }\n    100% {\n        -webkit-clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 100%, 0 100%, 0 63%);\n        clip-path: polygon(50% 50%, 37% 0, 100% 0, 100% 100%, 0 100%, 0 63%);\n    }\n}\n/* Progress Linear */\n.matter-progress-linear {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    -webkit-appearance: none;\n    -moz-appearance: none;\n    appearance: none;\n    border: none;\n    width: 160px;\n    height: 4px;\n    vertical-align: middle;\n    color: rgb(var(--matter-helper-theme));\n    background-color: rgba(var(--matter-helper-theme), 0.12);\n}\n.matter-progress-linear::-webkit-progress-bar {\n    background-color: transparent;\n}\n/* Determinate */\n.matter-progress-linear::-webkit-progress-value {\n    background-color: currentColor;\n    transition: all 0.2s;\n}\n.matter-progress-linear::-moz-progress-bar {\n    background-color: currentColor;\n    transition: all 0.2s;\n}\n.matter-progress-linear::-ms-fill {\n    border: none;\n    background-color: currentColor;\n    transition: all 0.2s;\n}\n/* Indeterminate */\n.matter-progress-linear:indeterminate {\n    background-size: 200% 100%;\n    background-image:\n        linear-gradient(to right, currentColor 16%, transparent 16%),\n        linear-gradient(to right, currentColor 16%, transparent 16%),\n        linear-gradient(to right, currentColor 25%, transparent 25%);\n    animation: matter-progress-linear 1.8s infinite linear;\n}\n.matter-progress-linear:indeterminate::-webkit-progress-value {\n    background-color: transparent;\n}\n.matter-progress-linear:indeterminate::-moz-progress-bar {\n    background-color: transparent;\n}\n.matter-progress-linear:indeterminate::-ms-fill {\n    animation-name: none;\n}\n@keyframes matter-progress-linear {\n    0% {\n        background-position: 32% 0, 32% 0, 50% 0;\n    }\n    2% {\n        background-position: 32% 0, 32% 0, 50% 0;\n    }\n    21% {\n        background-position: 32% 0, -18% 0, 0 0;\n    }\n    42% {\n        background-position: 32% 0, -68% 0, -27% 0;\n    }\n    50% {\n        background-position: 32% 0, -93% 0, -46% 0;\n    }\n    56% {\n        background-position: 32% 0, -118% 0, -68% 0;\n    }\n    66% {\n        background-position: -11% 0, -200% 0, -100% 0;\n    }\n    71% {\n        background-position: -32% 0, -200% 0, -100% 0;\n    }\n    79% {\n        background-position: -54% 0, -242% 0, -100% 0;\n    }\n    86% {\n        background-position: -68% 0, -268% 0, -100% 0;\n    }\n    100% {\n        background-position: -100% 0, -300% 0, -100% 0;\n    }\n}\n/* Checkbox */\n.matter-checkbox {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    --matter-helper-ontheme: var(--matter-ontheme-rgb, var(--matter-onprimary-rgb, 255, 255, 255));\n    z-index: 0;\n    position: relative;\n    display: inline-block;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    line-height: 1.5;\n}\n/* Box */\n.matter-checkbox > input {\n    appearance: none;\n    -moz-appearance: none;\n    -webkit-appearance: none;\n    z-index: 1;\n    position: absolute;\n    display: block;\n    box-sizing: border-box;\n    margin: 3px 1px;\n    border: solid 2px; /* Safari */\n    border-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    border-radius: 2px;\n    width: 18px;\n    height: 18px;\n    outline: none;\n    cursor: var(--cursor-pointer);\n    transition: border-color 0.2s, background-color 0.2s;\n}\n/* Span */\n.matter-checkbox > input + span {\n    display: inline-block;\n    box-sizing: border-box;\n    padding-left: 30px;\n    width: inherit;\n    cursor: var(--cursor-pointer);\n}\n/* Highlight */\n.matter-checkbox > input + span::before {\n    content: \"\";\n    position: absolute;\n    left: -10px;\n    top: -8px;\n    display: block;\n    border-radius: 50%;\n    width: 40px;\n    height: 40px;\n    background-color: rgb(var(--matter-onsurface-rgb, 0, 0, 0));\n    opacity: 0;\n    transform: scale(1);\n    pointer-events: none;\n    transition: opacity 0.3s, transform 0.2s;\n}\n/* Check Mark */\n.matter-checkbox > input + span::after {\n    content: \"\";\n    z-index: 1;\n    display: block;\n    position: absolute;\n    top: 3px;\n    left: 1px;\n    box-sizing: content-box;\n    width: 10px;\n    height: 5px;\n    border: solid 2px transparent;\n    border-right-width: 0;\n    border-top-width: 0;\n    pointer-events: none;\n    transform: translate(3px, 4px) rotate(-45deg);\n    transition: border-color 0.2s;\n}\n/* Checked, Indeterminate */\n.matter-checkbox > input:checked,\n.matter-checkbox > input:indeterminate {\n    border-color: rgb(var(--matter-helper-theme));\n    background-color: rgb(var(--matter-helper-theme));\n}\n.matter-checkbox > input:checked + span::before,\n.matter-checkbox > input:indeterminate + span::before {\n    background-color: rgb(var(--matter-helper-theme));\n}\n.matter-checkbox > input:checked + span::after,\n.matter-checkbox > input:indeterminate + span::after {\n    border-color: rgb(var(--matter-helper-ontheme, 255, 255, 255));\n}\n.matter-checkbox > input:indeterminate + span::after {\n    border-left-width: 0;\n    transform: translate(4px, 3px);\n}\n/* Hover, Focus */\n.matter-checkbox:hover > input + span::before {\n    opacity: 0.04;\n}\n.matter-checkbox > input:focus + span::before {\n    opacity: 0.12;\n}\n.matter-checkbox:hover > input:focus + span::before {\n    opacity: 0.16;\n}\n/* Active */\n.matter-checkbox:active > input,\n.matter-checkbox:active:hover > input {\n    border-color: rgb(var(--matter-helper-theme));\n}\n.matter-checkbox:active > input:checked {\n    border-color: transparent;\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n}\n.matter-checkbox:active > input + span::before {\n    opacity: 1;\n    transform: scale(0);\n    transition: transform 0s, opacity 0s;\n}\n/* Disabled */\n.matter-checkbox > input:disabled {\n    border-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    cursor: initial;\n}\n.matter-checkbox > input:checked:disabled,\n.matter-checkbox > input:indeterminate:disabled {\n    border-color: transparent;\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n}\n.matter-checkbox > input:disabled + span {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    cursor: initial;\n}\n.matter-checkbox > input:disabled + span::before {\n    opacity: 0;\n    transform: scale(0);\n}\n/* Radio */\n.matter-radio {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    z-index: 0;\n    position: relative;\n    display: inline-block;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    line-height: 1.5;\n}\n/* Circle */\n.matter-radio > input {\n    appearance: none;\n    -moz-appearance: none;\n    -webkit-appearance: none;\n    z-index: 1;\n    position: absolute;\n    display: block;\n    box-sizing: border-box;\n    margin: 2px 0;\n    border: solid 2px; /* Safari */\n    border-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    border-radius: 50%;\n    width: 20px;\n    height: 20px;\n    outline: none;\n    cursor: var(--cursor-pointer);\n    transition: border-color 0.2s;\n}\n/* Span */\n.matter-radio > input + span {\n    display: inline-block;\n    box-sizing: border-box;\n    padding-left: 30px;\n    width: inherit;\n    cursor: var(--cursor-pointer);\n}\n/* Highlight */\n.matter-radio > input + span::before {\n    content: \"\";\n    position: absolute;\n    left: -10px;\n    top: -8px;\n    display: block;\n    border-radius: 50%;\n    width: 40px;\n    height: 40px;\n    background-color: rgb(var(--matter-onsurface-rgb, 0, 0, 0));\n    opacity: 0;\n    transform: scale(0);\n    pointer-events: none;\n    transition: opacity 0.3s, transform 0.2s;\n}\n/* Check Mark */\n.matter-radio > input + span::after {\n    content: \"\";\n    display: block;\n    position: absolute;\n    top: 2px;\n    left: 0;\n    border-radius: 50%;\n    width: 10px;\n    height: 10px;\n    background-color: rgb(var(--matter-helper-theme));\n    transform: translate(5px, 5px) scale(0);\n    transition: transform 0.2s;\n}\n/* Checked */\n.matter-radio > input:checked {\n    border-color: rgb(var(--matter-helper-theme));\n}\n.matter-radio > input:checked + span::before {\n    background-color: rgb(var(--matter-helper-theme));\n}\n.matter-radio > input:checked + span::after {\n    transform: translate(5px, 5px) scale(1);\n}\n/* Hover, Focus */\n.matter-radio:hover > input + span::before {\n    transform: scale(1);\n    opacity: 0.04;\n}\n.matter-radio > input:focus + span::before  {\n    transform: scale(1);\n    opacity: 0.12;\n}\n.matter-radio:hover > input:focus + span::before {\n    transform: scale(1);\n    opacity: 0.16;\n}\n/* Active */\n.matter-radio:active > input {\n    border-color: rgb(var(--matter-helper-theme));\n}\n.matter-radio:active > input + span::before,\n.matter-radio:active:hover > input + span::before {\n    opacity: 1;\n    transform: scale(0);\n    transition: transform 0s, opacity 0s;\n}\n/* Disabled */\n.matter-radio > input:disabled {\n    border-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    cursor: initial;\n}\n.matter-radio > input:disabled + span {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    cursor: initial;\n}\n.matter-radio > input:disabled + span::before {\n    opacity: 0;\n    transform: scale(0);\n}\n.matter-radio > input:disabled + span::after {\n    background-color: currentColor;\n}\n/* Switch */\n.matter-switch {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    z-index: 0;\n    position: relative;\n    display: inline-block;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    line-height: 1.5;\n}\n/* Track */\n.matter-switch > input {\n    appearance: none;\n    -moz-appearance: none;\n    -webkit-appearance: none;\n    z-index: 1;\n    position: relative;\n    float: right;\n    display: inline-block;\n    margin: 0 0 0 5px;\n    border: solid 5px transparent;\n    border-radius: 12px;\n    width: 46px;\n    height: 24px;\n    background-clip: padding-box;\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    outline: none;\n    cursor: var(--cursor-pointer);\n    transition: background-color 0.2s, opacity 0.2s;\n}\n/* Span */\n.matter-switch > input + span {\n    display: inline-block;\n    box-sizing: border-box;\n    margin-right: -51px;\n    padding-right: 51px;\n    width: inherit;\n    cursor: var(--cursor-pointer);\n}\n/* Highlight */\n.matter-switch > input + span::before {\n    content: \"\";\n    position: absolute;\n    right: 11px;\n    top: -8px;\n    display: block;\n    border-radius: 50%;\n    width: 40px;\n    height: 40px;\n    background-color: rgb(var(--matter-onsurface-rgb, 0, 0, 0));\n    opacity: 0;\n    transform: scale(1);\n    pointer-events: none;\n    transition: opacity 0.3s 0.1s, transform 0.2s 0.1s;\n}\n/* Thumb */\n.matter-switch > input + span::after {\n    content: \"\";\n    z-index: 1;\n    position: absolute;\n    top: 2px;\n    right: 21px;\n    border-radius: 50%;\n    width: 20px;\n    height: 20px;\n    background-color: rgb(var(--matter-surface-rgb, 255, 255, 255));\n    box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n    pointer-events: none;\n    transition: background-color 0.2s, transform 0.2s;\n}\n/* Checked */\n.matter-switch > input:checked {\n    background-color: rgba(var(--matter-helper-theme), 0.6);\n}\n.matter-switch > input:checked + span::before {\n    right: -5px;\n    background-color: rgb(var(--matter-helper-theme));\n}\n.matter-switch > input:checked + span::after {\n    background-color: rgb(var(--matter-helper-theme));\n    transform: translateX(16px);\n}\n/* Hover, Focus */\n.matter-switch:hover > input + span::before {\n    opacity: 0.04;\n}\n.matter-switch > input:focus + span::before {\n    opacity: 0.12;\n}\n.matter-switch:hover > input:focus + span::before {\n    opacity: 0.16;\n}\n/* Active */\n.matter-switch:active > input {\n    background-color: rgba(var(--matter-helper-theme), 0.6);\n}\n.matter-switch:active > input:checked {\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n}\n.matter-switch:active > input + span::before {\n    opacity: 1;\n    transform: scale(0);\n    transition: transform 0s, opacity 0s;\n}\n/* Disabled */\n.matter-switch > input:disabled {\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    opacity: 0.38;\n    cursor: default;\n}\n.matter-switch > input:checked:disabled {\n    background-color: rgba(var(--matter-helper-theme), 0.6);\n}\n.matter-switch > input:disabled + span {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0, 0.38));\n    cursor: default;\n}\n.matter-switch > input:disabled + span::before {\n    z-index: 1;\n    margin: 10px;\n    width: 20px;\n    height: 20px;\n    background-color: rgb(var(--matter-surface-rgb, 255, 255, 255));\n    transform: scale(1);\n    opacity: 1;\n    transition: none;\n}\n.matter-switch > input:disabled + span::after {\n    opacity: 0.38;\n}\n/* Textfield Standard */\n.matter-textfield-standard {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    position: relative;\n    display: inline-block;\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    line-height: 1.5;\n}\n/* Input, Textarea */\n.matter-textfield-standard > input,\n.matter-textfield-standard > textarea {\n    display: block;\n    box-sizing: border-box;\n    margin: 0;\n    border: none;\n    border-top: solid 24px transparent;\n    border-bottom: solid 1px rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    padding: 0 0 7px;\n    width: 100%;\n    height: inherit;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    -webkit-text-fill-color: currentColor; /* Safari */\n    background-color: transparent;\n    box-shadow: none; /* Firefox */\n    font-family: inherit;\n    font-size: inherit;\n    line-height: inherit;\n    caret-color: rgb(var(--matter-helper-theme));\n    transition: border-bottom 0.2s, background-color 0.2s;\n}\n/* Span */\n.matter-textfield-standard > input + span,\n.matter-textfield-standard > textarea + span {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    display: block;\n    box-sizing: border-box;\n    padding: 7px 0 0;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    font-size: 75%;\n    line-height: 18px;\n    pointer-events: none;\n    transition: color 0.2s, font-size 0.2s, line-height 0.2s;\n}\n/* Underline */\n.matter-textfield-standard > input + span::after,\n.matter-textfield-standard > textarea + span::after {\n    content: \"\";\n    position: absolute;\n    left: 0;\n    bottom: 0;\n    display: block;\n    width: 100%;\n    height: 2px;\n    background-color: rgb(var(--matter-helper-theme));\n    transform-origin: bottom center;\n    transform: scaleX(0);\n    transition: transform 0.2s;\n}\n/* Hover */\n.matter-textfield-standard:hover > input,\n.matter-textfield-standard:hover > textarea {\n    border-bottom-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n}\n/* Placeholder-shown */\n.matter-textfield-standard > input:not(:focus):placeholder-shown + span,\n.matter-textfield-standard > textarea:not(:focus):placeholder-shown + span {\n    font-size: inherit;\n    line-height: 56px;\n}\n/* Focus */\n.matter-textfield-standard > input:focus,\n.matter-textfield-standard > textarea:focus {\n    outline: none;\n}\n.matter-textfield-standard > input:focus + span,\n.matter-textfield-standard > textarea:focus + span {\n    color: rgb(var(--matter-helper-theme));\n}\n.matter-textfield-standard > input:focus + span::after,\n.matter-textfield-standard > textarea:focus + span::after {\n    transform: scale(1);\n}\n/* Disabled */\n.matter-textfield-standard > input:disabled,\n.matter-textfield-standard > textarea:disabled {\n    border-bottom-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n}\n.matter-textfield-standard > input:disabled + span,\n.matter-textfield-standard > textarea:disabled + span {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n}\n/* Faster transition in Safari for less noticable fractional font-size issue */\n@media not all and (min-resolution:.001dpcm) {\n    @supports (-webkit-appearance:none) {\n        .matter-textfield-standard > input,\n        .matter-textfield-standard > input + span,\n        .matter-textfield-standard > input + span::after,\n        .matter-textfield-standard > textarea,\n        .matter-textfield-standard > textarea + span,\n        .matter-textfield-standard > textarea + span::after {\n            transition-duration: 0.1s;\n        }\n    }\n}\n/* Textfield Filled */\n.matter-textfield-filled {\n    --matter-helper-theme: var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243));\n    position: relative;\n    display: inline-block;\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    line-height: 1.5;\n}\n/* Input, Textarea */\n.matter-textfield-filled > input,\n.matter-textfield-filled > textarea {\n    display: block;\n    box-sizing: border-box;\n    margin: 0;\n    border: none;\n    border-top: solid 24px transparent;\n    border-bottom: solid 1px rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    border-radius: 4px 4px 0 0;\n    padding: 0 12px 7px;\n    width: 100%;\n    height: inherit;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    -webkit-text-fill-color: currentColor; /* Safari */\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.04);\n    box-shadow: none; /* Firefox */\n    font-family: inherit;\n    font-size: inherit;\n    line-height: inherit;\n    caret-color: rgb(var(--matter-helper-theme));\n    transition: border-bottom 0.2s, background-color 0.2s;\n}\n/* Span */\n.matter-textfield-filled > input + span,\n.matter-textfield-filled > textarea + span {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    display: block;\n    box-sizing: border-box;\n    padding: 7px 12px 0;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    font-size: 75%;\n    line-height: 18px;\n    pointer-events: none;\n    transition: color 0.2s, font-size 0.2s, line-height 0.2s;\n}\n/* Underline */\n.matter-textfield-filled > input + span::after,\n.matter-textfield-filled > textarea + span::after {\n    content: \"\";\n    position: absolute;\n    left: 0;\n    bottom: 0;\n    display: block;\n    width: 100%;\n    height: 2px;\n    background-color: rgb(var(--matter-helper-theme));\n    transform-origin: bottom center;\n    transform: scaleX(0);\n    transition: transform 0.3s;\n}\n/* Hover */\n.matter-textfield-filled:hover > input,\n.matter-textfield-filled:hover > textarea {\n    border-bottom-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.08);\n}\n/* Placeholder-shown */\n.matter-textfield-filled > input:not(:focus):placeholder-shown + span,\n.matter-textfield-filled > textarea:not(:focus):placeholder-shown + span {\n    font-size: inherit;\n    line-height: 48px;\n}\n/* Focus */\n.matter-textfield-filled > input:focus,\n.matter-textfield-filled > textarea:focus {\n    outline: none;\n}\n.matter-textfield-filled > input:focus + span,\n.matter-textfield-filled > textarea:focus + span {\n    color: rgb(var(--matter-helper-theme));\n}\n.matter-textfield-filled > input:focus + span::after,\n.matter-textfield-filled > textarea:focus + span::after {\n    transform: scale(1);\n}\n/* Disabled */\n.matter-textfield-filled > input:disabled,\n.matter-textfield-filled > textarea:disabled {\n    border-bottom-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.24);\n}\n.matter-textfield-filled > input:disabled + span,\n.matter-textfield-filled > textarea:disabled + span {\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n}\n/* Faster transition in Safari for less noticable fractional font-size issue */\n@media not all and (min-resolution:.001dpcm) {\n    @supports (-webkit-appearance:none) {\n        .matter-textfield-filled > input,\n        .matter-textfield-filled > input + span,\n        .matter-textfield-filled > input + span::after,\n        .matter-textfield-filled > textarea,\n        .matter-textfield-filled > textarea + span,\n        .matter-textfield-filled > textarea + span::after {\n            transition-duration: 0.1s;\n        }\n    }\n}\n/* Textfield Outlined */\n.matter-textfield-outlined {\n    --matter-helper-theme: rgb(var(--matter-theme-rgb, var(--matter-primary-rgb, 33, 150, 243)));\n    --matter-helper-safari1: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    --matter-helper-safari2: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    --matter-helper-safari3: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    position: relative;\n    display: inline-block;\n    padding-top: 6px;\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    line-height: 1.5;\n}\n/* Input, Textarea */\n.matter-textfield-outlined > input,\n.matter-textfield-outlined > textarea {\n    box-sizing: border-box;\n    margin: 0;\n    border-style: solid;\n    border-width: 1px;\n    border-color: transparent var(--matter-helper-safari2) var(--matter-helper-safari2);\n    border-radius: 4px;\n    padding: 15px 13px 15px;\n    width: 100%;\n    height: inherit;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.87);\n    -webkit-text-fill-color: currentColor; /* Safari */\n    background-color: transparent;\n    box-shadow: inset 1px 0 transparent, inset -1px 0 transparent, inset 0 -1px transparent;\n    font-family: inherit;\n    font-size: inherit;\n    line-height: inherit;\n    caret-color: var(--matter-helper-theme);\n    transition: border 0.2s, box-shadow 0.2s;\n}\n.matter-textfield-outlined > input:not(:focus):placeholder-shown,\n.matter-textfield-outlined > textarea:not(:focus):placeholder-shown {\n    border-top-color: var(--matter-helper-safari2);\n}\n/* Span */\n.matter-textfield-outlined > input + span,\n.matter-textfield-outlined > textarea + span {\n    position: absolute;\n    top: 0;\n    left: 0;\n    display: flex;\n    width: 100%;\n    max-height: 100%;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.6);\n    font-size: 75%;\n    line-height: 15px;\n    cursor: text;\n    transition: color 0.2s, font-size 0.2s, line-height 0.2s;\n}\n.matter-textfield-outlined > input:not(:focus):placeholder-shown + span,\n.matter-textfield-outlined > textarea:not(:focus):placeholder-shown + span {\n    font-size: inherit;\n    line-height: 68px;\n}\n/* Corners */\n.matter-textfield-outlined > input + span::before,\n.matter-textfield-outlined > input + span::after,\n.matter-textfield-outlined > textarea + span::before,\n.matter-textfield-outlined > textarea + span::after {\n    content: \"\";\n    display: block;\n    box-sizing: border-box;\n    margin-top: 6px;\n    border-top: solid 1px var(--matter-helper-safari2);\n    min-width: 10px;\n    height: 8px;\n    pointer-events: none;\n    box-shadow: inset 0 1px transparent;\n    transition: border 0.2s, box-shadow 0.2s;\n}\n.matter-textfield-outlined > input + span::before,\n.matter-textfield-outlined > textarea + span::before {\n    margin-right: 4px;\n    border-left: solid 1px transparent;\n    border-radius: 4px 0;\n}\n.matter-textfield-outlined > input + span::after,\n.matter-textfield-outlined > textarea + span::after {\n    flex-grow: 1;\n    margin-left: 4px;\n    border-right: solid 1px transparent;\n    border-radius: 0 4px;\n}\n.matter-textfield-outlined > input:not(:focus):placeholder-shown + span::before,\n.matter-textfield-outlined > textarea:not(:focus):placeholder-shown + span::before,\n.matter-textfield-outlined > input:not(:focus):placeholder-shown + span::after,\n.matter-textfield-outlined > textarea:not(:focus):placeholder-shown + span::after {\n    border-top-color: transparent;\n}\n/* Hover */\n.matter-textfield-outlined:hover > input,\n.matter-textfield-outlined:hover > textarea {\n    border-color: transparent var(--matter-helper-safari3) var(--matter-helper-safari3);\n}\n.matter-textfield-outlined:hover > input + span::before,\n.matter-textfield-outlined:hover > textarea + span::before,\n.matter-textfield-outlined:hover > input + span::after,\n.matter-textfield-outlined:hover > textarea + span::after {\n    border-top-color: var(--matter-helper-safari3);\n}\n.matter-textfield-outlined:hover > input:not(:focus):placeholder-shown,\n.matter-textfield-outlined:hover > textarea:not(:focus):placeholder-shown {\n    border-color: var(--matter-helper-safari3);\n}\n/* Focus */\n.matter-textfield-outlined > input:focus,\n.matter-textfield-outlined > textarea:focus {\n    border-color: transparent var(--matter-helper-theme) var(--matter-helper-theme);\n    box-shadow: inset 1px 0 var(--matter-helper-theme), inset -1px 0 var(--matter-helper-theme), inset 0 -1px var(--matter-helper-theme);\n    outline: none;\n}\n.matter-textfield-outlined > input:focus + span,\n.matter-textfield-outlined > textarea:focus + span {\n    color: var(--matter-helper-theme);\n}\n.matter-textfield-outlined > input:focus + span::before,\n.matter-textfield-outlined > input:focus + span::after,\n.matter-textfield-outlined > textarea:focus + span::before,\n.matter-textfield-outlined > textarea:focus + span::after {\n    border-top-color: var(--matter-helper-theme) !important;\n    box-shadow: inset 0 1px var(--matter-helper-theme);\n}\n/* Disabled */\n.matter-textfield-outlined > input:disabled,\n.matter-textfield-outlined > input:disabled + span,\n.matter-textfield-outlined > textarea:disabled,\n.matter-textfield-outlined > textarea:disabled + span {\n    border-color: transparent var(--matter-helper-safari1) var(--matter-helper-safari1) !important;\n    color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.38);\n    pointer-events: none;\n}\n.matter-textfield-outlined > input:disabled + span::before,\n.matter-textfield-outlined > input:disabled + span::after,\n.matter-textfield-outlined > textarea:disabled + span::before,\n.matter-textfield-outlined > textarea:disabled + span::after {\n    border-top-color: var(--matter-helper-safari1) !important;\n}\n.matter-textfield-outlined > input:disabled:placeholder-shown,\n.matter-textfield-outlined > input:disabled:placeholder-shown + span,\n.matter-textfield-outlined > textarea:disabled:placeholder-shown,\n.matter-textfield-outlined > textarea:disabled:placeholder-shown + span {\n    border-top-color: var(--matter-helper-safari1) !important;\n}\n.matter-textfield-outlined > input:disabled:placeholder-shown + span::before,\n.matter-textfield-outlined > input:disabled:placeholder-shown + span::after,\n.matter-textfield-outlined > textarea:disabled:placeholder-shown + span::before,\n.matter-textfield-outlined > textarea:disabled:placeholder-shown + span::after {\n    border-top-color: transparent !important;\n}\n/* Faster transition in Safari for less noticable fractional font-size issue */\n@media not all and (min-resolution:.001dpcm) {\n    @supports (-webkit-appearance:none) {\n        .matter-textfield-outlined > input,\n        .matter-textfield-outlined > input + span,\n        .matter-textfield-outlined > textarea,\n        .matter-textfield-outlined > textarea + span,\n        .matter-textfield-outlined > input + span::before,\n        .matter-textfield-outlined > input + span::after,\n        .matter-textfield-outlined > textarea + span::before,\n        .matter-textfield-outlined > textarea + span::after {\n            transition-duration: 0.1s;\n        }\n    }\n}\n/* Tooltip */\n.matter-tooltip,\n.matter-tooltip-top {\n    z-index: 10;\n    position: absolute;\n    left: 0;\n    right: 0;\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 10px;\n    font-weight: 400;\n    line-height: 16px;\n    white-space: nowrap;\n    text-transform: none;\n    text-align: center;\n    pointer-events: none;\n}\n.matter-tooltip {\n    bottom: -40px;\n}\n.matter-tooltip-top {\n    top: -40px;\n}\n.matter-tooltip > span,\n.matter-tooltip-top > span {\n    position: -webkit-sticky;\n    position: sticky;\n    left: 0;\n    right: 0;\n    display: inline-block;\n    box-sizing: content-box;\n    margin: 0 -100vw;\n    border: solid 8px transparent;\n    border-radius: 12px;\n    padding: 4px 8px;\n    color: rgb(var(--matter-surface-rgb, 255, 255, 255));\n    background-clip: padding-box;\n    background-image: linear-gradient(rgba(var(--matter-surface-rgb, 255, 255, 255), 0.34), rgba(var(--matter-surface-rgb, 255, 255, 255), 0.34));\n    background-color: rgba(var(--matter-onsurface-rgb, 0, 0, 0), 0.85);\n    transform: scale(0);\n    opacity: 0;\n    pointer-events: auto;\n    transition: transform 0.075s, opacity 0.075s;\n}\n:not(html):hover > .matter-tooltip > span,\n.matter-tooltip:hover > span,\n:not(html):hover > .matter-tooltip-top > span,\n.matter-tooltip-top:hover > span {\n    transform: scale(1);\n    opacity: 1;\n    transition: transform 0.15s, opacity 0.15s;\n}\n:focus-within > .matter-tooltip > span,\n:focus-within > .matter-tooltip-top > span {\n    transform: scale(1);\n    opacity: 1;\n    transition: transform 0.15s, opacity 0.15s;\n}\n/* Non-desktop */\n@media (pointer: coarse), (hover: none) {\n\n    .matter-tooltip,\n    .matter-tooltip-top {\n        font-size: 14px;\n        line-height: 20px;\n    }\n\n    .matter-tooltip {\n        bottom: -48px;\n    }\n    .matter-tooltip-top {\n        top: -48px;\n    }\n\n    .matter-tooltip > span,\n    .matter-tooltip-top > span {\n        padding: 6px 16px;\n    }\n}\n/* Utilities */\n/* Colors */\n/* Components */\n.matter-primary {\n    --matter-theme-rgb: var(--matter-primary-rgb, 33, 150, 243);\n    --matter-ontheme-rgb: var(--matter-onprimary-rgb, 255, 255, 255);\n}\n.matter-secondary {\n    --matter-theme-rgb: var(--matter-secondary-rgb, 102, 0, 238);\n    --matter-ontheme-rgb: var(--matter-onsecondary-rgb, 255, 255, 255);\n}\n.matter-error {\n    --matter-theme-rgb: var(--matter-error-rgb, 238, 0, 0);\n    --matter-ontheme-rgb: var(--matter-error-rgb, 255, 255, 255);\n}\n.matter-warning {\n    --matter-theme-rgb: var(--matter-warning-rgb, 238, 102, 0);\n    --matter-ontheme-rgb: var(--matter-onwarning-rgb, 255, 255, 255);\n}\n.matter-success {\n    --matter-theme-rgb: var(--matter-success-rgb, 17, 136, 34);\n    --matter-ontheme-rgb: var(--matter-onsuccess-rgb, 255, 255, 255);\n}\n/* Text */\n.matter-primary-text {\n    color: rgb(var(--matter-primary-rgb, 33, 150, 243));\n}\n.matter-secondary-text {\n    color: rgb(var(--matter-secondary-rgb, 102, 0, 238));\n}\n.matter-error-text {\n    color: rgb(var(--matter-error-rgb, 238, 0, 0));\n}\n.matter-warning-text {\n    color: rgb(var(--matter-warning-rgb, 238, 102, 0));\n}\n.matter-success-text {\n    color: rgb(var(--matter-success-rgb, 17, 136, 34));\n}\n/* Typography */\n.matter-h1 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 96px;\n    font-weight: 300;\n    letter-spacing: -1.5px;\n    line-height: 120px;\n}\n.matter-h2 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 60px;\n    font-weight: 300;\n    letter-spacing: -0.5px;\n    line-height: 80px;\n}\n.matter-h3 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 48px;\n    font-weight: 400;\n    letter-spacing: 0;\n    line-height: 64px;\n}\n.matter-h4 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 34px;\n    font-weight: 400;\n    letter-spacing: 0.25px;\n    line-height: 48px;\n}\n.matter-h5 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 24px;\n    font-weight: 400;\n    letter-spacing: 0;\n    line-height: 36px;\n}\n.matter-h6 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 20px;\n    font-weight: 500;\n    letter-spacing: 0.15px;\n    line-height: 28px;\n}\n.matter-subtitle1 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    font-weight: 400;\n    letter-spacing: 0.15px;\n    line-height: 24px;\n}\n.matter-subtitle2 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 14px;\n    font-weight: 500;\n    letter-spacing: 0.1px;\n    line-height: 20px;\n}\n.matter-body1 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 16px;\n    font-weight: 400;\n    letter-spacing: 0.5px;\n    line-height: 24px;\n}\n.matter-body2 {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 14px;\n    font-weight: 400;\n    letter-spacing: 0.25px;\n    line-height: 20px;\n}\n.matter-button {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 14px;\n    font-weight: 500;\n    letter-spacing: 1.25px;\n    text-transform: uppercase;\n    line-height: 20px;\n}\n.matter-caption {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 12px;\n    font-weight: 400;\n    letter-spacing: 0.4px;\n    line-height: 20px;\n}\n.matter-overline {\n    font-family: var(--matter-font-family, \"Roboto\", \"Segoe UI\", BlinkMacSystemFont, system-ui, -apple-system);\n    font-size: 10px;\n    font-weight: 400;\n    letter-spacing: 1.5px;\n    text-transform: uppercase;\n    line-height: 16px;\n}"
  },
  {
    "path": "public/cursor_changer.js",
    "content": "const style = document.createElement(\"style\");\nconsole.log(\"Cursor Engine Injected\");\nconst anuraSettings = window.parent.parent.parent.anura.ui.theme.settings;\nstyle.textContent = `\n\t:root {\n\t\t--cursor-normal: url(\"/cursors/dark/normal.svg\") 6 0, default !important; \n\t\t--cursor-pointer: url(\"/cursors/dark/pointer.svg\") 6 0, pointer !important;\n\t\t--cursor-text: url(\"/cursors/dark/text.svg\") 10 0, text !important;\n\t\t--cursor-crosshair: url(\"/cursors/dark/crosshair.svg\") 0 0, crosshair !important;\n\t\t--cursor-wait: url(\"/cursors/dark/wait.svg\") 0 0, wait !important;\n\t\t--cursor-nw-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, nw-resize !important;\n\t\t--cursor-ne-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, ne-resize !important;\n\t\t--cursor-sw-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, sw-resize !important;\n\t\t--cursor-se-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, se-resize !important;\n\t\t--cursor-n-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, n-resize !important;\n\t\t--cursor-s-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, s-resize !important;\n\t\t--cursor-e-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, e-resize !important;\n\t\t--cursor-w-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, w-resize !important;\n\t\t--theme-fg: ${anuraSettings[\"foreground\"] || \"#FFFFFF\"};\n\t\t--theme-secondary-fg: ${anuraSettings[\"secondaryForeground\"] || \"#C1C1C1\"};\n\t\t--theme-border: ${anuraSettings[\"border\"] || \"#444444\"};\n\t\t--material-border: ${anuraSettings[\"border\"] || \"#444444\"};\n\t\t--theme-dark-border: ${anuraSettings[\"darkBorder\"] || \"#000000\"};\n\t\t--theme-bg: ${anuraSettings[\"darkBackground\"] || \"#202124\"};\n\t\t--material-bg: ${anuraSettings[\"darkBackground\"] || \"#202124\"};\n\t\t--theme-secondary-bg: ${anuraSettings[\"secondaryBackground\"] || \"#383838\"};\n\t\t--theme-dark-bg: ${anuraSettings[\"darkBackground\"] || \"#161616\"};\n\t\t--theme-accent: ${anuraSettings[\"accent\"] || \"#4285F4\"};\n\t\t--matter-helper-theme: ${anuraSettings[\"accent\"] || \"#4285F4\"};\n\t}\n\t* {\n\t\tcursor: var(--cursor-normal);\n\t}\n\ta, a:-webkit-any-link {\n\t\tcursor: var(--cursor-pointer) !important;\n\t}\n\tinput[type=\"text\"], textarea {\n\t\tcursor: var(--cursor-text) !important;\n\t}\n\t.crosshair {\n\t\tcursor: var(--cursor-crosshair) !important;\n\t}\n\t.loading {\n\t\tcursor: var(--cursor-wait) !important;\n\t}\n\tinput[disabled], button[disabled] {\n\t\tcursor: var(--cursor-normal) !important;\n\t}\n\t[contenteditable=\"true\"] {\n\t\tcursor: var(--cursor-text) !important;\n\t}\n`;\nconsole.log(\"Applied TB Styles\");\ndocument.head.appendChild(style);\n"
  },
  {
    "path": "public/lib/dreamland/all.js",
    "content": "// dreamland.js, MIT license\nconst DLFEATURES = ['css', 'jsxLiterals', 'usestring', 'stores']; const DLVERSION = '0.0.24';\n!function(e){const[t,n,r,s,o,l,i]=Array.from(Array(7),Symbol),f=\"dlcomponent\",a={};let c;function u(){if(c)return!0;const e=document.createElement(\"style\");e.textContent=\"@scope (.test) { :scope { color: red } }\",document.head.appendChild(e);const t=document.createElement(\"div\");t.className=\"test\",document.body.appendChild(t);const n=getComputedStyle(t).color;return document.head.removeChild(e),document.body.removeChild(t),c=\"rgb(255, 0, 0)\"==n,c}const d=50;function p(){return`${Array(4).fill(0).map((()=>Math.floor(36*Math.random()).toString(36))).join(\"\")}`}const m=e=>function(t,...n){let r=\"\";for(let e of t)r+=e+(n.shift()||\"\");return g(\"dl\"+p(),r,e)},h=m(!1),b=m(!0);function g(e,t,n){let r=a[t];if(r)return r;a[t]=e;const s=document.createElement(\"style\");document.head.appendChild(s);let o=\"\",l=\"\";for(t+=\"\\n\";;){let[e,...n]=t.split(\"\\n\");if(e.trim().endsWith(\"{\"))break;if(l+=e+\"\\n\",!(t=n.join(\"\\n\")))break}s.textContent=t;let i=!0;if(i=u(),n&&i){let t=\"\";for(const n of s.sheet.cssRules)n.selectorText||n.media?n.selectorText?.startsWith(\":\")?(n.selectorText=`.${e}${n.selectorText}`,t+=n.cssText):o+=n.cssText:t+=n.cssText;s.textContent=`.${e} {${l}} @scope (.${e}) to (:not(.${e}).${f} *) { ${o} } ${t}`}else{let t=\"\";n&&!i&&(t=function(e){let t=`:not(${e}).${f}`,n=(r,s)=>`${r} *${s>d?\"\":`:not(${n(r+\" \"+(s%2==0?e:t),s+1)})`}`;return`:not(${n(t,0)})`}(`.${e}`));const r=n=>{n.selectorText&&(n.selectorText=n.selectorText.split(\",\").map((n=>\"&\"===(n=n.trim())[0]?`.${e}${n.slice(1)}${t}`:\":\"===n[0]?`.${e}${n}${t}`:`.${e} ${n}${t}`)).join(\", \")),o+=n.cssText};for(const e of s.sheet.cssRules)e.media&&e.media.mediaText?(o+=`@media(${e.media.mediaText}){`,Array.from(e.cssRules).map(r),o+=\"}\"):r(e);s.textContent=`.${e} {${l}}${o}`}return e}let y=document;const $=Symbol();let v=!1;Object.defineProperty(window,\"use\",{get:()=>(v=!0,(e,o,...l)=>{if(e instanceof Array)return x(e,o,...l);T(e)||k(e),v=!1;let i={get value(){return function(e){let o=e[r],l=o[s],i=e[t],f=o[n];for(let e of l)if(f=f[e],!j(f))break;for(let e of i)f=e(f);return f}(i)}};if(k(e)){let n=[...e[t]];o&&n.push(o),i[r]=e[r],i[t]=n}else i[r]=e,i[t]=o?[o]:[];return i})}),Object.defineProperty(window,\"useChange\",{get:()=>(v=!0,(e,t)=>{v=!1,e=e instanceof Array?e:[e];for(let n of e)T(n)||k(n),A(use(n),t)})});const x=(e,...t)=>{v=!1;let n=w({});const r=[];for(const s in e)if(r.push(e[s]),t[s]){let e=t[s];if(T(e)&&(e=use(e)),k(e)){const t=r.length;let s;A(use(e),(e=>{r[t]=String(e);let o=r.join(\"\");o!=s&&(n.string=o),s=o}))}else r.push(String(e))}return n.string=r.join(\"\"),use(n.string)};let S=new Map;function w(e){j(e),e[o]=[],e[n]=e;let l=Symbol.toPrimitive,f=new Proxy(e,{get(e,o,i){if(v){let f=Symbol(),a=new Proxy({[n]:e,[r]:i,[s]:[o],[l]:()=>f},{get:(e,o)=>[n,r,s,t,l].includes(o)?e[o]:(o=S.get(o)||o,e[s].push(o),a)});return S.set(f,a),a}return Reflect.get(e,o,i)},set(e,t,n){let r=Reflect.set(e,t,n);for(let r of e[o])r(e,t,n);return e[i]&&e[i](e,t,e[t]),r}});return f}let j=e=>e instanceof Object;function L(e){return j(e)&&o in e}function T(e){return j(e)&&s in e}function k(e){return j(e)&&t in e}function A(e,l){k(e);let i,f=e[r],a=e[t],c=[];function u(){let e=f[n];for(i of c)if(e=e[i],!j(e))break;for(let t of a)e=t(e);l(e)}let d=(e,t)=>function r(s,l,i){if(l===c[t]&&e===s&&(u(),j(i))){let e=i[o];e&&!e.includes(r)&&e.push(d(i[n],t+1))}};for(let e in f[s]){let t=f[s][e];j(t)&&t[n]?A(t,(t=>{c[e]=t,u()})):c[e]=t}let p=d(f[n],0);f[n][o].push(p),p(f[n],c[0],f[n][c[0]])}function O(e,t,n){let r,s,o,l;A(e,(e=>{o=s?.[0],o&&(r=o.previousSibling||(l=o.parentNode)),s&&s.forEach((e=>e.remove())),s=E(n?e?n.then:n.otherwise:e,(e=>{r?(l?(r.prepend(e),l=null):r.after(e),r=e):t(e)}))}))}let N=e=>t=>{let n=e[r],o=e[s],l=0;for(;l<o.length-1;l++)if(n=n[o[l]],!j(n))return;n[o[l]]=t};function C(e,t,...n){if(e==$)return n;if(\"function\"==typeof e){let s=w(Object.create(e.prototype));for(let e in t){let n=t[e];if(e.startsWith(\"bind:\")){k(n);let o=N(n[r]),l=e.substring(5);if(\"this\"==l)o(s);else{let e=!1;A(n,(t=>{e?e=!1:(e=!0,s[l]=t)})),A(use(s[l]),(t=>{e?e=!1:(e=!0,o(t))}))}delete t[e]}}Object.assign(s,t),s.children=[];for(let e of n)E(e,s.children.push.bind(s.children));let o=e.apply(s);o.$=s,s.root=o;let l=o.classList,i=s.css,a=e.name.replaceAll(\"$\",\"-\");return i&&l.add(g(`${a}-${p()}`,i,!0)),s._leak||l.add(f),o.setAttribute(\"data-component\",e.name),\"function\"==typeof s.mount&&s.mount(),o}let s=t?.xmlns,o=s?y.createElementNS(s,e):y.createElement(e);for(let e of n){E(e,o.append.bind(o))}if(!t)return o;((e,n)=>{if(!(e in t))return;n(t[e]),delete t[e]})(\"class\",(e=>{if(\"string\"==typeof e||e instanceof Array||k(e),\"string\"!=typeof e)if(k(e)){let t=\"\";A(e,(e=>{for(let e of t.split(\" \"))e&&o.classList.remove(e);if(\"string\"==typeof e){for(let t of e.split(\" \"))t&&o.classList.add(t);t=e}}))}else for(let t of e)if(k(t)){let e=null;A(t,(t=>{\"string\"==typeof e&&o.classList.remove(e),o.classList.add(t),e=t}))}else o.classList.add(t);else o.setAttribute(\"class\",e)}));for(let e in t){let n=t[e];if(e.startsWith(\"bind:\")){k(n);let s=e.substring(5),l=N(n[r]);\"this\"==s?l(o):\"value\"==s?(A(n,(e=>o.value=e)),o.addEventListener(\"change\",(()=>l(o.value)))):\"checked\"==s&&(A(n,(e=>o.checked=e)),o.addEventListener(\"click\",(()=>l(o.checked)))),delete t[e]}if(e.startsWith(\"class:\")){let r=e.substring(6);k(n)?A(n,(e=>{e?o.classList.add(r):o.classList.remove(r)})):n&&o.classList.add(r),delete t[e]}if(\"style\"==e&&j(n)&&!k(n)){for(let e in n){let t=n[e];k(t)?A(t,(t=>o.style[e]=t)):o.style[e]=t}delete t[e]}}for(let e in t){let n=t[e];k(n)?A(n,(t=>{P(o,e,t)})):P(o,e,n)}return s&&(o.innerHTML=o.innerHTML),o}function E(e,t){let n,r,s;if(k(e))O(e,t);else{if(!j(e)||!(l in e)){if(e instanceof Node)return t(e),[e];if(e instanceof Array){for(n of(r=[],e))r=r.concat(E(n,t));return r[0]||(r=E(\"\",t)),r}return null==e&&(e=\"\"),s=y.createTextNode(e),t(s),[s]}O(e[l],t,e)}}function P(e,t,n){if(!n&&e.hasAttribute(t)&&e.removeAttribute(t),n)if(t.startsWith(\"on:\")){let r=t.substring(3);for(let t of r.split(\"$\"))e.addEventListener(t,((...t)=>{self.$el=e,n(...t)}))}else e.setAttribute(t,n)}e.$if=function(e,t,n){return n??=y.createTextNode(\"\"),k(e)?{[l]:e,then:t,otherwise:n}:e?t:n},e.$state=w,e.$store=function(e,{ident:t,backing:r,autosave:s}){let o,l;if(\"string\"==typeof r){if(\"localstorage\"===r)o=()=>localStorage.getItem(t),l=(e,t)=>{localStorage.setItem(e,t)}}else({read:o,write:l}=r);let f=()=>{console.info(\"[dreamland.js]: saving \"+t);let n={},r=0,s=e=>{let t={stateful:L(e),values:{}},o=r++;n[o]=t;for(let n in e){let r=e[n];if(!k(r))switch(typeof r){case\"string\":case\"number\":case\"boolean\":case\"undefined\":t.values[n]=JSON.stringify(r);break;case\"object\":if(r instanceof Array){t.values[n]=r.map((e=>\"object\"==typeof e?s(e):JSON.stringify(e)));break}null===r?t.values[n]=\"null\":(r.__proto__,Object.prototype,t.values[n]=s(r))}}return o};s(e);let o=JSON.stringify(n);l(t,o)},a=(e,t,r)=>{L(r)&&(r[n][i]=a),f()},c=JSON.parse(o(t));if(c){let t={},n=e=>{if(t[e])return t[e];let r=c[e],o={};for(let e in r.values){let t=r.values[e];o[e]=\"string\"==typeof t?JSON.parse(t):t instanceof Array?t.map((e=>\"string\"==typeof e?JSON.parse(e):n(e))):n(t)}r.stateful&&\"auto\"==s&&(o[i]=a);let l=r.stateful?w(o):o;return t[e]=l,l};e=n(0)}switch(s){case\"beforeunload\":addEventListener(\"beforeunload\",f);break;case\"manual\":break;case\"auto\":e[i]=a}return w(e)},e.Fragment=$,e.css=h,e.h=C,e.html=function(e,...t){e=[...e];let n=\"\",r={};for(let s=0;s<e.length;s++){let o=e[s],l=t[s],i=t[s]instanceof Function&&/^ *\\/>/.exec(e[s+1]);if(/< *$/.test(o)&&i&&(e[s+1]=e[s+1].substr(i.index+i[0].length)),n+=o,s<t.length){let e,t=Object.values(r).findIndex((e=>e===l));-1!==t?e=Object.keys(r)[t]:(e=\"h\"+p(),r[e]=l),n+=e,i&&(n+=`></${e}>`)}}let s=(new DOMParser).parseFromString(n,\"text/html\");return s.body.children.length,function e(t){let n=t.nodeName.toLowerCase();if(\"#text\"===n)return t.textContent;n in r&&(n=r[n]);let s=[...t.childNodes].map(e);for(let e=0;e<s.length;e++){let t=s[e];if(\"string\"==typeof t)for(const[n,o]of Object.entries(r)){if(!t)break;if(!t.includes(n))continue;let r;[r,t]=t.split(n),s=[...s.slice(0,e),r,o,t,...s.slice(e+1)],e+=2}}let o={};if(!t.attributes)return t;for(const e of[...t.attributes]){let t=e.nodeValue;t in r&&(t=r[t]),o[e.name]=t}return C(n,o,s)}(s.body.children[0])},e.isDLPtr=k,e.isStateful=L,e.scope=b}(window)\n//# sourceMappingURL=all.js.map\n"
  },
  {
    "path": "public/lib/dreamland/dev.js",
    "content": "\n// dreamland.js, MIT license\nconst DLFEATURES = ['css', 'jsxLiterals', 'usestring', 'stores']; const DLVERSION = '0.0.24';\n(function (exports) {\n\n    class DreamlandError extends Error {\n        constructor(message) {\n            super('[dreamland-js/dev] ' + message);\n            this.name = 'DreamlandDevError';\n        }\n    }\n\n    function log(message) {\n        console.log('[dreamland-js/dev] ' + message);\n    }\n\n    function panic(message) {\n        throw new DreamlandError('fatal: ' + message)\n    }\n\n    function assert(condition, message) {\n        if (!condition) {\n            panic(message);\n        }\n    }\n\n    // We add some extra properties into various objects throughout, better to use symbols and not interfere. this is just a tiny optimization\n    const [USE_COMPUTED, TARGET, PROXY, STEPS, LISTENERS, IF, STATEHOOK] =\n        Array.from(Array(7), Symbol);\n\n    const cssBoundary = 'dlcomponent';\n\n    var CONSTS = /*#__PURE__*/Object.freeze({\n        __proto__: null,\n        IF: IF,\n        LISTENERS: LISTENERS,\n        PROXY: PROXY,\n        STATEHOOK: STATEHOOK,\n        STEPS: STEPS,\n        TARGET: TARGET,\n        USE_COMPUTED: USE_COMPUTED,\n        cssBoundary: cssBoundary\n    });\n\n    const cssmap = {};\n\n    /* POLYFILL.SCOPE.START */\n    let scopeSupported;\n    function checkScopeSupported() {\n        if (scopeSupported) return true\n        const style = document.createElement('style');\n        style.textContent = '@scope (.test) { :scope { color: red } }';\n        document.head.appendChild(style);\n\n        const testElement = document.createElement('div');\n        testElement.className = 'test';\n        document.body.appendChild(testElement);\n\n        const computedColor = getComputedStyle(testElement).color;\n        document.head.removeChild(style);\n        document.body.removeChild(testElement);\n\n        scopeSupported = computedColor == 'rgb(255, 0, 0)';\n        return scopeSupported\n    }\n    const depth = 50;\n    // polyfills @scope for firefox and older browsers, using a :not selector recursively increasing in depth\n    // depth 50 means that after 50 layers of nesting, switching between an unrelated component and the target component, it will eventually stop applying styles (or let them leak into children)\n    // this is slow. please ask mozilla to implement @scope\n    function polyfill_scope(target) {\n        let boundary = `:not(${target}).${cssBoundary}`;\n        let g = (str, i) =>\n            `${str} *${i > depth ? '' : `:not(${g(str + ' ' + (i % 2 == 0 ? target : boundary), i + 1)})`}`;\n        return `:not(${g(boundary, 0)})`\n    }\n    /* POLYFILL.SCOPE.END */\n\n    function genuid() {\n        return `${Array(4)\n        .fill(0)\n        .map(() => {\n            return Math.floor(Math.random() * 36).toString(36)\n        })\n        .join('')}`\n    }\n\n    const csstag = (scoped) =>\n        function css(strings, ...values) {\n            let str = '';\n            for (let f of strings) {\n                str += f + (values.shift() || '');\n            }\n\n            return genCss('dl' + genuid(), str, scoped)\n        };\n\n    const css = csstag(false);\n    const scope = csstag(true);\n\n    function genCss(uid, str, scoped) {\n        let cached = cssmap[str];\n        if (cached) return cached\n\n        cssmap[str] = uid;\n\n        const styleElement = document.createElement('style');\n        document.head.appendChild(styleElement);\n\n        let newstr = '';\n        let selfstr = '';\n\n        str += '\\n';\n        for (;;) {\n            let [first, ...rest] = str.split('\\n');\n            if (first.trim().endsWith('{')) break\n\n            selfstr += first + '\\n';\n            str = rest.join('\\n');\n            if (!str) break\n        }\n\n        styleElement.textContent = str;\n        let scopeSupported = true;\n        /* POLYFILL.SCOPE.START */\n        scopeSupported = checkScopeSupported();\n        /* POLYFILL.SCOPE.END */\n        if (scoped && scopeSupported) {\n            let extstr = '';\n            for (const rule of styleElement.sheet.cssRules) {\n                if (!rule.selectorText && !rule.media) {\n                    extstr += rule.cssText;\n                } else if (rule.selectorText?.startsWith(':')) {\n                    rule.selectorText = `.${uid}${rule.selectorText}`;\n                    extstr += rule.cssText;\n                } else {\n                    newstr += rule.cssText;\n                }\n            }\n\n            styleElement.textContent = `.${uid} {${selfstr}} @scope (.${uid}) to (:not(.${uid}).${cssBoundary} *) { ${newstr} } ${extstr}`;\n        } else {\n            let scopedSelector = '';\n            /* POLYFILL.SCOPE.START */\n            if (scoped && !scopeSupported)\n                scopedSelector = polyfill_scope(`.${uid}`);\n            /* POLYFILL.SCOPE.END */\n            const processRule = (rule) => {\n                if (rule.selectorText)\n                    rule.selectorText = rule.selectorText\n                        .split(',')\n                        .map((x) => {\n                            x = x.trim();\n                            if (x[0] === '&') {\n                                return `.${uid}${x.slice(1)}${scopedSelector}`\n                            } else if (x[0] === ':') {\n                                return `.${uid}${x}${scopedSelector}`\n                            } else {\n                                return `.${uid} ${x}${scopedSelector}`\n                            }\n                        })\n                        .join(', ');\n                newstr += rule.cssText;\n            };\n            for (const rule of styleElement.sheet.cssRules) {\n                if (rule.media && rule.media.mediaText) {\n                    newstr += `@media(${rule.media.mediaText}){`;\n                    Array.from(rule.cssRules).map(processRule);\n                    newstr += '}';\n                } else {\n                    processRule(rule);\n                }\n            }\n\n            styleElement.textContent = `.${uid} {${selfstr}}${newstr}`;\n        }\n\n        return uid\n    }\n\n    /* FEATURE.CSS.END */\n\n    // saves a few characters, since document will never change\n    let doc = document;\n\n    const Fragment = Symbol();\n\n    // whether to return the true value from a stateful object or a \"trap\" containing the pointer\n    let __use_trap = false;\n\n    // Say you have some code like\n    //// let state = $state({\n    ////    a: $state({\n    ////      b: 1\n    ////    })\n    //// })\n    //// let elm = <p>{window.use(state.a.b)}</p>\n    //\n    // According to the standard, the order of events is as follows:\n    // - the getter for window.use gets called, setting __use_trap true\n    // - the proxy for state.a is triggered and instead of returning the normal value it returns the trap\n    // - the trap proxy is triggered, storing [\"a\", \"b\"] as the order of events\n    // - the function that the getter of `use` returns is called, setting __use_trap to false and restoring order\n    // - the JSX factory h() is now passed the trap, which essentially contains a set of pointers pointing to the theoretical value of b\n    // - with the setter on the stateful proxy, we can listen to any change in any of the nested layers and call whatever listeners registered\n    // - the result is full intuitive reactivity with minimal overhead\n    Object.defineProperty(window, 'use', {\n        get: () => {\n            __use_trap = true;\n            return (ptr, transform, ...rest) => {\n                /* FEATURE.USESTRING.START */\n                if (ptr instanceof Array) return usestr(ptr, transform, ...rest)\n                /* FEATURE.USESTRING.END */\n                assert(\n                    isDLPtrInternal(ptr) || isDLPtr(ptr),\n                    'a value was passed into use() that was not part of a stateful context'\n                );\n                __use_trap = false;\n\n                let newp = {\n                    get value() {\n                        return resolve(newp)\n                    },\n                };\n\n                if (isDLPtr(ptr)) {\n                    let cloned = [...ptr[USE_COMPUTED]];\n                    if (transform) {\n                        cloned.push(transform);\n                    }\n\n                    newp[PROXY] = ptr[PROXY];\n                    newp[USE_COMPUTED] = cloned;\n                } else {\n                    newp[PROXY] = ptr;\n                    newp[USE_COMPUTED] = transform ? [transform] : [];\n                }\n\n                return newp\n            }\n        },\n    });\n\n    Object.defineProperty(window, 'useChange', {\n        get: () => {\n            __use_trap = true;\n            return (ptrs, callback) => {\n                __use_trap = false;\n                ptrs = ptrs instanceof Array ? ptrs : [ptrs];\n                for (let ptr of ptrs) {\n                    assert(\n                        isDLPtrInternal(ptr) || isDLPtr(ptr),\n                        'a value was passed into useChange() that was not part of a stateful context'\n                    );\n                    handle(use(ptr), callback);\n                }\n            }\n        },\n    });\n\n    /* FEATURE.USESTRING.START */\n    const usestr = (strings, ...values) => {\n        __use_trap = false;\n\n        let state = $state({});\n        const flattened_template = [];\n        for (const i in strings) {\n            flattened_template.push(strings[i]);\n            if (values[i]) {\n                let prop = values[i];\n\n                if (isDLPtrInternal(prop)) prop = use(prop);\n\n                if (isDLPtr(prop)) {\n                    const current_i = flattened_template.length;\n                    let oldparsed;\n                    handle(use(prop), (val) => {\n                        flattened_template[current_i] = String(val);\n                        let parsed = flattened_template.join('');\n                        if (parsed != oldparsed) state.string = parsed;\n                        oldparsed = parsed;\n                    });\n                } else {\n                    flattened_template.push(String(prop));\n                }\n            }\n        }\n\n        state.string = flattened_template.join('');\n\n        return use(state.string)\n    };\n    /* FEATURE.USESTRING.END */\n\n    let TRAPS = new Map();\n    // This wraps the target in a proxy, doing 2 things:\n    // - whenever a property is accessed, return a \"trap\" that catches and records accessors\n    // - whenever a property is set, notify the subscribed listeners\n    // This is what makes our \"pass-by-reference\" magic work\n    function $state(target) {\n        assert(isobj(target), '$state() requires an object');\n        target[LISTENERS] = [];\n        target[TARGET] = target;\n        let TOPRIMITIVE = Symbol.toPrimitive;\n\n        let proxy = new Proxy(target, {\n            get(target, property, proxy) {\n                if (__use_trap) {\n                    let sym = Symbol();\n                    let trap = new Proxy(\n                        {\n                            [TARGET]: target,\n                            [PROXY]: proxy,\n                            [STEPS]: [property],\n                            [TOPRIMITIVE]: () => sym,\n                        },\n                        {\n                            get(target, property) {\n                                if (\n                                    [\n                                        TARGET,\n                                        PROXY,\n                                        STEPS,\n                                        USE_COMPUTED,\n                                        TOPRIMITIVE,\n                                    ].includes(property)\n                                )\n                                    return target[property]\n                                property = TRAPS.get(property) || property;\n                                target[STEPS].push(property);\n                                return trap\n                            },\n                        }\n                    );\n                    TRAPS.set(sym, trap);\n\n                    return trap\n                }\n                return Reflect.get(target, property, proxy)\n            },\n            set(target, property, val) {\n                let trap = Reflect.set(target, property, val);\n                for (let listener of target[LISTENERS]) {\n                    listener(target, property, val);\n                }\n\n                /* FEATURE.STORES.START */\n                if (target[STATEHOOK])\n                    target[STATEHOOK](target, property, target[property]);\n                /* FEATURE.STORES.END */\n\n                return trap\n            },\n        });\n\n        return proxy\n    }\n\n    let isobj = (o) => o instanceof Object;\n    let isfn = (o) => typeof o === 'function';\n\n    function isStateful(obj) {\n        return isobj(obj) && LISTENERS in obj\n    }\n\n    function isDLPtrInternal(arr) {\n        return isobj(arr) && STEPS in arr\n    }\n\n    function isDLPtr(arr) {\n        return isobj(arr) && USE_COMPUTED in arr\n    }\n\n    function $if(condition, then, otherwise) {\n        otherwise ??= doc.createTextNode('');\n        if (!isDLPtr(condition)) return condition ? then : otherwise\n\n        return { [IF]: condition, then, otherwise }\n    }\n\n    function resolve(exptr) {\n        let proxy = exptr[PROXY];\n        let steps = proxy[STEPS];\n        let computed = exptr[USE_COMPUTED];\n\n        let val = proxy[TARGET];\n        for (let step of steps) {\n            val = val[step];\n            if (!isobj(val)) break\n        }\n\n        for (let transform of computed) {\n            val = transform(val);\n        }\n\n        return val\n    }\n\n    // This lets you subscribe to a stateful object\n    function handle(exptr, callback) {\n        assert(isDLPtr(exptr), 'handle() requires a stateful object');\n        assert(isfn(callback), 'handle() requires a callback function');\n\n        let ptr = exptr[PROXY],\n            computed = exptr[USE_COMPUTED],\n            step,\n            resolvedSteps = [];\n\n        function update() {\n            let val = ptr[TARGET];\n            for (step of resolvedSteps) {\n                val = val[step];\n                if (!isobj(val)) break\n            }\n\n            for (let transform of computed) {\n                val = transform(val);\n            }\n            callback(val);\n        }\n\n        // inject ourselves into nested objects\n        let curry = (target, i) =>\n            function subscription(tgt, prop, val) {\n                if (prop === resolvedSteps[i] && target === tgt) {\n                    update();\n\n                    if (isobj(val)) {\n                        let v = val[LISTENERS];\n                        if (v && !v.includes(subscription)) {\n                            v.push(curry(val[TARGET], i + 1));\n                        }\n                    }\n                }\n            };\n\n        // imagine we have a `use(state.a[state.b])`\n        // simply recursively resolve any of the intermediate steps until we get to the final value\n        // this will \"misfire\" occassionaly with a scenario like state.a[state.b][state.c] and call the listener more than needed\n        // it is up to the caller to not implode\n        for (let i in ptr[STEPS]) {\n            let step = ptr[STEPS][i];\n            if (isobj(step) && step[TARGET]) {\n                handle(step, (val) => {\n                    resolvedSteps[i] = val;\n                    update();\n                });\n                continue\n            }\n            resolvedSteps[i] = step;\n        }\n\n        let sub = curry(ptr[TARGET], 0);\n        ptr[TARGET][LISTENERS].push(sub);\n\n        sub(ptr[TARGET], resolvedSteps[0], ptr[TARGET][resolvedSteps[0]]);\n    }\n\n    function JSXAddFixedWrapper(ptr, cb, $if) {\n        let before, appended, first, flag;\n        handle(ptr, (val) => {\n            first = appended?.[0];\n            if (first) before = first.previousSibling || (flag = first.parentNode);\n            if (appended) appended.forEach((a) => a.remove());\n\n            appended = JSXAddChild(\n                $if ? (val ? $if.then : $if.otherwise) : val,\n                (el) => {\n                    if (before) {\n                        if (flag) {\n                            before.prepend(el);\n                            flag = null;\n                        } else before.after(el);\n                        before = el;\n                    } else cb(el);\n                }\n            );\n        });\n    }\n\n    // returns a function that sets a reference\n    // the currying is a small optimization\n    let curryset = (ptr) => (val) => {\n        let next = ptr[PROXY];\n        let steps = ptr[STEPS];\n        let i = 0;\n        for (; i < steps.length - 1; i++) {\n            next = next[steps[i]];\n            if (!isobj(next)) return\n        }\n        next[steps[i]] = val;\n    };\n\n    // Actual JSX factory. Responsible for creating the HTML elements and all of the *reactive* syntactic sugar\n    function h(type, props, ...children) {\n        if (type == Fragment) return children\n        if (typeof type == 'function') {\n            // functional components. create the stateful object\n            let newthis = $state(Object.create(type.prototype));\n\n            for (let name in props) {\n                let ptr = props[name];\n                if (name.startsWith('bind:')) {\n                    assert(\n                        isDLPtr(ptr),\n                        'bind: requires a reference pointer from use'\n                    );\n\n                    let set = curryset(ptr[PROXY]);\n                    let propname = name.substring(5);\n                    if (propname == 'this') {\n                        set(newthis);\n                    } else {\n                        // component two way data binding!! (exact same behavior as svelte:bind)\n                        let isRecursive = false;\n\n                        handle(ptr, (value) => {\n                            if (isRecursive) {\n                                isRecursive = false;\n                                return\n                            }\n                            isRecursive = true;\n                            newthis[propname] = value;\n                        });\n                        handle(use(newthis[propname]), (value) => {\n                            if (isRecursive) {\n                                isRecursive = false;\n                                return\n                            }\n                            isRecursive = true;\n                            set(value);\n                        });\n                    }\n                    delete props[name];\n                }\n            }\n            Object.assign(newthis, props);\n\n            newthis.children = [];\n            for (let child of children) {\n                JSXAddChild(child, newthis.children.push.bind(newthis.children));\n            }\n\n            let elm = type.apply(newthis);\n            assert(\n                !(elm instanceof Array),\n                'Functional component cannot return a Fragment'\n            );\n            assert(elm instanceof Node, 'Functional component must return a Node');\n            assert(\n                !('$' in elm),\n                'Functional component cannot have another functional component at root level'\n            ); // reasoning: it would overwrite data-component and make a mess of the css\n            elm.$ = newthis;\n            newthis.root = elm;\n            /* FEATURE.CSS.START */\n            let cl = elm.classList;\n            let css = newthis.css;\n            let sanitizedName = type.name.replaceAll('$', '-');\n            if (css) {\n                cl.add(genCss(`${sanitizedName}-${genuid()}`, css, true));\n            }\n\n            // for ui toolkits, sometimes it's desirable to let outside css leak into the component. the caller has the responsibility of making sure outside css won't break the styles\n            if (!newthis._leak) {\n                cl.add(cssBoundary);\n            }\n            /* FEATURE.CSS.END */\n            elm.setAttribute('data-component', type.name);\n            if (typeof newthis.mount === 'function') newthis.mount();\n            return elm\n        }\n\n        let xmlns = props?.xmlns;\n        let elm = xmlns ? doc.createElementNS(xmlns, type) : doc.createElement(type);\n\n        for (let child of children) {\n            let bappend = elm.append.bind(elm);\n            JSXAddChild(child, bappend);\n        }\n\n        if (!props) return elm\n\n        let useProp = (name, callback) => {\n            if (!(name in props)) return\n            let prop = props[name];\n            callback(prop);\n            delete props[name];\n        };\n\n        useProp('class', (classlist) => {\n            assert(\n                typeof classlist === 'string' ||\n                    classlist instanceof Array ||\n                    isDLPtr(classlist),\n                'class must be a string or ar ray (r pointer)'\n            );\n            if (typeof classlist === 'string') {\n                elm.setAttribute('class', classlist);\n                return\n            }\n\n            /// this will be cleaned up once class arrays are removed\n            if (isDLPtr(classlist)) {\n                let oldvalue = '';\n                handle(classlist, (classname) => {\n                    for (let name of oldvalue.split(' ')) {\n                        if (name) elm.classList.remove(name);\n                    }\n                    if (typeof classname === 'string') {\n                        for (let name of classname.split(' ')) {\n                            if (name) elm.classList.add(name);\n                        }\n                        oldvalue = classname;\n                    }\n                });\n                return\n            }\n\n            /* DEV.START */\n            if (!window.dlwarnedclassarrays) {\n                console.error(\n                    \"WARN: class arrays (eg, <div class={['container', 'flex']} />) are deprecated and will be REMOVED in the next release\"\n                );\n                window.dlwarnedclassarrays = true;\n            }\n            /* DEV.END */\n\n            for (let name of classlist) {\n                if (isDLPtr(name)) {\n                    let oldvalue = null;\n                    handle(name, (value) => {\n                        if (typeof oldvalue === 'string') {\n                            elm.classList.remove(oldvalue);\n                        }\n                        elm.classList.add(value);\n                        oldvalue = value;\n                    });\n                } else {\n                    elm.classList.add(name);\n                }\n            }\n        });\n\n        for (let name in props) {\n            let ptr = props[name];\n            if (name.startsWith('bind:')) {\n                assert(isDLPtr(ptr), 'bind: requires a reference pointer from use');\n                let propname = name.substring(5);\n\n                // create the function to set the value of the pointer\n                let set = curryset(ptr[PROXY]);\n                if (propname == 'this') {\n                    set(elm);\n                } else if (propname == 'value') {\n                    handle(ptr, (value) => (elm.value = value));\n                    elm.addEventListener('change', () => set(elm.value));\n                } else if (propname == 'checked') {\n                    handle(ptr, (value) => (elm.checked = value));\n                    elm.addEventListener('click', () => set(elm.checked));\n                }\n                delete props[name];\n            }\n\n            if (name.startsWith('class:')) {\n                let classname = name.substring(6);\n                if (isDLPtr(ptr)) {\n                    handle(ptr, (value) => {\n                        if (value) {\n                            elm.classList.add(classname);\n                        } else {\n                            elm.classList.remove(classname);\n                        }\n                    });\n                } else {\n                    if (ptr) {\n                        elm.classList.add(classname);\n                    }\n                }\n\n                delete props[name];\n            }\n\n            if (name == 'style' && isobj(ptr) && !isDLPtr(ptr)) {\n                for (let key in ptr) {\n                    let prop = ptr[key];\n                    if (isDLPtr(prop)) {\n                        handle(prop, (value) => (elm.style[key] = value));\n                    } else {\n                        elm.style[key] = prop;\n                    }\n                }\n                delete props[name];\n            }\n        }\n\n        // apply the non-reactive properties\n        for (let name in props) {\n            let prop = props[name];\n            if (isDLPtr(prop)) {\n                handle(prop, (val) => {\n                    JSXAddAttributes(elm, name, val);\n                });\n            } else {\n                JSXAddAttributes(elm, name, prop);\n            }\n        }\n\n        // hack to fix svgs\n        if (xmlns) elm.innerHTML = elm.innerHTML;\n\n        return elm\n    }\n\n    // glue for nested children\n    function JSXAddChild(child, cb) {\n        let childchild, elms, node;\n        if (isDLPtr(child)) {\n            JSXAddFixedWrapper(child, cb);\n        } else if (isobj(child) && IF in child) {\n            JSXAddFixedWrapper(child[IF], cb, child);\n        } else if (child instanceof Node) {\n            cb(child);\n            return [child]\n        } else if (child instanceof Array) {\n            elms = [];\n\n            for (childchild of child) {\n                elms = elms.concat(JSXAddChild(childchild, cb));\n            }\n            if (!elms[0]) elms = JSXAddChild('', cb);\n            return elms\n        } else {\n            // this is what makes it so that {null} won't render. the empty string would seem odd coming from other frameworks but it is for the best\n            if (child === null || child === undefined) child = '';\n\n            node = doc.createTextNode(child);\n            cb(node);\n            return [node]\n        }\n    }\n\n    // Where properties are assigned to elements, and where the *non-reactive* syntax sugar goes\n    function JSXAddAttributes(elm, name, prop) {\n        if (!prop && elm.hasAttribute(name)) elm.removeAttribute(name);\n        if (!prop) return\n\n        if (name.startsWith('on:')) {\n            assert(typeof prop === 'function', 'on: requires a function');\n            let names = name.substring(3);\n            for (let name of names.split('$')) {\n                elm.addEventListener(name, (...args) => {\n                    self.$el = elm;\n                    prop(...args);\n                });\n            }\n            return\n        }\n\n        elm.setAttribute(name, prop);\n    }\n\n    function html(strings, ...values) {\n        // normalize the strings array, it would otherwise give us an object\n        strings = [...strings];\n        let flattened = '';\n        let markers = {};\n        for (let i = 0; i < strings.length; i++) {\n            let string = strings[i];\n            let value = values[i];\n\n            // since self closing tags don't exist in regular html, look for the pattern <tag /> enclosing a function, and replace it with `<tag`\n            let match =\n                values[i] instanceof Function && /^ *\\/>/.exec(strings[i + 1]);\n            if (/< *$/.test(string) && match) {\n                strings[i + 1] = strings[i + 1].substr(\n                    match.index + match[0].length\n                );\n            }\n\n            flattened += string;\n            if (i < values.length) {\n                let dupe = Object.values(markers).findIndex((v) => v === value);\n                let marker;\n                if (dupe !== -1) {\n                    marker = Object.keys(markers)[dupe];\n                } else {\n                    marker = 'h' + genuid();\n                    markers[marker] = value;\n                }\n\n                flattened += marker;\n\n                // close the self closing tag\n                if (match) {\n                    flattened += `></${marker}>`;\n                }\n            }\n        }\n        let dom = new DOMParser().parseFromString(flattened, 'text/html');\n        assert(\n            dom.body.children.length == 1,\n            'html builder needs exactly one child'\n        );\n\n        function wraph(elm) {\n            let nodename = elm.nodeName.toLowerCase();\n            if (nodename === '#text') return elm.textContent\n            if (nodename in markers) nodename = markers[nodename];\n\n            let children = [...elm.childNodes].map(wraph);\n            for (let i = 0; i < children.length; i++) {\n                let text = children[i];\n                if (typeof text !== 'string') continue\n                for (const [marker, value] of Object.entries(markers)) {\n                    if (!text) break\n                    if (!text.includes(marker)) continue\n                    let before\n                    ;[before, text] = text.split(marker);\n                    children = [\n                        ...children.slice(0, i),\n                        before,\n                        value,\n                        text,\n                        ...children.slice(i + 1),\n                    ];\n                    i += 2;\n                }\n            }\n\n            let attributes = {};\n\n            if (!elm.attributes) return elm // passthrough comments\n            for (const attr of [...elm.attributes]) {\n                let val = attr.nodeValue;\n                if (val in markers) val = markers[val];\n                attributes[attr.name] = val;\n            }\n\n            return h(nodename, attributes, children)\n        }\n\n        return wraph(dom.body.children[0])\n    }\n\n    function $store(target, { ident, backing, autosave }) {\n        let read, write;\n        if (typeof backing === 'string') {\n            switch (backing) {\n                case 'localstorage':\n                    read = () => localStorage.getItem(ident);\n                    write = (ident, data) => {\n                        localStorage.setItem(ident, data);\n                    };\n                    break\n                default:\n                    assert('Unknown store type: ' + backing);\n            }\n        } else {\n    ({ read, write } = backing);\n        }\n\n        let save = () => {\n            console.info('[dreamland.js]: saving ' + ident);\n\n            // stack gets filled with \"pointers\" representing unique objects\n            // this is to avoid circular references\n\n            let serstack = {};\n            let vpointercount = 0;\n\n            let ser = (tgt) => {\n                let obj = {\n                    stateful: isStateful(tgt),\n                    values: {},\n                };\n                let i = vpointercount++;\n                serstack[i] = obj;\n\n                for (let key in tgt) {\n                    let value = tgt[key];\n\n                    if (isDLPtr(value)) continue // i don\"t think we should be serializing pointers?\n                    switch (typeof value) {\n                        case 'string':\n                        case 'number':\n                        case 'boolean':\n                        case 'undefined':\n                            // primitives, serialize as strings\n                            obj.values[key] = JSON.stringify(value);\n                            break\n\n                        case 'object':\n                            if (value instanceof Array) {\n                                obj.values[key] = value.map((v) => {\n                                    if (typeof v === 'object') {\n                                        return ser(v)\n                                    } else {\n                                        return JSON.stringify(v)\n                                    }\n                                });\n                                break\n                            } else if (value === null) {\n                                obj.values[key] = 'null';\n                            } else {\n                                assert(\n                                    value.__proto__ === Object.prototype,\n                                    'Only plain objects can be serialized in stores'\n                                );\n\n                                // if it's not a primitive, store it as a number acting as a pointer\n                                obj.values[key] = ser(value);\n                            }\n                            break\n\n                        case 'symbol':\n                        case 'function':\n                        case 'bigint':\n                            assert('Unsupported type: ' + typeof value);\n                            break\n                    }\n                }\n\n                return i\n            };\n            ser(target);\n\n            let string = JSON.stringify(serstack);\n            write(ident, string);\n        };\n\n        let autohook = (target, prop, value) => {\n            if (isStateful(value)) value[TARGET][STATEHOOK] = autohook;\n            save();\n        };\n\n        let destack = JSON.parse(read(ident));\n        if (destack) {\n            let objcache = {};\n\n            let de = (i) => {\n                if (objcache[i]) return objcache[i]\n                let obj = destack[i];\n                let tgt = {};\n                for (let key in obj.values) {\n                    let value = obj.values[key];\n                    if (typeof value === 'string') {\n                        // it's a primitive, easy deser\n                        tgt[key] = JSON.parse(value);\n                    } else {\n                        if (value instanceof Array) {\n                            tgt[key] = value.map((v) => {\n                                if (typeof v === 'string') {\n                                    return JSON.parse(v)\n                                } else {\n                                    return de(v)\n                                }\n                            });\n                        } else {\n                            tgt[key] = de(value);\n                        }\n                    }\n                }\n                if (obj.stateful && autosave == 'auto') tgt[STATEHOOK] = autohook;\n                let newobj = obj.stateful ? $state(tgt) : tgt;\n                objcache[i] = newobj;\n                return newobj\n            };\n\n            // \"0\" pointer is the root object\n            target = de(0);\n        }\n        switch (autosave) {\n            case 'beforeunload':\n                addEventListener('beforeunload', save);\n                break\n            case 'manual':\n                break\n            case 'auto':\n                target[STATEHOOK] = autohook;\n                break\n            default:\n                assert('Unknown autosave type: ' + autosave);\n        }\n\n        return $state(target)\n    }\n\n    window.DREAMLAND_SECRET_DEV_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {\n        ...CONSTS,\n        isDLPtrInternal,\n        handle,\n    };\n\n    log('Version: ' + DLVERSION);\n    console.warn(\n        'This is a DEVELOPER build of dreamland.js. It is not suitable for production use.'\n    );\n    console.info('Enabled features:', DLFEATURES.join(', '));\n    /* DEV.END */\n\n    exports.$if = $if;\n    exports.$state = $state;\n    exports.$store = $store;\n    exports.Fragment = Fragment;\n    exports.css = css;\n    exports.h = h;\n    exports.html = html;\n    exports.isDLPtr = isDLPtr;\n    exports.isStateful = isStateful;\n    exports.scope = scope;\n\n})(window)\n//# sourceMappingURL=dev.js.map\n"
  },
  {
    "path": "public/lib/dreamland/minimal.js",
    "content": "// dreamland.js, MIT license\nconst DLFEATURES = []; const DLVERSION = '0.0.24';\n!function(e){const[t,n,r,i,l,s,o]=Array.from(Array(7),Symbol);let f=document;const u=Symbol();let c=!1;Object.defineProperty(window,\"use\",{get:()=>(c=!0,(e,l,...s)=>{p(e)||b(e),c=!1;let o={get value(){return function(e){let l=e[r],s=l[i],o=e[t],f=l[n];for(let e of s)if(f=f[e],!h(f))break;for(let e of o)f=e(f);return f}(o)}};if(b(e)){let n=[...e[t]];l&&n.push(l),o[r]=e[r],o[t]=n}else o[r]=e,o[t]=l?[l]:[];return o})}),Object.defineProperty(window,\"useChange\",{get:()=>(c=!0,(e,t)=>{c=!1,e=e instanceof Array?e:[e];for(let n of e)p(n)||b(n),y(use(n),t)})});let a=new Map;function d(e){e[l]=[],e[n]=e;let s=Symbol.toPrimitive,o=new Proxy(e,{get(e,l,o){if(c){let f=Symbol(),u=new Proxy({[n]:e,[r]:o,[i]:[l],[s]:()=>f},{get:(e,l)=>[n,r,i,t,s].includes(l)?e[l]:(l=a.get(l)||l,e[i].push(l),u)});return a.set(f,u),u}return Reflect.get(e,l,o)},set(e,t,n){let r=Reflect.set(e,t,n);for(let r of e[l])r(e,t,n);return r}});return o}let h=e=>e instanceof Object;function p(e){return h(e)&&i in e}function b(e){return h(e)&&t in e}function y(e,s){b(e);let o,f=e[r],u=e[t],c=[];function a(){let e=f[n];for(o of c)if(e=e[o],!h(e))break;for(let t of u)e=t(e);s(e)}let d=(e,t)=>function r(i,s,o){if(s===c[t]&&e===i&&(a(),h(o))){let e=o[l];e&&!e.includes(r)&&e.push(d(o[n],t+1))}};for(let e in f[i]){let t=f[i][e];h(t)&&t[n]?y(t,(t=>{c[e]=t,a()})):c[e]=t}let p=d(f[n],0);f[n][l].push(p),p(f[n],c[0],f[n][c[0]])}function g(e,t,n){let r,i,l,s;y(e,(e=>{l=i?.[0],l&&(r=l.previousSibling||(s=l.parentNode)),i&&i.forEach((e=>e.remove())),i=v(n?e?n.then:n.otherwise:e,(e=>{r?(s?(r.prepend(e),s=null):r.after(e),r=e):t(e)}))}))}let m=e=>t=>{let n=e[r],l=e[i],s=0;for(;s<l.length-1;s++)if(n=n[l[s]],!h(n))return;n[l[s]]=t};function v(e,t){let n,r,i;if(b(e))g(e,t);else{if(!h(e)||!(s in e)){if(e instanceof Node)return t(e),[e];if(e instanceof Array){for(n of(r=[],e))r=r.concat(v(n,t));return r[0]||(r=v(\"\",t)),r}return null==e&&(e=\"\"),i=f.createTextNode(e),t(i),[i]}g(e[s],t,e)}}function L(e,t,n){if(!n&&e.hasAttribute(t)&&e.removeAttribute(t),n)if(t.startsWith(\"on:\")){let r=t.substring(3);for(let t of r.split(\"$\"))e.addEventListener(t,((...t)=>{self.$el=e,n(...t)}))}else e.setAttribute(t,n)}e.$if=function(e,t,n){return n??=f.createTextNode(\"\"),b(e)?{[s]:e,then:t,otherwise:n}:e?t:n},e.$state=d,e.Fragment=u,e.h=function(e,t,...n){if(e==u)return n;if(\"function\"==typeof e){let i=d(Object.create(e.prototype));for(let e in t){let n=t[e];if(e.startsWith(\"bind:\")){b(n);let l=m(n[r]),s=e.substring(5);if(\"this\"==s)l(i);else{let e=!1;y(n,(t=>{e?e=!1:(e=!0,i[s]=t)})),y(use(i[s]),(t=>{e?e=!1:(e=!0,l(t))}))}delete t[e]}}Object.assign(i,t),i.children=[];for(let e of n)v(e,i.children.push.bind(i.children));let l=e.apply(i);return l.$=i,i.root=l,l.setAttribute(\"data-component\",e.name),\"function\"==typeof i.mount&&i.mount(),l}let i=t?.xmlns,l=i?f.createElementNS(i,e):f.createElement(e);for(let e of n){v(e,l.append.bind(l))}if(!t)return l;((e,n)=>{if(!(e in t))return;n(t[e]),delete t[e]})(\"class\",(e=>{if(\"string\"==typeof e||e instanceof Array||b(e),\"string\"!=typeof e)if(b(e)){let t=\"\";y(e,(e=>{for(let e of t.split(\" \"))e&&l.classList.remove(e);if(\"string\"==typeof e){for(let t of e.split(\" \"))t&&l.classList.add(t);t=e}}))}else for(let t of e)if(b(t)){let e=null;y(t,(t=>{\"string\"==typeof e&&l.classList.remove(e),l.classList.add(t),e=t}))}else l.classList.add(t);else l.setAttribute(\"class\",e)}));for(let e in t){let n=t[e];if(e.startsWith(\"bind:\")){b(n);let i=e.substring(5),s=m(n[r]);\"this\"==i?s(l):\"value\"==i?(y(n,(e=>l.value=e)),l.addEventListener(\"change\",(()=>s(l.value)))):\"checked\"==i&&(y(n,(e=>l.checked=e)),l.addEventListener(\"click\",(()=>s(l.checked)))),delete t[e]}if(e.startsWith(\"class:\")){let r=e.substring(6);b(n)?y(n,(e=>{e?l.classList.add(r):l.classList.remove(r)})):n&&l.classList.add(r),delete t[e]}if(\"style\"==e&&h(n)&&!b(n)){for(let e in n){let t=n[e];b(t)?y(t,(t=>l.style[e]=t)):l.style[e]=t}delete t[e]}}for(let e in t){let n=t[e];b(n)?y(n,(t=>{L(l,e,t)})):L(l,e,n)}return i&&(l.innerHTML=l.innerHTML),l},e.isDLPtr=b,e.isStateful=function(e){return h(e)&&l in e}}(window)\n//# sourceMappingURL=minimal.js.map\n"
  },
  {
    "path": "public/lib/dreamland/ssr.js",
    "content": "var __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n  for (var name in all)\n    __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n  if (from && typeof from === \"object\" || typeof from === \"function\") {\n    for (let key of __getOwnPropNames(from))\n      if (!__hasOwnProp.call(to, key) && key !== except)\n        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n  }\n  return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/ssr/main.js\nvar main_exports = {};\n__export(main_exports, {\n  hSSR: () => hSSR,\n  renderToString: () => renderToString\n});\nmodule.exports = __toCommonJS(main_exports);\nvar import_jsdom = require(\"jsdom\");\nfunction renderToString(component, props, children) {\n  globalThis.h = hSSR;\n  globalThis.use = (p) => p;\n  return hSSR(component, props, children).outerHTML;\n}\nfunction hSSR(type, props, ...children) {\n  const { document, HTMLElement } = new import_jsdom.JSDOM().window;\n  if (typeof type == \"function\") {\n    const newthis = {};\n    for (let key in props) {\n      if (key.startsWith(\"bind:\")) {\n        const attr = key.slice(5);\n        newthis[attr] = props[key];\n        continue;\n      }\n      newthis[key] = props[key];\n    }\n    const elm = type.apply(newthis);\n    elm.setAttribute(\"data-component\", type.name);\n    elm.setAttribute(\"ssr-data-component\", type.name);\n    return elm;\n  }\n  let el = document.createElement(type);\n  for (let child of children) {\n    if (typeof child == \"object\" && child != null && \"remove\" in child) {\n      el.appendChild(child);\n    } else {\n      el.appendChild(document.createTextNode(child));\n    }\n  }\n  for (let key in props) {\n    let val = props[key];\n    if (key == \"class\") {\n      el.className = val;\n      continue;\n    }\n    if (key == \"style\" && typeof val == \"object\") {\n      for (let skey in val) {\n        el.style[skey] = val[skey];\n      }\n      continue;\n    }\n    if (key.startsWith(\"on:\")) {\n      continue;\n    }\n    if (key.startsWith(\"bind:\")) {\n      let attr = key.slice(5);\n      el.setAttribute(attr, val);\n    }\n    el.setAttribute(key, props[key]);\n  }\n  return el;\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n  hSSR,\n  renderToString\n});\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n\t\"name\": \"Terbium WebOS\",\n\t\"description\": \"The next generation of Terbium WebOS Built for developers, enthusiests, and consumers to last\",\n\t\"short_name\": \"Terbium\",\n\t\"start_url\": \"/?boot=true\",\n\t\"display\": \"standalone\",\n\t\"background_color\": \"#D16FFF\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"tb.svg\",\n\t\t\t\"sizes\": \"16x16 32x32 48x48 72x72 96x96 128x128 256x256 512x512\",\n\t\t\t\"type\": \"image/svg+xml\",\n\t\t\t\"purpose\": \"any maskable\"\n\t\t}\n\t],\n\t\"display_override\": [\"window-controls-overlay\", \"standalone\", \"minimal-ui\", \"browser\"]\n}\n"
  },
  {
    "path": "public/media_interactions.js",
    "content": "console.log(\"Media Interactions Injected\");\n\nfunction useSec(timeStr) {\n\tconst [minutes, seconds] = timeStr.split(\":\").map(Number);\n\treturn minutes * 60 + seconds;\n}\n\nfunction getBg(style) {\n\tconst match = /url\\(['\"]?([^'\"\\)]+)['\"]?\\)/.exec(style);\n\treturn match ? match[1] : null;\n}\n\nfunction runMp(config) {\n\tif (config.type === \"video\") {\n\t\twindow.parent.tb.mediaplayer.video({\n\t\t\tvideo_name: config.video_name,\n\t\t\tcreator: config.creator,\n\t\t\tbackground: config.background,\n\t\t\ttime: config.time,\n\t\t\tendtime: config.endtime,\n\t\t\tonBack: config.onBack,\n\t\t\tonPausePlay: config.onPausePlay,\n\t\t\tonNext: config.onNext,\n\t\t});\n\t} else {\n\t\twindow.parent.tb.mediaplayer.music({\n\t\t\ttrack_name: config.track_name,\n\t\t\tartist: config.artist,\n\t\t\tbackground: config.background,\n\t\t\ttime: config.time,\n\t\t\tendtime: config.endtime,\n\t\t\tonBack: config.onBack,\n\t\t\tonPausePlay: config.onPausePlay,\n\t\t\tonNext: config.onNext,\n\t\t});\n\t}\n}\n\nasync function newPlayer(elem, getConfig) {\n\telem?.addEventListener(\"click\", async () => {\n\t\tconst exists = await window.parent.tb.mediaplayer.isExisting();\n\t\tif (!exists) {\n\t\t\trunMp(getConfig());\n\t\t}\n\t});\n}\n\nsetInterval(() => {\n\t// YouTube Music\n\tconst ytmTab = document.querySelector(\".ytmusic-app\");\n\tif (ytmTab) {\n\t\tconst playButtons = [document.querySelector(\"ytmusic-play-button-renderer\"), document.querySelector(\"tp-yt-paper-icon-button#play-pause-button\")];\n\t\tconst nex = document.querySelector('tp-yt-paper-icon-button[title=\"Next\"]');\n\t\tconst bac = document.querySelector('tp-yt-paper-icon-button[title=\"Previous\"]');\n\t\tconst pic = document.querySelector(\".image.style-scope.ytmusic-player-bar\")?.src;\n\t\tconst aname = document.querySelector(\"yt-formatted-string.byline.style-scope\")?.innerHTML;\n\t\tconst cmf = document.querySelector(\".title.style-scope.ytmusic-player-bar\")?.innerHTML;\n\t\tconst timeText = document.querySelector(\"span.time-info.style-scope.ytmusic-player-bar\")?.textContent.trim();\n\t\tconst [currTimeStr, endTimeStr] = timeText?.split(\" / \") || [];\n\t\tconst config = () => ({\n\t\t\ttrack_name: cmf,\n\t\t\tartist: aname,\n\t\t\tbackground: pic,\n\t\t\ttime: useSec(currTimeStr),\n\t\t\tendtime: useSec(endTimeStr),\n\t\t\tonBack: () => bac?.click(),\n\t\t\tonPausePlay: () => playButtons[0]?.click(),\n\t\t\tonNext: () => {\n\t\t\t\tnex?.click();\n\t\t\t\twindow.parent.tb.mediaplayer.hide();\n\t\t\t},\n\t\t});\n\t\tplayButtons.forEach(btn => newPlayer(btn, config));\n\t}\n\n\t// SoundCloud\n\t[\".sc-button-play.playButton\", \".playControl.sc-ir\", \".sc-button-play playButton.sc-button.sc-button-xlarge\"].forEach(selector => {\n\t\tconst playBtn = document.querySelector(selector);\n\t\tif (!playBtn) return;\n\t\tnewPlayer(playBtn, () => {\n\t\t\tconst imgStyle = document.querySelector(\"span.sc-artwork\")?.getAttribute(\"style\") || \"\";\n\t\t\tconst img = getBg(imgStyle);\n\t\t\tconst aname = document.querySelector(\".playbackSoundBadge__lightLink\")?.title;\n\t\t\tconst sname = document.querySelector(\".playbackSoundBadge__titleLink\")?.title;\n\t\t\tconst curr = document.querySelector('.playbackTimeline__timePassed span[aria-hidden=\"true\"]')?.textContent;\n\t\t\tconst end = document.querySelector('.playbackTimeline__duration span[aria-hidden=\"true\"]')?.textContent;\n\t\t\tconst fw = document.querySelector(\".playControls__next\");\n\t\t\tconst bk = document.querySelector(\".playControls__prev\");\n\t\t\tconst seeker = document.querySelector(\".playbackTimeline__progressWrapper.sc-mx-1x\").ariaValueNow;\n\t\t\treturn {\n\t\t\t\ttrack_name: sname.length > 18 ? sname.slice(0, 18) + \"...\" : sname,\n\t\t\t\tartist: aname,\n\t\t\t\tbackground: img,\n\t\t\t\ttime: useSec(curr),\n\t\t\t\tendtime: useSec(end),\n\t\t\t\tonBack: () => bk?.click(),\n\t\t\t\tonPausePlay: () => playBtn?.click(),\n\t\t\t\tonNext: () => {\n\t\t\t\t\tfw?.click();\n\t\t\t\t\twindow.parent.tb.mediaplayer.hide();\n\t\t\t\t},\n\t\t\t\tonSeek: val => {\n\t\t\t\t\tseeker = val;\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t});\n\n\t// Spotify\n\tconst spotTab = document.querySelector(\".vnCew8qzJq3cVGlYFXRI\");\n\tif (spotTab) {\n\t\tnewPlayer(spotTab, () => {\n\t\t\tconst currTime = document.querySelector(\".playback-bar__progress-time-elapsed\")?.textContent;\n\t\t\tconst endTime = document.querySelector(\".kQqIrFPM5PjMWb5qUS56\")?.textContent;\n\t\t\tconst artist = document.querySelector('a[data-testid=\"context-item-info-artist\"]')?.textContent;\n\t\t\tconst track = document.querySelector('a[data-testid=\"context-item-link\"]')?.textContent;\n\t\t\tconst bgStyle = document.querySelector(\".mMx2LUixlnN_Fu45JpFB\")?.style.backgroundImage || \"\";\n\t\t\tconst bg = getBg(bgStyle);\n\t\t\tconst bk = document.querySelector(\".fn72ari9aEmKo4JcwteT\");\n\t\t\tconst fw = document.querySelector(\".mnipjT4SLDMgwiDCEnRC\");\n\t\t\treturn {\n\t\t\t\ttrack_name: track,\n\t\t\t\tartist: artist,\n\t\t\t\tbackground: bg,\n\t\t\t\ttime: useSec(currTime),\n\t\t\t\tendtime: useSec(endTime),\n\t\t\t\tonBack: () => bk?.click(),\n\t\t\t\tonPausePlay: () => spotTab?.click(),\n\t\t\t\tonNext: () => {\n\t\t\t\t\tfw?.click();\n\t\t\t\t\twindow.parent.tb.mediaplayer.hide();\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\t// YouTube vid\n\tconst audtab = document.querySelector(\".video-stream\");\n\tif (audtab) {\n\t\tconst setupYT = async () => {\n\t\t\tconst exists = await window.parent.tb.mediaplayer.isExisting();\n\t\t\tif (!exists) {\n\t\t\t\tconst fav = document.querySelector('link[rel=\"icon\"]')?.href;\n\t\t\t\tconst vidName = document.querySelector(\"yt-formatted-string.style-scope.ytd-watch-metadata\")?.innerHTML;\n\t\t\t\tconst creator = document.querySelector(\"a.yt-simple-endpoint.style-scope.yt-formatted-string\")?.innerHTML;\n\t\t\t\tconst duration = document.querySelector(\".ytp-time-duration\")?.innerHTML;\n\t\t\t\trunMp({\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tvideo_name: vidName,\n\t\t\t\t\tcreator,\n\t\t\t\t\tbackground: fav,\n\t\t\t\t\tendtime: useSec(duration),\n\t\t\t\t\tonPausePlay: () => document.querySelector(\".ytp-play-button\")?.click(),\n\t\t\t\t\tonNext: () => {\n\t\t\t\t\t\tdocument.querySelector(\".ytp-next-button\")?.click();\n\t\t\t\t\t\twindow.parent.tb.mediaplayer.hide();\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\taudtab.addEventListener(\"play\", setupYT);\n\t\taudtab.addEventListener(\"click\", setupYT);\n\t}\n\n\t// Snae\n\t[\".-q.-u.-Y.da.-m.ha.Qc\", \".-D.d.-K.-F.q.Bb\"].forEach(selector => {\n\t\tconst snaeRoot = document.querySelector(selector);\n\t\tif (!snaeRoot) return;\n\t\tconst playBtn = snaeRoot.querySelector(\".na.F.Q.-a.-Y.oa.a.m.pa.Va\");\n\t\tif (!playBtn) return;\n\t\tplayBtn.addEventListener(\"click\", async () => {\n\t\t\tconst exists = await window.parent.tb.mediaplayer.isExisting();\n\t\t\tif (exists) return;\n\t\t\tconst snameEl = snaeRoot.querySelector(\".I.T.-d\") || snaeRoot.querySelector(\".C.N.Y\");\n\t\t\tconst sname = snameEl?.innerHTML || \"Unknown Title\";\n\t\t\tconst bgStyle = snaeRoot.querySelector(\".fa.Na.Pa\")?.style.backgroundImage || snaeRoot.querySelector(\".fa.Na.Sc.Pa\")?.style.backgroundImage;\n\t\t\tconst backgroundMatch = bgStyle?.match(/url\\([\"']?(.*?)[\"']?\\)/);\n\t\t\tconst background = backgroundMatch ? backgroundMatch[1] : null;\n\t\t\tconst parentDiv = snaeRoot.querySelector(\".-J.-F.-S.da.zb\");\n\t\t\tconst timeEl = parentDiv?.querySelector(\".H.S.-c.fa.Ab:nth-of-type(1)\");\n\t\t\tconst currTM = timeEl ? useSec(timeEl.textContent.trim()) : null;\n\t\t\tconst etEl = parentDiv?.querySelector(\".H.S.-c.fa.Ab:nth-of-type(2)\");\n\t\t\tconst endTime = etEl ? useSec(etEl.textContent.trim()) : null;\n\t\t\tconst fw = snaeRoot.querySelector('button[title=\"Play previous track (SHIFT+P)\"]');\n\t\t\tconst bk = snaeRoot.querySelector('button[title=\"Play next track (SHIFT+N)\"]');\n\t\t\trunMp({\n\t\t\t\ttrack_name: sname,\n\t\t\t\tartist: \"Snae Player\",\n\t\t\t\tbackground: background,\n\t\t\t\ttime: currTM,\n\t\t\t\tendtime: endTime,\n\t\t\t\tonBack: () => bk?.click(),\n\t\t\t\tonPausePlay: () => playBtn?.click(),\n\t\t\t\tonNext: () => {\n\t\t\t\t\tfw?.click();\n\t\t\t\t\twindow.parent.tb.mediaplayer.hide();\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t});\n}, 1000);\n"
  },
  {
    "path": "public/robots.txt",
    "content": "User-agent: *\nAllow: /\n\nUser-agent: Googlebot\nAllow: /\n\nUser-agent: Bingbot\nAllow: /\n\nUser-agent: Slurp\nAllow: /\n\nUser-agent: DuckDuckBot\nAllow: /\n\nUser-agent: Baiduspider\nAllow: /\n\nUser-agent: YandexBot\nAllow: /\n\nSitemap: https://terbiumon.top/sitemap.xml"
  },
  {
    "path": "public/sitemap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n    <url>\n        <loc>https://terbiumon.top/</loc>\n        <lastmod>2026-02-10</lastmod>\n        <changefreq>weekly</changefreq>\n        <priority>1.0</priority>\n    </url>\n</urlset>"
  },
  {
    "path": "public/theme.css",
    "content": "/* Liquor Comptability */\n\n:root {\n\t--material-bg: #202124;\n\t--material-border: #444;\n\t--matter-surface-rgb: 32, 33, 36;\n\t--matter-onsurface-rgb: 255, 255, 255;\n}\n\n.background {\n\tbackground-color: var(--material-bg);\n}\n\n.matter-switch {\n\tcolor: var(--theme-fg) !important;\n}\n\n.matter-button-contained:not(.settingsIcon, .symbolButton) {\n\tbackground-color: var(--theme-accent) !important;\n\t/* color: var(--theme-fg); */\n}\n\n.matter-button-outlined {\n\tcolor: var(--theme-accent) !important;\n}\n\n.matter-switch > input + span::before,\n.matter-switch > input + span::after {\n\tbackground-color: var(--theme-secondary-fg) !important;\n}\n\n.matter-switch > input {\n\tbackground-color: var(--theme-secondary-bg) !important;\n}\n\n.matter-switch > input:checked + span::before,\n.matter-switch > input:checked + span::after {\n\tbackground-color: var(--theme-accent) !important;\n}\n\n.matter-switch > input:checked {\n\tbackground-color: color-mix(in srgb, var(--theme-accent) 75%, black 25%) !important;\n}\n\n.matter-button-outlined {\n\tcolor: var(--theme-accent) !important;\n\tborder-color: var(--theme-border) !important;\n}\n\n.matter-button-outlined::before {\n\tbackground-color: color-mix(in srgb, var(--theme-accent) 75%, var(--theme-bg) 25%) !important;\n}\n\n.matter-button-text {\n\tcolor: var(--theme-accent) !important;\n}\n"
  },
  {
    "path": "public/uv/uv.config.js",
    "content": "/*global Ultraviolet*/\nself.__uv$config = {\n\tprefix: \"/uv/service/\",\n\tencodeUrl: Ultraviolet.codec.xor.encode,\n\tdecodeUrl: Ultraviolet.codec.xor.decode,\n\thandler: \"/uv/uv.handler.js\",\n\tclient: \"/uv/uv.client.js\",\n\tbundle: \"/uv/uv.bundle.js\",\n\tconfig: \"/uv/uv.config.js\",\n\tsw: \"/uv/uv.sw.js\",\n\tinject: [\n\t\t{\n\t\t\thost: /discord\\.com/,\n\t\t\tinjectTo: \"head\",\n\t\t\thtml: `\n                <script src=\"https://raw.githubusercontent.com/Vencord/builds/main/browser.js\"></script>\n                <link rel=\"stylesheet\" href=\"https://raw.githubusercontent.com/Vencord/builds/main/browser.css\">\n            `,\n\t\t},\n\t],\n};\n"
  },
  {
    "path": "server.ts",
    "content": "import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { Hono } from \"hono\";\nimport { serveStatic } from \"@hono/node-server/serve-static\";\nimport { cors } from \"hono/cors\";\nimport { getCookie, setCookie } from \"hono/cookie\";\nimport config from \"dotenv\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { version } from \"./package.json\";\n// @ts-expect-error no types\nimport { server as wisp } from \"@mercuryworkshop/wisp-js/server\";\n\nexport function TServer() {\n\tconfig.config();\n\tconst __filename = fileURLToPath(import.meta.url);\n\tconst __dirname = path.dirname(__filename);\n\n\tconsole.log(\"Starting Terbium...\");\n\tconst app = new Hono();\n\n\tconst port = Number.parseInt(process.env.PORT || \"8080\", 10);\n\n\tapp.use(\n\t\t\"*\",\n\t\tcors({\n\t\t\torigin: `http://localhost:${port}`,\n\t\t\tallowMethods: [\"GET\", \"HEAD\", \"PUT\", \"PATCH\", \"POST\", \"DELETE\"],\n\t\t\tcredentials: true,\n\t\t}),\n\t);\n\n\tconst masqrCheck = process.env.MASQR && process.env.MASQR.toLowerCase() === \"true\";\n\tif (masqrCheck) {\n\t\tconsole.log(\"Masqr is Enabled\");\n\t} else {\n\t\tconsole.log(\"Masqr is Disabled\");\n\t}\n\n\tasync function MasqFail(c: any) {\n\t\tconst host = c.req.header(\"host\");\n\t\tif (!host) {\n\t\t\treturn c.html(fs.readFileSync(\"fail.html\", \"utf8\"));\n\t\t}\n\t\tconst safeHost = host.split(\":\")[0].replace(/[^a-zA-Z0-9-_\\.]/g, \"\");\n\t\tconst safeFilename = path.basename(`${safeHost}.html`);\n\t\tconst safeJoin = path.join(process.cwd(), \"Masqrd\", safeFilename);\n\t\ttry {\n\t\t\tawait fs.promises.access(safeJoin);\n\t\t\tconst failureFileLocal = await fs.promises.readFile(safeJoin, \"utf8\");\n\t\t\treturn c.html(failureFileLocal);\n\t\t} catch {\n\t\t\treturn c.html(fs.readFileSync(\"fail.html\", \"utf8\"));\n\t\t}\n\t}\n\n\tif (masqrCheck) {\n\t\tconst whitelisted = (process.env.WHITELISTED_DOMAINS || \"\")\n\t\t\t.split(\",\")\n\t\t\t.map(s => s.trim())\n\t\t\t.filter(Boolean);\n\n\t\tapp.use(\"*\", async (c, next) => {\n\t\t\tconst host = c.req.header(\"host\") ?? \"\";\n\t\t\tif (host && whitelisted.includes(host)) {\n\t\t\t\tawait next();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (c.req.url.includes(\"/bare\")) {\n\t\t\t\tawait next();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (getCookie(c, \"authcheck\")) {\n\t\t\t\tawait next();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (getCookie(c, \"refreshcheck\") !== \"true\") {\n\t\t\t\tsetCookie(c, \"refreshcheck\", \"true\", { maxAge: 10000 });\n\t\t\t\treturn await MasqFail(c);\n\t\t\t}\n\t\t\tconst authheader = c.req.header(\"authorization\");\n\t\t\tif (!authheader) {\n\t\t\t\tc.header(\"WWW-Authenticate\", \"Basic\");\n\t\t\t\tc.status(401);\n\t\t\t\treturn await MasqFail(c);\n\t\t\t}\n\t\t\tconst token = authheader.split(\" \")[1] ?? \"\";\n\t\t\tlet user = \"\";\n\t\t\tlet pass = \"\";\n\t\t\ttry {\n\t\t\t\tconst decoded = Buffer.from(token, \"base64\").toString();\n\t\t\t\t[user, pass] = decoded.split(\":\");\n\t\t\t} catch {\n\t\t\t\treturn await MasqFail(c);\n\t\t\t}\n\t\t\tconst licenseResp = await fetch(`${process.env.LICENSE_SERVER_URL}${encodeURIComponent(pass)}&host=${encodeURIComponent(host)}`);\n\t\t\tconst licenseCheck = (await licenseResp.json())?.status;\n\t\t\tconsole.log(`\\x1b[0m${process.env.LICENSE_SERVER_URL}${pass}&host=${host} returned: ${licenseCheck}`);\n\t\t\tif (licenseCheck === \"License valid\") {\n\t\t\t\tsetCookie(c, \"authcheck\", \"true\", {\n\t\t\t\t\texpires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),\n\t\t\t\t});\n\t\t\t\treturn c.redirect(\"/\");\n\t\t\t}\n\t\t\treturn await MasqFail(c);\n\t\t});\n\t}\n\n\tapp.use(\n\t\t\"*\",\n\t\tserveStatic({\n\t\t\troot: path.join(__dirname, \"dist\"),\n\t\t}),\n\t);\n\twisp.options.dns_method = \"resolve\";\n\twisp.options.dns_servers = [\"1.1.1.3\", \"1.0.0.3\"];\n\twisp.options.dns_result_order = \"ipv4first\";\n\tconst server = createServer(nodeHandler);\n\n\tserver.on(\"upgrade\", (req: IncomingMessage, socket: any, head: Buffer) => {\n\t\tif (req.url?.endsWith(\"/wisp/\")) {\n\t\t\twisp.routeRequest(req as any, socket as any, head as any);\n\t\t} else {\n\t\t\tsocket.destroy();\n\t\t}\n\t});\n\n\tserver.listen(port, () => {\n\t\tconsole.log(`\n  \\x1b[38;2;50;174;98m@@@@@@@@@@@@@@~ B@@@@@@@@#G?.\n  \\x1b[38;2;50;174;98mB###&@@@@&####^ #@@@&PPPB@@@G.\n  \\x1b[38;2;50;174;98m .. ~@@@@J ..  .#@@@P   ~&@@@^      \\x1b[38;2;60;195;240mWelcome to Terbium React v${version}\n      \\x1b[38;2;50;174;98m^@@@@?     .#@@@@###&@@&7\n      \\x1b[38;2;50;174;98m^@@@@?     .#@@@#555P&@@B7      \\x1b[38;2;182;182;182mTerbium is running on ${port}\n      \\x1b[38;2;50;174;98m^@@@@?     .#@@@P    G@@@@      \\x1b[38;2;182;182;182mAny problems you encounter let us know!\n      \\x1b[38;2;50;174;98m^@@@@?     .#@@@&GGG#@@@@Y\n      \\x1b[38;2;50;174;98m^&@@@?      B@@@@@@@@&B5~\n    `);\n\t});\n\tasync function nodeHandler(req: IncomingMessage, res: ServerResponse) {\n\t\tconst proto = (req.socket as any).encrypted ? \"https\" : \"http\";\n\t\tconst host = req.headers.host || \"localhost\";\n\t\tconst url = new URL(req.url || \"/\", `${proto}://${host}`);\n\t\tconst request = new Request(url.toString(), {\n\t\t\tmethod: req.method,\n\t\t\theaders: req.headers as any,\n\t\t\tbody: req.method === \"GET\" || req.method === \"HEAD\" ? undefined : (req as any),\n\t\t\tduplex: \"half\",\n\t\t} as any);\n\n\t\ttry {\n\t\t\tconst response = await app.fetch(request);\n\t\t\tres.statusCode = response.status;\n\t\t\tresponse.headers.forEach((val, key) => {\n\t\t\t\tif (key.toLowerCase() === \"set-cookie\") {\n\t\t\t\t\tconst prev = res.getHeader(\"set-cookie\");\n\t\t\t\t\tif (prev) {\n\t\t\t\t\t\tconst arr = Array.isArray(prev) ? prev.concat(val) : [String(prev), val];\n\t\t\t\t\t\tres.setHeader(\"set-cookie\", arr);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tres.setHeader(\"set-cookie\", val);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tres.setHeader(key, val);\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (response.body) {\n\t\t\t\tconst reader = response.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tif (value) res.write(Buffer.from(value));\n\t\t\t\t}\n\t\t\t}\n\t\t\tres.end();\n\t\t} catch (err) {\n\t\t\tconsole.error(err);\n\t\t\tres.statusCode = 500;\n\t\t\tres.end(\"Internal Server Error\");\n\t\t}\n\t}\n\n\tprocess.on(\"SIGINT\", () => {\n\t\tconsole.log(\"\\x1b[0m\");\n\t\tprocess.exit();\n\t});\n}\n"
  },
  {
    "path": "src/App.tsx",
    "content": "import Api from \"./sys/Api\";\nimport Desktop from \"./sys/gui/Desktop\";\n\nfunction App() {\n\tApi();\n\treturn (\n\t\t<>\n\t\t\t<Desktop\n\t\t\t\tdesktop={1}\n\t\t\t\tonContextMenu={(e: MouseEvent) => {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nexport default App;\n"
  },
  {
    "path": "src/Boot.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport { version } from \"../package.json\";\nimport { dirExists, fileExists } from \"./sys/types\";\n\nexport default function Boot() {\n\tconst [selected, setSelected] = useState(0);\n\tconst [showCursor, setShowCursor] = useState(false);\n\tconst [bootentries, setentries] = useState<{ name: string; action: void | any }[]>([]);\n\n\tconst boot = () => {\n\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\twindow.location.reload();\n\t};\n\n\tconst cloak = () => {\n\t\tconst newWindow = window.open(\"about:blank\", \"_blank\");\n\t\tconst newDocument = newWindow!.document.open();\n\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\tnewDocument.write(`\n            <!DOCTYPE html>\n            <html>\n            <head>\n                <style type=\"text/css\">\n                    body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; }\n                </style>\n            </head>\n            <body>\n                <iframe style=\"border: none; width: 100%; height: 100vh;\" src=\"${window.location.href}?boot=true\"></iframe>\n            </body>\n            </html>\n        `);\n\t\tnewDocument.close();\n\t\twindow.location.href = \"https://google.com\";\n\t\tconsole.log(\"Cloak Opened!\");\n\t};\n\n\tconst recovery = () => {\n\t\tsessionStorage.setItem(\"recovery\", \"true\");\n\t\twindow.location.reload();\n\t};\n\n\tuseEffect(() => {\n\t\tconst getEntries = async () => {\n\t\t\tlet entries = [];\n\t\t\tif (!(await fileExists(\"/bootentries.json\"))) {\n\t\t\t\tconst ent = [\n\t\t\t\t\t{ name: \"TB React\", action: boot.toString() },\n\t\t\t\t\t{ name: \"TB React (Cloaked)\", action: cloak.toString() },\n\t\t\t\t\t{ name: \"TB System Recovery\", action: recovery.toString() },\n\t\t\t\t];\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/bootentries.json\", JSON.stringify(ent));\n\t\t\t\tconsole.log(\"Added default bootentries\");\n\t\t\t\tentries = ent;\n\t\t\t} else {\n\t\t\t\tentries = JSON.parse(await window.tb.fs.promises.readFile(\"/bootentries.json\", \"utf8\"));\n\t\t\t}\n\n\t\t\tconst FilerDirExists = async (path: string): Promise<boolean> => {\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tFiler.fs.stat(path, (err: any, stats: any) => {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\tif (err.code === \"ENOENT\") {\n\t\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst exists = stats.type === \"DIRECTORY\";\n\t\t\t\t\t\t\tresolve(exists);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t};\n\n\t\t\t// @ts-expect-error\n\t\t\tconst recreatedEntries = entries.map(entry => ({\n\t\t\t\t...entry,\n\t\t\t\taction: eval(`(${entry.action})`),\n\t\t\t}));\n\t\t\tif (localStorage.getItem(\"setup\") === \"true\" && (!(await dirExists(\"/system/etc/terbium/\")) || !(await dirExists(\"/apps/system/\")))) {\n\t\t\t\tconst bootent = recreatedEntries.filter((entry: any) => entry.name !== \"TB React\" && entry.name !== \"TB React (Cloaked)\");\n\t\t\t\tconst fsxr = await FilerDirExists(\"/system/etc/terbium\");\n\t\t\t\tif (fsxr) {\n\t\t\t\t\tsessionStorage.setItem(\"migrateFs\", \"true\");\n\t\t\t\t\tsetentries(recreatedEntries);\n\t\t\t\t} else {\n\t\t\t\t\tsetentries(bootent);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsetentries(recreatedEntries);\n\t\t\t}\n\t\t};\n\t\tgetEntries();\n\t\tconst getPlatform = () => {\n\t\t\tconst mobileuas =\n\t\t\t\t/(android|bb\\d+|meego).+mobile|armv7l|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series[46]0|samsungbrowser.*mobile|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk|iPhone|iPad/i;\n\t\t\tconst crosua = /CrOS/;\n\t\t\tif (mobileuas.test(navigator.userAgent) && !crosua.test(navigator.userAgent)) {\n\t\t\t\treturn \"mobile\";\n\t\t\t} else if (!mobileuas.test(navigator.userAgent) && navigator.maxTouchPoints > 1 && navigator.userAgent.indexOf(\"Macintosh\") !== -1 && navigator.userAgent.indexOf(\"Safari\") !== -1) {\n\t\t\t\treturn \"mobile\";\n\t\t\t} else {\n\t\t\t\treturn \"desktop\";\n\t\t\t}\n\t\t};\n\t\tif (getPlatform() === \"mobile\") {\n\t\t\tsetShowCursor(true);\n\t\t}\n\t\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"ArrowUp\") {\n\t\t\t\tsetSelected(prevSelected => (bootentries.length === 0 ? 0 : prevSelected === 0 ? bootentries.length - 1 : prevSelected - 1));\n\t\t\t} else if (e.key === \"ArrowDown\") {\n\t\t\t\tsetSelected(prevSelected => (prevSelected === bootentries.length - 1 ? 0 : prevSelected + 1));\n\t\t\t} else if (e.key === \"Enter\") {\n\t\t\t\tconst selectedEntry = bootentries[selected];\n\t\t\t\tselectedEntry.action();\n\t\t\t} else if (e.key === \"Escape\") {\n\t\t\t\tsetShowCursor(prev => !prev);\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"keydown\", handleKeyDown);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"keydown\", handleKeyDown);\n\t\t};\n\t}, [selected, bootentries]);\n\n\treturn (\n\t\t<div className={`overflow-hidden w-full h-full flex justify-center pt-[30px] bg-[#0e0e0e] ${showCursor ? null : \"cursor-none\"}`}>\n\t\t\t<div className=\"flex flex-col items-center w-full p-2 text-[#ffffff48] overflow-hidden\">\n\t\t\t\t<div className=\"py-10 w-full flex justify-center text-[#ffffff68] font-bold text-2xl duration-150\">Terbium Boot Loader - Version {version}</div>\n\t\t\t\t<div className=\"mt-1 p-2 flex flex-col grow overflow-auto w-full border-solid border-[#ffffff68] border-2 rounded-xl\">\n\t\t\t\t\t{bootentries.map((entry, index) => (\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\tclassName={`p-2 px-2.5 text-sm font-extrabold lg:text-lg md:text-base border-[1px] rounded-md ${selected === index && showCursor !== true ? \"bg-[#ffffff18] border-[#ffffff20]\" : \"border-transparent\"} ${showCursor ? \"hover:bg-[#ffffff18] hover:border-[#ffffff20]\" : null}`}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tshowCursor ? entry.action() : null;\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{entry.name}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t\t<span className=\"font-mono\">\n\t\t\t\t\tUse the <span className=\"text-[#ffffff68] text-2xl\">↑</span> and <span className=\"text-[#ffffff68] text-2xl\">↓</span> keys to switch entry.\n\t\t\t\t</span>\n\t\t\t\t<span className=\"font-mono\">\n\t\t\t\t\tPress the <span className=\"text-[#ffffff68] font-sans font-bold\">enter</span> key to boot into the selection.\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/CustomOS.tsx",
    "content": "import { useEffect, useState } from \"react\";\n\nexport default function CustomOS() {\n\tconst [loaded, setloaded] = useState(false);\n\n\tuseEffect(() => {\n\t\tconst rep = (content: string) => {\n\t\t\tconst parser = new DOMParser();\n\t\t\tconst doc = parser.parseFromString(content, \"text/html\");\n\t\t\tconsole.log(`Terbium Bootloader v2.3.0 is now loading: ${sessionStorage.getItem(\"bootfile\")}`);\n\t\t\tif (doc.body && doc.head) {\n\t\t\t\tconst b = document.createElement(\"base\");\n\t\t\t\tb.href = `/fs/${sessionStorage.getItem(\"bootfile\")!.replace(/\\/?[^\\/]+\\.html$/, \"\")}/`;\n\t\t\t\tdoc.head.insertBefore(b, doc.head.firstChild);\n\t\t\t\tdocument.body.innerHTML = doc.body.innerHTML;\n\t\t\t\tdocument.head.innerHTML = doc.head.innerHTML;\n\t\t\t\tconst htmlAttrs = doc.documentElement.attributes;\n\t\t\t\tfor (const attr of Array.from(htmlAttrs)) {\n\t\t\t\t\tdocument.documentElement.setAttribute(attr.name, attr.value);\n\t\t\t\t}\n\t\t\t\tconst headAttrs = doc.head.attributes;\n\t\t\t\tfor (const attr of Array.from(headAttrs)) {\n\t\t\t\t\tdocument.head.setAttribute(attr.name, attr.value);\n\t\t\t\t}\n\t\t\t\tconst bodyAttrs = doc.body.attributes;\n\t\t\t\tfor (const attr of Array.from(bodyAttrs)) {\n\t\t\t\t\tdocument.body.setAttribute(attr.name, attr.value);\n\t\t\t\t}\n\t\t\t\tconst scripts = document.querySelectorAll(\"script\");\n\t\t\t\tfor (const script of scripts) {\n\t\t\t\t\tconst newScript = document.createElement(\"script\");\n\t\t\t\t\tfor (const attr of script.attributes) {\n\t\t\t\t\t\tif (attr.name !== \"src\") {\n\t\t\t\t\t\t\tnewScript.setAttribute(attr.name, attr.value);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (script.src) {\n\t\t\t\t\t\tlet newSrc = script.src;\n\t\t\t\t\t\tif (script.src.includes(\"http\")) {\n\t\t\t\t\t\t\tnewSrc = script.src;\n\t\t\t\t\t\t} else if (!script.src.includes(`${window.location.origin}/fs/`)) {\n\t\t\t\t\t\t\tnewSrc = `/fs/${sessionStorage.getItem(\"bootfile\")!.replace(/\\/?[^\\/]+\\.html$/, \"\")}${script.src.replace(window.location.origin, \"\")}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnewScript.src = newSrc;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewScript.textContent = script.textContent;\n\t\t\t\t\t}\n\t\t\t\t\tdocument.head.appendChild(newScript);\n\t\t\t\t\tscript.parentNode?.removeChild(script);\n\t\t\t\t}\n\t\t\t\tconst styles: any = document.querySelectorAll(\"link[rel='stylesheet']\");\n\t\t\t\tfor (const style of styles) {\n\t\t\t\t\tconst newStyle = document.createElement(\"link\");\n\t\t\t\t\tfor (const attr of style.attributes) {\n\t\t\t\t\t\tif (attr.name !== \"href\") {\n\t\t\t\t\t\t\tnewStyle.setAttribute(attr.name, attr.value);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (style.href) {\n\t\t\t\t\t\tlet newHref = style.href;\n\t\t\t\t\t\tif (style.href.includes(\"http\")) {\n\t\t\t\t\t\t\tnewHref = style.href;\n\t\t\t\t\t\t} else if (!style.href.includes(`${window.location.origin}/fs/`)) {\n\t\t\t\t\t\t\tnewHref = `/fs/${sessionStorage.getItem(\"bootfile\")!.replace(/\\/?[^\\/]+\\.html$/, \"\")}${style.href.replace(window.location.origin, \"\")}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnewStyle.href = newHref;\n\t\t\t\t\t}\n\t\t\t\t\tdocument.head.appendChild(newStyle);\n\t\t\t\t\tstyle.parentNode?.removeChild(style);\n\t\t\t\t}\n\t\t\t\tsetloaded(true);\n\t\t\t} else {\n\t\t\t\tconsole.error(`Failed to boot: ${sessionStorage.getItem(\"bootfile\")}`);\n\t\t\t\tsessionStorage.clear();\n\t\t\t\twindow.location.reload();\n\t\t\t}\n\t\t};\n\n\t\twindow.tb.fs.promises\n\t\t\t.readFile(sessionStorage.getItem(\"bootfile\")!, \"utf8\")\n\t\t\t.then(data => {\n\t\t\t\trep(data);\n\t\t\t})\n\t\t\t.catch(err => {\n\t\t\t\tconsole.error(`Failed to read bootfile because of: ${err}`);\n\t\t\t\tsessionStorage.clear();\n\t\t\t\twindow.location.reload();\n\t\t\t});\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst back = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"Escape\") {\n\t\t\t\tsessionStorage.clear();\n\t\t\t\twindow.location.reload();\n\t\t\t}\n\t\t};\n\n\t\tif (loaded) {\n\t\t\twindow.removeEventListener(\"keydown\", back);\n\t\t} else {\n\t\t\twindow.addEventListener(\"keydown\", back);\n\t\t}\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"keydown\", back);\n\t\t};\n\t}, [loaded]);\n\n\treturn (\n\t\t<div className=\"bg-[#0e0e0e] h-full justify-center items-center flex flex-col lg:h-full md:h-full\">\n\t\t\t<img src=\"/tb.svg\" alt=\"Terbium\" className=\"w-[25%] h-[25%]\" />\n\t\t\t<div className=\"duration-150 flex flex-col justify-center items-center\">\n\t\t\t\t<div className=\"text-container relative flex flex-col justify-center items-end\">\n\t\t\t\t\t<div className=\"bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text flex flex-col lg:items-center md:items-center sm:items-center\">\n\t\t\t\t\t\t<span className=\"font-[700] lg:text-[34px] md:text-[28px] sm:text-[22px] text-right duration-150\">\n\t\t\t\t\t\t\t<span className=\"font-[1000] duration-150\">Terbium Bootloader</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\t<p>Press ESC to return to boot menu</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/Loading.tsx",
    "content": "import \"./sys/gui/styles/loader.css\";\n\nexport default function Loader() {\n\treturn (\n\t\t<div className=\"bg-[#0e0e0e] h-full justify-center items-center flex flex-col lg:h-full md:h-full\">\n\t\t\t<img src=\"/tb.svg\" alt=\"Terbium\" className=\"w-[25%] h-[25%] breathe\" />\n\t\t\t<div className=\"duration-150 flex flex-col justify-center items-center\">\n\t\t\t\t<div className=\"text-container relative flex flex-col justify-center items-end\">\n\t\t\t\t\t<div className=\"bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text flex flex-col lg:items-center md:items-center sm:items-center\">\n\t\t\t\t\t\t<span className=\"font-[700] lg:text-[34px] md:text-[28px] sm:text-[22px] text-right duration-150\">\n\t\t\t\t\t\t\t<span className=\"font-[1000] duration-150\">TerbiumOS</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/Login.tsx",
    "content": "import { useEffect, useState, useRef } from \"react\";\nimport \"./sys/gui/styles/login.css\";\nimport { GetTime, GetDate } from \"./sys/apis/Date\";\nimport pwd from \"./sys/apis/Crypto\";\nimport DialogContainer, { setDialogFn } from \"./sys/apis/Dialogs\";\nimport { User } from \"./sys/types\";\nconst pw = new pwd();\n\nexport default function Login() {\n\tconst [isLoggingIn, setIsLoggingIn] = useState(false);\n\n\tconst [time, setTime] = useState(GetTime());\n\tconst [date, setDate] = useState(GetDate());\n\tconst [hasPw, setHasPw] = useState(false);\n\tconst [accounts, setAccounts] = useState([]);\n\tconst [selectedUser, setSelectedUser] = useState<string | any>(sessionStorage.getItem(\"currAcc\") || \"/home/user/\");\n\tconst [profilePictures, setProfilePictures] = useState<{ [key: string]: string | null }>({});\n\tconst [wallpaper, setWallpaper] = useState<string | null>(null);\n\tconst [changingpw, setChangepw] = useState(false);\n\tconst passwordRef = useRef<HTMLInputElement>(null);\n\tuseEffect(() => {\n\t\tconst intervalId = setInterval(() => {\n\t\t\tsetTime(GetTime());\n\t\t\tsetDate(GetDate());\n\t\t}, 1000);\n\t\treturn () => clearInterval(intervalId);\n\t}, []);\n\tuseEffect(() => {\n\t\tconst FS = async () => {\n\t\t\tconst entries = await window.tb.fs.promises.readdir(\"/home/\");\n\t\t\tconst dirEntries = await Promise.all(\n\t\t\t\tentries.map(async entry => {\n\t\t\t\t\tconst stat = await window.tb.fs.promises.stat(`/home/${entry}`);\n\t\t\t\t\tif (stat && stat.isDirectory()) {\n\t\t\t\t\t\tconst exists = await window.tb.fs.promises.exists(`/home/${entry}/user.json`);\n\t\t\t\t\t\tif (exists) {\n\t\t\t\t\t\t\treturn entry;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst directories = dirEntries.filter(entry => entry !== null);\n\t\t\t// @ts-expect-error\n\t\t\tsetAccounts(directories);\n\t\t};\n\t\tFS();\n\t}, []);\n\tuseEffect(() => {\n\t\tconst getDefUsr = async () => {\n\t\t\tconst data = await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\");\n\t\t\tconst res = JSON.parse(data);\n\t\t\tsetWallpaper(JSON.parse(await window.tb.fs.promises.readFile(`/home/${res.defaultUser}/settings.json`)).wallpaper);\n\t\t\tsetSelectedUser(res.defaultUser);\n\t\t};\n\t\tgetDefUsr();\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst getUsr = async () => {\n\t\t\tconst pictures: { [key: string]: string | null } = {};\n\t\t\tfor (const account of accounts) {\n\t\t\t\ttry {\n\t\t\t\t\tconst res = JSON.parse(await window.tb.fs.promises.readFile(`/home/${account}/user.json`, \"utf8\"));\n\t\t\t\t\tpictures[account] = res.pfp || null;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`Error reading user data for ${account}:`, error);\n\t\t\t\t\tpictures[account] = null;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (selectedUser && selectedUser !== \"/home/user/\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst res = JSON.parse(await window.tb.fs.promises.readFile(`/home/${selectedUser}/user.json`, \"utf8\"));\n\t\t\t\t\tsetHasPw(res.password !== false);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Error reading user data:\", error);\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetProfilePictures(pictures);\n\t\t};\n\t\tif (accounts.length > 0) {\n\t\t\tgetUsr();\n\t\t}\n\t}, [accounts, selectedUser]);\n\tconst login = async () => {\n\t\t// @ts-expect-error\n\t\tconst passVal = passwordRef.current.value;\n\t\tif (passVal !== \"\") {\n\t\t\tconst data = await window.tb.fs.promises.readFile(`/home/${selectedUser}/user.json`, \"utf8\");\n\t\t\tconst res = JSON.parse(data);\n\t\t\tconst user_pass = res.password.toString();\n\t\t\tconst pass = pw.harden(passVal.toString());\n\t\t\tif (user_pass === pass) {\n\t\t\t\tsessionStorage.setItem(\"logged-in\", \"true\");\n\t\t\t\tsessionStorage.setItem(\"currAcc\", selectedUser);\n\t\t\t\twindow.location.reload();\n\t\t\t} else {\n\t\t\t\tErr();\n\t\t\t}\n\t\t}\n\t};\n\tconst Err = () => {\n\t\tif (passwordRef.current) {\n\t\t\tpasswordRef.current.classList.add(\"ring-[#ff7e7e5a]\", \"ring-[2px]\", \"border-[#ff7e7ed5]\", \"placeholder-[#ff7e7e6b]\");\n\t\t\tpasswordRef.current.value = \"\";\n\t\t\tpasswordRef.current.placeholder = \"Incorrect Password\";\n\t\t\tpasswordRef.current.addEventListener(\"keydown\", () => {\n\t\t\t\tif (passwordRef.current) {\n\t\t\t\t\tpasswordRef.current.classList.remove(\"ring-[#ff7e7e5a]\", \"ring-[2px]\", \"border-[#ff7e7ed5]\", \"placeholder-[#ff7e7e6b]\");\n\t\t\t\t\tpasswordRef.current.placeholder = \"Password\";\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tconst keyCheck = async (e: KeyboardEvent) => {\n\t\t\tswitch (e.key) {\n\t\t\t\tcase \"Enter\":\n\t\t\t\t\tif (isLoggingIn && !changingpw) {\n\t\t\t\t\t\tlogin();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"Escape\":\n\t\t\t\t\tif (isLoggingIn) {\n\t\t\t\t\t\tsetIsLoggingIn(false);\n\t\t\t\t\t} else {\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tsetIsLoggingIn(true);\n\t\t\t\t\tif (passwordRef.current && !changingpw) {\n\t\t\t\t\t\tpasswordRef.current.focus();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\t\twindow.addEventListener(\"keydown\", keyCheck);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"keydown\", keyCheck);\n\t\t};\n\t}, [isLoggingIn, selectedUser, changingpw]);\n\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tclassName=\"absolute inset-0\"\n\t\t\t\tstyle={{\n\t\t\t\t\tbackgroundImage: `url(\"${wallpaper?.includes(\"/system/etc/\") ? `/fs/${wallpaper}` : wallpaper || \"\"}\")`,\n\t\t\t\t\tbackgroundSize: \"cover\",\n\t\t\t\t\tbackgroundRepeat: \"no-repeat\",\n\t\t\t\t\tbackgroundPosition: \"center\",\n\t\t\t\t}}\n\t\t\t></div>\n\t\t\t<div\n\t\t\t\tclassName={`login_container relative flex flex-col justify-center items-center size-full gap-5`}\n\t\t\t\tonMouseDown={() => {\n\t\t\t\t\tif (!isLoggingIn) {\n\t\t\t\t\t\tconst user = JSON.parse(localStorage.getItem(\"setup\") || \"{}\");\n\t\t\t\t\t\tif (user.password !== false) {\n\t\t\t\t\t\t\tsetIsLoggingIn(true);\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\tif (passwordRef.current && !changingpw) {\n\t\t\t\t\t\t\t\t\tpasswordRef.current.focus();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}, 200);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{accounts.length > 1 ? (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={`\n                            absolute flex gap-4 top-5 right-5 items-center z-10 duration-150\n                            ${isLoggingIn ? \"opacity-100\" : \"opacity-0 pointer-events-none translate-y-7\"}\n                        `}\n\t\t\t\t\t>\n\t\t\t\t\t\t{accounts.map((account, i) => (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName={`\n                                        size-14 bg-[#00000028] rounded-full duration-150\n                                        ${selectedUser === account ? \"shadow-[inset_0_0_0_2px_#7e91ff] scale-[1.2]\" : \"hover:scale-[1.1]\"}\n                                    `}\n\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\tstyle={{ backgroundImage: `url(\"${profilePictures[account] || \"\"}\")`, backgroundSize: \"cover\", backgroundPosition: \"center\", backgroundRepeat: \"no-repeat\" }}\n\t\t\t\t\t\t\t\tonMouseDown={async () => {\n\t\t\t\t\t\t\t\t\tsetSelectedUser(account);\n\t\t\t\t\t\t\t\t\tsetWallpaper(JSON.parse(await window.tb.fs.promises.readFile(`/home/${account}/settings.json`)).wallpaper);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t></div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t) : null}\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n                    flex flex-col justify-center items-center gap-5 size-full duration-150\n                    ${isLoggingIn ? \"opacity-0 pointer-events-none\" : \"opacity-100\"}\n                `}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"date_time text-[#ffffffcb] font-extrabold flex flex-col justify-center items-center [text-shadow:0_0_16px_#00000038]\">\n\t\t\t\t\t\t<div className=\"time text-7xl\">{time}</div>\n\t\t\t\t\t\t<div className=\"date text-2xl\">{date}</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h1 className=\"text-[#ffffffcb] text-xl font-bold\">Press any key to login</h1>\n\t\t\t\t</div>\n\t\t\t\t<DialogContainer />\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n                    absolute\n                    flex flex-col justify-center items-center gap-5 size-full backdrop-blur-lg duration-150 ${wallpaper ? \"bg-[#0e0e0e99]\" : \"bg-[#0e0e0e]\"}\n                    ${isLoggingIn ? \"opacity-100\" : \"opacity-0 pointer-events-none\"}\n                `}\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={`\n                        flex flex-col justify-center items-center gap-5 size-full duration-150\n                        ${isLoggingIn ? \"\" : \"translate-y-6\"}\n                    `}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"date_time text-[#ffffff87] font-extrabold flex flex-col justify-center items-center\">\n\t\t\t\t\t\t\t<div className=\"time text-6xl\">{time}</div>\n\t\t\t\t\t\t\t<div className=\"date text-lg\">{date}</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"user flex flex-row gap-5\">\n\t\t\t\t\t\t\t<div className=\"user flex flex-col justify-center items-center gap-[10px]\">\n\t\t\t\t\t\t\t\t<div className=\"relative flex size-[120px]\">\n\t\t\t\t\t\t\t\t\t{accounts.length > 1 ? (\n\t\t\t\t\t\t\t\t\t\taccounts.map((account, i) => (\n\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                    absolute\n                                                    flex justify-center items-center border-[1px] border-[#ffffff10] rounded-full bg-[center] size-[120px] duration-150\n                                                    ${selectedUser === account ? \"\" : \"scale-[0.85] opacity-0\"}\n                                                `}\n\t\t\t\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\t\t\t\tstyle={{ backgroundImage: `url(\"${profilePictures[account] || \"\"}\")`, backgroundSize: \"102%\", backgroundRepeat: \"no-repeat\" }}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-xl font-bold\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttextShadow: \"0 0 16px #000000\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolor: \"#ffffff\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{account}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex justify-center items-center border-[1px] border-[#ffffff10] rounded-full bg-[center] w-[120px] h-[120px]\" style={{ backgroundImage: `url(\"${profilePictures[selectedUser] || \"\"}\")`, backgroundSize: \"102%\", backgroundRepeat: \"no-repeat\" }}>\n\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-xl font-bold\"\n\t\t\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\t\t\ttextShadow: \"0 0 16px #000000\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tcolor: \"#ffffff\",\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t{selectedUser}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div className=\"pass_container flex gap-[6px] justify-center items-center\">\n\t\t\t\t\t\t\t\t\t<div className=\"relative flex flex-row justify-center\">\n\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                            flex flex-col items-center gap-1.5 duration-150\n                                            ${hasPw ? \"opacity-100\" : \"opacity-0 pointer-events-none\"}\n                                        `}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex\">\n\t\t\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"pass cursor-[var(--cursor-text)] bg-[#ffffff10] border-[1px] border-[#ffffff10] rounded-[6px] px-[8px] py-[6px] text-[#ffffff] font-[18px] placeholder-[#ffffff38] placeholder-opacity-50 transition duration-150 ring-0 ring-[transparent] focus:outline-hidden focus:ring-[2.5px] focus:ring-[#7e91ff5a] focus:border-[#7e91ffd5] focus:placeholder-[#ffffff6b]\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tautoComplete=\"off\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"Password\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tref={passwordRef}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonKeyDown={e => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\") login();\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"pass_button ml-2 cursor-pointer bg-[#ffffff10] border-[1px] border-[#ffffff10] rounded-[6px] px-[8px] py-[6px] stroke-[#ffffff] stroke-width-[2px] text-[#ffffff] font-[18px] transition duration-150 ring-0 ring-[transparent] focus:outline-hidden focus:ring-[2.5px] focus:ring-[#7e91ff5a] focus:border-[#7e91ffd5]\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tonMouseDown={login}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<path fillRule=\"evenodd\" d=\"M3.75 12a.75.75 0 01.75-.75h13.19l-5.47-5.47a.75.75 0 011.06-1.06l6.75 6.75a.75.75 0 010 1.06l-6.75 6.75a.75.75 0 11-1.06-1.06l5.47-5.47H4.5a.75.75 0 01-.75-.75z\" clipRule=\"evenodd\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t{hasPw && (\n\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"forgot cursor-pointer text-[#ffffff38] text-[16px] font-[700] transition duration-150 hover:text-[#ffffff87] focus:outline-hidden focus:text-[#ffffff87]\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tonMouseDown={async () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst changepw = async () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetChangepw(true);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet settings: User = JSON.parse(await window.tb.fs.promises.readFile(`/home/${selectedUser}/user.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (settings.securityQuestion) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetDialogFn(\"message\", {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `${settings.securityQuestion.question}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: (val: string) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (pw.harden(val) === settings.securityQuestion!.answer) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetDialogFn(\"message\", {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `Enter a new Password for the account: ${selectedUser}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: (val: string) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsettings.password = pw.harden(val);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow.tb.fs.promises.writeFile(`/home/${selectedUser}/user.json`, JSON.stringify(settings, null, 4));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetChangepw(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"true\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"currAcc\", selectedUser);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetDialogFn(\"alert\", {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `Incorrect answer to the security question`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tchangepw();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetDialogFn(\"message\", {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `Enter a new Password for the account: ${selectedUser}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: (val: string) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsettings.password = pw.harden(val);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow.tb.fs.promises.writeFile(`/home/${selectedUser}/user.json`, JSON.stringify(settings, null, 4));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetChangepw(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"true\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"currAcc\", selectedUser);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tchangepw();\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\tForgot Password?\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                            absolute pass_button ml-2 cursor-pointer bg-[#ffffff10] border-[1px] border-[#ffffff10] rounded-[6px] px-[8px] py-[6px] stroke-[#ffffff] stroke-width-[2px] text-[#ffffff] font-[18px] transition duration-150 ring-0 ring-[transparent] focus:outline-hidden focus:ring-[2.5px] focus:ring-[#7e91ff5a] focus:border-[#7e91ffd5]\n                                            ${hasPw ? \"opacity-0 pointer-events-none\" : \"opacity-100\"}\n                                        `}\n\t\t\t\t\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"true\");\n\t\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"currAcc\", selectedUser);\n\t\t\t\t\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tLogin\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "src/Recovery.tsx",
    "content": "import { useEffect, useRef, useState } from \"react\";\nimport { version } from \"../package.json\";\nimport { libcurl } from \"libcurl.js\";\nimport { dirExists, unzip } from \"./sys/types\";\nimport { hash } from \"./hash.json\";\nimport apps from \"./apps.json\";\n\nexport default function Recovery() {\n\tconst [selected, setSelected] = useState(0);\n\tconst [showCursor, setShowCursor] = useState(false);\n\tconst [msg, setMsg] = useState<string | null>(null);\n\tconst [action, setAction] = useState<string>(\"\");\n\tconst [progress, setProgress] = useState(0);\n\tconst [updCache, setUpdCache] = useState(false);\n\tconst msgbox = useRef<HTMLDivElement>(null);\n\tconst main = useRef<HTMLDivElement>(null);\n\tconst progresscheck = useRef<HTMLDivElement>(null);\n\tconst statusref = useRef<HTMLDivElement>(null);\n\n\tasync function copyDir(inp: string, dest: string, rn?: boolean) {\n\t\tif (rn === true) {\n\t\t\tif (!(await dirExists(dest))) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(dest);\n\t\t\t}\n\t\t}\n\t\tconst files = await window.tb.fs.promises.readdir(inp);\n\t\tconst totalFiles = files.length;\n\t\tfor (const [index, file] of files.entries()) {\n\t\t\tconst stats = await window.tb.fs.promises.stat(`${inp}/${file}`);\n\t\t\tif (stats && stats.isDirectory()) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(`${dest}/${file}`);\n\t\t\t\tawait copyDir(`${inp}/${file}`, `${dest}/${file}`, true);\n\t\t\t} else {\n\t\t\t\tawait window.tb.fs.promises.writeFile(`${dest}/${file}`, await window.tb.fs.promises.readFile(`${inp}/${file}`, \"utf8\"));\n\t\t\t}\n\t\t\tstatusref.current!.innerText = `Creating a copy of: ${file}...`;\n\t\t\tsetProgress(Math.floor(((index + 1) / totalFiles) * 100));\n\t\t}\n\t}\n\n\tconst getTmp = async () => {\n\t\tif (await dirExists(\"/system/tmp/terb-upd/\")) {\n\t\t\tsetUpdCache(true);\n\t\t}\n\t};\n\tgetTmp();\n\n\tconst boot = () => {\n\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\twindow.location.reload();\n\t};\n\n\tconst cloak = () => {\n\t\tconst newWindow = window.open(\"about:blank\", \"_blank\");\n\t\tconst newDocument = newWindow!.document.open();\n\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\tnewDocument.write(`\n\t\t\t<!DOCTYPE html>\n\t\t\t<html>\n\t\t\t\t<head>\n\t\t\t\t\t<style type=\"text/css\">\n\t\t\t\t\t\tbody, html { margin: 0; padding: 0; height: 100%; overflow: hidden; }\n\t\t\t\t\t</style>\n\t\t\t\t</head>\n\t\t\t\t<body>\n\t\t\t\t\t<iframe style=\"border: none; width: 100%; height: 100vh;\" src=\"${window.location.href}?boot=true\"></iframe>\n\t\t\t\t</body>\n\t\t\t</html>\n\t\t`);\n\t\tnewDocument.close();\n\t\twindow.location.href = \"https://google.com\";\n\t\tconsole.log(\"Cloak Opened!\");\n\t};\n\n\tconst recovery = () => {\n\t\tsessionStorage.setItem(\"recovery\", \"true\");\n\t\twindow.location.reload();\n\t};\n\n\tconst prodins = async () => {\n\t\tsetShowCursor(false);\n\t\tmsgbox.current!.classList.remove(\"flex\");\n\t\tmsgbox.current!.classList.add(\"hidden\");\n\t\tprogresscheck.current!.classList.remove(\"hidden\");\n\t\tprogresscheck.current!.classList.add(\"flex\");\n\t\tif (localStorage.getItem(\"setup\")) {\n\t\t\tawait window.tb.sh.format();\n\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\"/bootentries.json\",\n\t\t\t\tJSON.stringify([\n\t\t\t\t\t{ name: \"TB React\", action: boot.toString() },\n\t\t\t\t\t{ name: \"TB React (Cloaked)\", action: cloak.toString() },\n\t\t\t\t\t{ name: \"TB System Recovery\", action: recovery.toString() },\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\t\tawait download(\"https://cdn.terbiumon.top/recovery/latest.zip\", \"/uploaded.zip\");\n\t\tsetShowCursor(false);\n\t\tawait unzip(\"//uploaded.zip\", \"//\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/tmp/\");\n\t\tawait window.tb.sh.promises.rm(\"/home/Guest/desktop/\", { recursive: true });\n\t\tawait window.tb.fs.promises.mkdir(\"/home/Guest/desktop/\");\n\t\tlet r2 = [];\n\t\tlet sysapps: { name: string; config: string; user: string }[] = [];\n\t\tlet items: { name: string; item: string; position: { custom: boolean; top: number; left: number } }[] = [];\n\t\tfor (let i = 0; i < apps.length; i++) {\n\t\t\tconst app = apps[i];\n\t\t\tconst name = app.name.toLowerCase();\n\t\t\tvar topPos: number = 0;\n\t\t\tvar leftPos: number = 0;\n\t\t\tif (i % 12 === 0) {\n\t\t\t\ttopPos = 0;\n\t\t\t} else {\n\t\t\t\ttopPos = i % 12;\n\t\t\t}\n\t\t\tif (i < 12) {\n\t\t\t\tleftPos = 0;\n\t\t\t} else {\n\t\t\t\tleftPos = 1;\n\t\t\t}\n\t\t\tif (topPos * 66 > window.innerHeight - 130) {\n\t\t\t\tleftPos = 1.15;\n\t\t\t\tif (r2.length === 0) {\n\t\t\t\t\ttopPos = 0;\n\t\t\t\t} else {\n\t\t\t\t\ttopPos = r2.length % 12;\n\t\t\t\t}\n\t\t\t\tr2.push({\n\t\t\t\t\tname: app.name,\n\t\t\t\t});\n\t\t\t}\n\t\t\titems.push({\n\t\t\t\tname: app.name,\n\t\t\t\titem: `/home/Guest/desktop/${name}.lnk`,\n\t\t\t\tposition: {\n\t\t\t\t\tcustom: false,\n\t\t\t\t\ttop: topPos,\n\t\t\t\t\tleft: leftPos,\n\t\t\t\t},\n\t\t\t});\n\t\t\tsysapps.push({\n\t\t\t\tname: app.name,\n\t\t\t\tconfig: `/apps/system/${name}.tapp/index.json`,\n\t\t\t\tuser: \"System\",\n\t\t\t});\n\t\t\tawait window.tb.fs.promises.writeFile(`/home/Guest/desktop/.desktop.json`, JSON.stringify(items));\n\t\t\tawait window.tb.fs.promises.symlink(`/apps/system/${name}.tapp/index.json`, `/home/Guest/desktop/${name}.lnk`);\n\t\t}\n\t\tstatusref.current!.innerText = \"Cleaning up...\";\n\t\tsetProgress(85);\n\t\tawait window.tb.fs.promises.unlink(\"//uploaded.zip\");\n\t\tsetProgress(100);\n\t\tstatusref.current!.innerText = \"Restarting...\";\n\t\tsessionStorage.clear();\n\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\tlocalStorage.setItem(\"setup\", \"true\");\n\t\twindow.location.reload();\n\t};\n\n\t// @ts-expect-error types\n\twindow.prodins = prodins;\n\n\tconst zipins = async () => {\n\t\tconst fauxput = document.createElement(\"input\");\n\t\tfauxput.type = \"file\";\n\t\tfauxput.accept = \".zip\";\n\t\tfauxput.onchange = async e => {\n\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\tif (target && target.files) {\n\t\t\t\tconst file = target.files[0];\n\t\t\t\tconst content = await file.arrayBuffer();\n\t\t\t\tsetProgress(10);\n\t\t\t\tif (localStorage.getItem(\"setup\")) {\n\t\t\t\t\tawait window.tb.sh.format();\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t\"/bootentries.json\",\n\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t{ name: \"TB React\", action: boot.toString() },\n\t\t\t\t\t\t\t{ name: \"TB React (Cloaked)\", action: cloak.toString() },\n\t\t\t\t\t\t\t{ name: \"TB System Recovery\", action: recovery.toString() },\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tsetProgress(25);\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"//uploaded.zip\", window.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\tsetProgress(35);\n\t\t\t\tsetShowCursor(false);\n\t\t\t\tmain.current!.classList.remove(\"flex\");\n\t\t\t\tmain.current!.classList.add(\"hidden\");\n\t\t\t\tprogresscheck.current!.classList.remove(\"hidden\");\n\t\t\t\tprogresscheck.current!.classList.add(\"flex\");\n\t\t\t\tawait unzip(\"//uploaded.zip\", \"//\");\n\t\t\t\tsetProgress(72);\n\t\t\t\tconst users = await window.tb.fs.promises.readdir(\"/home/\");\n\t\t\t\tfor (const user of users) {\n\t\t\t\t\t// note from XSTARS, this is a workaround that fixes the stupid symlink bug but it fucks over people with custom symlinks so be aware of that\n\t\t\t\t\tawait window.tb.sh.promises.rm(`/home/${user}/desktop/`, { recursive: true });\n\t\t\t\t\tawait window.tb.fs.promises.mkdir(`/home/${user}/desktop/`);\n\t\t\t\t\tlet r2 = [];\n\t\t\t\t\tlet sysapps: { name: string; config: string; user: string }[] = [];\n\t\t\t\t\tlet items: { name: string; item: string; position: { custom: boolean; top: number; left: number } }[] = [];\n\t\t\t\t\tfor (let i = 0; i < apps.length; i++) {\n\t\t\t\t\t\tconst app = apps[i];\n\t\t\t\t\t\tconst name = app.name.toLowerCase();\n\t\t\t\t\t\tvar topPos: number = 0;\n\t\t\t\t\t\tvar leftPos: number = 0;\n\t\t\t\t\t\tif (i % 12 === 0) {\n\t\t\t\t\t\t\ttopPos = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttopPos = i % 12;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (i < 12) {\n\t\t\t\t\t\t\tleftPos = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tleftPos = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (topPos * 66 > window.innerHeight - 130) {\n\t\t\t\t\t\t\tleftPos = 1.15;\n\t\t\t\t\t\t\tif (r2.length === 0) {\n\t\t\t\t\t\t\t\ttopPos = 0;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttopPos = r2.length % 12;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tr2.push({\n\t\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\titems.push({\n\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\titem: `/home/${user}/desktop/${name}.lnk`,\n\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsysapps.push({\n\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\tconfig: `/apps/system/${name}.tapp/index.json`,\n\t\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(items));\n\t\t\t\t\t\tawait window.tb.fs.promises.symlink(`/apps/system/${name}.tapp/index.json`, `/home/${user}/desktop/${name}.lnk`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tawait window.tb.fs.promises.mkdir(\"/system/tmp/\");\n\t\t\t\tstatusref.current!.innerText = \"Cleaning up...\";\n\t\t\t\tsetProgress(85);\n\t\t\t\tawait window.tb.fs.promises.unlink(\"//uploaded.zip\");\n\t\t\t\tsetProgress(100);\n\t\t\t\tstatusref.current!.innerText = \"Restarting...\";\n\t\t\t\tsessionStorage.clear();\n\t\t\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\t\t\tlocalStorage.setItem(\"setup\", \"true\");\n\t\t\t\twindow.location.reload();\n\t\t\t}\n\t\t};\n\t\tfauxput.click();\n\t};\n\n\tasync function download(url: string, location: string) {\n\t\tif (!window.loadLock) {\n\t\t\twindow.loadLock = true;\n\t\t\tawait libcurl.load_wasm(\"https://cdn.jsdelivr.net/npm/libcurl.js@latest/libcurl.wasm\");\n\t\t}\n\t\t// @ts-expect-error types\n\t\tlibcurl.set_websocket(`${window.location.protocol.replace(\"http\", \"ws\")}//${window.location.hostname}:${window.location.port}/wisp/`);\n\t\tconst response = await libcurl.fetch(url);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download the file. Status: ${response.status}`);\n\t\t}\n\t\tconst content = await response.arrayBuffer();\n\t\tawait window.tb.fs.promises.writeFile(location, window.tb.buffer.from(content), \"arraybuffer\");\n\t\tconsole.log(`File saved successfully at: ${location}`);\n\t}\n\n\tconst migrateFs = async () => {\n\t\tsetShowCursor(false);\n\t\tmain.current!.classList.remove(\"flex\");\n\t\tmain.current!.classList.add(\"hidden\");\n\t\tprogresscheck.current!.classList.remove(\"hidden\");\n\t\tprogresscheck.current!.classList.add(\"flex\");\n\t\tasync function copyRecursive(src: string, dest: string) {\n\t\t\tconst entries = await Filer.fs.promises.readdir(src);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tconst srcPath = src.endsWith(\"/\") ? src + entry : src + \"/\" + entry;\n\t\t\t\tconst destPath = dest.endsWith(\"/\") ? dest + entry : dest + \"/\" + entry;\n\t\t\t\tconst stat = await Filer.fs.promises.stat(srcPath);\n\t\t\t\tif (stat.isDirectory()) {\n\t\t\t\t\tif (!(await dirExists(destPath))) {\n\t\t\t\t\t\tawait window.tb.fs.promises.mkdir(destPath);\n\t\t\t\t\t}\n\t\t\t\t\tawait copyRecursive(srcPath, destPath);\n\t\t\t\t} else {\n\t\t\t\t\tconst fileBuffer = await Filer.fs.promises.readFile(srcPath);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(destPath, fileBuffer);\n\t\t\t\t}\n\t\t\t\tstatusref.current!.innerText = `Copying: ${srcPath}`;\n\t\t\t}\n\t\t}\n\t\tawait copyRecursive(\"/\", \"/\");\n\t\tsetProgress(85);\n\t\tstatusref.current!.innerText = \"Recreating Desktop Shortcuts...\";\n\t\tfor (const user of await window.tb.fs.promises.readdir(\"/home/\")) {\n\t\t\tconst items = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\tfor (const item of items) {\n\t\t\t\tconst target = await Filer.fs.promises.readlink(item.item);\n\t\t\t\tawait window.tb.fs.promises.symlink(target, item.item);\n\t\t\t\tstatusref.current!.innerText = `Creating shortcut: ${item.name}.lnk...`;\n\t\t\t}\n\t\t}\n\t\tsetProgress(93);\n\t\tstatusref.current!.innerText = \"Formatting Filer...\";\n\t\tconst fsh = new Filer.fs.Shell();\n\t\tfor (const loc of await Filer.fs.promises.readdir(\"//\")) {\n\t\t\tawait fsh.promises.rm(`/${loc}`, { recursive: true });\n\t\t}\n\t\tsetProgress(100);\n\t\tstatusref.current!.innerText = \"Migration complete!\";\n\t\tsessionStorage.clear();\n\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\tlocalStorage.setItem(\"setup\", \"true\");\n\t\tsessionStorage.removeItem(\"migrateFs\");\n\t\twindow.location.reload();\n\t};\n\n\t// @ts-expect-error types\n\twindow.migrateFs = migrateFs;\n\n\tuseEffect(() => {\n\t\tconst handleKeyDown = async (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"ArrowUp\") {\n\t\t\t\tsetSelected(prevSelected => (prevSelected === 0 ? (updCache ? 5 : 4) : prevSelected - 1));\n\t\t\t} else if (e.key === \"ArrowDown\") {\n\t\t\t\tsetSelected(prevSelected => (prevSelected === (updCache ? 5 : 4) ? 0 : prevSelected + 1));\n\t\t\t} else if (e.key === \"Enter\") {\n\t\t\t\tif (selected === 0) {\n\t\t\t\t\tlocalStorage.clear();\n\t\t\t\t\tsessionStorage.clear();\n\t\t\t\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\t\t\t\tif (localStorage.getItem(\"setup\")) {\n\t\t\t\t\t\tawait window.tb.sh.format();\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t\t\"/bootentries.json\",\n\t\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t\t{ name: \"TB React\", action: boot.toString() },\n\t\t\t\t\t\t\t\t{ name: \"TB React (Cloaked)\", action: cloak.toString() },\n\t\t\t\t\t\t\t\t{ name: \"TB System Recovery\", action: recovery.toString() },\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t} else if (selected === 1) {\n\t\t\t\t\tmsgbox.current!.classList.remove(\"hidden\");\n\t\t\t\t\tmsgbox.current!.classList.add(\"flex\");\n\t\t\t\t\tmain.current!.classList.add(\"hidden\");\n\t\t\t\t\tmain.current!.classList.remove(\"flex\");\n\t\t\t\t\tsetShowCursor(true);\n\t\t\t\t\tsetMsg(\"BE AWARE if your static hosting this download will NOT work. Proceed?\");\n\t\t\t\t\tsetAction(\"prodins()\");\n\t\t\t\t} else if (selected === 2) {\n\t\t\t\t\tmigrateFs();\n\t\t\t\t} else if (selected === 3) {\n\t\t\t\t\tzipins();\n\t\t\t\t} else if (selected === 4 && updCache) {\n\t\t\t\t\tsetShowCursor(false);\n\t\t\t\t\tmsgbox.current!.classList.remove(\"flex\");\n\t\t\t\t\tmsgbox.current!.classList.add(\"hidden\");\n\t\t\t\t\tprogresscheck.current!.classList.remove(\"hidden\");\n\t\t\t\t\tprogresscheck.current!.classList.add(\"flex\");\n\t\t\t\t\tawait copyDir(\"/system/tmp/terb-upd/\", \"/apps/\", true);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/hash.cache\", hash);\n\t\t\t\t\tawait window.tb.sh.promises.rm(\"/system/tmp/terb-upd/\", { recursive: true });\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t} else if (selected === (updCache ? 5 : 4)) {\n\t\t\t\t\tsessionStorage.clear();\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t}\n\t\t\t} else if (e.key === \"Escape\") {\n\t\t\t\tsetShowCursor(prev => !prev);\n\t\t\t}\n\t\t};\n\t\tconst getPlatform = () => {\n\t\t\tconst mobileuas =\n\t\t\t\t/(android|bb\\d+|meego).+mobile|armv7l|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series[46]0|samsungbrowser.*mobile|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk|iPhone|iPad/i;\n\t\t\tconst crosua = /CrOS/;\n\t\t\tif (mobileuas.test(navigator.userAgent) && !crosua.test(navigator.userAgent)) {\n\t\t\t\treturn \"mobile\";\n\t\t\t} else if (!mobileuas.test(navigator.userAgent) && navigator.maxTouchPoints > 1 && navigator.userAgent.indexOf(\"Macintosh\") !== -1 && navigator.userAgent.indexOf(\"Safari\") !== -1) {\n\t\t\t\treturn \"mobile\";\n\t\t\t} else {\n\t\t\t\treturn \"desktop\";\n\t\t\t}\n\t\t};\n\t\tif (getPlatform() === \"mobile\") {\n\t\t\tsetShowCursor(true);\n\t\t}\n\n\t\twindow.addEventListener(\"keydown\", handleKeyDown);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"keydown\", handleKeyDown);\n\t\t};\n\t}, [selected]);\n\n\treturn (\n\t\t<div className={`overflow-hidden w-full h-full flex justify-center pt-[30px] bg-[#0e0e0e] ${showCursor ? null : \"cursor-none\"}`}>\n\t\t\t<div className=\"flex flex-col items-center w-full p-2 text-[#ffffff48] overflow-hidden\">\n\t\t\t\t<div className=\"py-10 w-full flex justify-center text-[#ffffff68] font-bold text-2xl duration-150\">Terbium Recovery Utility - Version {version}</div>\n\t\t\t\t<div ref={msgbox} className=\"hidden mt-1 p-2 flex-col flex-grow overflow-auto w-full border-solid border-[#ffffff68] border-2 rounded-xl z-10\">\n\t\t\t\t\t<div className=\"flex flex-col items-center justify-center h-full\">\n\t\t\t\t\t\t<div className=\"flex items-center\">\n\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"1.5\" stroke=\"currentColor\" className=\"size-45\">\n\t\t\t\t\t\t\t\t<path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z\" />\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t<span className=\"ml-2 text-5xl\">{msg}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"flex mt-4\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"mr-2 cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\t\t\tonClick={() => eval(action)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tProceed\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\tsetShowCursor(false);\n\t\t\t\t\t\t\t\t\tmsgbox.current!.classList.remove(\"flex\");\n\t\t\t\t\t\t\t\t\tmsgbox.current!.classList.add(\"hidden\");\n\t\t\t\t\t\t\t\t\tmain.current!.classList.add(\"flex\");\n\t\t\t\t\t\t\t\t\tmain.current!.classList.remove(\"hidden\");\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ref={progresscheck} className=\"hidden bg-[#0e0e0e] h-full justify-center items-center flex-col lg:h-full md:h-full\">\n\t\t\t\t\t<img src=\"/tb.svg\" alt=\"Terbium\" className=\"w-[25%] h-[25%]\" />\n\t\t\t\t\t<div className=\"duration-150 flex flex-col justify-center items-center\">\n\t\t\t\t\t\t<div className=\"text-container relative flex flex-col justify-center items-end\">\n\t\t\t\t\t\t\t<div className=\"bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text flex flex-col lg:items-center md:items-center sm:items-center\">\n\t\t\t\t\t\t\t\t<span className=\"font-[700] lg:text-[34px] md:text-[28px] sm:text-[22px] text-right duration-150\">\n\t\t\t\t\t\t\t\t\t<span className=\"font-[1000] duration-150\">Terbium is installing</span>\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<br />\n\t\t\t\t\t\t\t\t<p>Please DO NOT close this tab</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<p ref={statusref} className=\"mt-1\">\n\t\t\t\t\t\tDownloading...\n\t\t\t\t\t</p>\n\t\t\t\t\t<div className=\"relative flex w-[30%] h-3 rounded-full bg-[#00000020] overflow-hidden mt-4\">\n\t\t\t\t\t\t<div className=\"absolute h-full bg-[#50bf66] rounded-full\" style={{ width: `${progress}%` }}></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ref={main} className=\"mt-1 p-2 flex flex-col flex-grow overflow-auto w-full border-solid border-[#ffffff68] border-2 rounded-xl\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\"p-2 px-2.5 text-sm font-extrabold lg:text-lg md:text-base border-[1px] rounded-md\" +\n\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t${selected === 0 && showCursor !== true ? \"bg-[#ffffff18] border-[#ffffff20]\" : \"border-transparent\"}\n\t\t\t\t\t\t\t${showCursor ? \"hover:bg-[#ffffff18] hover:border-[#ffffff20]\" : null}\n\t\t\t\t\t\t`\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={async () => {\n\t\t\t\t\t\t\tlocalStorage.clear();\n\t\t\t\t\t\t\tsessionStorage.clear();\n\t\t\t\t\t\t\tsessionStorage.setItem(\"boot\", \"true\");\n\t\t\t\t\t\t\tif (localStorage.getItem(\"setup\")) {\n\t\t\t\t\t\t\t\tawait window.tb.sh.format();\n\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t\t\t\t\"/bootentries.json\",\n\t\t\t\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t\t\t\t{ name: \"TB React\", action: boot.toString() },\n\t\t\t\t\t\t\t\t\t\t{ name: \"TB React (Cloaked)\", action: cloak.toString() },\n\t\t\t\t\t\t\t\t\t\t{ name: \"TB System Recovery\", action: recovery.toString() },\n\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tReinstall Terbium\n\t\t\t\t\t</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\"p-2 px-2.5 text-sm font-extrabold lg:text-lg md:text-base border-[1px] rounded-md\" +\n\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t${selected === 1 && showCursor !== true ? \"bg-[#ffffff18] border-[#ffffff20]\" : \"border-transparent\"}\n\t\t\t\t\t\t\t${showCursor ? \"hover:bg-[#ffffff18] hover:border-[#ffffff20]\" : null}\n\t\t\t\t\t\t`\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tmsgbox.current!.classList.remove(\"hidden\");\n\t\t\t\t\t\t\tmsgbox.current!.classList.add(\"flex\");\n\t\t\t\t\t\t\tmain.current!.classList.add(\"hidden\");\n\t\t\t\t\t\t\tmain.current!.classList.remove(\"flex\");\n\t\t\t\t\t\t\tsetShowCursor(true);\n\t\t\t\t\t\t\tsetMsg(\"BE AWARE if your static hosting this download will NOT work. Proceed?\");\n\t\t\t\t\t\t\tsetAction(\"prodins()\");\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tRestore from Production Instance (Beta)\n\t\t\t\t\t</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\"p-2 px-2.5 text-sm font-extrabold lg:text-lg md:text-base border-[1px] rounded-md\" +\n\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t${selected === 2 && showCursor !== true ? \"bg-[#ffffff18] border-[#ffffff20]\" : \"border-transparent\"}\n\t\t\t\t\t\t\t${showCursor ? \"hover:bg-[#ffffff18] hover:border-[#ffffff20]\" : null}\n\t\t\t\t\t\t`\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tmigrateFs();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tMigrate from Filer to OPFS\n\t\t\t\t\t</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\"p-2 px-2.5 text-sm font-extrabold lg:text-lg md:text-base border-[1px] rounded-md\" +\n\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t${selected === 3 && showCursor !== true ? \"bg-[#ffffff18] border-[#ffffff20]\" : \"border-transparent\"}\n\t\t\t\t\t\t\t${showCursor ? \"hover:bg-[#ffffff18] hover:border-[#ffffff20]\" : null}\n\t\t\t\t\t\t`\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tzipins();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tRestore from ZIP (Beta)\n\t\t\t\t\t</span>\n\t\t\t\t\t{updCache && (\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\"p-2 px-2.5 text-sm font-extrabold lg:text-lg md:text-base border-[1px] rounded-md\" +\n\t\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t${selected === 4 && showCursor !== true ? \"bg-[#ffffff18] border-[#ffffff20]\" : \"border-transparent\"}\n\t\t\t\t\t\t\t\t${showCursor ? \"hover:bg-[#ffffff18] hover:border-[#ffffff20]\" : null}\n\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tonClick={async () => {\n\t\t\t\t\t\t\t\tsetShowCursor(false);\n\t\t\t\t\t\t\t\tmsgbox.current!.classList.remove(\"flex\");\n\t\t\t\t\t\t\t\tmsgbox.current!.classList.add(\"hidden\");\n\t\t\t\t\t\t\t\tprogresscheck.current!.classList.remove(\"hidden\");\n\t\t\t\t\t\t\t\tprogresscheck.current!.classList.add(\"flex\");\n\t\t\t\t\t\t\t\tawait copyDir(\"/system/tmp/terb-upd/\", \"/apps/\", true);\n\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/hash.cache\", hash);\n\t\t\t\t\t\t\t\tawait window.tb.sh.promises.rm(\"/system/tmp/terb-upd/\", { recursive: true });\n\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tRestore from Update Cache\n\t\t\t\t\t\t</span>\n\t\t\t\t\t)}\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\"p-2 px-2.5 text-sm font-extrabold lg:text-lg md:text-base border-[1px] rounded-md\" +\n\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t${selected === (updCache ? 5 : 4) && showCursor !== true ? \"bg-[#ffffff18] border-[#ffffff20]\" : \"border-transparent\"}\n\t\t\t\t\t\t\t${showCursor ? \"hover:bg-[#ffffff18] hover:border-[#ffffff20]\" : null}\n\t\t\t\t\t\t`\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tsessionStorage.clear();\n\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tExit\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<span className=\"font-mono\">\n\t\t\t\t\tUse the <span className=\"text-[#ffffff68] text-2xl\">↑</span> and <span className=\"text-[#ffffff68] text-2xl\">↓</span> keys to switch entry.\n\t\t\t\t</span>\n\t\t\t\t<span className=\"font-mono\">\n\t\t\t\t\tPress the <span className=\"text-[#ffffff68] font-sans font-bold\">enter</span> key to select.\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/Setup.tsx",
    "content": "import Cropper from \"cropperjs\";\nimport Compressor from \"compressorjs\";\nimport { useState, useRef, useEffect } from \"react\";\nimport \"./sys/gui/styles/login.css\";\nimport \"./sys/gui/styles/cropper.css\";\nimport \"./sys/gui/styles/oobe.css\";\nimport \"./sys/gui/styles/dropdown.css\";\nimport pwd from \"./sys/apis/Crypto\";\nimport { init } from \"./init\";\nimport { fileExists, User } from \"./sys/types\";\nimport { InformationCircleIcon } from \"@heroicons/react/24/outline\";\nimport { libcurl } from \"libcurl.js\";\nimport { auth, getinfo, setinfo } from \"./sys/apis/utils/tauth\";\nconst pw = new pwd();\n\nexport default function Setup() {\n\tconst [beforeSetup, setBeforeSetup] = useState(1);\n\tconst [currentStep, setCurrentStep] = useState(1);\n\tconst currentViewRef = useRef<HTMLDivElement | null>(null);\n\tvar nextButtonClick = () => void 0;\n\tconst Next = (step?: number) => {\n\t\tsetBeforeSetup(currentStep);\n\t\tif (step) {\n\t\t\tsetCurrentStep(step);\n\t\t} else {\n\t\t\tsetCurrentStep(prevStep => Math.min(prevStep + 1, 5));\n\t\t}\n\t};\n\tconst Back = () => {\n\t\tsetBeforeSetup(currentStep);\n\t\tif (currentStep === 2.1 || currentStep === 2.2) {\n\t\t\tsessionStorage.removeItem(\"tacc\");\n\t\t\tsetCurrentStep(2);\n\t\t} else if (currentStep === 2.3 || currentStep === 2.4 || currentStep === 2.5) {\n\t\t\tsetCurrentStep(2.2);\n\t\t} else if (currentStep === 3.1) {\n\t\t\tsetCurrentStep(2.5);\n\t\t} else if (currentStep === 3) {\n\t\t\tif (sessionStorage.getItem(\"tacc\")) {\n\t\t\t\tsetCurrentStep(2.5);\n\t\t\t} else {\n\t\t\t\tsetCurrentStep(2.1);\n\t\t\t}\n\t\t} else {\n\t\t\tsetCurrentStep(prevStep => Math.max(prevStep - 1, 1));\n\t\t}\n\t};\n\tif (!window.libcurlLock) {\n\t\twindow.libcurlLock = true;\n\t\tlibcurl.load_wasm(\"https://cdn.jsdelivr.net/npm/libcurl.js@latest/libcurl.wasm\");\n\t}\n\t// @ts-expect-error no types\n\tlibcurl.set_websocket(`${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`);\n\tconst authClient = auth;\n\tconst randomColors = [\"orange\", \"red\", \"green\", \"blue\", \"purple\", \"pink\", \"yellow\"];\n\tconst makePFP = () => {\n\t\tconst uploader = document.createElement(\"input\");\n\t\tuploader.type = \"file\";\n\t\tuploader.accept = \"img/*\";\n\t\tuploader.onchange = () => {\n\t\t\tconst files = uploader!.files;\n\t\t\tconst file = files![0];\n\t\t\tconst reader = new FileReader();\n\t\t\treader.onload = () => {\n\t\t\t\tconst img = document.createElement(\"img\");\n\t\t\t\timg.classList.add(\"opacity-0\", \"pointer-events-none\");\n\t\t\t\timg.src = reader.result as string;\n\t\t\t\timg.onload = () => {\n\t\t\t\t\tconst cropper_container = document.createElement(\"div\");\n\t\t\t\t\tconst cropper_container_styles = [\"w-screen\", \"h-screen\", \"fixed\", \"top-0\", \"left-0\", \"right-0\", \"bottom-0\", \"z-999999\", \"bg-[#000000a6]\", \"flex\", \"flex-col\", \"justify-center\", \"items-center\", \"gap-[10px]\"];\n\t\t\t\t\tcropper_container_styles.forEach(style => cropper_container.classList.add(style));\n\t\t\t\t\tconst cropper_img_container = document.createElement(\"div\");\n\t\t\t\t\tcropper_img_container.className = \"cropper-img-container\";\n\t\t\t\t\tlet cropper_img_container_sizes = [\"bg-[#ffffff0a]\", \"lg:w-[500px]\", \"lg:h-[500px]\", \"md:w-[400px]\", \"md:h-[400px]\", \"sm:w-[300px]\", \"sm:h-[300px]\", \"flex\", \"justify-center\", \"items-center\", \"rounded-[8px]\", \"overflow-hidden\"];\n\t\t\t\t\tcropper_img_container_sizes.forEach(size => cropper_img_container.classList.add(size));\n\t\t\t\t\tconst cropper_img = document.createElement(\"img\");\n\t\t\t\t\tcropper_img.src = img.src;\n\t\t\t\t\tcropper_img.classList.add(\"cropper-img\");\n\t\t\t\t\tcropper_img_container.classList.add(\"w-[500px]\");\n\t\t\t\t\tcropper_img_container.classList.add(\"h-[500px]\");\n\t\t\t\t\tcropper_img.style.objectFit = \"cover\";\n\t\t\t\t\tcropper_img.style.objectPosition = \"center\";\n\t\t\t\t\tcropper_img_container.appendChild(cropper_img);\n\t\t\t\t\tcropper_container.appendChild(cropper_img_container);\n\t\t\t\t\tdocument.body.appendChild(cropper_container);\n\t\t\t\t\tconst cropper = new Cropper(cropper_img, { aspectRatio: 1, viewMode: 1, cropBoxResizable: false, movable: true, rotatable: true, scalable: true, responsive: true });\n\t\t\t\t\tconst buttons = document.createElement(\"div\");\n\t\t\t\t\tbuttons.className = \"flex w-[500px] justify-between items-center\";\n\t\t\t\t\tcropper_container.appendChild(buttons);\n\t\t\t\t\tconst save = document.createElement(\"button\");\n\t\t\t\t\tsave.className = \"save broken_button cursor-pointer\";\n\t\t\t\t\tsave.innerText = \"Save\";\n\t\t\t\t\tconst save_styles = [\n\t\t\t\t\t\t\"bg-[#1d1d1d]\",\n\t\t\t\t\t\t\"text-[#ffffff38]\",\n\t\t\t\t\t\t\"border-[#ffffff22]\",\n\t\t\t\t\t\t\"hover:bg-[#414141]\",\n\t\t\t\t\t\t\"hover:text-[#ffffff8d]\",\n\t\t\t\t\t\t\"focus:bg-[#ffffff1f]\",\n\t\t\t\t\t\t\"focus:text-[#ffffff8d]\",\n\t\t\t\t\t\t\"focus:border-[#73a9ffd6]\",\n\t\t\t\t\t\t\"focus:ring-[#73a9ff74]\",\n\t\t\t\t\t\t\"focus:outline-hidden\",\n\t\t\t\t\t\t\"focus:ring-2\",\n\t\t\t\t\t\t\"ring-[transparent]\",\n\t\t\t\t\t\t\"ring-0\",\n\t\t\t\t\t\t\"border-[1px]\",\n\t\t\t\t\t\t\"font-[600]\",\n\t\t\t\t\t\t\"px-[20px]\",\n\t\t\t\t\t\t\"py-[8px]\",\n\t\t\t\t\t\t\"h-[18px]\",\n\t\t\t\t\t\t\"rounded-[6px]\",\n\t\t\t\t\t\t\"transition\",\n\t\t\t\t\t\t\"duration-150\",\n\t\t\t\t\t];\n\t\t\t\t\tsave_styles.forEach(style => save.classList.add(style));\n\t\t\t\t\tsave.onclick = () => {\n\t\t\t\t\t\tconst canvas = cropper.getCroppedCanvas();\n\t\t\t\t\t\tconst pfp = document.querySelector(\".pfp\");\n\t\t\t\t\t\tcanvas.toBlob(blob => {\n\t\t\t\t\t\t\tnew Compressor(blob as Blob, {\n\t\t\t\t\t\t\t\tquality: 0.5,\n\t\t\t\t\t\t\t\tsuccess(result) {\n\t\t\t\t\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\t\t\t\t\treader.readAsDataURL(result);\n\t\t\t\t\t\t\t\t\treader.onload = () => {\n\t\t\t\t\t\t\t\t\t\t(pfp! as HTMLImageElement).style.background = `url(${reader.result})`;\n\t\t\t\t\t\t\t\t\t\t(pfp! as HTMLImageElement).style.backgroundSize = \"cover\";\n\t\t\t\t\t\t\t\t\t\t(pfp! as HTMLImageElement).style.backgroundPosition = \"center\";\n\t\t\t\t\t\t\t\t\t\t(pfp! as HTMLImageElement).style.backgroundRepeat = \"no-repeat\";\n\t\t\t\t\t\t\t\t\t\t(pfp! as HTMLImageElement).setAttribute(\"data-src\", reader.result as string);\n\t\t\t\t\t\t\t\t\t\tdocument.body.removeChild(cropper_container);\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\t\t\t\t\tconst cancel = document.createElement(\"button\");\n\t\t\t\t\tcancel.className = \"cancel broken_button cursor-pointer\";\n\t\t\t\t\tcancel.innerText = \"Cancel\";\n\t\t\t\t\tconst cancel_styles = [\n\t\t\t\t\t\t\"bg-[#1d1d1d]\",\n\t\t\t\t\t\t\"text-[#ffffff38]\",\n\t\t\t\t\t\t\"border-[#ffffff22]\",\n\t\t\t\t\t\t\"hover:bg-[#414141]\",\n\t\t\t\t\t\t\"hover:text-[#ffffff8d]\",\n\t\t\t\t\t\t\"focus:bg-[#ffffff1f]\",\n\t\t\t\t\t\t\"focus:text-[#ffffff8d]\",\n\t\t\t\t\t\t\"focus:border-[#73a9ffd6]\",\n\t\t\t\t\t\t\"focus:ring-[#73a9ff74]\",\n\t\t\t\t\t\t\"focus:outline-hidden\",\n\t\t\t\t\t\t\"focus:ring-2\",\n\t\t\t\t\t\t\"ring-[transparent]\",\n\t\t\t\t\t\t\"ring-0\",\n\t\t\t\t\t\t\"border-[1px]\",\n\t\t\t\t\t\t\"font-[600]\",\n\t\t\t\t\t\t\"px-[20px]\",\n\t\t\t\t\t\t\"py-[8px]\",\n\t\t\t\t\t\t\"h-[18px]\",\n\t\t\t\t\t\t\"rounded-[6px]\",\n\t\t\t\t\t\t\"transition\",\n\t\t\t\t\t\t\"duration-150\",\n\t\t\t\t\t];\n\t\t\t\t\tcancel_styles.forEach(style => cancel.classList.add(style));\n\t\t\t\t\tcancel.onclick = () => {\n\t\t\t\t\t\tdocument.body.removeChild(cropper_container);\n\t\t\t\t\t};\n\t\t\t\t\tbuttons.appendChild(cancel);\n\t\t\t\t\tbuttons.appendChild(save);\n\t\t\t\t};\n\t\t\t};\n\t\t\treader.readAsDataURL(file);\n\t\t};\n\t\tuploader.click();\n\t};\n\tconst saveData = async () => {\n\t\twindow.onbeforeunload = e => {\n\t\t\te.preventDefault();\n\t\t\te.returnValue = \"Terbium is still updating\";\n\t\t};\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: \"Initializing File System...\" }));\n\t\tconst int = await init();\n\t\tconsole.log(`Init State: ${int}`);\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: \"Setting up user...\" }));\n\t\tlet data: User = JSON.parse(sessionStorage.getItem(\"new-user\") as string);\n\t\tsessionStorage.setItem(\"new-user\", JSON.stringify(data));\n\t\tconst usr = data[\"username\"];\n\t\tdata[\"id\"] = usr;\n\t\tlet pass: any;\n\t\tif (typeof data[\"password\"] === \"string\" && data[\"password\"].length > 0) {\n\t\t\tpass = pw.harden(data[\"password\"]);\n\t\t} else {\n\t\t\tpass = false;\n\t\t}\n\t\tconst email = data[\"email\"];\n\t\tif (email) {\n\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify([data], null, 2), \"utf8\");\n\t\t} else {\n\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify([], null, 2), \"utf8\");\n\t\t}\n\t\tconst userInf: User = {\n\t\t\tid: usr,\n\t\t\tusername: usr,\n\t\t\tpassword: pass,\n\t\t\tpfp: data[\"pfp\"],\n\t\t\tperm: data[\"perm\"],\n\t\t};\n\t\tif (data.securityQuestion) {\n\t\t\tuserInf[\"securityQuestion\"] = {\n\t\t\t\tquestion: data.securityQuestion.question,\n\t\t\t\tanswer: pw.harden(data.securityQuestion.answer),\n\t\t\t};\n\t\t}\n\t\tif (sessionStorage.getItem(\"tacc-settings\") === \"null\") {\n\t\t\tconst tosave = {\n\t\t\t\tsettings: userInf,\n\t\t\t\tapps: [],\n\t\t\t\tdavs: [],\n\t\t\t};\n\t\t\tawait setinfo(email as string, data[\"password\"] as string, \"tbs\", tosave);\n\t\t}\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: \"Finalizing setup...\" }));\n\t\tawait window.tb.fs.promises.writeFile(`/home/${usr}/user.json`, JSON.stringify(userInf), \"utf8\");\n\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/sudousers.json\", JSON.stringify([usr]), \"utf8\");\n\t\tawait window.tb.fs.promises.mkdir(`/home/${usr}/documents/`);\n\t\tawait window.tb.fs.promises.mkdir(`/home/${usr}/images/`);\n\t\tawait window.tb.fs.promises.mkdir(`/home/${usr}/videos/`);\n\t\tawait window.tb.fs.promises.mkdir(`/home/${usr}/music/`);\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: \"Finalizing settings...\" }));\n\t\tlet settings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${usr}/settings.json`, \"utf8\"));\n\t\tlet syssettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\tif (!syssettings[\"setup\"] || syssettings[\"setup\"] === false) {\n\t\t\tsyssettings[\"setup\"] = true;\n\t\t}\n\t\tsyssettings[\"defaultUser\"] = usr;\n\t\tconst transport = sessionStorage.getItem(\"selectedTransport\") || \"Default (Epoxy)\";\n\t\tif (transport === \"Default (Epoxy)\") {\n\t\t\tsettings[\"transport\"] = \"Default (Epoxy)\";\n\t\t} else if (transport === \"Anura BCC\") {\n\t\t\tsettings[\"transport\"] = \"Anura BCC\";\n\t\t} else {\n\t\t\tsettings[\"transport\"] = \"Libcurl\";\n\t\t}\n\t\tconst wsrv = sessionStorage.getItem(\"selectedBare\") || `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`;\n\t\tsettings[\"wispServer\"] = wsrv;\n\t\tawait window.tb.fs.promises.writeFile(`/home/${usr}/settings.json`, JSON.stringify(settings), \"utf8\");\n\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(syssettings), \"utf8\");\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: \"Finalizing setup...\" }));\n\t\tconst wispExist = await fileExists(\"//apps/system/settings.tapp/wisp-servers.json\");\n\t\tif (!wispExist) {\n\t\t\tconst stockDat = [\n\t\t\t\t{ id: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`, name: \"Backend\" },\n\t\t\t\t{ id: \"wss://wisp.terbiumon.top/wisp/\", name: \"TB Wisp Instance\" },\n\t\t\t];\n\t\t\tawait window.tb.fs.promises.writeFile(\"//apps/system/settings.tapp/wisp-servers.json\", JSON.stringify(stockDat));\n\t\t}\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: \"Restarting Terbium...\" }));\n\t\tlocalStorage.setItem(\"setup\", \"true\");\n\t\twindow.onbeforeunload = null;\n\t\tif (sessionStorage!.getItem(\"logged-in\") === null || sessionStorage!.getItem(\"logged-in\") === undefined || sessionStorage!.getItem(\"logged-in\") === \"false\") {\n\t\t\twindow.location.reload();\n\t\t\tsessionStorage.setItem(\"firstRun\", \"true\");\n\t\t} else {\n\t\t\twindow.location.reload();\n\t\t\tsessionStorage.setItem(\"firstRun\", \"true\");\n\t\t}\n\t\tsessionStorage.removeItem(\"new-user\");\n\t};\n\n\tconst Step1 = () => {\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<div className=\"text-container relative flex flex-col justify-center items-end\">\n\t\t\t\t\t<div className=\"bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text flex flex-col lg:items-end md:items-center sm:items-center\">\n\t\t\t\t\t\t<span className=\"font-[700] lg:text-[34px] md:text-[28px] sm:text-[22px] text-right duration-150\">\n\t\t\t\t\t\t\tThe next generation of <span className=\"font-[1000] duration-150\">Terbium.</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"font-[1000] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Built to last.</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step2 = () => {\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Choose what kind of account you want to use</span>\n\t\t\t\t<div className=\"relative flex flex-col justify-center items-end\">\n\t\t\t\t\t<div className=\"flex flex-row gap-2\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={`cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150 ${currentStep === 5 ? \"translate-y-8 opacity-0 pointer-events-none\" : \"\"}`}\n\t\t\t\t\t\t\tonMouseDown={() => Next(2.1)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tLocal Account\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={`cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150 ${currentStep === 5 ? \"translate-y-8 opacity-0 pointer-events-none\" : \"\"}`}\n\t\t\t\t\t\t\tonMouseDown={() => Next(2.2)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tTerbium Cloud&trade; Account\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step2CA = () => {\n\t\tconst usernameRef = useRef<HTMLInputElement>(null);\n\t\tconst passwordRef = useRef<HTMLInputElement>(null);\n\t\tconst [connected, setConnected] = useState(false);\n\t\tconst [error, setError] = useState(\"\");\n\t\tuseEffect(() => {\n\t\t\tconst test = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await libcurl.fetch(`https://auth.terbiumon.top/ping`, { method: \"GET\" });\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tsetConnected(false);\n\t\t\t\t\t\tBack();\n\t\t\t\t\t}\n\t\t\t\t\tsetConnected(true);\n\t\t\t\t} catch {\n\t\t\t\t\tsetConnected(false);\n\t\t\t\t\tBack();\n\t\t\t\t}\n\t\t\t};\n\t\t\ttest();\n\t\t}, [connected]);\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\tnextButtonClick = () => {\n\t\t\tif (!connected) return;\n\t\t\tconst password = passwordRef.current?.value || \"\";\n\t\t\tauthClient.signIn.email({\n\t\t\t\temail: usernameRef.current?.value || \"\",\n\t\t\t\tpassword: password,\n\t\t\t\trememberMe: true,\n\t\t\t\tfetchOptions: {\n\t\t\t\t\tonSuccess: async data => {\n\t\t\t\t\t\tconst p = await getinfo(null, null, \"tbs\");\n\t\t\t\t\t\tif (p.settings === null || p.settings === undefined) {\n\t\t\t\t\t\t\tsessionStorage.setItem(\"tacc-settings\", \"null\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsessionStorage.setItem(\"tacc-settings\", JSON.stringify(p.settings));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsessionStorage.setItem(\n\t\t\t\t\t\t\t\"new-user\",\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\tusername: data.data.user.name,\n\t\t\t\t\t\t\t\tpassword: password,\n\t\t\t\t\t\t\t\tperm: \"admin\",\n\t\t\t\t\t\t\t\tpfp: data.data.user.image,\n\t\t\t\t\t\t\t\temail: data.data.user.email,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tNext(2.5);\n\t\t\t\t\t},\n\t\t\t\t\tonError: error => {\n\t\t\t\t\t\tsetError(error.error.message || \"An unknown error occurred during registration.\");\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t});\n\t\t};\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t{connected ? (\n\t\t\t\t\t<div className=\"flex flex-col justify-center items-center\">\n\t\t\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Sign in with Terbium Cloud&trade;</span>\n\t\t\t\t\t\t<div className=\"relative flex flex-col justify-center items-end\">\n\t\t\t\t\t\t\t{error && (\n\t\t\t\t\t\t\t\t<div className=\"w-full p-2 mb-2 bg-red-500/20 border border-red-500/50 rounded-md text-sm text-red-300\">\n\t\t\t\t\t\t\t\t\t<InformationCircleIcon className=\"inline-block w-5 h-5 mr-1\" /> {error}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tref={usernameRef}\n\t\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\t\tclassName=\"username cursor-[var(--cursor-text)] rounded-[6px] px-[10px] py-[8px] text-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:placeholder-[#ffffff48] focus:text-[#ffffff] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"email\"\n\t\t\t\t\t\t\t\t\tonKeyDown={(e: any) => {\n\t\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\" && passwordRef.current) {\n\t\t\t\t\t\t\t\t\t\t\tpasswordRef.current.focus();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tref={passwordRef}\n\t\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t\tclassName=\"password cursor-[var(--cursor-text)] rounded-[6px] px-[10px] py-[8px] text-[#ffffff] caret-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:text-[#ffffff] focus:placeholder-[#ffffff48] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"password\"\n\t\t\t\t\t\t\t\t\tonKeyDown={(e: any) => {\n\t\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\" && passwordRef.current) {\n\t\t\t\t\t\t\t\t\t\t\tnextButtonClick();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<div className=\"flex flex-row gap-2\">\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\tclassName=\"cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\t\t\t\t\tonMouseDown={() => Next(2.3)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\tCreate Account\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\tclassName=\"cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\t\t\t\t\tonMouseDown={() => Next(2.4)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\tForgot Password\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Terbium Cloud&trade;</span>\n\t\t\t\t\t\t<p>Connecting to Authentication servers please wait...</p>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step2CR = () => {\n\t\tconst usernameRef = useRef<HTMLInputElement>(null);\n\t\tconst passwordRef = useRef<HTMLInputElement>(null);\n\t\tconst pfpRef = useRef<HTMLDivElement>(null);\n\t\tconst emailRef = useRef<HTMLInputElement>(null);\n\t\tconst [error, setError] = useState(\"\");\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\tnextButtonClick = () => {\n\t\t\tauthClient.signUp.email({\n\t\t\t\temail: emailRef.current?.value || \"\",\n\t\t\t\tpassword: passwordRef.current?.value || \"\",\n\t\t\t\tname: usernameRef.current?.value || \"\",\n\t\t\t\tfetchOptions: {\n\t\t\t\t\tonSuccess: async data => {\n\t\t\t\t\t\tconsole.log(\"Successfully registered:\", data);\n\t\t\t\t\t\tsessionStorage.setItem(\n\t\t\t\t\t\t\t\"new-user\",\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\tusername: usernameRef.current?.value,\n\t\t\t\t\t\t\t\tpassword: passwordRef.current?.value,\n\t\t\t\t\t\t\t\tperm: \"admin\",\n\t\t\t\t\t\t\t\tpfp: pfpRef.current?.getAttribute(\"data-src\") || `/assets/img/default - ${randomColors[Math.floor(Math.random() * randomColors.length)]}.png`,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tNext(2.5);\n\t\t\t\t\t},\n\t\t\t\t\tonError: error => {\n\t\t\t\t\t\tif (error.error.message.toLocaleLowerCase() === \"missing or null origin\") {\n\t\t\t\t\t\t\tlocalStorage.removeItem(\"libcurl_cookies\");\n\t\t\t\t\t\t\tnextButtonClick();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetError(error.error.message || \"An unknown error occurred during registration.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\timage: pfpRef.current?.getAttribute(\"data-src\") || `/assets/img/default - ${randomColors[Math.floor(Math.random() * randomColors.length)]}.png`,\n\t\t\t});\n\t\t};\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Create a Terbium Cloud&trade; account</span>\n\t\t\t\t<div className=\"relative flex flex-col justify-center items-end\">\n\t\t\t\t\t{error && (\n\t\t\t\t\t\t<div className=\"w-full p-2 mb-2 bg-red-500/20 border border-red-500/50 rounded-md text-sm text-red-300\">\n\t\t\t\t\t\t\t<InformationCircleIcon className=\"inline-block w-5 h-5 mr-1\" /> {error}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t<div className=\"flex flex-col gap-2 items-center\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tref={pfpRef}\n\t\t\t\t\t\t\tclassName=\"pfp relative group w-[100px] h-[100px] rounded-[50%] bg-[#ffffff0a] border-[#3b3b3b] border-[2px] transition duration-150 ring-[transparent] ring-0 cursor-pointer\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tmakePFP();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"uploader opacity-0 size-full rounded-[50%] bg-[#00000060] transition duration-150 group-hover:opacity-100 cursor-pointer\"></div>\n\t\t\t\t\t\t\t<p className=\"absolute top-1/2 cursor-pointer left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-0 text-[#cccccc] text-[16px] font-[600] group-hover:opacity-100 transition duration-150 pointer-events-none\">Upload</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={usernameRef}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclassName=\"username cursor-[var(--cursor-text)] rounded-[6px] px-[10px] py-[8px] text-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:placeholder-[#ffffff48] focus:text-[#ffffff] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\tplaceholder=\"username\"\n\t\t\t\t\t\t\tonKeyDown={(e: any) => {\n\t\t\t\t\t\t\t\tif (e.key === \"Enter\" && passwordRef.current) {\n\t\t\t\t\t\t\t\t\tpasswordRef.current.focus();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={passwordRef}\n\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\tclassName=\"password cursor-[var(--cursor-text)] rounded-[6px] px-[10px] py-[8px] text-[#ffffff] caret-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:text-[#ffffff] focus:placeholder-[#ffffff48] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\tplaceholder=\"password\"\n\t\t\t\t\t\t\tonKeyDown={(e: any) => {\n\t\t\t\t\t\t\t\tif (e.key === \"Enter\" && emailRef.current) {\n\t\t\t\t\t\t\t\t\temailRef.current.focus();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={emailRef}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclassName=\"email cursor-[var(--cursor-text)] rounded-[6px] px-[10px] py-[8px] text-[#ffffff] caret-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:text-[#ffffff] focus:placeholder-[#ffffff48] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\tplaceholder=\"email address\"\n\t\t\t\t\t\t\tonKeyDown={(e: any) => {\n\t\t\t\t\t\t\t\tif (e.key === \"Enter\") {\n\t\t\t\t\t\t\t\t\tnextButtonClick();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\t\tonMouseDown={() => Next(2.2)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAlready have an account? Sign in\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t};\n\t// @ts-expect-error future\n\tconst Step2FG = () => {\n\t\tconst usernameRef = useRef<HTMLInputElement>(null);\n\t\tconst sendBTNRef = useRef<HTMLButtonElement>(null);\n\t\tconst [emailSent, setEmailSent] = useState(false);\n\t\tconst [error, setError] = useState(\"\");\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\tnextButtonClick = () => {\n\t\t\tif (!emailSent) {\n\t\t\t\tsendBTNRef.current!.innerText = \"Email Sent\";\n\t\t\t\tauthClient.requestPasswordReset({\n\t\t\t\t\temail: usernameRef.current?.value || \"\",\n\t\t\t\t\tfetchOptions: {\n\t\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\t\tsetEmailSent(true);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonError: error => {\n\t\t\t\t\t\t\tsetError(error.error.message || \"An unknown error occurred while attempting to send the password reset email.\");\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tNext(2.2);\n\t\t};\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Reset Terbium Cloud&trade; password</span>\n\t\t\t\t<div className=\"relative flex flex-col justify-center items-end\">\n\t\t\t\t\t<div className=\"flex flex-col gap-2 items-center\">\n\t\t\t\t\t\t{error && (\n\t\t\t\t\t\t\t<div className=\"w-full p-2 mb-2 bg-red-500/20 border border-red-500/50 rounded-md text-sm text-red-300\">\n\t\t\t\t\t\t\t\t<InformationCircleIcon className=\"inline-block w-5 h-5 mr-1\" /> {error}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={usernameRef}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclassName=\"username cursor-[var(--cursor-text)] rounded-[6px] px-[10px] py-[8px] text-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:placeholder-[#ffffff48] focus:text-[#ffffff] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\tplaceholder=\"email\"\n\t\t\t\t\t\t\tonKeyDown={e => {\n\t\t\t\t\t\t\t\tif (e.key === \"Enter\") {\n\t\t\t\t\t\t\t\t\tnextButtonClick();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div className=\"flex flex-row gap-2\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName=\"cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\t\t\tonMouseDown={() => Next(2.2)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tBack to Sign in\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tref={sendBTNRef}\n\t\t\t\t\t\t\t\tclassName=\"cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\t\t\tdisabled={emailSent}\n\t\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\t\tsetEmailSent(true);\n\t\t\t\t\t\t\t\t\tsendBTNRef.current!.innerText = \"Email Sent\";\n\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\tsetEmailSent(false);\n\t\t\t\t\t\t\t\t\t\tsendBTNRef.current!.innerText = \"Send Email\";\n\t\t\t\t\t\t\t\t\t}, 60000);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tSend Email\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step2CF = () => {\n\t\tconst [hasSettings, setHasSettings] = useState(false);\n\t\tconst userdata = JSON.parse(sessionStorage.getItem(\"new-user\") as string) || {};\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\tuseEffect(() => {\n\t\t\tif (sessionStorage.getItem(\"tacc-settings\") !== \"null\") {\n\t\t\t\tsetHasSettings(true);\n\t\t\t}\n\t\t}, [hasSettings]);\n\t\tnextButtonClick = () => {\n\t\t\tif (!hasSettings) {\n\t\t\t\tNext(3);\n\t\t\t\tsessionStorage.setItem(\"tacc\", \"true\");\n\t\t\t} else {\n\t\t\t\tNext(3.1);\n\t\t\t}\n\t\t};\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center gap-1.5\"\n\t\t\t>\n\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Confirm Identity</span>\n\t\t\t\t<img src={userdata.pfp} className=\"w-[100px] h-[100px] rounded-[50%] mb-[10px]\" />\n\t\t\t\t<p className=\"text-[#ffffffb3] text-center lg:w-[400px] md:w-[300px] sm:w-[250px] text-2xl\">Welcome, {userdata.username}!</p>\n\t\t\t\t{hasSettings ? (\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<div className=\"w-full p-2 mb-2 bg-green-500/20 border border-green-500/50 rounded-md text-sm text-green-300\">\n\t\t\t\t\t\t\t<InformationCircleIcon className=\"inline-block w-5 h-5 mr-1\" /> Terbium Settings were found for this account.\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div className=\"w-full p-2 mb-2 bg-red-500/20 border border-red-500/50 rounded-md text-sm text-red-300\">\n\t\t\t\t\t\t<InformationCircleIcon className=\"inline-block w-5 h-5 mr-1\" /> Terbium Settings were not found for this account.\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<p>Click next to use this account</p>\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step2L = () => {\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\tconst usernameRef = useRef<HTMLInputElement>(null);\n\t\tconst passwordRef = useRef<HTMLInputElement>(null);\n\t\tconst pfpRef = useRef<HTMLDivElement>(null);\n\t\tconst secQ = useRef<HTMLInputElement>(null);\n\t\tconst secA = useRef<HTMLInputElement>(null);\n\t\tconst [showSec, setShowsec] = useState(false);\n\n\t\tnextButtonClick = () => {\n\t\t\tconst pfp = pfpRef.current?.getAttribute(\"data-src\");\n\t\t\tconst finalPfp = pfp || `/assets/img/default - ${randomColors[Math.floor(Math.random() * randomColors.length)]}.png`;\n\t\t\tlet passdata: any = JSON.parse(sessionStorage.getItem(\"new-user\") as string) || {};\n\t\t\tpassdata[\"pfp\"] = finalPfp;\n\t\t\tsessionStorage.setItem(\"new-user\", JSON.stringify(passdata));\n\t\t\tconst password = passwordRef.current?.value || false;\n\t\t\tconst username = usernameRef.current?.value || \"Guest\";\n\t\t\tlet data: any = JSON.parse(sessionStorage.getItem(\"new-user\") as string) || {};\n\t\t\tdata[\"username\"] = username;\n\t\t\tdata[\"password\"] = password;\n\t\t\tdata[\"perm\"] = \"admin\";\n\t\t\tif (secA.current?.value && secQ.current?.value && secA.current.value.length > 0 && secQ.current.value.length > 0) {\n\t\t\t\tdata[\"securityQuestion\"] = {\n\t\t\t\t\tquestion: secQ.current.value,\n\t\t\t\t\tanswer: secA.current.value,\n\t\t\t\t};\n\t\t\t}\n\t\t\tsessionStorage.setItem(\"new-user\", JSON.stringify(data));\n\t\t\tcurrentViewRef.current?.classList.add(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.add(\"opacity-0\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tNext(3);\n\t\t\t}, 150);\n\t\t};\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<div className=\"relative flex flex-col justify-center\">\n\t\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text mb-[20px]\">Setup your account.</span>\n\t\t\t\t\t<div className=\"flex flex-col gap-[10px] justify-center items-center\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tref={pfpRef}\n\t\t\t\t\t\t\tclassName=\"pfp relative group w-[100px] h-[100px] rounded-[50%] bg-[#ffffff0a] border-[#3b3b3b] border-[2px] transition duration-150 ring-[transparent] ring-0 cursor-pointer\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tmakePFP();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"uploader opacity-0 size-full rounded-[50%] bg-[#00000060] transition duration-150 group-hover:opacity-100 cursor-pointer\"></div>\n\t\t\t\t\t\t\t<p className=\"absolute top-1/2 cursor-pointer left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-0 text-[#cccccc] text-[16px] font-[600] group-hover:opacity-100 transition duration-150 pointer-events-none\">Upload</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"inputs flex flex-col gap-[10px] justify-center items-left\">\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tref={usernameRef}\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tclassName=\"username cursor-[var(--cursor-text)] rounded-[6px] px-[10px] py-[8px] text-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:placeholder-[#ffffff48] focus:text-[#ffffff] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\t\tplaceholder=\"Username\"\n\t\t\t\t\t\t\t\tonKeyDown={(e: any) => {\n\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\" && passwordRef.current) {\n\t\t\t\t\t\t\t\t\t\tpasswordRef.current.focus();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<div className=\"pass relative flex justify-center items-center gap-[10px]\">\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tref={passwordRef}\n\t\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t\tclassName=\"password cursor-(--cursor-text) rounded-md px-2.5 py-2 w-full text-[#ffffff] caret-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border transition duration-150 ring-transparent ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:text-[#ffffff] focus:placeholder-[#ffffff48] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"Password\"\n\t\t\t\t\t\t\t\t\tonChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n\t\t\t\t\t\t\t\t\t\tif (e.target.value.length > 0) {\n\t\t\t\t\t\t\t\t\t\t\tsetShowsec(true);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tsetShowsec(false);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{showSec ? (\n\t\t\t\t\t\t\t<div className=\"security-section mt-4 p-3 rounded bg-[#ffffff0a] border border-[#ffffff22] w-[250]\">\n\t\t\t\t\t\t\t\t<div className=\"font-semibold text-[#ffffffa0] mb-2\">Security Question (optional)</div>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tref={secQ}\n\t\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\t\tclassName=\"security-question mb-2 w-full rounded-[6px] px-[10px] py-[8px] text-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:text-[#ffffff] focus:placeholder-[#ffffff48] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"Enter a security question\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tref={secA}\n\t\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\t\tclassName=\"security-answer w-full rounded-[6px] px-[10px] py-[8px] text-[#ffffff] placeholder-[#ffffff38] bg-[#ffffff0a] border-[#ffffff22] border-[1px] transition duration-150 ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:text-[#ffffff] focus:placeholder-[#ffffff48] focus:outline-hidden focus:ring-2\"\n\t\t\t\t\t\t\t\t\tplaceholder=\"Enter your answer\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step3 = () => {\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\n\t\tnextButtonClick = () => {\n\t\t\tcurrentViewRef.current?.classList.add(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.add(\"opacity-0\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tNext();\n\t\t\t}, 150);\n\t\t};\n\n\t\tconst [selectedProxy, setSelectedProxy] = useState(() => sessionStorage.getItem(\"selectedProxy\") || \"Scramjet\");\n\t\tconst [proxyDropdownOpen, setProxyDropdownOpen] = useState(false);\n\t\tconst toggleProxyDropDown = () => {\n\t\t\tsetProxyDropdownOpen(prev => {\n\t\t\t\treturn !prev;\n\t\t\t});\n\t\t};\n\t\tconst proxyClick = (optionLabel: any) => {\n\t\t\tsetSelectedProxy(optionLabel);\n\t\t\tsessionStorage.setItem(\"selectedProxy\", optionLabel);\n\t\t\tsetProxyDropdownOpen(false);\n\t\t};\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Choose your default proxy.</span>\n\t\t\t\t<div className=\"dropdown def-proxy\">\n\t\t\t\t\t<div className=\"dropdown-title\" onMouseDown={toggleProxyDropDown}>\n\t\t\t\t\t\t<span className=\"pointer-events-none\">{selectedProxy}</span>\n\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-[22px] h-[22px] pointer-events-none\">\n\t\t\t\t\t\t\t<path fillRule=\"evenodd\" d=\"M11.47 4.72a.75.75 0 011.06 0l3.75 3.75a.75.75 0 01-1.06 1.06L12 6.31 8.78 9.53a.75.75 0 01-1.06-1.06l3.75-3.75zm-3.75 9.75a.75.75 0 011.06 0L12 17.69l3.22-3.22a.75.75 0 111.06 1.06l-3.75 3.75a.75.75 0 01-1.06 0l-3.75-3.75a.75.75 0 010-1.06z\" clipRule=\"evenodd\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</div>\n\t\t\t\t\t{proxyDropdownOpen && (\n\t\t\t\t\t\t<div className=\"dropdown-options active\">\n\t\t\t\t\t\t\t<div className=\"dropdown-option\" onMouseDown={() => proxyClick(\"Ultraviolet\")}>\n\t\t\t\t\t\t\t\t<span className=\"pointer-events-none\">Ultraviolet</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"dropdown-option\" onMouseDown={() => proxyClick(\"Scramjet\")}>\n\t\t\t\t\t\t\t\t<span className=\"pointer-events-none\">Scramjet</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step3SR = () => {\n\t\tuseEffect(() => {\n\t\t\tconst t = setTimeout(() => {\n\t\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\", \"opacity-0\");\n\t\t\t}, 150);\n\t\t\treturn () => clearTimeout(t);\n\t\t}, []);\n\t\tnextButtonClick = () => {\n\t\t\tNext(5);\n\t\t};\n\t\tconst opts = JSON.parse(sessionStorage.getItem(\"tacc-settings\") as string) || [];\n\t\ttype OptionItem = { id: string; label: string; raw: any };\n\t\tconst isb64 = (v: any) => {\n\t\t\tif (!v || typeof v !== \"string\") return false;\n\t\t\tif (v.startsWith(\"data:\")) return true;\n\t\t\tconst stripped = v.replace(/\\s+/g, \"\");\n\t\t\treturn /^[A-Za-z0-9+/=]+$/.test(stripped) && stripped.length > 200;\n\t\t};\n\t\tconst normalizeRaw = (r: any) => (typeof r === \"string\" && isb64(r) ? \"Synced Wallpaper\" : r);\n\t\tconst getOptions = (cat: string): OptionItem[] => {\n\t\t\tconst val = (opts[0] as any)[cat];\n\t\t\tif (cat === \"settings\" && val && typeof val === \"object\") {\n\t\t\t\tconst copy = { ...val };\n\t\t\t\treturn Object.entries(copy).flatMap(([k, v]) => {\n\t\t\t\t\tif (v && typeof v === \"object\" && !Array.isArray(v)) {\n\t\t\t\t\t\treturn Object.entries(v).map(([kk, vv]) => ({ id: `settings.${k}.${kk}`, label: `${k}.${kk}`, raw: normalizeRaw(vv) }));\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn [{ id: `settings.${k}`, label: k, raw: normalizeRaw(v) }];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (Array.isArray(val)) {\n\t\t\t\treturn val.length === 0\n\t\t\t\t\t? []\n\t\t\t\t\t: val.map((item, i) => {\n\t\t\t\t\t\t\tlet label = typeof item === \"object\" && item ? item.name || item.url || JSON.stringify(item).slice(0, 30) : String(item);\n\t\t\t\t\t\t\treturn { id: `${cat}[${i}]`, label, raw: normalizeRaw(item) };\n\t\t\t\t\t\t});\n\t\t\t}\n\t\t\tif (val && typeof val === \"object\") {\n\t\t\t\treturn Object.keys(val).map(k => ({ id: `${cat}.${k}`, label: k, raw: normalizeRaw(val[k]) }));\n\t\t\t}\n\t\t\treturn [{ id: cat, label: `${cat}: ${String(val)}`, raw: normalizeRaw(val) }];\n\t\t};\n\t\tconst categories = Object.keys(opts[0]);\n\t\tconst [currMap, setCurrMap] = useState<Record<string, string[]>>(() =>\n\t\t\tcategories.reduce(\n\t\t\t\t(acc, cat) => {\n\t\t\t\t\tacc[cat] = getOptions(cat).map(o => o.id);\n\t\t\t\t\treturn acc;\n\t\t\t\t},\n\t\t\t\t{} as Record<string, string[]>,\n\t\t\t),\n\t\t);\n\t\tconst toggleOption = (cat: string, optionId: string) =>\n\t\t\tsetCurrMap(prev => {\n\t\t\t\tconst s = new Set(prev[cat] || []);\n\t\t\t\ts.has(optionId) ? s.delete(optionId) : s.add(optionId);\n\t\t\t\treturn { ...prev, [cat]: Array.from(s) };\n\t\t\t});\n\t\tconst setAll = (cat: string, enabled: boolean) => {\n\t\t\tconst ids = getOptions(cat).map(o => o.id);\n\t\t\tsetCurrMap(prev => ({ ...prev, [cat]: enabled ? ids : [] }));\n\t\t};\n\t\tconst Card: React.FC<{ cat: string }> = ({ cat }) => {\n\t\t\tconst options = getOptions(cat);\n\t\t\tconst sel = currMap[cat] || [];\n\t\t\tconst allSelected = options.length > 0 && options.every(o => sel.includes(o.id));\n\t\t\tconst noneSelected = sel.length === 0;\n\t\t\treturn (\n\t\t\t\t<div className=\"w-[420px] max-w-full p-4 rounded-md bg-[#111111] border border-[#2a2a2a] mb-4\">\n\t\t\t\t\t<div className=\"flex justify-between items-center mb-2\">\n\t\t\t\t\t\t<div className=\"font-[700] text-lg\">{cat}</div>\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<button className=\"px-2 py-1 text-sm cursor-pointer\" onMouseDown={() => setAll(cat, true)}>\n\t\t\t\t\t\t\t\tSelect all\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button className=\"px-2 py-1 text-sm cursor-pointer\" onMouseDown={() => setAll(cat, false)}>\n\t\t\t\t\t\t\t\tClear\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t{options.length === 0 ? (\n\t\t\t\t\t\t<div className=\"text-sm text-[#ffffff88]\">There are no options at this time.</div>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t{options\n\t\t\t\t\t\t\t\t.filter(opt => opt.id !== \"apps.installed\")\n\t\t\t\t\t\t\t\t.map(opt => (\n\t\t\t\t\t\t\t\t\t<label key={opt.id} className=\"flex items-center gap-2 text-sm\">\n\t\t\t\t\t\t\t\t\t\t<input type=\"checkbox\" checked={sel.includes(opt.id)} onChange={() => toggleOption(cat, opt.id)} />\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-[#ffffffb3]\">{opt.label}</span>\n\t\t\t\t\t\t\t\t\t\t{typeof opt.raw !== \"object\" && opt.raw !== null ? <span className=\"ml-auto text-[#ffffff44] text-xs\">{String(opt.raw)}</span> : null}\n\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2 text-xs text-[#ffffff66] mt-1\">\n\t\t\t\t\t\t\t\t<span>{allSelected ? \"All selected\" : noneSelected ? \"None selected\" : `${sel.length} selected`}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t);\n\t\t};\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Restore Terbium Settings</span>\n\t\t\t\t<div className=\"flex flex-wrap gap-4 justify-center mt-4 overflow-y-auto max-h-[50%] p-2\">\n\t\t\t\t\t{categories.map(cat => (\n\t\t\t\t\t\t<Card key={cat} cat={cat} />\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step4 = () => {\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\n\t\tnextButtonClick = () => {\n\t\t\tcurrentViewRef.current?.classList.add(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.add(\"opacity-0\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tNext();\n\t\t\t}, 150);\n\t\t};\n\n\t\tconst [selectedBare, setSelectedBare] = useState(() => sessionStorage.getItem(\"selectedBare\") || \"Backend (Default)\");\n\t\tconst [selectedTransport, setSelectedTransport] = useState(() => sessionStorage.getItem(\"selectedTransport\") || \"Default (Epoxy)\");\n\t\tconst [bareDropdownOpen, setBareDropdownOpen] = useState(false);\n\t\tconst [transportDropdownOpen, setTransportDropdownOpen] = useState(false);\n\t\tconst [customServer, setCustomServer] = useState(\"\");\n\t\tconst bareOptions = [{ label: \"Backend (Default)\" }, { label: \"TB Wisp Instance\" }, { label: \"Custom Server\" }];\n\t\tconst transportOptions = [{ label: \"Default (Epoxy)\" }, { label: \"Libcurl\" }, { label: \"Anura BCC\" }];\n\t\tconst bClick = (label: any) => {\n\t\t\tsetSelectedBare(label);\n\t\t\tif (label === \"Custom Server\") {\n\t\t\t\tsetCustomServer(\"\");\n\t\t\t} else if (label === \"Backend (Default)\") {\n\t\t\t\tsessionStorage.setItem(\"selectedBare\", `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`);\n\t\t\t} else if (label === \"TB Wisp Instance\") {\n\t\t\t\tsessionStorage.setItem(\"selectedBare\", `wss://wisp.terbiumon.top/wisp/`);\n\t\t\t}\n\t\t\tsetBareDropdownOpen(false);\n\t\t};\n\t\tconst transportOnClick = (label: any) => {\n\t\t\tsetSelectedTransport(label);\n\t\t\tsessionStorage.setItem(\"selectedTransport\", label);\n\t\t\tsetTransportDropdownOpen(false);\n\t\t};\n\t\tconst ServerChange = async (e: any) => {\n\t\t\tconst value = e.target.value;\n\t\t\tsetCustomServer(value);\n\t\t\tsessionStorage.setItem(\"selectedBare\", value);\n\t\t\tconst stockDat = [\n\t\t\t\t{ id: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`, name: \"Backend\" },\n\t\t\t\t{ id: \"wss://wisp.terbiumon.top/wisp/\", name: \"TB Wisp Instance\" },\n\t\t\t\t{ id: value, name: \"Custom Wisp\" },\n\t\t\t];\n\t\t\tawait window.tb.fs.promises.writeFile(\"//apps/system/settings.tapp/wisp-servers.json\", JSON.stringify(stockDat));\n\t\t};\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={el => {\n\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t}}\n\t\t\t\tclassName=\"duration-150 -translate-x-6 opacity-0 flex flex-col justify-center items-center\"\n\t\t\t>\n\t\t\t\t<span className=\"font-[800] text-[34px] bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text lg:mb-[20px] md:mb-[20px] sm:mb-[10px] lg:text-[34px] md:text-[28px] sm:text-[22px] duration-150\">Customize Proxy Settings</span>\n\t\t\t\t<div className=\"dropdown def-proxy mr-[185px]\">\n\t\t\t\t\t<div className=\"dropdown-title\" onMouseDown={() => setBareDropdownOpen(prev => !prev)}>\n\t\t\t\t\t\t<span className=\"pointer-events-none\">{selectedBare}</span>\n\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-[22px] h-[22px] pointer-events-none\">\n\t\t\t\t\t\t\t<path fillRule=\"evenodd\" d=\"M11.47 4.72a.75.75 0 011.06 0l3.75 3.75a.75.75 0 01-1.06 1.06L12 6.31 8.78 9.53a.75.75 0 01-1.06-1.06l3.75-3.75zm-3.75 9.75a.75.75 0 011.06 0L12 17.69l3.22-3.22a.75.75 0 111.06 1.06l-3.75 3.75a.75.75 0 01-1.06 0l-3.75-3.75a.75.75 0 010-1.06z\" clipRule=\"evenodd\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</div>\n\t\t\t\t\t{bareDropdownOpen && (\n\t\t\t\t\t\t<div className=\"dropdown-options active\">\n\t\t\t\t\t\t\t{bareOptions.map((option, index) => (\n\t\t\t\t\t\t\t\t<div className=\"dropdown-option\" key={index} onMouseDown={() => bClick(option.label)}>\n\t\t\t\t\t\t\t\t\t<span className=\"pointer-events-none\">{option.label}</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"dropdown def-transport ml-[230px] mt-[-42px]\">\n\t\t\t\t\t<div className=\"dropdown-title\" onMouseDown={() => setTransportDropdownOpen(prev => !prev)}>\n\t\t\t\t\t\t<span className=\"pointer-events-none\">{selectedTransport}</span>\n\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-[22px] h-[22px] pointer-events-none\">\n\t\t\t\t\t\t\t<path fillRule=\"evenodd\" d=\"M11.47 4.72a.75.75 0 011.06 0l3.75 3.75a.75.75 0 01-1.06 1.06L12 6.31 8.78 9.53a.75.75 0 01-1.06-1.06l3.75-3.75zm-3.75 9.75a.75.75 0 011.06 0L12 17.69l3.22-3.22a.75.75 0 111.06 1.06l-3.75 3.75a.75.75 0 01-1.06 0l-3.75-3.75a.75.75 0 010-1.06z\" clipRule=\"evenodd\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</div>\n\t\t\t\t\t{transportDropdownOpen && (\n\t\t\t\t\t\t<div className=\"dropdown-options active\">\n\t\t\t\t\t\t\t{transportOptions.map((option, index) => (\n\t\t\t\t\t\t\t\t<div className=\"dropdown-option\" key={index} onMouseDown={() => transportOnClick(option.label)}>\n\t\t\t\t\t\t\t\t\t<span className=\"pointer-events-none\">{option.label}</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t{selectedBare === \"Custom Server\" && (\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"url\"\n\t\t\t\t\t\tvalue={customServer}\n\t\t\t\t\t\tonChange={ServerChange}\n\t\t\t\t\t\tplaceholder=\"Enter custom server\"\n\t\t\t\t\t\tclassName=\"custom rounded-[6px] px-[10px] py-[8px] mt-[10px] bg-[#ffffff0a] border-[#ffffff22] border-[1px] text-[#ffffff] caret-[#ffffff] placeholder-[#ffffff38] ring-[transparent] ring-0 focus:bg-[#ffffff1f] focus:border-[#73a9ffd6] focus:outline-hidden duration-150\"\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t);\n\t};\n\tconst Step5 = () => {\n\t\tconst ranRef = useRef(false);\n\t\tconst actionRef = useRef<HTMLParagraphElement | null>(null);\n\t\tsetTimeout(() => {\n\t\t\tcurrentViewRef.current?.classList.remove(\"-translate-x-6\");\n\t\t\tcurrentViewRef.current?.classList.remove(\"opacity-0\");\n\t\t}, 150);\n\t\tuseEffect(() => {\n\t\t\tif (ranRef.current) return;\n\t\t\tranRef.current = true;\n\t\t\tconst updateActionText = (e: CustomEvent) => {\n\t\t\t\tif (actionRef.current) {\n\t\t\t\t\tactionRef.current.innerHTML = e.detail;\n\t\t\t\t}\n\t\t\t};\n\t\t\twindow.addEventListener(\"oobe-setupstage\", updateActionText as EventListener);\n\t\t\t(async () => {\n\t\t\t\tawait saveData();\n\t\t\t})();\n\t\t}, []);\n\t\treturn (\n\t\t\t<div>\n\t\t\t\t<p\n\t\t\t\t\tref={el => {\n\t\t\t\t\t\tcurrentViewRef.current = el;\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"font-[700] text-[28px] duration-150 -translate-x-6 opacity-0\"\n\t\t\t\t>\n\t\t\t\t\tWelcome to Terbium.\n\t\t\t\t</p>\n\t\t\t\t<p ref={actionRef} className=\"font-bold text-lg text-[#ffffff66] mt-2\">\n\t\t\t\t\tStarting Services...\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t);\n\t};\n\n\tconst Buttons = () => {\n\t\tconst currentMotionEl = useRef<HTMLDivElement | HTMLButtonElement | null>(null);\n\t\tsetTimeout(() => {\n\t\t\tcurrentMotionEl.current?.classList.remove(\"translate-y-8\", \"opacity-0\");\n\t\t}, 150);\n\n\t\treturn (\n\t\t\t<div className={`absolute bottom-2.5 left-2.5 right-2.5 h-max flex justify-center items-center max-w-full overflow-x-auto overflow-y-hidden`}>\n\t\t\t\t{currentStep === 1 ? (\n\t\t\t\t\t<button\n\t\t\t\t\t\tref={el => {\n\t\t\t\t\t\t\tcurrentMotionEl.current = el;\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"translate-y-8 opacity-0 cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150\"\n\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\tcurrentViewRef.current?.classList.add(\"-translate-x-6\");\n\t\t\t\t\t\t\tcurrentViewRef.current?.classList.add(\"opacity-0\");\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\tNext();\n\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tStart\n\t\t\t\t\t</button>\n\t\t\t\t) : null}\n\t\t\t\t{currentStep > 1 ? (\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={el => {\n\t\t\t\t\t\t\tcurrentStep < 4 && (currentMotionEl.current = el);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName={`${currentStep === 2 && beforeSetup !== 3 && \"translate-y-8 opacity-0\"} duration-150 w-full flex flex-wrap justify-between gap-2 max-w-full`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{currentStep < 5 && (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclassName={`cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150 ${currentStep === 5 ? \"translate-y-8 opacity-0 pointer-events-none\" : \"\"}`}\n\t\t\t\t\t\t\t\tonMouseDown={() => Back()}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tPrevious\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{currentStep > 2 && currentStep < 5 && (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tref={el => {\n\t\t\t\t\t\t\t\t\tcurrentStep === 4 && (currentMotionEl.current = el);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tclassName={`${currentStep === 5 && \"translate-y-8 opacity-0\"} cursor-pointer bg-[#ffffff0a] text-[#ffffff38] border-[#ffffff22] hover:bg-[#ffffff10] hover:text-[#ffffff8d] focus:bg-[#ffffff1f] focus:text-[#ffffff8d] focus:border-[#73a9ffd6] focus:ring-[#73a9ff74] focus:outline-hidden focus:ring-2 ring-[transparent] ring-0 border-[1px] font-[600] px-[20px] py-[8px] rounded-[6px] duration-150`}\n\t\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\t\tcurrentStep < 5 ? nextButtonClick() : null;\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{currentStep < 4 ? \"Next\" : \"Finish\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t);\n\t};\n\n\treturn (\n\t\t<div className=\"bg-[#0e0e0e] h-full\">\n\t\t\t<div className=\"steps-container flex flex-col lg:flex-row md:flex-row w-full h-full overflow-y-hidden\">\n\t\t\t\t<div className=\"logo sm:h-full sm:w-1/2 h-1/2 w-full flex flex-col justify-end items-center sm:justify-center sm:items-center overflow-y-hidden\">\n\t\t\t\t\t<img src=\"/assets/img/logo.png\" alt=\"TB\" className=\"w-[240px] lg:w-[480px] h-auto\" />\n\t\t\t\t</div>\n\t\t\t\t<div className=\"sm:h-full sm:w-1/2 h-1/2 w-full flex flex-col justify-start items-center text-center sm:justify-center sm:items-center overflow-y-hidden\">\n\t\t\t\t\t{currentStep === 1 ? (\n\t\t\t\t\t\t<Step1 />\n\t\t\t\t\t) : currentStep === 2 ? (\n\t\t\t\t\t\t<Step2 />\n\t\t\t\t\t) : currentStep === 2.1 ? (\n\t\t\t\t\t\t<Step2L />\n\t\t\t\t\t) : currentStep === 2.2 ? (\n\t\t\t\t\t\t<Step2CA />\n\t\t\t\t\t) : currentStep === 2.3 ? (\n\t\t\t\t\t\t<Step2CR />\n\t\t\t\t\t) : currentStep === 2.4 ? (\n\t\t\t\t\t\t<Step2CA />\n\t\t\t\t\t) : currentStep === 2.5 ? (\n\t\t\t\t\t\t<Step2CF />\n\t\t\t\t\t) : currentStep === 3 ? (\n\t\t\t\t\t\t<Step3 />\n\t\t\t\t\t) : currentStep === 3.1 ? (\n\t\t\t\t\t\t<Step3SR />\n\t\t\t\t\t) : currentStep === 4 ? (\n\t\t\t\t\t\t<Step4 />\n\t\t\t\t\t) : currentStep === 5 ? (\n\t\t\t\t\t\t<Step5 />\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<Buttons />\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/Updater.tsx",
    "content": "import { useEffect, useState, useRef } from \"react\";\nimport { dirExists, fileExists, unzip, UserSettings } from \"./sys/types\";\nimport { hash } from \"./hash.json\";\nimport paths from \"./installer.json\";\n\nexport default function Updater() {\n\tconst [progress, setProgress] = useState(0);\n\tconst statusref = useRef<HTMLDivElement>(null);\n\n\tasync function copyDir(inp: string, dest: string, rn?: boolean) {\n\t\tif (rn === true) {\n\t\t\tif (!(await dirExists(dest))) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(dest);\n\t\t\t}\n\t\t}\n\t\tconst files = await window.tb.fs.promises.readdir(inp);\n\t\tconst totalFiles = files.length;\n\t\tfor (const [index, file] of files.entries()) {\n\t\t\tconst stats = await window.tb.fs.promises.stat(`${inp}/${file}`);\n\t\t\tif (stats && stats.isDirectory()) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(`${dest}/${file}`);\n\t\t\t\tawait copyDir(`${inp}/${file}`, `${dest}/${file}`, true);\n\t\t\t} else {\n\t\t\t\tawait window.tb.fs.promises.writeFile(`${dest}/${file}`, await window.tb.fs.promises.readFile(`${inp}/${file}`, \"utf8\"));\n\t\t\t}\n\t\t\tstatusref.current!.innerText = `Creating a copy of: ${file}...`;\n\t\t\tsetProgress(Math.floor(((index + 1) / totalFiles) * 100));\n\t\t}\n\t}\n\n\tuseEffect(() => {\n\t\tconst main = async () => {\n\t\t\twindow.onbeforeunload = e => {\n\t\t\t\te.preventDefault();\n\t\t\t\te.returnValue = \"Terbium is still updating\";\n\t\t\t};\n\t\t\tlet sysapps = [\"about.tapp\", \"app store.tapp\", \"browser.tapp\", \"calculator.tapp\", \"feedback.tapp\", \"files.tapp\", \"media viewer.tapp\", \"settings.tapp\", \"task manager.tapp\", \"terminal.tapp\", \"text editor.tapp\"];\n\t\t\tlet sysscripts = [\n\t\t\t\t\"cat.js\",\n\t\t\t\t\"cd.js\",\n\t\t\t\t\"clear.js\",\n\t\t\t\t\"curl.js\",\n\t\t\t\t\"echo.js\",\n\t\t\t\t\"exit.js\",\n\t\t\t\t\"git.js\",\n\t\t\t\t\"help.js\",\n\t\t\t\t\"info.json\",\n\t\t\t\t\"info.schema.json\",\n\t\t\t\t\"ls.js\",\n\t\t\t\t\"mkdir.js\",\n\t\t\t\t\"node.js\",\n\t\t\t\t\"ping.js\",\n\t\t\t\t\"pkg.js\",\n\t\t\t\t\"pkill.js\",\n\t\t\t\t\"pwd.js\",\n\t\t\t\t\"qwick.js\",\n\t\t\t\t\"qwick_install.js\",\n\t\t\t\t\"rm.js\",\n\t\t\t\t\"rmdir.js\",\n\t\t\t\t\"sysfetch.js\",\n\t\t\t\t\"taskkill.js\",\n\t\t\t\t\"tb.js\",\n\t\t\t\t\"touch.js\",\n\t\t\t\t\"unzip.js\",\n\t\t\t\t\"ssh.js\",\n\t\t\t\t\"ssh-keygen.js\",\n\t\t\t\t\"nano.js\",\n\t\t\t];\n\t\t\tif (await dirExists(\"/system/tmp/terb-upd/\")) {\n\t\t\t\tawait window.tb.sh.promises.rm(`/system/tmp/terb-upd/`, { recursive: true });\n\t\t\t}\n\t\t\tstatusref.current!.innerText = \"Installing latest version of TB...\";\n\t\t\tawait window.tb.fs.promises.mkdir(\"/system/tmp/terb-upd/\");\n\t\t\tconst apps = await window.tb.fs.promises.readdir(\"/apps/system/\");\n\t\t\tconst scripts = await window.tb.fs.promises.readdir(\"/apps/system/terminal.tapp/scripts/\");\n\t\t\tsetProgress(20);\n\t\t\tstatusref.current!.innerText = \"Creating a backup\";\n\t\t\tif (await fileExists(\"/apps/system/settings.tapp/wisp-servers.json\")) {\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/tmp/terb-upd/wisp-servers.json\", await window.tb.fs.promises.readFile(\"/apps/system/settings.tapp/wisp-servers.json\"));\n\t\t\t} else {\n\t\t\t\tconst stockDat = [\n\t\t\t\t\t{ id: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`, name: \"Backend\" },\n\t\t\t\t\t{ id: \"wss://wisp.terbiumon.top/wisp/\", name: \"TB Wisp Instance\" },\n\t\t\t\t];\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/tmp/terb-upd/wisp-servers.json\", JSON.stringify(stockDat));\n\t\t\t}\n\t\t\tfor (const item of apps) {\n\t\t\t\tsetProgress(prevProgress => prevProgress + 1);\n\t\t\t\tif (sysapps.includes(item)) {\n\t\t\t\t\tif (item === \"terminal.tapp\") {\n\t\t\t\t\t\tfor (const item of scripts) {\n\t\t\t\t\t\t\tsetProgress(prevProgress => prevProgress + 1);\n\t\t\t\t\t\t\tif (!sysscripts.includes(item)) {\n\t\t\t\t\t\t\t\tconsole.log(`Skipping ${item}...`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait copyDir(`/apps/system/${item}/`, `/system/tmp/terb-upd/${item}.old`, true);\n\t\t\t\t\t\tawait window.tb.sh.promises.rm(`/apps/system/${item}/`, { recursive: true });\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`Skipping ${item}...`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetProgress(50);\n\t\t\tstatusref.current!.innerText = \"Updating Terbium...\";\n\t\t\tsetProgress(0);\n\t\t\tfor (const item of paths) {\n\t\t\t\tsetProgress(prevProgress => prevProgress + 1);\n\t\t\t\tstatusref.current!.innerText = `Installing ${item}...`;\n\t\t\t\tconst isDir = item.toString().endsWith(\"/\");\n\t\t\t\tif (isDir) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\tawait window.tb.fs.promises.mkdir(`/apps/system/${item.toString()}`, { recursive: true });\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t}\n\t\t\t\t} else if (item.toString().endsWith(\".tapp.zip\")) {\n\t\t\t\t\tconst res = await fetch(`/apps/${item.toString()}`);\n\t\t\t\t\tif (!res.ok) {\n\t\t\t\t\t\tconsole.error(`Failed to fetch /apps/${item.toString()}: ${res.status} ${res.statusText}`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst data = await res.arrayBuffer();\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/system/${item}`, window.tb.buffer.from(data));\n\t\t\t\t\tawait unzip(`/apps/system/${item}`, `/apps/system/${item.slice(0, -4)}`);\n\t\t\t\t\tawait window.tb.fs.promises.unlink(`/apps/system/${item}`);\n\t\t\t\t} else {\n\t\t\t\t\tconst path = `/apps/system/${item.toString()}`;\n\t\t\t\t\tconst dir = path.substring(0, path.lastIndexOf(\"/\"));\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!(await dirExists(dir))) {\n\t\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\t\tawait window.tb.fs.promises.mkdir(dir, { recursive: true });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst res = await fetch(`/apps/${item.toString()}`);\n\t\t\t\t\t\tconst data = await res.text();\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(path, data);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait window.tb.fs.promises.writeFile(\"/apps/system/settings.tapp/wisp-servers.json\", await window.tb.fs.promises.readFile(\"/system/tmp/terb-upd/wisp-servers.json\"));\n\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/hash.cache\", hash);\n\t\t\tconst user = sessionStorage.getItem(\"currAcc\") || JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\")).defaultUser;\n\t\t\t// v2.0-Beta2 update\n\t\t\tif (!(await fileExists(\"/apps/installed.json\"))) {\n\t\t\t\tstatusref.current!.innerText = \"Installing Terbium v2.0-Beta2 prerequisites...\";\n\t\t\t\tlet insapps = [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"About\",\n\t\t\t\t\t\tconfig: \"/apps/system/about.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"App Store\",\n\t\t\t\t\t\tconfig: \"/apps/system/app store.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Calculator\",\n\t\t\t\t\t\tconfig: \"/apps/system/calculator.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Feedback\",\n\t\t\t\t\t\tconfig: \"/apps/system/feedback.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Files\",\n\t\t\t\t\t\tconfig: \"/apps/system/files.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Media Viewer\",\n\t\t\t\t\t\tconfig: \"/apps/system/media viewer.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Settings\",\n\t\t\t\t\t\tconfig: \"/apps/system/settings.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Task Manager\",\n\t\t\t\t\t\tconfig: \"/apps/system/task manager.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Terminal\",\n\t\t\t\t\t\tconfig: \"/apps/system/terminal.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Text Editor\",\n\t\t\t\t\t\tconfig: \"/apps/system/text editor.tapp/index.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Anura File Manager\",\n\t\t\t\t\t\tconfig: \"/system/etc/anura/configs/Anura File Manager.json\",\n\t\t\t\t\t\tuser: \"System\",\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t\tconst startApps = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\")).system_apps;\n\t\t\t\tfor (const app of startApps) {\n\t\t\t\t\tlet appName = \"\";\n\t\t\t\t\tlet appTitle = \"\";\n\t\t\t\t\tif (typeof app.title === \"object\" && app.title !== null && \"text\" in app.title) {\n\t\t\t\t\t\tappTitle = app.title.text;\n\t\t\t\t\t} else if (typeof app.title === \"string\") {\n\t\t\t\t\t\tappTitle = app.title;\n\t\t\t\t\t} else if (typeof app.name === \"string\") {\n\t\t\t\t\t\tappTitle = app.name;\n\t\t\t\t\t}\n\t\t\t\t\tappName = appTitle?.toLowerCase?.() || \"\";\n\t\t\t\t\tconst isSysApp = sysapps.some(s => s.toLowerCase() === appName) || appName === \"anura file manager\" || appName === \"browser\";\n\t\t\t\t\tconst alreadyInstalled = insapps.some(a => a.name?.toLowerCase?.() === appName || a.name?.toLowerCase?.() === appTitle?.toLowerCase?.());\n\t\t\t\t\tif (!isSysApp && !alreadyInstalled) {\n\t\t\t\t\t\tlet configPath = \"/apps/\";\n\t\t\t\t\t\tif (app.name) {\n\t\t\t\t\t\t\tlet appDir = `/apps/system/${app.name}/`;\n\t\t\t\t\t\t\tlet configFile = \"\";\n\t\t\t\t\t\t\tif (await fileExists(`${appDir}.tbconfig`)) {\n\t\t\t\t\t\t\t\tconfigFile = `${appDir}.tbconfig`;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (user) {\n\t\t\t\t\t\t\t\t\tappDir = `/apps/user/${user}/${app.name}/`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tappDir = `/apps/user/${JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\")).defaultUser}/${app.name}/`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (await fileExists(`${appDir}.tbconfig`)) {\n\t\t\t\t\t\t\t\t\tconfigFile = `${appDir}.tbconfig`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconfigFile = `${appDir}index.json`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (configFile) {\n\t\t\t\t\t\t\t\tconfigPath = configFile;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinsapps.push({\n\t\t\t\t\t\t\tname: appTitle,\n\t\t\t\t\t\t\tconfig: configPath,\n\t\t\t\t\t\t\tuser: user || \"System\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tawait window.tb.fs.promises.mkdir(\"/system/etc/anura/configs/\");\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/apps/installed.json\", JSON.stringify(insapps));\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/recent.json\", JSON.stringify([]));\n\t\t\t}\n\t\t\t// v2.1 update\n\t\t\tif (!(await fileExists(`/apps/user/${user}/app store/repos.json`))) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${user}/app store/`);\n\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t`/apps/user/${user}/app store/repos.json`,\n\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"TB App Repo\",\n\t\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/TerbiumOS/tb-repo/refs/heads/main/manifest.json\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"XSTARS XTRAS\",\n\t\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/Notplayingallday383/app-repo/refs/heads/main/manifest.json\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: \"Anura App Repo\",\n\t\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/MercuryWorkshop/anura-repo/refs/heads/master/manifest.json\",\n\t\t\t\t\t\t\ticon: \"https://anura.pro/icon.png\",\n\t\t\t\t\t\t},\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!(await fileExists(`/apps/user/${user}/browser/favorites.json`))) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${user}/browser/`);\n\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/browser/favorites.json`, JSON.stringify([]));\n\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/browser/userscripts.json`, JSON.stringify([]));\n\t\t\t}\n\t\t\t// v2.2 Update\n\t\t\tfor (const user of await window.tb.fs.promises.readdir(\"/home/\")) {\n\t\t\t\tconst usrSettings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/settings.json`, \"utf8\"));\n\t\t\t\tif (!usrSettings.window) {\n\t\t\t\t\tusrSettings.window = {\n\t\t\t\t\t\twinAccent: \"#ffffff\",\n\t\t\t\t\t\tblurlevel: 18,\n\t\t\t\t\t\talwaysMaximized: false,\n\t\t\t\t\t\talwaysFullscreen: false,\n\t\t\t\t\t};\n\t\t\t\t\tusrSettings.showFPS = false;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/settings.json`, JSON.stringify(usrSettings, null, 4));\n\t\t\t\t}\n\t\t\t\t// hotfix for v2.2.1 & migration from v2.0.0-beta.x to v2.1.x\n\t\t\t\tif (await fileExists(`/home/${user}/desktop/browser.lnk`)) {\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/browser.lnk`, \"symlink:/apps/system/browser.tapp/index.json:file\");\n\t\t\t\t\tconst desktopItems = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\tconst hasBrowserShortcut = desktopItems.some((item: any) => item.name?.toLowerCase?.() === \"browser\" || item.item === `/home/${user}/desktop/browser.lnk`);\n\t\t\t\t\tif (!hasBrowserShortcut) {\n\t\t\t\t\t\tdesktopItems.push({\n\t\t\t\t\t\t\tname: \"Browser\",\n\t\t\t\t\t\t\titem: `/home/${user}/desktop/browser.lnk`,\n\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\ttop: desktopItems.length,\n\t\t\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopItems, null, 4));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!(await fileExists(\"/system/etc/terbium/taccs.json\"))) {\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify({}));\n\t\t\t}\n\t\t\tif (!(await fileExists(\"/system/var/terbium/startup.json\"))) {\n\t\t\t\tconst users = await window.tb.fs.promises.readdir(\"/home/\");\n\t\t\t\tconst startupObj = {\n\t\t\t\t\tSystem: {},\n\t\t\t\t\t...Object.fromEntries(users.map(user => [user, {}])),\n\t\t\t\t};\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/startup.json\", JSON.stringify(startupObj), \"utf8\");\n\t\t\t}\n\t\t\t// v2.3 update\n\t\t\tconst dockConfig = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/dock.json\", \"utf8\"));\n\t\t\tconst startConfig = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\ttry {\n\t\t\t\tlet changed = false;\n\t\t\t\tconst termHtml = `<div class=\"term-tab-container\">\\n<style>.term-tabs{display:flex;align-items:center;gap:6px;width:100%;height:100%;}.term-tab-list{display:flex;gap:6px;align-items:center;overflow:hidden;white-space:nowrap}.term-tab{display:inline-flex;align-items:center;gap:8px;padding:4px 10px;border-radius:8px;background:transparent;color:#fff;font-weight:700;cursor:pointer;user-select:none;border:1px solid transparent}.term-tab.active{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.08)}.term-tab .close{opacity:0.6;font-weight:600;margin-left:8px}.term-tab-controls{display:flex;gap:6px;align-items:center}.term-add{background:#ffffff0f;color:#fff;border-radius:6px;padding:2px 6px;border:none;font-weight:700;cursor:pointer}</style>\\n<div class=\"term-tabs\">\\n<div class=\"term-tab-list\" aria-hidden=\"false\"></div>\\n<div class=\"term-tab-controls\">\\n<button class=\"term-add\" title=\"New tab\">+</button>\\n</div>\\n</div>\\n</div>`;\n\t\t\t\tfunction upgradeEntry(entry: any): boolean {\n\t\t\t\t\tif (!entry || typeof entry !== \"object\") return false;\n\t\t\t\t\tlet updated = false;\n\t\t\t\t\tif (typeof entry.title === \"string\" && entry.title.toLowerCase() === \"terminal\") {\n\t\t\t\t\t\tentry.title = { text: \"Terminal\", html: termHtml };\n\t\t\t\t\t\tupdated = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (entry.title && typeof entry.title === \"object\" && entry.title.text && entry.title.text.toLowerCase() === \"terminal\" && !entry.title.html) {\n\t\t\t\t\t\tentry.title.html = termHtml;\n\t\t\t\t\t\tupdated = true;\n\t\t\t\t\t}\n\t\t\t\t\tif (entry.src && typeof entry.src === \"string\" && entry.src.includes(\"terminal.tapp\")) {\n\t\t\t\t\t\tif (typeof entry.title === \"string\") {\n\t\t\t\t\t\t\tentry.title = { text: \"Terminal\", html: termHtml };\n\t\t\t\t\t\t\tupdated = true;\n\t\t\t\t\t\t} else if (entry.title && typeof entry.title === \"object\" && !entry.title.html) {\n\t\t\t\t\t\t\tentry.title.html = termHtml;\n\t\t\t\t\t\t\tupdated = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (entry.size && typeof entry.size === \"object\" && entry.size.height === 400) {\n\t\t\t\t\t\tentry.size.height = 415;\n\t\t\t\t\t\tupdated = true;\n\t\t\t\t\t}\n\t\t\t\t\treturn updated;\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(dockConfig)) {\n\t\t\t\t\tdockConfig.forEach((e: any) => {\n\t\t\t\t\t\tif (upgradeEntry(e)) changed = true;\n\t\t\t\t\t});\n\t\t\t\t} else if (dockConfig && typeof dockConfig === \"object\") {\n\t\t\t\t\tif (Array.isArray(dockConfig.pinned_apps)) {\n\t\t\t\t\t\tdockConfig.pinned_apps.forEach((e: any) => {\n\t\t\t\t\t\t\tif (upgradeEntry(e)) changed = true;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (Array.isArray(dockConfig.apps)) {\n\t\t\t\t\t\tdockConfig.apps.forEach((e: any) => {\n\t\t\t\t\t\t\tif (upgradeEntry(e)) changed = true;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (Array.isArray(dockConfig.items)) {\n\t\t\t\t\t\tdockConfig.items.forEach((e: any) => {\n\t\t\t\t\t\t\tif (upgradeEntry(e)) changed = true;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (startConfig && Array.isArray(startConfig.system_apps)) {\n\t\t\t\t\tstartConfig.system_apps.forEach((app: any) => {\n\t\t\t\t\t\tif (upgradeEntry(app)) changed = true;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (changed) {\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/dock.json\", JSON.stringify(dockConfig, null, 4), \"utf8\");\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(startConfig, null, 4), \"utf8\");\n\t\t\t\t\tconsole.log(\"Migrated terminal entries in dock.json/start.json to new metadata\");\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.warn(\"Failed to migrate dock/start config:\", err);\n\t\t\t}\n\n\t\t\tsetProgress(80);\n\t\t\tstatusref.current!.innerText = \"Cleaning up...\";\n\t\t\tsetProgress(95);\n\t\t\tawait window.tb.sh.promises.rm(`/system/tmp/terb-upd/`, { recursive: true });\n\t\t\twindow.onbeforeunload = null;\n\t\t\tsessionStorage.setItem(\"justUpdated\", \"true\");\n\t\t\tsetProgress(100);\n\t\t\tstatusref.current!.innerText = \"Restarting...\";\n\t\t\twindow.location.reload();\n\t\t};\n\t\tconst migrateFs = async () => {\n\t\t\tasync function copyRecursive(src: string, dest: string) {\n\t\t\t\tconst entries = await Filer.fs.promises.readdir(src);\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tconst srcPath = src.endsWith(\"/\") ? src + entry : src + \"/\" + entry;\n\t\t\t\t\tconst destPath = dest.endsWith(\"/\") ? dest + entry : dest + \"/\" + entry;\n\t\t\t\t\tconst stat = await Filer.fs.promises.stat(srcPath);\n\t\t\t\t\tif (stat.isDirectory()) {\n\t\t\t\t\t\tif (!(await dirExists(destPath))) {\n\t\t\t\t\t\t\tawait window.tb.fs.promises.mkdir(destPath);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait copyRecursive(srcPath, destPath);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst fileBuffer = await Filer.fs.promises.readFile(srcPath);\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(destPath, fileBuffer);\n\t\t\t\t\t}\n\t\t\t\t\tstatusref.current!.innerText = `Copying: ${srcPath}`;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait copyRecursive(\"/\", \"/\");\n\t\t\tsetProgress(85);\n\t\t\tstatusref.current!.innerText = \"Recreating Desktop Shortcuts...\";\n\t\t\tfor (const user of await window.tb.fs.promises.readdir(\"/home/\")) {\n\t\t\t\tconst items = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tconst target = await Filer.fs.promises.readlink(item.item);\n\t\t\t\t\tawait window.tb.fs.promises.symlink(target, item.item);\n\t\t\t\t\tstatusref.current!.innerText = `Creating shortcut: ${item.name}.lnk...`;\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetProgress(93);\n\t\t\tstatusref.current!.innerText = \"Formatting Filer...\";\n\t\t\tconst fsh = new Filer.fs.Shell();\n\t\t\tfor (const loc of await Filer.fs.promises.readdir(\"//\")) {\n\t\t\t\tawait fsh.promises.rm(`/${loc}`, { recursive: true });\n\t\t\t}\n\t\t\tsetProgress(99);\n\t\t\tstatusref.current!.innerText = \"Migration complete!\";\n\t\t\tsessionStorage.removeItem(\"migrateFs\");\n\t\t\tstatusref.current!.innerText = \"Updating System Files...\";\n\t\t\tmain();\n\t\t};\n\t\tconst run = async () => {\n\t\t\tif (!sessionStorage.getItem(\"migrateFs\")) {\n\t\t\t\tconst existsOPFS = await dirExists(\"/system/etc/terbium/\");\n\t\t\t\tconst existsFiler = await Filer.fs.promises\n\t\t\t\t\t.stat(\"/system/etc/terbium/\")\n\t\t\t\t\t.then(() => true)\n\t\t\t\t\t.catch(() => false);\n\t\t\t\tif (!existsOPFS && existsFiler) {\n\t\t\t\t\tsessionStorage.setItem(\"migrateFs\", \"true\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (sessionStorage.getItem(\"migrateFs\")) {\n\t\t\t\tawait migrateFs();\n\t\t\t} else {\n\t\t\t\tawait main();\n\t\t\t}\n\t\t};\n\t\trun();\n\t}, []);\n\n\treturn (\n\t\t<div className=\"bg-[#0e0e0e] h-full justify-center items-center flex flex-col lg:h-full md:h-full\">\n\t\t\t<img src=\"/tb.svg\" alt=\"Terbium\" className=\"w-[25%] h-[25%]\" />\n\t\t\t<div className=\"duration-150 flex flex-col justify-center items-center\">\n\t\t\t\t<div className=\"text-container relative flex flex-col justify-center items-end\">\n\t\t\t\t\t<div className=\"bg-linear-to-b from-[#ffffff] to-[#ffffff77] text-transparent bg-clip-text flex flex-col lg:items-center md:items-center sm:items-center\">\n\t\t\t\t\t\t<span className=\"font-bold lg:text-[34px] md:text-[28px] sm:text-[22px] text-right duration-150\">\n\t\t\t\t\t\t\t<span className=\"font-[1000] duration-150\">Terbium is updating</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\t<p>Please DO NOT close this tab</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<p ref={statusref} className=\"mt-1\">\n\t\t\t\tDownloading Updates...\n\t\t\t</p>\n\t\t\t<div className=\"relative flex w-[30%] h-3 rounded-full bg-[#00000020] overflow-hidden mt-4\">\n\t\t\t\t<div className=\"absolute h-full bg-[#50bf66] rounded-full\" style={{ width: `${progress}%` }}></div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/index.css",
    "content": "@import \"tailwindcss\";\n\n@theme {\n\t--shadow-tb-border-shadow: 0px 0px 6px 0px #00000052, inset 0 0 0 0.5px #ffffff38;\n\t--shadow-tb-border: inset 0 0 0 0.5px #ffffff38;\n\t--shadow-window-shadow: 0px 0px 10px 1px #00000052;\n\n\t--cursor-text: var(--cursor-text);\n\t--cursor-pointer: var(--cursor-pointer);\n\t--cursor-default: var(--cursor-normal);\n\t--cursor-crosshair: var(--cursor-crosshair);\n\t--cursor-wait: var(--cursor-wait);\n\t--cursor-nw-resize: var(--cursor-nw-resize);\n\t--cursor-ne-resize: var(--cursor-ne-resize);\n\t--cursor-sw-resize: var(--cursor-sw-resize);\n\t--cursor-se-resize: var(--cursor-se-resize);\n\t--cursor-n-resize: var(--cursor-n-resize);\n\t--cursor-s-resize: var(--cursor-s-resize);\n\t--cursor-e-resize: var(--cursor-e-resize);\n\t--cursor-w-resize: var(--cursor-w-resize);\n}\n\n/*\n  The default border color has changed to `currentColor` in Tailwind CSS v4,\n  so we've added these compatibility styles to make sure everything still\n  looks the same as it did with Tailwind CSS v3.\n\n  If we ever want to remove these styles, we need to add an explicit border\n  color utility to any element that depends on these defaults.\n*/\n@layer base {\n\t*,\n\t::after,\n\t::before,\n\t::backdrop,\n\t::file-selector-button {\n\t\tborder-color: var(--color-gray-200, currentColor);\n\t}\n}\n\n/*\n  The default border color has changed to `currentColor` in Tailwind CSS v4,\n  so we've added these compatibility styles to make sure everything still\n  looks the same as it did with Tailwind CSS v3.\n\n  If we ever want to remove these styles, we need to add an explicit border\n  color utility to any element that depends on these defaults.\n*/\n@layer base {\n\t*,\n\t::after,\n\t::before,\n\t::backdrop,\n\t::file-selector-button {\n\t\tborder-color: var(--color-gray-200, currentColor);\n\t}\n}\n\n@font-face {\n\tfont-family: Inter;\n\tsrc: url(\"/fonts/Inter.ttf\") format(\"truetype\");\n}\n\n:root {\n\tfont-family: Inter;\n\tcolor-scheme: light dark;\n\tcolor: #ffffffde;\n\tbackground-color: #000000;\n\t--cursor-normal: url(\"/cursors/dark/normal.svg\") 6 0, default;\n\t--cursor-pointer: url(\"/cursors/dark/pointer.svg\") 6 0, pointer;\n\t--cursor-text: url(\"/cursors/dark/text.svg\") 10 0, text;\n\t--cursor-crosshair: url(\"/cursors/dark/crosshair.svg\") 0 0, crosshair;\n\t--cursor-wait: url(\"/cursors/dark/wait.svg\") 0 0, wait;\n\t--cursor-nw-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, nw-resize;\n\t--cursor-ne-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, ne-resize;\n\t--cursor-sw-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, sw-resize;\n\t--cursor-se-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, se-resize;\n\t--cursor-n-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, n-resize;\n\t--cursor-s-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, s-resize;\n\t--cursor-e-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, e-resize;\n\t--cursor-w-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, w-resize;\n}\n\nhtml,\nbody {\n\theight: 100%;\n\tmargin: 0;\n\tcursor: var(--cursor-normal);\n}\n\niframe {\n\tbackground: transparent !important;\n}\n\n.grainy {\n\tbackground: repeating-conic-gradient(#0000 0.000045%, #000 0.0005%);\n\tmix-blend-mode: overlay;\n}\n"
  },
  {
    "path": "src/init/fs.init.ts",
    "content": "import paths from \"../installer.json\";\nimport { unzip } from \"../sys/types\";\n\nexport async function copyfs() {\n\tfor (const item of paths) {\n\t\tconst p = item.toString();\n\t\tif (p.includes(\"browser.tapp\")) continue;\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: `Copying ${p} to File System...` }));\n\t\tif (p.toLowerCase().includes(\".tapp.zip\")) {\n\t\t\tconst res = await fetch(`/apps/${p}`);\n\t\t\tif (!res.ok) {\n\t\t\t\tconsole.error(`Failed to fetch /apps/${p}: ${res.status} ${res.statusText}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = await res.arrayBuffer();\n\t\t\tawait window.tb.fs.promises.writeFile(`/apps/system/${p}`, window.tb.buffer.from(data));\n\t\t\tawait unzip(`/apps/system/${p}`, `/apps/system/${p.slice(0, -4)}`);\n\t\t\tawait window.tb.fs.promises.unlink(`/apps/system/${p}`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (p.endsWith(\"/\")) {\n\t\t\tawait window.tb.fs.promises.mkdir(`/apps/system/${p}`);\n\t\t} else {\n\t\t\tconst res = await fetch(`/apps/${p}`);\n\t\t\tif (!res.ok) {\n\t\t\t\tconsole.error(`Failed to fetch /apps/${p}: ${res.status} ${res.statusText}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = await res.text();\n\t\t\tawait window.tb.fs.promises.writeFile(`/apps/system/${p}`, data);\n\t\t}\n\t}\n\treturn \"Success\";\n}\n"
  },
  {
    "path": "src/init/index.ts",
    "content": "import { dirExists, TAuthSSData, UserSettings } from \"../sys/types\";\nimport apps from \"../apps.json\";\nimport { copyfs } from \"./fs.init\";\nimport { hash } from \"../hash.json\";\n\nexport async function init() {\n\t/**\n\t * create home structure\n\t */\n\tconsole.log(\"Initing File System please wait...\");\n\tif (!(await dirExists(\"/home\"))) {\n\t\tawait window.tb.fs.promises.mkdir(\"/home\");\n\t}\n\tconst user = JSON.parse(`${sessionStorage.getItem(\"new-user\")}`).username;\n\n\t/**\n\t * create apps structure\n\t */\n\tif (!(await dirExists(\"/apps\"))) {\n\t\tawait window.tb.fs.promises.mkdir(\"/apps\");\n\t\tawait window.tb.fs.promises.mkdir(\"/apps/system\");\n\t\tawait copyfs();\n\t\twindow.dispatchEvent(new CustomEvent(\"oobe-setupstage\", { detail: \"Initializing File System...\" }));\n\t\tawait window.tb.fs.promises.mkdir(\"/apps/user\");\n\t\tawait window.tb.fs.promises.writeFile(\"/apps/web_apps.json\", JSON.stringify({ apps: [] }));\n\t} else {\n\t\tif (!(await dirExists(\"/apps/user\"))) {\n\t\t\tawait window.tb.fs.promises.mkdir(\"/apps/user\");\n\t\t}\n\t}\n\n\tif (!(await dirExists(`/apps/user/${user}`))) {\n\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${user}`);\n\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${user}/files`);\n\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${user}/terminal`);\n\t}\n\n\t/**\n\t * create system structure\n\t */\n\tif (!(await dirExists(\"/system\"))) {\n\t\tawait window.tb.fs.promises.mkdir(\"/system\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/trash\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/bin\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/etc\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/etc/terbium\");\n\t\tlet stockSettings = {\n\t\t\ttheme: \"dark\",\n\t\t\t\"system-blur\": true,\n\t\t\t\"dock-full\": false,\n\t\t\tfileAssociatedApps: {\n\t\t\t\ttext: \"text-editor\",\n\t\t\t\timage: \"media-viewer\",\n\t\t\t\tvideo: \"media-viewer\",\n\t\t\t\taudio: \"media-viewer\",\n\t\t\t},\n\t\t\tlocation: \"40.7831,-73.9712\",\n\t\t\tweather: {\n\t\t\t\tunit: \"Celsius\",\n\t\t\t},\n\t\t\t\"host-name\": \"terbium\",\n\t\t};\n\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(stockSettings));\n\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/sudousers.json\", JSON.stringify([]));\n\t\tawait window.tb.fs.promises.mkdir(\"/system/etc/terbium/wallpapers\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/var\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/var/terbium\");\n\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/hash.cache\", hash);\n\t\tlet startApps = {\n\t\t\tsystem_apps: apps.map(app => app.config),\n\t\t\tpinned_apps: [],\n\t\t};\n\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(startApps));\n\t\tawait window.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify([]));\n\t\tawait window.tb.fs.promises.mkdir(\"/apps/anura/\");\n\t\tlet dockPins = [\n\t\t\t{\n\t\t\t\ttitle: {\n\t\t\t\t\ttext: \"Terminal\",\n\t\t\t\t\thtml: '<div class=\"term-tab-container\">\\n<style>.term-tabs{display:flex;align-items:center;gap:6px;width:100%;height:100%;}.term-tab-list{display:flex;gap:6px;align-items:center;overflow:hidden;white-space:nowrap}.term-tab{display:inline-flex;align-items:center;gap:8px;padding:4px 10px;border-radius:8px;background:transparent;color:#fff;font-weight:700;cursor:pointer;user-select:none;border:1px solid transparent}.term-tab.active{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.08)}.term-tab .close{opacity:0.6;font-weight:600;margin-left:8px}.term-tab-controls{display:flex;gap:6px;align-items:center}.term-add{background:#ffffff0f;color:#fff;border-radius:6px;padding:2px 6px;border:none;font-weight:700;cursor:pointer}</style>\\n<div class=\"term-tabs\">\\n<div class=\"term-tab-list\" aria-hidden=\"false\"></div>\\n<div class=\"term-tab-controls\">\\n<button class=\"term-add\" title=\"New tab\">+</button>\\n</div>\\n</div>\\n</div>',\n\t\t\t\t},\n\t\t\t\ticon: \"/fs/apps/system/terminal.tapp/icon.svg\",\n\t\t\t\tisPinnable: true,\n\t\t\t\tsrc: \"/fs/apps/system/terminal.tapp/index.html\",\n\t\t\t\tsize: {\n\t\t\t\t\twidth: 612,\n\t\t\t\t\theight: 415,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttitle: \"Files\",\n\t\t\t\ticon: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\tisPinnable: true,\n\t\t\t\tsrc: \"/fs/apps/system/files.tapp/index.html\",\n\t\t\t\tsize: {\n\t\t\t\t\twidth: 600,\n\t\t\t\t\theight: 500,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\ttitle: \"Settings\",\n\t\t\t\ticon: \"/fs/apps/system/settings.tapp/icon.svg\",\n\t\t\t\tisPinnable: true,\n\t\t\t\tsrc: \"/fs/apps/system/settings.tapp/index.html\",\n\t\t\t\tsingle: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\ttitle: \"Feedback\",\n\t\t\t\ticon: \"/fs/apps/system/feedback.tapp/icon.svg\",\n\t\t\t\tproxy: true,\n\t\t\t\tisPinnable: true,\n\t\t\t\tsrc: \"https://forms.gle/m664xxmrugWQADQt9\",\n\t\t\t\tsize: {\n\t\t\t\t\twidth: 600,\n\t\t\t\t\theight: 500,\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/dock.json\", JSON.stringify(dockPins));\n\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/startup.json\", JSON.stringify({ System: {}, [user]: {} }), \"utf8\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/lib\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/lib/anura\");\n\t\tawait window.tb.fs.promises.mkdir(\"/system/tmp\");\n\n\t\tlet recentApps: any[] = [];\n\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/recent.json\", JSON.stringify(recentApps));\n\t}\n\n\tconst tcaccSettings: TAuthSSData = sessionStorage.getItem(\"tacc-settings\") ? JSON.parse(sessionStorage.getItem(\"tacc-settings\")!) : null;\n\tvar items: any[] = [];\n\n\tif (!(await dirExists(`/home/${user}`))) {\n\t\tawait window.tb.fs.promises.mkdir(`/home/${user}`);\n\t\tlet userSettings: UserSettings = {\n\t\t\twallpaper: \"/assets/wallpapers/1.png\",\n\t\t\twallpaperMode: \"cover\",\n\t\t\tanimations: true,\n\t\t\t// @ts-ignore\n\t\t\tproxy: sessionStorage.getItem(\"selectedProxy\") || \"Scramjet\",\n\t\t\ttransport: \"Default (Epoxy)\",\n\t\t\twispServer: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`,\n\t\t\t\"battery-percent\": false,\n\t\t\taccent: \"#32ae62\",\n\t\t\ttimes: {\n\t\t\t\tformat: \"12h\",\n\t\t\t\tinternet: false,\n\t\t\t\tshowSeconds: false,\n\t\t\t},\n\t\t\tshowFPS: false,\n\t\t\twindowOptimizations: false,\n\t\t\twindow: {\n\t\t\t\twinAccent: \"#ffffff\",\n\t\t\t\tblurlevel: 18,\n\t\t\t\talwaysMaximized: false,\n\t\t\t\talwaysFullscreen: false,\n\t\t\t},\n\t\t};\n\t\tif (tcaccSettings && Array.isArray(tcaccSettings) && tcaccSettings[0] && tcaccSettings[0].settings) {\n\t\t\tuserSettings = {\n\t\t\t\t...userSettings,\n\t\t\t\t...tcaccSettings[0].settings,\n\t\t\t};\n\t\t}\n\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/settings.json`, JSON.stringify(userSettings));\n\t\tawait window.tb.fs.promises.mkdir(`/home/${user}/desktop`);\n\t\tlet r2 = [];\n\t\tlet sysapps: { name: string; config: string; user: string }[] = [];\n\t\tfor (let i = 0; i < apps.length; i++) {\n\t\t\tconst app = apps[i];\n\t\t\tconst name = app.name.toLowerCase();\n\t\t\tvar topPos: number = 0;\n\t\t\tvar leftPos: number = 0;\n\t\t\tif (i % 12 === 0) {\n\t\t\t\ttopPos = 0;\n\t\t\t} else {\n\t\t\t\ttopPos = i % 12;\n\t\t\t}\n\t\t\tif (i < 12) {\n\t\t\t\tleftPos = 0;\n\t\t\t} else {\n\t\t\t\tleftPos = 1;\n\t\t\t}\n\t\t\tif (topPos * 66 > window.innerHeight - 130) {\n\t\t\t\tleftPos = 1.15;\n\t\t\t\tif (r2.length === 0) {\n\t\t\t\t\ttopPos = 0;\n\t\t\t\t} else {\n\t\t\t\t\ttopPos = r2.length % 12;\n\t\t\t\t}\n\t\t\t\tr2.push({\n\t\t\t\t\tname: app.name,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\titems.push({\n\t\t\t\tname: app.name,\n\t\t\t\titem: `/home/${user}/desktop/${name}.lnk`,\n\t\t\t\tposition: {\n\t\t\t\t\tcustom: false,\n\t\t\t\t\ttop: topPos,\n\t\t\t\t\tleft: leftPos,\n\t\t\t\t},\n\t\t\t});\n\t\t\tawait window.tb.fs.promises.mkdir(`/apps/system/${name}.tapp`);\n\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t`/apps/system/${name}.tapp/index.json`,\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: app.name,\n\t\t\t\t\tconfig: app.config,\n\t\t\t\t\ticon: app.config.icon,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tsysapps.push({\n\t\t\t\tname: app.name,\n\t\t\t\tconfig: `/apps/system/${name}.tapp/index.json`,\n\t\t\t\tuser: \"System\",\n\t\t\t});\n\t\t\tawait window.tb.fs.promises.symlink(`/apps/system/${name}.tapp/index.json`, `/home/${user}/desktop/${name}.lnk`);\n\t\t}\n\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(items));\n\t\tif (tcaccSettings && Array.isArray(tcaccSettings) && tcaccSettings[0]) {\n\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t`/apps/user/${user}/files/config.json`,\n\t\t\t\tJSON.stringify({\n\t\t\t\t\t\"quick-center\": true,\n\t\t\t\t\t\"sidebar-width\": 180,\n\t\t\t\t\tdrives: {\n\t\t\t\t\t\t\"File System\": `/home/${user}/`,\n\t\t\t\t\t\t...tcaccSettings[0].davs.reduce((acc: any, d: any) => {\n\t\t\t\t\t\t\tconst driveName = d.name || d.driveName;\n\t\t\t\t\t\t\tacc[driveName] = `/mnt/${driveName}/`;\n\t\t\t\t\t\t\treturn acc;\n\t\t\t\t\t\t}, {}),\n\t\t\t\t\t},\n\t\t\t\t\tstorage: {\n\t\t\t\t\t\t\"File System\": \"storage-device\",\n\t\t\t\t\t\tlocalStorage: \"storage-device\",\n\t\t\t\t\t},\n\t\t\t\t\t\"open-collapsibles\": {\n\t\t\t\t\t\t\"quick-center\": true,\n\t\t\t\t\t\tdrives: true,\n\t\t\t\t\t},\n\t\t\t\t\t\"show-hidden-files\": false,\n\t\t\t\t}),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/files/davs.json`, JSON.stringify(tcaccSettings[0].davs, null, 2));\n\t\t} else {\n\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t`/apps/user/${user}/files/config.json`,\n\t\t\t\tJSON.stringify({\n\t\t\t\t\t\"quick-center\": true,\n\t\t\t\t\t\"sidebar-width\": 180,\n\t\t\t\t\tdrives: {\n\t\t\t\t\t\t\"File System\": `/home/${user}/`,\n\t\t\t\t\t},\n\t\t\t\t\tstorage: {\n\t\t\t\t\t\t\"File System\": \"storage-device\",\n\t\t\t\t\t\tlocalStorage: \"storage-device\",\n\t\t\t\t\t},\n\t\t\t\t\t\"open-collapsibles\": {\n\t\t\t\t\t\t\"quick-center\": true,\n\t\t\t\t\t\tdrives: true,\n\t\t\t\t\t},\n\t\t\t\t\t\"show-hidden-files\": false,\n\t\t\t\t}),\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/files/davs.json`, JSON.stringify([]));\n\t\t}\n\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${user}/browser`);\n\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/browser/favorites.json`, JSON.stringify([]));\n\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/browser/userscripts.json`, JSON.stringify([]));\n\t\tawait window.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(sysapps));\n\t\tconst response = await fetch(\"/apps/files.tapp/icons.json\");\n\t\tconst dat = await response.json();\n\t\tconst iconNames = Object.keys(dat[\"name-to-path\"]);\n\t\tvar iconArrays: { [key: string]: string } = {};\n\n\t\tawait window.tb.fs.promises.mkdir(`/system/etc/terbium/file-icons`);\n\t\tfor (const name of iconNames) {\n\t\t\tconst path = `/system/etc/terbium/file-icons/${name}.svg`;\n\t\t\ticonArrays[name] = path;\n\t\t\tconst icon = dat[\"name-to-path\"][name];\n\t\t\tawait window.tb.fs.promises.writeFile(path, icon as any);\n\t\t}\n\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t`/system/etc/terbium/file-icons.json`,\n\t\t\tJSON.stringify({\n\t\t\t\t\"ext-to-name\": dat[\"ext-to-name\"],\n\t\t\t\t\"name-to-path\": iconArrays,\n\t\t\t}),\n\t\t);\n\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t`/apps/user/${user}/files/quick-center.json`,\n\t\t\tJSON.stringify({\n\t\t\t\tpaths: {\n\t\t\t\t\tDesktop: `/home/${user}/desktop`,\n\t\t\t\t\tDocuments: `/home/${user}/documents`,\n\t\t\t\t\tImages: `/home/${user}/images`,\n\t\t\t\t\tVideos: `/home/${user}/videos`,\n\t\t\t\t\tMusic: `/home/${user}/music`,\n\t\t\t\t\tTrash: `/system/trash`,\n\t\t\t\t},\n\t\t\t}),\n\t\t\t\"utf8\",\n\t\t);\n\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/terminal/info.json`, JSON.stringify({}));\n\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${user}/app store/`);\n\t\tif (tcaccSettings && Array.isArray(tcaccSettings) && tcaccSettings[0].apps) {\n\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${user}/app store/repos.json`, JSON.stringify(tcaccSettings[0].apps.repos), \"utf8\");\n\t\t} else {\n\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t`/apps/user/${user}/app store/repos.json`,\n\t\t\t\tJSON.stringify([\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"TB App Repo\",\n\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/TerbiumOS/tb-repo/refs/heads/main/manifest.json\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"XSTARS XTRAS\",\n\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/Notplayingallday383/app-repo/refs/heads/main/manifest.json\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"Anura App Repo\",\n\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/MercuryWorkshop/anura-repo/refs/heads/master/manifest.json\",\n\t\t\t\t\t\ticon: \"https://anura.pro/icon.png\",\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\t}\n\treturn true;\n}\n"
  },
  {
    "path": "src/main.tsx",
    "content": "import { StrictMode, useEffect, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport App from \"./App.tsx\";\nimport \"./index.css\";\nimport { BareMuxConnection } from \"@mercuryworkshop/bare-mux\";\nimport Boot from \"./Boot.tsx\";\nimport CustomOS from \"./CustomOS.tsx\";\nimport { hash } from \"./hash.json\";\nimport Loader from \"./Loading.tsx\";\nimport Login from \"./Login.tsx\";\nimport Recovery from \"./Recovery.tsx\";\nimport Setup from \"./Setup.tsx\";\nimport { fileExists } from \"./sys/types.ts\";\nimport Updater from \"./Updater.tsx\";\n\nconst Root = () => {\n\tconst [currPag, setPag] = useState(<Loader />);\n\tconst params = new URLSearchParams(window.location.search);\n\tuseEffect(() => {\n\t\tconst tempTransport = async () => {\n\t\t\tconst connection = new BareMuxConnection(\"/baremux/worker.js\");\n\t\t\tawait connection.setTransport(\"/epoxy/index.mjs\", [{ wisp: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/` }]);\n\t\t\tconst tbOn = async () => {\n\t\t\t\twhile (!window.tb.system?.version) {\n\t\t\t\t\tawait new Promise(res => setTimeout(res, 50));\n\t\t\t\t}\n\t\t\t\twindow.dispatchEvent(new Event(\"tfsready\"));\n\t\t\t};\n\t\t\ttbOn();\n\t\t\tconst { ScramjetController } = $scramjetLoadController();\n\t\t\twindow.scramjetTb = {\n\t\t\t\tprefix: \"/service/\",\n\t\t\t\tfiles: {\n\t\t\t\t\twasm: \"/scram/scramjet.wasm.wasm\",\n\t\t\t\t\tall: \"/scram/scramjet.all.js\",\n\t\t\t\t\tsync: \"/scram/scramjet.sync.js\",\n\t\t\t\t},\n\t\t\t\tdefaultFlags: {\n\t\t\t\t\trewriterLogs: false,\n\t\t\t\t},\n\t\t\t\tcodec: {\n\t\t\t\t\tencode: function encode(input: string): string {\n\t\t\t\t\t\tlet result = \"\";\n\t\t\t\t\t\tlet len = input.length;\n\t\t\t\t\t\tfor (let i = 0; i < len; i++) {\n\t\t\t\t\t\t\tconst char = input[i];\n\t\t\t\t\t\t\tresult += i % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn encodeURIComponent(result);\n\t\t\t\t\t},\n\t\t\t\t\tdecode: function decode(input: string): string {\n\t\t\t\t\t\tif (!input) return input;\n\t\t\t\t\t\tinput = decodeURIComponent(input);\n\t\t\t\t\t\tlet result = \"\";\n\t\t\t\t\t\tlet len = input.length;\n\t\t\t\t\t\tfor (let i = 0; i < len; i++) {\n\t\t\t\t\t\t\tconst char = input[i];\n\t\t\t\t\t\t\tresult += i % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t\twindow.scramjet = new ScramjetController(scramjetTb);\n\t\t\tscramjet.init();\n\t\t\tnavigator.serviceWorker.register(\"/anura-sw.js\");\n\t\t};\n\t\ttempTransport();\n\t\tif (sessionStorage.getItem(\"recovery\")) {\n\t\t\tsetPag(<Recovery />);\n\t\t} else if (sessionStorage.getItem(\"boot\") || params.get(\"boot\")) {\n\t\t\tconst upd = async () => {\n\t\t\t\tlet sha;\n\t\t\t\tif (await fileExists(\"/system/etc/terbium/hash.cache\")) {\n\t\t\t\t\tsha = await window.tb.fs.promises.readFile(\"/system/etc/terbium/hash.cache\", \"utf8\");\n\t\t\t\t} else {\n\t\t\t\t\tsha = hash;\n\t\t\t\t}\n\t\t\t\tif (localStorage.getItem(\"setup\")) {\n\t\t\t\t\tif (localStorage.getItem(\"setup\") && (sha !== hash || sessionStorage.getItem(\"migrateFs\"))) {\n\t\t\t\t\t\tsetPag(<Updater />);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (sessionStorage.getItem(\"logged-in\") && sessionStorage.getItem(\"logged-in\") === \"true\") {\n\t\t\t\t\t\t\tsetPag(<App />);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetPag(<Login />);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsetPag(<Setup />);\n\t\t\t\t}\n\t\t\t};\n\t\t\tupd();\n\t\t} else if (sessionStorage.getItem(\"cusboot\")) {\n\t\t\tsetPag(<CustomOS />);\n\t\t} else {\n\t\t\tsetPag(<Boot />);\n\t\t}\n\t}, []);\n\treturn currPag;\n};\n\ncreateRoot(document.getElementById(\"root\")!).render(\n\t<StrictMode>\n\t\t<Root />\n\t</StrictMode>,\n);\n"
  },
  {
    "path": "src/sys/Api.ts",
    "content": "import { BareMuxConnection } from \"@mercuryworkshop/bare-mux\";\nimport type { ScramjetController } from \"@mercuryworkshop/scramjet\";\nimport * as fflate from \"fflate\";\nimport { libcurl } from \"libcurl.js\";\nimport apps from \"../apps.json\";\nimport { hash } from \"../hash.json\";\nimport pwd from \"./apis/Crypto\";\nimport { setDialogFn } from \"./apis/Dialogs\";\nimport { hideFn, isExistingFn, setMusicFn, setVideoFn } from \"./apis/Mediaisland\";\nimport { dismissNotifFn, setNotifFn } from \"./apis/Notifications\";\nimport { registry } from \"./apis/Registry\";\nimport { System } from \"./apis/System\";\nimport { XOR } from \"./apis/Xor\";\nimport { type AppIslandProps, clearControls, clearInfo, updateControls } from \"./gui/AppIsland\";\nimport type { TDockItem } from \"./gui/Dock\";\nimport { createWindow } from \"./gui/WindowArea\";\nimport { Lemonade } from \"./lemonade\";\nimport { AliceWM } from \"./liquor/AliceWM\";\nimport { Anura } from \"./liquor/Anura\";\nimport { LocalFS } from \"./liquor/api/LocalFS\";\nimport { AnuraBareClient } from \"./liquor/bcc\";\nimport { ExternalApp } from \"./liquor/coreapps/ExternalApp\";\nimport { ExternalLib } from \"./liquor/libs/ExternalLib\";\nimport { initializeWebContainer } from \"./Node/runtimes/Webcontainers/nodeProc\";\nimport parse from \"./Parser\";\nimport { useWindowStore } from \"./Store\";\nimport { type COM, type cmprops, type dialogProps, fileExists, type launcherProps, type MediaProps, type NotificationProps, type SysSettings, type User, type UserSettings, type WindowConfig } from \"./types\";\nimport { vFS } from \"./vFS\";\nimport { auth, getinfo, setinfo } from \"./apis/utils/tauth\";\nimport { launchProcs, addStartupProc, removeStartupProc, enableProc, disableProc } from \"./apis/utils/startupHandler\";\n\nconst system = new System();\nconst pw = new pwd();\ndeclare const tb: COM;\ndeclare global {\n\tinterface Window {\n\t\ttb: COM;\n\t\tFiler: FilerType;\n\t\tScramjetController: ScramjetController;\n\t}\n\tvar scramjetTb: any;\n\tvar scramjet: ScramjetController;\n}\n\nexport default async function Api() {\n\twindow.tb = {\n\t\tregistry: registry,\n\t\tsh: window.tb.sh,\n\t\tbuffer: window.tb.buffer,\n\t\tbattery: {\n\t\t\tasync showPercentage() {\n\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\tsettings[\"battery-percent\"] = true;\n\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"controlBatteryPercentVisibility\", { detail: true }));\n\t\t\t\treturn \"Success\";\n\t\t\t},\n\t\t\tasync hidePercentage() {\n\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\tsettings[\"battery-percent\"] = false;\n\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"controlBatteryPercentVisibility\", { detail: false }));\n\t\t\t\treturn \"Success\";\n\t\t\t},\n\t\t\tasync canUse() {\n\t\t\t\tif (\"BatteryManager\" in window) {\n\t\t\t\t\tconst battery = await navigator.getBattery();\n\t\t\t\t\treturn battery ? true : false;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t},\n\t\t},\n\t\tlauncher: {\n\t\t\tasync addApp(props: launcherProps) {\n\t\t\t\tconst apps: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\t\tif (!props.name) throw new Error(\"Name is required\");\n\t\t\t\tif (!props.icon) throw new Error(\"Icon is required\");\n\t\t\t\tif (apps.system_apps.some((app: any) => app.title === props.name)) {\n\t\t\t\t\tthrow new Error(\"App with the same name already exists\");\n\t\t\t\t}\n\t\t\t\tapps.system_apps.push(props);\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(apps, null, 2));\n\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\tasync removeApp(name: string) {\n\t\t\t\tif (!name) throw new Error(\"Name is required\");\n\t\t\t\tconst data: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\t\tconst apps = data.system_apps;\n\t\t\t\tconst realName = String(name)\n\t\t\t\t\t.replace(/[^a-zA-Z0-9]/g, \"\")\n\t\t\t\t\t.toLowerCase();\n\t\t\t\tconst appIndex = apps.findIndex((app: any) => {\n\t\t\t\t\tconst n = app.name ? app.name.replace(/[^a-zA-Z0-9]/g, \"\").toLowerCase() : \"\";\n\t\t\t\t\treturn n === realName;\n\t\t\t\t});\n\t\t\t\tif (appIndex !== -1) {\n\t\t\t\t\tapps.splice(appIndex, 1);\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(`App with name '${name}' not found`);\n\t\t\t\t}\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(data, null, 2));\n\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t\ttheme: {\n\t\t\tasync get() {\n\t\t\t\treturn JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"theme\"];\n\t\t\t},\n\t\t\tasync set(data: string) {\n\t\t\t\treturn new Promise(async resolve => {\n\t\t\t\t\tconst settings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\tsettings[\"theme\"] = data;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(settings), \"utf8\");\n\t\t\t\t\tresolve(true);\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t\tdesktop: {\n\t\t\tpreferences: {\n\t\t\t\tasync setTheme(color: string) {\n\t\t\t\t\tcolor.toString().includes('\"') ? (color = color.replace(/\"/g, \"\")) : (color = color);\n\t\t\t\t\tdocument.body.setAttribute(\"theme\", color);\n\t\t\t\t\tconst settings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\tsettings[\"theme\"] = color;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(settings), \"utf8\");\n\t\t\t\t},\n\t\t\t\tasync theme() {\n\t\t\t\t\tconst settings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\treturn settings[\"theme\"];\n\t\t\t\t},\n\t\t\t\tasync setAccent(color: string) {\n\t\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\t\tsettings[\"accent\"] = color;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings), \"utf8\");\n\t\t\t\t},\n\t\t\t\tasync getAccent() {\n\t\t\t\t\treturn JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"))[\"accent\"];\n\t\t\t\t},\n\t\t\t},\n\t\t\twallpaper: {\n\t\t\t\tasync set(path: string) {\n\t\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\t\tsettings[\"wallpaper\"] = path;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings));\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updWallpaper\"));\n\t\t\t\t},\n\t\t\t\tasync contain() {\n\t\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\t\tsettings[\"wallpaperMode\"] = \"contain\";\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings), \"utf8\");\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updWallpaper\"));\n\t\t\t\t},\n\t\t\t\tasync stretch() {\n\t\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\t\tsettings[\"wallpaperMode\"] = \"stretch\";\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings), \"utf8\");\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updWallpaper\"));\n\t\t\t\t},\n\t\t\t\tasync cover() {\n\t\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\t\tsettings[\"wallpaperMode\"] = \"cover\";\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings), \"utf8\");\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updWallpaper\"));\n\t\t\t\t},\n\t\t\t\tasync fillMode() {\n\t\t\t\t\treturn new Promise(async resolve => {\n\t\t\t\t\t\tresolve(JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"))[\"wallpaperMode\"]);\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t\tdock: {\n\t\t\t\tasync pin(app: TDockItem) {\n\t\t\t\t\tconst apps: Array<TDockItem> = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/dock.json\"));\n\t\t\t\t\tapps.push(app);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/dock.json\", JSON.stringify(apps));\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updPins\"));\n\t\t\t\t\treturn \"Success\";\n\t\t\t\t},\n\t\t\t\tasync unpin(app: string) {\n\t\t\t\t\tlet apps: Array<TDockItem> = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/dock.json\"));\n\t\t\t\t\tconst appExists = apps.some(appIndex => appIndex.title === app);\n\t\t\t\t\tif (!appExists) {\n\t\t\t\t\t\tthrow new Error(`App with title \"${app}\" not found in the dock.`);\n\t\t\t\t\t}\n\t\t\t\t\tapps = apps.filter(appIndex => appIndex.title !== app);\n\t\t\t\t\tapps.filter(appIndex => appIndex.title !== app);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/dock.json\", JSON.stringify(apps));\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updPins\"));\n\t\t\t\t\treturn \"Success\";\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\twindow: {\n\t\t\tgetId() {\n\t\t\t\treturn useWindowStore.getState().currentPID;\n\t\t\t},\n\t\t\tcreate(props: any) {\n\t\t\t\tcreateWindow(props);\n\t\t\t},\n\t\t\tcontent: {\n\t\t\t\tget() {\n\t\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t\tconst getContent = (e: CustomEvent) => {\n\t\t\t\t\t\t\twindow.removeEventListener(\"curr-win-content\", getContent as EventListener);\n\t\t\t\t\t\t\tresolve(e.detail);\n\t\t\t\t\t\t};\n\t\t\t\t\t\twindow.addEventListener(\"curr-win-content\", getContent as EventListener);\n\t\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"get-content\", { detail: useWindowStore.getState().currentPID }));\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tset(html: string | HTMLElement) {\n\t\t\t\t\tconst msg = {\n\t\t\t\t\t\tcurrWin: useWindowStore.getState().currentPID,\n\t\t\t\t\t\tcontent: html,\n\t\t\t\t\t};\n\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"upd-wincont\", { detail: JSON.stringify(msg) }));\n\t\t\t\t},\n\t\t\t},\n\t\t\ttitlebar: {\n\t\t\t\tsetColor(hex: string) {\n\t\t\t\t\tconst msg = {\n\t\t\t\t\t\tcurrWin: useWindowStore.getState().currentPID,\n\t\t\t\t\t\tcolor: hex,\n\t\t\t\t\t};\n\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"upd-winbarcol\", { detail: JSON.stringify(msg) }));\n\t\t\t\t},\n\t\t\t\tsetText(text: string) {\n\t\t\t\t\tconst msg = {\n\t\t\t\t\t\tcurrWin: useWindowStore.getState().currentPID,\n\t\t\t\t\t\ttxt: text,\n\t\t\t\t\t};\n\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"upd-winbartxt\", { detail: JSON.stringify(msg) }));\n\t\t\t\t},\n\t\t\t\tsetBackgroundColor(hex: string) {\n\t\t\t\t\tconst msg = {\n\t\t\t\t\t\tcurrWin: useWindowStore.getState().currentPID,\n\t\t\t\t\t\tcolor: hex,\n\t\t\t\t\t};\n\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"upd-winbarbg\", { detail: JSON.stringify(msg) }));\n\t\t\t\t},\n\t\t\t},\n\t\t\tisland: {\n\t\t\t\taddControl(args: AppIslandProps) {\n\t\t\t\t\tif (!args.text) throw new Error(\"text is required\");\n\t\t\t\t\tif (!args.click) throw new Error(\"click function is required\");\n\t\t\t\t\tif (!args.appname) throw new Error(\"appname is required\");\n\t\t\t\t\tif (!args.id) throw new Error(\"control_id is required\");\n\t\t\t\t\tupdateControls({\n\t\t\t\t\t\ttext: args.text,\n\t\t\t\t\t\tappname: args.appname,\n\t\t\t\t\t\tid: args.id,\n\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\tif (args.click) {\n\t\t\t\t\t\t\t\targs.click();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tremoveControl(control_id: string) {\n\t\t\t\t\tif (!control_id) throw new Error(\"control_id is required\");\n\t\t\t\t\tclearControls(control_id);\n\t\t\t\t},\n\t\t\t},\n\t\t\tchangeSrc(src: string) {\n\t\t\t\tconst currWin = useWindowStore.getState().currentPID;\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"upd-src\", { detail: JSON.stringify({ pid: currWin, url: src }) }));\n\t\t\t},\n\t\t\treload() {\n\t\t\t\tconst currWin = useWindowStore.getState().currentPID;\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"reload-win\", { detail: currWin }));\n\t\t\t},\n\t\t\tminimize() {\n\t\t\t\tconst currWin = useWindowStore.getState().currentPID;\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"min-win\", { detail: currWin }));\n\t\t\t},\n\t\t\tmaximize() {\n\t\t\t\tconst currWin = useWindowStore.getState().currentPID;\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"max-win\", { detail: currWin }));\n\t\t\t},\n\t\t\tclose() {\n\t\t\t\tconst currWin = useWindowStore.getState().currentPID;\n\t\t\t\tclearInfo();\n\t\t\t\tuseWindowStore.getState().killWindow(currWin);\n\t\t\t},\n\t\t},\n\t\tcontextmenu: {\n\t\t\tcreate(props: cmprops) {\n\t\t\t\tlet adjustedX = props.x;\n\t\t\t\tlet adjustedY = props.y;\n\t\t\t\tlet shouldAdjust = false;\n\t\t\t\tif (props.iframe === true) {\n\t\t\t\t\tshouldAdjust = true;\n\t\t\t\t} else if (props.iframe === false) {\n\t\t\t\t\tshouldAdjust = false;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst stack = new Error().stack || \"\";\n\t\t\t\t\t\tconst isCalledFromIframe =\n\t\t\t\t\t\t\tstack.includes(\"about:srcdoc\") ||\n\t\t\t\t\t\t\t/at\\s+.*?\\/apps\\/.*?\\.tapp\\//.test(stack) ||\n\t\t\t\t\t\t\tstack.split(\"\\n\").some(line => {\n\t\t\t\t\t\t\t\treturn line.includes(\"blob:\") || line.includes(\"/apps/\");\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\tshouldAdjust = isCalledFromIframe;\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.warn(\"Could not detect iframe context for context menu:\", err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (shouldAdjust) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst currentPID = useWindowStore.getState().currentPID;\n\t\t\t\t\t\tconst windows = useWindowStore.getState().windows;\n\t\t\t\t\t\tif (currentPID && windows) {\n\t\t\t\t\t\t\tconst windowConfig = windows.find((w: any) => w.pid === currentPID);\n\t\t\t\t\t\t\tif (windowConfig?.wid && windowConfig?.src) {\n\t\t\t\t\t\t\t\tconst windowElement = document.getElementById(windowConfig.wid);\n\t\t\t\t\t\t\t\tif (windowElement) {\n\t\t\t\t\t\t\t\t\tconst iframe = windowElement.querySelector(\"iframe\");\n\t\t\t\t\t\t\t\t\tif (iframe) {\n\t\t\t\t\t\t\t\t\t\tconst rect = iframe.getBoundingClientRect();\n\t\t\t\t\t\t\t\t\t\tadjustedX = props.x + rect.left;\n\t\t\t\t\t\t\t\t\t\tadjustedY = props.y + rect.top;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.warn(\"Could not calculate iframe offset for context menu:\", err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twindow.dispatchEvent(\n\t\t\t\t\tnew CustomEvent(\"ctxm\", {\n\t\t\t\t\t\tdetail: {\n\t\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\t\ttitlebar: props.titlebar || false,\n\t\t\t\t\t\t\t\tx: adjustedX,\n\t\t\t\t\t\t\t\ty: adjustedY,\n\t\t\t\t\t\t\t\toptions: props.options,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t},\n\t\t\tclose() {\n\t\t\t\twindow.dispatchEvent(new Event(\"close-ctxm\"));\n\t\t\t},\n\t\t},\n\t\tuser: {\n\t\t\tasync username() {\n\t\t\t\ttry {\n\t\t\t\t\tconst username = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"))[\"username\"];\n\t\t\t\t\treturn username || \"Guest\";\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Error Fetching username:\", error);\n\t\t\t\t\treturn \"Guest\";\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync pfp() {\n\t\t\t\ttry {\n\t\t\t\t\treturn JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/user.json`, \"utf8\"))[\"pfp\"] || \"/assets/img/defualt - blue.png\";\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Error Fetching pfp:\", error);\n\t\t\t\t\treturn \"/assets/img/defualt - blue.png\";\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tproxy: {\n\t\t\tasync get() {\n\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\treturn settings[\"proxy\"];\n\t\t\t},\n\t\t\tasync set(proxy: \"Ultraviolet\" | \"Scramjet\") {\n\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\t\t\tsettings[\"proxy\"] = proxy;\n\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, JSON.stringify(settings, null, 2), \"utf8\");\n\t\t\t\twindow.tb.proxy.updateSWs();\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\tasync updateSWs() {\n\t\t\t\tawait navigator.serviceWorker.getRegistrations().then(registrations => {\n\t\t\t\t\tregistrations.forEach(registration => {\n\t\t\t\t\t\tregistration.unregister().catch(error => {\n\t\t\t\t\t\t\tconsole.error(\"Error unregistering service worker:\", error);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tconst request = indexedDB.open(\"$scramjet\");\n\t\t\t\trequest.onsuccess = () => {\n\t\t\t\t\tconst db = request.result;\n\t\t\t\t\tif (db.objectStoreNames.length === 0) {\n\t\t\t\t\t\tdb.close();\n\t\t\t\t\t\tconst deleteRequest = indexedDB.deleteDatabase(\"$scramjet\");\n\t\t\t\t\t\tdeleteRequest.onsuccess = () => {\n\t\t\t\t\t\t\tconsole.log(\"Cleared SJ DB\");\n\t\t\t\t\t\t};\n\t\t\t\t\t\tdeleteRequest.onerror = err => {\n\t\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(\"Scramjet is fine\");\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\trequest.onerror = err => {\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t};\n\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`));\n\t\t\t\tconst updateTransport = async () => {\n\t\t\t\t\tconst wispserver = settings.wispServer || `${window.location.origin.replace(/^https?:\\/\\//, \"ws://\")}/wisp/`;\n\t\t\t\t\tconst connection = new BareMuxConnection(\"/baremux/worker.js\");\n\t\t\t\t\tif (settings.transport === \"Default (Epoxy)\") {\n\t\t\t\t\t\tawait connection.setTransport(\"/epoxy/index.mjs\", [{ wisp: wispserver }]);\n\t\t\t\t\t} else if (settings.transport === \"Anura BCC\") {\n\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\tawait connection.setRemoteTransport(new AnuraBareClient(), \"AnuraBareClient\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait connection.setTransport(\"/libcurl/index.mjs\", [{ wisp: wispserver }]);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst { ScramjetController } = $scramjetLoadController();\n\t\t\t\twindow.scramjet = new ScramjetController(window.scramjetTb);\n\t\t\t\tscramjet.init();\n\t\t\t\tnavigator.serviceWorker\n\t\t\t\t\t.register(\"anura-sw.js\", {\n\t\t\t\t\t\tscope: \"/\",\n\t\t\t\t\t})\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tupdateTransport();\n\t\t\t\t\t});\n\t\t\t\tnavigator.serviceWorker.ready.then(async () => {\n\t\t\t\t\tawait updateTransport();\n\t\t\t\t});\n\t\t\t\tif (settings.wispServer === null) {\n\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\twindow.tb.libcurl.set_websocket(`${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`);\n\t\t\t\t} else {\n\t\t\t\t\twindow.tb.libcurl.set_websocket(settings.wispServer);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\tasync encode(url: string, encoder: string) {\n\t\t\t\tif (encoder === \"xor\" || encoder === \"XOR\") {\n\t\t\t\t\tconst enc = await XOR.encode(url);\n\t\t\t\t\treturn enc;\n\t\t\t\t}\n\t\t\t\tthrow new Error(\"Encoder not found\");\n\t\t\t\t// Stubbed for future addition of say AES\n\t\t\t},\n\t\t\tasync decode(url: string, decoder: string) {\n\t\t\t\tif (decoder === \"xor\" || decoder === \"XOR\") {\n\t\t\t\t\tconst dec = await XOR.decode(url);\n\t\t\t\t\treturn dec;\n\t\t\t\t}\n\t\t\t\tthrow new Error(\"Encoder not found\");\n\t\t\t\t// Stubbed for future addition of say AES\n\t\t\t},\n\t\t},\n\t\tnotification: {\n\t\t\tMessage(props: NotificationProps) {\n\t\t\t\tsetNotifFn(\"message\", props);\n\t\t\t},\n\t\t\tToast(props: NotificationProps) {\n\t\t\t\tsetNotifFn(\"toast\", props);\n\t\t\t},\n\t\t\tInstalling<T>(props: NotificationProps, task?: Promise<T> | (() => Promise<T>), doneToast?: Partial<NotificationProps> | null, failToast?: Partial<NotificationProps> | null) {\n\t\t\t\tif (!task) {\n\t\t\t\t\tsetNotifFn(\"installing\", props);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst notifId = setNotifFn(\"installing\", props);\n\t\t\t\tconst runTask = typeof task === \"function\" ? task() : task;\n\t\t\t\treturn Promise.resolve(runTask)\n\t\t\t\t\t.then(result => {\n\t\t\t\t\t\tdismissNotifFn(notifId);\n\t\t\t\t\t\tif (doneToast !== null) {\n\t\t\t\t\t\t\tsetNotifFn(\"toast\", {\n\t\t\t\t\t\t\t\tapplication: doneToast?.application || props.application,\n\t\t\t\t\t\t\t\ticonSrc: doneToast?.iconSrc || props.iconSrc,\n\t\t\t\t\t\t\t\tmessage: doneToast?.message || `${props.message} complete`,\n\t\t\t\t\t\t\t\ttime: doneToast?.time,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t})\n\t\t\t\t\t.catch(error => {\n\t\t\t\t\t\tdismissNotifFn(notifId);\n\t\t\t\t\t\tif (failToast !== null) {\n\t\t\t\t\t\t\tsetNotifFn(\"toast\", {\n\t\t\t\t\t\t\t\tapplication: failToast?.application || props.application,\n\t\t\t\t\t\t\t\ticonSrc: failToast?.iconSrc || props.iconSrc,\n\t\t\t\t\t\t\t\tmessage: failToast?.message || `${props.message} failed`,\n\t\t\t\t\t\t\t\ttime: failToast?.time,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t});\n\t\t\t},\n\t\t},\n\t\tdialog: {\n\t\t\tAlert(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"alert\", props);\n\t\t\t},\n\t\t\tMessage(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"message\", props);\n\t\t\t},\n\t\t\tSelect(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"select\", props);\n\t\t\t},\n\t\t\tAuth(props: dialogProps, options: { sudo: boolean }) {\n\t\t\t\tsetDialogFn(\"auth\", props, options);\n\t\t\t},\n\t\t\tPermissions(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"permissions\", props);\n\t\t\t},\n\t\t\tFileBrowser(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"filebrowser\", props);\n\t\t\t},\n\t\t\tDirectoryBrowser(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"directorybrowser\", props);\n\t\t\t},\n\t\t\tSaveFile(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"savefile\", props);\n\t\t\t},\n\t\t\tCropper(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"cropper\", props);\n\t\t\t},\n\t\t\tWebAuth(props: dialogProps) {\n\t\t\t\tsetDialogFn(\"webauth\", props);\n\t\t\t},\n\t\t},\n\t\tsystem: {\n\t\t\tversion: () => {\n\t\t\t\treturn system.version(\"string\");\n\t\t\t},\n\t\t\tinstance: system.instance,\n\t\t\topenApp: async (pkg: string) => {\n\t\t\t\tconst apps = JSON.parse(await window.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\t\tconst app = apps.find((a: any) => a.name.toLowerCase() === pkg.toLowerCase());\n\t\t\t\tif (!app) throw new Error(`App \"${pkg}\" not found`);\n\t\t\t\tlet type: \"anura\" | \"terbium\";\n\t\t\t\tif (app.config.endsWith(\"manifest.json\") || app.config.endsWith(`${pkg}.json`)) {\n\t\t\t\t\ttype = \"anura\";\n\t\t\t\t} else {\n\t\t\t\t\ttype = \"terbium\";\n\t\t\t\t}\n\t\t\t\tlet config: any;\n\t\t\t\tif (type === \"anura\") {\n\t\t\t\t\tlet aPath = app.config;\n\t\t\t\t\tif (aPath.endsWith(\"manifest.json\")) {\n\t\t\t\t\t\taPath = aPath.replace(/manifest\\.json$/, \"\");\n\t\t\t\t\t\tconfig = JSON.parse(await window.tb.fs.promises.readFile(app.config, \"utf8\"));\n\t\t\t\t\t} else if (aPath.endsWith(`${pkg}.json`)) {\n\t\t\t\t\t\tconfig = JSON.parse(await window.tb.fs.promises.readFile(app.config, \"utf8\")).manifest;\n\t\t\t\t\t\taPath = aPath.replace(new RegExp(`${pkg}\\\\.json$`), \"\");\n\t\t\t\t\t}\n\t\t\t\t\tconst src = config.index || config.src || \"index.html\";\n\t\t\t\t\tconfig.title = config.name;\n\t\t\t\t\tconfig.src = src.startsWith(\".\") || src.startsWith(\"/\") ? `/fs${aPath}${src.replace(/^\\.\\//, \"\")}` : `/fs${aPath}${src}`;\n\t\t\t\t\tconfig.icon = config.icon ? (config.icon.startsWith(\".\") || config.icon.startsWith(\"/\") ? `/fs${aPath}${config.icon.replace(/^\\.\\//, \"\")}` : `/fs${aPath}${config.icon}`) : undefined;\n\t\t\t\t} else {\n\t\t\t\t\tconst conf: WindowConfig = JSON.parse(await window.tb.fs.promises.readFile(app.config, \"utf8\")).wmArgs || JSON.parse(await window.tb.fs.promises.readFile(app.config, \"utf8\")).config;\n\t\t\t\t\tconst aPath = app.config.replace(/[^/]+$/, \"\");\n\t\t\t\t\tlet src = conf.src || \"index.html\";\n\t\t\t\t\tif (src.startsWith(\"./\")) {\n\t\t\t\t\t\tsrc = `/fs${aPath}${src.slice(2)}`;\n\t\t\t\t\t} else if (!src.startsWith(\"/\")) {\n\t\t\t\t\t\tsrc = `/fs${aPath}${src}`;\n\t\t\t\t\t}\n\t\t\t\t\tconf.src = src;\n\t\t\t\t\tlet icon = conf.icon || \"icon.png\";\n\t\t\t\t\tif (icon.startsWith(\"./\")) {\n\t\t\t\t\t\ticon = `/fs${aPath}${icon.slice(2)}`;\n\t\t\t\t\t} else if (!icon.startsWith(\"/\")) {\n\t\t\t\t\t\ticon = `/fs${aPath}${icon}`;\n\t\t\t\t\t}\n\t\t\t\t\tconf.icon = icon;\n\t\t\t\t\tconfig = conf;\n\t\t\t\t}\n\t\t\t\twindow.tb.window.create(config);\n\t\t\t},\n\t\t\tdownload: async (url: string, location: string) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst response: Response = await window.tb.libcurl.fetch(url);\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Failed to download the file. Status: ${response.status}`);\n\t\t\t\t\t}\n\t\t\t\t\tconst content = await response.arrayBuffer();\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(location, window.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t\t\tconsole.log(`File saved successfully at: ${location}`);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(error);\n\t\t\t\t}\n\t\t\t},\n\t\t\texportfs: async (startPath = \"/\", filename = \"tbfs.backup.zip\") => {\n\t\t\t\tconst files: Record<string, Uint8Array> = {};\n\t\t\t\tconst normalizeZipPath = (p: string) => p.replace(/^\\/+/, \"\");\n\t\t\t\tconst toUint8 = (raw: any): Uint8Array => {\n\t\t\t\t\tif (raw instanceof Uint8Array) return raw;\n\t\t\t\t\tif (raw instanceof ArrayBuffer) return new Uint8Array(raw);\n\t\t\t\t\tif (typeof raw === \"string\") return new TextEncoder().encode(raw);\n\t\t\t\t\tif (raw?.buffer instanceof ArrayBuffer) return new Uint8Array(raw.buffer);\n\t\t\t\t\treturn new Uint8Array(raw);\n\t\t\t\t};\n\t\t\t\tconst walk = async (fsPath: string, zipPrefix: string) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst stat = await window.tb.fs.promises.stat(fsPath);\n\t\t\t\t\t\tif (stat!.type === \"DIRECTORY\") {\n\t\t\t\t\t\t\tconst entries = await window.tb.fs.promises.readdir(fsPath);\n\t\t\t\t\t\t\tconst dirName = normalizeZipPath(zipPrefix);\n\t\t\t\t\t\t\tif (entries.length === 0 && dirName) {\n\t\t\t\t\t\t\t\tfiles[dirName.endsWith(\"/\") ? dirName : dirName + \"/\"] = new Uint8Array(0);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tawait Promise.all(\n\t\t\t\t\t\t\t\tentries.map(async entry => {\n\t\t\t\t\t\t\t\t\tconst childFsPath = `${fsPath.endsWith(\"/\") ? fsPath : fsPath + \"/\"}${entry}`;\n\t\t\t\t\t\t\t\t\tconst childZipPath = zipPrefix ? `${zipPrefix}/${entry}` : entry;\n\t\t\t\t\t\t\t\t\tawait walk(childFsPath, childZipPath);\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst raw = await window.tb.fs.promises.readFile(fsPath, \"arraybuffer\");\n\t\t\t\t\t\t\tconst name = normalizeZipPath(zipPrefix || fsPath.split(\"/\").pop() || \"file\");\n\t\t\t\t\t\t\tfiles[name] = toUint8(raw);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.warn(\"exportfs: skipping path\", fsPath, err);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tconst normalizedStart = normalizeZipPath(startPath);\n\t\t\t\t\tawait walk(startPath, normalizedStart === \"\" || normalizedStart === \".\" ? \"\" : normalizedStart);\n\t\t\t\t\tconst zipped = fflate.zipSync(files, { level: 1 });\n\t\t\t\t\t// @ts-expect-error blobs work fine\n\t\t\t\t\tconst blob = new Blob([zipped], { type: \"application/zip\" });\n\t\t\t\t\tconst url = URL.createObjectURL(blob);\n\t\t\t\t\tconst a = document.createElement(\"a\");\n\t\t\t\t\ta.href = url;\n\t\t\t\t\ta.download = filename;\n\t\t\t\t\ta.click();\n\t\t\t\t\tsetTimeout(() => URL.revokeObjectURL(url), 100);\n\t\t\t\t\treturn url;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(\"exportfs failed\", err);\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t},\n\t\t\tusers: {\n\t\t\t\tasync list() {\n\t\t\t\t\tconst usersDir = await window.tb.fs.promises.readdir(\"/home/\");\n\t\t\t\t\tconst users: string[] = [];\n\t\t\t\t\tfor (const user of usersDir) {\n\t\t\t\t\t\tif (await fileExists(`/home/${user}/user.json`)) {\n\t\t\t\t\t\t\tconst userData: User = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/user.json`, \"utf8\"));\n\t\t\t\t\t\t\tusers.push(userData.username);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn users;\n\t\t\t\t},\n\t\t\t\tasync add(user: User) {\n\t\t\t\t\tconst { username, password, pfp, perm, securityQuestion } = user;\n\t\t\t\t\tconst userDir = `/home/${username}`;\n\t\t\t\t\tawait window.tb.fs.promises.mkdir(userDir);\n\t\t\t\t\tconst userJson: User = {\n\t\t\t\t\t\tid: username,\n\t\t\t\t\t\tusername: username,\n\t\t\t\t\t\tpassword: password,\n\t\t\t\t\t\tpfp: pfp,\n\t\t\t\t\t\tperm: perm,\n\t\t\t\t\t};\n\t\t\t\t\tif (securityQuestion) {\n\t\t\t\t\t\tuserJson.securityQuestion = {\n\t\t\t\t\t\t\tquestion: securityQuestion.question,\n\t\t\t\t\t\t\tanswer: securityQuestion.answer,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`${userDir}/user.json`, JSON.stringify(userJson));\n\t\t\t\t\tconst userSettings: UserSettings = {\n\t\t\t\t\t\twallpaper: \"/assets/wallpapers/1.png\",\n\t\t\t\t\t\twallpaperMode: \"cover\",\n\t\t\t\t\t\tanimations: true,\n\t\t\t\t\t\tproxy: \"Scramjet\",\n\t\t\t\t\t\ttransport: \"Default (Epoxy)\",\n\t\t\t\t\t\twispServer: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`,\n\t\t\t\t\t\t\"battery-percent\": false,\n\t\t\t\t\t\taccent: \"#32ae62\",\n\t\t\t\t\t\ttimes: {\n\t\t\t\t\t\t\tformat: \"12h\",\n\t\t\t\t\t\t\tinternet: false,\n\t\t\t\t\t\t\tshowSeconds: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tshowFPS: false,\n\t\t\t\t\t\twindowOptimizations: false,\n\t\t\t\t\t\twindow: {\n\t\t\t\t\t\t\twinAccent: \"#ffffff\",\n\t\t\t\t\t\t\tblurlevel: 18,\n\t\t\t\t\t\t\talwaysMaximized: false,\n\t\t\t\t\t\t\talwaysFullscreen: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`${userDir}/settings.json`, JSON.stringify(userSettings));\n\t\t\t\t\tconst defaultDirs = [\"desktop\", \"documents\", \"downloads\", \"music\", \"pictures\", \"videos\"];\n\t\t\t\t\tdefaultDirs.forEach(async dir => {\n\t\t\t\t\t\tawait window.tb.fs.promises.mkdir(`${userDir}/${dir}`);\n\t\t\t\t\t});\n\t\t\t\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${username}`);\n\t\t\t\t\tawait window.tb.fs.promises.mkdir(`/apps/user/${username}/files`);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`/apps/user/${username}/files/config.json`,\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\"quick-center\": true,\n\t\t\t\t\t\t\t\"sidebar-width\": 180,\n\t\t\t\t\t\t\tdrives: {\n\t\t\t\t\t\t\t\t\"File System\": `/home/${username}/`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tstorage: {\n\t\t\t\t\t\t\t\t\"File System\": \"storage-device\",\n\t\t\t\t\t\t\t\tlocalStorage: \"storage-device\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"open-collapsibles\": {\n\t\t\t\t\t\t\t\t\"quick-center\": true,\n\t\t\t\t\t\t\t\tdrives: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t\"utf8\",\n\t\t\t\t\t);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${username}/files/davs.json`, JSON.stringify([]));\n\t\t\t\t\tconst response = await fetch(\"/apps/files.tapp/icons.json\");\n\t\t\t\t\tconst dat = await response.json();\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${username}/files/icns.json`, JSON.stringify(dat));\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`/apps/user/${username}/files/quick-center.json`,\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tpaths: {\n\t\t\t\t\t\t\t\tDocuments: `/home/${username}/documents`,\n\t\t\t\t\t\t\t\tImages: `/home/${username}/images`,\n\t\t\t\t\t\t\t\tVideos: `/home/${username}/videos`,\n\t\t\t\t\t\t\t\tMusic: `/home/${username}/music`,\n\t\t\t\t\t\t\t\tTrash: \"/system/trash\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t\"utf8\",\n\t\t\t\t\t);\n\t\t\t\t\tconst items: any[] = [];\n\t\t\t\t\tconst r2 = [];\n\t\t\t\t\tfor (let i = 0; i < apps.length; i++) {\n\t\t\t\t\t\tconst app = apps[i];\n\t\t\t\t\t\tconst name = app.name.toLowerCase();\n\t\t\t\t\t\tvar topPos = 0;\n\t\t\t\t\t\tvar leftPos = 0;\n\t\t\t\t\t\tif (i % 12 === 0) {\n\t\t\t\t\t\t\ttopPos = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttopPos = i % 12;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (i < 12) {\n\t\t\t\t\t\t\tleftPos = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tleftPos = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (topPos * 66 > parent.innerHeight - 130) {\n\t\t\t\t\t\t\tleftPos = 1.15;\n\t\t\t\t\t\t\tif (r2.length === 0) {\n\t\t\t\t\t\t\t\ttopPos = 0;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttopPos = r2.length % 12;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tr2.push({\n\t\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\titems.push({\n\t\t\t\t\t\t\tname: app.name,\n\t\t\t\t\t\t\titem: `/home/${username}/desktop/${name}.lnk`,\n\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t\tawait window.tb.fs.promises.symlink(`/apps/system/${name}.tapp/index.json`, `/home/${username}/desktop/${name}.lnk`);\n\t\t\t\t\t}\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${username}/desktop/.desktop.json`, JSON.stringify(items));\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`/apps/user/${username}/app store/repos.json`,\n\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: \"TB App Repo\",\n\t\t\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/TerbiumOS/tb-repo/refs/heads/main/manifest.json\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: \"XSTARS XTRAS\",\n\t\t\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/Notplayingallday383/app-repo/refs/heads/main/manifest.json\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: \"Anura App Repo\",\n\t\t\t\t\t\t\t\turl: \"https://raw.githubusercontent.com/MercuryWorkshop/anura-repo/refs/heads/master/manifest.json\",\n\t\t\t\t\t\t\t\ticon: \"https://anura.pro/icon.png\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tasync remove(id: string) {\n\t\t\t\t\tconst userDir = `/home/${id}`;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst uDir = await window.tb.fs.promises.stat(userDir);\n\t\t\t\t\t\tif (uDir && uDir.type === \"DIRECTORY\") {\n\t\t\t\t\t\t\tawait window.tb.sh.promises.rm(userDir, { recursive: true });\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err: any) {\n\t\t\t\t\t\tthrow new Error(err.message);\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst appDir = await window.tb.fs.promises.stat(`/apps/user/${id}`);\n\t\t\t\t\t\tif (appDir && appDir.type === \"DIRECTORY\") {\n\t\t\t\t\t\t\tawait window.tb.sh.promises.rm(`/apps/user/${id}`, { recursive: true });\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err: any) {\n\t\t\t\t\t\tthrow new Error(err.message);\n\t\t\t\t\t}\n\t\t\t\t\tconst sudoUsers: string[] = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/sudousers.json\", \"utf8\"));\n\t\t\t\t\tconst users = await window.tb.fs.promises.readdir(\"/home/\");\n\t\t\t\t\tconst idx = sudoUsers.indexOf(id);\n\t\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tsudoUsers.splice(idx, 1);\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/sudousers.json\", JSON.stringify(sudoUsers, null, 2), \"utf8\");\n\t\t\t\t\t\tif (sudoUsers.length === 0) {\n\t\t\t\t\t\t\twindow.tb.dialog.Select({\n\t\t\t\t\t\t\t\ttitle: \"Select new sudo user\",\n\t\t\t\t\t\t\t\tmessage: \"Please select a new sudo user\",\n\t\t\t\t\t\t\t\toptions: users.map(u => ({ text: u, value: u })),\n\t\t\t\t\t\t\t\tonOk: async (selected: string) => {\n\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/sudousers.json\", JSON.stringify({ id: selected }), \"utf8\");\n\t\t\t\t\t\t\t\t\twindow.tb.notification.Toast({\n\t\t\t\t\t\t\t\t\t\tapplication: \"System\",\n\t\t\t\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\tmessage: `Sudo user changed to ${selected}`,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tconst syssettings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\t\t\t\t\tif (id === syssettings.defaultUser) {\n\t\t\t\t\t\t\t\t\t\tsyssettings.defaultUser = selected;\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(syssettings, null, 2), \"utf8\");\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (id === sessionStorage.getItem(\"currAcc\")) {\n\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"false\");\n\t\t\t\t\t\t\t\t\t\tsessionStorage.removeItem(\"currAcc\");\n\t\t\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tasync update(user: User) {\n\t\t\t\t\tconst { username, password, pfp, perm, securityQuestion } = user;\n\t\t\t\t\tconst userDir = `/home/${username}`;\n\t\t\t\t\tconst userConfig = JSON.parse(await window.tb.fs.promises.readFile(`${userDir}/user.json`, \"utf8\"));\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t`${userDir}/user.json`,\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tid: userConfig.id,\n\t\t\t\t\t\t\tusername: username === userConfig.username ? userConfig.username : username,\n\t\t\t\t\t\t\tpassword: password === userConfig.password ? userConfig.password : password,\n\t\t\t\t\t\t\tpfp: pfp === userConfig.pfp ? userConfig.pfp : pfp,\n\t\t\t\t\t\t\tperm: perm === userConfig.perm ? userConfig.perm : perm,\n\t\t\t\t\t\t\t...(securityQuestion !== undefined ? { securityQuestion: securityQuestion === userConfig.securityQuestion ? userConfig.securityQuestion : securityQuestion } : userConfig.securityQuestion !== undefined ? { securityQuestion: userConfig.securityQuestion } : {}),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\tasync renameUser(olduser: string, newuser: string) {\n\t\t\t\t\tconst userData = JSON.parse(await window.tb.fs.promises.readFile(`/home/${olduser}/user.json`, \"utf8\"));\n\t\t\t\t\tuserData[\"username\"] = newuser;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${olduser}/user.json`, JSON.stringify(userData));\n\t\t\t\t\tlet linkpaths = [];\n\t\t\t\t\tfor (const item of await window.tb.fs.promises.readdir(`/home/${olduser}/desktop/`)) {\n\t\t\t\t\t\tconst stat = await window.tb.fs.promises.stat(`/home/${olduser}/desktop/${item}`);\n\t\t\t\t\t\tif (stat && stat.type === \"SYMLINK\") {\n\t\t\t\t\t\t\tlinkpaths.push(await window.tb.fs.promises.readlink(`/home/${olduser}/desktop/${item}`));\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait window.tb.fs.promises.unlink(`/home/${olduser}/desktop/${item}`);\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\tconsole.log(e);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tawait window.tb.fs.promises.rename(`/home/${olduser}`, `/home/${newuser}`);\n\t\t\t\t\tsessionStorage.setItem(\"currAcc\", newuser);\n\t\t\t\t\tfor (const link of linkpaths) {\n\t\t\t\t\t\tconst tappMatch = link.match(/([^/]+)(?=\\.tapp(?:\\/|$))/);\n\t\t\t\t\t\tconst parts = link.split(\"/\").filter(Boolean);\n\t\t\t\t\t\tlet linkName = \"\";\n\t\t\t\t\t\tif (tappMatch) {\n\t\t\t\t\t\t\tlinkName = tappMatch[1];\n\t\t\t\t\t\t} else if (parts.length > 1) {\n\t\t\t\t\t\t\tconst last = parts[parts.length - 1];\n\t\t\t\t\t\t\tlinkName = last.includes(\".\") ? parts[parts.length - 2] : last;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlinkName = parts[0] || \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlinkName = linkName.replace(/\\.tapp$/, \"\");\n\t\t\t\t\t\tawait window.tb.fs.promises.symlink(link, `/home/${newuser}/desktop/${linkName}.lnk`);\n\t\t\t\t\t}\n\t\t\t\t\tconst desktopItems = JSON.parse(await window.tb.fs.promises.readFile(`/home/${newuser}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\tfor (const item of desktopItems) {\n\t\t\t\t\t\tif (item.position && item.name) {\n\t\t\t\t\t\t\tconst name = item.name.toLowerCase();\n\t\t\t\t\t\t\titem.item = `/home/${newuser}/desktop/${name}.lnk`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${newuser}/desktop/.desktop.json`, JSON.stringify(desktopItems), \"utf8\");\n\t\t\t\t\tawait window.tb.fs.promises.rename(`/apps/user/${olduser}`, `/apps/user/${newuser}`);\n\t\t\t\t\tconst sysSettings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\tif (sysSettings[\"defaultUser\"] === olduser) {\n\t\t\t\t\t\tsysSettings[\"defaultUser\"] = newuser;\n\t\t\t\t\t}\n\t\t\t\t\tconst sudousers = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/sudousers.json\", \"utf8\"));\n\t\t\t\t\tconst idx = sudousers.indexOf(olduser);\n\t\t\t\t\tif (idx !== -1) {\n\t\t\t\t\t\tsudousers[idx] = newuser;\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/sudousers.json\", JSON.stringify(sudousers), \"utf8\");\n\t\t\t\t\t}\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(sysSettings));\n\t\t\t\t\tconst fcfg = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${newuser}/files/config.json`, \"utf8\"));\n\t\t\t\t\tfcfg.drives[\"File System\"] = `/home/${newuser}/`;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${newuser}/files/config.json`, JSON.stringify(fcfg));\n\t\t\t\t\tconst qcfg = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${newuser}/files/quick-center.json`, \"utf8\"));\n\t\t\t\t\tfor (const key in qcfg.paths) {\n\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(qcfg.paths, key)) {\n\t\t\t\t\t\t\tqcfg.paths[key] = qcfg.paths[key].replace(olduser, newuser);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${newuser}/files/quick-center.json`, JSON.stringify(qcfg));\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tbootmenu: {\n\t\t\t\tasync addEntry(name: string, file: string) {\n\t\t\t\t\tconst data = JSON.parse(await window.tb.fs.promises.readFile(\"/bootentries.json\", \"utf8\"));\n\t\t\t\t\tdata.push({\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\taction: `() => { sessionStorage.setItem(\"cusboot\", \"true\"); sessionStorage.setItem(\"bootfile\", \"${file}\"); window.location.reload(); }`,\n\t\t\t\t\t});\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/bootentries.json\", JSON.stringify(data, null, 2));\n\t\t\t\t},\n\t\t\t\tasync removeEntry(name: string) {\n\t\t\t\t\tconst data = JSON.parse(await window.tb.fs.promises.readFile(\"/bootentries.json\", \"utf8\"));\n\t\t\t\t\tconst dat = data.filter((entry: any) => entry.name !== name);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/bootentries.json\", JSON.stringify(dat, null, 2));\n\t\t\t\t},\n\t\t\t},\n\t\t\tstartup: {\n\t\t\t\tasync addProc(pkgorname: string, target: \"System\" | \"User\", cmd?: string) {\n\t\t\t\t\tawait addStartupProc(pkgorname, target, cmd);\n\t\t\t\t},\n\t\t\t\tasync removeProc(pkgorname: string, target: \"System\" | \"User\") {\n\t\t\t\t\tawait removeStartupProc(pkgorname, target);\n\t\t\t\t},\n\t\t\t\tasync enable(pkgorname: string, target: \"System\" | \"User\") {\n\t\t\t\t\tawait enableProc(pkgorname, target);\n\t\t\t\t},\n\t\t\t\tasync disable(pkgorname: string, target: \"System\" | \"User\") {\n\t\t\t\t\tawait disableProc(pkgorname, target);\n\t\t\t\t},\n\t\t\t\tasync list() {\n\t\t\t\t\tconst procs = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/startup.json\", \"utf8\"));\n\t\t\t\t\treturn procs;\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tlibcurl: libcurl,\n\t\tfflate: fflate,\n\t\tfs: window.tb.fs,\n\t\tvfs: await vFS.create(),\n\t\ttauth: {\n\t\t\tclient: auth,\n\t\t\tsignIn: () => {\n\t\t\t\treturn new Promise<any>((resolve, reject) => {\n\t\t\t\t\twindow.tb.dialog.WebAuth({\n\t\t\t\t\t\ttitle: \"Terbium Cloud Sign In\",\n\t\t\t\t\t\tmessage: \"Please sign in to your Terbium Cloud Account to continue.\",\n\t\t\t\t\t\tonOk: async (username: string, password: string) => {\n\t\t\t\t\t\t\tawait window.tb.tauth.client.signIn.email({\n\t\t\t\t\t\t\t\temail: username,\n\t\t\t\t\t\t\t\tpassword: password,\n\t\t\t\t\t\t\t\trememberMe: true,\n\t\t\t\t\t\t\t\tfetchOptions: {\n\t\t\t\t\t\t\t\t\tonSuccess: async response => {\n\t\t\t\t\t\t\t\t\t\tconst exists = await window.tb.fs.promises.exists(\"/system/etc/terbium/taccs.json\");\n\t\t\t\t\t\t\t\t\t\tif (!exists) {\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify([], null, 2), \"utf8\");\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tconst conf = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/taccs.json\", \"utf8\"));\n\t\t\t\t\t\t\t\t\t\tconst existingIndex = conf.findIndex((acc: any) => acc && (acc.id === response.data.user.id || acc.email === response.data.user.email));\n\t\t\t\t\t\t\t\t\t\tif (existingIndex !== -1) {\n\t\t\t\t\t\t\t\t\t\t\tconf[existingIndex] = {\n\t\t\t\t\t\t\t\t\t\t\t\tusername: response.data.user.name,\n\t\t\t\t\t\t\t\t\t\t\t\tperm: \"admin\",\n\t\t\t\t\t\t\t\t\t\t\t\tpfp: response.data.user.image,\n\t\t\t\t\t\t\t\t\t\t\t\temail: response.data.user.email,\n\t\t\t\t\t\t\t\t\t\t\t\tid: response.data.user.id,\n\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\tconsole.log(\"[TAUTH] Updated existing Account Info in FS\");\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tconf.push({\n\t\t\t\t\t\t\t\t\t\t\t\tusername: response.data.user.name,\n\t\t\t\t\t\t\t\t\t\t\t\tperm: \"admin\",\n\t\t\t\t\t\t\t\t\t\t\t\tpfp: response.data.user.image,\n\t\t\t\t\t\t\t\t\t\t\t\temail: response.data.user.email,\n\t\t\t\t\t\t\t\t\t\t\t\tid: response.data.user.id,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\tconsole.log(\"[TAUTH] Saved Account Info to FS\");\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify(conf, null, 2), \"utf8\");\n\t\t\t\t\t\t\t\t\t\tconst info = response;\n\t\t\t\t\t\t\t\t\t\tinfo.data.user.password = password;\n\t\t\t\t\t\t\t\t\t\tresolve(info);\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tonError: error => {\n\t\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonCancel: () => {\n\t\t\t\t\t\t\treject(new Error(\"User cancelled the sign-in process\"));\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t},\n\t\t\tsignOut: async () => {\n\t\t\t\tlet conf = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/taccs.json\", \"utf8\"));\n\t\t\t\tif (!Array.isArray(conf)) {\n\t\t\t\t\tif (conf && typeof conf === \"object\") {\n\t\t\t\t\t\tconf = Object.values(conf);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconf = [];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst currUser = sessionStorage.getItem(\"currAcc\");\n\t\t\t\tconst idx = conf.findIndex((acc: any) => acc && acc.username === currUser);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\tconf.splice(idx, 1);\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify(conf, null, 2), \"utf8\");\n\t\t\t\t\tconsole.log(\"[TAUTH] Removed Account Info from FS\");\n\t\t\t\t}\n\t\t\t},\n\t\t\tisTACC(username?: string) {\n\t\t\t\treturn new Promise<boolean>(async resolve => {\n\t\t\t\t\tif (!username) {\n\t\t\t\t\t\tusername = sessionStorage.getItem(\"currAcc\") || \"Guest\";\n\t\t\t\t\t}\n\t\t\t\t\tconst conf = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/taccs.json\", \"utf8\"));\n\t\t\t\t\tconst exists = conf.some((acc: any) => acc && acc.username === username);\n\t\t\t\t\tresolve(exists);\n\t\t\t\t});\n\t\t\t},\n\t\t\tupdateInfo: async (user: Partial<User>) => {\n\t\t\t\tconst target = (user as any).id || user.username || sessionStorage.getItem(\"currAcc\");\n\t\t\t\tif (!target) throw new Error(\"No target account specified\");\n\t\t\t\tlet conf = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/taccs.json\", \"utf8\"));\n\t\t\t\tconst exists = await window.tb.fs.promises.exists(\"/system/etc/terbium/taccs.json\");\n\t\t\t\tif (!exists) {\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify([], null, 2), \"utf8\");\n\t\t\t\t}\n\t\t\t\tif (!Array.isArray(conf)) {\n\t\t\t\t\tif (conf && typeof conf === \"object\") conf = Object.values(conf);\n\t\t\t\t\telse conf = [];\n\t\t\t\t}\n\t\t\t\tconst idx = conf.findIndex((acc: any) => acc && (acc.username === target || acc.id === target));\n\t\t\t\tif (idx === -1) throw new Error(`Account '${target}' not found`);\n\t\t\t\tconst existing = conf[idx] || {};\n\t\t\t\tconst updated = { ...existing, ...user };\n\t\t\t\tif (!updated.id && existing.id) updated.id = existing.id;\n\t\t\t\tconf[idx] = updated;\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/taccs.json\", JSON.stringify(conf, null, 2), \"utf8\");\n\t\t\t\tif (existing.username && updated.username && existing.username !== updated.username && sessionStorage.getItem(\"currAcc\") === existing.username) {\n\t\t\t\t\tsessionStorage.setItem(\"currAcc\", updated.username);\n\t\t\t\t}\n\t\t\t\tconst run = async () => {\n\t\t\t\t\tconst updobj = {\n\t\t\t\t\t\tname: updated.username,\n\t\t\t\t\t\timage: updated.pfp,\n\t\t\t\t\t\t...(updated.email ? { email: updated.email } : {}),\n\t\t\t\t\t\t...(updated.password ? { password: updated.password } : {}),\n\t\t\t\t\t};\n\t\t\t\t\tif (updated.email) {\n\t\t\t\t\t\tconsole.log(\"[TAUTH] Updating email is not currently supported\");\n\t\t\t\t\t\tdelete updobj.email;\n\t\t\t\t\t}\n\t\t\t\t\tawait window.tb.tauth.client.updateUser(updobj);\n\t\t\t\t\tconsole.log(\"[TAUTH] Updated TACC info successfully\");\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tawait run();\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\tif (error.error.message.toLowerCase() === \"unauthorized\") {\n\t\t\t\t\t\twindow.tb.dialog.WebAuth({\n\t\t\t\t\t\t\ttitle: \"Verify Identity to Update Account\",\n\t\t\t\t\t\t\tmessage: \"Please sign in to your Terbium Cloud Account to verify it's you.\",\n\t\t\t\t\t\t\tonOk: async (username: string, password: string) => {\n\t\t\t\t\t\t\t\tawait window.tb.tauth.client.signIn.email({\n\t\t\t\t\t\t\t\t\temail: username,\n\t\t\t\t\t\t\t\t\tpassword: password,\n\t\t\t\t\t\t\t\t\tfetchOptions: {\n\t\t\t\t\t\t\t\t\t\tonSuccess: async () => {\n\t\t\t\t\t\t\t\t\t\t\tawait run();\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tonError: error => {\n\t\t\t\t\t\t\t\t\t\t\tthrow new Error(error.error.message);\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonCancel: () => {\n\t\t\t\t\t\t\t\treturn new Error(\"User cancelled the sign-in process\");\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\treauth: async () => {\n\t\t\t\treturn new Promise<any>((resolve, reject) => {\n\t\t\t\t\twindow.tb.dialog.WebAuth({\n\t\t\t\t\t\ttitle: \"Verify Identity\",\n\t\t\t\t\t\tmessage: \"Please sign in to your Terbium Cloud Account to verify it's you.\",\n\t\t\t\t\t\tonOk: async (username: string, password: string) => {\n\t\t\t\t\t\t\tawait window.tb.tauth.client.signIn.email({\n\t\t\t\t\t\t\t\temail: username,\n\t\t\t\t\t\t\t\tpassword: password,\n\t\t\t\t\t\t\t\trememberMe: true,\n\t\t\t\t\t\t\t\tfetchOptions: {\n\t\t\t\t\t\t\t\t\tonSuccess: async () => {\n\t\t\t\t\t\t\t\t\t\tawait window.tb.tauth.sync.retreive();\n\t\t\t\t\t\t\t\t\t\tresolve(true);\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tonError: error => {\n\t\t\t\t\t\t\t\t\t\treject(new Error(error.error.message));\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonCancel: () => {\n\t\t\t\t\t\t\treject(new Error(\"User cancelled the sign-in process\"));\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t},\n\t\t\tsync: {\n\t\t\t\tretreive: async () => {\n\t\t\t\t\tconst info = await window.tb.tauth.getInfo();\n\t\t\t\t\tif (!info) throw new Error(\"No TACC info found\");\n\t\t\t\t\twindow.tb.tauth.sync.isSyncing = true;\n\t\t\t\t\tconst data = await getinfo(null, null, \"tbs\");\n\t\t\t\t\tconsole.log(\"[TAUTH] Retrieved synced data from cloud\");\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${info.username}/settings.json`, JSON.stringify(data.settings[0].settings, null, 2), \"utf8\");\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${info.username}/files/davs.json`, JSON.stringify(data.settings[0].davs, null, 2), \"utf8\");\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${info.username}/app store/repos.json`, JSON.stringify(data.settings[0].apps.repos || [], null, 2), \"utf8\");\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updWallpaper\"));\n\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"proxy-change\"));\n\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-accent\"));\n\t\t\t\t\twindow.tb.tauth.sync.isSyncing = false;\n\t\t\t\t},\n\t\t\t\tupload: async () => {\n\t\t\t\t\tconst info = await window.tb.tauth.getInfo();\n\t\t\t\t\tif (!info) throw new Error(\"No TACC info found\");\n\t\t\t\t\twindow.tb.tauth.sync.isSyncing = true;\n\t\t\t\t\tlet settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${info.username}/settings.json`, \"utf8\"));\n\t\t\t\t\tconst davs = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${info.username}/files/davs.json`, \"utf8\"));\n\t\t\t\t\tif (!settings.wallpaper.startsWith(\"/assets/wallpapers\") && !settings.wallpaper.startsWith(\"data:\")) {\n\t\t\t\t\t\tconst res = await fetch(`/fs/${settings.wallpaper}`);\n\t\t\t\t\t\tconst blob = await res.blob();\n\t\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\t\tconst dataURL: Promise<string> = new Promise((resolve, reject) => {\n\t\t\t\t\t\t\treader.onloadend = () => {\n\t\t\t\t\t\t\t\tif (typeof reader.result === \"string\") {\n\t\t\t\t\t\t\t\t\tresolve(reader.result);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\treject(new Error(\"Failed to convert wallpaper to data URL\"));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\treader.onerror = () => {\n\t\t\t\t\t\t\t\treject(new Error(\"Failed to read wallpaper blob\"));\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t});\n\t\t\t\t\t\treader.readAsDataURL(blob);\n\t\t\t\t\t\tsettings.wallpaper = await dataURL;\n\t\t\t\t\t}\n\t\t\t\t\tconst toupload = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsettings: settings,\n\t\t\t\t\t\t\tapps: {\n\t\t\t\t\t\t\t\trepos: JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${info.username}/app store/repos.json`, \"utf8\")),\n\t\t\t\t\t\t\t\tinstalled: [],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tdavs: davs,\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\t\t\t\t\tsetinfo(null, null, \"tbs\", toupload);\n\t\t\t\t\tconsole.log(\"[TAUTH] Uploaded synced data to cloud\");\n\t\t\t\t\twindow.tb.tauth.sync.isSyncing = false;\n\t\t\t\t},\n\t\t\t\tisSyncing: false,\n\t\t\t},\n\t\t\tgetInfo: async (username?: string) => {\n\t\t\t\tconst conf = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/taccs.json\", \"utf8\"));\n\t\t\t\tif (!conf.find((acc: any) => acc && acc.username === username)) {\n\t\t\t\t\tusername = sessionStorage.getItem(\"currAcc\") || \"Guest\";\n\t\t\t\t}\n\t\t\t\tconst account = conf.find((acc: any) => acc && acc.username === username) || null;\n\t\t\t\treturn account;\n\t\t\t},\n\t\t},\n\t\tnode: {\n\t\t\twebContainer: {},\n\t\t\tservers: new Map<number, string>(),\n\t\t\tisReady: false,\n\t\t\tstart: () => {\n\t\t\t\tinitializeWebContainer();\n\t\t\t},\n\t\t\tstop: () => {\n\t\t\t\tif (window.tb.node.isReady) {\n\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\twindow.tb.node.webContainer.teardown();\n\t\t\t\t\twindow.tb.node.isReady = false;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tthrow new Error(\"No WebContainer is running\");\n\t\t\t},\n\t\t},\n\t\tcrypto: async (pass: string, file?: string) => {\n\t\t\tconst newpw = pw.harden(pass);\n\t\t\tif (file) {\n\t\t\t\tawait window.tb.fs.promises.writeFile(file, newpw);\n\t\t\t\treturn \"Complete\";\n\t\t\t}\n\t\t\treturn newpw;\n\t\t},\n\t\tplatform: {\n\t\t\tasync getPlatform() {\n\t\t\t\tconst mobileuas =\n\t\t\t\t\t/(android|bb\\d+|meego).+mobile|armv7l|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series[46]0|samsungbrowser.*mobile|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk|iPhone|iPad/i;\n\t\t\t\tconst crosua = /CrOS/;\n\t\t\t\tif (mobileuas.test(navigator.userAgent) && !crosua.test(navigator.userAgent)) {\n\t\t\t\t\treturn \"mobile\";\n\t\t\t\t}\n\t\t\t\tif (!mobileuas.test(navigator.userAgent) && navigator.maxTouchPoints > 1 && navigator.userAgent.indexOf(\"Macintosh\") !== -1 && navigator.userAgent.indexOf(\"Safari\") !== -1) {\n\t\t\t\t\treturn \"mobile\";\n\t\t\t\t}\n\t\t\t\treturn \"desktop\";\n\t\t\t},\n\t\t},\n\t\tprocess: {\n\t\t\tprocs: {\n\t\t\t\t0: {\n\t\t\t\t\tname: \"Terbium Service Worker\",\n\t\t\t\t\twid: null,\n\t\t\t\t\tpid: 0,\n\t\t\t\t\tsrc: null,\n\t\t\t\t\tsize: null,\n\t\t\t\t\ticon: null,\n\t\t\t\t\ttype: \"runtime\",\n\t\t\t\t\tonKill: async () => {\n\t\t\t\t\t\tawait window.tb.proxy.updateSWs();\n\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t1: {\n\t\t\t\t\tname: \"Terbium Alexa Desktop Experience\",\n\t\t\t\t\twid: null,\n\t\t\t\t\tpid: 1,\n\t\t\t\t\tsrc: null,\n\t\t\t\t\tsize: null,\n\t\t\t\t\ticon: null,\n\t\t\t\t\ttype: \"runtime\",\n\t\t\t\t\tonKill: () => {\n\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t} as Record<number, any>,\n\t\t\tkill(pid: string | number) {\n\t\t\t\tconst pd = Number(pid);\n\t\t\t\tconst proc = Object.values(window.tb.process.list()).find((p: any) => {\n\t\t\t\t\treturn Number(p.pid) === pd;\n\t\t\t\t});\n\t\t\t\tif (proc) {\n\t\t\t\t\tif (proc.type === \"window\") {\n\t\t\t\t\t\tclearInfo();\n\t\t\t\t\t\tuseWindowStore.getState().killWindow(String(pd));\n\t\t\t\t\t\tdelete window.tb.process.procs[proc.pid];\n\t\t\t\t\t} else if (proc.type === \"runtime\") {\n\t\t\t\t\t\tdelete window.tb.process.procs[proc.pid];\n\t\t\t\t\t\tif (proc.onKill) proc.onKill();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(`Process with PID ${pid} not found`);\n\t\t\t\t}\n\t\t\t},\n\t\t\tlist() {\n\t\t\t\treturn window.tb.process.procs;\n\t\t\t},\n\t\t\tparse: {\n\t\t\t\tbuild(src: string) {\n\t\t\t\t\tparse.build(src);\n\t\t\t\t},\n\t\t\t},\n\t\t\tcreate(type: \"window\" | \"runtime\", config: any) {\n\t\t\t\tif (type === \"window\") {\n\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\ttitle: {\n\t\t\t\t\t\t\ttext: \"Generic Window\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsrc: \"about:blank\",\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tconst latestProcId = Math.max(...Object.keys(window.tb.process.procs).map(Number)) + 1;\n\t\t\t\t\twindow.tb.process.procs[latestProcId] = { ...config, pid: latestProcId, type: \"runtime\" };\n\t\t\t\t\treturn latestProcId;\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tscreen: {\n\t\t\tasync captureScreen() {\n\t\t\t\tif (!navigator.mediaDevices.getDisplayMedia) throw new Error(\"API Not Avalible on your browser\");\n\t\t\t\t// @ts-expect-error\n\t\t\t\tconst stream = await navigator.mediaDevices.getDisplayMedia({ preferCurrentTab: true });\n\t\t\t\tconst capture = new ImageCapture(stream.getVideoTracks()[0]);\n\t\t\t\tconst frame = await capture.grabFrame();\n\t\t\t\tstream.getVideoTracks()[0].stop();\n\t\t\t\tconst canvas: HTMLCanvasElement = document.createElement(\"canvas\");\n\t\t\t\tconst ctx: any = canvas.getContext(\"2d\");\n\t\t\t\tcanvas.width = frame.width;\n\t\t\t\tcanvas.height = frame.height;\n\t\t\t\tctx.drawImage(frame, 0, 0, frame.width, frame.height);\n\t\t\t\tconst dataURI = await new Promise(res => {\n\t\t\t\t\tcanvas.toBlob(blobImage => {\n\t\t\t\t\t\tres(blobImage);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tcanvas.remove();\n\t\t\t\tconst obj = await new Promise<ArrayBuffer>((resolve, reject) => {\n\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\treader.onloadend = () => resolve(reader.result as ArrayBuffer);\n\t\t\t\t\treader.onerror = () => reject(new Error(\"Failed to read blob\"));\n\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\treader.readAsArrayBuffer(dataURI);\n\t\t\t\t});\n\t\t\t\ttb.dialog.SaveFile({\n\t\t\t\t\ttitle: \"Save screenshot\",\n\t\t\t\t\tfilename: \"screenshot.png\",\n\t\t\t\t\tonOk: async (filePath: string) => {\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(filePath, window.tb.buffer.from(obj));\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t\tmediaplayer: {\n\t\t\tmusic(props: MediaProps) {\n\t\t\t\tsetMusicFn(props);\n\t\t\t},\n\t\t\tvideo(props: MediaProps) {\n\t\t\t\tsetVideoFn(props);\n\t\t\t},\n\t\t\thide: () => {\n\t\t\t\thideFn();\n\t\t\t},\n\t\t\tpauseplay: () => {\n\t\t\t\twindow.dispatchEvent(new Event(\"tb-pause-isl\"));\n\t\t\t},\n\t\t\tisExisting: () => {\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tisExistingFn();\n\t\t\t\t\tconst getContent = (e: CustomEvent) => {\n\t\t\t\t\t\twindow.removeEventListener(\"isExistingMP\", getContent as EventListener);\n\t\t\t\t\t\tresolve(e.detail);\n\t\t\t\t\t};\n\t\t\t\t\twindow.addEventListener(\"isExistingMP\", getContent as EventListener);\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t\tfile: {\n\t\t\thandler: {\n\t\t\t\topenFile: async (path: string, type: string) => {\n\t\t\t\t\tconst message = { type: \"process\", path: path };\n\t\t\t\t\tconst settings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\tconst fApps = settings[\"fileAssociatedApps\"];\n\t\t\t\t\tconst customHandler = fApps?.[type];\n\t\t\t\t\tif (customHandler) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst installed = JSON.parse(await window.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\t\t\t\t\tlet appInfo = installed.find((a: any) => a.name.toLowerCase() === customHandler.toLowerCase());\n\t\t\t\t\t\t\tif (!appInfo) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst altAppInfo = installed.find((a: any) => a.name.toLowerCase() === `${customHandler.toLowerCase()}.tapp`);\n\t\t\t\t\t\t\t\t\tif (altAppInfo) {\n\t\t\t\t\t\t\t\t\t\tappInfo = altAppInfo;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tconsole.error(`App \"${customHandler}\" not found in installed apps`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (appInfo.user === \"System\") return;\n\t\t\t\t\t\t\tconst appConfigRaw = JSON.parse(await window.tb.fs.promises.readFile(appInfo.config, \"utf8\"));\n\t\t\t\t\t\t\tlet windowConfig;\n\t\t\t\t\t\t\tif (appConfigRaw.wmArgs) {\n\t\t\t\t\t\t\t\twindowConfig = { ...appConfigRaw.wmArgs };\n\t\t\t\t\t\t\t\tconst configDir = appInfo.config.replace(/\\/(\\.tbconfig|index\\.json)$/, \"\");\n\t\t\t\t\t\t\t\tif (windowConfig.src && !windowConfig.src.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\t\twindowConfig.src = `/fs/${configDir}/${windowConfig.src}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (windowConfig.icon && !windowConfig.icon.startsWith(\"/\")) {\n\t\t\t\t\t\t\t\t\twindowConfig.icon = `/fs/${configDir}/${windowConfig.icon}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\t...windowConfig,\n\t\t\t\t\t\t\t\tmessage: JSON.stringify(message),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t}\n\t\t\t\t\tswitch (type) {\n\t\t\t\t\t\tcase \"text\":\n\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\ttitle: \"Text Editor\",\n\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/text editor.tapp/index.html\",\n\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\twidth: 460,\n\t\t\t\t\t\t\t\t\theight: 460,\n\t\t\t\t\t\t\t\t\tminWidth: 160,\n\t\t\t\t\t\t\t\t\tminHeight: 160,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/text editor.tapp/icon.svg\",\n\t\t\t\t\t\t\t\tmessage: JSON.stringify(message),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"image\":\n\t\t\t\t\t\tcase \"video\":\n\t\t\t\t\t\tcase \"audio\":\n\t\t\t\t\t\tcase \"pdf\":\n\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\ttitle: \"Media Viewer\",\n\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/media viewer.tapp/index.html\",\n\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\twidth: 460,\n\t\t\t\t\t\t\t\t\theight: 460,\n\t\t\t\t\t\t\t\t\tminWidth: 160,\n\t\t\t\t\t\t\t\t\tminHeight: 160,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/media viewer.tapp/icon.svg\",\n\t\t\t\t\t\t\t\tmessage: JSON.stringify(message),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"webpage\":\n\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\ttitle: \"Terbium Webview\",\n\t\t\t\t\t\t\t\tsrc: `/fs/${path}`,\n\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\twidth: 460,\n\t\t\t\t\t\t\t\t\theight: 460,\n\t\t\t\t\t\t\t\t\tminWidth: 160,\n\t\t\t\t\t\t\t\t\tminHeight: 160,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ticon: \"/apps/browser.tapp/icon.svg\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\taddHandler: async (app: string, ext: string) => {\n\t\t\t\t\tconst settings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\t(settings.fileAssociatedApps as Record<string, string>)[ext] = app;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(settings, null, 2), \"utf8\");\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tremoveHandler: async (ext: string) => {\n\t\t\t\t\tconst settings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"));\n\t\t\t\t\tdelete (settings.fileAssociatedApps as Record<string, string>)[ext];\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(settings, null, 2), \"utf8\");\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t},\n\t\t\ticons: {\n\t\t\t\tget: async (ext: string) => {\n\t\t\t\t\tconst fileExts = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\", \"utf8\"));\n\t\t\t\t\tconst extName = fileExts[\"ext-to-name\"][ext.toLowerCase()];\n\t\t\t\t\tif (extName && fileExts[\"name-to-path\"][extName]) {\n\t\t\t\t\t\treturn fileExts[\"name-to-path\"][extName];\n\t\t\t\t\t}\n\t\t\t\t\treturn fileExts[\"name-to-path\"][\"Unknown\"];\n\t\t\t\t},\n\t\t\t\tset: async (ext: string, iconPath: string) => {\n\t\t\t\t\tconst fileExts = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\", \"utf8\"));\n\t\t\t\t\tconst normalizedExt = ext.toLowerCase().replace(/^\\./, \"\");\n\t\t\t\t\tconst extName = normalizedExt.charAt(0).toUpperCase() + normalizedExt.slice(1);\n\t\t\t\t\tfileExts[\"ext-to-name\"][normalizedExt] = extName;\n\t\t\t\t\tfileExts[\"name-to-path\"][extName] = iconPath;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/file-icons.json\", JSON.stringify(fileExts, null, 2), \"utf8\");\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tremove: async (ext: string) => {\n\t\t\t\t\tconst fileExts = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\", \"utf8\"));\n\t\t\t\t\tconst normalizedExt = ext.toLowerCase().replace(/^\\./, \"\");\n\t\t\t\t\tconst extName = fileExts[\"ext-to-name\"][normalizedExt];\n\t\t\t\t\tif (extName) {\n\t\t\t\t\t\tdelete fileExts[\"ext-to-name\"][normalizedExt];\n\t\t\t\t\t\tdelete fileExts[\"name-to-path\"][extName];\n\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/file-icons.json\", JSON.stringify(fileExts, null, 2), \"utf8\");\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t};\n\n\tif (window.loadLock)\n\t\t// this function seems to be called twice, anura doesn't like initing twice, so well, this is the weird fix I chose instead of tackling the root problem - Rafflesia\n\t\treturn;\n\twindow.loadLock = true;\n\n\tconst anura = await Anura.new({\n\t\tmilestone: 5,\n\t\tFileExts: {\n\t\t\ttxt: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tmp3: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tflac: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\twav: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\togg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tmp4: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tmov: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\twebm: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tgif: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tpng: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tjpg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tjpeg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tsvg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tpdf: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tpy: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tjs: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tmjs: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tcjs: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tjson: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\thtml: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tcss: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\tdefault: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t},\n\t\t\"handler-migration-complete\": true,\n\t\tapps: [\"apps/fsapp.app\"],\n\t\tdefaultsettings: {\n\t\t\t\"use-sw-cache\": true,\n\t\t\tapplist: [\"anura.browser\", \"anura.settings\", \"anura.fsapp\", \"anura.term\"],\n\t\t\t\"relay-url\": \"wss://relay.widgetry.org/\",\n\t\t\tdirectories: {\n\t\t\t\tapps: \"/apps/anura/\",\n\t\t\t\tlibs: \"/system/lib/anura/\",\n\t\t\t\tinit: \"/system/etc/anura/init/\",\n\t\t\t\tbin: \"/system/bin/anura/\",\n\t\t\t},\n\t\t},\n\t\tx86: {\n\t\t\tdebian: {\n\t\t\t\tbzimage: \"/images/debian-boot/vmlinuz-6.1.0-11-686\",\n\t\t\t\tinitrd: \"/images/debian-boot/initrd.img-6.1.0-11-686\",\n\t\t\t\trootfs: [\n\t\t\t\t\t\"images/debian-rootfs/aa\",\n\t\t\t\t\t\"images/debian-rootfs/ab\",\n\t\t\t\t\t\"images/debian-rootfs/ac\",\n\t\t\t\t\t\"images/debian-rootfs/ad\",\n\t\t\t\t\t\"images/debian-rootfs/ae\",\n\t\t\t\t\t\"images/debian-rootfs/af\",\n\t\t\t\t\t\"images/debian-rootfs/ag\",\n\t\t\t\t\t\"images/debian-rootfs/ah\",\n\t\t\t\t\t\"images/debian-rootfs/ai\",\n\t\t\t\t\t\"images/debian-rootfs/aj\",\n\t\t\t\t\t\"images/debian-rootfs/ak\",\n\t\t\t\t\t\"images/debian-rootfs/al\",\n\t\t\t\t\t\"images/debian-rootfs/am\",\n\t\t\t\t\t\"images/debian-rootfs/an\",\n\t\t\t\t\t\"images/debian-rootfs/ao\",\n\t\t\t\t\t\"images/debian-rootfs/ap\",\n\t\t\t\t\t\"images/debian-rootfs/aq\",\n\t\t\t\t\t\"images/debian-rootfs/ar\",\n\t\t\t\t\t\"images/debian-rootfs/as\",\n\t\t\t\t\t\"images/debian-rootfs/at\",\n\t\t\t\t\t\"images/debian-rootfs/au\",\n\t\t\t\t\t\"images/debian-rootfs/av\",\n\t\t\t\t\t\"images/debian-rootfs/aw\",\n\t\t\t\t\t\"images/debian-rootfs/ax\",\n\t\t\t\t\t\"images/debian-rootfs/ay\",\n\t\t\t\t\t\"images/debian-rootfs/az\",\n\t\t\t\t\t\"images/debian-rootfs/ba\",\n\t\t\t\t\t\"images/debian-rootfs/bb\",\n\t\t\t\t\t\"images/debian-rootfs/bc\",\n\t\t\t\t\t\"images/debian-rootfs/bd\",\n\t\t\t\t\t\"images/debian-rootfs/be\",\n\t\t\t\t\t\"images/debian-rootfs/bf\",\n\t\t\t\t\t\"images/debian-rootfs/bg\",\n\t\t\t\t\t\"images/debian-rootfs/bh\",\n\t\t\t\t\t\"images/debian-rootfs/bi\",\n\t\t\t\t\t\"images/debian-rootfs/bj\",\n\t\t\t\t\t\"images/debian-rootfs/bk\",\n\t\t\t\t\t\"images/debian-rootfs/bl\",\n\t\t\t\t\t\"images/debian-rootfs/bm\",\n\t\t\t\t\t\"images/debian-rootfs/bn\",\n\t\t\t\t\t\"images/debian-rootfs/bo\",\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t});\n\twindow.anura = anura;\n\t// @ts-expect-error For backwards compatibility\n\twindow.anura.fs.Shell = window.tfs.sh;\n\twindow.AliceWM = AliceWM;\n\twindow.LocalFS = LocalFS;\n\twindow.ExternalApp = ExternalApp;\n\twindow.ExternalLib = ExternalLib;\n\twindow.electron = new Lemonade();\n\twindow.tb.libcurl.load_wasm(\"https://cdn.jsdelivr.net/npm/libcurl.js@latest/libcurl.wasm\");\n\tconst getupds = async () => {\n\t\tif (hash !== (await window.tb.fs.promises.readFile(\"/system/etc/terbium/hash.cache\", \"utf8\"))) {\n\t\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/hash.cache\", \"invalid\");\n\t\t\twindow.tb.notification.Toast({\n\t\t\t\tapplication: \"System\",\n\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\tmessage: \"A new version of terbium is ready to install\",\n\t\t\t\tonOk: async () => {\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t};\n\tif (!(await fileExists(\"/system/etc/terbium/hash.cache\"))) {\n\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/hash.cache\", \"invalid\");\n\t\twindow.tb.notification.Toast({\n\t\t\tapplication: \"System\",\n\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\tmessage: \"A new version of terbium is ready to install\",\n\t\t\tonOk: async () => {\n\t\t\t\twindow.location.reload();\n\t\t\t},\n\t\t});\n\t} else {\n\t\tgetupds();\n\t}\n\tsetInterval(() => {\n\t\tgetupds();\n\t}, 300000);\n\tconst libcurlload = (srv: any) => {\n\t\twindow.tb.libcurl.set_websocket(srv);\n\t};\n\tconst wsld = async () => {\n\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\"));\n\t\tif (settings.wispServer === null) {\n\t\t\tlibcurlload(`${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`);\n\t\t} else {\n\t\t\tlibcurlload(settings.wispServer);\n\t\t}\n\t};\n\tlet triggered = false;\n\tconst down = (e: KeyboardEvent) => {\n\t\tif (e.altKey && e.shiftKey) {\n\t\t\tif (!triggered) {\n\t\t\t\twindow.tb.screen.captureScreen();\n\t\t\t\ttriggered = true;\n\t\t\t}\n\t\t}\n\t};\n\tconst up = (e: KeyboardEvent) => {\n\t\tif (!e.altKey || !e.shiftKey) {\n\t\t\ttriggered = false;\n\t\t}\n\t};\n\tdocument.addEventListener(\"keydown\", down);\n\tdocument.addEventListener(\"keyup\", up);\n\twsld();\n\tawait window.tb.proxy.updateSWs();\n\tconst getchangelog = async () => {\n\t\tconst reCache: Record<string, { hash: string; changeFile: string }> = await (await window.tb.libcurl.fetch(\"https://cdn.terbiumon.top/changelogs/versions.json\")).json();\n\t\tconst vInf = reCache[system.version(\"string\") as string];\n\t\tif (hash === vInf.hash) {\n\t\t\twindow.tb.window.create({\n\t\t\t\ttitle: \"Changelog\",\n\t\t\t\tsrc: vInf.changeFile,\n\t\t\t\ticon: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\tsize: {\n\t\t\t\t\twidth: 600,\n\t\t\t\t\theight: 400,\n\t\t\t\t},\n\t\t\t\tproxy: true,\n\t\t\t});\n\t\t}\n\t};\n\tif (sessionStorage.getItem(\"justUpdated\") === \"true\") {\n\t\tgetchangelog();\n\t\tsessionStorage.removeItem(\"justUpdated\");\n\t}\n\tif (await window.tb.tauth.isTACC()) {\n\t\tawait window.tb.tauth.sync.retreive();\n\t\twindow.tb.fs.watch(`/home/${await window.tb.user.username()}/settings.json`, { recursive: true }, (e: string, _f: string) => {\n\t\t\tif (e === \"change\" && window.tb.tauth.sync.isSyncing === false) {\n\t\t\t\twindow.tb.tauth.sync.upload();\n\t\t\t}\n\t\t});\n\t\twindow.tb.fs.watch(`/apps/user/${await window.tb.user.username()}/files/davs.json`, { recursive: true }, (e: string, _f: string) => {\n\t\t\tif (e === \"change\" && window.tb.tauth.sync.isSyncing === false) {\n\t\t\t\twindow.tb.tauth.sync.upload();\n\t\t\t}\n\t\t});\n\t\twindow.tb.fs.watch(`/apps/user/${await window.tb.user.username()}/app store/repos.json`, { recursive: true }, (e: string, _f: string) => {\n\t\t\tif (e === \"change\" && window.tb.tauth.sync.isSyncing === false) {\n\t\t\t\twindow.tb.tauth.sync.upload();\n\t\t\t}\n\t\t});\n\t}\n\tlaunchProcs();\n\tdocument.addEventListener(\"libcurl_load\", wsld);\n\twindow.tb.node.webContainer = await initializeWebContainer();\n}\n"
  },
  {
    "path": "src/sys/Filer.d.ts",
    "content": "declare let Filer: FilerType;\ndeclare let $el: any;\n\n// Note: this is different from the Anura Filesystem type because file descriptors are internally stored as numbers rather than the AnuraFD type.\n// This should still be fully compatible as file descriptors are obtained from other methods and are not created directly. This will only be a\n// problem if someone for some reason tries to create a file descriptor manually or did some external logic based on the file descriptor being a\n// number.\ntype FilerFS = {\n\twatch(filename: string, listener: (event: string, filename: string) => void, options?: { recursive: boolean }): void;\n\tShell: {\n\t\tnew (): {\n\t\t\tcd: (t: string, r?: any) => void;\n\t\t\tpwd: () => string;\n\t\t\tenv: () => Record<string, string>;\n\t\t\tfs: () => any;\n\t\t\trm: (directory: string, options?: { recursive: boolean; force: boolean }) => void;\n\t\t\tpromises: {\n\t\t\t\tcat: () => Promise<string>;\n\t\t\t\tcd: (t: string, r?: any) => Promise<void>;\n\t\t\t\texec: (command: string) => Promise<any>;\n\t\t\t\tfind: (\n\t\t\t\t\tpath: string,\n\t\t\t\t\toptions?: {\n\t\t\t\t\t\tname?: string;\n\t\t\t\t\t\tregex?: RegExp | string;\n\t\t\t\t\t\texec?: boolean | Function<any>;\n\t\t\t\t\t},\n\t\t\t\t) => Promise<any>;\n\t\t\t\tls: (dir: string) => Promise<string[]>;\n\t\t\t\tmkdirp: (dir: string) => Promise<void>;\n\t\t\t\trm: (path: string, options?: { recursive?: boolean; force?: boolean }) => Promise<void>;\n\t\t\t\ttempDir: () => Promise<string>;\n\t\t\t\ttouch: (filePath: string) => Promise<void>;\n\t\t\t};\n\t\t};\n\t};\n\n\trename(oldPath: string, newPath: string, callback?: (err: Error | null) => void): void;\n\n\tftruncate(fd: number, len: number, callback?: (err: Error | null, fd: number) => void): void;\n\n\ttruncate(path: string, len: number, callback?: (err: Error | null) => void): void;\n\n\tstat(path: string, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\tfstat(fd: number, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\tlstat(path: string, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\t/** @deprecated fs.exists() is an anachronism and exists only for historical reasons. */\n\texists(path: string, callback?: (exists: boolean) => void): void;\n\n\tlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void): void;\n\n\tsymlink(srcPath: string, dstPath: string, type: string, callback?: (err: Error | null) => void): void;\n\n\tsymlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void): void;\n\n\treadlink(path: string, callback?: (err: Error | null, linkContents: string) => void): void;\n\n\tunlink(path: string, callback?: (err: Error | null) => void): void;\n\n\tmknod(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\trmdir(path: string, callback?: (err: Error | null) => void): void;\n\n\tmkdir(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\tmkdir(path: string, callback?: (err: Error | null) => void): void;\n\n\taccess(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\taccess(path: string, callback?: (err: Error | null) => void): void;\n\n\tmkdtemp(prefix: string, options: { encoding: string } | string, callback?: (err: Error | null, path: string) => void): void;\n\n\tmkdtemp(prefix: string, callback?: (err: Error | null, path: string) => void): void;\n\n\treaddir(path: string, options: { encoding: string; withFileTypes: boolean } | string, callback?: (err: Error | null, files: string[]) => void): void;\n\n\treaddir(path: string, callback?: (err: Error | null, files: string[]) => void): void;\n\n\tclose(fd: number, callback?: (err: Error | null) => void): void;\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode: number, callback?: (err: Error | null, fd: number) => void): void;\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", callback?: (err: Error | null, fd: number) => void): void;\n\n\tutimes(path: string, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void): void;\n\n\tfutimes(fd: number, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void): void;\n\n\tchown(path: string, uid: number, gid: number, callback?: (err: Error | null) => void): void;\n\n\tfchown(fd: number, uid: number, gid: number, callback?: (err: Error | null) => void): void;\n\n\tchmod(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\tfchmod(fd: number, mode: number, callback?: (err: Error | null) => void): void;\n\n\tfsync(fd: number, callback?: (err: Error | null) => void): void;\n\n\twrite(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, nbytes: number) => void): void;\n\n\tread(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, nbytes: number, buffer: Uint8Array) => void): void;\n\n\treadFile(path: string, callback?: (err: Error | null, data: Uint8Array) => void): void;\n\n\twriteFile(path: string, data: Uint8Array | string, options: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string, callback?: (err: Error | null) => void): void;\n\n\twriteFile(path: string, data: Uint8Array | string, callback?: (err: Error | null) => void): void;\n\n\tappendFile(path: string, data: Uint8Array, callback?: (err: Error | null) => void): void;\n\n\tsetxattr(path: string, name: string, value: string | object, flag: \"CREATE\" | \"REPLACE\", callback?: (err: Error | null) => void): void;\n\n\tsetxattr(path: string, name: string, value: string | object, callback?: (err: Error | null) => void): void;\n\n\tfsetxattr(fd: number, name: string, value: string | object, flag: \"CREATE\" | \"REPLACE\", callback?: (err: Error | null) => void): void;\n\n\tfsetxattr(fd: number, name: string, value: string | object, callback?: (err: Error | null) => void): void;\n\n\tgetxattr(path: string, name: string, callback?: (err: Error | null, value: string | object) => void): void;\n\n\tfgetxattr(fd: number, name: string, callback?: (err: Error | null, value: string | object) => void): void;\n\n\tremovexattr(path: string, name: string, callback?: (err: Error | null) => void): void;\n\n\tfremovexattr(fd: number, name: string, callback?: (err: Error | null) => void): void;\n\n\t/*\n\t * Asynchronous FS operations\n\t */\n\n\tpromises: {\n\t\tappendFile(path: string, data: Uint8Array, options: { encoding: string; mode: number; flag: string }): Promise<void>;\n\t\taccess(path: string, mode?: number): Promise<void>;\n\t\tchown(path: string, uid: number, gid: number): Promise<void>;\n\t\tchmod(path: string, mode: number): Promise<void>;\n\t\tgetxattr(path: string, name: string): Promise<string | object>;\n\t\tlink(srcPath: string, dstPath: string): Promise<void>;\n\t\tlstat(path: string): Promise<TStats>;\n\t\tmkdir(path: string, mode?: number): Promise<void>;\n\t\tmkdtemp(prefix: string, options?: { encoding: string }): Promise<string>;\n\t\tmknod(path: string, mode: number): Promise<void>;\n\t\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number): Promise<number>;\n\t\treaddir(path: string, options?: string | { encoding: string; withFileTypes: boolean }): Promise<string[]>;\n\t\treadFile(path: string, encoding?: string): Promise<any>;\n\t\treadlink(path: string): Promise<string>;\n\t\tremovexattr(path: string, name: string): Promise<void>;\n\t\trename(oldPath: string, newPath: string): Promise<void>;\n\t\trmdir(path: string): Promise<void>;\n\t\tsetxattr(path: string, name: string, value: string | object, flag?: \"CREATE\" | \"REPLACE\"): Promise<void>;\n\t\tstat(path: string, callback?: void | any): Promise<TStats>;\n\t\tsymlink(srcPath: string, dstPath: string, type?: string): Promise<void>;\n\t\ttruncate(path: string, len: number): Promise<void>;\n\t\tunlink(path: string): Promise<void>;\n\t\tutimes(path: string, atime: number | Date, mtime: number | Date): Promise<void>;\n\t\twriteFile(path: string, data: any | string, encoding?: string, mode?: number, flag?: string): Promise<void>;\n\t};\n};\ntype FilerType = {\n\tfs: FilerFS;\n\tpromises: FilerFS.promises;\n\tBuffer: any;\n\tPath: any;\n\tFileSystem: FilerFS.constructor;\n};\n"
  },
  {
    "path": "src/sys/FilerWP.d.ts",
    "content": "interface TStats {\n\tnode: string; // internal node id (unique)\n\tdev: string; // file system name\n\tname: string; // the entry's name (basename)\n\tsize: number; // file size in bytes\n\tnlinks: number; // number of links\n\tatime: Date; // last access time as JS Date Object\n\tmtime: Date; // last modification time as JS Date Object\n\tctime: Date; // creation time as JS Date Object\n\tatimeMs: number; // last access time as Unix Timestamp\n\tmtimeMs: number; // last modification time as Unix Timestamp\n\tctimeMs: number; // creation time as Unix Timestamp\n\ttype: string; // file type (FILE, DIRECTORY, SYMLINK)\n\tgid: number; // group name\n\tuid: number; // owner name\n\tmode: number; // permission\n\tversion: number; // version of the node\n\n\tisFile(): boolean; // Returns true if the node is a file.\n\tisDirectory(): boolean; // Returns true if the node is a directory.\n\tisBlockDevice(): boolean; // Not implemented, returns false.\n\tisCharacterDevice(): boolean; // Not implemented, returns false.\n\tisSymbolicLink(): boolean; // Returns true if the node is a symbolic link.\n\tisFIFO(): boolean; // Not implemented, returns false.\n\tisSocket(): boolean; // Not implemented, returns false;\n}\n\ninterface IAccessModes {\n\tF_OK: boolean; // Test for existence of file.\n\tR_OK: boolean; // Test whether the file exists and grants read permission.\n\tW_OK: boolean; // Test whether the file exists and grants write permission.\n\tX_OK: boolean; // Test whether the file exists and grants execute permission.\n}\n\ndeclare namespace Filer {\n\tnamespace fs {\n\t\tnamespace promises {\n\t\t\tfunction rename(oldPath: string, newPath: string): Promise<void>;\n\t\t\tfunction ftruncate(fd: number, len: number): Promise<void>;\n\t\t\tfunction truncate(fd: number, len: number): Promise<void>;\n\t\t\tfunction stat(path: string): TStats;\n\t\t\tfunction fstat(fd: number): TStats;\n\t\t\tfunction lstat(path: string): TStats;\n\t\t\tfunction exists(path: string): boolean;\n\t\t\tfunction link(srcPath: string, dstPath: string): Promise<void>;\n\t\t\tfunction symlink(srcPath: string, dstPath: string): Promise<void>;\n\t\t\tfunction readlink(srcPath: string): string;\n\t\t\tfunction unlink(path: string): Promise<void>;\n\t\t\tfunction mknod(path: string, mode: \"FILE\" | \"DIRECTORY\" | string): Promise<void>;\n\t\t\tfunction rmdir(path: string): Promise<void>;\n\t\t\tfunction mkdir(path: string, mode?: number): Promise<void>;\n\t\t\tfunction access(path: string, mode: IAccessModes): Promise<void>;\n\t\t\tfunction mkdtemp(path: string): string;\n\t\t\tfunction readdir(path: string, options?: { encoding: string; withFileTypes: boolean } | string): string[];\n\t\t\tfunction close(fd: number): void;\n\t\t\tfunction open(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number): number;\n\t\t\tfunction utimes(path: string, atime: number | Date, mtime: number | Date): Promise<void>;\n\t\t\tfunction futimes(fd: number, atime: number | Date, mtime: number | Date): Promise<void>;\n\t\t\tfunction chown(path: string, uid: number, gid: number): Promise<void>;\n\t\t\tfunction fchown(fd: number, uid: number, gid: number): Promise<void>;\n\t\t\tfunction chmod(path: string, mode: number | string): Promise<void>;\n\t\t\tfunction fchmod(fd: number, mode: number | string): Promise<void>;\n\t\t\tfunction fsync(fd: number): Promise<void>;\n\t\t\tfunction write(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null): number;\n\t\t\tfunction read(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null): number;\n\t\t\tfunction readFile(path: string, options?: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string): Uint8Array;\n\t\t\tfunction writeFile(path: string, data: Uint8Array | string, options?: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string): Promise<void>;\n\t\t\tfunction appendFile(filename: string, data: Uint8Array | string, options?: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string): Promise<void>;\n\t\t\tfunction setxattr(path: string, name: string, value: string | object, flag?: \"XATTR_CREATE\" | \"XATTR_REPLACE\" | string): Promise<void>;\n\t\t\tfunction fsetxattr(fd: number, name: string, value: string | object, flag?: \"XATTR_CREATE\" | \"XATTR_REPLACE\" | string): Promise<void>;\n\t\t\tfunction getxattr(path: string, name: string): string | object;\n\t\t\tfunction fgetxattr(fd: number, name: string): string | object;\n\t\t\tfunction removexattr(path: string, name: string): Promise<void>;\n\t\t\tfunction fremovexattr(fd: number, name: string): Promise<void>;\n\t\t}\n\t\t/**\n\t\t * Renames the file at `oldPath` to `newPath`. Asynchronous [rename(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/rename.html). Callback gets no additional arguments.\n\t\t * @see [fs.rename](https://github.com/filerjs/filer?tab=readme-ov-file#rename)\n\t\t */\n\t\tfunction rename(oldPath: string, newPath: string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Change the size of the file represented by the open file descriptor `fd` to be length `len` bytes.\n\n         * Asynchronous [ftruncate(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/ftruncate.html). If the file is larger than `len`, the extra bytes will be discarded;\n         * if smaller, its size will be increased, and the extended area will appear as if it were zero-filled.\n         *\n         * @see [fs.ftruncate](https://github.com/filerjs/filer?tab=readme-ov-file#ftruncate)\n         * @see also [fs.truncate()](https://github.com/filerjs/filer?tab=readme-ov-file#truncate)\n        */\n\t\tfunction ftruncate(fd: number, len: number, callback: (err: Error | null, fd: number) => void): void;\n\n\t\t/**\n         * Change the size of the file at `path` to be length `len` bytes.\n\n         * Asynchronous [truncate(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/truncate.html). If the file is larger than `len`, the extra bytes will be discarded;\n         * if smaller, its size will be increased, and the extended area will appear as if it were zero-filled.\n         * @see [fs.truncate](https://github.com/filerjs/filer?tab=readme-ov-file#truncate)\n         * @see also [fs.ftruncate](https://github.com/filerjs/filer?tab=readme-ov-file#ftruncate)\n         */\n\t\tfunction truncate(fd: number, len: number, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Obtain file status about the file at `path`. Asynchronous [stat(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/stat.html). Callback gets `(error, stats)`, where `stats` is an object with the following properties:\n            * - `node` internal node id (unique)\n            * - `dev` file system name\n            * - `name` the entry's name (basename)\n            * - `size` file size in bytes\n            * - `nlinks` number of links\n            * - `atime` last access time as JS Date Object\n            * - `mtime` last modification time as JS Date Object\n            * - `ctime` creation time as JS Date Object\n            * - `atimeMs` last access time as Unix Timestamp\n            * - `mtimeMs` last modification time as Unix Timestamp\n            * - `ctimeMs` creation time as Unix Timestamp\n            * - `type` file type (FILE, DIRECTORY, SYMLINK)\n            * - `gid` group name\n            * - `uid` owner name\n            * - `mode` permission\n            * - `version` version of the node\n\n            * The following convenience methods are also present on the callback's `stats`:\n            * - `isFile()` Returns true if the node is a file.\n            * - `isDirectory()` Returns true if the node is a directory.\n            * - `isBlockDevice()` Not implemented, returns false.\n            * - `isCharacterDevice()` Not implemented, returns false.\n            * - `isSymbolicLink()` Returns true if the node is a symbolic link.\n            * - `isFIFO()` Not implemented, returns false.\n            * - `isSocket()` Not implemented, returns false.\n\n         * If the file at `path` is a symbolic link, the file to which it links will be used instead. To get the status of a symbolic link file, use [fs.lstat()](https://github.com/filerjs/filer?tab=readme-ov-file#lstat) instead.\n         * @see [fs.stat](https://github.com/filerjs/filer?tab=readme-ov-file#stat)\n         * @see also [fs.lstat](https://github.com/filerjs/filer?tab=readme-ov-file#stat)\n         */\n\t\tfunction stat(path: string, callback: (err: Error | null, stats: TStats) => void): void;\n\n\t\t/**\n         * Obtain information about the open file known by the file descriptor `fd`.\n\n         * Asynchronous [fstat(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/fstat.html). Callback gets `(error, stats)`. `fstat()` is identical to `stat()`, except that the file to be stat-ed is specified by the open file descriptor `fd` instead of a path.\n         * @see [fs.fstat](https://github.com/filerjs/filer?tab=readme-ov-file#fstat)\n         * @see also [fs.stat](https://github.com/filerjs/filer?tab=readme-ov-file#stat)\n         */\n\t\tfunction fstat(fd: number, callback: (err: Error | null, stats: TStats) => void): void;\n\n\t\t/**\n         * Obtain information about the file at `path` (i.e., the symbolic link file itself) vs. the destination file to which it links.\n\n         * Asynchronous [lstat(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/lstat.html). Callback gets `(error, stats)`.\n         * @see [fs.lstat](https://github.com/filerjs/filer?tab=readme-ov-file#lstat)\n         * @see also [fs.stat](https://github.com/filerjs/filer?tab=readme-ov-file#stat)\n         */\n\t\tfunction lstat(path: string, callback: (err: Error | null, stats: TStats) => void): void;\n\n\t\t/**\n\t\t * Test whether or not the given path exists by checking with the file system. Then call the callback argument with either true or false.\n\t\t * @deprecated `fs.exists()` is an anachronism and exists only for historical reasons. There should almost never be a reason to use it in your own code.\n\t\t * @see [fs.exists](https://github.com/filerjs/filer?tab=readme-ov-file#exists)\n\t\t */\n\t\tfunction exists(path: string, callback: (exists: boolean) => void): void;\n\n\t\t/**\n         * Create a (hard) link to the file at `srcPath` named `dstPath`.\n\n         * Asynchronous [link(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/link.html). Callback gets no additional arguments. Links are directory entries that point to the same file node.\n         * @see [fs.link](https://github.com/filerjs/filer?tab=readme-ov-file#link)\n         */\n\t\tfunction link(srcPath: string, dstPath: string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Create a symbolic link to the file at `dstPath` containing the path `srcPath`.\n\n         * Asynchronous [symlink(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/symlink.html). Callback gets no additional arguments. Symbolic links are files that point to other paths.\n         * NOTE: Filer allows for, but ignores the optional `type` parameter used in node.js. The `srcPath` may be a relative path, which will be resolved relative to `dstPath`.\n         * @see [fs.symlink](https://github.com/filerjs/filer?tab=readme-ov-file#symlink)\n         */\n\t\tfunction symlink(srcPath: string, dstPath: string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Reads the contents of a symbolic link.\n\n         * Asynchronous [readlink(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/readlink.html). Callback gets `(error, linkContents)`, where `linkContents` is a string containing the symbolic link's link path.\n         * If the original `srcPath` given to `symlink()` was a relative path, it will be fully resolved relative to `dstPath` when returned by `readlink()`.\n         * @see [fs.readlink](https://github.com/filerjs/filer?tab=readme-ov-file#readlink)\n         */\n\t\tfunction readlink(srcPath: string, callback: (err: Error | null, linkContents: string) => void): void;\n\n\t\t/**\n         * Removes the directory entry located at `path`.\n\n         * Asynchronous [unlink(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/unlink.html). Callback gets no additional arguments. If `path` names a symbolic link, the symbolic link will be removed (i.e., not the linked file). Otherwise, the filed named by `path` will be removed (i.e., deleted).\n         * @see [fs.unlink](https://github.com/filerjs/filer?tab=readme-ov-file#unlink)\n         */\n\t\tfunction unlink(path: string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Creates a node at `path` based on the mode passed which is either `FILE` or `DIRECTORY`.\n\n         * Asynchronous [mknod(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/mknod.html). Callback gets no additional arguments.\n         * @see [fs.mknod](https://github.com/filerjs/filer?tab=readme-ov-file#mknod)\n         */\n\t\tfunction mknod(path: string, mode: \"FILE\" | \"DIRECTORY\" | string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Removes the directory at `path`.\n\n         * Asynchronous [rmdir(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html). Callback gets no additional arguments. The operation will fail if the directory at `path` is not empty.\n         * @see [fs.rmdir](https://github.com/filerjs/filer?tab=readme-ov-file#rmdir)\n         */\n\t\tfunction rmdir(path: string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Makes a directory with name supplied in `path` argument.\n\n         * Asynchronous [mkdir(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/mkdir.html). Callback gets no additional arguments.\n\n         * NOTE: Filer allows for, but ignores the optional `mode` argument used in node.js.\n         * @see [fs.mkdir](https://github.com/filerjs/filer?tab=readme-ov-file#mkdir)\n         */\n\t\tfunction mkdir(path: string, mode?: number, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Tests a user's permissions for the file or directory supplied in `path` argument.\n\n         * Asynchronous [access(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/access.html). Callback gets no additional arguments. The `mode` argument can be one of the following (constants are available on `fs.constants` and `fs`):\n\n            * - `F_OK`: Test for existence of file.\n            * - `R_OK`: Test whether the file exists and grants read permission.\n            * - `W_OK`: Test whether the file exists and grants write permission.\n            * - `X_OK`: Test whether the file exists and grants execute permission.\n\n         * NOTE: you can also create a mask consisting of the bitwise OR of two or more values (e.g. `fs.constants.W_OK | fs.constants.R_OK`).\n         * @see [fs.access](https://github.com/filerjs/filer?tab=readme-ov-file#access)\n         */\n\t\tfunction access(path: string, mode: IAccessModes, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Makes a temporary directory with prefix supplied in `path` argument. Method will append six random characters directly to the prefix. Asynchronous. Callback gets `(error, path)`, where path is the path to the created directory.\n\n         * NOTE: Filer allows for, but ignores the `optional` options argument used in node.js.\n         * @see [fs.mkdtemp](https://github.com/filerjs/filer?tab=readme-ov-file#mkdtemp)\n         */\n\t\tfunction mkdtemp(path: string, callback: (err: Error | null, path: string) => void): void;\n\n\t\t/**\n         * Reads the contents of a directory.\n\n         * Asynchronous [readdir(3)](http://pubs.opengroup.org/onlinepubs/009695399/functions/readdir.html). Callback gets `(error, files)`, where `files` is an array containing the names of each directory entry (i.e., file, directory, link) in the directory, excluding `.` and `..`.\n\n         * Optionally accepts an options parameter, which can be either an encoding (e.g. \"utf8\") or an object with optional properties `encoding` and `withFileTypes`.\n\n         * The `encoding` property is a `string` which will determine the character encoding to use for the names of each directory entry. The `withFileTypes` property is a `boolean` which defaults to `false`. If `true`, this method will return an array of [fs.Dirent](https://nodejs.org/api/fs.html#fs_class_fs_dirent) objects.\n\n         * The `name` property on the [fs.Dirent](https://nodejs.org/api/fs.html#fs_class_fs_dirent) objects will be encoded using the specified character encoding.\n         * @see [fs.readdir](https://github.com/filerjs/filer?tab=readme-ov-file#readdir)\n         */\n\t\tfunction readdir(path: string, options?: { encoding: string; withFileTypes: boolean } | string, callback: (err: Error | null, files: string[]) => void): void;\n\n\t\t/**\n         * Closes a file descriptor.\n\n         * Asynchronous [close(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/close.html). Callback gets no additional arguments.\n         * @see [fs.close](https://github.com/filerjs/filer?tab=readme-ov-file#close)\n         */\n\t\tfunction close(fd: number, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Opens a file.\n\n         * Asynchronous [open(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/open.html). Callback gets `(error, fd)`, where `fd` is the file descriptor. The `flags` argument can be:\n\n            * - `'r'`: Open file for reading. An exception occurs if the file does not exist.\n            * - `'r'`: Open file for reading. An exception occurs if the file does not exist.\n            * - `'r+'`: Open file for reading and writing. An exception occurs if the file does not exist.\n            * - `'w'`: Open file for writing. The file is created (if it does not exist) or truncated (if it exists).\n            * - `'w+'`: Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists).\n            * - `'a'`: Open file for appending. The file is created if it does not exist.\n            * - `'a+'`: Open file for reading and appending. The file is created if it does not exist.\n\n         * NOTE: Filer allows for, but ignores the optional `mode` argument used in node.js.\n         * @see [fs.open](https://github.com/filerjs/filer?tab=readme-ov-file#open)\n         */\n\t\tfunction open(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number, callback: (err: Error | null, fd: number) => void): void;\n\n\t\t/**\n         * Changes the file timestamps for the file given at path `path`.\n\n         * Asynchronous [utimes(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/utimes.html). Callback gets no additional arguments. Both `atime` (access time) and `mtime` (modified time) arguments should be a JavaScript Date or Number.\n         * @see [fs.utimes](https://github.com/filerjs/filer?tab=readme-ov-file#utimes)\n         */\n\t\tfunction utimes(path: string, atime: number | Date, mtime: number | Date, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Changes the file timestamps for the open file represented by the file descriptor `fd`.\n\n         * Asynchronous [utimes(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/utimes.html). Callback gets no additional arguments. Both `atime` (access time) and `mtime` (modified time) arguments should be a JavaScript Date or Number.\n         * @see [fs.futimes](https://github.com/filerjs/filer?tab=readme-ov-file#futimes)\n         */\n\t\tfunction futimes(fd: number, atime: number | Date, mtime: number | Date, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Changes the owner and group of a file.\n\n         * Asynchronous [chown(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chown.html). Callback gets no additional arguments. Both `uid` (user id) and `gid` (group id) arguments should be a JavaScript Number. By default, `0x0` is used (i.e., `root:root` ownership).\n         * @see [fs.chown](https://github.com/filerjs/filer?tab=readme-ov-file#chown)\n         */\n\t\tfunction chown(path: string, uid: number, gid: number, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Changes the owner and group of a file.\n\n         * Asynchronous [chown(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chown.html). Callback gets no additional arguments. Both `uid` (user id) and `gid` (group id) arguments should be a JavaScript Number. By default, `0x0` is used (i.e., `root:root` ownership).\n         * @see [fs.fchown](https://github.com/filerjs/filer?tab=readme-ov-file#fchown)\n         */\n\t\tfunction fchown(fd: number, uid: number, gid: number, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Changes the mode of a file.\n\n         * Asynchronous [chmod(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chmod.html). Callback gets no additional arguments. The `mode` argument should be a JavaScript Number, which combines file type and permission information. Here are a list of common values useful for setting the `mode`:\n            * - File type `S_IFREG=0x8000`\n            * - Dir type `S_IFDIR=0x4000`\n            * - Link type `S_IFLNK=0xA000`\n            * - Permissions `755=0x1ED`\n            * - Permissions `644=0x1A4`\n            * - Permissions `777=0x1FF`\n            * - Permissions `666=0x1B6`\n\n            * By default, directories use `(0x4000 | 0x1ED)` and files use `(0x8000 | 0x1A4)`.\n         * @see [fs.chmod](https://github.com/filerjs/filer?tab=readme-ov-file#chmod)\n         */\n\t\tfunction chmod(path: string, mode: number | string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Changes the mode of a file.\n\n         * Asynchronous [chmod(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chmod.html). Callback gets no additional arguments. The `mode` argument should be a JavaScript Number, which combines file type and permission information. By default, `755` (dir) and `644` (file) are used.\n         * @see [fs.fchmod](https://github.com/filerjs/filer?tab=readme-ov-file#fchmod)\n         */\n\t\tfunction fchmod(fd: number, mode: number | string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Synchronize the data and metadata for the file referred to by `fd` to disk.\n\n         * Asynchronous [fsync(2)](http://man7.org/linux/man-pages/man2/fsync.2.html). The callback gets `(error)`.\n         * @see [fs.fsync](https://github.com/filerjs/filer?tab=readme-ov-file#fsync)\n         */\n\t\tfunction fsync(fd: number, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Writes bytes from `buffer` to the file specified by `fd`.\n\n         * Asynchronous [write(2), pwrite(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html). The `offset` and `length` arguments describe the part of the buffer to be written. The `position` refers to the offset from the beginning of the file where this data should be written. If `position` is `null`, the data will be written at the current position. The callback gets `(error, nbytes)`, where `nbytes` is the number of bytes written.\n\n         * NOTE: Filer currently writes the entire buffer in a single operation. However, future versions may do it in chunks.\n         * @see [fs.write](https://github.com/filerjs/filer?tab=readme-ov-file#write)\n         */\n\t\tfunction write(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null, callback: (err: Error | null, nbytes: number) => void): void;\n\n\t\t/**\n         * Read bytes from the file specified by `fd` into `buffer`.\n\n         * Asynchronous [read(2), pread(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html). The `offset` and `length` arguments describe the part of the buffer to be used. The `position` refers to the offset from the beginning of the file where this data should be read. If `position` is `null`, the data will be written at the current position. The callback gets `(error, nbytes)`, where `nbytes` is the number of bytes read.\n\n         * NOTE: Filer currently reads into the buffer in a single operation. However, future versions may do it in chunks.\n         * @see [fs.read](https://github.com/filerjs/filer?tab=readme-ov-file#read)\n         */\n\t\tfunction read(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null, callback: (err: Error | null, nbytes: number, buffer: Uint8Array) => void): void;\n\n\t\t/**\n\t\t * Reads the entire contents of a file. The `options` argument is optional, and can take the form `\"utf8\"` (i.e., an encoding) or be an object literal: `{ encoding: \"utf8\", flag: \"r\" }`. If no encoding is specified, the raw binary buffer is returned via the callback. The callback gets `(error, data)`, where data is the contents of the file.\n\t\t * @see [fs.readFile](https://github.com/filerjs/filer?tab=readme-ov-file#readfile)\n\t\t */\n\t\tfunction readFile(path: string, options?: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string, callback: (err: Error | null, data: Uint8Array) => void): void;\n\n\t\t/**\n\t\t * Writes data to a file. `data` can be a string or `Buffer`, in which case any encoding option is ignored. The `options` argument is optional, and can take the form `\"utf8\"` (i.e., an encoding) or be an object literal: `{ encoding: \"utf8\", flag: \"w\" }`. If no encoding is specified, and `data` is a string, the encoding defaults to `'utf8'`. The callback gets `(error)`.\n\t\t * @see [fs.writeFile](https://github.com/filerjs/filer?tab=readme-ov-file#writefile)\n\t\t */\n\t\tfunction writeFile(path: string, data: Uint8Array | string, options?: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n\t\t * Writes data to the end of a file. `data` can be a string or a `Buffer`, in which case any encoding option is ignored. The `options` argument is optional, and can take the form `\"utf8\"` (i.e., an encoding) or be an object literal: `{ encoding: \"utf8\", flag: \"w\" }`. If no encoding is specified, and `data` is a string, the encoding defaults to `'utf8'`. The callback gets `(error)`.\n\t\t * @see [fs.appendFile](https://github.com/filerjs/filer?tab=readme-ov-file#appendfile)\n\t\t */\n\t\tfunction appendFile(filename: string, data: Uint8Array | string, options?: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Sets an extended attribute of a file or directory named `path`.\n\n         * Asynchronous [setxattr(2)](http://man7.org/linux/man-pages/man2/setxattr.2.html). The optional `flag` parameter can be set to the following:\n\n            * - `XATTR_CREATE`: ensures that the extended attribute with the given name will be new and not previously set. If an attribute with the given name already exists, it will return an `EExists` error to the callback.\n            * - `XATTR_REPLACE`: ensures that an extended attribute with the given name already exists. If an attribute with the given name does not exist, it will return an `ENoAttr` error to the callback.\n\n         * Callback gets no additional arguments.\n         * @see [fs.setxattr](https://github.com/filerjs/filer?tab=readme-ov-file#setxattr)\n         */\n\t\tfunction setxattr(path: string, name: string, value: string | object, flag?: \"XATTR_CREATE\" | \"XATTR_REPLACE\" | string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Sets an extended attribute of the file represented by the open file descriptor `fd`.\n\n         * Asynchronous [setxattr(2)](http://man7.org/linux/man-pages/man2/setxattr.2.html). See `fs.setxattr` for more details. Callback gets no additional arguments.\n         * @see [fs.fsetxattr](https://github.com/filerjs/filer?tab=readme-ov-file#fsetxattr)\n         */\n\t\tfunction fsetxattr(fd: number, name: string, value: string | object, flag?: \"XATTR_CREATE\" | \"XATTR_REPLACE\" | string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Gets an extended attribute value for a file or directory.\n\n         * Asynchronous [getxattr(2)](http://man7.org/linux/man-pages/man2/getxattr.2.html). Callback gets `(error, value)`, where `value` is the value for the extended attribute named `name`.\n         * @see [fs.getxattr](https://github.com/filerjs/filer?tab=readme-ov-file#getxattr)\n         */\n\t\tfunction getxattr(path: string, name: string, callback: (err: Error | null, value: string | object) => void): void;\n\n\t\t/**\n         * Gets an extended attribute value for the file represented by the open file descriptor `fd`.\n\n         * Asynchronous [getxattr(2)](http://man7.org/linux/man-pages/man2/getxattr.2.html). See `fs.getxattr` for more details. Callback gets `(error, value)`, where `value` is the value for the extended attribute named `name`.\n         * @see [fs.fgetxattr](https://github.com/filerjs/filer?tab=readme-ov-file#fgetxattr)\n         */\n\t\tfunction fgetxattr(fd: number, name: string, callback: (err: Error | null, value: string | object) => void): void;\n\n\t\t/**\n         * Removes the extended attribute identified by `name` for the file given at `path`.\n\n         * Asynchronous [removexattr(2)](http://man7.org/linux/man-pages/man2/removexattr.2.html). Callback gets no additional arguments.\n         * @see [fs.removexattr](https://github.com/filerjs/filer?tab=readme-ov-file#removexattr)\n         */\n\t\tfunction removexattr(path: string, name: string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Removes the extended attribute identified by `name` for the file represented by the open file descriptor `fd`.\n\n         * Asynchronous [removexattr(2)](http://man7.org/linux/man-pages/man2/removexattr.2.html). See `fs.removexattr` for more details. Callback gets no additional arguments.\n         * @see [fs.fremovexattr](https://github.com/filerjs/filer?tab=readme-ov-file#fremovexattr)\n         */\n\t\tfunction fremovexattr(fd: number, name: string, callback: (err: Error | null) => void): void;\n\n\t\t/**\n         * Watch for changes to a file or directory at filename. The object returned is an FSWatcher, which is an [`EventEmitter`](http://nodejs.org/api/events.html)` with the following additional method:\n\n            * - `close()` - stops listening for changes, and removes all listeners from this instance. Use this to stop watching a file or directory after calling `fs.watch()`.\n\n         * The only supported option is `recursive`, which if `true` will cause a watch to be placed on a directory, and all sub-directories and files beneath it.\n\n         * The `listener` callback gets two arguments `(event, filename)`. `event` is either `'rename'` or `'change'`, (currenty only `'rename'` is supported) and `filename` is the name of the file/dir which triggered the event.\n\n         * Unlike node.js, all watch events return a path. Also, all returned paths are absolute from the root vs. just a relative filename.\n         * @see [fs.watch](https://github.com/filerjs/filer?tab=readme-ov-file#watch)\n         */\n\t\tfunction watch(filename: string, options?: { recursive: boolean } | string, listener: (event: string, filename: string) => void): void;\n\t}\n}\n"
  },
  {
    "path": "src/sys/Node/runtimes/Webcontainers/nodeFSIntegration.ts",
    "content": ""
  },
  {
    "path": "src/sys/Node/runtimes/Webcontainers/nodeProc.ts",
    "content": "import { WebContainer } from \"@webcontainer/api\";\nimport getFileTree from \"./util/getFileTree\";\n\n/**\n * Initialize and boot WebContainer with mounted file tree mirrored from Terbium's FS\n * @returns Promise resolving to WebContainer instance\n */\nexport async function initializeWebContainer(): Promise<WebContainer> {\n\tconst webContainer = await WebContainer.boot();\n\n\t// Start the Nodebox runtime and setup FS mirroring from Terbium's FS on it\n\tconst fileTree = await getFileTree();\n\tawait webContainer.mount(fileTree);\n\n\tif (!window.tb.node.servers) {\n\t\twindow.tb.node.servers = new Map<number, string>();\n\t}\n\n\twebContainer.on(\"server-ready\", (port, url) => {\n\t\twindow.tb.node.servers.set(port, url);\n\t\tconsole.info(`[Node.js Subsystem] Server ready on port ${port}: ${url}`);\n\t});\n\n\tconsole.info(\"[Node.js Subsystem] WebContainer has been initialized!\");\n\twindow.tb.node.isReady = true;\n\twindow.tb.process.create(\"runtime\", {\n\t\tname: \"Terbium Node.js Runtime\",\n\t\twid: null,\n\t\tsrc: null,\n\t\tsize: null,\n\t\ticon: null,\n\t\tonKill: () => {\n\t\t\twindow.tb.node.stop();\n\t\t},\n\t});\n\n\treturn webContainer;\n}\n"
  },
  {
    "path": "src/sys/Node/runtimes/Webcontainers/util/getFileTree.ts",
    "content": "/**\n * @module getFileTree\n * A WebContainers-compatible version that adapts the generic file tree used in the Nodebox implementation\n * @see https://webcontainers.io/guides/working-with-the-file-system\n */\n\nimport getFileTreeGeneric from \"../../util/getFileTree\";\nimport type { FileSystemTree, DirectoryNode, FileNode } from \"@webcontainer/api\";\n\n/**\n * Converts a flat file tree (path -> contents) to WebContainer's FileSystemTree format\n * @param flatTree - Flat tree with full paths as keys and file contents as values\n * @returns FileSystemTree object compatible with WebContainers\n */\nfunction convertToWebContainerTree(flatTree: Record<string, string>): FileSystemTree {\n\tconst tree: FileSystemTree = {};\n\n\tfor (const [path, contents] of Object.entries(flatTree)) {\n\t\tconst pathParts = path.replace(/^\\//, \"\").split(\"/\").filter(Boolean);\n\n\t\tif (pathParts.length === 0) continue;\n\n\t\tlet currentLevel = tree;\n\n\t\tfor (let i = 0; i < pathParts.length - 1; i++) {\n\t\t\tconst dirName = pathParts[i];\n\n\t\t\tif (!currentLevel[dirName]) {\n\t\t\t\t// Create a new directory node\n\t\t\t\tcurrentLevel[dirName] = {\n\t\t\t\t\tdirectory: {},\n\t\t\t\t} as DirectoryNode;\n\t\t\t}\n\n\t\t\t// Advance to the next level\n\t\t\tcurrentLevel = (currentLevel[dirName] as DirectoryNode).directory;\n\t\t}\n\n\t\t// Attach the file contents to the final level\n\t\tconst fileName = pathParts[pathParts.length - 1];\n\t\tcurrentLevel[fileName] = {\n\t\t\tfile: {\n\t\t\t\tcontents,\n\t\t\t},\n\t\t} as FileNode;\n\t}\n\n\treturn tree;\n}\n\n/**\n * Builds a file tree-compatible with WebContainers by adapting the generic file tree\n * @param path The path to build the tree from\n * @returns FileSystemTree An object representing the file tree for WebContainers\n */\nexport default async function getFileTree(path = \"/\"): Promise<FileSystemTree> {\n\t// Get the flat file tree from the generic function\n\tconst flatTree = await getFileTreeGeneric(path);\n\n\t// Convert to the WebContainers format\n\tconst webContainerTree = convertToWebContainerTree(flatTree);\n\n\tif (path !== \"/\" && path !== \"\") {\n\t\tconst pathParts = path.replace(/^\\//, \"\").split(\"/\").filter(Boolean);\n\t\tlet subtree = webContainerTree;\n\n\t\t// Navigate to the requested path\n\t\tfor (const part of pathParts) {\n\t\t\tif (subtree[part] && \"directory\" in subtree[part]) {\n\t\t\t\t// Navigate\n\t\t\t\tsubtree = (subtree[part] as DirectoryNode).directory;\n\t\t\t} else {\n\t\t\t\treturn {};\n\t\t\t}\n\t\t}\n\n\t\treturn subtree;\n\t}\n\n\treturn webContainerTree;\n}\n"
  },
  {
    "path": "src/sys/Node/runtimes/shims/apis/child_process.ts",
    "content": "/**\n * The JSDoc is taken from Node itself\n */\n\nimport type * as NodeChildProcess from \"node:child_process\";\nimport type { ObjectEncodingOptions as NodeObjectEncodingOptions } from \"node:fs\";\nimport type { Readable, Writable } from \"node:stream\";\n\n// Re-export the types from Node\nexport type {\n\tChildProcess,\n\tChildProcessWithoutNullStreams,\n\tChildProcessByStdio,\n\tExecOptions,\n\tExecException,\n\tExecFileOptions,\n\tExecFileException,\n\tExecFileOptionsWithBufferEncoding,\n\tExecFileOptionsWithStringEncoding,\n\tExecFileOptionsWithOtherEncoding,\n\tExecSyncOptions,\n\tExecSyncOptionsWithStringEncoding,\n\tExecSyncOptionsWithBufferEncoding,\n\tExecFileSyncOptions,\n\tExecFileSyncOptionsWithStringEncoding,\n\tExecFileSyncOptionsWithBufferEncoding,\n\tSpawnOptions,\n\tSpawnOptionsWithoutStdio,\n\tSpawnOptionsWithStdioTuple,\n\tSpawnSyncOptions,\n\tSpawnSyncOptionsWithStringEncoding,\n\tSpawnSyncOptionsWithBufferEncoding,\n\tSpawnSyncReturns,\n\tForkOptions,\n\tPromiseWithChild,\n\tStdioPipe,\n\tStdioNull,\n} from \"node:child_process\";\n\n/**\n * Spawns a shell then executes the `command` within that shell, buffering any\n * generated output. The `command` string passed to the exec function is processed\n * directly by the shell and special characters (vary based on [shell](https://en.wikipedia.org/wiki/List_of_command-line_interpreters))\n * need to be dealt with accordingly:\n *\n * ```js\n * import { exec } from 'node:child_process';\n *\n * exec('\"/path/to/test file/test.sh\" arg1 arg2');\n * // Double quotes are used so that the space in the path is not interpreted as\n * // a delimiter of multiple arguments.\n *\n * exec('echo \"The \\\\$HOME variable is $HOME\"');\n * // The $HOME variable is escaped in the first instance, but not in the second.\n * ```\n *\n * **Never pass unsanitized user input to this function. Any input containing shell**\n * **metacharacters may be used to trigger arbitrary command execution.**\n *\n * If a `callback` function is provided, it is called with the arguments `(error, stdout, stderr)`. On success, `error` will be `null`. On error, `error` will be an instance of `Error`. The\n * `error.code` property will be\n * the exit code of the process. By convention, any exit code other than `0` indicates an error. `error.signal` will be the signal that terminated the\n * process.\n *\n * The `stdout` and `stderr` arguments passed to the callback will contain the\n * stdout and stderr output of the child process. By default, Node.js will decode\n * the output as UTF-8 and pass strings to the callback. The `encoding` option\n * can be used to specify the character encoding used to decode the stdout and\n * stderr output. If `encoding` is `'buffer'`, or an unrecognized character\n * encoding, `Buffer` objects will be passed to the callback instead.\n *\n * ```js\n * import { exec } from 'node:child_process';\n * exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {\n *   if (error) {\n *     console.error(`exec error: ${error}`);\n *     return;\n *   }\n *   console.log(`stdout: ${stdout}`);\n *   console.error(`stderr: ${stderr}`);\n * });\n * ```\n *\n * If `timeout` is greater than `0`, the parent will send the signal\n * identified by the `killSignal` property (the default is `'SIGTERM'`) if the\n * child runs longer than `timeout` milliseconds.\n *\n * Unlike the [`exec(3)`](http://man7.org/linux/man-pages/man3/exec.3.html) POSIX system call, `child_process.exec()` does not replace\n * the existing process and uses a shell to execute the command.\n *\n * If this method is invoked as its `util.promisify()` ed version, it returns\n * a `Promise` for an `Object` with `stdout` and `stderr` properties. The returned `ChildProcess` instance is attached to the `Promise` as a `child` property. In\n * case of an error (including any error resulting in an exit code other than 0), a\n * rejected promise is returned, with the same `error` object given in the\n * callback, but with two additional properties `stdout` and `stderr`.\n *\n * ```js\n * import util from 'node:util';\n * import child_process from 'node:child_process';\n * const exec = util.promisify(child_process.exec);\n *\n * async function lsExample() {\n *   const { stdout, stderr } = await exec('ls');\n *   console.log('stdout:', stdout);\n *   console.error('stderr:', stderr);\n * }\n * lsExample();\n * ```\n *\n * If the `signal` option is enabled, calling `.abort()` on the corresponding `AbortController` is similar to calling `.kill()` on the child process except\n * the error passed to the callback will be an `AbortError`:\n *\n * ```js\n * import { exec } from 'node:child_process';\n * const controller = new AbortController();\n * const { signal } = controller;\n * const child = exec('grep ssh', { signal }, (error) => {\n *   console.error(error); // an AbortError\n * });\n * controller.abort();\n * ```\n * @since v0.1.90\n * @param command The command to run, with space-separated arguments.\n * @param callback called with the output when process terminates.\n */\nexport function exec(command: string, callback?: (error: NodeChildProcess.ExecException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function exec(\n\tcommand: string,\n\toptions: {\n\t\tencoding: \"buffer\" | null;\n\t} & NodeChildProcess.ExecOptions,\n\tcallback?: (error: NodeChildProcess.ExecException | null, stdout: Buffer, stderr: Buffer) => void,\n): NodeChildProcess.ChildProcess;\nexport function exec(\n\tcommand: string,\n\toptions: {\n\t\tencoding: BufferEncoding;\n\t} & NodeChildProcess.ExecOptions,\n\tcallback?: (error: NodeChildProcess.ExecException | null, stdout: string, stderr: string) => void,\n): NodeChildProcess.ChildProcess;\nexport function exec(\n\tcommand: string,\n\toptions: {\n\t\tencoding: BufferEncoding;\n\t} & NodeChildProcess.ExecOptions,\n\tcallback?: (error: NodeChildProcess.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void,\n): NodeChildProcess.ChildProcess;\nexport function exec(command: string, options: NodeChildProcess.ExecOptions, callback?: (error: NodeChildProcess.ExecException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function exec(command: string, options: (NodeObjectEncodingOptions & NodeChildProcess.ExecOptions) | undefined | null, callback?: (error: NodeChildProcess.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) => void): NodeChildProcess.ChildProcess;\nexport function exec(\n\t_command: string,\n\t// biome-ignore lint/suspicious/noExplicitAny: I'll figure this out later\n\t..._args: any[]\n): NodeChildProcess.ChildProcess {\n\t// TODO: Implement this stub\n\tthrow new Error(\"execFileSync is not yet implemented\");\n}\n\nexport function execFile(file: string): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, options: (NodeObjectEncodingOptions & NodeChildProcess.ExecFileOptions) | undefined | null): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, args?: readonly string[] | null): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, args: readonly string[] | undefined | null, options: (NodeObjectEncodingOptions & NodeChildProcess.ExecFileOptions) | undefined | null): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, args: readonly string[] | undefined | null, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, options: NodeChildProcess.ExecFileOptionsWithBufferEncoding, callback: (error: NodeChildProcess.ExecFileException | null, stdout: Buffer, stderr: Buffer) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, args: readonly string[] | undefined | null, options: NodeChildProcess.ExecFileOptionsWithBufferEncoding, callback: (error: NodeChildProcess.ExecFileException | null, stdout: Buffer, stderr: Buffer) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, options: NodeChildProcess.ExecFileOptionsWithStringEncoding, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, args: readonly string[] | undefined | null, options: NodeChildProcess.ExecFileOptionsWithStringEncoding, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, options: NodeChildProcess.ExecFileOptionsWithOtherEncoding, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, args: readonly string[] | undefined | null, options: NodeChildProcess.ExecFileOptionsWithOtherEncoding, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, options: NodeChildProcess.ExecFileOptions, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, args: readonly string[] | undefined | null, options: NodeChildProcess.ExecFileOptions, callback: (error: NodeChildProcess.ExecFileException | null, stdout: string, stderr: string) => void): NodeChildProcess.ChildProcess;\nexport function execFile(file: string, options: (NodeObjectEncodingOptions & NodeChildProcess.ExecFileOptions) | undefined | null, callback: ((error: NodeChildProcess.ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void) | undefined | null): NodeChildProcess.ChildProcess;\nexport function execFile(\n\tfile: string,\n\targs: readonly string[] | undefined | null,\n\toptions: (NodeObjectEncodingOptions & NodeChildProcess.ExecFileOptions) | undefined | null,\n\tcallback: ((error: NodeChildProcess.ExecFileException | null, stdout: string | Buffer, stderr: string | Buffer) => void) | undefined | null,\n): NodeChildProcess.ChildProcess;\nexport function execFile(\n\t_file: string,\n\t// biome-ignore lint/suspicious/noExplicitAny: I'll figure this out later\n\t..._args: any[]\n): NodeChildProcess.ChildProcess {\n\t// TODO: Implement this stub\n\tthrow new Error(\"execFile from node:child-process is not yet implemented\");\n}\n\n// spawn function with all Node.js overloads\nexport function spawn(command: string, options?: NodeChildProcess.SpawnOptionsWithoutStdio): NodeChildProcess.ChildProcessWithoutNullStreams;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<Writable, Readable, Readable>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<Writable, Readable, null>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<Writable, null, Readable>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<null, Readable, Readable>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<Writable, null, null>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<null, Readable, null>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<null, null, Readable>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioNull, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<null, null, null>;\nexport function spawn(command: string, options: NodeChildProcess.SpawnOptions): NodeChildProcess.ChildProcess;\nexport function spawn(command: string, args?: readonly string[], options?: NodeChildProcess.SpawnOptionsWithoutStdio): NodeChildProcess.ChildProcessWithoutNullStreams;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<Writable, Readable, Readable>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<Writable, Readable, null>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<Writable, null, Readable>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<null, Readable, Readable>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<Writable, null, null>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<null, Readable, null>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioNull, NodeChildProcess.StdioPipe>): NodeChildProcess.ChildProcessByStdio<null, null, Readable>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptionsWithStdioTuple<NodeChildProcess.StdioNull, NodeChildProcess.StdioNull, NodeChildProcess.StdioNull>): NodeChildProcess.ChildProcessByStdio<null, null, null>;\nexport function spawn(command: string, args: readonly string[], options: NodeChildProcess.SpawnOptions): NodeChildProcess.ChildProcess;\n\nexport function fork(modulePath: string | URL, options?: NodeChildProcess.ForkOptions): NodeChildProcess.ChildProcess;\nexport function fork(modulePath: string | URL, args?: readonly string[], options?: NodeChildProcess.ForkOptions): NodeChildProcess.ChildProcess;\n\n// execSync function with Node.js signatures\nexport function execSync(command: string): Buffer;\nexport function execSync(command: string, options: NodeChildProcess.ExecSyncOptionsWithStringEncoding): string;\nexport function execSync(command: string, options: NodeChildProcess.ExecSyncOptionsWithBufferEncoding): Buffer;\nexport function execSync(command: string, options?: NodeChildProcess.ExecSyncOptions): string | Buffer;\nexport function execSync(command: string, options?: NodeChildProcess.ExecSyncOptions): string | Buffer {\n\t// TODO: Implement the actual execSync functionality\n\tthrow new Error(\"execSync is not yet implemented\");\n}\n\n// execFileSync function with Node.js signatures\nexport function execFileSync(file: string): Buffer;\nexport function execFileSync(file: string, options: NodeChildProcess.ExecFileSyncOptionsWithStringEncoding): string;\nexport function execFileSync(file: string, options: NodeChildProcess.ExecFileSyncOptionsWithBufferEncoding): Buffer;\nexport function execFileSync(file: string, options?: NodeChildProcess.ExecFileSyncOptions): string | Buffer;\nexport function execFileSync(file: string, args: readonly string[]): Buffer;\nexport function execFileSync(file: string, args: readonly string[], options: NodeChildProcess.ExecFileSyncOptionsWithStringEncoding): string;\nexport function execFileSync(file: string, args: readonly string[], options: NodeChildProcess.ExecFileSyncOptionsWithBufferEncoding): Buffer;\nexport function execFileSync(file: string, args?: readonly string[], options?: NodeChildProcess.ExecFileSyncOptions): string | Buffer;\nexport function execFileSync(file: string, ...args: any[]): string | Buffer {\n\t// TODO: Implement the actual execFileSync functionality\n\tthrow new Error(\"execFileSync is not yet implemented\");\n}\n\n// spawnSync function with Node.js signatures\nexport function spawnSync(command: string): NodeChildProcess.SpawnSyncReturns<Buffer>;\nexport function spawnSync(command: string, options: NodeChildProcess.SpawnSyncOptionsWithStringEncoding): NodeChildProcess.SpawnSyncReturns<string>;\nexport function spawnSync(command: string, options: NodeChildProcess.SpawnSyncOptionsWithBufferEncoding): NodeChildProcess.SpawnSyncReturns<Buffer>;\nexport function spawnSync(command: string, options?: NodeChildProcess.SpawnSyncOptions): NodeChildProcess.SpawnSyncReturns<string | Buffer>;\nexport function spawnSync(command: string, args: readonly string[]): NodeChildProcess.SpawnSyncReturns<Buffer>;\nexport function spawnSync(command: string, args: readonly string[], options: NodeChildProcess.SpawnSyncOptionsWithStringEncoding): NodeChildProcess.SpawnSyncReturns<string>;\nexport function spawnSync(command: string, args: readonly string[], options: NodeChildProcess.SpawnSyncOptionsWithBufferEncoding): NodeChildProcess.SpawnSyncReturns<Buffer>;\nexport function spawnSync(command: string, args?: readonly string[], options?: NodeChildProcess.SpawnSyncOptions): NodeChildProcess.SpawnSyncReturns<string | Buffer> {}\n"
  },
  {
    "path": "src/sys/Node/runtimes/shims/apis/http.ts",
    "content": ""
  },
  {
    "path": "src/sys/Node/runtimes/shims/path-remapper.ts",
    "content": ""
  },
  {
    "path": "src/sys/Node/runtimes/shims/util/Stub.ts",
    "content": "export default class NotImplementedError extends Error {\n\tpublic readonly methodName?: string;\n\n\tconstructor(methodName: string, packageName: string) {\n\t\tsuper(`${methodName}() from ${packageName} is not implemented yet`);\n\n\t\tthis.name = \"NotImplementedError\";\n\t\tthis.methodName = methodName;\n\n\t\tObject.setPrototypeOf(this, NotImplementedError.prototype);\n\t}\n}\n"
  },
  {
    "path": "src/sys/Node/runtimes/util/getFileTree.ts",
    "content": "/**\n * @module getFileTree\n */\n\n/**\n * Builds a full file tree for Terbium's File System\n * @param path The path to start at\n * @returns The flat file tree\n */\nexport default async function getFileTree(path = \"/\") {\n\tconst tree: Record<string, string> = {};\n\n\tasync function traverse(path: string): Promise<void> {\n\t\tconst stat = await window.parent.tb.fs.promises.stat(path);\n\n\t\tif (stat.isDirectory()) {\n\t\t\tconst entries = await window.parent.tb.fs.promises.readdir(path);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry === \".\" || entry === \"..\") continue;\n\n\t\t\t\tconst newPath = path.endsWith(\"/\") ? `${path}${entry}` : `${path}/${entry}`;\n\t\t\t\tawait traverse(newPath);\n\t\t\t}\n\t\t} else if (stat.isFile()) {\n\t\t\tconst contents = await window.parent.tb.fs.promises.readFile(path, \"utf8\");\n\t\t\ttree[path] = contents;\n\t\t}\n\t}\n\n\tawait traverse(path);\n\n\treturn tree;\n}\n"
  },
  {
    "path": "src/sys/Parser.ts",
    "content": "import * as htmlparser from \"htmlparser2\";\n\nvar isNative: boolean = false;\nvar region: HTMLElement | null = null;\nvar tbWindow: HTMLElement | null = null;\n// @ts-expect-error API Stub, Declaration will be read eventually\nvar appTitle: string | null = null;\n\nconst parse = {\n\t/**\n\t * THIS MAY NOT MAKE IT TO PRODUCTION\n\t *\n\t * Parses the given HTML or TML file and returns a built Terbium window\n\t * @param src The source code of the file\n\t * @returns new Terbium window\n\t * @example parse.build('<window><region><title>My Window</title></region></window>')\n\t * @example parse.build('https://example.com/window.tml')\n\t * @function `parse.build`\n\t */\n\tbuild: async (src: string) => {\n\t\tconst baseURL = new URL(src, window.location.href).href;\n\t\tconst response = await window.tb.libcurl.fetch(baseURL);\n\t\tif (!response) throw new Error(`Failed to fetch the source from ${src}`);\n\t\tconst data = await response.text();\n\t\tif (data.startsWith(`@native`) || (src.endsWith(\".tml\") && !data.startsWith(\"<body/>\"))) {\n\t\t\tisNative = true;\n\t\t}\n\t\tif (!isNative) return;\n\t\tconsole.warn(\"This functionality is not refined and may not work as expected.\");\n\t\ttbWindow = document.createElement(\"window-body\");\n\t\tconst shadow = tbWindow.attachShadow({ mode: \"open\" });\n\t\tconst parser: htmlparser.Parser | undefined = new htmlparser.Parser(\n\t\t\t{\n\t\t\t\tonopentag: (name: string, attribs: { [s: string]: string }) => {\n\t\t\t\t\tif (name === \"region\") {\n\t\t\t\t\t\tregion = document.createElement(\"region\");\n\t\t\t\t\t\tfor (const key in attribs) {\n\t\t\t\t\t\t\tregion.setAttribute(key, attribs[key]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst element = document.createElement(name);\n\t\t\t\t\tfor (const key in attribs) {\n\t\t\t\t\t\telement.setAttribute(key, attribs[key]);\n\t\t\t\t\t}\n\t\t\t\t\tshadow.appendChild(element);\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ decodeEntities: true },\n\t\t);\n\t\tparser!.write(data);\n\t\tparser!.end();\n\n\t\tconsole.log(tbWindow);\n\t\treturn tbWindow;\n\t},\n};\n\nexport default parse;\n"
  },
  {
    "path": "src/sys/Store.ts",
    "content": "import React from \"react\";\nimport { create } from \"zustand\";\nimport { WindowConfig, cmprops, fileExists } from \"./types\";\nimport { init } from \"@paralleldrive/cuid2\";\nimport { updateInfo } from \"./gui/AppIsland\";\n\ninterface WindowState {\n\twindows: WindowConfig[];\n\twid?: string;\n\tpid?: string;\n\tmatchedWindows: any;\n\taddWindow: (config: WindowConfig) => void;\n\tkillWindow: (wid: any) => void;\n\tremoveWindow: (wid: any) => void;\n\tarrange: (wid: any) => void;\n\tminimize: (wid: any) => void;\n\tgetWindow: (wid: any) => void;\n\tcurrentPID?: string;\n}\n\ninterface ContextMenuState {\n\tmenu: cmprops;\n\tsetContextMenu: (options: any) => void;\n\tclearContextMenu: () => void;\n}\n\ninterface SearchMenuState {\n\topen: boolean;\n\tsetOpen: (open: boolean) => void;\n\tsearchRef: React.RefObject<HTMLInputElement | null>;\n\tsearchMenuRef: React.RefObject<HTMLDivElement | null>;\n}\n\nlet lastPID: number = 0;\n\nfunction ensureLastPID() {\n\tif (lastPID === 0) {\n\t\tif (window.tb?.process?.list) {\n\t\t\ttry {\n\t\t\t\tconst list = window.tb.process.list();\n\t\t\t\tlastPID = Math.max(...Object.keys(list).map(Number));\n\t\t\t} catch (e) {\n\t\t\t\tconsole.warn(e);\n\t\t\t\tlastPID = 2;\n\t\t\t}\n\t\t} else {\n\t\t\tlastPID = 2;\n\t\t}\n\t}\n}\n\nexport const createPID = () => {\n\tensureLastPID();\n\tlastPID += 1;\n\treturn lastPID.toString();\n};\n\nexport const createWID = () => {\n\tconst cuid = init({\n\t\tlength: 10,\n\t});\n\treturn \"w-\" + cuid();\n};\n\nconst useWindowStore = create<WindowState>()(set => ({\n\twindows: [],\n\tmatchedWindows: [],\n\taddWindow: async (config: WindowConfig) => {\n\t\tconst recentApps = (await fileExists(\"/system/var/terbium/recent.json\"))\n\t\t\t? JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/recent.json\", \"utf8\"))\n\t\t\t: (await window.tb.fs.promises.writeFile(\"/system/var/terbium/recent.json\", JSON.stringify([], null, 2), \"utf8\").catch((err: any) => console.error(err)), []);\n\t\tconst updateState = async (state: WindowState) => {\n\t\t\tconst indexes = state.windows.map(w => w.zIndex ?? 0);\n\t\t\tconfig.zIndex = Math.max(...indexes) + 1;\n\t\t\tconfig.focused = true;\n\t\t\tif (config.zIndex === -Infinity) {\n\t\t\t\tconfig.zIndex = 2;\n\t\t\t}\n\t\t\tstate.windows.forEach(w => {\n\t\t\t\tif (w.wid !== config.wid) {\n\t\t\t\t\tw.focused = false;\n\t\t\t\t\tif (w.zIndex !== undefined) {\n\t\t\t\t\t\tMath.max(0, w.zIndex - 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// @ts-expect-error\n\t\t\tconst matched = state.matchedWindows.findIndex(group =>\n\t\t\t\t// @ts-expect-error\n\t\t\t\tgroup.some(w => (typeof w.title === \"string\" ? w.title : w.title?.text) === (typeof config.title === \"string\" ? config.title : config.title?.text)),\n\t\t\t);\n\n\t\t\tif (matched !== -1) {\n\t\t\t\tstate.matchedWindows[matched].push(config);\n\t\t\t} else {\n\t\t\t\tstate.matchedWindows.push([config]);\n\t\t\t}\n\n\t\t\tconfig.wid = createWID();\n\t\t\tconfig.pid = createPID();\n\t\t\tconst appName = typeof config.title === \"string\" ? config.title : config.title?.text;\n\t\t\tlet configData: any = null;\n\t\t\ttry {\n\t\t\t\tconst data = JSON.parse(await window.tb.fs.promises.readFile(`/apps/system/${appName.toLowerCase()}.tapp/index.json`, \"utf8\")).config;\n\t\t\t\tconfigData = {\n\t\t\t\t\t...data,\n\t\t\t\t\tweight: 1,\n\t\t\t\t};\n\t\t\t} catch (err) {\n\t\t\t\tconfigData = {\n\t\t\t\t\ttitle: appName,\n\t\t\t\t\ticon: config.icon,\n\t\t\t\t\tsrc: config.src,\n\t\t\t\t\tsize: config.size,\n\t\t\t\t\tproxy: config.proxy,\n\t\t\t\t\tweight: 1,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (recentApps.length > 10) {\n\t\t\t\tconst lowestWeight = Math.min(...recentApps.map((app: any) => app.weight));\n\t\t\t\tconst lowestWeightIndex = recentApps.findIndex((app: any) => app.weight === lowestWeight);\n\t\t\t\trecentApps.splice(lowestWeightIndex, 1);\n\t\t\t}\n\n\t\t\tconst recentAppIndex = recentApps.findIndex((app: any) => {\n\t\t\t\treturn (typeof app.title === \"string\" ? app.title.toLowerCase() : app.title?.text.toLowerCase()) === (typeof configData.title === \"string\" ? configData.title.toLowerCase() : configData.title?.text.toLowerCase());\n\t\t\t});\n\n\t\t\tif (recentAppIndex === -1) {\n\t\t\t\trecentApps.push(configData);\n\t\t\t} else {\n\t\t\t\trecentApps[recentAppIndex].weight += 1;\n\t\t\t}\n\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/recent.json\", JSON.stringify(recentApps, null, 2), \"utf8\").catch((err: any) => {\n\t\t\t\tconsole.error(\"Error writing recent apps file:\", err);\n\t\t\t});\n\n\t\t\twindow.dispatchEvent(new CustomEvent(\"selwin-upd\", { detail: typeof config.title === \"string\" ? config.title : config.title?.text }));\n\n\t\t\twindow.tb.process.procs[config.pid as any] = {\n\t\t\t\tname: typeof config.title === \"string\" ? config.title : config.title.text,\n\t\t\t\twid: config.wid,\n\t\t\t\ticon: config.icon!,\n\t\t\t\t// @ts-expect-error\n\t\t\t\tpid: config.pid!,\n\t\t\t\tsrc: config.src,\n\t\t\t\t// @ts-expect-error\n\t\t\t\tsize: config.size || { width: 800, height: 600 },\n\t\t\t\ttype: \"window\",\n\t\t\t};\n\n\t\t\treturn {\n\t\t\t\twindows: [...state.windows, config],\n\t\t\t\tmatchedWindows: [...state.matchedWindows],\n\t\t\t\tcurrentPID: config.pid,\n\t\t\t};\n\t\t};\n\n\t\tconst newState = await updateState(useWindowStore.getState());\n\t\tset(newState);\n\t},\n\tkillWindow: (pid: string) =>\n\t\tset((state: any) => {\n\t\t\tconst windows = state.windows.filter((w: any) => w.pid !== pid);\n\t\t\tconst matchedWindows = state.matchedWindows\n\t\t\t\t.map((group: any) => {\n\t\t\t\t\tconst newGroup = group.filter((w: any) => w.pid !== pid);\n\t\t\t\t\treturn newGroup.length > 0 ? newGroup : null;\n\t\t\t\t})\n\t\t\t\t.filter((group: any) => group !== null);\n\t\t\tconst indexes = windows.map((w: any) => w.zIndex ?? 0);\n\t\t\tconst highest = Math.max(...indexes);\n\t\t\tconst win = windows.find((w: any) => w.zIndex === highest);\n\t\t\tif (win) {\n\t\t\t\twin.focused = true;\n\t\t\t\tupdateInfo({ appname: typeof win.title === \"string\" ? win.title : win.title?.text });\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"selwin-upd\", { detail: typeof win.title === \"string\" ? win.title : win.title?.text }));\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twindows,\n\t\t\t\tmatchedWindows,\n\t\t\t};\n\t\t}),\n\tremoveWindow: (wid: string) => {\n\t\tset((state: any) => {\n\t\t\tconst windows = state.windows.filter((w: any) => w.wid !== wid);\n\t\t\tconst matchedWindows = state.matchedWindows\n\t\t\t\t.map((group: any) => {\n\t\t\t\t\tconst newGroup = group.filter((w: any) => w.wid !== wid);\n\t\t\t\t\treturn newGroup.length > 0 ? newGroup : null;\n\t\t\t\t})\n\t\t\t\t.filter((group: any) => group !== null);\n\t\t\tconst indexes = windows.map((w: any) => w.zIndex ?? 0);\n\t\t\tconst highest = Math.max(...indexes);\n\t\t\tconst win = windows.find((w: any) => w.zIndex === highest);\n\t\t\tif (win) {\n\t\t\t\twin.focused = true;\n\t\t\t\tupdateInfo({ appname: typeof win.title === \"string\" ? win.title : win.title?.text });\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"selwin-upd\", { detail: typeof win.title === \"string\" ? win.title : win.title?.text }));\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twindows,\n\t\t\t\tmatchedWindows,\n\t\t\t};\n\t\t});\n\t},\n\tarrange: (wid: string) =>\n\t\tset((state: WindowState) => {\n\t\t\tconst window = state.windows.find(w => w.wid === wid);\n\t\t\tif (!window) return state;\n\n\t\t\tconst indexes = state.windows.map(w => w.zIndex ?? 0);\n\t\t\tconst maxIndex = Math.max(...indexes);\n\n\t\t\t// Optimization: Check if window is already at highest z-index\n\t\t\t// This can be disabled if window optimization setting is off\n\t\t\tif (window.focused && window.zIndex === maxIndex) {\n\t\t\t\treturn state; // No update needed\n\t\t\t}\n\n\t\t\tset({ currentPID: window.pid });\n\n\t\t\twindow.zIndex = maxIndex + 1;\n\t\t\twindow.focused = true;\n\t\t\tstate.windows.forEach(w => {\n\t\t\t\tif (w.wid !== wid) {\n\t\t\t\t\tw.focused = false;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\twindows: [...state.windows], // Create new array reference for React to detect change\n\t\t\t};\n\t\t}),\n\tminimize: (wid: string) =>\n\t\tset((state: WindowState) => {\n\t\t\tconst window = state.windows.find(w => w.wid === wid);\n\t\t\tif (!window) return state;\n\t\t\twindow.focused = false;\n\t\t\treturn {\n\t\t\t\twindows: state.windows,\n\t\t\t};\n\t\t}),\n\tgetWindow: (wid: string) => {\n\t\tconst state = useWindowStore.getState();\n\t\treturn state.windows.find(w => w.wid === wid);\n\t},\n}));\n\nconst useContextMenuStore = create<ContextMenuState>()(set => ({\n\tmenu: { x: 0, y: 0, options: [] },\n\tsetContextMenu: (options: any) => set({ menu: options }),\n\tclearContextMenu: () => set({ menu: { x: 0, y: 0, options: [] } }),\n}));\n\nconst useSearchMenuStore = create<SearchMenuState>()(set => ({\n\topen: false,\n\tsetOpen: (open: boolean) => set({ open }),\n\tsearchRef: React.createRef<HTMLInputElement | null>(),\n\tsearchMenuRef: React.createRef<HTMLDivElement | null>(),\n}));\n\nexport { useWindowStore, useContextMenuStore, useSearchMenuStore };\n"
  },
  {
    "path": "src/sys/apis/Crypto.ts",
    "content": "// @ts-expect-error stfu\nimport { SHA256 } from \"crypto-js\";\n\nexport default class pwd {\n\tharden(password: string) {\n\t\tconst hash = SHA256(password).toString();\n\t\treturn hash;\n\t}\n}\n"
  },
  {
    "path": "src/sys/apis/Date.ts",
    "content": "export function GetTime() {\n\tconst date = new Date();\n\tconst hours = date.getHours();\n\tconst minutes = date.getMinutes();\n\tconst ampm = hours >= 12 ? \"PM\" : \"AM\";\n\tconst formattedHours = hours % 12 || 12;\n\tconst formattedMinutes = minutes < 10 ? `0${minutes}` : minutes;\n\tconst timeString = `${formattedHours}:${formattedMinutes} ${ampm}`;\n\treturn timeString;\n}\n\nexport function GetDate() {\n\tconst dateObj = new Date();\n\tconst month = dateObj.getMonth();\n\tconst day = dateObj.getDate();\n\tconst year = dateObj.getFullYear();\n\tconst months = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\n\tconst days = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n\tconst output = `${days[dateObj.getDay()]} ${months[month]} ${day} ${year}`;\n\treturn output;\n}\n"
  },
  {
    "path": "src/sys/apis/Dialogs.tsx",
    "content": "import { dialogProps } from \"../types\";\nimport { useState, useEffect, useRef } from \"react\";\nimport \"../gui/styles/dialog.css\";\nimport \"../gui/styles/cropper.css\";\nimport Cropper from \"cropperjs\";\nimport Compressor from \"compressorjs\";\n\nexport type dialogType = \"alert\" | \"message\" | \"select\" | \"auth\" | \"permissions\" | \"filebrowser\" | \"directorybrowser\" | \"savefile\" | \"cropper\" | \"webauth\";\n\nexport let setDialogFn: (type: dialogType, props: dialogProps, options?: { sudo: boolean }) => void;\nexport let removeFn: () => void;\n\nexport default function DialogContainer() {\n\tconst [dialogType, setdialogType] = useState<dialogType | null>(null);\n\tconst [dialogProps, setdialogProps] = useState<dialogProps | {}>({});\n\tconst [sudo, setSudo] = useState<boolean | null>(null);\n\tconst remove = () => {\n\t\tsetdialogType(null);\n\t\tsetdialogProps({});\n\t};\n\tconst setDialog = (type: dialogType, props: dialogProps, options?: { sudo: boolean }) => {\n\t\tsetdialogType(type);\n\t\tsetdialogProps(props);\n\t\tsetSudo(options?.sudo || null);\n\t};\n\t/**\n\t * @returns Components for COM\n\t * @author XSTARS\n\t */\n\tuseEffect(() => {\n\t\tsetDialogFn = setDialog;\n\t\tremoveFn = remove;\n\t}, []);\n\treturn (\n\t\t<>\n\t\t\t{dialogType === \"alert\" && <Alert {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"message\" && <Message {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"select\" && <Select {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"auth\" && <Auth {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"permissions\" && <Permissions {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"filebrowser\" && <FileBrowser {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"directorybrowser\" && <DirectoryBrowser {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"savefile\" && <SaveFile {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"cropper\" && <Crop {...(dialogProps as dialogProps)} />}\n\t\t\t{dialogType === \"webauth\" && <WebAuth {...(dialogProps as dialogProps)} />}\n\t\t</>\n\t);\n}\n\nconst formatSize = (size: number) => {\n\tif (size < 1024) return `${size} B`;\n\tif (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`;\n\tif (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(2)} MB`;\n\treturn `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n};\n\nexport function Alert({ title, message, onOk }: dialogProps) {\n\tconst container = useRef<HTMLDivElement>(null);\n\tconst dialog = useRef<HTMLDivElement>(null);\n\tconst [msg, setMsg] = useState<string | null>(null);\n\tconst OK = () => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontainer.current?.remove();\n\t\t\t\tif (onOk) onOk();\n\t\t\t}, 200);\n\t\t}\n\t\tremoveFn();\n\t};\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"mousedown\", e => {\n\t\t\tif (container.current && e.target !== dialog.current && e.target === container.current) {\n\t\t\t\tif (onOk) onOk();\n\t\t\t}\n\t\t});\n\t});\n\tuseEffect(() => {\n\t\t// @ts-expect-error\n\t\tif (message instanceof Error) {\n\t\t\tsetMsg(message.message);\n\t\t} else if (typeof message === \"object\" && message !== null) {\n\t\t\ttry {\n\t\t\t\tsetMsg(JSON.stringify(message));\n\t\t\t} catch {\n\t\t\t\tsetMsg(String(message));\n\t\t\t}\n\t\t} else {\n\t\t\tsetMsg(String(message));\n\t\t}\n\t}, [message]);\n\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\" ref={container}>\n\t\t\t<div ref={dialog} className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t<div className=\"dialog-message\">{msg}</div>\n\t\t\t\t<div className=\"flex justify-end\">\n\t\t\t\t\t<button className=\"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" onMouseDown={OK}>\n\t\t\t\t\t\tOK\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function Message({ title, defaultValue, onOk, onCancel }: dialogProps) {\n\tif (!title) throw new Error(\"title is required\");\n\tconst container = useRef<HTMLDivElement | null>(null);\n\tconst dialog = useRef<HTMLDivElement | null>(null);\n\tconst inputRef = useRef<HTMLInputElement | null>(null);\n\tconst OK = () => {\n\t\tconst inpVAl = inputRef.current?.value;\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontainer.current?.remove();\n\t\t\t\tif (onOk && inpVAl !== undefined) {\n\t\t\t\t\tonOk(inpVAl);\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t\tremoveFn();\n\t\t}\n\t};\n\tconst Cancel = () => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontainer.current?.remove();\n\t\t\t\tif (onCancel) {\n\t\t\t\t\tonCancel();\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t\tremoveFn();\n\t\t}\n\t};\n\tconst onDown = (event: React.KeyboardEvent) => {\n\t\tif (event.key === \"Enter\") {\n\t\t\tOK();\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"mousedown\", e => {\n\t\t\tif (container.current && e.target !== dialog.current && e.target === container.current) {\n\t\t\t\tCancel();\n\t\t\t}\n\t\t});\n\t});\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\" ref={container}>\n\t\t\t<div ref={dialog} className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t<input type=\"text\" defaultValue={defaultValue} className=\"p-2 pl-4 rounded-lg bg-[#ffffff16] cursor-text outline-hidden shadow-tb-border-shadow duration-150\" style={{ width: \"100%\" }} ref={inputRef} onKeyDown={onDown} />\n\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button className=\"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" onMouseDown={OK}>\n\t\t\t\t\t\tOK\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function Select({ title, options, onOk, onCancel }: dialogProps) {\n\tif (!title) throw new Error(\"title is required\");\n\tconst container = useRef<HTMLDivElement | null>(null);\n\tconst dialog = useRef<HTMLDivElement | null>(null);\n\tconst OK = (value: string) => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontainer.current?.remove();\n\t\t\t\tif (onOk) {\n\t\t\t\t\tonOk(value);\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t\tremoveFn();\n\t\t}\n\t};\n\tconst Cancel = () => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontainer.current?.remove();\n\t\t\t\tif (onCancel) {\n\t\t\t\t\tonCancel();\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t\tremoveFn();\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"mousedown\", e => {\n\t\t\tif (container.current && e.target !== dialog.current && e.target === container.current) {\n\t\t\t\tCancel();\n\t\t\t}\n\t\t});\n\t});\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\" ref={container}>\n\t\t\t<div ref={dialog} className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t<div className=\"grid grid-cols-4 gap-2\">\n\t\t\t\t\t{options &&\n\t\t\t\t\t\toptions.map((option: { text: string; value: string }) => (\n\t\t\t\t\t\t\t<button key={option.value} className=\"py-1.5 px-2.5 rounded-md bg-[#ffffff10] hover:bg-[#ffffff28] shadow-tb-border-shadow duration-150 cursor-pointer\" onMouseDown={() => OK(option.value)}>\n\t\t\t\t\t\t\t\t{option.text}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function Auth({ title, defaultUsername, onOk, onCancel, sudo }: dialogProps) {\n\tif (!title) throw new Error(\"title is required\");\n\tconst container = useRef<HTMLDivElement | null>(null);\n\tconst dialog = useRef<HTMLDivElement | null>(null);\n\tconst usernameRef = useRef<HTMLInputElement | null>(null);\n\tconst passwordRef = useRef<HTMLInputElement | null>(null);\n\tconst [sudoList, setSudoList] = useState<string[]>([]);\n\tconst OK = () => {\n\t\tconst username = usernameRef.current?.value;\n\t\tconst password = passwordRef.current?.value;\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (container.current) {\n\t\t\t\t\tremoveFn();\n\t\t\t\t}\n\t\t\t\tif (onOk && username && password) {\n\t\t\t\t\tonOk(username, password);\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t}\n\t};\n\tconst Cancel = () => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (container.current) {\n\t\t\t\t\tremoveFn();\n\t\t\t\t}\n\t\t\t\tif (onCancel) {\n\t\t\t\t\tonCancel();\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t}\n\t};\n\tconst onDown = (event: React.KeyboardEvent) => {\n\t\tif (event.key === \"Enter\") {\n\t\t\tOK();\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"mousedown\", e => {\n\t\t\tif (container.current && e.target !== dialog.current && e.target === container.current) {\n\t\t\t\tCancel();\n\t\t\t}\n\t\t});\n\t});\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\" ref={container}>\n\t\t\t<div ref={dialog} className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t<input\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tdisabled={true}\n\t\t\t\t\tdefaultValue={sudo ? \"sudo\" : defaultUsername}\n\t\t\t\t\tplaceholder=\"Username\"\n\t\t\t\t\tclassName={`\n                        ${sudo ? `p-2 pl-4 rounded-lg bg-[#ffffff08] outline-hidden shadow-tb-border-shadow` : `p-2 pl-4 rounded-lg bg-[#ffffff16] cursor-text outline-hidden shadow-tb-border-shadow`}\n                    `}\n\t\t\t\t\tstyle={{ width: \"100%\" }}\n\t\t\t\t\tref={usernameRef}\n\t\t\t\t/>\n\t\t\t\t<input type=\"password\" placeholder=\"Password\" className=\"p-2 pl-4 rounded-lg bg-[#ffffff20] cursor-text outline-hidden shadow-tb-border-shadow duration-150\" style={{ width: \"100%\" }} ref={passwordRef} onKeyDown={onDown} />\n\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button className=\"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" onMouseDown={OK}>\n\t\t\t\t\t\tOK\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function Permissions({ title, message, onOk, onCancel }: dialogProps) {\n\tif (!message) throw new Error(\"message is required\");\n\tconst container = useRef<HTMLDivElement | null>(null);\n\tconst dialog = useRef<HTMLDivElement | null>(null);\n\tconst OK = () => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontainer.current?.remove();\n\t\t\t\tif (onOk) {\n\t\t\t\t\tonOk();\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t\tremoveFn();\n\t\t}\n\t};\n\tconst Cancel = () => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontainer.current?.remove();\n\t\t\t\tif (onCancel) {\n\t\t\t\t\tonCancel();\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t\tremoveFn();\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"mousedown\", e => {\n\t\t\tif (container.current && e.target !== dialog.current && e.target === container.current) {\n\t\t\t\tCancel();\n\t\t\t}\n\t\t});\n\t});\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\" ref={container}>\n\t\t\t<div ref={dialog} className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t<div className=\"dialog-message\">{message}</div>\n\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<div className=\"dialog-action-buttons\">\n\t\t\t\t\t\t<button className=\"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" onMouseDown={OK}>\n\t\t\t\t\t\t\tOK\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function FileBrowser({ title, filter, local, onOk, onCancel }: dialogProps) {\n\tif (!title) throw new Error(\"title is required\");\n\tconst [selectedEntry, setSelectedEntry] = useState<string | null>(null);\n\tconst [currentDirectory, setCurrentDirectory] = useState<string>(\"storage devices\");\n\tconst [fileEntries, setFileEntries] = useState<any[]>([]);\n\tconst [loading, setLoading] = useState<boolean>(true);\n\tconst [showBackButton, setShowBackButton] = useState<boolean>(false);\n\tconst [storageInfo, setStorageInfo] = useState<{ usage: number; quota: number } | null>(null);\n\tconst anura = window.parent.anura;\n\tuseEffect(() => {\n\t\tconst openDirectory = async (directory: string) => {\n\t\t\tsetLoading(true);\n\t\t\ttry {\n\t\t\t\tif (directory.startsWith(\"/mnt/\")) {\n\t\t\t\t\tconst serverName = directory.split(\"/\")[2];\n\t\t\t\t\tconst server = window.tb.vfs.servers.get(serverName);\n\t\t\t\t\tif (server && server.connected && server.connection?.client) {\n\t\t\t\t\t\tconst client = server.connection.client;\n\t\t\t\t\t\tconst path = directory.replace(`/mnt/${serverName}`, \"\") || \"/\";\n\t\t\t\t\t\tconst entries = await client.getDirectoryContents(path);\n\t\t\t\t\t\tconst entriesInfo = entries.map((entry: any) => ({\n\t\t\t\t\t\t\tentry: entry.basename,\n\t\t\t\t\t\t\tisDirectory: entry.type === \"directory\",\n\t\t\t\t\t\t\ttype: \"external\",\n\t\t\t\t\t\t\tconnected: true,\n\t\t\t\t\t\t}));\n\t\t\t\t\t\tsetFileEntries(\n\t\t\t\t\t\t\tentriesInfo.filter((info: any) => {\n\t\t\t\t\t\t\t\tif (!filter || info.isDirectory || (filter !== \"*.*\" && info.entry.endsWith(filter))) {\n\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsetShowBackButton(true);\n\t\t\t\t\t\tsetLoading(false);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst entries = await anura.fs.promises.readdir(directory);\n\t\t\t\tconst entriesInfo = await Promise.all(\n\t\t\t\t\tentries.map(async entry => {\n\t\t\t\t\t\tconst fileInfo = await anura.fs.promises.stat(`${directory}/${entry}`);\n\t\t\t\t\t\tconst isDirectory = fileInfo.isDirectory();\n\t\t\t\t\t\tconst type = \"internal\";\n\t\t\t\t\t\tif (!filter || isDirectory || (filter !== \"*.*\" && entry.endsWith(filter))) {\n\t\t\t\t\t\t\treturn { entry, isDirectory, type };\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tsetFileEntries(entriesInfo.filter(Boolean));\n\t\t\t\tsetShowBackButton(true);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(error);\n\t\t\t} finally {\n\t\t\t\tsetLoading(false);\n\t\t\t}\n\t\t};\n\t\tif (currentDirectory === \"storage devices\") {\n\t\t\tnavigator.storage.estimate().then(({ usage, quota }) => {\n\t\t\t\tsetStorageInfo({ usage: usage as number, quota: quota as number });\n\t\t\t});\n\t\t\tsetLoading(false);\n\t\t\tconst entries = local\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tentry: \"File System\",\n\t\t\t\t\t\t\ttype: \"internal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tentry: \"File System\",\n\t\t\t\t\t\t\ttype: \"internal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...Array.from(window.tb.vfs.servers.values()).map(server => ({\n\t\t\t\t\t\t\tentry: server.name,\n\t\t\t\t\t\t\ttype: \"external\",\n\t\t\t\t\t\t\tconnected: server.connected,\n\t\t\t\t\t\t})),\n\t\t\t\t\t];\n\t\t\tsetFileEntries(entries);\n\t\t\tsetShowBackButton(false);\n\t\t} else {\n\t\t\topenDirectory(currentDirectory);\n\t\t}\n\t}, [currentDirectory, filter, anura]);\n\n\tconst entClick = (entry: string, isDirectory: boolean, type: string) => {\n\t\tif (currentDirectory === \"storage devices\") {\n\t\t\tif (type === \"internal\") {\n\t\t\t\tsetCurrentDirectory(\"//\");\n\t\t\t} else {\n\t\t\t\twindow.tb.vfs.setServer(entry);\n\t\t\t\tsetCurrentDirectory(`/mnt/${entry}`);\n\t\t\t}\n\t\t} else if (isDirectory) {\n\t\t\tsetCurrentDirectory(`${currentDirectory}/${entry}`);\n\t\t} else {\n\t\t\tsetSelectedEntry(`${currentDirectory}/${entry}`);\n\t\t\tconst files = document.querySelectorAll(\".file-item\");\n\t\t\tfiles.forEach(file => {\n\t\t\t\tif (file.getAttribute(\"data-entry\") !== `${entry}`) {\n\t\t\t\t\tfile.classList.remove(\"bg-[#ffffff18]\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\tconst OK = () => {\n\t\tsetTimeout(() => {\n\t\t\tremoveFn();\n\t\t\tif (onOk) {\n\t\t\t\tonOk(selectedEntry);\n\t\t\t}\n\t\t}, 300);\n\t};\n\tconst Cancel = () => {\n\t\treturn new Promise(reject => {\n\t\t\tsetTimeout(() => {\n\t\t\t\treject(\"Canceled\");\n\t\t\t\tremoveFn();\n\t\t\t\tif (onCancel) {\n\t\t\t\t\tonCancel();\n\t\t\t\t}\n\t\t\t}, 300);\n\t\t});\n\t};\n\tconst setPath = (e: React.KeyboardEvent<HTMLInputElement>) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\tconst value = (e.target as HTMLInputElement).value;\n\t\t\tif (value === \"storage devices\") {\n\t\t\t\tsetCurrentDirectory(\"storage devices\");\n\t\t\t} else {\n\t\t\t\tsetCurrentDirectory(value);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\">\n\t\t\t<div className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t{loading ? (\n\t\t\t\t\t<div className=\"font-medium text-lg\">Loading...</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={`\n\t\t\t\t\t\toverflow-y-auto min-h-[100px] max-h-[300px]\n\t\t\t\t\t\t${fileEntries.length === 0 ? \" flex justify-center items-center\" : \"bg-[#ffffff10] shadow-tb-border-shadow rounded-lg\"}\n\t\t\t\t\t`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{fileEntries.length === 0 ? (\n\t\t\t\t\t\t\t<div className=\"font-extrabold text-xl select-none\">No files found</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\tfileEntries.map(({ entry, isDirectory, type, connected }) => (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={entry}\n\t\t\t\t\t\t\t\t\tdata-entry={entry}\n\t\t\t\t\t\t\t\t\tclassName=\"file-item flex flex-col gap-1 select-none p-1.5 first:rounded-t-lg last:rounded-b-lg duration-150\"\n\t\t\t\t\t\t\t\t\tonMouseDown={e => {\n\t\t\t\t\t\t\t\t\t\tentClick(entry, isDirectory, type);\n\t\t\t\t\t\t\t\t\t\tconst files = document.querySelectorAll(\".file-item\");\n\t\t\t\t\t\t\t\t\t\tfiles.forEach(file => {\n\t\t\t\t\t\t\t\t\t\t\tfile.classList.remove(\"bg-[#ffffff18]\");\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\te.currentTarget.classList.add(\"bg-[#ffffff18]\");\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t\t\t\t\t\t\t{currentDirectory === \"storage devices\" ? (\n\t\t\t\t\t\t\t\t\t\t\ttype === \"external\" ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<circle cx=\"18\" cy=\"17.25\" r=\"3\" fill={connected ? \"#5DD881\" : \"#FF4D4D\"} />\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t) : isDirectory ? (\n\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M19.5 21a3 3 0 003-3v-4.5a3 3 0 00-3-3h-15a3 3 0 00-3 3V18a3 3 0 003 3h15zM1.5 10.146V6a3 3 0 013-3h5.379a2.25 2.25 0 011.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 013 3v1.146A4.483 4.483 0 0019.5 9h-15a4.483 4.483 0 00-3 1.146z\" />\n\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t<div className=\"font-semibold text-lg\">{entry}</div>\n\t\t\t\t\t\t\t\t\t\t{currentDirectory === \"storage devices\" && entry === \"File System\" && storageInfo ? (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-xs text-[#ffffff88] ml-auto\">\n\t\t\t\t\t\t\t\t\t\t\t\t{(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn `${formatSize(storageInfo.usage)} of ${formatSize(storageInfo.quota)}`;\n\t\t\t\t\t\t\t\t\t\t\t\t})()}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t) : type === \"external\" && currentDirectory === \"storage devices\" ? (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-xs text-[#ffffff88] ml-auto\">WebDav Device</div>\n\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t{showBackButton && (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"dialog-button goBack-button cursor-pointer\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tif (currentDirectory === \"//\" || (currentDirectory.startsWith(\"/mnt/\") && currentDirectory.split(\"/\").length <= 3)) {\n\t\t\t\t\t\t\t\t\tsetCurrentDirectory(\"storage devices\");\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst parts = currentDirectory.split(\"/\");\n\t\t\t\t\t\t\t\t\tparts.pop();\n\t\t\t\t\t\t\t\t\tconst inp = parts.join(\"/\") || \"storage devices\";\n\t\t\t\t\t\t\t\t\tsetCurrentDirectory(inp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tGo Back\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)}\n\t\t\t\t\t<input type=\"text\" className=\"p-2 pl-4 rounded-lg bg-[#ffffff16] cursor-text outline-hidden shadow-tb-border-shadow duration-150 mt-2\" value={currentDirectory} placeholder=\"Path\" onKeyDown={setPath} onChange={e => setCurrentDirectory(e.target.value)} />\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={`${selectedEntry ? \"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" : \"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff18] duration-150\"}`}\n\t\t\t\t\t\tonMouseDown={OK}\n\t\t\t\t\t\tdisabled={!selectedEntry}\n\t\t\t\t\t>\n\t\t\t\t\t\tSelect\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function DirectoryBrowser({ title, defualtDir, local, onOk, onCancel }: dialogProps) {\n\tconst [selectedEntry, setSelectedEntry] = useState<string | null>(null);\n\tconst [fileEntries, setFileEntries] = useState<any[]>([]);\n\tconst [loading, setLoading] = useState<boolean>(true);\n\tconst [currentDirectory, setCurrentDirectory] = useState<string>(defualtDir || \"storage devices\");\n\tconst [showBackButton, setShowBackButton] = useState<boolean>(false);\n\tconst [storageInfo, setStorageInfo] = useState<{ usage: number; quota: number } | null>(null);\n\tconst anura = window.parent.anura;\n\tconst openDirectory = async (directory: string) => {\n\t\tsetLoading(true);\n\t\ttry {\n\t\t\tif (directory.startsWith(\"/mnt/\")) {\n\t\t\t\tconst serverName = directory.split(\"/\")[2];\n\t\t\t\tconst server = window.tb.vfs.servers.get(serverName);\n\t\t\t\tif (server && server.connected && server.connection?.client) {\n\t\t\t\t\tconst client = server.connection.client;\n\t\t\t\t\tconst path = directory.replace(`/mnt/${serverName}`, \"\") || \"/\";\n\t\t\t\t\tconst entries = await client.getDirectoryContents(path);\n\t\t\t\t\tconst entriesInfo = entries\n\t\t\t\t\t\t.filter((entry: any) => entry.type === \"directory\")\n\t\t\t\t\t\t.map((entry: any) => ({\n\t\t\t\t\t\t\tentry: entry.basename,\n\t\t\t\t\t\t\tisDirectory: true,\n\t\t\t\t\t\t\ttype: \"external\",\n\t\t\t\t\t\t\tconnected: true,\n\t\t\t\t\t\t}));\n\t\t\t\t\tsetFileEntries(entriesInfo);\n\t\t\t\t\tsetShowBackButton(true);\n\t\t\t\t\tsetLoading(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst entries = await anura.fs.promises.readdir(directory);\n\t\t\tconst entriesInfo = await Promise.all(\n\t\t\t\tentries.map(async entry => {\n\t\t\t\t\tconst fileInfo = await anura.fs.promises.stat(`${directory}/${entry}`);\n\t\t\t\t\treturn { entry, isDirectory: fileInfo.isDirectory(), type: \"internal\" };\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst directories = entriesInfo.filter(info => info.isDirectory);\n\t\t\tsetFileEntries(directories);\n\t\t\tsetShowBackButton(true);\n\t\t\tif (directory.startsWith(\"//\")) {\n\t\t\t\tdirectory = directory.slice(1);\n\t\t\t}\n\t\t\tsetCurrentDirectory(directory);\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tif (currentDirectory === \"storage devices\") {\n\t\t\tnavigator.storage.estimate().then(({ usage, quota }) => {\n\t\t\t\tsetStorageInfo({ usage: usage as number, quota: quota as number });\n\t\t\t});\n\t\t\tconst entries = local\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tentry: \"File System\",\n\t\t\t\t\t\t\ttype: \"internal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tentry: \"File System\",\n\t\t\t\t\t\t\ttype: \"internal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...Array.from(window.tb.vfs.servers.values()).map(server => ({\n\t\t\t\t\t\t\tentry: server.name,\n\t\t\t\t\t\t\ttype: \"external\",\n\t\t\t\t\t\t\tconnected: server.connected,\n\t\t\t\t\t\t})),\n\t\t\t\t\t];\n\t\t\tsetFileEntries(entries);\n\t\t\tsetShowBackButton(false);\n\t\t\tsetLoading(false);\n\t\t} else {\n\t\t\topenDirectory(currentDirectory);\n\t\t}\n\t}, [currentDirectory]);\n\tconst Select = () => {\n\t\tif (selectedEntry) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tremoveFn();\n\t\t\t\tif (onOk) {\n\t\t\t\t\tonOk(selectedEntry);\n\t\t\t\t}\n\t\t\t}, 300);\n\t\t}\n\t};\n\tconst Cancel = () => {\n\t\treturn new Promise(reject => {\n\t\t\tsetTimeout(() => {\n\t\t\t\treject(\"Canceled\");\n\t\t\t\tremoveFn();\n\t\t\t\tif (onCancel) {\n\t\t\t\t\tonCancel();\n\t\t\t\t}\n\t\t\t}, 300);\n\t\t});\n\t};\n\tconst onChange = (e: React.KeyboardEvent<HTMLInputElement>) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\tconst value = (e.target as HTMLInputElement).value;\n\t\t\tif (value === \"storage devices\") {\n\t\t\t\tsetCurrentDirectory(\"storage devices\");\n\t\t\t} else {\n\t\t\t\tsetCurrentDirectory(value);\n\t\t\t}\n\t\t}\n\t};\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\">\n\t\t\t<div className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t{loading ? (\n\t\t\t\t\t<div>Loading...</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={`\n\t\t\t\t\t\toverflow-y-auto min-h-[100px] max-h-[300px]\n\t\t\t\t\t\t${fileEntries.length === 0 ? \" flex justify-center items-center\" : \"bg-[#ffffff10] shadow-tb-border-shadow rounded-lg\"}\n\t\t\t\t\t`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{fileEntries.length === 0 ? (\n\t\t\t\t\t\t\t<div className=\"font-extrabold text-xl select-none\">No directories found</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\tfileEntries.map(({ entry, isDirectory, type, connected }) => (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={entry}\n\t\t\t\t\t\t\t\t\tdata-entry={entry}\n\t\t\t\t\t\t\t\t\tclassName=\"file-item flex flex-col gap-1 select-none p-1.5 first:rounded-t-lg last:rounded-b-lg duration-150\"\n\t\t\t\t\t\t\t\t\tonDoubleClick={() => {\n\t\t\t\t\t\t\t\t\t\tif (currentDirectory === \"storage devices\") {\n\t\t\t\t\t\t\t\t\t\t\tif (type === \"internal\") {\n\t\t\t\t\t\t\t\t\t\t\t\tsetCurrentDirectory(\"//\");\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\twindow.tb.vfs.setServer(entry);\n\t\t\t\t\t\t\t\t\t\t\t\tsetCurrentDirectory(`/mnt/${entry}`);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tif (currentDirectory.endsWith(\"/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tsetCurrentDirectory(`${currentDirectory}${entry}`);\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tsetCurrentDirectory(`${currentDirectory}/${entry}`);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonMouseDown={(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\t\t\t\t\t\t\tlet path;\n\t\t\t\t\t\t\t\t\t\tif (currentDirectory === \"storage devices\") {\n\t\t\t\t\t\t\t\t\t\t\tif (type === \"internal\") {\n\t\t\t\t\t\t\t\t\t\t\t\tpath = \"//\";\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tpath = `/mnt/${entry}`;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tif (currentDirectory.endsWith(\"/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tpath = `${currentDirectory}${entry}`;\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tpath = `${currentDirectory}/${entry}`;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tsetSelectedEntry(path);\n\t\t\t\t\t\t\t\t\t\tconst files = document.querySelectorAll(\".file-item\");\n\t\t\t\t\t\t\t\t\t\tfiles.forEach(file => {\n\t\t\t\t\t\t\t\t\t\t\tif (file.getAttribute(\"data-entry\") !== `${entry}`) {\n\t\t\t\t\t\t\t\t\t\t\t\tfile.classList.remove(\"bg-[#ffffff18]\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\te.currentTarget.classList.add(\"bg-[#ffffff18]\");\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t\t\t\t\t\t\t{currentDirectory === \"storage devices\" ? (\n\t\t\t\t\t\t\t\t\t\t\ttype === \"external\" ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<circle cx=\"18\" cy=\"17.25\" r=\"3\" fill={connected ? \"#5DD881\" : \"#FF4D4D\"} />\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t) : isDirectory ? (\n\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M19.5 21a3 3 0 003-3v-4.5a3 3 0 00-3-3h-15a3 3 0 00-3 3V18a3 3 0 003 3h15zM1.5 10.146V6a3 3 0 013-3h5.379a2.25 2.25 0 011.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 013 3v1.146A4.483 4.483 0 0019.5 9h-15a4.483 4.483 0 00-3 1.146z\" />\n\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t<div className=\"font-semibold text-lg\">{entry}</div>\n\t\t\t\t\t\t\t\t\t\t{currentDirectory === \"storage devices\" && entry === \"File System\" && storageInfo ? (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-xs text-[#ffffff88] ml-auto\">\n\t\t\t\t\t\t\t\t\t\t\t\t{(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn `${formatSize(storageInfo.usage)} of ${formatSize(storageInfo.quota)}`;\n\t\t\t\t\t\t\t\t\t\t\t\t})()}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t) : type === \"external\" && currentDirectory === \"storage devices\" ? (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-xs text-[#ffffff88] ml-auto\">WebDav Device</div>\n\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t{showBackButton && (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"dialog-button goBack-button cursor-pointer\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tif (currentDirectory === \"//\" || (currentDirectory.startsWith(\"/mnt/\") && currentDirectory.split(\"/\").length <= 3)) {\n\t\t\t\t\t\t\t\t\tsetCurrentDirectory(\"storage devices\");\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst parts = currentDirectory.split(\"/\");\n\t\t\t\t\t\t\t\t\tparts.pop();\n\t\t\t\t\t\t\t\t\tconst inp = parts.join(\"/\") || \"storage devices\";\n\t\t\t\t\t\t\t\t\tsetCurrentDirectory(inp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tGo Back\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)}\n\t\t\t\t\t<input type=\"text\" className=\"p-2 pl-4 rounded-lg bg-[#ffffff16] cursor-text outline-hidden shadow-tb-border-shadow duration-150\" value={currentDirectory} placeholder=\"Directory\" onKeyDown={onChange} onChange={e => setCurrentDirectory(e.target.value)} />\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={`\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\tselectedEntry\n\t\t\t\t\t\t\t\t? \"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\"\n\t\t\t\t\t\t\t\t: \"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#ffffff10] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#ffffff18] duration-150\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\t`}\n\t\t\t\t\t\tonMouseDown={Select}\n\t\t\t\t\t\tdisabled={!selectedEntry}\n\t\t\t\t\t>\n\t\t\t\t\t\tSelect\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function SaveFile({ title, defualtDir, filename, local, onOk, onCancel }: dialogProps) {\n\tif (!title) throw new Error(\"title is required\");\n\tconst [selectedEntry, setSelectedEntry] = useState<string | null>(null);\n\tconst [fileEntries, setFileEntries] = useState<any[]>([]);\n\tconst [loading, setLoading] = useState<boolean>(true);\n\tconst [currentDirectory, setCurrentDirectory] = useState<string>(defualtDir || \"storage devices\");\n\tconst [showBackButton, setShowBackButton] = useState<boolean>(false);\n\tconst [storageInfo, setStorageInfo] = useState<{ usage: number; quota: number } | null>(null);\n\tconst fileInp = useRef<HTMLInputElement>(null);\n\tconst anura = window.parent.anura;\n\tconst openDirectory = async (directory: string) => {\n\t\tsetLoading(true);\n\t\ttry {\n\t\t\tif (directory.startsWith(\"/mnt/\")) {\n\t\t\t\tconst serverName = directory.split(\"/\")[2];\n\t\t\t\tconst server = window.tb.vfs.servers.get(serverName);\n\t\t\t\tif (server && server.connected && server.connection?.client) {\n\t\t\t\t\tconst client = server.connection.client;\n\t\t\t\t\tconst path = directory.replace(`/mnt/${serverName}`, \"\") || \"/\";\n\t\t\t\t\tconst entries = await client.getDirectoryContents(path);\n\t\t\t\t\tconst entriesInfo = entries\n\t\t\t\t\t\t.filter((entry: any) => entry.type === \"directory\")\n\t\t\t\t\t\t.map((entry: any) => ({\n\t\t\t\t\t\t\tentry: entry.basename,\n\t\t\t\t\t\t\tisDirectory: true,\n\t\t\t\t\t\t\ttype: \"external\",\n\t\t\t\t\t\t\tconnected: true,\n\t\t\t\t\t\t}));\n\t\t\t\t\tsetFileEntries(entriesInfo);\n\t\t\t\t\tsetShowBackButton(true);\n\t\t\t\t\tsetLoading(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst entries = await anura.fs.promises.readdir(directory);\n\t\t\tconst entriesInfo = await Promise.all(\n\t\t\t\tentries.map(async entry => {\n\t\t\t\t\tconst fileInfo = await anura.fs.promises.stat(`${directory}/${entry}`);\n\t\t\t\t\treturn { entry, isDirectory: fileInfo.isDirectory(), type: \"internal\" };\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst directories = entriesInfo.filter(info => info.isDirectory);\n\t\t\tsetFileEntries(directories);\n\t\t\tsetShowBackButton(true);\n\t\t\tsetLoading(false);\n\t\t} catch (error) {\n\t\t\tconsole.error(error);\n\t\t\tsetLoading(false);\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tif (currentDirectory === \"storage devices\") {\n\t\t\tnavigator.storage.estimate().then(({ usage, quota }) => {\n\t\t\t\tsetStorageInfo({ usage: usage as number, quota: quota as number });\n\t\t\t});\n\t\t\tconst entries = local\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tentry: \"File System\",\n\t\t\t\t\t\t\ttype: \"internal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tentry: \"File System\",\n\t\t\t\t\t\t\ttype: \"internal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...Array.from(window.tb.vfs.servers.values()).map(server => ({\n\t\t\t\t\t\t\tentry: server.name,\n\t\t\t\t\t\t\ttype: \"external\",\n\t\t\t\t\t\t\tconnected: server.connected,\n\t\t\t\t\t\t})),\n\t\t\t\t\t];\n\t\t\tsetFileEntries(entries);\n\t\t\tsetShowBackButton(false);\n\t\t\tsetLoading(false);\n\t\t} else {\n\t\t\topenDirectory(currentDirectory);\n\t\t}\n\t}, [currentDirectory]);\n\tconst Select = (entry: string, isDirectory: boolean, type: string) => {\n\t\tlet path;\n\t\tif (currentDirectory === \"storage devices\") {\n\t\t\tif (type === \"internal\") {\n\t\t\t\tpath = \"//\";\n\t\t\t} else {\n\t\t\t\twindow.tb.vfs.setServer(entry);\n\t\t\t\tpath = `/mnt/${entry}`;\n\t\t\t}\n\t\t\tsetCurrentDirectory(path);\n\t\t\treturn;\n\t\t}\n\t\tif (isDirectory) {\n\t\t\tif (currentDirectory.endsWith(\"/\")) {\n\t\t\t\tpath = `${currentDirectory}${entry}`;\n\t\t\t} else {\n\t\t\t\tpath = `${currentDirectory}/${entry}`;\n\t\t\t}\n\t\t\tsetCurrentDirectory(path);\n\t\t\tif (fileInp.current) {\n\t\t\t\tfileInp.current.value = `${path}/${filename || \"file.txt\"}`;\n\t\t\t}\n\t\t} else {\n\t\t\tif (currentDirectory.endsWith(\"/\")) {\n\t\t\t\tpath = `${currentDirectory}${entry}`;\n\t\t\t} else {\n\t\t\t\tpath = `${currentDirectory}/${entry}`;\n\t\t\t}\n\t\t\tsetSelectedEntry(path);\n\t\t\tif (fileInp.current) {\n\t\t\t\tfileInp.current.value = `${path}`;\n\t\t\t}\n\t\t}\n\t};\n\tconst onSave = () => {\n\t\tconst fileName = fileInp.current?.value;\n\t\tremoveFn();\n\t\tif (fileName) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (onOk) {\n\t\t\t\t\tonOk(fileName);\n\t\t\t\t}\n\t\t\t}, 300);\n\t\t}\n\t};\n\tconst Cancel = () => {\n\t\tremoveFn();\n\t\tif (onCancel) {\n\t\t\tonCancel();\n\t\t}\n\t};\n\tconst onPathChange = (e: React.KeyboardEvent<HTMLInputElement>) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\tconst value = (e.target as HTMLInputElement).value;\n\t\t\tif (value === \"storage devices\") {\n\t\t\t\tsetCurrentDirectory(\"storage devices\");\n\t\t\t} else {\n\t\t\t\tsetCurrentDirectory(value);\n\t\t\t}\n\t\t}\n\t};\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\">\n\t\t\t<div className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t{loading ? (\n\t\t\t\t\t<div>Loading...</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={`\n\t\t\t\t\t\toverflow-y-auto min-h-[100px] max-h-[300px]\n\t\t\t\t\t\t${fileEntries.length === 0 ? \" flex justify-center items-center\" : \"bg-[#ffffff10] shadow-tb-border-shadow rounded-lg\"}\n\t\t\t\t\t`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{fileEntries.length === 0 ? (\n\t\t\t\t\t\t\t<div className=\"font-extrabold text-xl select-none\">No directories found</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\tfileEntries.map(({ entry, isDirectory, type, connected }) => (\n\t\t\t\t\t\t\t\t<div key={entry} data-entry={entry} className=\"file-item flex gap-2 items-center select-none p-1.5 first:rounded-t-lg last:rounded-b-lg duration-150\" onMouseDown={() => Select(entry, isDirectory, type)}>\n\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t\t\t\t\t\t\t{currentDirectory === \"storage devices\" ? (\n\t\t\t\t\t\t\t\t\t\t\ttype === \"external\" ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<circle cx=\"18\" cy=\"17.25\" r=\"3\" fill={connected ? \"#5DD881\" : \"#FF4D4D\"} />\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M4.07982 5.227C4.25015 4.58826 4.6267 4.02366 5.15094 3.62094C5.67518 3.21822 6.31775 2.99993 6.97882 3H17.0198C17.6811 2.99971 18.3239 3.2179 18.8483 3.62063C19.3728 4.02337 19.7494 4.58809 19.9198 5.227L22.0328 13.153C21.1022 12.4051 19.9437 11.9982 18.7498 12H5.24982C4.05559 11.998 2.89667 12.4049 1.96582 13.153L4.07982 5.227Z\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.25 13.5C4.75754 13.5 4.26991 13.597 3.81494 13.7855C3.35997 13.9739 2.94657 14.2501 2.59835 14.5983C2.25013 14.9466 1.97391 15.36 1.78545 15.8149C1.597 16.2699 1.5 16.7575 1.5 17.25C1.5 17.7425 1.597 18.2301 1.78545 18.6851C1.97391 19.14 2.25013 19.5534 2.59835 19.9017C2.94657 20.2499 3.35997 20.5261 3.81494 20.7145C4.26991 20.903 4.75754 21 5.25 21H18.75C19.2425 21 19.7301 20.903 20.1851 20.7145C20.64 20.5261 21.0534 20.2499 21.4017 19.9017C21.7499 19.5534 22.0261 19.14 22.2145 18.6851C22.403 18.2301 22.5 17.7425 22.5 17.25C22.5 16.7575 22.403 16.2699 22.2145 15.8149C22.0261 15.36 21.7499 14.9466 21.4017 14.5983C21.0534 14.2501 20.64 13.9739 20.1851 13.7855C19.7301 13.597 19.2425 13.5 18.75 13.5H5.25ZM15.75 18C15.9489 18 16.1397 17.921 16.2803 17.7803C16.421 17.6397 16.5 17.4489 16.5 17.25C16.5 17.0511 16.421 16.8603 16.2803 16.7197C16.1397 16.579 15.9489 16.5 15.75 16.5C15.5511 16.5 15.3603 16.579 15.2197 16.7197C15.079 16.8603 15 17.0511 15 17.25C15 17.4489 15.079 17.6397 15.2197 17.7803C15.3603 17.921 15.5511 18 15.75 18ZM19.5 17.25C19.5 17.4489 19.421 17.6397 19.2803 17.7803C19.1397 17.921 18.9489 18 18.75 18C18.5511 18 18.3603 17.921 18.2197 17.7803C18.079 17.6397 18 17.4489 18 17.25C18 17.0511 18.079 16.8603 18.2197 16.7197C18.3603 16.579 18.5511 16.5 18.75 16.5C18.9489 16.5 19.1397 16.579 19.2803 16.7197C19.421 16.8603 19.5 17.0511 19.5 17.25Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t) : isDirectory ? (\n\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M19.5 21a3 3 0 003-3v-4.5a3 3 0 00-3-3h-15a3 3 0 00-3 3V18a3 3 0 003 3h15zM1.5 10.146V6a3 3 0 013-3h5.379a2.25 2.25 0 011.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 013 3v1.146A4.483 4.483 0 0019.5 9h-15a4.483 4.483 0 00-3 1.146z\" />\n\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10 pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\" />\n\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t<div className=\"font-semibold text-lg\">{entry}</div>\n\t\t\t\t\t\t\t\t\t\t{currentDirectory === \"storage devices\" && entry === \"File System\" && storageInfo ? (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-xs text-[#ffffff88] ml-auto\">\n\t\t\t\t\t\t\t\t\t\t\t\t{(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn `${formatSize(storageInfo.usage)} of ${formatSize(storageInfo.quota)}`;\n\t\t\t\t\t\t\t\t\t\t\t\t})()}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t) : type === \"external\" && currentDirectory === \"storage devices\" ? (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-xs text-[#ffffff88] ml-auto\">WebDav Device</div>\n\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<div className=\"flex justify-between items-center\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t{showBackButton && (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName=\"dialog-button goBack-button cursor-pointer\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tif (currentDirectory === \"//\" || (currentDirectory.startsWith(\"/mnt/\") && currentDirectory.split(\"/\").length <= 3)) {\n\t\t\t\t\t\t\t\t\tsetCurrentDirectory(\"storage devices\");\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst parts = currentDirectory.split(\"/\");\n\t\t\t\t\t\t\t\t\tparts.pop();\n\t\t\t\t\t\t\t\t\tconst inp = parts.join(\"/\") || \"storage devices\";\n\t\t\t\t\t\t\t\t\tsetCurrentDirectory(inp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tGo Back\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)}\n\t\t\t\t\t<input\n\t\t\t\t\t\tref={fileInp}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tvalue={selectedEntry || `${currentDirectory}/${filename || \"file.txt\"}`}\n\t\t\t\t\t\tplaceholder=\"Enter file name\"\n\t\t\t\t\t\tclassName=\"p-2 pl-4 rounded-lg bg-[#ffffff16] cursor-text outline-hidden shadow-tb-border-shadow duration-150\"\n\t\t\t\t\t\tonKeyDown={e => {\n\t\t\t\t\t\t\tif (e.key === \"Enter\") {\n\t\t\t\t\t\t\t\tconst inputPath = fileInp.current!.value;\n\t\t\t\t\t\t\t\tif (inputPath.endsWith(\"/\")) {\n\t\t\t\t\t\t\t\t\tsetCurrentDirectory(inputPath);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tonSave();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonChange={e => onPathChange(e as unknown as React.KeyboardEvent<HTMLInputElement>)}\n\t\t\t\t\t/>\n\t\t\t\t\t<button className=\"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" onMouseDown={onSave}>\n\t\t\t\t\t\tSelect\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function Crop({ title, img, onOk, onCancel }: dialogProps) {\n\tconst imgRef = useRef<HTMLImageElement>(null);\n\tconst cropperRef = useRef<Cropper | null>(null);\n\tuseEffect(() => {\n\t\tif (imgRef.current && img) {\n\t\t\timgRef.current.src = img;\n\t\t\tcropperRef.current = new Cropper(imgRef.current, {\n\t\t\t\taspectRatio: 1,\n\t\t\t\tviewMode: 1,\n\t\t\t\tcropBoxResizable: false,\n\t\t\t\tmovable: true,\n\t\t\t\trotatable: true,\n\t\t\t\tscalable: true,\n\t\t\t\tresponsive: true,\n\t\t\t});\n\t\t}\n\t\treturn () => {\n\t\t\tif (cropperRef.current) {\n\t\t\t\tcropperRef.current.destroy();\n\t\t\t}\n\t\t};\n\t}, [img]);\n\tconst onSave = () => {\n\t\tif (!cropperRef.current) return;\n\t\tconst canvas = cropperRef.current.getCroppedCanvas();\n\t\tcanvas.toBlob((blob: any) => {\n\t\t\tnew Compressor(blob as Blob, {\n\t\t\t\tquality: 0.5,\n\t\t\t\tsuccess(result) {\n\t\t\t\t\tconst reader = new FileReader();\n\t\t\t\t\treader.readAsDataURL(result);\n\t\t\t\t\treader.onload = () => {\n\t\t\t\t\t\tremoveFn();\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tif (onOk) {\n\t\t\t\t\t\t\t\tonOk(reader.result);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 300);\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t};\n\tconst Cancel = () => {\n\t\tremoveFn();\n\t\tif (onCancel) {\n\t\t\tonCancel();\n\t\t}\n\t};\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\">\n\t\t\t<div className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t<img ref={imgRef} className=\"w-full h-24\"></img>\n\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button className=\"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" onMouseDown={onSave}>\n\t\t\t\t\t\tSave\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function WebAuth({ title, defaultUsername, onOk, onCancel }: dialogProps) {\n\tif (!title) throw new Error(\"title is required\");\n\tconst container = useRef<HTMLDivElement | null>(null);\n\tconst dialog = useRef<HTMLDivElement | null>(null);\n\tconst usernameRef = useRef<HTMLInputElement | null>(null);\n\tconst passwordRef = useRef<HTMLInputElement | null>(null);\n\tconst OK = () => {\n\t\tconst username = usernameRef.current?.value;\n\t\tconst password = passwordRef.current?.value;\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (container.current) {\n\t\t\t\t\tremoveFn();\n\t\t\t\t}\n\t\t\t\tif (onOk && username && password) {\n\t\t\t\t\tonOk(username, password);\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t}\n\t};\n\tconst Cancel = () => {\n\t\tif (container.current) {\n\t\t\tcontainer.current.classList.add(\"fade-out\");\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (container.current) {\n\t\t\t\t\tremoveFn();\n\t\t\t\t}\n\t\t\t\tif (onCancel) {\n\t\t\t\t\tonCancel();\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t}\n\t};\n\tconst onDown = (event: React.KeyboardEvent) => {\n\t\tif (event.key === \"Enter\") {\n\t\t\tOK();\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"mousedown\", e => {\n\t\t\tif (container.current && e.target !== dialog.current && e.target === container.current) {\n\t\t\t\tCancel();\n\t\t\t}\n\t\t});\n\t});\n\treturn (\n\t\t<div className=\"fixed inset-0 z-999999999 flex flex-col items-center justify-center bg-[#00000078] backdrop-blur-xs duration-150\" ref={container}>\n\t\t\t<div ref={dialog} className=\"flex flex-col p-2.5 gap-2.5 backdrop-blur-md rounded-lg sm:min-w-[340px] md:min-w-[400px] lg:min-w-[600px] bg-[#ffffff18] text-white shadow-tb-border-shadow duration-150\">\n\t\t\t\t<div className=\"font-extrabold text-xl leading-none select-none\">{title}</div>\n\t\t\t\t<input type=\"text\" defaultValue={defaultUsername} placeholder=\"Username\" className=\"p-2 pl-4 rounded-lg bg-[#ffffff16] cursor-text outline-hidden shadow-tb-border-shadow duration-150\" style={{ width: \"100%\" }} ref={usernameRef} />\n\t\t\t\t<input type=\"password\" placeholder=\"Password\" className=\"p-2 pl-4 rounded-lg bg-[#ffffff16] cursor-text outline-hidden shadow-tb-border-shadow duration-150\" style={{ width: \"100%\" }} ref={passwordRef} onKeyDown={onDown} />\n\t\t\t\t<div className=\"flex justify-between\">\n\t\t\t\t\t<button className=\"p-2 text-[#ffffff78] cursor-pointer hover:text-white duration-150\" onMouseDown={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button className=\"flex gap-1.5 w-max py-2 px-5 rounded-md cursor-pointer bg-[#86ff9085] shadow-[0px_0px_6px_0px_#00000052,_inset_0_0_0_0.5px_#ffffff38] hover:bg-[#8fff98a2] duration-150\" onMouseDown={OK}>\n\t\t\t\t\t\tOK\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/sys/apis/Mediaisland.tsx",
    "content": "import { useState, useEffect, useRef, useCallback } from \"react\";\nimport \"../gui/styles/mediaisland.css\";\nimport { MediaProps } from \"../types\";\n\nexport let setMusicFn: (props: MediaProps) => void;\nexport let setVideoFn: (props: MediaProps) => void;\nexport let hideFn: () => void;\nexport let isExistingFn: () => void;\n\nexport default function MediaIsland() {\n\tconst [mediaType, setMediaType] = useState<\"music\" | \"video\" | null>(null);\n\tconst [mediaProps, setMediaProps] = useState<MediaProps | {}>({});\n\tconst removeMedia = () => {\n\t\tsetMediaType(null);\n\t\tsetMediaProps({});\n\t};\n\tconst setMusic = (props: MediaProps) => {\n\t\tsetMediaType(\"music\");\n\t\tsetMediaProps(props);\n\t};\n\tconst setVideo = (props: MediaProps) => {\n\t\tsetMediaType(\"video\");\n\t\tsetMediaProps(props);\n\t};\n\t/**\n\t * @returns Components for COM\n\t * @author XSTARS\n\t */\n\tuseEffect(() => {\n\t\tsetMusicFn = setMusic;\n\t\tsetVideoFn = setVideo;\n\t\thideFn = removeMedia;\n\t\tisExistingFn = () => {\n\t\t\twindow.dispatchEvent(new CustomEvent(\"isExistingMP\", { detail: mediaType !== null }));\n\t\t};\n\t}, []);\n\treturn (\n\t\t<div className={`island media_island w-[250px] h-[50px] rounded-lg ${mediaType ? \"opacity-100\" : \"opacity-0\"}`} style={{ backgroundImage: `url(${(mediaProps as MediaProps).background})`, backgroundSize: \"cover\", backgroundPosition: \"center\", backgroundRepeat: \"no-repeat\" }}>\n\t\t\t{mediaType === \"music\" && <Music {...(mediaProps as MediaProps)} onRemove={removeMedia} />}\n\t\t\t{mediaType === \"video\" && <Video {...(mediaProps as MediaProps)} onRemove={removeMedia} />}\n\t\t</div>\n\t);\n}\n\nfunction Music({ track_name, artist, endtime, onRemove, onPausePlay, onNext, onBack, onSeek, time }: MediaProps & { onRemove: () => void }) {\n\tconst [isPaused, setIsPaused] = useState(false);\n\tconst [elapsedTime, setElapsedTime] = useState(() => (typeof time === \"number\" ? Math.max(0, Math.min(time, endtime)) : 0));\n\tconst [track, setTrack] = useState(track_name);\n\tconst rafRef = useRef<number | null>(null);\n\tconst lastClientXRef = useRef<number | null>(null);\n\tuseEffect(() => {\n\t\tif (typeof time === \"number\") {\n\t\t\tsetElapsedTime(Math.max(0, Math.min(time, endtime)));\n\t\t}\n\t}, [time, endtime]);\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tlet localId: ReturnType<typeof setTimeout> | null = null;\n\t\tconst runTick = () => {\n\t\t\tif (isPaused || cancelled) return;\n\t\t\tlocalId = setTimeout(() => {\n\t\t\t\tsetElapsedTime(prev => {\n\t\t\t\t\tconst next = prev + 1;\n\t\t\t\t\tif (next >= endtime) {\n\t\t\t\t\t\tonRemove();\n\t\t\t\t\t\treturn prev;\n\t\t\t\t\t}\n\t\t\t\t\treturn next;\n\t\t\t\t});\n\t\t\t\tif (!isPaused && !cancelled) runTick();\n\t\t\t}, 1000);\n\t\t};\n\t\tif (!isPaused) runTick();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tif (localId) clearTimeout(localId);\n\t\t};\n\t}, [isPaused]);\n\tconst PausePlay = useCallback(() => {\n\t\tsetIsPaused(prev => !prev);\n\t\t// @ts-expect-error\n\t\tif (onPausePlay) {\n\t\t\t// @ts-expect-error\n\t\t\tonPausePlay();\n\t\t}\n\t}, [onPausePlay]);\n\tuseEffect(() => {\n\t\tconst handler = () => PausePlay();\n\t\twindow.addEventListener(\"tb-pause-isl\", handler);\n\t\treturn () => window.removeEventListener(\"tb-pause-isl\", handler);\n\t}, [PausePlay]);\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (rafRef.current) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\tconst next = () => {\n\t\tif (onNext) {\n\t\t\t// @ts-ignore\n\t\t\tonNext();\n\t\t} else {\n\t\t\tonRemove();\n\t\t}\n\t};\n\tconst back = () => {\n\t\tif (onBack) {\n\t\t\t// @ts-ignore\n\t\t\tonBack();\n\t\t} else {\n\t\t\tonRemove();\n\t\t}\n\t};\n\tconst seek = (value: number) => {\n\t\tconst clamped = Math.max(0, Math.min(value, endtime));\n\t\tsetElapsedTime(clamped);\n\t\tif (onSeek) {\n\t\t\t// @ts-expect-error\n\t\t\tonSeek(clamped);\n\t\t}\n\t};\n\tconst formatTime = (time: number) => {\n\t\tconst minutes = Math.floor(time / 60);\n\t\tconst seconds = time % 60;\n\t\treturn `${minutes}:${seconds < 10 ? \"0\" : \"\"}${seconds}`;\n\t};\n\tuseEffect(() => {\n\t\tif (track.length > 21) {\n\t\t\tsetTrack(track.slice(0, 21) + \"...\");\n\t\t}\n\t}, [track_name]);\n\tconst updSeek = (clientX: number, el: HTMLDivElement) => {\n\t\tconst rect = el.getBoundingClientRect();\n\t\tconst rel = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n\t\tseek(Math.round(rel * endtime));\n\t};\n\treturn (\n\t\t<div className=\"music-player w-[250px] h-[50px]\">\n\t\t\t<div className=\"info\">\n\t\t\t\t<h1 className=\"track cursor-(--cursor-text)\">{track}</h1>\n\t\t\t\t<h2 className=\"artist cursor-(--cursor-text)\">{artist}</h2>\n\t\t\t</div>\n\t\t\t<div className=\"playerctrl gap-2\">\n\t\t\t\t<svg\n\t\t\t\t\twidth=\"16\"\n\t\t\t\t\theight=\"9\"\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tnext();\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"back cursor-pointer\"\n\t\t\t\t\tviewBox=\"0 0 16 9\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M6.25233 8.81131C7.22283 9.35152 8.43012 8.66736 8.43012 7.57709V5.80418L13.8222 8.81055C14.7927 9.35152 16 8.66811 16 7.57709V1.42267C16 0.331653 14.7927 -0.35175 13.8222 0.189215L8.43012 3.1971V1.42419C8.43012 0.333168 7.22283 -0.350992 6.25233 0.189972L0.733696 3.26756C-0.244565 3.81307 -0.244565 5.18897 0.733696 5.73448L6.25233 8.81131Z\"\n\t\t\t\t\t\tfill=\"#A4A4A4\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t\t{isPaused ? (\n\t\t\t\t\t<svg width=\"14\" height=\"15\" onClick={PausePlay} className=\"pauseplay cursor-pointer\" viewBox=\"0 0 14 15\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t\t<path fillRule=\"evenodd\" clipRule=\"evenodd\" d=\"M0 1.71209C0 0.411806 1.39998 -0.412497 2.5445 0.213937L13.1107 6.0023C14.2964 6.65153 14.2964 8.34847 13.1107 8.9977L2.54541 14.7861C1.40089 15.4125 0.00091555 14.5882 0.00091555 13.2879L0 1.71209Z\" fill=\"#DFDFDF\" />\n\t\t\t\t\t</svg>\n\t\t\t\t) : (\n\t\t\t\t\t<svg width=\"14\" height=\"15\" onClick={PausePlay} className=\"pauseplay cursor-pointer\" viewBox=\"0 0 14 15\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t\t<rect width=\"5.25\" height=\"15\" rx=\"2.625\" fill=\"#DFDFDF\" />\n\t\t\t\t\t\t<rect x=\"8.75\" width=\"5.25\" height=\"15\" rx=\"2.625\" fill=\"#DFDFDF\" />\n\t\t\t\t\t</svg>\n\t\t\t\t)}\n\t\t\t\t<svg\n\t\t\t\t\twidth=\"16\"\n\t\t\t\t\theight=\"9\"\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tback();\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"forward cursor-pointer\"\n\t\t\t\t\tviewBox=\"0 0 16 9\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M2.17779 0.189122C1.2073 -0.351863 0 0.332324 0 1.42263V7.57727C0 8.66834 1.2073 9.35176 2.17779 8.81078L7.56988 5.8043V7.57727C7.56988 8.66834 8.77717 9.35176 9.74767 8.81078L15.2663 5.73383C16.2446 5.1883 16.2446 3.81235 15.2663 3.26682L9.74767 0.189122C8.77717 -0.351863 7.56988 0.333081 7.56988 1.42263V3.1956L2.17779 0.189122Z\"\n\t\t\t\t\t\tfill=\"#A4A4A4\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t</div>\n\t\t\t<div className=\"seekbar\">\n\t\t\t\t<h4 id=\"currenttime\">{formatTime(elapsedTime)}</h4>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bar custom-range cursor-pointer\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"relative\",\n\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\theight: 6,\n\t\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\t\tbackground: \"#7b7b7b\",\n\t\t\t\t\t\tmargin: \"0 8px\",\n\t\t\t\t\t}}\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\taria-valuemin={0}\n\t\t\t\t\taria-valuemax={endtime}\n\t\t\t\t\taria-valuenow={elapsedTime}\n\t\t\t\t\ttabIndex={0}\n\t\t\t\t\tonKeyDown={e => {\n\t\t\t\t\t\tif (e.key === \"ArrowLeft\") seek(Math.max(0, elapsedTime - 5));\n\t\t\t\t\t\tif (e.key === \"ArrowRight\") seek(Math.min(endtime, elapsedTime + 5));\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerDown={e => {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tel.setPointerCapture(e.pointerId);\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\tconst handle = el.querySelector(\".custom-handle\") as HTMLDivElement | null;\n\t\t\t\t\t\tif (handle) {\n\t\t\t\t\t\t\thandle.style.opacity = \"1\";\n\t\t\t\t\t\t\thandle.style.transform = \"translate(-50%, -50%) scale(1.25)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastClientXRef.current = e.clientX;\n\t\t\t\t\t\tupdSeek(e.clientX, el);\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerMove={e => {\n\t\t\t\t\t\tif (!(e.buttons & 1)) return;\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\tlastClientXRef.current = e.clientX;\n\t\t\t\t\t\tif (rafRef.current == null) {\n\t\t\t\t\t\t\trafRef.current = requestAnimationFrame(() => {\n\t\t\t\t\t\t\t\trafRef.current = null;\n\t\t\t\t\t\t\t\tif (lastClientXRef.current != null) {\n\t\t\t\t\t\t\t\t\tupdSeek(lastClientXRef.current, el);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerUp={e => {\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tel.releasePointerCapture(e.pointerId);\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\tconst handle = el.querySelector(\".custom-handle\") as HTMLDivElement | null;\n\t\t\t\t\t\tif (handle) {\n\t\t\t\t\t\t\thandle.style.opacity = \"0\";\n\t\t\t\t\t\t\thandle.style.transform = \"translate(-50%, -50%) scale(1)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (rafRef.current) {\n\t\t\t\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\t\t\t\trafRef.current = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastClientXRef.current = null;\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerCancel={e => {\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\tconst handle = el.querySelector(\".custom-handle\") as HTMLDivElement | null;\n\t\t\t\t\t\tif (handle) {\n\t\t\t\t\t\t\thandle.style.opacity = \"0\";\n\t\t\t\t\t\t\thandle.style.transform = \"translate(-50%, -50%) scale(1)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (rafRef.current) {\n\t\t\t\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\t\t\t\trafRef.current = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastClientXRef.current = null;\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"progress\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t\t\twidth: `${endtime ? (elapsedTime / endtime) * 100 : 0}%`,\n\t\t\t\t\t\t\tbackground: \"#fff\",\n\t\t\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\t\t\ttransition: \"width 0.05s linear\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"custom-handle\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\ttop: \"50%\",\n\t\t\t\t\t\t\tleft: `${endtime ? (elapsedTime / endtime) * 100 : 0}%`,\n\t\t\t\t\t\t\ttransform: \"translate(-50%, -50%) scale(1)\",\n\t\t\t\t\t\t\twidth: 10,\n\t\t\t\t\t\t\theight: 10,\n\t\t\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\t\t\tbackground: \"#fff\",\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t\ttransition: \"transform 0.12s ease, opacity 0.12s ease\",\n\t\t\t\t\t\t\tboxShadow: \"0 0 0 6px rgba(255,255,255,0.06)\",\n\t\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<h4 id=\"endtime\">{formatTime(endtime)}</h4>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction Video({ video_name, creator, endtime, onRemove, onPausePlay, onBack, onNext, onSeek, time }: MediaProps & { onRemove: () => void }) {\n\tconst [isPaused, setIsPaused] = useState(false);\n\tconst [elapsedTime, setElapsedTime] = useState(() => (typeof time === \"number\" ? Math.max(0, Math.min(time, endtime)) : 0));\n\tconst [video, setVideo] = useState(video_name);\n\tconst rafRef = useRef<number | null>(null);\n\tconst lastClientXRef = useRef<number | null>(null);\n\tuseEffect(() => {\n\t\tif (typeof time === \"number\") {\n\t\t\tsetElapsedTime(Math.max(0, Math.min(time, endtime)));\n\t\t}\n\t}, [time, endtime]);\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tlet localId: ReturnType<typeof setTimeout> | null = null;\n\t\tconst runTick = () => {\n\t\t\tif (isPaused || cancelled) return;\n\t\t\tlocalId = setTimeout(() => {\n\t\t\t\tsetElapsedTime(prev => {\n\t\t\t\t\tconst next = prev + 1;\n\t\t\t\t\tif (next >= endtime) {\n\t\t\t\t\t\tonRemove();\n\t\t\t\t\t\treturn prev;\n\t\t\t\t\t}\n\t\t\t\t\treturn next;\n\t\t\t\t});\n\t\t\t\tif (!isPaused && !cancelled) runTick();\n\t\t\t}, 1000);\n\t\t};\n\t\tif (!isPaused) runTick();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tif (localId) clearTimeout(localId);\n\t\t};\n\t}, [isPaused]);\n\tconst PausePlay = useCallback(() => {\n\t\tsetIsPaused(prev => !prev);\n\t\t// @ts-expect-error\n\t\tif (onPausePlay) {\n\t\t\t// @ts-expect-error\n\t\t\tonPausePlay();\n\t\t}\n\t}, [onPausePlay]);\n\tuseEffect(() => {\n\t\tconst handler = () => PausePlay();\n\t\twindow.addEventListener(\"tb-pause-isl\", handler);\n\t\treturn () => window.removeEventListener(\"tb-pause-isl\", handler);\n\t}, [PausePlay]);\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (rafRef.current) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t};\n\t}, []);\n\tconst next = () => {\n\t\tif (onNext) {\n\t\t\t// @ts-ignore\n\t\t\tonNext();\n\t\t} else {\n\t\t\tonRemove();\n\t\t}\n\t};\n\tconst back = () => {\n\t\tif (onBack) {\n\t\t\t// @ts-ignore\n\t\t\tonBack();\n\t\t} else {\n\t\t\tonRemove();\n\t\t}\n\t};\n\tconst seek = (value: number) => {\n\t\tconst clamped = Math.max(0, Math.min(value, endtime));\n\t\tsetElapsedTime(clamped);\n\t\tif (onSeek) {\n\t\t\t// @ts-expect-error\n\t\t\tonSeek(clamped);\n\t\t}\n\t};\n\tconst formatTime = (time: number) => {\n\t\tconst minutes = Math.floor(time / 60);\n\t\tconst seconds = time % 60;\n\t\treturn `${minutes}:${seconds < 10 ? \"0\" : \"\"}${seconds}`;\n\t};\n\tuseEffect(() => {\n\t\tif (video.length > 21) {\n\t\t\tsetVideo(video.slice(0, 21) + \"...\");\n\t\t}\n\t}, [video_name]);\n\tconst updSeek = (clientX: number, el: HTMLDivElement) => {\n\t\tconst rect = el.getBoundingClientRect();\n\t\tconst rel = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n\t\tseek(Math.round(rel * endtime));\n\t};\n\treturn (\n\t\t<div className=\"music-player w-[250px] h-[50px]\">\n\t\t\t<div className=\"info\">\n\t\t\t\t<h1 className=\"track cursor-(--cursor-text)\">{video}</h1>\n\t\t\t\t<h2 className=\"artist cursor-(--cursor-text)\">{creator}</h2>\n\t\t\t</div>\n\t\t\t<div className=\"playerctrl gap-2\">\n\t\t\t\t<svg\n\t\t\t\t\twidth=\"16\"\n\t\t\t\t\theight=\"9\"\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tnext();\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"back cursor-pointer\"\n\t\t\t\t\tviewBox=\"0 0 16 9\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M6.25233 8.81131C7.22283 9.35152 8.43012 8.66736 8.43012 7.57709V5.80418L13.8222 8.81055C14.7927 9.35152 16 8.66811 16 7.57709V1.42267C16 0.331653 14.7927 -0.35175 13.8222 0.189215L8.43012 3.1971V1.42419C8.43012 0.333168 7.22283 -0.350992 6.25233 0.189972L0.733696 3.26756C-0.244565 3.81307 -0.244565 5.18897 0.733696 5.73448L6.25233 8.81131Z\"\n\t\t\t\t\t\tfill=\"#A4A4A4\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t\t{isPaused ? (\n\t\t\t\t\t<svg width=\"14\" height=\"15\" onClick={PausePlay} className=\"pauseplay cursor-pointer\" viewBox=\"0 0 14 15\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t\t<path fillRule=\"evenodd\" clipRule=\"evenodd\" d=\"M0 1.71209C0 0.411806 1.39998 -0.412497 2.5445 0.213937L13.1107 6.0023C14.2964 6.65153 14.2964 8.34847 13.1107 8.9977L2.54541 14.7861C1.40089 15.4125 0.00091555 14.5882 0.00091555 13.2879L0 1.71209Z\" fill=\"#DFDFDF\" />\n\t\t\t\t\t</svg>\n\t\t\t\t) : (\n\t\t\t\t\t<svg width=\"14\" height=\"15\" onClick={PausePlay} className=\"pauseplay cursor-pointer\" viewBox=\"0 0 14 15\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t\t\t\t\t<rect width=\"5.25\" height=\"15\" rx=\"2.625\" fill=\"#DFDFDF\" />\n\t\t\t\t\t\t<rect x=\"8.75\" width=\"5.25\" height=\"15\" rx=\"2.625\" fill=\"#DFDFDF\" />\n\t\t\t\t\t</svg>\n\t\t\t\t)}\n\t\t\t\t<svg\n\t\t\t\t\twidth=\"16\"\n\t\t\t\t\theight=\"9\"\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tback();\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"forward cursor-pointer\"\n\t\t\t\t\tviewBox=\"0 0 16 9\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M2.17779 0.189122C1.2073 -0.351863 0 0.332324 0 1.42263V7.57727C0 8.66834 1.2073 9.35176 2.17779 8.81078L7.56988 5.8043V7.57727C7.56988 8.66834 8.77717 9.35176 9.74767 8.81078L15.2663 5.73383C16.2446 5.1883 16.2446 3.81235 15.2663 3.26682L9.74767 0.189122C8.77717 -0.351863 7.56988 0.333081 7.56988 1.42263V3.1956L2.17779 0.189122Z\"\n\t\t\t\t\t\tfill=\"#A4A4A4\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t</div>\n\t\t\t<div className=\"seekbar\">\n\t\t\t\t<h4 id=\"currenttime\">{formatTime(elapsedTime)}</h4>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"bar custom-range cursor-pointer\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tposition: \"relative\",\n\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\theight: 6,\n\t\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\t\tbackground: \"#7b7b7b\",\n\t\t\t\t\t\tmargin: \"0 8px\",\n\t\t\t\t\t}}\n\t\t\t\t\trole=\"slider\"\n\t\t\t\t\taria-valuemin={0}\n\t\t\t\t\taria-valuemax={endtime}\n\t\t\t\t\taria-valuenow={elapsedTime}\n\t\t\t\t\ttabIndex={0}\n\t\t\t\t\tonKeyDown={e => {\n\t\t\t\t\t\tif (e.key === \"ArrowLeft\") seek(Math.max(0, elapsedTime - 5));\n\t\t\t\t\t\tif (e.key === \"ArrowRight\") seek(Math.min(endtime, elapsedTime + 5));\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerDown={e => {\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tel.setPointerCapture(e.pointerId);\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\tconst handle = el.querySelector(\".custom-handle\") as HTMLDivElement | null;\n\t\t\t\t\t\tif (handle) {\n\t\t\t\t\t\t\thandle.style.opacity = \"1\";\n\t\t\t\t\t\t\thandle.style.transform = \"translate(-50%, -50%) scale(1.25)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastClientXRef.current = e.clientX;\n\t\t\t\t\t\tupdSeek(e.clientX, el);\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerMove={e => {\n\t\t\t\t\t\tif (!(e.buttons & 1)) return;\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\tlastClientXRef.current = e.clientX;\n\t\t\t\t\t\tif (rafRef.current == null) {\n\t\t\t\t\t\t\trafRef.current = requestAnimationFrame(() => {\n\t\t\t\t\t\t\t\trafRef.current = null;\n\t\t\t\t\t\t\t\tif (lastClientXRef.current != null) {\n\t\t\t\t\t\t\t\t\tupdSeek(lastClientXRef.current, el);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerUp={e => {\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tel.releasePointerCapture(e.pointerId);\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\tconst handle = el.querySelector(\".custom-handle\") as HTMLDivElement | null;\n\t\t\t\t\t\tif (handle) {\n\t\t\t\t\t\t\thandle.style.opacity = \"0\";\n\t\t\t\t\t\t\thandle.style.transform = \"translate(-50%, -50%) scale(1)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (rafRef.current) {\n\t\t\t\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\t\t\t\trafRef.current = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastClientXRef.current = null;\n\t\t\t\t\t}}\n\t\t\t\t\tonPointerCancel={e => {\n\t\t\t\t\t\tconst el = e.currentTarget as HTMLDivElement;\n\t\t\t\t\t\tconst handle = el.querySelector(\".custom-handle\") as HTMLDivElement | null;\n\t\t\t\t\t\tif (handle) {\n\t\t\t\t\t\t\thandle.style.opacity = \"0\";\n\t\t\t\t\t\t\thandle.style.transform = \"translate(-50%, -50%) scale(1)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (rafRef.current) {\n\t\t\t\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\t\t\t\trafRef.current = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastClientXRef.current = null;\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"progress\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t\t\twidth: `${endtime ? (elapsedTime / endtime) * 100 : 0}%`,\n\t\t\t\t\t\t\tbackground: \"#fff\",\n\t\t\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\t\t\ttransition: \"width 0.05s linear\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"custom-handle\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\ttop: \"50%\",\n\t\t\t\t\t\t\tleft: `${endtime ? (elapsedTime / endtime) * 100 : 0}%`,\n\t\t\t\t\t\t\ttransform: \"translate(-50%, -50%) scale(1)\",\n\t\t\t\t\t\t\twidth: 10,\n\t\t\t\t\t\t\theight: 10,\n\t\t\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\t\t\tbackground: \"#fff\",\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t\ttransition: \"transform 0.12s ease, opacity 0.12s ease\",\n\t\t\t\t\t\t\tboxShadow: \"0 0 0 6px rgba(255,255,255,0.06)\",\n\t\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<h4 id=\"endtime\">{formatTime(endtime)}</h4>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/sys/apis/Notifications.tsx",
    "content": "import { NotificationProps } from \"../types\";\nimport { useState, useEffect } from \"react\";\nimport \"../gui/styles/notification.css\";\n\nexport let setNotifFn: (type: \"message\" | \"toast\" | \"installing\", props: NotificationProps) => number;\nexport let dismissNotifFn: (id: number) => void;\nlet notificationId = 0;\nlet notificationCount = 0;\n\nexport default function NotificationContainer() {\n\tconst [notifications, setNotifications] = useState<{ id: number; type: \"message\" | \"toast\" | \"installing\"; props: NotificationProps }[]>([]);\n\tconst remove = (id: number) => {\n\t\tsetNotifications(prev => prev.filter(notif => notif.id !== id));\n\t};\n\tconst setNotif = (type: \"message\" | \"toast\" | \"installing\", props: NotificationProps) => {\n\t\tconst id = notificationId++;\n\t\tconst newNotification = { id, type, props };\n\t\tsetNotifications(prev => [...prev, newNotification]);\n\t\treturn id;\n\t};\n\t/**\n\t * @returns Components for COM\n\t * @author XSTARS\n\t */\n\tuseEffect(() => {\n\t\tsetNotifFn = setNotif;\n\t\tdismissNotifFn = remove;\n\t}, []);\n\treturn (\n\t\t<div className=\"absolute grid grid-cols-1 h-max max-h-[calc(100%-calc(60px+1.5rem))] w-[380px] top-[60px] right-1.5 z-9999 gap-2\">\n\t\t\t{notifications.map(({ id, type, props }) => {\n\t\t\t\tif (type === \"message\") {\n\t\t\t\t\treturn <Message key={id} {...props} remove={() => remove(id)} />;\n\t\t\t\t} else if (type === \"toast\") {\n\t\t\t\t\treturn <Toast key={id} {...props} remove={() => remove(id)} />;\n\t\t\t\t} else if (type === \"installing\") {\n\t\t\t\t\treturn <Installing key={id} {...props} remove={() => remove(id)} />;\n\t\t\t\t}\n\t\t\t})}\n\t\t</div>\n\t);\n}\n\nexport function Message({ iconSrc, application, message, txt, onOk, onCancel, time, remove }: NotificationProps & { remove: () => void }) {\n\tif (!message) throw new Error(\"message is required\");\n\tconst [inputValue, setInputValue] = useState(txt || \"\");\n\tconst [elapsedTime, setElapsedTime] = useState<string>(\"Now\");\n\tuseEffect(() => {\n\t\tconst startTime = Date.now();\n\t\tconst int = setInterval(() => {\n\t\t\tconst elapsed = Math.floor((Date.now() - startTime) / 60000);\n\t\t\tif (elapsed > 0) {\n\t\t\t\tsetElapsedTime(`${elapsed}min ago`);\n\t\t\t} else if (elapsed > 60) {\n\t\t\t\tsetElapsedTime(`${Math.floor(elapsed / 60)}h ago`);\n\t\t\t} else if (elapsed > 1440) {\n\t\t\t\tsetElapsedTime(`${Math.floor(elapsed / 1440)}d ago`);\n\t\t\t} else if (elapsed > 10080) {\n\t\t\t\tsetElapsedTime(`${Math.floor(elapsed / 10080)}w ago`);\n\t\t\t}\n\t\t}, 60000);\n\t\tconst tID = setTimeout(() => {\n\t\t\tCancel();\n\t\t}, time || 10000);\n\t\treturn () => {\n\t\t\tclearInterval(int);\n\t\t\tclearTimeout(tID);\n\t\t};\n\t}, [time]);\n\tconst OK = () => {\n\t\tsetTimeout(() => {\n\t\t\tremove();\n\t\t\tif (onOk) onOk(inputValue);\n\t\t}, 200);\n\t};\n\tconst Cancel = () => {\n\t\tsetTimeout(() => {\n\t\t\tremove();\n\t\t\tnotificationCount += 1;\n\t\t\twindow.dispatchEvent(\n\t\t\t\tnew CustomEvent(\"notification-count\", {\n\t\t\t\t\tdetail: { count: notificationCount },\n\t\t\t\t}),\n\t\t\t);\n\t\t\tSaveNotification({ iconSrc, application, message, onOk });\n\t\t\tif (onCancel) onCancel();\n\t\t}, 200);\n\t};\n\tconst onDown = (event: React.KeyboardEvent) => {\n\t\tif (event.key === \"Enter\") {\n\t\t\tOK();\n\t\t}\n\t};\n\treturn (\n\t\t<div className={`flex flex-col shadow-tb-border-shadow bg-[#00000088] rounded-lg backdrop-blur-lg fade-in`}>\n\t\t\t<div className=\"flex justify-between items-center p-2 bg-[#ffffff20] rounded-t-lg\">\n\t\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t\t<img className=\"size-6\" src={iconSrc || \"/assets/img/logo.png\"} alt={application} />\n\t\t\t\t\t<div>{application || \"Unknown App\"}</div>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"font-semibold text-[#ffffffa0] text-sm\">{elapsedTime}</div>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col gap-2 p-2.5\">\n\t\t\t\t<div className=\"text-lg font-semibold\">{message}</div>\n\t\t\t\t<input type=\"text\" value={inputValue} onChange={e => setInputValue(e.target.value)} onKeyDown={onDown} className=\"w-full p-2 rounded-md leading-none text-lg bg-[#ffffff20] shadow-tb-border-shadow cursor-[var(--cursor-text)] focus-within:outline-hidden\" />\n\t\t\t\t<div className=\"flex gap-2 justify-between\">\n\t\t\t\t\t<button className=\"leading-none p-2.5 px-4 bg-[#ffffff20] shadow-tb-border rounded-md hover:bg-[#ffffff30] duration-150 cursor-pointer\" onClick={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button className=\"leading-none p-2.5 px-4 bg-[#53f67463] shadow-tb-border rounded-md hover:bg-[#53f67473] duration-150 cursor-pointer\" onClick={OK}>\n\t\t\t\t\t\tOK\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function Toast({ iconSrc, application, message, time, onOk, onCancel, remove }: NotificationProps & { remove: () => void }) {\n\tconst [elapsedTime, setElapsedTime] = useState<string>(\"Now\");\n\tuseEffect(() => {\n\t\tconst startTime = Date.now();\n\t\tconst int = setInterval(() => {\n\t\t\tconst elapsed = Math.floor((Date.now() - startTime) / 60000);\n\t\t\tif (elapsed > 0) {\n\t\t\t\tsetElapsedTime(`${elapsed}min ago`);\n\t\t\t} else if (elapsed > 60) {\n\t\t\t\tsetElapsedTime(`${Math.floor(elapsed / 60)}h ago`);\n\t\t\t} else if (elapsed > 1440) {\n\t\t\t\tsetElapsedTime(`${Math.floor(elapsed / 1440)}d ago`);\n\t\t\t} else if (elapsed > 10080) {\n\t\t\t\tsetElapsedTime(`${Math.floor(elapsed / 10080)}w ago`);\n\t\t\t}\n\t\t}, 60000);\n\t\tconst tID = setTimeout(() => {\n\t\t\tCancel();\n\t\t}, time || 10000);\n\t\treturn () => {\n\t\t\tclearInterval(int);\n\t\t\tclearTimeout(tID);\n\t\t};\n\t}, [time]);\n\tconst Cancel = () => {\n\t\tsetTimeout(() => {\n\t\t\tremove();\n\t\t\tnotificationCount += 1;\n\t\t\twindow.dispatchEvent(\n\t\t\t\tnew CustomEvent(\"notification-count\", {\n\t\t\t\t\tdetail: { count: notificationCount },\n\t\t\t\t}),\n\t\t\t);\n\t\t\tSaveNotification({ iconSrc, application, message, onOk });\n\t\t\tif (onCancel) onCancel();\n\t\t}, 200);\n\t};\n\tconst OK = () => {\n\t\tsetTimeout(() => {\n\t\t\tremove();\n\t\t\tif (onOk) onOk();\n\t\t}, 200);\n\t};\n\treturn (\n\t\t<div className={`flex flex-col shadow-tb-border-shadow bg-[#00000088] rounded-lg backdrop-blur-lg fade-in`}>\n\t\t\t<div className=\"flex justify-between items-center p-2 bg-[#ffffff20] rounded-t-lg\">\n\t\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t\t<img className=\"size-6\" src={iconSrc || \"/assets/img/logo.png\"} alt={application} />\n\t\t\t\t\t<div>{application || \"Unkown App\"}</div>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"font-semibold text-[#ffffffa0] text-sm\">{elapsedTime}</div>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col gap-2 p-2.5\">\n\t\t\t\t<div className=\"text-lg font-semibold\">{message}</div>\n\t\t\t\t<div className=\"flex gap-2 justify-between\">\n\t\t\t\t\t<button className=\"leading-none p-2.5 px-4 bg-[#ffffff20] shadow-tb-border rounded-md hover:bg-[#ffffff30] duration-150 cursor-pointer\" onClick={Cancel}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</button>\n\t\t\t\t\t<button className=\"leading-none p-2.5 px-4 bg-[#53f67463] shadow-tb-border rounded-md hover:bg-[#53f67473] duration-150 cursor-pointer\" onClick={OK}>\n\t\t\t\t\t\tOK\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport function Installing({ iconSrc, application, message, time, onOk, remove }: NotificationProps & { remove: () => void }) {\n\tconst [currentAnimation, setCurrentAnimation] = useState<number>(0);\n\n\tuseEffect(() => {\n\t\tif (typeof time !== \"number\" || time <= 0) return;\n\t\tconst tID = setTimeout(() => {\n\t\t\tOK();\n\t\t}, time);\n\t\treturn () => {\n\t\t\tclearTimeout(tID);\n\t\t};\n\t}, [time]);\n\n\tconst anim0 = `anim0 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite`;\n\tconst anim1 = `anim1 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite`;\n\n\tuseEffect(() => {\n\t\tconst int = setInterval(() => {\n\t\t\tsetCurrentAnimation(prev => (prev === 0 ? 1 : 0));\n\t\t}, 2100);\n\t\treturn () => clearInterval(int);\n\t}, []);\n\n\tconst OK = () => {\n\t\tsetTimeout(() => {\n\t\t\tremove();\n\t\t\tif (onOk) onOk();\n\t\t}, 200);\n\t};\n\treturn (\n\t\t<div className={`flex flex-col shadow-tb-border-shadow bg-[#00000088] rounded-lg backdrop-blur-lg fade-in`}>\n\t\t\t<div className=\"flex items-center gap-2 p-2 bg-[#ffffff20] rounded-t-lg\">\n\t\t\t\t<img src={iconSrc || \"/assets/img/logo.png\"} alt=\"Icon\" style={{ width: \"25px\", height: \"25px\" }} />\n\t\t\t\t<div className=\"notification-application\">{application || \"com.tb.genericapp\"}</div>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col gap-2 p-2.5\">\n\t\t\t\t<div className=\"text-lg font-semibold\">{message}</div>\n\t\t\t\t<div className=\"relative flex w-full h-3 rounded-full bg-[#00000020] overflow-hidden\">\n\t\t\t\t\t{currentAnimation === 0 ? <div className=\"absolute h-full bg-[#50bf66] rounded-full\" style={{ animation: anim0 }}></div> : <div className=\"absolute h-full bg-[#50bf66] rounded-full\" style={{ animation: anim1 }}></div>}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex gap-2 justify-between\">\n\t\t\t\t\t<button className=\"leading-none p-2.5 px-4 bg-[#53f67463] shadow-tb-border rounded-md hover:bg-[#53f67473] duration-150 cursor-pointer\" style={{ position: \"sticky\", left: \"100%\" }} onClick={OK}>\n\t\t\t\t\t\tOK\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport async function SaveNotification({ iconSrc, application, message, onOk }: NotificationProps) {\n\tconst notifications = JSON.parse(sessionStorage.getItem(\"notifications\") || \"[]\");\n\tif (onOk) {\n\t\tconst notificationObject = {\n\t\t\tmessage: message,\n\t\t\ticon: iconSrc || \"/assets/img/logo.png\",\n\t\t\tapplication: application || \"com.tb.genericapp\",\n\t\t\ttime: new Date().toISOString(),\n\t\t\tonOk: {\n\t\t\t\tcode: await onOk.toString(),\n\t\t\t},\n\t\t};\n\t\tconsole.log(notificationObject);\n\t\tnotifications.push(notificationObject);\n\t\tsessionStorage.setItem(\"notifications\", JSON.stringify(notifications));\n\t\twindow.dispatchEvent(new CustomEvent(\"notification-update\"));\n\t} else {\n\t\tconst notificationObject = {\n\t\t\tmessage: message,\n\t\t\ticon: iconSrc || \"/assets/img/logo.png\",\n\t\t\tapplication: application || \"com.tb.genericapp\",\n\t\t\ttime: new Date().toISOString(),\n\t\t};\n\t\tnotifications.push(notificationObject);\n\t\tsessionStorage.setItem(\"notifications\", JSON.stringify(notifications));\n\t\twindow.dispatchEvent(new CustomEvent(\"notification-update\"));\n\t}\n}\n"
  },
  {
    "path": "src/sys/apis/Registry.ts",
    "content": "export const registry = {\n\tcache: {\n\t\tstate: \"unready\",\n\t},\n\tasync get(data: any): Promise<any> {\n\t\tif (this.cache.state == \"unready\") {\n\t\t\t// Defer if not loaded\n\t\t\tconsole.log(\"Unable to get data, retrying in 2 seconds\");\n\t\t\tawait new Promise(r => setTimeout(r, 2000));\n\n\t\t\treturn (await this.get(data)) as any;\n\t\t}\n\t\t// @ts-expect-error\n\t\treturn this.cache[data.path] as any;\n\t},\n\tasync set(data: any) {\n\t\tif (this.cache.state == \"unready\") {\n\t\t\t// Defer if not loaded\n\t\t\tconsole.log(\"Unable to get data, retrying in 2 seconds\");\n\t\t\tawait new Promise(r => setTimeout(r, 2000));\n\n\t\t\tawait this.set(data);\n\t\t}\n\n\t\t// void, nothing happens here for now other than storing changes for the session\n\t\t//@ts-ignore\n\t\tthis.cache[data.path] = data.content;\n\n\t\tawait window.tb.fs.promises.writeFile(\"/system/etc/terbium/settings.json\", JSON.stringify(this.cache));\n\t},\n\texists(data: any) {\n\t\t// @ts-expect-error\n\t\tif (this.cache[data]) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n};\n"
  },
  {
    "path": "src/sys/apis/SysSearch.ts",
    "content": "import { WindowConfig } from \"../types\";\nimport { fileStat, isFilePathString } from \"./utils/file\";\n\ntype TSearchTerm = string | object | File | ArrayBuffer | Blob | null;\n\nexport const searchFiles = async (searchTerm: TSearchTerm): Promise<boolean | any[]> => {\n\tconst sh = window.tb.sh;\n\tconst searchResults: any[] = [];\n\tconst searchTermString = typeof searchTerm === \"string\" ? searchTerm : JSON.stringify(searchTerm);\n\tconst files = await sh.promises.find(\"/\", { name: `*${searchTermString}` });\n\n\tif (!isFilePathString(searchTermString)) return false;\n\tfor (const file of files) {\n\t\tconst filePath = file.replace(/\\\\/g, \"/\");\n\t\tconst fileName = await fileStat.name(filePath);\n\t\tconst fileDir = await fileStat.dir(filePath);\n\t\tconst fileSize = await fileStat.size(filePath);\n\t\tconst fileType = await fileStat.type(filePath);\n\t\tconst fileMTime = await fileStat.mTime(filePath);\n\t\tconst fileCTime = await fileStat.cTime(filePath);\n\t\tconst fileExt = await fileStat.ext(filePath);\n\n\t\tif (fileName.toLowerCase().includes(searchTermString.toLowerCase())) {\n\t\t\tlet result = {\n\t\t\t\tname: fileName,\n\t\t\t\text: fileExt,\n\t\t\t\ttype: fileType,\n\t\t\t\tsize: fileSize,\n\t\t\t\tcTime: fileCTime,\n\t\t\t\tmTime: fileMTime,\n\t\t\t\tpath: filePath,\n\t\t\t\tdir: fileDir,\n\t\t\t};\n\t\t\tsearchResults.push(result);\n\t\t}\n\t}\n\n\tif (searchResults.length > 0) {\n\t\treturn searchResults;\n\t} else {\n\t\treturn false;\n\t}\n};\n\ninterface IAppName {\n\ttext: string;\n\tweight?: number;\n\thtml?: string;\n}\n\ntype TAppName = string | IAppName;\n\nexport const searchApps = async (searchTerm: TSearchTerm): Promise<boolean | { dir: string; icon: string; cfg: WindowConfig; name?: TAppName }[]> => {\n\tlet searchTermString = typeof searchTerm === \"string\" ? searchTerm.toLowerCase() : JSON.stringify(searchTerm).toLowerCase();\n\tif (searchTermString.endsWith(\".tapp\")) searchTermString = searchTermString.replace(\".tapp\", \"\");\n\n\tlet installed = JSON.parse(await window.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\tconst searchResults: any[] = [];\n\tfor (const app of installed) {\n\t\tif (app.name && app.name.toLowerCase().includes(searchTermString)) {\n\t\t\tlet cfg = JSON.parse(await window.tb.fs.promises.readFile(app.config, \"utf8\"));\n\t\t\tlet icon: string | null = null;\n\t\t\tlet appDir = app.config.replace(/\\/[^\\/]+\\.json$|\\/[^\\/]+\\.tbconfig$/i, \"\");\n\n\t\t\ttry {\n\t\t\t\tif (cfg.icon) {\n\t\t\t\t\ticon = cfg.icon.includes(\"http\") ? cfg.icon : cfg.icon;\n\t\t\t\t} else if (cfg.config && cfg.config.icon) {\n\t\t\t\t\ticon = cfg.config.icon.includes(\"http\") ? cfg.config.icon : cfg.config.icon;\n\t\t\t\t} else if (cfg.wmArgs) {\n\t\t\t\t\ticon = cfg.wmArgs.icon.includes(\"http\") ? cfg.config.icon : cfg.config.icon;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\ticon = `\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-[49] h-[49]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                        <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\" clip-rule=\"evenodd\"></path>\n                        <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\"></path>\n                    </svg>\n                `;\n\t\t\t}\n\n\t\t\tif (!icon) {\n\t\t\t\ticon = `\n                    <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-[49] h-[49]\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\">\n                        <path fill-rule=\"evenodd\" d=\"M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0016.5 9h-1.875a1.875 1.875 0 01-1.875-1.875V5.25A3.75 3.75 0 009 1.5H5.625zM7.5 15a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5A.75.75 0 017.5 15zm.75 2.25a.75.75 0 000 1.5H12a.75.75 0 000-1.5H8.25z\" clip-rule=\"evenodd\"></path>\n                        <path d=\"M12.971 1.816A5.23 5.23 0 0114.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 013.434 1.279 9.768 9.768 0 00-6.963-6.963z\"></path>\n                    </svg>\n                `;\n\t\t\t}\n\n\t\t\tif (cfg.manifest) {\n\t\t\t\tcfg.config = {\n\t\t\t\t\ttitle: cfg.manifest.name,\n\t\t\t\t\ticon: cfg.manifest.name.includes(\"Anura File Manager\") ? \"/apps/fsapp.app/files.png\" : `${appDir}/${cfg.manifest.icon}`,\n\t\t\t\t\tsrc: cfg.manifest.name.includes(\"Anura File Manager\") ? \"/apps/fsapp.app/index.html\" : `${appDir}/${cfg.manifest.index}`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet title: string = cfg.name;\n\t\t\tif (cfg.config && cfg.config.title) {\n\t\t\t\ttitle = typeof cfg.config.title === \"object\" ? cfg.config.title.text : cfg.config.title;\n\t\t\t} else if (cfg.wmArgs && cfg.wmArgs.title) {\n\t\t\t\ttitle = typeof cfg.wmArgs.title === \"object\" ? cfg.wmArgs.title.text : cfg.wmArgs.title;\n\t\t\t}\n\t\t\tsearchResults.push({\n\t\t\t\tdir: title.includes(\"Anura File Manager\") ? \"int://apps/fsapp.app/\" : appDir,\n\t\t\t\ticon: title.includes(\"Anura File Manager\") ? \"/apps/fsapp.app/files.png\" : icon,\n\t\t\t\tname: title,\n\t\t\t\tcfg: cfg.config,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn searchResults.length > 0 ? searchResults : false;\n};\n"
  },
  {
    "path": "src/sys/apis/System.ts",
    "content": "import { version } from \"../../../package.json\";\nimport { hash, repository } from \"../../hash.json\";\n\nexport class System {\n\tversion(type: string | number) {\n\t\tif (type === \"string\") return version.toString();\n\t\telse if (type === \"number\") return Number(version);\n\t}\n\tinstance = {\n\t\trepo: repository,\n\t\thash: hash,\n\t};\n}\n"
  },
  {
    "path": "src/sys/apis/Time.ts",
    "content": "let format: string = \"12h\";\nlet internet: boolean;\nlet showSeconds: boolean;\nconst getTime = () => {\n\twindow.tb.fs.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, (err: any, data: any) => {\n\t\tif (err) return console.error(err);\n\t\tconst settings = JSON.parse(data);\n\t\tformat = settings[\"times\"][\"format\"];\n\t\tinternet = settings[\"times\"][\"internet\"];\n\t\tif (settings[\"times\"][\"showSeconds\"] === true) {\n\t\t\tshowSeconds = true;\n\t\t} else {\n\t\t\tshowSeconds = false;\n\t\t}\n\t});\n\tconst date = new Date();\n\tlet hours: any;\n\tlet minutes: any;\n\tlet seconds: any;\n\tlet time: string = \"\";\n\tif (format === \"24h\") {\n\t\thours = date.getHours();\n\t} else if (format === \"12h\") {\n\t\thours = date.getHours();\n\t\thours = hours % 12;\n\t\thours = hours ? hours : 12;\n\t}\n\tlet ampm: string;\n\n\t// set am or pm\n\tif (date.getHours() >= 12) {\n\t\tampm = \"PM\";\n\t} else {\n\t\tampm = \"AM\";\n\t}\n\n\tminutes = date.getMinutes();\n\tseconds = date.getSeconds();\n\thours = hours < 10 ? \"0\" + hours : hours;\n\tminutes = minutes < 10 ? \"0\" + minutes : minutes;\n\tseconds = seconds < 10 ? \"0\" + seconds : seconds;\n\tif (format === \"24h\") {\n\t\tif (showSeconds === true) {\n\t\t\ttime = `${hours}:${minutes}:${seconds}`;\n\t\t} else {\n\t\t\ttime = `${hours}:${minutes}`;\n\t\t}\n\t} else if (format === \"12h\") {\n\t\tif (showSeconds === true) {\n\t\t\ttime = `${hours}:${minutes}:${seconds} ${ampm}`;\n\t\t} else {\n\t\t\ttime = `${hours}:${minutes} ${ampm}`;\n\t\t}\n\t}\n\treturn time;\n};\n\nexport default getTime;\n"
  },
  {
    "path": "src/sys/apis/Xor.ts",
    "content": "export const XOR = {\n\tencode(input: string): string {\n\t\tlet result = \"\";\n\t\tlet len = input.length;\n\t\tfor (let i = 0; i < len; i++) {\n\t\t\tconst char = input[i];\n\t\t\tresult += i % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char;\n\t\t}\n\t\treturn encodeURIComponent(result);\n\t},\n\tdecode(input: string): string {\n\t\tif (!input) return input;\n\t\tinput = decodeURIComponent(input);\n\t\tlet result = \"\";\n\t\tlet len = input.length;\n\t\tfor (let i = 0; i < len; i++) {\n\t\t\tconst char = input[i];\n\t\t\tresult += i % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char;\n\t\t}\n\t\treturn result;\n\t},\n};\n"
  },
  {
    "path": "src/sys/apis/utils/WindowPerformanceMonitor.ts",
    "content": "/**\n * Window Performance Monitor\n *\n * This utility helps measure window rendering performance.\n * Usage in browser console:\n *\n * const monitor = new WindowPerformanceMonitor();\n * monitor.start();\n * // Perform window operations (drag, resize, open/close)\n * monitor.stop();\n * monitor.getReport();\n */\n\nclass WindowPerformanceMonitor {\n\tprivate startTime: number = 0;\n\tprivate metrics: {\n\t\tframeRates: number[];\n\t\tpaintTimes: number[];\n\t\tlayoutTimes: number[];\n\t\tdragCount: number;\n\t\tresizeCount: number;\n\t\trenderCount: number;\n\t} = {\n\t\tframeRates: [],\n\t\tpaintTimes: [],\n\t\tlayoutTimes: [],\n\t\tdragCount: 0,\n\t\tresizeCount: 0,\n\t\trenderCount: 0,\n\t};\n\tprivate observer: PerformanceObserver | null = null;\n\tprivate rafId: number | null = null;\n\tprivate lastFrameTime: number = 0;\n\n\tstart() {\n\t\tconsole.log(\"🚀 Window Performance Monitor started\");\n\t\tthis.startTime = performance.now();\n\t\tthis.lastFrameTime = this.startTime;\n\n\t\tthis.observer = new PerformanceObserver(list => {\n\t\t\tfor (const entry of list.getEntries()) {\n\t\t\t\tif (entry.entryType === \"paint\") {\n\t\t\t\t\tthis.metrics.paintTimes.push(entry.startTime);\n\t\t\t\t} else if (entry.entryType === \"measure\") {\n\t\t\t\t\tif (entry.name.includes(\"layout\")) {\n\t\t\t\t\t\tthis.metrics.layoutTimes.push(entry.duration);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tthis.observer.observe({ entryTypes: [\"paint\", \"measure\"] });\n\n\t\tconst measureFrameRate = () => {\n\t\t\tconst now = performance.now();\n\t\t\tconst delta = now - this.lastFrameTime;\n\t\t\tconst fps = 1000 / delta;\n\t\t\tthis.metrics.frameRates.push(fps);\n\t\t\tthis.lastFrameTime = now;\n\t\t\tthis.rafId = requestAnimationFrame(measureFrameRate);\n\t\t};\n\n\t\tthis.rafId = requestAnimationFrame(measureFrameRate);\n\n\t\tconst mutationObserver = new MutationObserver(mutations => {\n\t\t\tthis.metrics.renderCount += mutations.length;\n\t\t});\n\n\t\tconst windowArea = document.querySelector(\"window-area\");\n\t\tif (windowArea) {\n\t\t\tmutationObserver.observe(windowArea, {\n\t\t\t\tattributes: true,\n\t\t\t\tchildList: true,\n\t\t\t\tsubtree: true,\n\t\t\t});\n\t\t}\n\n\t\tthis.monitorWindowOperations();\n\t}\n\n\tprivate monitorWindowOperations() {\n\t\tconst originalAddEventListener = window.addEventListener;\n\t\tlet isDragging = false;\n\t\tlet isResizing = false;\n\n\t\twindow.addEventListener = function (type: string, listener: any, options?: any) {\n\t\t\tif (type === \"mousemove\") {\n\t\t\t\tconst wrappedListener = (e: MouseEvent) => {\n\t\t\t\t\tif (isDragging) {\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tthis.metrics.dragCount++;\n\t\t\t\t\t}\n\t\t\t\t\tif (isResizing) {\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tthis.metrics.resizeCount++;\n\t\t\t\t\t}\n\t\t\t\t\tlistener(e);\n\t\t\t\t};\n\t\t\t\t// @ts-ignore\n\t\t\t\treturn originalAddEventListener.call(window, type, wrappedListener, options);\n\t\t\t}\n\t\t\treturn originalAddEventListener.call(window, type, listener, options);\n\t\t}.bind(this);\n\n\t\twindow.addEventListener(\"mousedown\", (e: MouseEvent) => {\n\t\t\tconst target = e.target as HTMLElement;\n\t\t\tif (target.closest(\".region\")) {\n\t\t\t\tisDragging = true;\n\t\t\t}\n\t\t\tif (target.dataset.resizer) {\n\t\t\t\tisResizing = true;\n\t\t\t}\n\t\t});\n\n\t\twindow.addEventListener(\"mouseup\", () => {\n\t\t\tisDragging = false;\n\t\t\tisResizing = false;\n\t\t});\n\t}\n\n\tstop() {\n\t\tconsole.log(\"Window Performance Monitor stopped\");\n\t\tif (this.observer) {\n\t\t\tthis.observer.disconnect();\n\t\t}\n\t\tif (this.rafId) {\n\t\t\tcancelAnimationFrame(this.rafId);\n\t\t}\n\t}\n\n\tgetReport() {\n\t\tconst duration = performance.now() - this.startTime;\n\t\tconst avgFPS = this.metrics.frameRates.reduce((a, b) => a + b, 0) / this.metrics.frameRates.length;\n\t\tconst minFPS = Math.min(...this.metrics.frameRates);\n\t\tconst maxFPS = Math.max(...this.metrics.frameRates);\n\t\tconst avgPaintTime = this.metrics.paintTimes.reduce((a, b) => a + b, 0) / this.metrics.paintTimes.length;\n\t\tconst avgLayoutTime = this.metrics.layoutTimes.reduce((a, b) => a + b, 0) / this.metrics.layoutTimes.length;\n\n\t\tconst report = {\n\t\t\tduration: `${(duration / 1000).toFixed(2)}s`,\n\t\t\tframeRate: {\n\t\t\t\taverage: `${avgFPS.toFixed(2)} FPS`,\n\t\t\t\tmin: `${minFPS.toFixed(2)} FPS`,\n\t\t\t\tmax: `${maxFPS.toFixed(2)} FPS`,\n\t\t\t\tsamples: this.metrics.frameRates.length,\n\t\t\t},\n\t\t\tperformance: {\n\t\t\t\tavgPaintTime: `${avgPaintTime.toFixed(2)}ms`,\n\t\t\t\tavgLayoutTime: `${avgLayoutTime.toFixed(2)}ms`,\n\t\t\t},\n\t\t\toperations: {\n\t\t\t\tdragOperations: this.metrics.dragCount,\n\t\t\t\tresizeOperations: this.metrics.resizeCount,\n\t\t\t\trenders: this.metrics.renderCount,\n\t\t\t},\n\t\t\thealth: this.getPerformanceHealth(avgFPS, minFPS),\n\t\t};\n\n\t\tconsole.table(report);\n\t\treturn report;\n\t}\n\n\tprivate getPerformanceHealth(avgFPS: number, minFPS: number) {\n\t\tif (avgFPS >= 55 && minFPS >= 45) {\n\t\t\treturn \"Excellent (60 FPS target met)\";\n\t\t} else if (avgFPS >= 45 && minFPS >= 30) {\n\t\t\treturn \"Good (some frame drops)\";\n\t\t} else if (avgFPS >= 30) {\n\t\t\treturn \"Fair (noticeable lag)\";\n\t\t} else {\n\t\t\treturn \"Poor (significant performance issues)\";\n\t\t}\n\t}\n\n\tgetDetailedMetrics() {\n\t\treturn {\n\t\t\tframeRates: this.metrics.frameRates,\n\t\t\tpaintTimes: this.metrics.paintTimes,\n\t\t\tlayoutTimes: this.metrics.layoutTimes,\n\t\t\tdragCount: this.metrics.dragCount,\n\t\t\tresizeCount: this.metrics.resizeCount,\n\t\t\trenderCount: this.metrics.renderCount,\n\t\t};\n\t}\n}\n\nif (typeof window !== \"undefined\") {\n\t// @ts-ignore\n\twindow.WindowPerformanceMonitor = WindowPerformanceMonitor;\n\tconsole.log(\"💡 Window Performance Monitor loaded. Usage:\");\n\tconsole.log(\"  const monitor = new WindowPerformanceMonitor();\");\n\tconsole.log(\"  monitor.start();\");\n\tconsole.log(\"  // Perform window operations\");\n\tconsole.log(\"  monitor.stop();\");\n\tconsole.log(\"  monitor.getReport();\");\n}\n\nexport default WindowPerformanceMonitor;\n"
  },
  {
    "path": "src/sys/apis/utils/file.ts",
    "content": "import path from \"path-browserify\";\n\nexport interface FileStats {\n\tname: (path: string) => Promise<string>;\n\text: (path: string) => Promise<string>;\n\ttype: (path: string) => Promise<string>;\n\tsize: (path: string) => Promise<string>;\n\tcTime: (path: string) => Promise<string>;\n\tmTime: (path: string) => Promise<string>;\n\tisFile: (path: string) => Promise<boolean>;\n\tisDir: (path: string) => Promise<boolean>;\n\tdir: (path: string) => Promise<string>;\n}\n\nexport const extensionToNameMap: Record<string, string> = {\n\thtml: \"HTML\",\n\thtm: \"HTML\",\n\tcss: \"CSS\",\n\tjs: \"JavaScript\",\n\tjsx: \"JavaScript (JSX)\",\n\tts: \"TypeScript\",\n\ttsx: \"TypeScript (TSX)\",\n\tjson: \"JSON\",\n\txml: \"XML\",\n\tyml: \"YAML\",\n\tyaml: \"YAML\",\n\tphp: \"PHP\",\n\tasp: \"ASP\",\n\taspx: \"ASP.NET\",\n\tvue: \"Vue.js\",\n\tsvelte: \"Svelte\",\n\tejs: \"Embedded JavaScript (EJS)\",\n\thandlebars: \"Handlebars\",\n\tmustache: \"Mustache\",\n\n\tjava: \"Java\",\n\tpy: \"Python\",\n\tpyc: \"Compiled Python\",\n\trb: \"Ruby\",\n\tpl: \"Perl\",\n\tswift: \"Swift\",\n\tgo: \"Go\",\n\trs: \"Rust\",\n\tc: \"C\",\n\th: \"C Header\",\n\tcpp: \"C++\",\n\tcxx: \"C++\",\n\tcc: \"C++\",\n\tcs: \"C#\",\n\tkotlin: \"Kotlin\",\n\tkt: \"Kotlin\",\n\tscala: \"Scala\",\n\tdart: \"Dart\",\n\tlua: \"Lua\",\n\tr: \"R\",\n\tsh: \"Shell Script\",\n\tbash: \"Bash Script\",\n\tzsh: \"Zsh Script\",\n\tbat: \"Batch File\",\n\tps1: \"PowerShell Script\",\n\tsql: \"SQL\",\n\tini: \"INI Config\",\n\ttoml: \"TOML Config\",\n\tcfg: \"Config File\",\n\tenv: \"Environment Config\",\n\n\tcsv: \"CSV\",\n\ttsv: \"TSV\",\n\tmd: \"Markdown\",\n\trst: \"reStructuredText\",\n\tlatex: \"LaTeX\",\n\ttex: \"TeX\",\n\tbib: \"BibTeX\",\n\tlog: \"Log File\",\n\n\tpng: \"PNG Image\",\n\tjpg: \"JPEG Image\",\n\tjpeg: \"JPEG Image\",\n\tgif: \"GIF Image\",\n\tbmp: \"Bitmap Image\",\n\tsvg: \"SVG Vector Image\",\n\twebp: \"WebP Image\",\n\tico: \"Icon File\",\n\ttiff: \"TIFF Image\",\n\theic: \"HEIC Image\",\n\n\tmp4: \"MPEG-4 Video\",\n\tmov: \"QuickTime Video\",\n\tavi: \"AVI Video\",\n\twebm: \"WebM Video\",\n\tmkv: \"Matroska Video\",\n\tflv: \"Flash Video\",\n\tm4v: \"MPEG-4 Video\",\n\t\"3gp\": \"3GP Video\",\n\n\tmp3: \"MP3 Audio\",\n\twav: \"WAV Audio\",\n\togg: \"OGG Audio\",\n\tflac: \"FLAC Audio\",\n\taac: \"AAC Audio\",\n\tm4a: \"MPEG-4 Audio\",\n\twma: \"Windows Media Audio\",\n\topus: \"Opus Audio\",\n\n\tzip: \"ZIP Archive\",\n\trar: \"RAR Archive\",\n\t\"7z\": \"7-Zip Archive\",\n\ttar: \"TAR Archive\",\n\tgz: \"Gzip Archive\",\n\tbz2: \"Bzip2 Archive\",\n\txz: \"XZ Archive\",\n\ttgz: \"Tarball Gzip Archive\",\n\t\"tar.gz\": \"Tarball Gzip Archive\",\n\n\tpdf: \"PDF Document\",\n\tdoc: \"Microsoft Word Document\",\n\tdocx: \"Microsoft Word (OpenXML)\",\n\txls: \"Microsoft Excel Spreadsheet\",\n\txlsx: \"Microsoft Excel (OpenXML)\",\n\tppt: \"Microsoft PowerPoint\",\n\tpptx: \"Microsoft PowerPoint (OpenXML)\",\n\todt: \"OpenDocument Text\",\n\tods: \"OpenDocument Spreadsheet\",\n\todp: \"OpenDocument Presentation\",\n\trtf: \"Rich Text Format\",\n\ttxt: \"Plain Text\",\n\n\tttf: \"TrueType Font\",\n\totf: \"OpenType Font\",\n\twoff: \"Web Open Font Format\",\n\twoff2: \"Web Open Font Format 2\",\n\n\texe: \"Windows Executable\",\n\tdll: \"Dynamic Link Library\",\n\tdeb: \"Debian Package\",\n\trpm: \"Red Hat Package Manager\",\n\tdmg: \"macOS Disk Image\",\n\tiso: \"ISO Disk Image\",\n\tapp: \"macOS App Package\",\n\tbin: \"Binary File\",\n\n\tjar: \"Java Archive\",\n\twar: \"Web Application Archive\",\n\tclass: \"Java Class File\",\n\twasm: \"WebAssembly\",\n\tnode: \"Node.js Binary\",\n\n\tlock: \"Lock File\",\n\tbak: \"Backup File\",\n\ttmp: \"Temporary File\",\n\tcrt: \"Certificate File\",\n\tpem: \"PEM Certificate\",\n\tkey: \"Private Key File\",\n\tpub: \"Public Key File\",\n\tsig: \"Signature File\",\n\tdat: \"Data File\",\n};\n\nexport const nameToExtensionMap: Record<string, string> = Object.entries(extensionToNameMap).reduce((acc: Record<string, string>, [ext, name]) => {\n\tconst key = name.toLowerCase();\n\tif (!acc[key]) acc[key] = ext;\n\treturn acc;\n}, {});\n\n/**\n * Get a human-readable file type name from a file extension.\n * @param ext The file extension (e.g., \"js\", \"pdf\").\n * @returns A human-readable name or \"Unknown\" if not found.\n */\nexport function getNameFromExtension(ext: string): string {\n\treturn extensionToNameMap[ext.toLowerCase()] || \"Unknown\";\n}\n\n/**\n * Get a typical file extension for a human-readable file type name.\n * @param name The human-readable name (e.g., \"JavaScript\").\n * @returns A file extension or \"unknown\" if not found.\n */\nexport function getExtensionFromName(name: string): string {\n\treturn nameToExtensionMap[name.toLowerCase()] || \"unknown\";\n}\n\n/**\n * Check if a string is a valid file path (contains a file name and extension).\n * @param str The string to check.\n * @returns True if it's a valid file path, false otherwise.\n */\nexport const isFilePathString = (str: string): boolean => {\n\tconst last = str.split(\"/\").pop();\n\treturn !!last && /\\.[a-zA-Z0-9]+$/.test(last); // checks for an extension\n};\n\nexport const fileStat: FileStats = {\n\tname: async (file: string): Promise<string> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\tconst name = path.basename(stats.name);\n\t\t\treturn name;\n\t\t}\n\t\treturn \"unknown\";\n\t},\n\text: async (file: string): Promise<string> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\tconst ext = path.extname(stats.name).replace(\".\", \"\").toLowerCase();\n\t\t\treturn ext;\n\t\t}\n\t\treturn \"unknown\";\n\t},\n\ttype: async (file: string): Promise<string> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\tconst ext = path.extname(stats.name).replace(\".\", \"\").toLowerCase();\n\t\t\tconst type = getNameFromExtension(ext);\n\t\t\treturn type;\n\t\t}\n\t\treturn \"unknown\";\n\t},\n\tsize: async (file: string): Promise<string> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\tconst size = stats.size;\n\t\t\tconst units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\t\t\tlet i = 0;\n\t\t\tlet sizeInUnits = size;\n\n\t\t\twhile (sizeInUnits >= 1024 && i < units.length - 1) {\n\t\t\t\tsizeInUnits /= 1024;\n\t\t\t\ti++;\n\t\t\t}\n\t\t\treturn `${sizeInUnits.toFixed(2)} ${units[i]}`;\n\t\t}\n\t\treturn \"0 B\";\n\t},\n\tcTime: async (file: string): Promise<string> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\tconst cTime = new Date(stats.ctime).toLocaleString(\"en-US\", {\n\t\t\t\tyear: \"numeric\",\n\t\t\t\tmonth: \"2-digit\",\n\t\t\t\tday: \"2-digit\",\n\t\t\t\thour: \"2-digit\",\n\t\t\t\tminute: \"2-digit\",\n\t\t\t\tsecond: \"2-digit\",\n\t\t\t});\n\t\t\treturn cTime;\n\t\t}\n\t\treturn \"unknown\";\n\t},\n\tmTime: async (file: string): Promise<string> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\tconst mTime = new Date(stats.mtime).toLocaleString(\"en-US\", {\n\t\t\t\tyear: \"numeric\",\n\t\t\t\tmonth: \"2-digit\",\n\t\t\t\tday: \"2-digit\",\n\t\t\t\thour: \"2-digit\",\n\t\t\t\tminute: \"2-digit\",\n\t\t\t\tsecond: \"2-digit\",\n\t\t\t});\n\t\t\treturn mTime;\n\t\t}\n\n\t\treturn \"unknown\";\n\t},\n\tisFile: async (file: string): Promise<boolean> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n\tisDir: async (file: string): Promise<boolean> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"directory\") {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n\tdir: async (file: string): Promise<string> => {\n\t\tconst stats = await window.tb.fs.promises.stat(file);\n\t\tif (stats.type.toLowerCase() === \"file\") {\n\t\t\treturn path.dirname(file);\n\t\t}\n\t\treturn \"unknown\";\n\t},\n};\n"
  },
  {
    "path": "src/sys/apis/utils/startupHandler.ts",
    "content": "import { fileExists } from \"../../types\";\n\nexport async function launchProcs(): Promise<void> {\n\tconst toLaunch = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/startup.json\", \"utf8\"));\n\tconst currUser = sessionStorage.getItem(\"currAcc\") || \"Guest\";\n\tconst userProcs = toLaunch[currUser] || {};\n\tconst systemProcs = toLaunch[\"System\"] || {};\n\tfor (const proc in systemProcs) {\n\t\tsetTimeout(() => {}, 1000);\n\t\tif (systemProcs[proc].enabled) {\n\t\t\ttry {\n\t\t\t\teval(systemProcs[proc].start);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(`Failed to launch system startup process ${proc}:`, e);\n\t\t\t}\n\t\t}\n\t}\n\tfor (const proc in userProcs) {\n\t\tsetTimeout(() => {}, 1000);\n\t\tif (userProcs[proc].enabled) {\n\t\t\ttry {\n\t\t\t\teval(userProcs[proc].start);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(`Failed to launch user startup process ${proc}:`, e);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport async function addStartupProc(proc: string, target: \"System\" | \"User\", cmd?: string): Promise<void> {\n\tif (!(await fileExists(\"/system/var/terbium/startup.json\"))) {\n\t\tconst users = await window.tb.fs.promises.readdir(\"/home/\");\n\t\tconst startupObj = {\n\t\t\tSystem: {},\n\t\t\t...Object.fromEntries(users.map(user => [user, {}])),\n\t\t};\n\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/startup.json\", JSON.stringify(startupObj), \"utf8\");\n\t}\n\tconst startupData = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/startup.json\", \"utf8\"));\n\tconst currUser = sessionStorage.getItem(\"currAcc\") || \"Guest\";\n\tconst relTarget = target === \"System\" ? \"System\" : currUser;\n\tif (!startupData[relTarget]) {\n\t\tstartupData[relTarget] = {};\n\t}\n\tstartupData[relTarget][proc] = {\n\t\tstart: cmd || `tb.system.openApp('${proc.toLowerCase().replace(/\\s+/g, \"\")}')`,\n\t\tinstalledby: currUser,\n\t\tenabled: false,\n\t};\n\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/startup.json\", JSON.stringify(startupData, null, 4), \"utf8\");\n}\n\nexport async function removeStartupProc(proc: string, target: \"System\" | \"User\"): Promise<void> {\n\tif (!(await fileExists(\"/system/var/terbium/startup.json\"))) {\n\t\treturn;\n\t}\n\tconst startupData = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/startup.json\", \"utf8\"));\n\tconst currUser = sessionStorage.getItem(\"currAcc\") || \"Guest\";\n\tconst relTarget = target === \"System\" ? \"System\" : currUser;\n\tif (!startupData[relTarget] || !startupData[relTarget][proc]) {\n\t\treturn;\n\t}\n\tdelete startupData[relTarget][proc];\n\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/startup.json\", JSON.stringify(startupData, null, 4), \"utf8\");\n}\n\nexport async function enableProc(proc: string, target: \"System\" | \"User\"): Promise<void> {\n\tif (!(await fileExists(\"/system/var/terbium/startup.json\"))) {\n\t\treturn;\n\t}\n\tconst startupData = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/startup.json\", \"utf8\"));\n\tconst currUser = sessionStorage.getItem(\"currAcc\") || \"Guest\";\n\tconst relTarget = target === \"System\" ? \"System\" : currUser;\n\tif (!startupData[relTarget] || !startupData[relTarget][proc]) {\n\t\treturn;\n\t}\n\tstartupData[relTarget][proc].enabled = true;\n\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/startup.json\", JSON.stringify(startupData, null, 4), \"utf8\");\n}\n\nexport async function disableProc(proc: string, target: \"System\" | \"User\"): Promise<void> {\n\tif (!(await fileExists(\"/system/var/terbium/startup.json\"))) {\n\t\treturn;\n\t}\n\tconst startupData = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/startup.json\", \"utf8\"));\n\tconst currUser = sessionStorage.getItem(\"currAcc\") || \"Guest\";\n\tconst relTarget = target === \"System\" ? \"System\" : currUser;\n\tif (!startupData[relTarget] || !startupData[relTarget][proc]) {\n\t\treturn;\n\t}\n\tstartupData[relTarget][proc].enabled = false;\n\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/startup.json\", JSON.stringify(startupData, null, 4), \"utf8\");\n}\n"
  },
  {
    "path": "src/sys/apis/utils/tauth.ts",
    "content": "import { TAuthReturnType } from \"../../types\";\nimport { createAuthClient } from \"better-auth/client\";\nimport { libcurl } from \"libcurl.js\";\n\nexport const auth = createAuthClient({\n\tbaseURL: \"https://auth.terbiumon.top\",\n\tfetchOptions: {\n\t\tcustomFetchImpl: async (input: string | URL | Request, init?: RequestInit | undefined) => {\n\t\t\tif (!window.libcurlLock) {\n\t\t\t\twindow.libcurlLock = true;\n\t\t\t\tlibcurl.load_wasm(\"https://cdn.jsdelivr.net/npm/libcurl.js@latest/libcurl.wasm\");\n\t\t\t\t// @ts-expect-error no types\n\t\t\t\tlibcurl.set_websocket(`${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`);\n\t\t\t\tconsole.log(\"libcurl wasm loaded\");\n\t\t\t}\n\t\t\tconst savedCookies = localStorage.getItem(\"libcurl_cookies\") || \"\";\n\t\t\tconst session = new libcurl.HTTPSession({\n\t\t\t\tenable_cookies: true,\n\t\t\t\tcookie_jar: savedCookies,\n\t\t\t});\n\t\t\tsession.import_cookies();\n\t\t\ttry {\n\t\t\t\tconst headers = new Headers(init?.headers);\n\t\t\t\tif (!headers.has(\"origin\") && !headers.has(\"referer\")) {\n\t\t\t\t\theaders.set(\"origin\", window.location.origin);\n\t\t\t\t}\n\t\t\t\tconst response = await session.fetch(input.toString(), {\n\t\t\t\t\t...init,\n\t\t\t\t\theaders,\n\t\t\t\t});\n\t\t\t\treturn response;\n\t\t\t} finally {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tconst currentCookies = session.export_cookies();\n\t\t\t\t\tlocalStorage.setItem(\"libcurl_cookies\", currentCookies);\n\t\t\t\t\tsession.close();\n\t\t\t\t\tconsole.log(\"HTTP Session Destroyed\");\n\t\t\t\t}, 100);\n\t\t\t}\n\t\t},\n\t},\n});\n\nexport async function getinfo(user?: string | null, pass?: string | null, setting?: string): Promise<TAuthReturnType> {\n\tif (user && pass) {\n\t\tconsole.log(\"[TAUTH] Signing in with provided credentials...\");\n\t\tawait auth.signIn.email({\n\t\t\temail: user,\n\t\t\tpassword: pass,\n\t\t\trememberMe: true,\n\t\t});\n\t}\n\n\tconst response = await auth.$fetch(\"https://auth.terbiumon.top/user/info\", {\n\t\tcredentials: \"include\",\n\t\tmethod: \"GET\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t});\n\tconst uinf = !response.error ? response.data : { error: \"Failed to fetch user info\" };\n\n\tconst sr = setting\n\t\t? await auth.$fetch(`https://auth.terbiumon.top/kv/retrieve/${setting}`, {\n\t\t\t\tcredentials: \"include\",\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t})\n\t\t: null;\n\tconst settings = sr === null ? null : !sr.error ? sr.data : { error: \"Failed to fetch settings\" };\n\n\treturn {\n\t\tuser: uinf,\n\t\tsettings: settings\n\t\t\t? (() => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// @ts-expect-error no\n\t\t\t\t\t\treturn JSON.parse(settings.value);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\twindow.tb.notification.Toast({\n\t\t\t\t\t\t\tmessage: \"Your session is out of date. Click OK to sign in to Terbium Cloud again\",\n\t\t\t\t\t\t\tapplication: \"System\",\n\t\t\t\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\t\t\t\tonOk: async () => {\n\t\t\t\t\t\t\t\tawait window.tb.tauth.reauth();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttime: 6000,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t})()\n\t\t\t: null,\n\t};\n}\n\nexport async function setinfo(user?: string | null, pass?: string | null, setting?: string, toset?: any) {\n\tif (user && pass) {\n\t\tconsole.log(\"[TAUTH] Signing in with provided credentials...\");\n\t\tawait auth.signIn.email({\n\t\t\temail: user,\n\t\t\tpassword: pass,\n\t\t\trememberMe: true,\n\t\t});\n\t}\n\tif (!setting || !toset) {\n\t\treturn { error: \"No setting or value to set provided\" };\n\t}\n\n\tconst response = await auth.$fetch(`https://auth.terbiumon.top/kv/set/${setting}`, {\n\t\tcredentials: \"include\",\n\t\tmethod: \"POST\",\n\t\tbody: JSON.stringify({\n\t\t\tvalue: JSON.stringify(toset),\n\t\t}),\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t});\n\tconst changer = !response.error ? response.data : { error: \"Failed to set info\" };\n\treturn changer;\n}\n"
  },
  {
    "path": "src/sys/apis/utils/winPreview.ts",
    "content": "// utility for showing a lightweight preview of an element\n// call getPrev(elem) to display the preview, hidePrev() to remove it.\n// Uses modern-screenshot for static snapshots to avoid RAM spikes from live content like iframes.\n\nimport { domToCanvas } from \"modern-screenshot\";\n\nlet container: HTMLDivElement | null = null;\nlet snapshotCache = new WeakMap<HTMLElement, HTMLCanvasElement>();\nlet currentTarget: HTMLElement | null = null;\nlet pendingTarget: HTMLElement | null = null;\nlet pendingOptions: PreviewOptions | undefined;\nlet updateScheduled = false;\n\ninterface PreviewOptions {\n\tmaxSize?: number;\n\tx?: number;\n\ty?: number;\n\tscale?: number;\n}\n\nfunction ensureContainer() {\n\tif (container) return;\n\tif (!document.getElementById(\"win-preview-styles\")) {\n\t\tconst styleEl = document.createElement(\"style\");\n\t\tstyleEl.id = \"win-preview-styles\";\n\t\tstyleEl.textContent = `\n.win-preview-container {\n    position: fixed;\n    top: 0;\n    left: 0;\n    pointer-events: none;\n    z-index: 1000000;\n    transition: opacity 0.1s;\n    opacity: 0;\n    will-change: transform,opacity;\n}\n\n.win-preview-container * {\n    pointer-events: none;\n}\n        `;\n\t\tdocument.head.appendChild(styleEl);\n\t}\n\n\tcontainer = document.createElement(\"div\");\n\tcontainer.className = \"win-preview-container\";\n\tdocument.body.appendChild(container);\n}\n\nfunction scheduleUpdate(target: HTMLElement, options?: PreviewOptions) {\n\tpendingTarget = target;\n\tpendingOptions = options;\n\tif (!updateScheduled) {\n\t\tupdateScheduled = true;\n\t\trequestAnimationFrame(performUpdate);\n\t}\n}\n\nfunction performUpdate() {\n\tupdateScheduled = false;\n\tif (!pendingTarget) return;\n\tconst target = pendingTarget;\n\tconst opts = pendingOptions || {};\n\tif (target === currentTarget) {\n\t\tpositionContainer(opts);\n\t\treturn;\n\t}\n\tensureContainer();\n\n\t// check cache first\n\tlet canvas = snapshotCache.get(target);\n\tif (!canvas) {\n\t\tcurrentTarget = target;\n\t\t// capture snapshot asynchronously\n\t\tconst filter = (node: Node) => {\n\t\t\tconst tagName = (node as Element).tagName;\n\t\t\tif (tagName === \"IFRAME\" || tagName === \"SCRIPT\" || tagName === \"NOSCRIPT\") return false;\n\t\t\treturn true;\n\t\t};\n\n\t\tconst drawResolvedCanvas = (newCanvas: HTMLCanvasElement) => {\n\t\t\tsnapshotCache.set(target, newCanvas);\n\t\t\tif (currentTarget === target) {\n\t\t\t\tupdateContainerWithCanvas(newCanvas, target, opts);\n\t\t\t}\n\t\t};\n\n\t\tdomToCanvas(target, {\n\t\t\tscale: 1,\n\t\t\tbackgroundColor: null,\n\t\t\tfilter,\n\t\t\tfeatures: {\n\t\t\t\trestoreScrollPosition: true,\n\t\t\t},\n\t\t})\n\t\t\t.then(newCanvas => {\n\t\t\t\tdrawResolvedCanvas(newCanvas);\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\t// silently fail to avoid spamming console\n\t\t\t});\n\t\t// for now, show a placeholder or nothing\n\t\treturn;\n\t}\n\n\tupdateContainerWithCanvas(canvas, target, opts);\n\tcurrentTarget = target;\n}\n\nfunction updateContainerWithCanvas(canvas: HTMLCanvasElement, target: HTMLElement, opts: PreviewOptions) {\n\tif (!container) return;\n\n\t// clear and add canvas\n\tcontainer.innerHTML = \"\";\n\tcontainer.appendChild(canvas);\n\n\tconst rect = target.getBoundingClientRect();\n\tlet scale = opts.scale ?? 1;\n\tif (opts.maxSize) {\n\t\tconst maxSz = opts.maxSize;\n\t\tconst factor = Math.min(maxSz / rect.width, maxSz / rect.height, 1);\n\t\tscale = Math.min(scale, factor);\n\t}\n\tcanvas.style.transformOrigin = \"top left\";\n\tcanvas.style.transform = `scale(${scale})`;\n\tcontainer.style.width = `${rect.width * scale}px`;\n\tcontainer.style.height = `${rect.height * scale}px`;\n\n\tpositionContainer(opts);\n\tcontainer.style.opacity = \"1\";\n}\n\nfunction positionContainer(opts: PreviewOptions) {\n\tif (!container) return;\n\tif (opts.x != null && opts.y != null) {\n\t\tcontainer.style.transform = `translate(${opts.x}px, ${opts.y}px) ${container.style.transform.replace(/translate\\([^)]*\\)/, \"\")}`;\n\t}\n}\n\n/**\n * Show a preview of the given element. The preview is positioned fixed relative to the viewport.\n *\n * @param el the element to preview\n * @param options optional configuration: maxSize, x/y coordinate, scale\n */\nexport function getPrev(el: HTMLElement, options?: PreviewOptions) {\n\tif (!el || !(el instanceof HTMLElement)) return;\n\tscheduleUpdate(el, options);\n}\n\n/**\n * Hide the preview immediately.\n */\nexport function hidePrev() {\n\tif (container) container.style.opacity = \"0\";\n\tcurrentTarget = null;\n\tpendingTarget = null;\n}\n\nexport function previewAtMouse(el: HTMLElement, evt: MouseEvent, options?: PreviewOptions) {\n\tconst coords = { x: evt.clientX + 10, y: evt.clientY + 10 };\n\tgetPrev(el, { ...options, ...coords });\n}\n\nexport default {\n\tgetPrev,\n\thidePrev,\n\tpreviewAtMouse,\n};\n"
  },
  {
    "path": "src/sys/gui/AppIsland.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport \"./styles/shell.css\";\nimport { createId } from \"@paralleldrive/cuid2\";\n\nexport interface AppIslandProps {\n\ttext?: string;\n\tclick?: () => void;\n\tappname?: string;\n\tid?: string;\n}\n\ntype IslandState = {\n\tprops: AppIslandProps | null;\n\tcontrols: React.JSX.Element[];\n};\n\nexport let updateInfo: (props: AppIslandProps) => void;\nexport let updateControls: (props: AppIslandProps) => void;\nexport let clearInfo: () => void;\nexport let clearControls: (appname: string) => void;\nexport default function AppIsland() {\n\tconst [islands, setIslands] = useState<{ [appname: string]: IslandState }>({});\n\tconst [activeApp, setActiveApp] = useState<string | null>(null);\n\tconst onUpdate = (props: AppIslandProps) => {\n\t\tif (!props.appname) return;\n\t\tsetIslands(prev => ({\n\t\t\t...prev,\n\t\t\t[props.appname!]: {\n\t\t\t\t...(prev[props.appname!] || { props: null, controls: [] }),\n\t\t\t\tprops: { ...prev[props.appname!]?.props, ...props },\n\t\t\t},\n\t\t}));\n\t\tsetActiveApp(props.appname);\n\t\twindow.dispatchEvent(new CustomEvent(\"selwin-upd\", { detail: props.appname }));\n\t};\n\tconst updconts = (props: AppIslandProps) => {\n\t\tif (!props.appname) return;\n\t\tconst appname = props.appname;\n\t\tconst controlId = props.id || createId();\n\t\tconst whenClick = props.click ? props.click : () => {};\n\t\tsetIslands(prev => {\n\t\t\tconst existingControls = prev[appname]?.controls || [];\n\t\t\tif (existingControls.some(control => control.key === controlId)) return prev;\n\t\t\tconst control = (\n\t\t\t\t<button key={controlId} className=\"cursor-pointer hover:text-[#ffffffe3] leading-none duration-150\" control-id={controlId} onClick={whenClick}>\n\t\t\t\t\t{props.text}\n\t\t\t\t</button>\n\t\t\t);\n\t\t\treturn {\n\t\t\t\t...prev,\n\t\t\t\t[appname]: {\n\t\t\t\t\tprops: prev[appname]?.props || null,\n\t\t\t\t\tcontrols: [...existingControls, control],\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t\twindow.dispatchEvent(new CustomEvent(\"selwin-upd\", { detail: appname }));\n\t};\n\tconst clear = (appname: string) => {\n\t\tsetIslands(prev => ({\n\t\t\t...prev,\n\t\t\t[appname]: {\n\t\t\t\t...prev[appname],\n\t\t\t\tcontrols: [],\n\t\t\t},\n\t\t}));\n\t};\n\tconst clearinf = () => {\n\t\tsetActiveApp(null);\n\t};\n\tuseEffect(() => {\n\t\tupdateInfo = onUpdate;\n\t\tupdateControls = updconts;\n\t\tclearInfo = clearinf;\n\t\tclearControls = clear;\n\t});\n\n\treturn (\n\t\t<div className=\"island-container\">\n\t\t\t<div className={`island app_island relative p-3 py-2 flex gap-2 items-center rounded-xl h-min ${activeApp ? \"opacity-100\" : \"opacity-0\"}`}>\n\t\t\t\t{Object.entries(islands).map(([appname, island]) => (\n\t\t\t\t\t<div className={`flex gap-3 ${activeApp === appname ? \"opacity-100 z-1\" : \"opacity-0 absolute pointer-events-none\"} duration-150`} key={appname} id={island.props?.id} data-app-name={appname}>\n\t\t\t\t\t\t<div className=\"font-bold text-white text-2xl cursor-(--cursor-text)\">{appname}</div>\n\t\t\t\t\t\t{island.controls.length > 0 && <div className=\"font-medium text-[#ffffff88] text-sm flex gap-2\">{island.controls}</div>}\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/sys/gui/Battery.tsx",
    "content": "import { useEffect, useRef, useState } from \"react\";\nimport { UserSettings } from \"../types\";\n\ndeclare global {\n\tinterface Navigator {\n\t\tgetBattery(): Promise<BatteryManager>;\n\t\tuserAgent: string;\n\t}\n\tinterface BatteryManager {\n\t\tlevel: number;\n\t\tcharging: boolean;\n\t\tchargingTime: number;\n\t\tonchargingchange: (() => void) | null;\n\t\tonlevelchange: (() => void) | null;\n\t}\n}\n\nexport default function Battery() {\n\tconst [batteryStatus, setBattery] = useState(\"100%\");\n\tconst [charging, setCharging] = useState(false);\n\tconst [shouldRender, setShouldRender] = useState(true);\n\tconst [showPercent, setShowPercent] = useState(false);\n\tconst [batteryNumber, setBatteryNumber] = useState(0);\n\tconst percentRef = useRef<HTMLDivElement>(null);\n\n\tuseEffect(() => {\n\t\tconst controlBatteryPercentVisibility = (value: CustomEvent) => {\n\t\t\tif (typeof value.detail !== \"boolean\") return;\n\t\t\tsetShowPercent(value.detail);\n\t\t};\n\n\t\twindow.addEventListener(\"controlBatteryPercentVisibility\", controlBatteryPercentVisibility as EventListener);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"controlBatteryPercentVisibility\", controlBatteryPercentVisibility as EventListener);\n\t\t};\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst getBattery = async () => {\n\t\t\tif (\"getBattery\" in navigator) {\n\t\t\t\ttry {\n\t\t\t\t\tconst battery = await navigator.getBattery();\n\t\t\t\t\tconst batteryChange = () => {\n\t\t\t\t\t\tif (battery.charging) {\n\t\t\t\t\t\t\tsetBattery(`${Math.floor(battery.level * 100)}%`);\n\t\t\t\t\t\t\tsetBatteryNumber(Math.floor(battery.level * 100));\n\t\t\t\t\t\t\tsetCharging(true);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetBattery(`${Math.floor(battery.level * 100)}%`);\n\t\t\t\t\t\t\tsetBatteryNumber(Math.floor(battery.level * 100));\n\t\t\t\t\t\t\tsetCharging(false);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\tbattery.onchargingchange = batteryChange;\n\t\t\t\t\tbattery.onlevelchange = batteryChange;\n\t\t\t\t\tbatteryChange();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`Error fetching battery: ${error}`);\n\t\t\t\t\tsetBattery(\"none\");\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsetBattery(\"none\");\n\t\t\t\tsetShouldRender(false);\n\t\t\t}\n\t\t};\n\t\tsetInterval(getBattery);\n\t\tgetBattery();\n\t\tconst getShowPercent = async () => {\n\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\t\tsetShowPercent(settings[\"battery-percent\"]);\n\t\t};\n\t\tgetShowPercent();\n\t}, []);\n\n\tfunction renderBattery() {\n\t\treturn (\n\t\t\tshouldRender && (\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n            relative\n            battery tooltip_item flex gap-[6px] items-center\n            ${showPercent === true ? \"w-[67px] h-[28px]\" : \"w-[28px] h-[28px]\"}\n            duration-150\n          `}\n\t\t\t\t>\n\t\t\t\t\t<svg\n\t\t\t\t\t\twidth=\"28\"\n\t\t\t\t\t\theight=\"28\"\n\t\t\t\t\t\tviewBox=\"0 0 168 168\"\n\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\tclassName={`\n              absolute right-0\n            `}\n\t\t\t\t\t>\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\td=\"M29.7333 43C24.4997 43 19.4805 45.1335 15.7798 48.9311C12.079 52.7287 10 57.8794 10 63.25V103.75C10 109.121 12.079 114.271 15.7798 118.069C19.4805 121.867 24.4997 124 29.7333 124H128.4C133.634 124 138.653 121.867 142.354 118.069C146.054 114.271 148.133 109.121 148.133 103.75V103.5C153.764 102.326 158 97.2227 158 91.0938V75.9062C158 69.784 153.764 64.6743 148.133 63.5065V63.25C148.133 57.8794 146.054 52.7287 142.354 48.9311C138.653 45.1335 133.634 43 128.4 43H29.7333ZM128.4 53.125C131.017 53.125 133.526 54.1917 135.377 56.0905C137.227 57.9893 138.267 60.5647 138.267 63.25V103.75C138.267 106.435 137.227 109.011 135.377 110.909C133.526 112.808 131.017 113.875 128.4 113.875H29.7333C27.1165 113.875 24.6069 112.808 22.7565 110.909C20.9062 109.011 19.8667 106.435 19.8667 103.75V63.25C19.8667 60.5647 20.9062 57.9893 22.7565 56.0905C24.6069 54.1917 27.1165 53.125 29.7333 53.125H128.4Z\"\n\t\t\t\t\t\t\tfill=\"white\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<rect x=\"29\" y=\"63\" width={batteryStatus !== \"none\" ? batteryNumber : 0} height=\"41\" rx=\"4\" fill={`${batteryNumber < 15 ? \"#ff4545\" : batteryNumber <= 30 && batteryNumber > 15 ? \"#fee685\" : \"#ffffff\"}`} />\n\t\t\t\t\t\t<g filter=\"url(#filter0_d_355_134)\">\n\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\tclassName={`charge_icon transition duration-150 ${charging === true ? \"opacity-100 block -ml-[4px]\" : \"opacity-0 hidden\"} ${charging === true ? \"w-6 h-[auto]\" : \"w-[0px] h-[auto]\"}`}\n\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\td=\"M91.4298 31.4743C92.1538 31.8817 92.7219 32.5226 93.0436 33.2947C93.3654 34.0668 93.4221 34.9256 93.2049 35.7341L83.3555 72.2479H119.292C120.015 72.2479 120.721 72.4615 121.326 72.8624C121.93 73.2632 122.405 73.8339 122.693 74.5042C122.981 75.1745 123.069 75.9153 122.946 76.6354C122.823 77.3555 122.495 78.0237 122.001 78.5577L70.0845 134.806C69.519 135.42 68.7663 135.824 67.9466 135.955C67.127 136.085 66.2878 135.933 65.5634 135.524C64.8389 135.116 64.2709 134.473 63.9504 133.699C63.6298 132.925 63.5752 132.065 63.7951 131.256L73.6445 94.7471H37.7081C36.9854 94.7471 36.2785 94.5335 35.6742 94.1327C35.07 93.7318 34.5947 93.1612 34.3069 92.4908C34.019 91.8205 33.9311 91.0798 34.054 90.3596C34.1769 89.6395 34.5052 88.9714 34.9985 88.4373L86.9155 32.1893C87.481 31.5775 88.2327 31.1747 89.0508 31.0452C89.8689 30.9156 90.7064 31.0667 91.4298 31.4743Z\"\n\t\t\t\t\t\t\t\tfill=\"#FFE600\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</g>\n\t\t\t\t\t\t<defs>\n\t\t\t\t\t\t\t<filter id=\"filter0_d_355_134\" x=\"23\" y=\"20\" width=\"111\" height=\"127\" filterUnits=\"userSpaceOnUse\" colorInterpolationFilters=\"sRGB\">\n\t\t\t\t\t\t\t\t<feFlood floodOpacity=\"0\" result=\"BackgroundImageFix\" />\n\t\t\t\t\t\t\t\t<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\" />\n\t\t\t\t\t\t\t\t<feOffset />\n\t\t\t\t\t\t\t\t<feGaussianBlur stdDeviation=\"5.5\" />\n\t\t\t\t\t\t\t\t<feComposite in2=\"hardAlpha\" operator=\"out\" />\n\t\t\t\t\t\t\t\t<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0\" />\n\t\t\t\t\t\t\t\t<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_355_134\" />\n\t\t\t\t\t\t\t\t<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect1_dropShadow_355_134\" result=\"shape\" />\n\t\t\t\t\t\t\t</filter>\n\t\t\t\t\t\t\t<filter id=\"filter1_d_355_134\" x=\"40\" y=\"46\" width=\"78\" height=\"72\" filterUnits=\"userSpaceOnUse\" colorInterpolationFilters=\"sRGB\">\n\t\t\t\t\t\t\t\t<feFlood floodOpacity=\"0\" result=\"BackgroundImageFix\" />\n\t\t\t\t\t\t\t\t<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\" />\n\t\t\t\t\t\t\t\t<feOffset />\n\t\t\t\t\t\t\t\t<feGaussianBlur stdDeviation=\"5.5\" />\n\t\t\t\t\t\t\t\t<feComposite in2=\"hardAlpha\" operator=\"out\" />\n\t\t\t\t\t\t\t\t<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0\" />\n\t\t\t\t\t\t\t\t<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_355_134\" />\n\t\t\t\t\t\t\t\t<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect1_dropShadow_355_134\" result=\"shape\" />\n\t\t\t\t\t\t\t</filter>\n\t\t\t\t\t\t</defs>\n\t\t\t\t\t</svg>\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={percentRef}\n\t\t\t\t\t\tclassName={`\n              battery_percent text-sm font-bold\n              absolute left-0\n              ${batteryNumber < 15 ? \"text-red-500\" : batteryNumber <= 30 && batteryNumber > 15 ? \"text-amber-200\" : \"text-[#ffffffc9]\"}\n              ${showPercent === true ? \"\" : \"opacity-0 translate-x-2\"}\n              duration-150\n            `}\n\t\t\t\t\t>\n\t\t\t\t\t\t{batteryStatus != \"none\" ? batteryStatus : \"N/A\"}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)\n\t\t);\n\t}\n\treturn renderBattery();\n}\n"
  },
  {
    "path": "src/sys/gui/ContextMenu.tsx",
    "content": "import { useEffect, useState, useRef } from \"react\";\nimport \"./styles/contextmenu.css\";\nimport { useContextMenuStore } from \"../Store\";\n\nconst ContextMenuArea = () => {\n\tconst contextMenuStore = useContextMenuStore();\n\tconst menuAreaRef = useRef<HTMLDivElement | null>(null);\n\tconst menuRef = useRef<HTMLDivElement | null>(null);\n\tconst [menuOpen, setMenuOpen] = useState(false);\n\tconst [menuPos, setmenuPos] = useState({ x: 0, y: 0 });\n\n\tuseEffect(() => {\n\t\tconst ctx = (e: CustomEvent) => {\n\t\t\tcontextMenuStore.setContextMenu(e.detail.props);\n\t\t\tsetTimeout(() => {\n\t\t\t\tsetMenuOpen(true);\n\t\t\t}, 50);\n\t\t};\n\t\tconst withinRadius = (e: MouseEvent) => {\n\t\t\tif (!menuRef.current) return false;\n\t\t\tconst rect = menuRef.current.getBoundingClientRect();\n\t\t\tconst xBound = e.clientX >= rect.left - 75 && e.clientX <= rect.right + 75;\n\t\t\tconst yBound = e.clientY >= rect.top - 75 && e.clientY <= rect.bottom + 75;\n\t\t\treturn xBound && yBound;\n\t\t};\n\t\tconst onDown = (e: MouseEvent) => {\n\t\t\tif (e.button === 0) {\n\t\t\t\tif (!menuRef.current?.contains(e.target as Node) && !withinRadius(e)) {\n\t\t\t\t\tsetMenuOpen(false);\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tcontextMenuStore.clearContextMenu();\n\t\t\t\t\t}, 150);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tconst close = () => {\n\t\t\tsetMenuOpen(false);\n\t\t\tsetTimeout(() => {\n\t\t\t\tcontextMenuStore.clearContextMenu();\n\t\t\t}, 1000);\n\t\t};\n\t\tif (contextMenuStore.menu.options.length > 0) {\n\t\t\tlet x = contextMenuStore.menu.x;\n\t\t\tlet y = contextMenuStore.menu.y;\n\t\t\tif (x > window.innerWidth - 190) {\n\t\t\t\tx = window.innerWidth - 190;\n\t\t\t}\n\t\t\tif (y > window.innerHeight - 160) {\n\t\t\t\ty = window.innerHeight - 160;\n\t\t\t}\n\t\t\tsetmenuPos({ x, y });\n\t\t}\n\t\twindow.addEventListener(\"ctxm\", ctx as unknown as EventListener);\n\t\twindow.addEventListener(\"close-ctxm\", close);\n\t\tdocument.addEventListener(\"click\", onDown);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"ctxm\", ctx as unknown as EventListener);\n\t\t\twindow.removeEventListener(\"close-ctxm\", close);\n\t\t\tdocument.removeEventListener(\"click\", onDown);\n\t\t};\n\t}, [contextMenuStore]);\n\n\treturn (\n\t\t<div ref={menuAreaRef} onClick={() => {}}>\n\t\t\t{contextMenuStore.menu.options.length > 0 && (\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n                        absolute z-99999999 flex flex-col rounded-lg overflow-hidden bg-[#ffffff10] text-white shadow-tb-border-shadow backdrop-blur-[10px]\n                        ${menuOpen ? \"translate-y-0\" : \"opacity-0 -translate-y-6\"} duration-200\n                    `}\n\t\t\t\t\tref={menuRef}\n\t\t\t\t\tstyle={{ backdropFilter: \"brightness(0.8) blur(10px)\", top: menuPos.y + \"px\", left: menuPos.x + \"px\" }}\n\t\t\t\t>\n\t\t\t\t\t{contextMenuStore.menu.titlebar ? typeof contextMenuStore.menu.titlebar === \"string\" ? <div className=\"flex items-center px-3 py-2.5 bg-[#ffffff3c] w-full text-left select-none\">{contextMenuStore.menu.titlebar}</div> : contextMenuStore.menu.titlebar : null}\n\t\t\t\t\t<div className={`${menuOpen ? \"\" : \"-translate-y-2 opacity-0\"} duration-700`}>\n\t\t\t\t\t\t{contextMenuStore.menu.options.map((option, i) => {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\tclassName=\"flex text-lg font-bold leading-none px-3 py-2.5 hover:bg-[#ffffff3c] w-full text-left select-none duration-150 cursor-pointer\"\n\t\t\t\t\t\t\t\t\tstyle={{ color: option.color }}\n\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\toption.click();\n\t\t\t\t\t\t\t\t\t\tcontextMenuStore.clearContextMenu();\n\t\t\t\t\t\t\t\t\t\tsetMenuOpen(false);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{option.text}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n};\n\nexport default ContextMenuArea;\n"
  },
  {
    "path": "src/sys/gui/Desktop.tsx",
    "content": "import { FC, createElement, useRef, useState, useEffect } from \"react\";\nimport Dock, { TDockItem } from \"./Dock\";\nimport WindowArea from \"./WindowArea\";\nimport Shell from \"./Shell\";\nimport { WispMenu } from \"./Wifi\";\nimport DialogContainer from \"../apis/Dialogs\";\nimport NotificationContainer from \"../apis/Notifications\";\nimport { NotificationMenu } from \"./NotificationCenter\";\nimport { dirExists, UserSettings, WindowConfig } from \"../types\";\nimport WinSwitcher from \"./WinSwitcher\";\nimport ContextMenuArea from \"./ContextMenu\";\nimport { domToCanvas } from \"modern-screenshot\";\n\n// Mostly for testing stuff but these are the prod settings rn. Feel free to change in dev\nconst PREVIEW_DEBUG = false;\nconst THUMB_WIDTH = 250;\nconst THUMB_HEIGHT = 200;\n\ninterface IDesktopProps {\n\tdesktop: number;\n\tonContextMenu?: (e: MouseEvent) => void;\n}\n\nconst Desktop: FC<IDesktopProps> = ({ desktop, onContextMenu }) => {\n\tconst desktopRef = useRef<HTMLDivElement>(null);\n\tconst [showMenu, setShowMenu] = useState(false);\n\tconst [showNotif, setShowNotif] = useState(false);\n\tconst [pinned, setPinned] = useState<Array<TDockItem>>([]);\n\tconst [winPrev, setWinPrev] = useState<{ open: boolean; windows: any; location: string } | null>(null);\n\tconst [previewWin, setPreviewWin] = useState<string | null>(null);\n\tconst [windowOptimizationsEnabled, setWindowOptimizationsEnabled] = useState(false);\n\tconst [thumbnailFallbacks, setThumbnailFallbacks] = useState<Set<string>>(new Set());\n\tconst [thumbnailLoading, setThumbnailLoading] = useState<Set<string>>(new Set());\n\tconst thumbnailCacheRef = useRef<Map<string, HTMLCanvasElement>>(new Map());\n\n\tconst setThumbnailLoadingState = (winId: string, loading: boolean) => {\n\t\tsetThumbnailLoading(prev => {\n\t\t\tconst isLoading = prev.has(winId);\n\t\t\tif (loading === isLoading) return prev;\n\t\t\tconst next = new Set(prev);\n\t\t\tif (loading) {\n\t\t\t\tnext.add(winId);\n\t\t\t} else {\n\t\t\t\tnext.delete(winId);\n\t\t\t}\n\t\t\treturn next;\n\t\t});\n\t};\n\n\tconst markThumbnailFallback = (winId: string) => {\n\t\tsetThumbnailFallbacks(prev => {\n\t\t\tif (prev.has(winId)) return prev;\n\t\t\tconst next = new Set(prev);\n\t\t\tnext.add(winId);\n\t\t\treturn next;\n\t\t});\n\t};\n\n\tconst drawCanvasFitted = (ctx: CanvasRenderingContext2D, source: HTMLCanvasElement, targetWidth: number, targetHeight: number) => {\n\t\tctx.clearRect(0, 0, targetWidth, targetHeight);\n\t\tctx.fillStyle = \"#111\";\n\t\tctx.fillRect(0, 0, targetWidth, targetHeight);\n\t\tif (source.width <= 0 || source.height <= 0) return;\n\t\tconst scale = Math.min(targetWidth / source.width, targetHeight / source.height);\n\t\tconst drawWidth = source.width * scale;\n\t\tconst drawHeight = source.height * scale;\n\t\tconst offsetX = (targetWidth - drawWidth) / 2;\n\t\tconst offsetY = (targetHeight - drawHeight) / 2;\n\t\tctx.drawImage(source, offsetX, offsetY, drawWidth, drawHeight);\n\t};\n\n\tconst renderWindowThumbnail = (winId: string, canvasEl: HTMLCanvasElement, attempt = 0) => {\n\t\tsetThumbnailLoadingState(winId, true);\n\t\tif (windowOptimizationsEnabled) {\n\t\t\tmarkThumbnailFallback(winId);\n\t\t\tsetThumbnailLoadingState(winId, false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst ctx = canvasEl.getContext(\"2d\");\n\t\tif (!ctx) {\n\t\t\tif (PREVIEW_DEBUG) console.warn(\"[win-preview] no 2d context\", { winId, attempt });\n\t\t\tsetThumbnailLoadingState(winId, false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst cachedCanvas = thumbnailCacheRef.current.get(winId);\n\t\tif (cachedCanvas) {\n\t\t\tif (PREVIEW_DEBUG) console.debug(\"[win-preview] draw from cache\", { winId, attempt, width: cachedCanvas.width, height: cachedCanvas.height });\n\t\t\tdrawCanvasFitted(ctx, cachedCanvas, THUMB_WIDTH, THUMB_HEIGHT);\n\t\t\tcanvasEl.dataset.rendered = \"1\";\n\t\t\tsetThumbnailLoadingState(winId, false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst parElem = document.getElementById(winId);\n\t\tconst mainparElem = parElem?.querySelector(\".w-full.h-full\");\n\t\tlet elem = mainparElem;\n\t\tif (mainparElem?.querySelector(\"iframe\")) {\n\t\t\telem = mainparElem.querySelector(\"iframe\") as HTMLElement | null;\n\t\t}\n\t\tif (!elem) {\n\t\t\tif (PREVIEW_DEBUG) console.warn(\"[win-preview] missing element\", { winId });\n\t\t\tmarkThumbnailFallback(winId);\n\t\t\tcanvasEl.dataset.rendered = \"0\";\n\t\t\tsetThumbnailLoadingState(winId, false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst rect = elem.getBoundingClientRect();\n\t\tif (PREVIEW_DEBUG) console.debug(\"[win-preview] element rect\", { winId, attempt, width: rect.width, height: rect.height, x: rect.x, y: rect.y });\n\t\tif (rect.width <= 0 || rect.height <= 0) {\n\t\t\tif (attempt < 2) {\n\t\t\t\tif (PREVIEW_DEBUG) console.warn(\"[win-preview] zero rect, retrying\", { winId, attempt });\n\t\t\t\trequestAnimationFrame(() => renderWindowThumbnail(winId, canvasEl, attempt + 1));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (PREVIEW_DEBUG) console.error(\"[win-preview] zero rect after retries\", { winId });\n\t\t\tmarkThumbnailFallback(winId);\n\t\t\tcanvasEl.dataset.rendered = \"0\";\n\t\t\tsetThumbnailLoadingState(winId, false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst filter = (node: Node) => {\n\t\t\tconst tagName = (node as Element).tagName;\n\t\t\tif (tagName === \"IFRAME\" || tagName === \"SCRIPT\" || tagName === \"NOSCRIPT\") return false;\n\t\t\treturn true;\n\t\t};\n\n\t\tif (PREVIEW_DEBUG) console.debug(\"[win-preview] rendering via modern-screenshot domToCanvas\", { winId, attempt });\n\t\tdomToCanvas(elem, {\n\t\t\tscale: 1,\n\t\t\tbackgroundColor: null,\n\t\t\tfilter,\n\t\t\tfeatures: {\n\t\t\t\trestoreScrollPosition: true,\n\t\t\t},\n\t\t})\n\t\t\t.then(fullCanvas => {\n\t\t\t\tif (PREVIEW_DEBUG) console.debug(\"[win-preview] modern-screenshot success\", { winId, width: fullCanvas.width, height: fullCanvas.height });\n\t\t\t\tthumbnailCacheRef.current.set(winId, fullCanvas);\n\t\t\t\tdrawCanvasFitted(ctx, fullCanvas, THUMB_WIDTH, THUMB_HEIGHT);\n\t\t\t\tcanvasEl.dataset.rendered = \"1\";\n\t\t\t\tsetThumbnailLoadingState(winId, false);\n\t\t\t})\n\t\t\t.catch(err => {\n\t\t\t\tif (PREVIEW_DEBUG) console.error(\"[win-preview] modern-screenshot failed\", { winId, attempt, err });\n\t\t\t\tif (attempt < 2) {\n\t\t\t\t\trequestAnimationFrame(() => renderWindowThumbnail(winId, canvasEl, attempt + 1));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmarkThumbnailFallback(winId);\n\t\t\t\tcanvasEl.dataset.rendered = \"0\";\n\t\t\t\tsetThumbnailLoadingState(winId, false);\n\t\t\t});\n\t};\n\n\tuseEffect(() => {\n\t\tconst menu = () => {\n\t\t\tsetShowMenu(prev => !prev);\n\t\t};\n\t\tconst nMenu = () => {\n\t\t\tsetShowNotif(prev => !prev);\n\t\t};\n\t\tconst getWallpaper = async () => {\n\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`));\n\t\t\tsetWindowOptimizationsEnabled(settings.windowOptimizations ?? false);\n\t\t\tif (settings.wallpaper.startsWith(\"/system\")) {\n\t\t\t\tif (!desktopRef.current) return;\n\t\t\t\tdesktopRef.current.style.backgroundImage = `url(\"/fs/${settings.wallpaper}\")`;\n\t\t\t\tdesktopRef.current.style.backgroundSize = settings.wallpaperMode === \"stretch\" ? \"100% 100%\" : settings.wallpaperMode;\n\t\t\t\tdesktopRef.current.style.backgroundPosition = \"center\";\n\t\t\t\tdesktopRef.current.style.backgroundRepeat = \"no-repeat\";\n\t\t\t} else {\n\t\t\t\tif (!desktopRef.current) return;\n\t\t\t\tdesktopRef.current.style.backgroundImage = `url(\"${settings.wallpaper}\")`;\n\t\t\t\tdesktopRef.current.style.backgroundSize = settings.wallpaperMode === \"stretch\" ? \"100% 100%\" : settings.wallpaperMode;\n\t\t\t\tdesktopRef.current.style.backgroundPosition = \"center\";\n\t\t\t\tdesktopRef.current.style.backgroundRepeat = \"no-repeat\";\n\t\t\t}\n\t\t};\n\t\tconst showWinPrev = (e: CustomEvent) => {\n\t\t\tconst data = JSON.parse(e.detail);\n\t\t\tsetWinPrev(data);\n\t\t\tif (!data.open) {\n\t\t\t\tsetPreviewWin(null);\n\t\t\t\tsetThumbnailLoading(new Set());\n\t\t\t\tdocument.querySelectorAll(\".window-element\").forEach(el => {\n\t\t\t\t\t(el as HTMLElement).style.opacity = \"1\";\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\tconst getPins = async () => {\n\t\t\tif (await dirExists(\"/system\")) {\n\t\t\t\tsetPinned(JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/dock.json\", \"utf8\")));\n\t\t\t}\n\t\t};\n\t\tgetPins();\n\t\twindow.addEventListener(\"tfsready\", getWallpaper);\n\t\twindow.addEventListener(\"open-net\", menu);\n\t\twindow.addEventListener(\"open-notif\", nMenu);\n\t\twindow.addEventListener(\"load\", getWallpaper);\n\t\twindow.addEventListener(\"updWallpaper\", getWallpaper);\n\t\twindow.addEventListener(\"updPins\", getPins);\n\t\t// @ts-expect-error\n\t\twindow.addEventListener(\"windows-prev\", showWinPrev);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"open-net\", menu);\n\t\t\twindow.removeEventListener(\"open-notif\", nMenu);\n\t\t\twindow.removeEventListener(\"load\", getWallpaper);\n\t\t\twindow.removeEventListener(\"updWallpaper\", getWallpaper);\n\t\t\twindow.removeEventListener(\"updPins\", getPins);\n\t\t\t// @ts-expect-error\n\t\t\twindow.removeEventListener(\"windows-prev\", showWinPrev);\n\t\t\twindow.removeEventListener(\"tfsready\", getWallpaper);\n\t\t};\n\t}, [showNotif, winPrev, pinned]);\n\n\treturn (\n\t\t<div\n\t\t\tclassName={`desktop flex flex-col h-[inherit] overflow-hidden `}\n\t\t\tdata-desktop={desktop}\n\t\t\tref={desktopRef}\n\t\t\tonContextMenuCapture={(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\tonContextMenu?.(e.nativeEvent);\n\t\t\t}}\n\t\t>\n\t\t\t<Shell />\n\t\t\t<WispMenu isOpen={showMenu} />\n\t\t\t<WindowArea className=\"h-full m-2 mt-0\" />\n\t\t\t<DialogContainer />\n\t\t\t<NotificationContainer />\n\t\t\t<NotificationMenu isOpen={showNotif} />\n\t\t\t<ContextMenuArea />\n\t\t\t<WinSwitcher />\n\t\t\t<div\n\t\t\t\tdata-win-preview=\"true\"\n\t\t\t\tclassName={`${winPrev?.open ? \"opacity-100\" : \"opacity-0\"} duration-150`}\n\t\t\t\tonMouseLeave={() => {\n\t\t\t\t\tsetPreviewWin(null);\n\t\t\t\t\tdocument.querySelectorAll(\".window-element\").forEach(el => {\n\t\t\t\t\t\t(el as HTMLElement).style.opacity = \"1\";\n\t\t\t\t\t});\n\t\t\t\t\tthumbnailCacheRef.current.clear();\n\t\t\t\t\tsetThumbnailFallbacks(new Set());\n\t\t\t\t\tsetThumbnailLoading(new Set());\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{winPrev?.windows && winPrev.windows.length > 0 && (\n\t\t\t\t\t<div data-win-preview=\"true\" className={`absolute bottom-16 flex justify-center items-start gap-2 rounded-lg bg-[#2020208c] shadow-tb-border-shadow backdrop-blur-[100px] border-none overflow-hidden z-9999999 p-2`} style={{ left: `calc(${winPrev?.location}px - ${120 * winPrev?.windows.length}px)` }}>\n\t\t\t\t\t\t{winPrev.windows[0].map((win: WindowConfig) => {\n\t\t\t\t\t\t\tconst winId = win.wid;\n\t\t\t\t\t\t\tif (!winId) return null;\n\t\t\t\t\t\t\tconst shouldUseLogoFallback = windowOptimizationsEnabled || thumbnailFallbacks.has(winId);\n\t\t\t\t\t\t\tconst isThumbnailLoading = !shouldUseLogoFallback && thumbnailLoading.has(winId);\n\t\t\t\t\t\t\tconst iconSrc = win.icon ?? \"/assets/img/null.svg\";\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={winId}\n\t\t\t\t\t\t\t\t\tdata-win-preview=\"true\"\n\t\t\t\t\t\t\t\t\tclassName=\"relative cursor-pointer w-54 rounded-md bg-[#0f0f0fa8] p-1\"\n\t\t\t\t\t\t\t\t\tonMouseEnter={e => {\n\t\t\t\t\t\t\t\t\t\tconst canvasEl = e.currentTarget.querySelector(\"canvas\") as HTMLCanvasElement | null;\n\t\t\t\t\t\t\t\t\t\tif (!shouldUseLogoFallback && canvasEl && canvasEl.dataset.rendered !== \"1\") {\n\t\t\t\t\t\t\t\t\t\t\tif (PREVIEW_DEBUG) console.debug(\"[win-preview] hover-triggered thumbnail retry\", { winId });\n\t\t\t\t\t\t\t\t\t\t\trenderWindowThumbnail(winId, canvasEl);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif (previewWin !== winId) {\n\t\t\t\t\t\t\t\t\t\t\tsetPreviewWin(winId);\n\t\t\t\t\t\t\t\t\t\t\tdocument.querySelectorAll(\".window-element\").forEach(otherEl => {\n\t\t\t\t\t\t\t\t\t\t\t\tif (otherEl.id !== winId) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t(otherEl as HTMLElement).style.opacity = \"0\";\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t(otherEl as HTMLElement).style.opacity = \"1\";\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (!otherEl.classList.contains(\"shadow-window-shadow\")) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\totherEl.classList.add(\"shadow-window-shadow\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"sel-win\", { detail: winId }));\n\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"currWID\", { detail: winId }));\n\t\t\t\t\t\t\t\t\t\tsetPreviewWin(null);\n\t\t\t\t\t\t\t\t\t\tdocument.querySelectorAll(\".window-element\").forEach(el => {\n\t\t\t\t\t\t\t\t\t\t\t(el as HTMLElement).style.opacity = \"1\";\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tsetWinPrev(prev => ({\n\t\t\t\t\t\t\t\t\t\t\t...prev,\n\t\t\t\t\t\t\t\t\t\t\topen: false,\n\t\t\t\t\t\t\t\t\t\t\twindows: prev?.windows,\n\t\t\t\t\t\t\t\t\t\t\tlocation: prev?.location ?? \"\",\n\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<div className=\"mb-1 flex items-center gap-1 px-1\">\n\t\t\t\t\t\t\t\t\t\t<img src={iconSrc} alt=\"Window icon\" className=\"size-3 rounded-sm\" />\n\t\t\t\t\t\t\t\t\t\t<span className=\"max-w-45 truncate text-[10px] leading-none text-white/90\">{typeof win.title === \"string\" ? win.title : win.title?.text}</span>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"ml-auto size-4 rounded-sm bg-red-500/20 text-[10px] leading-none text-red-200 hover:bg-red-500/35\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={e => {\n\t\t\t\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t\t\t\t\twindow.tb.process.kill(win.pid);\n\t\t\t\t\t\t\t\t\t\t\t\tsetWinPrev(prev => ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t...prev,\n\t\t\t\t\t\t\t\t\t\t\t\t\twindows: prev?.windows.map((w: WindowConfig[]) => w.filter((windowItem: WindowConfig) => windowItem.wid !== winId)),\n\t\t\t\t\t\t\t\t\t\t\t\t\topen: prev?.open ?? false,\n\t\t\t\t\t\t\t\t\t\t\t\t\tlocation: prev?.location ?? \"\",\n\t\t\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div className=\"relative w-50 h-32\">\n\t\t\t\t\t\t\t\t\t\t{shouldUseLogoFallback ? (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"rounded border border-[#ffffff20] w-50 h-32 bg-[#1f1f1f] flex items-center justify-center\">\n\t\t\t\t\t\t\t\t\t\t\t\t<img src={iconSrc} alt=\"App logo\" className=\"size-12 rounded-md\" />\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t<canvas\n\t\t\t\t\t\t\t\t\t\t\t\twidth={THUMB_WIDTH}\n\t\t\t\t\t\t\t\t\t\t\t\theight={THUMB_HEIGHT}\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"rounded border border-[#ffffff20] w-50 h-32\"\n\t\t\t\t\t\t\t\t\t\t\t\tref={canvasRef => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (canvasRef && canvasRef.dataset.rendered !== \"1\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trenderWindowThumbnail(winId, canvasRef);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t{isThumbnailLoading && (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"absolute inset-0 rounded border border-[#ffffff20] bg-[#111111b0] flex items-center justify-center pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 200 200\" className=\"size-8\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<defs>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<radialGradient id={`a12-${winId}`} cx=\".66\" fx=\".66\" cy=\".3125\" fy=\".3125\" gradientTransform=\"scale(1.5)\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\"0\" stopColor=\"#32ae62\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\".3\" stopColor=\"#32ae62\" stopOpacity=\".9\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\".6\" stopColor=\"#32ae62\" stopOpacity=\".6\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\".8\" stopColor=\"#32ae62\" stopOpacity=\".3\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\"1\" stopColor=\"#32ae62\" stopOpacity=\"0\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</defs>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<circle style={{ transformOrigin: \"center\" }} fill=\"none\" stroke={`url(#a12-${winId})`} strokeWidth=\"15\" strokeLinecap=\"round\" strokeDasharray=\"200 1000\" strokeDashoffset=\"0\" cx=\"100\" cy=\"100\" r=\"70\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<animateTransform type=\"rotate\" attributeName=\"transform\" calcMode=\"spline\" dur=\"2s\" values=\"360;0\" keyTimes=\"0;1\" keySplines=\"0 0 1 1\" repeatCount=\"indefinite\"></animateTransform>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</circle>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<circle style={{ transformOrigin: \"center\" }} fill=\"none\" opacity=\".2\" stroke=\"#32ae62\" strokeWidth=\"15\" strokeLinecap=\"round\" cx=\"100\" cy=\"100\" r=\"70\"></circle>\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t<Dock pinned={pinned} />\n\t\t</div>\n\t);\n};\n\nexport const createDesktop = (amount: number) => {\n\tfor (let i = 0; i < amount; i++) {\n\t\tcreateElement(Desktop, { desktop: i });\n\t}\n};\n\nexport default Desktop;\n"
  },
  {
    "path": "src/sys/gui/Dock.tsx",
    "content": "import { FC, ReactNode, useEffect, useRef, useState } from \"react\";\nimport { MagnifyingGlassIcon, ChevronRightIcon, PuzzlePieceIcon } from \"@heroicons/react/24/solid\";\nimport \"./styles/dock.css\";\nimport { dirExists, isURL, WindowConfig } from \"../types\";\nimport { useWindowStore, useSearchMenuStore } from \"../Store\";\nimport SearchMenu from \"./Search\";\n\nexport type TDockItem = {\n\tclassName?: string;\n\ttitle: string;\n\ticon: string | undefined;\n\tsrc: string;\n\tsize?: number[] | any;\n\tchildren?: Array<TDockItem>;\n\tisPinnable?: boolean;\n\tsnapable?: boolean;\n\tpid?: string;\n\twid?: string;\n\tproxy?: boolean;\n\tuser?: string;\n\tonClick?: (e: MouseEvent) => void;\n\tonContextMenu?: (e: MouseEvent) => void;\n};\n\nexport type TStartItem = {\n\ttitle: string;\n\ticon: string | ReactNode | undefined;\n\tpid: string | undefined;\n\tonClick?: (e: MouseEvent) => void;\n\tinPins?: boolean;\n\tclassName?: string;\n\tsrc?: string;\n\tproxy?: boolean;\n\tsize?: { width: number; height: number };\n\tsnapable?: boolean;\n};\n\ninterface IDockProps {\n\tshowPins?: boolean;\n\tpinned: Array<TDockItem> | null;\n}\n\ninterface IUser {\n\tpfp: string | undefined;\n\tusername: string | undefined;\n}\n\nconst Dock: FC<IDockProps> = ({ pinned }) => {\n\tconst windowStore = useWindowStore();\n\tconst searchMenuStore = useSearchMenuStore();\n\n\tconst [isStartOpen, setStartOpen] = useState<boolean>(false);\n\tconst [user, setUser] = useState<IUser>({\n\t\tpfp: undefined,\n\t\tusername: undefined,\n\t});\n\n\tconst startRef = useRef<HTMLDivElement>(null);\n\tconst searchRef = useRef<HTMLInputElement>(null);\n\tconst searchDockRef = useRef<HTMLDivElement>(null);\n\tconst [searchHasText, setSearchHasText] = useState<boolean>((searchRef.current?.value?.length ?? 0) >= 1);\n\tconst [searchActive, setSearchActive] = useState<boolean>(false);\n\tconst placeholderRef = useRef<HTMLSpanElement>(null);\n\tconst openAppsRef = useRef<HTMLDivElement>(null);\n\tconst startButtonRef = useRef<SVGSVGElement>(null);\n\tconst systemAppsRef = useRef<HTMLDivElement>(null);\n\tconst pinnedAppsRef = useRef<HTMLDivElement>(null);\n\tconst searchMatchRef = useRef<HTMLDivElement>(null);\n\tconst pinnedAppsDockRef = useRef<HTMLDivElement>(null);\n\tconst openedAppsDockRef = useRef<HTMLDivElement>(null);\n\tconst userOptsRef = useRef<HTMLDivElement>(null);\n\tconst [searchMatch, setSearchMatch] = useState<boolean>(false);\n\tconst [systemApps, setSysApps] = useState<Array<TDockItem>>([]);\n\tconst [pins, setPins] = useState<Array<TDockItem>>([]);\n\n\tuseEffect(() => {\n\t\tconst fetchData = async () => {\n\t\t\tif (await dirExists(\"/system\")) {\n\t\t\t\tsetSysApps(JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\")).system_apps);\n\t\t\t\tsetPins(JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\")).pinned_apps);\n\t\t\t}\n\t\t};\n\t\tfetchData();\n\t\tif (isStartOpen === true) {\n\t\t\tsetStartOpen(false);\n\t\t}\n\t\twindow.addEventListener(\"updApps\", fetchData);\n\t\treturn () => window.removeEventListener(\"updApps\", fetchData);\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst fetchUser = async () => {\n\t\t\tconst pfp = await window.tb.user.pfp();\n\t\t\tconst username = await window.tb.user.username();\n\t\t\tconst user = {\n\t\t\t\tpfp: pfp,\n\t\t\t\tusername: username,\n\t\t\t};\n\t\t\tsetUser(user);\n\t\t};\n\t\twindow.addEventListener(\"accUpd\", fetchUser);\n\t\twindow.addEventListener(\"tfsready\", fetchUser);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"accUpd\", fetchUser);\n\t\t\twindow.removeEventListener(\"tfsready\", fetchUser);\n\t\t};\n\t}, []);\n\n\tconst filteredSysApps = systemApps.filter((item, index, self) => index === self.findIndex(t => t.title === item.title && t.icon === item.icon && t.src === item.src) && (!item.user || item.user === user.username));\n\n\t// @ts-expect-error\n\tconst filteredPins = pinned.filter((item, index, self) => index === self.findIndex(t => t.src === item.src && t.title === item.title && t.icon === item.icon));\n\n\tvar openStart = (focusSearch?: boolean | null, close?: boolean) => {\n\t\tif (close) {\n\t\t\tsetStartOpen(false);\n\t\t\tsetSearchHasText(false);\n\t\t\tsetSearchMatch(false);\n\t\t\tif (searchRef.current) {\n\t\t\t\tsearchRef.current.value = \"\";\n\t\t\t}\n\t\t\tconst systemApps = systemAppsRef.current;\n\t\t\tconst pinnedApps = pinnedAppsRef.current;\n\t\t\tif (systemApps !== null && pinnedApps !== null) {\n\t\t\t\tconst systemAppsChildren = systemApps.children;\n\t\t\t\tconst pinnedAppsChildren = pinnedApps.children;\n\t\t\t\tfor (let i = 0; i < systemAppsChildren.length; i++) {\n\t\t\t\t\tconst child: Element = systemAppsChildren[i];\n\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t}\n\t\t\t\tfor (let i = 0; i < pinnedAppsChildren.length; i++) {\n\t\t\t\t\tconst child: Element = pinnedAppsChildren[i];\n\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst clickElsewhere = (e: MouseEvent) => {\n\t\t\tif (e.target !== startRef.current && e.target !== startButtonRef.current && !startRef.current?.contains(e.target as Node) && !searchDockRef.current?.contains(e.target as Node)) {\n\t\t\t\tsetStartOpen(false);\n\t\t\t\tsetSearchHasText(false);\n\t\t\t\tsetSearchMatch(false);\n\t\t\t\tif (searchRef.current) {\n\t\t\t\t\tsearchRef.current.value = \"\";\n\t\t\t\t}\n\t\t\t\tconst systemApps = systemAppsRef.current;\n\t\t\t\tconst pinnedApps = pinnedAppsRef.current;\n\t\t\t\tif (systemApps !== null && pinnedApps !== null) {\n\t\t\t\t\tconst systemAppsChildren = systemApps.children;\n\t\t\t\t\tconst pinnedAppsChildren = pinnedApps.children;\n\t\t\t\t\tfor (let i = 0; i < systemAppsChildren.length; i++) {\n\t\t\t\t\t\tconst child: Element = systemAppsChildren[i];\n\t\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t\t}\n\t\t\t\t\tfor (let i = 0; i < pinnedAppsChildren.length; i++) {\n\t\t\t\t\t\tconst child: Element = pinnedAppsChildren[i];\n\t\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twindow.removeEventListener(\"mousedown\", clickElsewhere);\n\t\t\t}\n\t\t};\n\t\twindow.addEventListener(\"mousedown\", clickElsewhere);\n\t\tsetStartOpen(prev => !prev);\n\t\topenSearchMenu(true);\n\t\tif (focusSearch) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tsearchRef.current?.focus();\n\t\t\t}, 150);\n\t\t}\n\t\tsetTimeout(() => {\n\t\t\tif (searchRef.current) {\n\t\t\t\tsearchRef.current.value = \"\";\n\t\t\t}\n\t\t\tsetSearchHasText(false);\n\t\t\tsetSearchMatch(false);\n\t\t\tconst systemApps = systemAppsRef.current;\n\t\t\tconst pinnedApps = pinnedAppsRef.current;\n\t\t\tif (systemApps !== null && pinnedApps !== null) {\n\t\t\t\tconst systemAppsChildren = systemApps.children;\n\t\t\t\tconst pinnedAppsChildren = pinnedApps.children;\n\t\t\t\tfor (let i = 0; i < systemAppsChildren.length; i++) {\n\t\t\t\t\tconst child: Element = systemAppsChildren[i];\n\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t}\n\t\t\t\tfor (let i = 0; i < pinnedAppsChildren.length; i++) {\n\t\t\t\t\tconst child: Element = pinnedAppsChildren[i];\n\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t}\n\t\t\t}\n\t\t}, 200);\n\t};\n\n\tvar openSearchMenu = (close?: boolean) => {\n\t\tconst clearout = () => {\n\t\t\tsearchMenuStore.setOpen(false);\n\t\t};\n\t\tif (close) {\n\t\t\tclearout();\n\t\t\treturn;\n\t\t}\n\t\tconst clickElsewhere = (e: MouseEvent) => {\n\t\t\tif (e.target !== searchDockRef.current && e.target !== startButtonRef.current && !searchDockRef.current?.contains(e.target as Node) && !searchMenuStore.searchMenuRef.current?.contains(e.target as Node)) {\n\t\t\t\tclearout();\n\t\t\t\twindow.removeEventListener(\"mousedown\", clickElsewhere);\n\t\t\t}\n\t\t};\n\t\twindow.addEventListener(\"mousedown\", clickElsewhere);\n\t\tsearchMenuStore.setOpen(!searchMenuStore.open);\n\t\topenStart(null, true);\n\t};\n\n\treturn (\n\t\t<div className=\"flex flex-col relative justify-center items-center pb-1.5 z-9999999\">\n\t\t\t<SearchMenu className={`absolute ${searchMenuStore.open ? \"bottom-15 duration-150\" : \"opacity-0 pointer-events-none bottom-10 duration-200\"}`} />\n\t\t\t<div\n\t\t\t\tref={startRef}\n\t\t\t\tclassName={`\n                absolute flex flex-col rounded-2xl\n                bg-[#2020208c] shadow-tb-border-shadow backdrop-blur-sm overflow-hidden\n                w-max min-w-110 h-max min-h-50 ease-in\n                ${isStartOpen ? \"bottom-15 duration-150\" : \"opacity-0 pointer-events-none bottom-10 duration-200\"}\n                ${searchHasText ? \"scale-110\" : \"\"}\n            `}\n\t\t\t\tstyle={{ backgroundImage: \"url(/assets/img/grain.png)\" }}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName={`flex gap-2 items-center\n                    p-2 pb-0 text-[#ffffffa4]\n                    ${isStartOpen ? \"\" : \"translate-y-2 opacity-0\"} duration-700\n                `}\n\t\t\t\t>\n\t\t\t\t\t<MagnifyingGlassIcon className=\"size-6 text-[#ffffff86] stroke-current stroke-[2px]\" />\n\t\t\t\t\t<div className=\"relative flex items-center w-full\">\n\t\t\t\t\t\t<span ref={placeholderRef} className={`absolute font-[680] text-lg pointer-events-none duration-150 ${searchHasText ? \"opacity-0 -translate-x-8\" : searchActive ? \"opacity-100\" : \"opacity-75\"}`}>\n\t\t\t\t\t\t\tSearch\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={searchRef}\n\t\t\t\t\t\t\tclassName={`bg-transparent focus-within:outline-hidden text-lg font-[680] cursor-[var(--cursor-text)] w-full`}\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tonFocus={() => {\n\t\t\t\t\t\t\t\tsetSearchActive(true);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonBlur={() => {\n\t\t\t\t\t\t\t\tsetSearchActive(false);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonInput={(e: React.FormEvent<HTMLInputElement>) => {\n\t\t\t\t\t\t\t\tif (e.currentTarget.value.length > 0) {\n\t\t\t\t\t\t\t\t\tsetSearchHasText(true);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tsetSearchHasText(false);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst query = e.currentTarget.value.toLowerCase();\n\t\t\t\t\t\t\t\tconst systemApps = systemAppsRef.current;\n\t\t\t\t\t\t\t\tconst pinnedApps = pinnedAppsRef.current;\n\t\t\t\t\t\t\t\tconsole.log(systemApps, pinnedApps);\n\t\t\t\t\t\t\t\tif (systemApps !== null) {\n\t\t\t\t\t\t\t\t\tconst systemAppsChildren = systemApps.children;\n\t\t\t\t\t\t\t\t\tlet systemAppsMatch = 0;\n\t\t\t\t\t\t\t\t\tfor (let i = 0; i < systemAppsChildren.length; i++) {\n\t\t\t\t\t\t\t\t\t\tconst child: Element = systemAppsChildren[i];\n\t\t\t\t\t\t\t\t\t\tif (child.textContent && child.textContent.toLowerCase().includes(query)) {\n\t\t\t\t\t\t\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\t\t\t\tsystemAppsMatch++;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tchild.classList.add(\"-translate-x-2\");\n\t\t\t\t\t\t\t\t\t\t\tchild.classList.add(\"opacity-0\");\n\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.add(\"hidden\");\n\t\t\t\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (pinnedApps !== null) {\n\t\t\t\t\t\t\t\t\t\tconst pinnedAppsChildren = pinnedApps.children;\n\t\t\t\t\t\t\t\t\t\tlet pinnedAppsMatch = 0;\n\t\t\t\t\t\t\t\t\t\tfor (let i = 0; i < pinnedAppsChildren.length; i++) {\n\t\t\t\t\t\t\t\t\t\t\tconst child: Element = pinnedAppsChildren[i];\n\t\t\t\t\t\t\t\t\t\t\tif (child.textContent && child.textContent.toLowerCase().includes(query)) {\n\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.remove(\"hidden\");\n\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.remove(\"opacity-0\");\n\t\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.remove(\"-translate-x-2\");\n\t\t\t\t\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\t\t\t\t\tpinnedAppsMatch++;\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.add(\"-translate-x-2\");\n\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.add(\"opacity-0\");\n\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tchild.classList.add(\"hidden\");\n\t\t\t\t\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (systemAppsMatch === 0 && pinnedAppsMatch === 0) {\n\t\t\t\t\t\t\t\t\t\t\tsetSearchMatch(true);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tsetSearchMatch(false);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (systemAppsMatch === 0) {\n\t\t\t\t\t\t\t\t\t\t\tsetSearchMatch(true);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tsetSearchMatch(false);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName={`relative flex min-h-26 overflow-y-auto max-h-49 w-full gap-2 p-2 pt-0\n                    ${isStartOpen ? \"\" : \"translate-y-4 opacity-0\"} duration-1000\n                    ${searchMatch ? \"justify-center\" : \"justify-between\"}\n                `}\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={systemAppsRef}\n\t\t\t\t\t\tclassName={`\n                        grid ${pins.length > 0 ? \"max-h-47 grid-cols-2\" : \"w-full grid-cols-3\"} gap-1 overflow-y-auto\n                    `}\n\t\t\t\t\t>\n\t\t\t\t\t\t{filteredSysApps.map((item, index) => (\n\t\t\t\t\t\t\t<StartItem\n\t\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\ticon={item.icon}\n\t\t\t\t\t\t\t\tpid={undefined}\n\t\t\t\t\t\t\t\tsrc={item.src}\n\t\t\t\t\t\t\t\tsize={item.size}\n\t\t\t\t\t\t\t\tproxy={item.proxy}\n\t\t\t\t\t\t\t\tsnapable={item.snapable}\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\titem.onClick?.(new MouseEvent(\"click\"));\n\t\t\t\t\t\t\t\t\twindowStore.addWindow({\n\t\t\t\t\t\t\t\t\t\tsrc: item.src,\n\t\t\t\t\t\t\t\t\t\tsize: item.size,\n\t\t\t\t\t\t\t\t\t\ticon: typeof item.icon === \"string\" ? item.icon : undefined,\n\t\t\t\t\t\t\t\t\t\ttitle: item.title,\n\t\t\t\t\t\t\t\t\t\tproxy: item.proxy,\n\t\t\t\t\t\t\t\t\t\tsnapable: item.snapable,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tsetStartOpen(false);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t\t{pins.length > 0 ? (\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2 h-full\">\n\t\t\t\t\t\t\t<span className=\"font-semibold\">Pinned Apps</span>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tref={pinnedAppsRef}\n\t\t\t\t\t\t\t\tclassName={`\n                                    flex flex-col bg-[#ffffff10] max-h-50 overflow-y-auto w-max rounded-2xl last:rounded-b-2xl\n                                `}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{pins.length > 0\n\t\t\t\t\t\t\t\t\t? pins.map((item, index) => (\n\t\t\t\t\t\t\t\t\t\t\t<StartItem\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"first:rounded-t-lg last:rounded-b-lg\"\n\t\t\t\t\t\t\t\t\t\t\t\tinPins\n\t\t\t\t\t\t\t\t\t\t\t\tpid={undefined}\n\t\t\t\t\t\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\t\t\t\ticon={item.icon}\n\t\t\t\t\t\t\t\t\t\t\t\tsrc={item.src}\n\t\t\t\t\t\t\t\t\t\t\t\tsize={item.size}\n\t\t\t\t\t\t\t\t\t\t\t\tproxy={item.proxy}\n\t\t\t\t\t\t\t\t\t\t\t\tsnapable={item.snapable}\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={(e: MouseEvent) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (e.button === 0) item.onClick?.(new MouseEvent(\"click\"));\n\t\t\t\t\t\t\t\t\t\t\t\t\twindowStore.addWindow({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsrc: item.src,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ticon: typeof item.icon === \"string\" ? item.icon : undefined,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize: item.size,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: item.title,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tproxy: item.proxy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsnapable: item.snapable,\n\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetStartOpen(false);\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t: null}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<div ref={searchMatchRef} className={\"absolute top-1/2 left-1/2 -translate-1/2 flex gap-1.5 duration-150\" + \" \" + `${searchMatch ? \"\" : \"opacity-0 pointer-events-none -translate-x-3\"}`}>\n\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-6\">\n\t\t\t\t\t\t\t<path d=\"M12 2a10 10 0 1 0 0 20A10 10 0 0 0 12 2Zm1.5 14.25h-3v-1.5h3v1.5Zm0-3h-3V7.5h3v5.75Z\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<span className=\"text-sm\">No results found</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div ref={userOptsRef} className=\"flex items-center gap-2 p-2 bg-[#00000048] rounded-b-lg last:rounded-b-lg\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"flex items-center gap-2 p-1.5 pr-2 rounded-lg hover:bg-[#ffffff19] hover:scale-95 duration-150 cursor-pointer\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\t\t\t\tx: userOptsRef.current?.getBoundingClientRect().x ?? 0,\n\t\t\t\t\t\t\t\ty: userOptsRef.current ? userOptsRef.current.getBoundingClientRect().y - 75 : 0,\n\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Manage Account\",\n\t\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\t\twindow.tb.window.create({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Settings\",\n\t\t\t\t\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/settings.tapp/index.html\",\n\t\t\t\t\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/settings.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\t\t\tsingle: true,\n\t\t\t\t\t\t\t\t\t\t\t\tmessage: JSON.stringify({ page: \"privacy\" }),\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Sign out\",\n\t\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"false\");\n\t\t\t\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<img className=\"size-8 rounded-full pointer-events-none\" src={user.pfp} alt={user.username} />\n\t\t\t\t\t\t<span className=\"font-bold text-lg pointer-events-none\">{user.username}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"flex items-center gap-2 text-sm text-[#ffffff58]\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName=\"font-medium hover:text-[#ffffff98] cursor-pointer duration-150\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\twindow.tb.window.create({\n\t\t\t\t\t\t\t\t\ttitle: \"Settings\",\n\t\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/settings.tapp/index.html\",\n\t\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/settings.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tsetStartOpen(false);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tSettings\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName=\"font-medium hover:text-[#ffffff98] cursor-pointer duration-150\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\twindow.tb.window.create({\n\t\t\t\t\t\t\t\t\ttitle: \"About\",\n\t\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/about.tapp/index.html\",\n\t\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tsetStartOpen(false);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAbout\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName=\"font-medium hover:text-[#ffffff98] cursor-pointer duration-150\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsessionStorage.setItem(\"ldir\", `/home/${user.username}/Documents`);\n\t\t\t\t\t\t\t\twindow.tb.window.create({\n\t\t\t\t\t\t\t\t\ttitle: \"Files\",\n\t\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/files.tapp/index.html\",\n\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\t\t\theight: 500,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tsetStartOpen(false);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tDocuments\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t<div className=\"flex items-center gap-1.5 shadow-tb-border-shadow backdrop-blur-sm bg-[#2020208c] p-2 rounded-xl\">\n\t\t\t\t\t<svg ref={startButtonRef} viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"cursor-pointer w-7 h-7\" onClick={() => openStart(false)}>\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tclassName=\"pointer-events-none\"\n\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\td=\"M3 6a3 3 0 0 1 3-3h2.25a3 3 0 0 1 3 3v2.25a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6Zm9.75 0a3 3 0 0 1 3-3H18a3 3 0 0 1 3 3v2.25a3 3 0 0 1-3 3h-2.25a3 3 0 0 1-3-3V6ZM3 15.75a3 3 0 0 1 3-3h2.25a3 3 0 0 1 3 3V18a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3v-2.25Zm9.75 0a3 3 0 0 1 3-3H18a3 3 0 0 1 3 3V18a3 3 0 0 1-3 3h-2.25a3 3 0 0 1-3-3v-2.25Z\"\n\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</svg>\n\t\t\t\t\t<div ref={searchDockRef} className=\"flex items-center min-w-34 gap-1 p-2 bg-[#ffffff10] rounded-full cursor-text shadow-tb-border-shadow\" onMouseDown={() => openSearchMenu()}>\n\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-4 text-[#ffffffb9] pointer-events-none stroke-2 stroke-current\">\n\t\t\t\t\t\t\t<path fillRule=\"evenodd\" d=\"M10.5 3.75a6.75 6.75 0 1 0 0 13.5 6.75 6.75 0 0 0 0-13.5ZM2.25 10.5a8.25 8.25 0 1 1 14.59 5.28l4.69 4.69a.75.75 0 1 1-1.06 1.06l-4.69-4.69A8.25 8.25 0 0 1 2.25 10.5Z\" clipRule=\"evenodd\" />\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<span className=\"leading-none text-[#ffffffa6] font-bold pointer-events-none select-none\">Search</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tref={openAppsRef}\n\t\t\t\t\tclassName={`\n                    shadow-tb-border-shadow backdrop-blur-sm bg-[#2020208c]\n                    flex items-center\n                    gap-2 py-1.5 px-2 rounded-xl\n                    duration-150 ease-in\n                        ${pinned != null ? ((pinned.length > 0 && windowStore.windows.length > 0) || pinned.length > 0 || windowStore.windows.length > 0 ? \"translate-x-0 opacity-100\" : \"translate-y-3 opacity-0 pointer-events-none\") : null}\n                `}\n\t\t\t\t\tstyle={{ backgroundImage: \"url(/assets/img/grain.png)\" }}\n\t\t\t\t>\n\t\t\t\t\t{pinned != null && pinned.length > 0 ? (\n\t\t\t\t\t\t<div ref={pinnedAppsDockRef} className=\"flex items-center\">\n\t\t\t\t\t\t\t{filteredPins.map((item, index) => (\n\t\t\t\t\t\t\t\t<PinnedDockItem\n\t\t\t\t\t\t\t\t\tsrc={item.src}\n\t\t\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\ticon={item.icon ?? \"/assets/img/null.svg\"}\n\t\t\t\t\t\t\t\t\tsize={item.size}\n\t\t\t\t\t\t\t\t\tproxy={item.proxy}\n\t\t\t\t\t\t\t\t\tsnapable={item.snapable}\n\t\t\t\t\t\t\t\t\tonContextMenu={(e: MouseEvent) => {\n\t\t\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : null}\n\t\t\t\t\t{(pinned?.length ?? 0) > 0 && windowStore.windows.length > 0 ? <span className=\"flex bg-[#ffffff38] backdrop-blur-[20px] h-[20px] w-1 rounded-full\"></span> : null}\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={openedAppsDockRef}\n\t\t\t\t\t\tclassName={`\n                        flex items-center gap-0.5\n                        ${windowStore.windows.length > 0 ? \"flex\" : \"hidden\"}\n                    `}\n\t\t\t\t\t>\n\t\t\t\t\t\t{windowStore.windows\n\t\t\t\t\t\t\t.filter((item, index, self) => index === self.findIndex(t => (typeof t.title === \"string\" ? t.title : t.title?.text) === (typeof item.title === \"string\" ? item.title : item.title?.text)))\n\t\t\t\t\t\t\t.map((item, index) => (\n\t\t\t\t\t\t\t\t<DockItem src={item.src} key={index} title={typeof item.title === \"string\" ? item.title : item.title.text} icon={item.icon ?? \"/assets/img/null.svg\"} size={item.size} proxy={item.proxy} wid={item.wid} pid={item.pid} />\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n};\n\nconst DockItem: FC<TDockItem> = ({ className, icon, title, src, onClick, onContextMenu, size, snapable, pid, wid, proxy }) => {\n\tconst windowStore = useWindowStore();\n\tconst dockItemRef = useRef<HTMLDivElement>(null);\n\tconst [currWID, setcurrWID] = useState(wid);\n\tconst [winfocused, setwinfocused] = useState(windowStore.windows.find((w: any) => w.wid === currWID)?.focused);\n\tconst mm = (e: MouseEvent) => {\n\t\tconst target = e.target;\n\t\tif (target instanceof Element && target.closest(\"[data-win-preview='true']\")) {\n\t\t\treturn;\n\t\t}\n\t\tconst withinRadius = (e: MouseEvent) => {\n\t\t\tif (!dockItemRef.current) return false;\n\t\t\tconst rect = dockItemRef.current.getBoundingClientRect();\n\t\t\tconst xDistance = Math.abs(e.clientX - (rect.left + rect.width / 2));\n\t\t\tconst yDistance = Math.abs(e.clientY - (rect.top + rect.height / 2));\n\t\t\tconst distance = Math.sqrt(xDistance * xDistance + yDistance * yDistance);\n\t\t\treturn distance > 350;\n\t\t};\n\t\tif (withinRadius(e)) {\n\t\t\twindow.removeEventListener(\"mousemove\", mm);\n\t\t\twindow.dispatchEvent(new CustomEvent(\"windows-prev\", { detail: JSON.stringify({ open: false, location: null }) }));\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tconst setWID = (e: CustomEvent) => {\n\t\t\tsetcurrWID(e.detail);\n\t\t\tsetwinfocused(windowStore.windows.find((w: any) => w.wid === e.detail)?.focused);\n\t\t};\n\t\tconst updsel = (e: CustomEvent) => {\n\t\t\tif (e.detail !== title) {\n\t\t\t\tsetwinfocused(false);\n\t\t\t} else {\n\t\t\t\tsetwinfocused(true);\n\t\t\t}\n\t\t};\n\t\twindow.addEventListener(\"selwin-upd\", updsel as EventListener);\n\t\twindow.addEventListener(\"currWID\", setWID as EventListener);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"currWID\", setWID as EventListener);\n\t\t\twindow.removeEventListener(\"mousemove\", mm);\n\t\t};\n\t}, [currWID, winfocused]);\n\treturn (\n\t\t<dock-item\n\t\t\tref={dockItemRef}\n\t\t\t// @ts-expect-error\n\t\t\twid={wid}\n\t\t\tclass={\n\t\t\t\tclassName\n\t\t\t\t\t? className + \" cursor-pointer p-1 hover:bg-[#ffffff28] rounded-md duration-100 ease-in select-none\"\n\t\t\t\t\t: \"cursor-pointer p-1 hover:bg-[#ffffff28] rounded-md duration-100 ease-in select-none\" +\n\t\t\t\t\t\t`\n            ${winfocused ? \"bg-[#ffffff28] shadow-tb-border-shadow\" : \"\"}\n            `\n\t\t\t}\n\t\t\tonMouseEnter={() => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tconst rect = dockItemRef.current?.getBoundingClientRect();\n\t\t\t\t\tconst x = rect ? rect.x : 0;\n\t\t\t\t\twindow.addEventListener(\"mousemove\", mm);\n\t\t\t\t\twindow.dispatchEvent(\n\t\t\t\t\t\tnew CustomEvent(\"windows-prev\", {\n\t\t\t\t\t\t\tdetail: JSON.stringify({\n\t\t\t\t\t\t\t\topen: true,\n\t\t\t\t\t\t\t\twindows: [\n\t\t\t\t\t\t\t\t\twindowStore.matchedWindows.find((group: any[]) =>\n\t\t\t\t\t\t\t\t\t\tgroup.some((w: WindowConfig) => {\n\t\t\t\t\t\t\t\t\t\t\tif (typeof w.title === \"string\") {\n\t\t\t\t\t\t\t\t\t\t\t\treturn w.title === title;\n\t\t\t\t\t\t\t\t\t\t\t} else if (w.title && w.title.text) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn w.title.text === title;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tlocation: x,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}, 950);\n\t\t\t}}\n\t\t\tonClick={() => {\n\t\t\t\tonClick?.(new MouseEvent(\"click\"));\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"sel-win\", { detail: currWID }));\n\t\t\t}}\n\t\t\tonContextMenu={(e: React.MouseEvent) => {\n\t\t\t\te.preventDefault();\n\t\t\t\tonContextMenu?.(new MouseEvent(\"contextmenu\"));\n\t\t\t\tconst { clientX, clientY } = e;\n\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\tx: clientX - 10,\n\t\t\t\t\ty: clientY - 150,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"New Window\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindowStore.addWindow({\n\t\t\t\t\t\t\t\t\tsrc: src,\n\t\t\t\t\t\t\t\t\ticon: typeof icon === \"string\" ? icon : undefined,\n\t\t\t\t\t\t\t\t\tsize: size,\n\t\t\t\t\t\t\t\t\ttitle: title,\n\t\t\t\t\t\t\t\t\tproxy: proxy,\n\t\t\t\t\t\t\t\t\tsnapable: snapable,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Pin to Dock\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindow.tb.desktop.dock.pin({\n\t\t\t\t\t\t\t\t\t// @ts-expect-error ignore this\n\t\t\t\t\t\t\t\t\ttitle: typeof title === \"string\" ? title : title?.text,\n\t\t\t\t\t\t\t\t\ticon: typeof icon === \"string\" ? icon : undefined,\n\t\t\t\t\t\t\t\t\tisPinnable: true,\n\t\t\t\t\t\t\t\t\tsrc: src,\n\t\t\t\t\t\t\t\t\tproxy: proxy,\n\t\t\t\t\t\t\t\t\tsnapable: snapable,\n\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\twidth: size?.width ?? 600,\n\t\t\t\t\t\t\t\t\t\theight: size?.height ?? 400,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Close\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindow.tb.process.kill(pid);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}}\n\t\t>\n\t\t\t{typeof icon === \"undefined\" || icon === null ? (\n\t\t\t\t<img src={\"/assets/img/null.svg\"} alt={title} className=\"w-7 h-7 flex items-center justify-center pointer-events-none\" />\n\t\t\t) : typeof icon === \"string\" ? (\n\t\t\t\t<img src={icon} alt={title} className=\"w-7 h-7 flex items-center justify-center pointer-events-none\" />\n\t\t\t) : (\n\t\t\t\t<div className=\"w-7 h-7 flex items-center justify-center pointer-events-none\">{icon}</div>\n\t\t\t)}\n\t\t</dock-item>\n\t);\n};\n\nconst PinnedDockItem: FC<TDockItem> = ({ className, icon, title, src, onClick, onContextMenu, size, snapable, proxy }) => {\n\tconst windowStore = useWindowStore();\n\treturn (\n\t\t<dock-item\n\t\t\t// @ts-expect-error\n\t\t\tclass={className ? className + \" cursor-pointer p-1 hover:bg-[#ffffff28] rounded-md duration-100 ease-in select-none\" : \"cursor-pointer p-1 hover:bg-[#ffffff28] rounded-md duration-100 ease-in select-none\"}\n\t\t\tonContextMenu={() => {\n\t\t\t\treturn;\n\t\t\t}}\n\t\t\ttitle={title}\n\t\t\tonClick={() => {\n\t\t\t\tonClick?.(new MouseEvent(\"click\"));\n\t\t\t\twindowStore.addWindow({\n\t\t\t\t\tsrc: src,\n\t\t\t\t\ticon: typeof icon === \"string\" ? icon : undefined,\n\t\t\t\t\tsize: size,\n\t\t\t\t\ttitle: title,\n\t\t\t\t\tproxy: proxy,\n\t\t\t\t\tsnapable: snapable,\n\t\t\t\t});\n\t\t\t}}\n\t\t\tonContextMenuCapture={(e: React.MouseEvent) => {\n\t\t\t\te.preventDefault();\n\t\t\t\tonContextMenu?.(new MouseEvent(\"contextmenu\"));\n\t\t\t\tconst { clientX, clientY } = e;\n\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\tx: clientX - 10,\n\t\t\t\t\ty: clientY - 100,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"New Window\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindowStore.addWindow({\n\t\t\t\t\t\t\t\t\tsrc: src,\n\t\t\t\t\t\t\t\t\ticon: typeof icon === \"string\" ? icon : undefined,\n\t\t\t\t\t\t\t\t\tsize: size,\n\t\t\t\t\t\t\t\t\ttitle: title,\n\t\t\t\t\t\t\t\t\tproxy: proxy,\n\t\t\t\t\t\t\t\t\tsnapable: snapable,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Unpin from Dock\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindow.tb.desktop.dock.unpin(title);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}}\n\t\t>\n\t\t\t{typeof icon === \"undefined\" || icon === null ? (\n\t\t\t\t<img src={\"/assets/img/null.svg\"} alt={title} className=\"w-7 h-7 flex items-center justify-center pointer-events-none\" />\n\t\t\t) : typeof icon === \"string\" ? (\n\t\t\t\t<img src={icon} alt={title} className=\"w-7 h-7 flex items-center justify-center pointer-events-none\" />\n\t\t\t) : (\n\t\t\t\t<div className=\"w-7 h-7 flex items-center justify-center pointer-events-none\">{icon}</div>\n\t\t\t)}\n\t\t</dock-item>\n\t);\n};\n\nexport const StartItem: FC<TStartItem> = ({ icon, title, onClick, inPins, className, src, proxy, size, snapable }) => {\n\t// @ts-expect-error\n\tconst chars = typeof title === \"string\" ? title.split(\"\") : title?.text.split(\"\");\n\tconst [resolvedIcon, setResolvedIcon] = useState<string | boolean | undefined>(false);\n\tlet sysapps = [{ title: { text: \"Terminal\" } }, { title: \"Files\" }, { title: \"Settings\" }, { title: { text: \"App Store\" } }, { title: \"Browser\" }, { title: \"Calculator\" }, { title: \"Feedback\" }, { title: \"About\" }, { title: \"Text Editor\" }, { title: \"Task Manager\" }, { title: \"Anura File Manager\" }];\n\t// @ts-expect-error\n\tconst isSystemApp = sysapps.map(app => (typeof app.title === \"string\" ? app.title : app.title.text)).includes(typeof title === \"string\" ? title : title?.text);\n\n\tuseEffect(() => {\n\t\tconst checkIcon = async (icon: string | ReactNode | null) => {\n\t\t\tif (typeof icon === \"undefined\" || icon === null) {\n\t\t\t\tsetResolvedIcon(false);\n\t\t\t} else if (typeof icon === \"string\") {\n\t\t\t\tsetResolvedIcon(true);\n\t\t\t\tif (icon.startsWith(\"/\")) {\n\t\t\t\t\tlet origin: string;\n\t\t\t\t\tlet path: string;\n\t\t\t\t\tlet url: string;\n\t\t\t\t\tif (icon.match(isURL)) {\n\t\t\t\t\t\turl = icon;\n\t\t\t\t\t} else {\n\t\t\t\t\t\torigin = window.location.origin;\n\t\t\t\t\t\tpath = icon.startsWith(\"/\") ? icon : `/${icon}`;\n\t\t\t\t\t\turl = `${origin}${path}`;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst response = await fetch(url);\n\t\t\t\t\t\tconst data = await response.text();\n\t\t\t\t\t\tif (!data.startsWith(\"<!\")) {\n\t\t\t\t\t\t\tsetResolvedIcon(true);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetResolvedIcon(false);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tsetResolvedIcon(false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsetResolvedIcon(false);\n\t\t\t}\n\t\t};\n\t\tcheckIcon(icon);\n\t}, [icon]);\n\n\treturn inPins ? (\n\t\t<div\n\t\t\tclassName={`${className ? className : \"\"} group p-2 pr-2.5 gap-4 flex justify-between items-center hover:bg-[#ffffff28] hover:shadow-tb-border duration-150 cursor-pointer w-full`}\n\t\t\tonClick={() => onClick?.(new MouseEvent(\"click\"))}\n\t\t\tonContextMenu={(e: React.MouseEvent) => {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst { clientX, clientY } = e;\n\t\t\t\tconsole.log(\n\t\t\t\t\tsrc,\n\t\t\t\t\tsrc\n\t\t\t\t\t\t?.replace(\"/fs\", \"\")\n\t\t\t\t\t\t.replace(/\\/[^/]+\\.html$/, \"/\")\n\t\t\t\t\t\t.replace(/\\/\\.\\//, \"/\"),\n\t\t\t\t);\n\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\tx: clientX - 10,\n\t\t\t\t\ty: clientY - 150,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Open\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tonClick?.(new MouseEvent(\"click\"));\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Pin to Dock\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\twindow.tb.desktop.dock.pin({\n\t\t\t\t\t\t\t\t\t// @ts-expect-error ignore this\n\t\t\t\t\t\t\t\t\ttitle: typeof title === \"string\" ? title : title?.text,\n\t\t\t\t\t\t\t\t\ticon: typeof icon === \"string\" ? icon : undefined,\n\t\t\t\t\t\t\t\t\tisPinnable: true,\n\t\t\t\t\t\t\t\t\tsrc: src,\n\t\t\t\t\t\t\t\t\tproxy: proxy,\n\t\t\t\t\t\t\t\t\tsnapable: snapable,\n\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\twidth: size?.width ?? 600,\n\t\t\t\t\t\t\t\t\t\theight: size?.height ?? 400,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Unpin from Start\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\tconst apps: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\t\t\t\t\t\tapps.pinned_apps = apps.pinned_apps.filter((app: any) => !(app.title === title && app.icon === icon) && !(app.name === title && app.icon === icon));\n\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(apps, null, 2));\n\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t...(isSystemApp\n\t\t\t\t\t\t\t? []\n\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Uninstall\",\n\t\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\t\tlet appPath = \"\";\n\t\t\t\t\t\t\t\t\t\t\tconst appName = typeof title === \"string\" ? title : title && typeof title === \"object\" && \"text\" in title ? (title as { text: string }).text : \"\";\n\t\t\t\t\t\t\t\t\t\t\tif (src?.startsWith(\"/fs/apps/user/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (src.includes(\".tapp\")) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/user/${await window.tb.user.username()}/${appName.toLowerCase()}.tapp`;\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/user/${await window.tb.user.username()}/${appName.toLowerCase()}`;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t} else if (src?.startsWith(\"/fs/apps/system/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/system/${appName.toLowerCase()}.tapp`;\n\t\t\t\t\t\t\t\t\t\t\t} else if (src?.includes(\"/apps/anura/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/anura/${appName.toLowerCase()}`;\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/user/${await window.tb.user.username()}/${appName}`;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tlet installedApps = JSON.parse(await window.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\tinstalledApps = installedApps.filter((app: any) => app.title === title);\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(installedApps));\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.sh.promises.rm(appPath, { recursive: true });\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.launcher.removeApp(chars);\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t{\n\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\tresolvedIcon === true ? <img src={icon} className=\"w-7 h-7 flex items-center justify-center\" /> : <div className=\"w-7 h-7 flex items-center justify-center\">{<PuzzlePieceIcon className=\"size-7\" />}</div>\n\t\t\t\t}\n\t\t\t\t<span className=\"text-white font-[680]\">{chars.length > 10 ? chars.slice(0, 10).join(\"\") + \"...\" : chars.join(\"\")}</span>\n\t\t\t</div>\n\t\t\t<ChevronRightIcon\n\t\t\t\tonClick={async () => {\n\t\t\t\t\tconst apps: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\t\t\tapps.pinned_apps = apps.pinned_apps.filter((app: any) => !(app.title === title && app.icon === icon));\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(apps, null, 2));\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\t}}\n\t\t\t\tclassName=\"size-7 bg-[#ffffff18] backdrop-blur-[20px] shadow-tb-border-shadow p-1.5 rounded-full text-white stroke-current stroke-[3px] opacity-0 group-hover:opacity-100 duration-150 ease-in\"\n\t\t\t/>\n\t\t</div>\n\t) : (\n\t\t<div\n\t\t\tclassName={`${className ? className : \"\"} group p-2 pr-2.5 gap-2 flex justify-between items-center hover:bg-[#ffffff28] hover:shadow-tb-border duration-150 cursor-pointer rounded-lg w-full`}\n\t\t\tonClick={(e: React.MouseEvent) => {\n\t\t\t\tif (e.button === 0) onClick?.(new MouseEvent(\"click\"));\n\t\t\t}}\n\t\t\tonContextMenu={async (e: React.MouseEvent) => {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst { clientX, clientY } = e;\n\t\t\t\tconst appsStart: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\t\tconst appsDock: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/dock.json\", \"utf8\"));\n\t\t\t\tconst isPinnedStart = appsStart.pinned_apps.some((app: any) => app.title === title && app.icon === icon);\n\t\t\t\tconst isPinnedDock = appsDock.some((app: any) => app.src === src && app.icon === icon);\n\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\tx: clientX - 10,\n\t\t\t\t\ty: clientY - 150,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Open\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tonClick?.(new MouseEvent(\"click\"));\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tisPinnedDock\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\ttext: \"Unpin from Dock\",\n\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\twindow.tb.desktop.dock.unpin(title);\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\ttext: \"Pin to Dock\",\n\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\twindow.tb.desktop.dock.pin({\n\t\t\t\t\t\t\t\t\t\t\t// @ts-expect-error ignore this\n\t\t\t\t\t\t\t\t\t\t\ttitle: typeof title === \"string\" ? title : title?.text,\n\t\t\t\t\t\t\t\t\t\t\ticon: typeof icon === \"string\" ? icon : undefined,\n\t\t\t\t\t\t\t\t\t\t\tisPinnable: true,\n\t\t\t\t\t\t\t\t\t\t\tsrc: src,\n\t\t\t\t\t\t\t\t\t\t\tproxy: proxy,\n\t\t\t\t\t\t\t\t\t\t\tsnapable: snapable,\n\t\t\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\t\t\twidth: size?.width ?? 600,\n\t\t\t\t\t\t\t\t\t\t\t\theight: size?.height ?? 400,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\tisPinnedStart\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\ttext: \"Unpin from Start\",\n\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\tconst apps: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\t\t\t\t\t\t\t\tapps.pinned_apps = apps.pinned_apps.filter((app: any) => !(app.title === title && app.icon === icon));\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(apps, null, 2));\n\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\ttext: \"Pin to Start\",\n\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\tconst path = src\n\t\t\t\t\t\t\t\t\t\t\t?.replace(\"/fs\", \"\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\/[^/]+\\.html$/, \"/\")\n\t\t\t\t\t\t\t\t\t\t\t.replace(/\\/\\.\\//, \"/\");\n\t\t\t\t\t\t\t\t\t\tconst appConfig = JSON.parse(await window.tb.fs.promises.readFile(path + \"index.json\", \"utf8\"));\n\t\t\t\t\t\t\t\t\t\tif (appsStart.pinned_apps.some((app: any) => app.title === appConfig.config.title && app.icon === appConfig.config.icon)) {\n\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tappsStart.pinned_apps.push({\n\t\t\t\t\t\t\t\t\t\t\tname: typeof appConfig.config.title === \"string\" ? appConfig.config.title : appConfig.config.title.text,\n\t\t\t\t\t\t\t\t\t\t\t...appConfig.config,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(appsStart, null, 2));\n\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t...(isSystemApp\n\t\t\t\t\t\t\t? []\n\t\t\t\t\t\t\t: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Uninstall\",\n\t\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\t\tlet appPath = \"\";\n\t\t\t\t\t\t\t\t\t\t\tconst appName = typeof title === \"string\" ? title : title && typeof title === \"object\" && \"text\" in title ? (title as { text: string }).text : \"\";\n\t\t\t\t\t\t\t\t\t\t\tif (src?.startsWith(\"/fs/apps/user/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (src.includes(\".tapp\")) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/user/${await window.tb.user.username()}/${appName.toLowerCase()}.tapp`;\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/user/${await window.tb.user.username()}/${appName.toLowerCase()}`;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t} else if (src?.startsWith(\"/fs/apps/system/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/system/${appName.toLowerCase()}.tapp`;\n\t\t\t\t\t\t\t\t\t\t\t} else if (src?.includes(\"/apps/anura/\")) {\n\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/anura/${appName.toLowerCase()}`;\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tappPath = `/apps/user/${await window.tb.user.username()}/${appName}`;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tlet installedApps = JSON.parse(await window.tb.fs.promises.readFile(`/apps/installed.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\tinstalledApps = installedApps.filter((app: any) => app.title === title);\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/apps/installed.json`, JSON.stringify(installedApps));\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.sh.promises.rm(appPath, { recursive: true });\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.launcher.removeApp(chars);\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}}\n\t\t>\n\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t{\n\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\tresolvedIcon === true ? <img src={icon} className=\"w-7 h-7 flex items-center justify-center\" /> : <div className=\"w-7 h-7 flex items-center justify-center\">{<PuzzlePieceIcon className=\"size-7\" />}</div>\n\t\t\t\t}\n\t\t\t\t<span className=\"text-white font-[680]\">{chars.length > 10 ? chars.slice(0, 10).join(\"\") + \"...\" : chars.join(\"\")}</span>\n\t\t\t</div>\n\t\t\t<ChevronRightIcon\n\t\t\t\tonClick={async () => {\n\t\t\t\t\tconst apps: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t\t\t\tapps.pinned_apps.push({\n\t\t\t\t\t\ttitle: title,\n\t\t\t\t\t\ticon: icon,\n\t\t\t\t\t\tsrc: src,\n\t\t\t\t\t});\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(apps, null, 2));\n\t\t\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\t\t}}\n\t\t\t\tclassName=\"size-7 bg-[#ffffff18] backdrop-blur-[20px] shadow-tb-border-shadow p-1.5 rounded-full text-white stroke-current stroke-[3px] opacity-0 group-hover:opacity-100 duration-150 ease-in\"\n\t\t\t/>\n\t\t</div>\n\t);\n};\n\nexport default Dock;\n"
  },
  {
    "path": "src/sys/gui/FPSCounter.tsx",
    "content": "import { useEffect, useState, useRef } from \"react\";\n\nexport const FPSCounter = () => {\n\tconst [fps, setFps] = useState(0);\n\tconst [showFPS, setShowFPS] = useState(false);\n\tconst frameCountRef = useRef(0);\n\tconst lastTimeRef = useRef(performance.now());\n\tconst rafRef = useRef<number>(null);\n\n\tuseEffect(() => {\n\t\tconst loadSettings = async () => {\n\t\t\ttry {\n\t\t\t\tconst settings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\t\t\tsetShowFPS(settings.showFPS ?? false);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"Failed to load FPS counter setting:\", err);\n\t\t\t}\n\t\t};\n\t\tloadSettings();\n\n\t\tconst handleSettingsChange = (e: CustomEvent) => {\n\t\t\tif (e.detail?.showFPS !== undefined) {\n\t\t\t\tsetShowFPS(e.detail.showFPS);\n\t\t\t}\n\t\t};\n\t\twindow.addEventListener(\"settings-changed\", handleSettingsChange as EventListener);\n\t\treturn () => window.removeEventListener(\"settings-changed\", handleSettingsChange as EventListener);\n\t}, []);\n\n\tuseEffect(() => {\n\t\tif (!showFPS) {\n\t\t\tif (rafRef.current) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst updateFPS = () => {\n\t\t\tframeCountRef.current++;\n\t\t\tconst now = performance.now();\n\t\t\tconst delta = now - lastTimeRef.current;\n\n\t\t\tif (delta >= 1000) {\n\t\t\t\tconst currentFPS = Math.round((frameCountRef.current * 1000) / delta);\n\t\t\t\tsetFps(currentFPS);\n\t\t\t\tframeCountRef.current = 0;\n\t\t\t\tlastTimeRef.current = now;\n\t\t\t}\n\n\t\t\trafRef.current = requestAnimationFrame(updateFPS);\n\t\t};\n\n\t\trafRef.current = requestAnimationFrame(updateFPS);\n\n\t\treturn () => {\n\t\t\tif (rafRef.current) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t}\n\t\t};\n\t}, [showFPS]);\n\n\tif (!showFPS) return null;\n\n\treturn (\n\t\t<div className=\"flex gap-2\">\n\t\t\t<div className=\"font-bold cursor-default\">{fps} FPS</div>\n\t\t\t<span className=\"w-1 h-4.5 bg-[#ffffff28] rounded-full\" />\n\t\t</div>\n\t);\n};\n"
  },
  {
    "path": "src/sys/gui/NotificationCenter.tsx",
    "content": "import { useRef, useState, useEffect } from \"react\";\nimport \"./styles/notification.css\";\n\ninterface Notification {\n\tmessage: string;\n\tapplication: string;\n\ticon: string;\n\tonOk?: { code: string };\n\ttime?: string;\n}\n\nexport let totalNotifs: number;\n\nexport default function NotificationCenter() {\n\tconst [notificationCount, setNotificationCount] = useState(() => {\n\t\tconst savedCount = JSON.parse(sessionStorage.getItem(\"notifications\") || \"[]\").length;\n\t\treturn savedCount ? parseInt(savedCount) : 0;\n\t});\n\tconst iconRef = useRef<HTMLImageElement>(null);\n\tconst updateIcon = () => {\n\t\tif (iconRef.current) {\n\t\t\ticonRef.current.src = `/assets/img/notif_${notificationCount <= 9 ? notificationCount : \"plus\"}.svg`;\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tconst updateCount = (event: CustomEvent) => {\n\t\t\tsetNotificationCount(event.detail.count);\n\t\t};\n\t\twindow.addEventListener(\"notification-count\", updateCount as EventListener);\n\t\tupdateIcon();\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"notification-count\", updateCount as EventListener);\n\t\t};\n\t}, [notificationCount]);\n\tuseEffect(() => {\n\t\ttotalNotifs = notificationCount;\n\t});\n\treturn (\n\t\t<img\n\t\t\talt=\"notifimg\"\n\t\t\tref={iconRef}\n\t\t\tsrc={`/assets/img/notif_${notificationCount > 9 ? \"plus\" : Math.max(0, Math.min(notificationCount, 9))}.svg`}\n\t\t\tclassName=\"tooltip_item w-6 h-6 cursor-pointer duration-150 select-none\"\n\t\t\tonMouseUp={() => {\n\t\t\t\ticonRef.current?.classList.remove(\"scale-90\");\n\t\t\t\twindow.dispatchEvent(new Event(\"open-notif\"));\n\t\t\t}}\n\t\t\tonMouseLeave={() => {\n\t\t\t\ticonRef.current?.classList.remove(\"scale-90\");\n\t\t\t}}\n\t\t\tonMouseOver={() => {\n\t\t\t\ticonRef.current?.classList.add(\"scale-90\");\n\t\t\t}}\n\t\t\tonMouseDown={() => {\n\t\t\t\ticonRef.current?.classList.add(\"scale-90\");\n\t\t\t}}\n\t\t></img>\n\t);\n}\n\ninterface INotificationProps {\n\tisOpen: boolean;\n}\n\nconst NotificationMenu = ({ isOpen }: INotificationProps) => {\n\tconst [notifications, setNotifications] = useState<Notification[]>(() => {\n\t\tconst savedNotifs = sessionStorage.getItem(\"notifications\");\n\t\treturn savedNotifs ? JSON.parse(savedNotifs) : [];\n\t});\n\n\tconst updateNotifications = () => {\n\t\tconst notifs = JSON.parse(sessionStorage.getItem(\"notifications\") || \"[]\");\n\t\tsetNotifications(notifs);\n\t};\n\n\twindow.addEventListener(\"notification-update\", updateNotifications as EventListener);\n\n\tuseEffect(() => {\n\t\tconst leave = (e: MouseEvent) => {\n\t\t\tconst withinRadius = (e: MouseEvent) => {\n\t\t\t\tif (!notificationCenterRef.current) return false;\n\t\t\t\tconst rect = notificationCenterRef.current.getBoundingClientRect();\n\t\t\t\tconst xBound = e.clientX >= rect.left - 5 && e.clientX <= rect.right + 5;\n\t\t\t\tconst yBound = e.clientY >= rect.top - 5 && e.clientY <= rect.bottom + 5;\n\t\t\t\treturn xBound && yBound;\n\t\t\t};\n\t\t\tif (e.button === 0) {\n\t\t\t\tif (!notificationCenterRef.current?.contains(e.target as Node) && !withinRadius(e)) {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\twindow.dispatchEvent(new Event(\"open-notif\"));\n\t\t\t\t\t}, 150);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tif (!isOpen) {\n\t\t\tdocument.removeEventListener(\"mousedown\", leave);\n\t\t} else {\n\t\t\tdocument.addEventListener(\"mousedown\", leave);\n\t\t}\n\t\treturn () => document.removeEventListener(\"mousedown\", leave);\n\t}, [isOpen]);\n\n\tconst notificationCenterRef = useRef<HTMLDivElement>(null);\n\tconst dismiss = (index: number) => {\n\t\tconst notifData = notifications.filter((_, i) => i !== index);\n\t\tsetNotifications(notifData);\n\t\tif (totalNotifs > 0)\n\t\t\twindow.dispatchEvent(\n\t\t\t\tnew CustomEvent(\"notification-count\", {\n\t\t\t\t\tdetail: { count: (totalNotifs -= 1) },\n\t\t\t\t}),\n\t\t\t);\n\t\tsessionStorage.setItem(\"notifications\", JSON.stringify(notifData));\n\t};\n\n\treturn (\n\t\t<div\n\t\t\tref={notificationCenterRef}\n\t\t\tclassName={`\n            absolute top-15 right-1.5\n            flex flex-col\n            w-100 h-max max-h-[calc(100%-calc(60px+1.5rem))] rounded-xl p-2.5 gap-2.5\n            bg-[#2020208c] shadow-tb-border-shadow backdrop-blur-[100px] text-white z-999 overflow-y-auto\n            ${isOpen ? \"duration-200\" : \"opacity-0 pointer-events-none -translate-y-6 duration-300\"}\n        `}\n\t\t>\n\t\t\t<h1 className=\"text-2xl font-bold text-[#ffffffe6]\">Notifications</h1>\n\t\t\t{notifications.length > 0 ? (\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n                    flex flex-col gap-2.5\n                    duration-700\n                    ${isOpen ? \"\" : \"opacity-0 -translate-y-2\"}\n                `}\n\t\t\t\t>\n\t\t\t\t\t{notifications.map((notification, index) => {\n\t\t\t\t\t\tvar time = \"Now\";\n\t\t\t\t\t\tconst currentTime = new Date().getTime();\n\t\t\t\t\t\tconst timeDiff = notification.time ? currentTime - new Date(notification.time).getTime() : 0;\n\t\t\t\t\t\tif (timeDiff < 60000) {\n\t\t\t\t\t\t\ttime = \"Just now\";\n\t\t\t\t\t\t} else if (timeDiff < 3600000) {\n\t\t\t\t\t\t\ttime = `${Math.floor(timeDiff / 60000)}min ago`;\n\t\t\t\t\t\t} else if (timeDiff < 86400000) {\n\t\t\t\t\t\t\ttime = `${Math.floor(timeDiff / 3600000)}h ago`;\n\t\t\t\t\t\t} else if (timeDiff < 604800000) {\n\t\t\t\t\t\t\ttime = `${Math.floor(timeDiff / 86400000)}d ago`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttime = `${Math.floor(timeDiff / 604800000)}w ago`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={index} className=\"flex flex-col bg-[#ffffff18] shadow-tb-border-shadow rounded-lg overflow-hidden\">\n\t\t\t\t\t\t\t\t<div className=\"flex justify-between items-center bg-[#ffffff20] p-2.5\">\n\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2 items-center\">\n\t\t\t\t\t\t\t\t\t\t<img src={notification.icon} alt=\"Icon\" style={{ width: \"25px\", height: \"25px\" }} />\n\t\t\t\t\t\t\t\t\t\t<div className=\"notification-application\">{notification.application}</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t{notification.time ? <div className=\"font-semibold text-[#ffffffa0] text-sm\">{time}</div> : null}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2 p-2.5\">\n\t\t\t\t\t\t\t\t\t<div className=\"text-lg font-semibold\">{notification.message}</div>\n\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2 justify-between\">\n\t\t\t\t\t\t\t\t\t\t<button className=\"leading-none p-2.5 cursor-pointer px-4 bg-[#ffffff20] shadow-tb-border rounded-md hover:bg-[#ffffff30] duration-150\" onClick={() => dismiss(index)}>\n\t\t\t\t\t\t\t\t\t\t\tDismiss\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"leading-none p-2.5 cursor-pointer px-4 bg-[#53f67463] shadow-tb-border rounded-md hover:bg-[#53f67473] duration-150\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={async () => {\n\t\t\t\t\t\t\t\t\t\t\t\tif (notification.onOk) {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst onOk = new Function(`return ${notification.onOk.code}`)();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait onOk();\n\t\t\t\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconsole.error(error);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tdismiss(index);\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tOpen\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n                    justify-center items-center flex h-full font-[700] text-[20px] duration-700\n                    ${isOpen ? \"\" : \"opacity-0 -translate-y-2\"}\n                `}\n\t\t\t\t>\n\t\t\t\t\t<div>No Notifications yet.</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n};\n\nexport { NotificationMenu };\n"
  },
  {
    "path": "src/sys/gui/Power.tsx",
    "content": "import { useState, useRef, useEffect } from \"react\";\nimport { PowerIcon, MoonIcon, LockClosedIcon, ArrowPathIcon } from \"@heroicons/react/24/solid\";\n\nexport default function Power() {\n\tconst [showMenu, setShowMenu] = useState(false);\n\tconst [showHardRestart, setShowHardRestart] = useState(false);\n\tconst menu = useRef<HTMLDivElement>(null);\n\tconst iconRef = useRef<SVGSVGElement>(null);\n\tuseEffect(() => {\n\t\tconst leave = (e: MouseEvent) => {\n\t\t\tif (showMenu) {\n\t\t\t\tif (e.target instanceof HTMLElement) {\n\t\t\t\t\tif (e.target !== menu.current && !menu.current?.contains(e.target)) {\n\t\t\t\t\t\tsetShowMenu(false);\n\t\t\t\t\t\tsetShowHardRestart(false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tdocument.addEventListener(\"mousedown\", leave);\n\t\treturn () => document.removeEventListener(\"mousedown\", leave);\n\t});\n\tuseEffect(() => {\n\t\tconst down = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"Shift\") {\n\t\t\t\tsetShowHardRestart(true);\n\t\t\t}\n\t\t};\n\t\tconst up = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"Shift\") {\n\t\t\t\tsetShowHardRestart(false);\n\t\t\t}\n\t\t};\n\t\tdocument.addEventListener(\"keydown\", down);\n\t\tdocument.addEventListener(\"keyup\", up);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"keydown\", down);\n\t\t\tdocument.removeEventListener(\"keyup\", up);\n\t\t};\n\t}, []);\n\treturn (\n\t\t<>\n\t\t\t<PowerIcon\n\t\t\t\tref={iconRef}\n\t\t\t\tclassName=\"size-6 stroke-[1.3px] stroke-current cursor-pointer duration-150\"\n\t\t\t\tonMouseUp={() => {\n\t\t\t\t\ticonRef.current?.classList.remove(\"scale-90\");\n\t\t\t\t\tsetShowMenu(prev => !prev);\n\t\t\t\t}}\n\t\t\t\tonMouseLeave={() => {\n\t\t\t\t\ticonRef.current?.classList.remove(\"scale-90\");\n\t\t\t\t}}\n\t\t\t\tonMouseOver={() => {\n\t\t\t\t\ticonRef.current?.classList.add(\"scale-90\");\n\t\t\t\t}}\n\t\t\t\tonClick={() => {\n\t\t\t\t\ticonRef.current?.classList.add(\"scale-90\");\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<div\n\t\t\t\tref={menu}\n\t\t\t\tclassName={`\n                absolute top-13.5 right-1.5 z-[-1] bg-[#2020208c] bg-[url(/assets/img/grain.png)] shadow-tb-border-shadow\n                ${showMenu ? \"duration-150\" : \"opacity-0 -translate-y-6 pointer-events-none duration-200\"} rounded-xl backdrop-blur-sm overflow-hidden\n            `}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n                    flex flex-col duration-1000\n                    ${showMenu ? \"\" : \"opacity-0 -translate-y-2\"}\n                `}\n\t\t\t\t>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={`\n                        first:rounded-t-lg last:rounded-b-lg hover:bg-[#ffffff28] flex gap-8 justify-between items-center px-3 py-2 duration-150\n                    `}\n\t\t\t\t\t>\n\t\t\t\t\t\t<span className=\"select-none font-semibold\">Sleep</span>\n\t\t\t\t\t\t<MoonIcon className=\"size-5\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={`\n                        first:rounded-t-lg last:rounded-b-lg hover:bg-[#ffffff28] flex gap-8 justify-between items-center px-3 py-2 duration-150\n                    `}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"false\");\n\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<span className=\"select-none font-semibold\">Lock</span>\n\t\t\t\t\t\t<LockClosedIcon className=\"size-5\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={`\n                        first:rounded-t-lg last:rounded-b-lg hover:bg-[#ffffff28] flex gap-8 justify-between items-center px-3 py-2 duration-150\n                    `}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"false\");\n\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<span className=\"select-none font-semibold\">Restart</span>\n\t\t\t\t\t\t<ArrowPathIcon className=\"size-5 stroke-[1.3px] stroke-current\" />\n\t\t\t\t\t</button>\n\t\t\t\t\t{showHardRestart && (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclassName={`\n                            first:rounded-t-lg last:rounded-b-lg hover:bg-[#ffffff28] flex gap-8 justify-between items-center px-3 py-2 duration-150\n                        `}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsessionStorage.clear();\n\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span className=\"select-none font-semibold\">Hard Restart</span>\n\t\t\t\t\t\t\t<ArrowPathIcon className=\"size-5 stroke-[1.3px] stroke-current\" />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)}\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName={`\n                        first:rounded-t-lg last:rounded-b-lg hover:bg-[#ff6060ce] flex gap-8 justify-between items-center px-3 py-2 duration-150\n                    `}\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tsessionStorage.setItem(\"logged-in\", \"false\");\n\t\t\t\t\t\t\twindow.location.href = \"https://google.com\";\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<span className=\"select-none font-semibold\">Shutdown</span>\n\t\t\t\t\t\t<PowerIcon className=\"size-5 stroke-[1.3px] stroke-current\" />\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "src/sys/gui/Search.tsx",
    "content": "import { FC, useEffect, useRef, useState } from \"react\";\nimport { MagnifyingGlassIcon } from \"@heroicons/react/24/solid\";\nimport { useSearchMenuStore } from \"../Store\";\nimport { StartItem } from \"./Dock\";\nimport { searchApps, searchFiles } from \"../apis/SysSearch\";\nimport { createWindow } from \"./WindowArea\";\n\ninterface SearchProps {\n\tclassName: string;\n\tsearchRef?: React.RefObject<HTMLInputElement | null>;\n}\n\nconst SearchMenu: FC<SearchProps> = ({ className }) => {\n\tconst searchMenuStore = useSearchMenuStore();\n\n\tconst [searchMatch, setSearchMatch] = useState<boolean>(false);\n\tconst [resultOpen, setResultOpen] = useState<boolean>(false);\n\tconst [searchHasText, setSearchHasText] = useState<boolean>(false);\n\tconst [searchActive, setSearchActive] = useState<boolean>(false);\n\tconst [recentApps, setRecentApps] = useState<Object[]>([]);\n\tconst searchMenuRef = useRef<HTMLDivElement>(null);\n\tconst searchRefRef = useRef<HTMLInputElement>(null);\n\tconst placeholderRef = useRef<HTMLSpanElement>(null);\n\tconst containerRef = useRef<HTMLDivElement>(null);\n\tconst resultRef = useRef<HTMLDivElement>(null);\n\tconst recentAppsRef = useRef<HTMLDivElement>(null);\n\tconst [results, setResults] = useState<any[]>([]);\n\tconst [noResutls, setNoResults] = useState<boolean>(false);\n\n\tuseEffect(() => {\n\t\tconst getRecentApps = async () => {\n\t\t\tconst recentApps = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/recent.json\"));\n\t\t\tconst recentAppsList = recentApps.map((app: any) => {\n\t\t\t\treturn app;\n\t\t\t});\n\t\t\tsetRecentApps(recentAppsList);\n\t\t\tsearchRefRef.current!.focus();\n\t\t};\n\t\tsearchMenuStore.open ? getRecentApps() : null;\n\t\tif (!searchMenuStore.open) {\n\t\t\tsearchRefRef.current!.value = \"\";\n\t\t\tsetNoResults(false);\n\t\t\tsetSearchMatch(false);\n\t\t\tsetSearchActive(false);\n\t\t\tsetSearchHasText(false);\n\t\t\tsetResultOpen(false);\n\t\t\tsetTimeout(() => {\n\t\t\t\trecentAppsRef.current!.classList.add(\"col-span-2\");\n\t\t\t\tcontainerRef.current!.classList.remove(\"grid-cols-2\");\n\t\t\t\tcontainerRef.current!.classList.add(\"grid-cols-1\");\n\t\t\t}, 200);\n\t\t\tsetTimeout(() => {\n\t\t\t\tresultRef.current!.classList.add(\"absolute\");\n\t\t\t}, 300);\n\t\t\tsetResults([]);\n\t\t}\n\t\tgetRecentApps();\n\t}, [searchMenuStore]);\n\n\tuseEffect(() => {\n\t\tsearchMenuStore.searchRef = { current: searchRefRef.current };\n\t\tsearchMenuStore.searchMenuRef = { current: searchMenuRef.current };\n\t});\n\n\treturn (\n\t\t<div\n\t\t\tref={searchMenuRef}\n\t\t\tclassName={\n\t\t\t\tclassName +\n\t\t\t\t`\n            bg-[#2020208c] shadow-tb-border-shadow backdrop-blur-sm rounded-xl\n            flex flex-col items-center justify-between\n            min-w-[440px] h-[266px]\n        `\n\t\t\t}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName={\n\t\t\t\t\t\"flex gap-2 items-center text-[#ffffffa4] p-2.5 pb-0 w-full duration-700\" +\n\t\t\t\t\t\" \" +\n\t\t\t\t\t`\n                ${searchMenuStore.open ? \"\" : \"translate-y-2 opacity-0\"}\n            `\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<MagnifyingGlassIcon className=\"size-6 text-[#ffffff86] stroke-current stroke-[2px]\" />\n\t\t\t\t<div className=\"relative flex items-center w-full\">\n\t\t\t\t\t<span ref={placeholderRef} className={`absolute font-[680] text-lg pointer-events-none duration-150 ${searchHasText ? \"opacity-0 -translate-x-1.5\" : searchActive ? \"opacity-100\" : \"opacity-75\"}`}>\n\t\t\t\t\t\tSearch for apps and files\n\t\t\t\t\t</span>\n\t\t\t\t\t<input\n\t\t\t\t\t\tref={searchRefRef}\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tclassName=\"bg-transparent focus-visible:outline-none text-lg font-[680] w-full cursor-text\"\n\t\t\t\t\t\tonFocus={() => setSearchActive(true)}\n\t\t\t\t\t\tonBlur={() => setSearchActive(false)}\n\t\t\t\t\t\tonChange={async e => {\n\t\t\t\t\t\t\tconst value = (e.target as HTMLInputElement).value;\n\t\t\t\t\t\t\tif (value.length > 0) {\n\t\t\t\t\t\t\t\tsetSearchActive(true);\n\t\t\t\t\t\t\t\tresultRef.current!.classList.remove(\"absolute\");\n\t\t\t\t\t\t\t\trecentAppsRef.current!.classList.remove(\"col-span-2\");\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.remove(\"grid-cols-1\");\n\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.add(\"grid-cols-2\");\n\t\t\t\t\t\t\t\t}, 200);\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tsetResultOpen(true);\n\t\t\t\t\t\t\t\t}, 200);\n\t\t\t\t\t\t\t\tsetSearchHasText(true);\n\t\t\t\t\t\t\t\tconst appres = await searchApps(value);\n\t\t\t\t\t\t\t\tconst filesres = await searchFiles(value);\n\t\t\t\t\t\t\t\tif (appres && Array.isArray(appres) && appres.length > 0) {\n\t\t\t\t\t\t\t\t\tsetSearchMatch(true);\n\t\t\t\t\t\t\t\t\tconst app = appres[0];\n\t\t\t\t\t\t\t\t\tlet iconHtml = \"\";\n\t\t\t\t\t\t\t\t\tif (typeof app.icon === \"string\") {\n\t\t\t\t\t\t\t\t\t\tif (app.icon.trim().startsWith(\"<svg\")) {\n\t\t\t\t\t\t\t\t\t\t\ticonHtml = app.icon;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\ticonHtml = `<img class=\"w-[49px] h-[49px]\" src=\"${app.icon}\"/>`;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconst appName = typeof app.name === \"string\" ? app.name : app.name && typeof app.name.text === \"string\" ? app.name.text : \"\";\n\t\t\t\t\t\t\t\t\tsetResults([\n\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\ticon: iconHtml,\n\t\t\t\t\t\t\t\t\t\t\t\tname: appName.charAt(0).toUpperCase() + appName.slice(1),\n\t\t\t\t\t\t\t\t\t\t\t\tdir: app.dir || \"Unknown Path\",\n\t\t\t\t\t\t\t\t\t\t\t\tconfig: app.cfg,\n\t\t\t\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tcreateWindow(app.cfg);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsearchMenuStore.open = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\tsearchRefRef.current!.value = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetSearchMatch(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetSearchActive(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetSearchHasText(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetResultOpen(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trecentAppsRef.current!.classList.add(\"col-span-2\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.remove(\"grid-cols-2\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.add(\"grid-cols-1\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t}, 200);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresultRef.current!.classList.add(\"absolute\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t}, 300);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetResults([]);\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t[],\n\t\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\t\t\tsetNoResults(false);\n\t\t\t\t\t\t\t\t\tsetSearchActive(false);\n\t\t\t\t\t\t\t\t} else if (filesres && Array.isArray(filesres) && filesres.length > 0) {\n\t\t\t\t\t\t\t\t\tsetSearchMatch(true);\n\t\t\t\t\t\t\t\t\twindow.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\", \"utf8\").then(async (data: string) => {\n\t\t\t\t\t\t\t\t\t\tconst fileIconsData = JSON.parse(data);\n\t\t\t\t\t\t\t\t\t\tconst getIcon = (ext: string) => {\n\t\t\t\t\t\t\t\t\t\t\tlet iconName = fileIconsData[\"ext-to-name\"][ext];\n\t\t\t\t\t\t\t\t\t\t\tlet iconPath = fileIconsData[\"name-to-path\"][iconName];\n\t\t\t\t\t\t\t\t\t\t\tif (iconPath) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn iconPath;\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\treturn fileIconsData[\"name-to-path\"][\"Unknown\"];\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tconst fileItems = await Promise.all(\n\t\t\t\t\t\t\t\t\t\t\tfilesres.map(async (f: any) => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst iconSvg = await window.tb.fs.promises.readFile(getIcon(f.ext), \"utf8\");\n\t\t\t\t\t\t\t\t\t\t\t\tfunction rewriteSvgSize(svg: string) {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn svg.replace(/<svg([^>]*)>/, (_, attrs) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet newAttrs = attrs.replace(/\\swidth=['\"][^'\"]*['\"]/, \"\").replace(/\\sheight=['\"][^'\"]*['\"]/, \"\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn `<svg${newAttrs} width=\"48\" height=\"48\">`;\n\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tconst newSvg = rewriteSvgSize(iconSvg);\n\t\t\t\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\t\t\t\ticon: newSvg,\n\t\t\t\t\t\t\t\t\t\t\t\t\tname: f.name.charAt(0).toUpperCase() + f.name.slice(1) || value.charAt(0).toUpperCase() + value.slice(1),\n\t\t\t\t\t\t\t\t\t\t\t\t\tpath: f.path || \"\",\n\t\t\t\t\t\t\t\t\t\t\t\t\text: f.ext,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdir: f.dir,\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick: async () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet handlers = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"fileAssociatedApps\"];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thandlers = Object.entries(handlers).filter(([type, app]) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn !((type === \"text\" && app === \"text-editor\") || (type === \"image\" && app === \"media-viewer\") || (type === \"video\" && app === \"media-viewer\") || (type === \"audio\" && app === \"media-viewer\"));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst dat = JSON.parse(await window.tb.fs.promises.readFile(\"/apps/system/files.tapp/extensions.json\", \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet hands: { text: string; value: string }[] = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor (const [type, app] of handlers) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thands.push({ text: app, value: type });\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawait window.tb.dialog.Select({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `Select a application to open: ${f.name}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\toptions: [{ text: \"Text Editor\", value: \"text\" }, { text: \"Media Viewer\", value: \"media\" }, { text: \"Webview\", value: \"webview\" }, ...hands, { text: \"Other\", value: \"other\" }],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: async (val: string) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tswitch (val) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase \"text\":\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(f.path, \"text\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase \"media\":\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst ext = f.name.split(\".\").pop();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (dat[\"image\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(f.path, \"image\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else if (dat[\"video\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(f.path, \"video\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else if (dat[\"audio\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(f.path, \"audio\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase \"webview\":\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(f.path, \"webpage\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcase \"other\":\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilter: \".tapp\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: async (val: string) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst app = JSON.parse(await window.tb.fs.promises.readFile(`${val}/.tbconfig`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow.parent.tb.window.create({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t...app.wmArgs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmessage: { type: \"process\", path: f.dir },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (hands.length === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(f.path, \"text\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(f.path, val);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsearchMenuStore.open = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsearchRefRef.current!.value = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetNoResults(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetSearchMatch(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetSearchActive(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetSearchHasText(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetResultOpen(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trecentAppsRef.current!.classList.add(\"col-span-2\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.remove(\"grid-cols-2\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.add(\"grid-cols-1\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}, 200);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tresultRef.current!.classList.add(\"absolute\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}, 300);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetResults([]);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\tsetNoResults(false);\n\t\t\t\t\t\t\t\t\t\tsetResults([[], fileItems]);\n\t\t\t\t\t\t\t\t\t\tsetSearchActive(false);\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else if (appres === false && filesres === false) {\n\t\t\t\t\t\t\t\t\tsetSearchMatch(false);\n\t\t\t\t\t\t\t\t\tsetNoResults(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetSearchMatch(false);\n\t\t\t\t\t\t\t\tsetSearchHasText(false);\n\t\t\t\t\t\t\t\tsetResults([]);\n\t\t\t\t\t\t\t\tsetResultOpen(false);\n\t\t\t\t\t\t\t\tsetNoResults(false);\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\trecentAppsRef.current!.classList.add(\"col-span-2\");\n\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.remove(\"grid-cols-2\");\n\t\t\t\t\t\t\t\t\tcontainerRef.current!.classList.add(\"grid-cols-1\");\n\t\t\t\t\t\t\t\t}, 200);\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tresultRef.current!.classList.add(\"absolute\");\n\t\t\t\t\t\t\t\t}, 300);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tclassName={\n\t\t\t\t\t\"relative flex items-center min-h-[104px] h-full w-full gap-2 p-2.5 pt-0 duration-1000\" +\n\t\t\t\t\t\" \" +\n\t\t\t\t\t`\n                ${searchMenuStore.open ? \"\" : \"translate-y-4 opacity-0\"}`\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<div ref={containerRef} className={\"grid gap-2 pt-2 w-full h-full overflow-hidden\"}>\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={recentAppsRef}\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\"relative grid overflow-hidden\" +\n\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t`\n                        ${recentApps.length > 0 ? \"\" : \"items-center justify-center\"}\n                    `\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t{recentApps.length <= 0 && (\n\t\t\t\t\t\t\t<h1\n\t\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\t\"font-bold text-lg leading-none pt-2 text-[#ffffff68]\" +\n\t\t\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t\t\t`\n                                ${recentApps.length <= 0 ? \"\" : \"opacity-0 pointer-events-none translate-4 duration-200\"}\n                            `\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tNo recent apps\n\t\t\t\t\t\t\t</h1>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\"flex flex-col gap-2\" +\n\t\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t\t`\n                            ${recentApps.length > 0 ? \"duration-150\" : \"opacity-0 pointer-events-none translate-4 duration-200\"}\n                        `\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<h1 className={\"font-bold text-lg leading-none text-[#ffffff68]\"}>Recent apps</h1>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\t\"grid items-center gap-1 overflow-y-auto rounded-md\" +\n\t\t\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t\t\t`\n                                ${results.length > 0 ? \"grid-cols-1\" : \"grid-cols-2\"}\n                            `\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{recentApps.length > 0\n\t\t\t\t\t\t\t\t\t? recentApps\n\t\t\t\t\t\t\t\t\t\t\t.sort((a: any, b: any) => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst valueDiff = (b.value ?? 0) - (a.value ?? 0);\n\t\t\t\t\t\t\t\t\t\t\t\tif (valueDiff !== 0) return valueDiff;\n\t\t\t\t\t\t\t\t\t\t\t\treturn (b.weight ?? 0) - (a.weight ?? 0);\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t.slice(0, 8)\n\t\t\t\t\t\t\t\t\t\t\t.map((app: any, i: number) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<StartItem\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={app.title}\n\t\t\t\t\t\t\t\t\t\t\t\t\ticon={app.icon}\n\t\t\t\t\t\t\t\t\t\t\t\t\tpid={undefined}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsrc={app.src}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsize={app.size}\n\t\t\t\t\t\t\t\t\t\t\t\t\tproxy={app.proxy}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsnapable={app.snapable}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsrc: app.src,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize: app.size,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ticon: typeof app.icon === \"string\" ? app.icon : undefined,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: app.title,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproxy: app.proxy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsnapable: app.snapable,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsearchMenuStore.open = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t: null}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={resultRef}\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\"flex flex-col p-2 bg-[#15151594] rounded-lg shadow-tb-border-shadow overflow-y-auto\" +\n\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t`\n                        ${searchMatch === false ? \"justify-center items-center\" : \"\"}\n                        ${resultOpen ? \"duration-150\" : \"opacity-0 pointer-events-none translate-y-4 duration-200\"}\n                    `\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<h1 className={\"font-bold text-lg leading-none pt-2 text-[#ffffff68]\"}>Search results</h1>\n\t\t\t\t\t\t{noResutls ? (\n\t\t\t\t\t\t\t<div className=\"flex gap-1.5 duration-150 items-center text-[#ffffff51]\">\n\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10\">\n\t\t\t\t\t\t\t\t\t<path d=\"M12 2a10 10 0 1 0 0 20A10 10 0 0 0 12 2Zm1.5 14.25h-3v-1.5h3v1.5Zm0-3h-3V7.5h3v5.75Z\" />\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t<span className=\"text-sm font-black\">No apps or files relevant to searches</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t) : results.length > 0 && searchActive ? (\n\t\t\t\t\t\t\t<div className=\"flex flex-col items-center justify-center gap-1.5 h-full w-full text-[#ffffffa4] font-[680] text-lg\">\n\t\t\t\t\t\t\t\tSearching...\n\t\t\t\t\t\t\t\t<div className=\"relative flex w-[80%] h-2 rounded-full bg-[#00000020] overflow-hidden shadow-tb-border-shadow\">\n\t\t\t\t\t\t\t\t\t<div className=\"absolute h-full bg-[#50bf66] rounded-full\" style={{ animation: \"2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite normal none running anim1\" }}></div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<div className={\"flex flex-col gap-0.5 overflow-y-auto\"}>\n\t\t\t\t\t\t\t\t{Array.isArray(results[0]) && results[0].length > 0\n\t\t\t\t\t\t\t\t\t? results[0].map((app: any, i: number) => (\n\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={\"flex flex-col gap-2 search-result-app cursor-pointer hover:bg-[#22222288] rounded-md p-2\"}\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tapp.click();\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className={\"flex flex-row gap-1 items-center\"}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{typeof app.icon === \"string\" ? app.icon.trim().startsWith(\"<svg\") ? <span dangerouslySetInnerHTML={{ __html: app.icon }} /> : <img className=\"w-[49px] h-[49px]\" src={app.icon.replace(/^<img.*src=\"([^\"]*)\".*$/, \"$1\")} alt={app.name} /> : app.icon}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<h1 className={\"font-extrabold text-xl\"}>{app.name}</h1>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<h3 className={\"font-bold text-xs\"}>{app.dir || \"Unknown Path\"}</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t: Array.isArray(results[1]) && results[1].length > 0\n\t\t\t\t\t\t\t\t\t\t? results[1].map((f: any, i: number) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={\"flex flex-col gap-2 search-result-file cursor-pointer hover:bg-[#22222288] rounded-md p-2\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tf.onClick();\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div className={\"flex flex-row gap-1 items-center\"}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{typeof f.icon === \"string\" ? f.icon.trim().startsWith(\"<svg\") ? <span dangerouslySetInnerHTML={{ __html: f.icon }} /> : <img className=\"w-[49px] h-[49px]\" src={f.icon.replace(/^<img.*src=\"([^\"]*)\".*$/, \"$1\")} alt={f.name} /> : f.icon}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<h1 className={\"font-extrabold text-xl\"}>{f.name}</h1>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<h3 className={\"font-bold text-xs\"}>{f.path || \"\"}</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t\t: null}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t{recentApps.length === 0 ||\n\t\t\t\t\t(searchMatch === false && (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\"absolute top-1/2 left-1/2 -translate-1/2 flex gap-1.5 duration-150 items-center text-[#ffffff51]\" +\n\t\t\t\t\t\t\t\t\" \" +\n\t\t\t\t\t\t\t\t`\n                        ${searchMatch ? \"\" : \"opacity-0 pointer-events-none -translate-x-3\"}\n                    `\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-10\">\n\t\t\t\t\t\t\t\t<path d=\"M12 2a10 10 0 1 0 0 20A10 10 0 0 0 12 2Zm1.5 14.25h-3v-1.5h3v1.5Zm0-3h-3V7.5h3v5.75Z\" />\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t<span className=\"text-sm font-black\">No recent apps or relevant searches</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n};\n\nexport default SearchMenu;\n"
  },
  {
    "path": "src/sys/gui/Shell.tsx",
    "content": "import Mediaisland from \"../apis/Mediaisland\";\nimport Battery from \"./Battery\";\nimport \"./styles/shell.css\";\nimport Wifi from \"./Wifi\";\nimport getTime from \"../apis/Time\";\nimport { useEffect, useState } from \"react\";\nimport Weather from \"./Weather\";\nimport { FPSCounter } from \"./FPSCounter\";\nimport NotificationCenter from \"./NotificationCenter\";\nimport AppIsland from \"./AppIsland\";\nimport Power from \"./Power\";\n\nconst Shell = () => {\n\tconst [time, setTime] = useState<number>(0);\n\tuseEffect(() => {\n\t\tconst int = setInterval(() => {\n\t\t\t// @ts-expect-error\n\t\t\tsetTime(getTime());\n\t\t}, 100);\n\t\treturn () => clearInterval(int);\n\t}, []);\n\treturn (\n\t\t<div className=\"shell flex z-100 w-full gap-1.5 text-[#5f5f5f] px-1.5 py-1 justify-between\">\n\t\t\t<div className=\"islands_left relative flex gap-2 items-center\">\n\t\t\t\t<AppIsland />\n\t\t\t\t<Mediaisland />\n\t\t\t</div>\n\t\t\t<div className=\"islands_right flex gap-1.5 items-center\">\n\t\t\t\t<div className=\"island p-3 px-3.5 gap-2 rounded-xl select-none *:leading-none\" style={{ backgroundImage: \"url(/assets/img/grain.png)\" }}>\n\t\t\t\t\t<FPSCounter />\n\t\t\t\t\t<div className=\"weather font-bold cursor-default\">\n\t\t\t\t\t\t<Weather />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"time font-bold cursor-default\">{time}</div>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"island system_island gap-2 p-3 px-3.5 pr-2.5 py-2 rounded-xl *:leading-none\" style={{ backgroundImage: \"url(/assets/img/grain.png)\" }}>\n\t\t\t\t\t<Power />\n\t\t\t\t\t<Wifi />\n\t\t\t\t\t<NotificationCenter />\n\t\t\t\t\t<Battery />\n\t\t\t\t\t{/* Desktop */}\n\t\t\t\t\t<div className=\"show_desk bg-[#ffffff3e] h-8 w-4 rounded-[5px] cursor-pointer\" onClick={() => window.dispatchEvent(new Event(\"min-wins\"))}></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n};\n\nexport default Shell;\n"
  },
  {
    "path": "src/sys/gui/Weather.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport getTime from \"../apis/Time\";\nimport { SysSettings } from \"../types\";\n\ninterface LocationData {\n\tproperties: {\n\t\tforecast: string;\n\t};\n}\ninterface ForecastData {\n\tproperties: {\n\t\tperiods: Period[];\n\t};\n}\ninterface WeatherData {\n\ttemp: number;\n\tunit: string;\n\ticn: string;\n}\ninterface Period {\n\ttemperature: number;\n\tshortForecast: string;\n}\n\nexport default function Weather() {\n\tconst [weatherData, setWeatherData] = useState<WeatherData | null>(null);\n\tconst [loaded, setLoaded] = useState<boolean>(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\tuseEffect(() => {\n\t\tconst getWeather = async () => {\n\t\t\ttry {\n\t\t\t\tconst settings: SysSettings = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\"));\n\t\t\t\tconst defaultLocation = \"40.7590322,-74.0516312\";\n\t\t\t\tconst loc = settings.location || defaultLocation;\n\t\t\t\tconst locationResponse = await fetch(`https://api.weather.gov/points/${loc}`, {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0 Safari/537.36 Terbium-Browser/2.3.0\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tconst locationData: LocationData = await locationResponse.json();\n\t\t\t\tconst forecastResponse = await fetch(locationData.properties.forecast, {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0 Safari/537.36 Terbium-Browser/2.3.0\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tconst forecastData: ForecastData = await forecastResponse.json();\n\t\t\t\tconst currentPeriod = forecastData.properties.periods[0];\n\t\t\t\tlet temp = currentPeriod.temperature;\n\t\t\t\tlet unit = \"°F\";\n\t\t\t\tif (settings.weather) {\n\t\t\t\t\tconst { unit: userUnit } = settings.weather;\n\t\t\t\t\t({ temp, unit } = FormatTemp(temp, userUnit));\n\t\t\t\t}\n\t\t\t\tconst icn = getIcon(currentPeriod.shortForecast);\n\t\t\t\tsetWeatherData({ temp, unit, icn });\n\t\t\t\tsetLoaded(true);\n\t\t\t} catch (err: any) {\n\t\t\t\tsetError(err);\n\t\t\t}\n\t\t};\n\t\tgetWeather();\n\t\twindow.addEventListener(\"updWeather\", getWeather);\n\t\treturn () => window.removeEventListener(\"updWeather\", getWeather);\n\t}, []);\n\treturn loaded && !error ? (\n\t\t<div className=\"flex flex-row items-center gap-1.5\">\n\t\t\t<img src={weatherData?.icn} className=\"w-6 h-6\" />\n\t\t\t<div className=\"weather_temp\">\n\t\t\t\t{weatherData?.temp}\n\t\t\t\t{weatherData?.unit}\n\t\t\t</div>\n\t\t</div>\n\t) : null;\n}\n\nfunction FormatTemp(temp: number, unit: string): { temp: number; unit: string } {\n\tswitch (unit) {\n\t\tcase \"Celsius\":\n\t\t\treturn { temp: Math.round(((temp - 32) * 5) / 9), unit: \"°C\" };\n\t\tcase \"Kelvin\":\n\t\t\treturn { temp: Math.round(((temp - 32) * 5) / 9 + 273.15), unit: \" K\" };\n\t\tdefault:\n\t\t\treturn { temp, unit: \"°F\" };\n\t}\n}\n\nfunction getIcon(sky: string): string {\n\tconst time = getTime();\n\tconst [timePart, period] = time.split(\" \");\n\tconst [hours] = timePart.split(\":\").map(Number);\n\tconst hour24 = period === \"PM\" && hours !== 12 ? hours + 12 : period === \"AM\" && hours === 12 ? 0 : hours;\n\tlet icn = \"/assets/img/day.svg\";\n\tif (sky === \"Sunny\" || sky === \"Clear\" || sky === \"Mostly Clear\") {\n\t\ticn = hour24 >= 18 ? \"/assets/img/night.svg\" : \"/assets/img/day.svg\";\n\t} else if (sky.includes(\"Partly\")) {\n\t\ticn = hour24 >= 18 ? \"/assets/img/cloudy_night.svg\" : \"/assets/img/cloudy_day.svg\";\n\t} else if (sky.includes(\"Cloudy\")) {\n\t\ticn = \"/assets/img/cloudy.svg\";\n\t} else if (sky.includes(\"Rain\")) {\n\t\ticn = \"/assets/img/rainy.svg\";\n\t} else if (sky.includes(\"Snow\") || sky.includes(\"Hail\")) {\n\t\ticn = \"/assets/img/snowy.svg\";\n\t}\n\treturn icn;\n}\n"
  },
  {
    "path": "src/sys/gui/Wifi.tsx",
    "content": "import { useEffect, useRef, useState } from \"react\";\nimport \"./styles/wifi.css\";\nimport { fileExists } from \"../types\";\n\ninterface Server {\n\tid: string;\n\tname: string;\n\tlatency?: string;\n\tconnected?: boolean;\n\tisOnline?: boolean;\n}\n\nfunction ping(id: string): Promise<{ status: string; latency: number | string }> {\n\treturn new Promise(resolve => {\n\t\tconst websocket = new WebSocket(id);\n\t\tconst startTime = Date.now();\n\t\tconst onOpen = () => {\n\t\t\tconst latency = Date.now() - startTime;\n\t\t\twebsocket.close();\n\t\t\tresolve({ status: \"OK\", latency });\n\t\t};\n\t\tconst onMessage = () => {\n\t\t\tconst latency = Date.now() - startTime;\n\t\t\twebsocket.close();\n\t\t\tresolve({ status: \"OK\", latency });\n\t\t};\n\t\tconst onError = () => {\n\t\t\twebsocket.close();\n\t\t\tresolve({ status: \"Fail\", latency: \"N/A\" });\n\t\t};\n\t\twebsocket.addEventListener(\"open\", onOpen);\n\t\twebsocket.addEventListener(\"message\", onMessage);\n\t\twebsocket.addEventListener(\"error\", onError);\n\t\tsetTimeout(() => {\n\t\t\twebsocket.close();\n\t\t\tresolve({ status: \"Fail\", latency: \"N/A\" });\n\t\t}, 5000);\n\t});\n}\n\ninterface WifiIconProps {\n\tconnection: boolean;\n}\n\nconst WifiIcon: React.FC<WifiIconProps> = ({ connection }) => {\n\tconst iconRef = useRef<SVGSVGElement>(null);\n\tconst LoadMenu = () => window.dispatchEvent(new Event(\"open-net\"));\n\treturn (\n\t\t<svg\n\t\t\tref={iconRef}\n\t\t\tviewBox=\"0 0 33 24\"\n\t\t\tfill=\"none\"\n\t\t\tclassName={`w-6 h-6 tooltip_item cursor-pointer duration-150 dlcomponent`}\n\t\t\tonMouseUp={() => {\n\t\t\t\ticonRef.current?.classList.remove(\"scale-90\");\n\t\t\t\tLoadMenu();\n\t\t\t}}\n\t\t\tonMouseLeave={() => {\n\t\t\t\ticonRef.current?.classList.remove(\"scale-90\");\n\t\t\t}}\n\t\t\tonMouseOver={() => {\n\t\t\t\ticonRef.current?.classList.add(\"scale-90\");\n\t\t\t}}\n\t\t\tonClick={() => {\n\t\t\t\ticonRef.current?.classList.add(\"scale-90\");\n\t\t\t}}\n\t\t>\n\t\t\t{connection === true ? (\n\t\t\t\t<path\n\t\t\t\t\tclassName={`pointer-events-none`}\n\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\td=\"M0.332717 6.69389C9.25943 -2.2313 23.7329 -2.2313 32.6581 6.69389C32.7643 6.80002 32.8486 6.92605 32.9061 7.06476C32.9635 7.20347 32.9931 7.35214 32.9931 7.50229C32.9931 7.65244 32.9635 7.80112 32.9061 7.93982C32.8486 8.07853 32.7643 8.20456 32.6581 8.31069L31.8505 9.11833C31.6362 9.33236 31.3457 9.45257 31.0429 9.45257C30.74 9.45257 30.4495 9.33236 30.2352 9.11833C22.6464 1.53109 10.3444 1.53109 2.75716 9.11833C2.54287 9.33236 2.25239 9.45257 1.94952 9.45257C1.64666 9.45257 1.35617 9.33236 1.14188 9.11833L0.334241 8.31069C0.120216 8.0964 0 7.80592 0 7.50305C0 7.20019 0.120216 6.9097 0.334241 6.69541L0.332717 6.69389ZM5.18161 11.5428C11.4294 5.295 21.5615 5.295 27.8108 11.5428C27.917 11.6489 28.0012 11.7749 28.0587 11.9136C28.1162 12.0524 28.1458 12.201 28.1458 12.3512C28.1458 12.5013 28.1162 12.65 28.0587 12.7887C28.0012 12.9274 27.917 13.0534 27.8108 13.1596L27.0031 13.9672C26.897 14.0737 26.7708 14.1581 26.632 14.2157C26.4931 14.2733 26.3443 14.303 26.194 14.303C26.0436 14.303 25.8948 14.2733 25.7559 14.2157C25.6171 14.1581 25.491 14.0737 25.3848 13.9672C23.0272 11.6097 19.8296 10.2852 16.4954 10.2852C13.1613 10.2852 9.9637 11.6097 7.60605 13.9672C7.39176 14.1812 7.10128 14.3015 6.79841 14.3015C6.49554 14.3015 6.20506 14.1812 5.99077 13.9672L5.18313 13.1596C4.9691 12.9453 4.84889 12.6548 4.84889 12.3519C4.84889 12.0491 4.9691 11.7586 5.18313 11.5443L5.18161 11.5428ZM10.064 16.3917C10.9131 15.5425 11.921 14.8689 13.0304 14.4093C14.1398 13.9497 15.3289 13.7132 16.5297 13.7132C17.7305 13.7132 18.9196 13.9497 20.029 14.4093C21.1384 14.8689 22.1464 15.5425 22.9954 16.3917C23.1016 16.4978 23.1859 16.6238 23.2433 16.7625C23.3008 16.9012 23.3304 17.0499 23.3304 17.2001C23.3304 17.3502 23.3008 17.4989 23.2433 17.6376C23.1859 17.7763 23.1016 17.9023 22.9954 18.0085L22.1878 18.8161C22.0816 18.9223 21.9556 19.0066 21.8169 19.064C21.6782 19.1215 21.5295 19.1511 21.3794 19.1511C21.2292 19.1511 21.0805 19.1215 20.9418 19.064C20.8031 19.0066 20.6771 18.9223 20.571 18.8161C20.0403 18.2853 19.4103 17.8643 18.7169 17.577C18.0235 17.2897 17.2803 17.1419 16.5297 17.1419C15.7792 17.1419 15.0359 17.2897 14.3425 17.577C13.6491 17.8643 13.0191 18.2853 12.4885 18.8161C12.2742 19.0301 11.9837 19.1504 11.6808 19.1504C11.378 19.1504 11.0875 19.0301 10.8732 18.8161L10.0655 18.0085C9.85152 17.7942 9.7313 17.5037 9.7313 17.2008C9.7313 16.898 9.85152 16.6075 10.0655 16.3932L10.064 16.3917ZM14.9129 21.2406C15.1252 21.0281 15.3772 20.8596 15.6546 20.7447C15.9321 20.6297 16.2294 20.5705 16.5297 20.5705C16.83 20.5705 17.1274 20.6297 17.4048 20.7447C17.6822 20.8596 17.9342 21.0281 18.1465 21.2406C18.2527 21.3467 18.337 21.4727 18.3944 21.6114C18.4519 21.7501 18.4815 21.8988 18.4815 22.049C18.4815 22.1991 18.4519 22.3478 18.3944 22.4865C18.337 22.6252 18.2527 22.7512 18.1465 22.8574L17.3389 23.665C17.2327 23.7712 17.1067 23.8555 16.968 23.9129C16.8293 23.9704 16.6806 24 16.5305 24C16.3803 24 16.2316 23.9704 16.0929 23.9129C15.9542 23.8555 15.8282 23.7712 15.7221 23.665L14.9144 22.8574C14.7004 22.6431 14.5802 22.3526 14.5802 22.0497C14.5802 21.7469 14.7004 21.4564 14.9144 21.2421L14.9129 21.2406Z\"\n\t\t\t\t\tfill=\"white\"\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<>\n\t\t\t\t\t<path\n\t\t\t\t\t\tclassName={`pointer-events-none`}\n\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\td=\"M32.6581 6.69389C23.7329 -2.2313 9.25943 -2.2313 0.332717 6.69389L0.334241 6.69541C0.120216 6.9097 0 7.20019 0 7.50305C0 7.80592 0.120216 8.0964 0.334241 8.31069L1.14188 9.11833C1.35617 9.33236 1.64666 9.45257 1.94952 9.45257C2.25239 9.45257 2.54287 9.33236 2.75716 9.11833C10.3444 1.53109 22.6464 1.53109 30.2352 9.11833C30.4495 9.33236 30.74 9.45257 31.0429 9.45257C31.3457 9.45257 31.6362 9.33236 31.8505 9.11833L32.6581 8.31069C32.7643 8.20456 32.8486 8.07853 32.9061 7.93982C32.9635 7.80112 32.9931 7.65244 32.9931 7.50229C32.9931 7.35214 32.9635 7.20347 32.9061 7.06476C32.8486 6.92605 32.7643 6.80003 32.6581 6.69389ZM24.5232 9.01114C18.3999 5.45691 10.4236 6.30079 5.18161 11.5428L5.18313 11.5443C4.9691 11.7586 4.84889 12.0491 4.84889 12.3519C4.84889 12.6548 4.9691 12.9453 5.18313 13.1596L5.99077 13.9672C6.20506 14.1812 6.49554 14.3015 6.79841 14.3015C7.10128 14.3015 7.39176 14.1812 7.60605 13.9672C9.9637 11.6097 13.1613 10.2852 16.4954 10.2852C17.5155 10.2852 18.5228 10.4092 19.4972 10.6488C20.9516 9.68859 22.6715 9.09807 24.5232 9.01114ZM15.1995 20.9975C15.4157 22.0636 15.8014 23.0682 16.3273 23.9818C16.2472 23.9673 16.1686 23.9443 16.0929 23.9129C15.9542 23.8555 15.8282 23.7712 15.7221 23.665L14.9144 22.8574C14.7004 22.6431 14.5802 22.3526 14.5802 22.0497C14.5802 21.7469 14.7004 21.4564 14.9144 21.2421L14.9129 21.2406C15.0018 21.1516 15.0977 21.0703 15.1995 20.9975ZM16.5102 13.7132C15.8366 14.7927 15.3631 16.0099 15.1417 17.313C14.8698 17.3811 14.6027 17.4692 14.3425 17.577C13.6491 17.8643 13.0191 18.2853 12.4885 18.8161C12.2742 19.0301 11.9837 19.1504 11.6808 19.1504C11.378 19.1504 11.0875 19.0301 10.8732 18.8161L10.0655 18.0085C9.85152 17.7942 9.7313 17.5037 9.7313 17.2008C9.7313 16.898 9.85152 16.6075 10.0655 16.3932L10.064 16.3917C10.9131 15.5425 11.921 14.8689 13.0304 14.4093C14.1338 13.9522 15.316 13.7157 16.5102 13.7132Z\"\n\t\t\t\t\t\tfill=\"white\"\n\t\t\t\t\t/>\n\t\t\t\t\t<path\n\t\t\t\t\t\tclassName={`pointer-events-none`}\n\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\td=\"M21.2099 14.4482L29.5525 22.7908C30.4999 21.6554 30.9883 20.2067 30.9217 18.7295C30.8552 17.2523 30.2385 15.8534 29.1929 14.8078C28.1473 13.7622 26.7484 13.1456 25.2712 13.079C23.794 13.0125 22.3453 13.5009 21.2099 14.4482ZM28.7915 23.5518L20.4489 15.2092C19.5016 16.3446 19.0132 17.7933 19.0797 19.2705C19.1463 20.7477 19.763 22.1466 20.8086 23.1922C21.8542 24.2378 23.253 24.8544 24.7302 24.921C26.2074 24.9875 27.6561 24.4991 28.7915 23.5518ZM20.0505 14.0505C22.7837 11.3165 27.2156 11.3165 29.9495 14.0505C32.6835 16.7837 32.6835 21.2156 29.9495 23.9495C27.2163 26.6835 22.7844 26.6835 20.0505 23.9495C17.3165 21.2163 17.3165 16.7844 20.0505 14.0505Z\"\n\t\t\t\t\t\tfill=\"white\"\n\t\t\t\t\t\tstroke=\"white\"\n\t\t\t\t\t/>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</svg>\n\t);\n};\n\nexport default function Wifi() {\n\tconst [connection, setConnection] = useState(false);\n\tuseEffect(() => {\n\t\tconst getConnection = async () => {\n\t\t\tif (\"onLine\" in navigator) {\n\t\t\t\tsetConnection(navigator.onLine);\n\t\t\t}\n\t\t};\n\n\t\tconst int = setInterval(getConnection, 1000);\n\t\tgetConnection();\n\t\treturn () => clearInterval(int);\n\t}, []);\n\treturn <WifiIcon connection={connection} />;\n}\n\ninterface WispMenuProps {\n\tisOpen: boolean;\n}\n\nexport function WispMenu({ isOpen }: WispMenuProps) {\n\tconst [servers, setServers] = useState<Server[]>([]);\n\tconst [loading, setLoading] = useState<boolean>(true);\n\tconst menuRef = useRef<HTMLDivElement>(null);\n\tconst [isinDiag, setisinDiag] = useState(false);\n\tconst [isUpdating, setUpdating] = useState(false);\n\n\tuseEffect(() => {\n\t\tconst fetchServers = async (): Promise<void> => {\n\t\t\tconst exists = await fileExists(\"//apps/system/settings.tapp/wisp-servers.json\");\n\t\t\tif (!exists) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(\"//apps/system/settings.tapp/\", { recursive: true } as any);\n\t\t\t\tconst stockDat: Server[] = [\n\t\t\t\t\t{ id: `${location.protocol.replace(\"http\", \"ws\")}//${location.hostname}:${location.port}/wisp/`, name: \"Backend\" },\n\t\t\t\t\t{ id: \"wss://wisp.terbiumon.top/wisp/\", name: \"TB Wisp Instance\" },\n\t\t\t\t];\n\t\t\t\tawait window.tb.fs.promises.writeFile(\"//apps/system/settings.tapp/wisp-servers.json\", JSON.stringify(stockDat));\n\t\t\t}\n\t\t\tconst data: Server[] = JSON.parse(await window.tb.fs.promises.readFile(\"//apps/system/settings.tapp/wisp-servers.json\"));\n\t\t\tconst settings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`));\n\t\t\tconst servers = await Promise.all(\n\t\t\t\tdata.map(async server => {\n\t\t\t\t\tconst res = await ping(server.id);\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...server,\n\t\t\t\t\t\tlatency: res.latency === \"N/A\" ? res.latency : `${res.latency}ms`,\n\t\t\t\t\t\tconnected: server.id === settings[\"wispServer\"] ? true : false,\n\t\t\t\t\t\tisOnline: res.latency !== \"N/A\",\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t);\n\t\t\tsetServers(servers);\n\t\t\tsetLoading(false);\n\t\t};\n\t\tif (isUpdating) fetchServers();\n\t\twindow.addEventListener(\"update-wispsrvs\", fetchServers);\n\t\treturn () => window.removeEventListener(\"update-wispsrvs\", fetchServers);\n\t}, [isUpdating, isOpen]);\n\tuseEffect(() => {\n\t\tsetUpdating(true);\n\t});\n\tuseEffect(() => {\n\t\tconst leave = (e: MouseEvent) => {\n\t\t\tconst withinRadius = (e: MouseEvent) => {\n\t\t\t\tif (!menuRef.current) return false;\n\t\t\t\tconst rect = menuRef.current.getBoundingClientRect();\n\t\t\t\tconst xBound = e.clientX >= rect.left - 5 && e.clientX <= rect.right + 5;\n\t\t\t\tconst yBound = e.clientY >= rect.top - 5 && e.clientY <= rect.bottom + 5;\n\t\t\t\treturn xBound && yBound;\n\t\t\t};\n\t\t\tif (e.button === 0) {\n\t\t\t\tif (!menuRef.current?.contains(e.target as Node) && !withinRadius(e)) {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tif (!isinDiag) {\n\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"open-net\"));\n\t\t\t\t\t\t\tsetisinDiag(false);\n\t\t\t\t\t\t\tdocument.removeEventListener(\"mousedown\", leave);\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 150);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tif (!isOpen) {\n\t\t\tdocument.removeEventListener(\"mousedown\", leave);\n\t\t} else {\n\t\t\tdocument.addEventListener(\"mousedown\", leave);\n\t\t}\n\t\treturn () => document.removeEventListener(\"mousedown\", leave);\n\t}, [isOpen, isinDiag]);\n\treturn (\n\t\t<div\n\t\t\tclassName={`\n            absolute z-100 top-[60px] right-1.5\n            flex flex-col p-2 gap-2\n            rounded-xl bg-[#2020208c] backdrop-blur-[8px] shadow-tb-border-shadow\n            ${isOpen ? \"duration-150\" : \"opacity-0 pointer-events-none -translate-y-6 duration-200\"}\n        `}\n\t\t\tid=\"wisp-selector\"\n\t\t\tref={menuRef}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName={`\n                flex flex-col duration-700 gap-2\n                ${isOpen ? \"\" : \"opacity-0 -translate-y-2\"}\n            `}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName={`\n                    flex flex-col gap-1 ${servers.length === 0 ? \"justify-center items-center\" : \"\"}\n                `}\n\t\t\t\t\tkey={servers.length.toString()}\n\t\t\t\t>\n\t\t\t\t\t{loading ? (\n\t\t\t\t\t\t<p>Loading...</p>\n\t\t\t\t\t) : // check if there are any servers\n\t\t\t\t\tservers.length === 0 ? (\n\t\t\t\t\t\t<span className=\"font-medium text-lg text-[#ffffffc8]\">No servers found</span>\n\t\t\t\t\t) : (\n\t\t\t\t\t\tservers.map(server => (\n\t\t\t\t\t\t\t<div key={server.id}>\n\t\t\t\t\t\t\t\t{server.isOnline ? (\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tkey={server.id}\n\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                flex gap-6 items-center justify-between\n                                                p-2 rounded-lg\n                                                ${server.connected ? \"bg-[#4acd609c] text-[#ffffffd1]\" : \"bg-[#ffffff18] cursor-pointer hover:bg-[#ffffff38]\"}\n                                                duration-150\n                                            `}\n\t\t\t\t\t\t\t\t\t\tonClick={async () => {\n\t\t\t\t\t\t\t\t\t\t\tlet settings = await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\");\n\t\t\t\t\t\t\t\t\t\t\tlet settdata = JSON.parse(settings);\n\t\t\t\t\t\t\t\t\t\t\tsettdata.wispServer = server.id;\n\t\t\t\t\t\t\t\t\t\t\twindow.tb.proxy.updateSWs();\n\t\t\t\t\t\t\t\t\t\t\tconst updSet = JSON.stringify(settdata, null, 2);\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${await window.tb.user.username()}/settings.json`, updSet);\n\t\t\t\t\t\t\t\t\t\t\tsetServers(prevServers =>\n\t\t\t\t\t\t\t\t\t\t\t\tprevServers.map(server => ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t...server,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconnected: server.name === server.id,\n\t\t\t\t\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"update-wispsrvs\"));\n\t\t\t\t\t\t\t\t\t\t\tsetUpdating(true);\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex gap-3 items-center pointer-events-none\">\n\t\t\t\t\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t\t\t\t\tfill=\"currentColor\"\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                        size-8\n                                                    `}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M1.371 8.143c5.858-5.857 15.356-5.857 21.213 0a.75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.06 0c-4.98-4.979-13.053-4.979-18.032 0a.75.75 0 0 1-1.06 0l-.53-.53a.75.75 0 0 1 0-1.06Zm3.182 3.182c4.1-4.1 10.749-4.1 14.85 0a.75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.062 0 8.25 8.25 0 0 0-11.667 0 .75.75 0 0 1-1.06 0l-.53-.53a.75.75 0 0 1 0-1.06Zm3.204 3.182a6 6 0 0 1 8.486 0 .75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.061 0 3.75 3.75 0 0 0-5.304 0 .75.75 0 0 1-1.06 0l-.53-.53a.75.75 0 0 1 0-1.06Zm3.182 3.182a1.5 1.5 0 0 1 2.122 0 .75.75 0 0 1 0 1.061l-.53.53a.75.75 0 0 1-1.061 0l-.53-.53a.75.75 0 0 1 0-1.06Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"font-bold leading-tight\">{server.name}</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"font-medium text-xs leading-tight\">{server.id}</p>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-sm p-2 bg-[#ffffff38] rounded-lg leading-none text-[#ffffffce] font-extrabold pointer-events-none\">{server.latency}</p>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex gap-2 w-full justify-center items-center\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"p-3 px-4 rounded-md bg-[#ffffff18] shadow-tb-border-shadow leading-none font-medium hover:bg-[#ffffff28] duration-150 cursor-pointer\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tsetisinDiag(true);\n\t\t\t\t\t\t\twindow.tb.dialog.Message({\n\t\t\t\t\t\t\t\ttitle: \"Enter a name for the Wisp server\",\n\t\t\t\t\t\t\t\tonOk: async (name: string) => {\n\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"wispSrv\", name);\n\t\t\t\t\t\t\t\t\t(window as any).tb.dialog.Message({\n\t\t\t\t\t\t\t\t\t\ttitle: \"Enter the socket URL for the Wisp server\",\n\t\t\t\t\t\t\t\t\t\tonOk: async (url: string) => {\n\t\t\t\t\t\t\t\t\t\t\tconst newServer: Server = { id: url, name };\n\t\t\t\t\t\t\t\t\t\t\tlet data: Server[] = JSON.parse(await window.tb.fs.promises.readFile(\"//apps/system/settings.tapp/wisp-servers.json\"));\n\t\t\t\t\t\t\t\t\t\t\tdata.push(newServer);\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\"//apps/system/settings.tapp/wisp-servers.json\", JSON.stringify(data));\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"update-wispsrvs\"));\n\t\t\t\t\t\t\t\t\t\t\tsetisinDiag(false);\n\t\t\t\t\t\t\t\t\t\t\tsetLoading(true);\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tAdd Network\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "src/sys/gui/WinSwitcher.tsx",
    "content": "import React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport \"./styles/win_switcher.css\";\nimport { useWindowStore } from \"../Store\";\nimport { UserSettings, WindowConfig } from \"../types\";\nimport { domToCanvas } from \"modern-screenshot\";\n\ntype ThumbnailMap = Record<string, string>;\n\nconst titleText = (windowConfig: WindowConfig) => (typeof windowConfig.title === \"string\" ? windowConfig.title : windowConfig.title?.text);\n\nconst getPreviewSourceElement = (wid?: string) => {\n\tif (!wid) return null;\n\tconst windowElement = document.getElementById(wid);\n\tif (!windowElement) return null;\n\tconst mainElement = windowElement.querySelector(\".w-full.h-full\") as HTMLElement | null;\n\tif (!mainElement) return null;\n\tconst iframeElement = mainElement.querySelector(\"iframe\") as HTMLElement | null;\n\treturn iframeElement ?? mainElement;\n};\n\nconst nextFrame = () => new Promise<void>(resolve => requestAnimationFrame(() => resolve()));\n\nconst captureWindowPreview = async (wid?: string, attempt = 0): Promise<string | null> => {\n\tconst sourceEl = getPreviewSourceElement(wid);\n\tif (!sourceEl) return null;\n\tconst rect = sourceEl.getBoundingClientRect();\n\tif (rect.width <= 0 || rect.height <= 0) {\n\t\tif (attempt < 2) {\n\t\t\tawait nextFrame();\n\t\t\treturn captureWindowPreview(wid, attempt + 1);\n\t\t}\n\t\treturn null;\n\t}\n\tconst filter = (node: Node) => {\n\t\tconst tagName = (node as Element).tagName;\n\t\tif (tagName === \"SCRIPT\" || tagName === \"NOSCRIPT\" || tagName === \"IFRAME\") return false;\n\t\treturn true;\n\t};\n\ttry {\n\t\tconst fullCanvas = await domToCanvas(sourceEl, {\n\t\t\tscale: 1,\n\t\t\tbackgroundColor: null,\n\t\t\tfilter,\n\t\t\tfeatures: {\n\t\t\t\trestoreScrollPosition: true,\n\t\t\t},\n\t\t});\n\t\tif (fullCanvas.width <= 0 || fullCanvas.height <= 0) {\n\t\t\tif (attempt < 2) {\n\t\t\t\tawait nextFrame();\n\t\t\t\treturn captureWindowPreview(wid, attempt + 1);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\treturn fullCanvas.toDataURL(\"image/png\");\n\t} catch {\n\t\tif (attempt < 2) {\n\t\t\tawait nextFrame();\n\t\t\treturn captureWindowPreview(wid, attempt + 1);\n\t\t}\n\t\treturn null;\n\t}\n};\n\nconst WinSwitcher: React.FC = () => {\n\tconst [isVisible, setIsVisible] = useState<boolean>(false);\n\tconst [activeIndex, setActiveIndex] = useState<number>(0);\n\tconst [thumbnails, setThumbnails] = useState<ThumbnailMap>({});\n\tconst [thumbnailErrors, setThumbnailErrors] = useState<Set<string>>(new Set());\n\tconst [loadingThumbs, setLoadingThumbs] = useState<Set<string>>(new Set());\n\tconst [windowOptimizationsEnabled, setWindowOptimizationsEnabled] = useState<boolean>(false);\n\tconst isVisibleRef = useRef<boolean>(false);\n\tconst activeIndexRef = useRef<number>(0);\n\tconst windowsRef = useRef<WindowConfig[]>([]);\n\tconst isCapturingRef = useRef<Set<string>>(new Set());\n\tconst triggerModifierRef = useRef<\"alt\" | \"shift\" | null>(null);\n\tconst windows = useWindowStore(state => state.windows);\n\tconst orderedWindows = useMemo(() => {\n\t\treturn [...windows].sort((a, b) => (b.zIndex ?? 0) - (a.zIndex ?? 0));\n\t}, [windows]);\n\tuseEffect(() => {\n\t\tisVisibleRef.current = isVisible;\n\t}, [isVisible]);\n\tuseEffect(() => {\n\t\tactiveIndexRef.current = activeIndex;\n\t}, [activeIndex]);\n\tuseEffect(() => {\n\t\twindowsRef.current = orderedWindows;\n\t\tconst validIds = new Set(orderedWindows.map(win => win.wid).filter(Boolean) as string[]);\n\t\tsetThumbnails(prev => Object.fromEntries(Object.entries(prev).filter(([wid]) => validIds.has(wid))));\n\t\tsetThumbnailErrors(prev => new Set([...prev].filter(wid => validIds.has(wid))));\n\t\tsetLoadingThumbs(prev => new Set([...prev].filter(wid => validIds.has(wid))));\n\t\tisCapturingRef.current = new Set([...isCapturingRef.current].filter(wid => validIds.has(wid)));\n\t\tif (orderedWindows.length === 0) {\n\t\t\tsetIsVisible(false);\n\t\t\tsetActiveIndex(0);\n\t\t}\n\t\tif (activeIndex >= orderedWindows.length) {\n\t\t\tsetActiveIndex(0);\n\t\t}\n\t}, [orderedWindows, activeIndex]);\n\tconst cycleSelection = (direction: number) => {\n\t\tconst currentWindows = windowsRef.current;\n\t\tif (currentWindows.length === 0) return;\n\t\tconst nextIndex = (activeIndexRef.current + direction + currentWindows.length) % currentWindows.length;\n\t\tsetActiveIndex(nextIndex);\n\t};\n\tconst commitSelection = () => {\n\t\tconst currentWindows = windowsRef.current;\n\t\tif (currentWindows.length === 0) {\n\t\t\tsetIsVisible(false);\n\t\t\treturn;\n\t\t}\n\t\tconst win = currentWindows[activeIndexRef.current];\n\t\tif (win?.wid) {\n\t\t\twindow.dispatchEvent(new CustomEvent(\"sel-win\", { detail: win.wid }));\n\t\t\twindow.dispatchEvent(new CustomEvent(\"currWID\", { detail: win.wid }));\n\t\t}\n\t\tsetIsVisible(false);\n\t};\n\tconst commitByWindow = (windowConfig: WindowConfig) => {\n\t\tif (windowConfig?.wid) {\n\t\t\twindow.dispatchEvent(new CustomEvent(\"sel-win\", { detail: windowConfig.wid }));\n\t\t\twindow.dispatchEvent(new CustomEvent(\"currWID\", { detail: windowConfig.wid }));\n\t\t}\n\t\tsetIsVisible(false);\n\t};\n\tconst openSwitcher = (direction: number) => {\n\t\tconst currentWindows = windowsRef.current;\n\t\tif (currentWindows.length === 0) return;\n\t\tconst focusedIndex = currentWindows.findIndex(win => win.focused);\n\t\tconst baseIndex = focusedIndex >= 0 ? focusedIndex : 0;\n\t\tconst nextIndex = (baseIndex + direction + currentWindows.length) % currentWindows.length;\n\t\tsetActiveIndex(nextIndex);\n\t\tsetIsVisible(true);\n\t};\n\tconst captureForWindow = async (windowConfig: WindowConfig) => {\n\t\tif (!windowConfig.wid) return;\n\t\tif (thumbnails[windowConfig.wid] || thumbnailErrors.has(windowConfig.wid) || isCapturingRef.current.has(windowConfig.wid)) return;\n\t\tif (windowOptimizationsEnabled) {\n\t\t\tsetThumbnailErrors(prev => {\n\t\t\t\tconst next = new Set(prev);\n\t\t\t\tnext.add(windowConfig.wid as string);\n\t\t\t\treturn next;\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tisCapturingRef.current.add(windowConfig.wid);\n\t\tsetLoadingThumbs(prev => {\n\t\t\tconst next = new Set(prev);\n\t\t\tnext.add(windowConfig.wid as string);\n\t\t\treturn next;\n\t\t});\n\t\tconst thumbnail = await captureWindowPreview(windowConfig.wid);\n\t\tif (thumbnail) {\n\t\t\tsetThumbnails(prev => ({ ...prev, [windowConfig.wid as string]: thumbnail }));\n\t\t} else {\n\t\t\tsetThumbnailErrors(prev => {\n\t\t\t\tconst next = new Set(prev);\n\t\t\t\tnext.add(windowConfig.wid as string);\n\t\t\t\treturn next;\n\t\t\t});\n\t\t}\n\t\tsetLoadingThumbs(prev => {\n\t\t\tconst next = new Set(prev);\n\t\t\tnext.delete(windowConfig.wid as string);\n\t\t\treturn next;\n\t\t});\n\t\tisCapturingRef.current.delete(windowConfig.wid);\n\t};\n\tuseEffect(() => {\n\t\tconst updateWindowOptimizations = async () => {\n\t\t\ttry {\n\t\t\t\tconst settings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\")) as UserSettings;\n\t\t\t\tsetWindowOptimizationsEnabled(settings.windowOptimizations ?? false);\n\t\t\t} catch {\n\t\t\t\tsetWindowOptimizationsEnabled(false);\n\t\t\t}\n\t\t};\n\t\tupdateWindowOptimizations();\n\t\twindow.addEventListener(\"tfsready\", updateWindowOptimizations);\n\t\twindow.addEventListener(\"load\", updateWindowOptimizations);\n\t\twindow.addEventListener(\"updWallpaper\", updateWindowOptimizations);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"tfsready\", updateWindowOptimizations);\n\t\t\twindow.removeEventListener(\"load\", updateWindowOptimizations);\n\t\t\twindow.removeEventListener(\"updWallpaper\", updateWindowOptimizations);\n\t\t};\n\t}, []);\n\n\tuseEffect(() => {\n\t\tif (!windowOptimizationsEnabled) {\n\t\t\tsetThumbnailErrors(new Set());\n\t\t}\n\t}, [windowOptimizationsEnabled]);\n\n\tuseEffect(() => {\n\t\tconst onDown = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"Tab\" && e.altKey) {\n\t\t\t\te.preventDefault();\n\t\t\t\ttriggerModifierRef.current = \"alt\";\n\t\t\t\tif (!isVisibleRef.current) {\n\t\t\t\t\topenSwitcher(e.shiftKey ? -1 : 1);\n\t\t\t\t} else {\n\t\t\t\t\tcycleSelection(e.shiftKey ? -1 : 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (e.key === \"Tab\" && e.shiftKey && !e.altKey) {\n\t\t\t\te.preventDefault();\n\t\t\t\ttriggerModifierRef.current = \"shift\";\n\t\t\t\tif (!isVisibleRef.current) {\n\t\t\t\t\topenSwitcher(-1);\n\t\t\t\t} else {\n\t\t\t\t\tcycleSelection(-1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isVisibleRef.current && e.key === \"Escape\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tsetIsVisible(false);\n\t\t\t\ttriggerModifierRef.current = null;\n\t\t\t}\n\t\t};\n\t\tconst onUp = (e: KeyboardEvent) => {\n\t\t\tif (e.key === \"Alt\" && isVisibleRef.current && triggerModifierRef.current === \"alt\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tcommitSelection();\n\t\t\t\ttriggerModifierRef.current = null;\n\t\t\t}\n\t\t\tif (e.key === \"Shift\" && isVisibleRef.current && triggerModifierRef.current === \"shift\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tcommitSelection();\n\t\t\t\ttriggerModifierRef.current = null;\n\t\t\t}\n\t\t};\n\t\tconst onBlur = () => {\n\t\t\tif (isVisibleRef.current) {\n\t\t\t\tsetIsVisible(false);\n\t\t\t\ttriggerModifierRef.current = null;\n\t\t\t}\n\t\t};\n\t\twindow.addEventListener(\"keydown\", onDown);\n\t\twindow.addEventListener(\"keyup\", onUp);\n\t\twindow.addEventListener(\"blur\", onBlur);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"keydown\", onDown);\n\t\t\twindow.removeEventListener(\"keyup\", onUp);\n\t\t\twindow.removeEventListener(\"blur\", onBlur);\n\t\t};\n\t}, []);\n\tuseEffect(() => {\n\t\tif (!isVisible) return;\n\t\tif (windowOptimizationsEnabled) return;\n\t\torderedWindows.forEach(windowConfig => {\n\t\t\tcaptureForWindow(windowConfig);\n\t\t});\n\t}, [isVisible, orderedWindows, windowOptimizationsEnabled]);\n\tif (orderedWindows.length === 0) return null;\n\treturn (\n\t\t<div className={`win-switcher-backdrop ${isVisible ? \"visible\" : \"\"}`}>\n\t\t\t<div className={`win-switcher-panel ${isVisible ? \"visible\" : \"\"}`}>\n\t\t\t\t{orderedWindows.map((windowConfig: WindowConfig, index: number) => {\n\t\t\t\t\tconst text = titleText(windowConfig);\n\t\t\t\t\tconst thumbSrc = windowConfig.wid ? thumbnails[windowConfig.wid] : null;\n\t\t\t\t\tconst showFallback = windowOptimizationsEnabled || (windowConfig.wid && thumbnailErrors.has(windowConfig.wid)) || !thumbSrc;\n\t\t\t\t\tconst isLoading = !!windowConfig.wid && loadingThumbs.has(windowConfig.wid);\n\t\t\t\t\tconst spinnerId = `a12-switch-${windowConfig.wid ?? index}`;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tkey={windowConfig.wid}\n\t\t\t\t\t\t\tdata-index={index}\n\t\t\t\t\t\t\tclassName={`win-switcher-item ${index === activeIndex ? \"active\" : \"\"}`}\n\t\t\t\t\t\t\tonMouseEnter={() => setActiveIndex(index)}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsetActiveIndex(index);\n\t\t\t\t\t\t\t\tcommitByWindow(windowConfig);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"thumb-frame\">\n\t\t\t\t\t\t\t\t{showFallback ? (\n\t\t\t\t\t\t\t\t\t<div className=\"thumb-fallback\">\n\t\t\t\t\t\t\t\t\t\t<img src={windowConfig.icon ?? \"/assets/img/null.svg\"} alt={text} className=\"thumb-fallback-icon\" />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<img src={thumbSrc} alt={text} className=\"thumb-image\" />\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isLoading && (\n\t\t\t\t\t\t\t\t\t<div className=\"thumb-loading-overlay\">\n\t\t\t\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 200 200\" className=\"thumb-loading-spinner\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t<defs>\n\t\t\t\t\t\t\t\t\t\t\t\t<radialGradient id={spinnerId} cx=\".66\" fx=\".66\" cy=\".3125\" fy=\".3125\" gradientTransform=\"scale(1.5)\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\"0\" stopColor=\"#32ae62\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\".3\" stopColor=\"#32ae62\" stopOpacity=\".9\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\".6\" stopColor=\"#32ae62\" stopOpacity=\".6\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\".8\" stopColor=\"#32ae62\" stopOpacity=\".3\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<stop offset=\"1\" stopColor=\"#32ae62\" stopOpacity=\"0\"></stop>\n\t\t\t\t\t\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t\t\t\t\t</defs>\n\t\t\t\t\t\t\t\t\t\t\t<circle style={{ transformOrigin: \"center\" }} fill=\"none\" stroke={`url(#${spinnerId})`} strokeWidth=\"15\" strokeLinecap=\"round\" strokeDasharray=\"200 1000\" strokeDashoffset=\"0\" cx=\"100\" cy=\"100\" r=\"70\">\n\t\t\t\t\t\t\t\t\t\t\t\t<animateTransform type=\"rotate\" attributeName=\"transform\" calcMode=\"spline\" dur=\"2s\" values=\"360;0\" keyTimes=\"0;1\" keySplines=\"0 0 1 1\" repeatCount=\"indefinite\"></animateTransform>\n\t\t\t\t\t\t\t\t\t\t\t</circle>\n\t\t\t\t\t\t\t\t\t\t\t<circle style={{ transformOrigin: \"center\" }} fill=\"none\" opacity=\".2\" stroke=\"#32ae62\" strokeWidth=\"15\" strokeLinecap=\"round\" cx=\"100\" cy=\"100\" r=\"70\"></circle>\n\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"item-meta\">\n\t\t\t\t\t\t\t\t<img src={windowConfig.icon ?? \"/assets/img/null.svg\"} alt=\"\" aria-hidden=\"true\" className=\"item-icon\" />\n\t\t\t\t\t\t\t\t<span className=\"item-title\">{text}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n};\n\nexport default WinSwitcher;\n"
  },
  {
    "path": "src/sys/gui/WindowArea.tsx",
    "content": "import { useState, useRef, useEffect, memo } from \"react\";\nimport { fileExists, UserSettings, WindowConfig } from \"../types\";\nimport { clearInfo, updateInfo } from \"./AppIsland\";\nimport { useWindowStore } from \"../Store\";\n\ninterface WindowProps {\n\tconfig: WindowConfig;\n\tclassName?: string;\n\tchildren?: React.ReactNode;\n\tonSnapPreview?: (pos: string) => void;\n\tonSnapDone?: () => void;\n}\n\ninterface DesktopItem {\n\tname: string;\n\ticon: string;\n\tposition: {\n\t\tcustom: boolean;\n\t\tleft: number | string;\n\t\ttop: number | string;\n\t};\n\titem: string;\n\ttype: string;\n\tconfig: WindowConfig;\n}\n\nconst WindowElement: React.FC<WindowProps> = ({ className, config, onSnapDone, onSnapPreview }) => {\n\tconst windowStore = useWindowStore();\n\tconst [optimizationsEnabled, setOptimizationsEnabled] = useState(true);\n\n\tconst windowRef = useRef<HTMLDivElement>(null);\n\tconst regionRef = useRef<HTMLDivElement>(null);\n\tconst focuserRef = useRef<HTMLDivElement>(null);\n\tconst srcRef = useRef<HTMLIFrameElement>(null);\n\tconst miniRef = useRef<SVGSVGElement>(null);\n\tconst minMaxRef = useRef<SVGSVGElement>(null);\n\tconst closeRef = useRef<SVGSVGElement>(null);\n\n\tconst contentRef = useRef<HTMLDivElement>(null);\n\tconst titleRef = useRef<HTMLSpanElement>(null);\n\tconst thtmlref = useRef<HTMLDivElement>(null);\n\n\tconst [zIndex, setZIndex] = useState(config.zIndex);\n\tconst [isMouseDown, setIsMouseDown] = useState(false);\n\tconst [isDragging, setIsDragging] = useState(false);\n\tconst [x, setX] = useState<number | string>(\"center\");\n\tconst [y, setY] = useState<number | string>(\"center\");\n\tconst [width, setWidth] = useState(config.size?.width || 400);\n\tconst [height, setHeight] = useState(config.size?.height || 400);\n\tconst [titlebarhtml] = useState(typeof config.title === \"object\" ? config.title?.html : undefined);\n\tconst [maximized, setMaximized] = useState(false);\n\tconst [minimized, setMinimized] = useState(false);\n\tconst [title] = useState(typeof config.title === \"string\" ? config.title : config.title?.text);\n\tconst [snapRegion, setSnapRegion] = useState<string | null>(null);\n\tconst [isResizing, setIsResizing] = useState<boolean>(false);\n\tconst [controls, setControls] = useState(config.controls);\n\tconst [src, setSrc] = useState(config.src);\n\tconst originalSize = useRef<{ width: number; height: number } | null>(null);\n\tconst [isSnapped, setIsSnapped] = useState(false);\n\tconst [accent, setAccent] = useState<string>(\"#ffffff18\");\n\tconst [isFullscreen, setIsFullscreen] = useState<boolean>(false);\n\tconst mobileCheck = async () => {\n\t\tconst platform = await window.tb.platform.getPlatform();\n\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\tif (platform === \"mobile\" || settings.window.alwaysMaximized === true) {\n\t\t\tsetMaximized(true);\n\t\t}\n\t};\n\tmobileCheck();\n\n\tuseEffect(() => {\n\t\tconst loadOptimizationSettings = async () => {\n\t\t\ttry {\n\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\t\t\tsetOptimizationsEnabled(settings.windowOptimizations ?? true);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"Failed to load optimization settings:\", err);\n\t\t\t\tsetOptimizationsEnabled(true); // Default to enabled\n\t\t\t}\n\t\t};\n\t\tloadOptimizationSettings();\n\t}, []);\n\tuseEffect(() => {\n\t\tupdateInfo({ appname: typeof config.title === \"string\" ? config.title : config.title?.text });\n\t}, [config]);\n\tuseEffect(() => {\n\t\tif (windowRef.current) {\n\t\t\tif (x === \"center\") {\n\t\t\t\tsetX(window.innerWidth / 2 - windowRef.current.offsetWidth / 2);\n\t\t\t}\n\t\t\tif (y === \"center\") {\n\t\t\t\tsetY(window.innerHeight / 2 - windowRef.current.offsetHeight / 2);\n\t\t\t}\n\t\t\twindowRef.current.classList.remove(\"opacity-0\", \"translate-y-3\");\n\t\t\tsetTimeout(() => {\n\t\t\t\twindowRef.current?.classList.remove(\"duration-150\");\n\t\t\t}, 150);\n\t\t}\n\t\tif (thtmlref.current && titlebarhtml) {\n\t\t\tthtmlref.current.innerHTML = titlebarhtml;\n\t\t}\n\t\tconst prox = async () => {\n\t\t\tif (config.proxy === true) {\n\t\t\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\t\t\tsetSrc(\"about:blank\");\n\t\t\t\tconsole.log(settings.proxy);\n\t\t\t\tif (settings.proxy === \"Ultraviolet\") {\n\t\t\t\t\tsetSrc(`${window.location.origin}/uv/service/${await window.tb.proxy.encode(config.src, \"XOR\")}`);\n\t\t\t\t} else {\n\t\t\t\t\tsetSrc(`${window.location.origin}/service/${await window.tb.proxy.encode(config.src, \"XOR\")}`);\n\t\t\t\t}\n\t\t\t\tconst instanceWin = (await window.anura.wm.getWeakRef(Number(config.pid))) || {};\n\t\t\t\tObject.assign(srcRef.current?.contentWindow as typeof window, {\n\t\t\t\t\ttb: window.parent.tb,\n\t\t\t\t\tanura: window.parent.anura,\n\t\t\t\t\tAliceWM: window.parent.AliceWM,\n\t\t\t\t\tLocalFS: window.parent.LocalFS,\n\t\t\t\t\tExternalApp: window.parent.ExternalApp,\n\t\t\t\t\tExternalLib: window.parent.ExternalLib,\n\t\t\t\t\tFiler: window.parent.Filer,\n\t\t\t\t\tinstanceWindow: instanceWin,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconsole.log(await window.anura.wm.getWeakRef(Number(config.pid)));\n\t\t\t\tconst instanceWin = (await window.anura.wm.getWeakRef(Number(config.pid))) || {};\n\t\t\t\tObject.assign(srcRef.current?.contentWindow as typeof window, {\n\t\t\t\t\ttb: window.parent.tb,\n\t\t\t\t\tanura: window.parent.anura,\n\t\t\t\t\tAliceWM: window.parent.AliceWM,\n\t\t\t\t\tLocalFS: window.parent.LocalFS,\n\t\t\t\t\tExternalApp: window.parent.ExternalApp,\n\t\t\t\t\tExternalLib: window.parent.ExternalLib,\n\t\t\t\t\tFiler: window.parent.Filer,\n\t\t\t\t\tinstanceWindow: instanceWin,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\tprox();\n\t}, [srcRef, src]);\n\tuseEffect(() => {\n\t\tconst reload = (e: CustomEvent) => {\n\t\t\tif (e.detail === config.pid) {\n\t\t\t\tif (srcRef.current?.contentWindow) {\n\t\t\t\t\tsrcRef.current.contentWindow.location.reload();\n\t\t\t\t\tsrcRef.current.onload = () => {\n\t\t\t\t\t\tObject.assign(srcRef.current?.contentWindow!, {\n\t\t\t\t\t\t\ttb: window.parent.tb,\n\t\t\t\t\t\t\tanura: window.parent.anura,\n\t\t\t\t\t\t\tAliceWM: window.parent.AliceWM,\n\t\t\t\t\t\t\tLocalFS: window.parent.LocalFS,\n\t\t\t\t\t\t\tExternalApp: window.parent.ExternalApp,\n\t\t\t\t\t\t\tExternalLib: window.parent.ExternalLib,\n\t\t\t\t\t\t\tFiler: window.parent.Filer,\n\t\t\t\t\t\t\tinstanceWindow: window.anura.wm.getWeakRef(Number(config.pid)) || {},\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tconst max = (e: CustomEvent) => {\n\t\t\tif (e.detail === config.pid) {\n\t\t\t\tsetMaximized(true);\n\t\t\t\twindowStore.arrange(config.wid);\n\t\t\t}\n\t\t};\n\t\tconst min = (e: CustomEvent) => {\n\t\t\tif (e.detail === config.pid) {\n\t\t\t\tsetMinimized(true);\n\t\t\t}\n\t\t};\n\t\tconst returnCont = (e: CustomEvent) => {\n\t\t\tif (e.detail === config.pid) {\n\t\t\t\twindow.dispatchEvent(new CustomEvent(\"curr-win-content\", { detail: contentRef.current }));\n\t\t\t}\n\t\t};\n\t\tconst setCont = (e: CustomEvent) => {\n\t\t\tconst msg = JSON.parse(e.detail);\n\t\t\tif (msg.currWin === config.pid) {\n\t\t\t\tif (contentRef.current) {\n\t\t\t\t\tcontentRef.current.innerHTML = msg.content;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tconst setBC = (e: CustomEvent) => {\n\t\t\tconst msg = JSON.parse(e.detail);\n\t\t\tif (msg.currWin === config.pid) {\n\t\t\t\tif (titleRef.current) {\n\t\t\t\t\ttitleRef.current.style.color = msg.color;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tconst setBG = (e: CustomEvent) => {\n\t\t\tconst msg = JSON.parse(e.detail);\n\t\t\tif (msg.currWin === config.pid) {\n\t\t\t\tif (titleRef.current) {\n\t\t\t\t\ttitleRef.current.style.backgroundColor = msg.color;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tconst settxt = (e: CustomEvent) => {\n\t\t\tconst msg = JSON.parse(e.detail);\n\t\t\tif (msg.currWin === config.pid) {\n\t\t\t\tif (titleRef.current) {\n\t\t\t\t\ttitleRef.current.innerText = msg.txt;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tconst selWin = (e: CustomEvent) => {\n\t\t\tif (e.detail === config.wid) {\n\t\t\t\twindowStore.arrange(config.wid);\n\t\t\t\t// @ts-ignore\n\t\t\t\tsetZIndex(windowStore.getWindow(config.wid)?.zIndex);\n\t\t\t\tsetMinimized(false);\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\twindowRef.current?.classList.remove(\"duration-150\");\n\t\t\t\t}, 150);\n\t\t\t\tif (focuserRef.current) focuserRef.current.click();\n\t\t\t\tupdateInfo({ appname: typeof config.title === \"string\" ? config.title : config.title?.text });\n\t\t\t}\n\t\t};\n\t\tconst debugCTX = (e: MouseEvent) => {\n\t\t\tconst rect = (e.target as HTMLElement).getBoundingClientRect();\n\t\t\twindow.tb.contextmenu.create({\n\t\t\t\tx: rect.left + 100,\n\t\t\t\ty: rect.top + 40,\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttext: \"Minimize\",\n\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\tsetMinimized(true);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttext: \"Maximize\",\n\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\tsetMaximized(true);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttext: \"Reload\",\n\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\tif (srcRef.current?.contentWindow) {\n\t\t\t\t\t\t\t\tsrcRef.current.contentWindow.location.reload();\n\t\t\t\t\t\t\t\tObject.assign(srcRef.current?.contentWindow!, {\n\t\t\t\t\t\t\t\t\ttb: window.parent.tb,\n\t\t\t\t\t\t\t\t\tanura: window.parent.anura,\n\t\t\t\t\t\t\t\t\tAliceWM: window.parent.AliceWM,\n\t\t\t\t\t\t\t\t\tLocalFS: window.parent.LocalFS,\n\t\t\t\t\t\t\t\t\tExternalApp: window.parent.ExternalApp,\n\t\t\t\t\t\t\t\t\tExternalLib: window.parent.ExternalLib,\n\t\t\t\t\t\t\t\t\tFiler: window.parent.Filer,\n\t\t\t\t\t\t\t\t\tinstanceWindow: window.anura.wm.getWeakRef(Number(config.pid)) || {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttext: \"Close\",\n\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\twindowStore.removeWindow(config.wid);\n\t\t\t\t\t\t\tclearInfo();\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t};\n\t\tconst changeURL = (e: CustomEvent) => {\n\t\t\tconst det = JSON.parse(e.detail);\n\t\t\tif (det.pid === config.pid) {\n\t\t\t\tif (srcRef.current?.contentWindow) {\n\t\t\t\t\tsetSrc(det.url);\n\t\t\t\t\tsrcRef.current.onload = () => {\n\t\t\t\t\t\tObject.assign(srcRef.current?.contentWindow!, {\n\t\t\t\t\t\t\ttb: window.parent.tb,\n\t\t\t\t\t\t\tanura: window.parent.anura,\n\t\t\t\t\t\t\tAliceWM: window.parent.AliceWM,\n\t\t\t\t\t\t\tLocalFS: window.parent.LocalFS,\n\t\t\t\t\t\t\tExternalApp: window.parent.ExternalApp,\n\t\t\t\t\t\t\tExternalLib: window.parent.ExternalLib,\n\t\t\t\t\t\t\tFiler: window.parent.Filer,\n\t\t\t\t\t\t\tinstanceWindow: window.anura.wm.getWeakRef(Number(config.pid)) || {},\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tconst minall: any = () => {\n\t\t\tif (!minimized) setMinimized(true);\n\t\t};\n\n\t\tconst updAccent = async () => {\n\t\t\tconst settings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\")) as UserSettings;\n\t\t\tsetAccent(`${settings.window.winAccent}${settings.window.blurlevel}`);\n\t\t\tsetIsFullscreen(settings.window.alwaysFullscreen);\n\t\t};\n\t\tupdAccent();\n\n\t\twindow.addEventListener(\"reload-win\", reload as EventListener);\n\t\twindow.addEventListener(\"max-win\", max as EventListener);\n\t\twindow.addEventListener(\"min-win\", min as EventListener);\n\t\twindow.addEventListener(\"get-content\", returnCont as EventListener);\n\t\twindow.addEventListener(\"upd-wincont\", setCont as EventListener);\n\t\twindow.addEventListener(\"upd-winbarcol\", setBC as EventListener);\n\t\twindow.addEventListener(\"upd-winbartxt\", settxt as EventListener);\n\t\twindow.addEventListener(\"upd-winbarbg\", setBG as EventListener);\n\t\twindow.addEventListener(\"upd-src\", changeURL as EventListener);\n\t\twindow.addEventListener(\"sel-win\", selWin as EventListener);\n\t\twindow.addEventListener(\"min-wins\", minall);\n\t\twindow.addEventListener(\"upd-accent\", updAccent);\n\t\tif (regionRef.current) regionRef.current.addEventListener(\"contextmenu\", debugCTX);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"reload-win\", reload as EventListener);\n\t\t\twindow.removeEventListener(\"max-win\", max as EventListener);\n\t\t\twindow.removeEventListener(\"min-win\", min as EventListener);\n\t\t\twindow.removeEventListener(\"get-content\", returnCont as EventListener);\n\t\t\twindow.removeEventListener(\"upd-wincont\", setCont as EventListener);\n\t\t\twindow.removeEventListener(\"upd-winbarcol\", setBC as EventListener);\n\t\t\twindow.removeEventListener(\"upd-winbartxt\", settxt as EventListener);\n\t\t\twindow.removeEventListener(\"upd-winbarbg\", setBG as EventListener);\n\t\t\twindow.removeEventListener(\"upd-src\", changeURL as EventListener);\n\t\t\twindow.removeEventListener(\"sel-win\", selWin as EventListener);\n\t\t\twindow.removeEventListener(\"min-wins\", minall);\n\t\t\twindow.removeEventListener(\"upd-accent\", updAccent);\n\t\t\tif (regionRef.current) regionRef.current.removeEventListener(\"contextmenu\", debugCTX);\n\t\t};\n\t}, []);\n\n\tconst handleSnap = (newX: number, newY: number) => {\n\t\tif (config.snapable !== false) {\n\t\t\tif (!windowRef.current) return;\n\t\t\toriginalSize.current = { width, height };\n\t\t\tconst windowWidth = windowRef.current.offsetWidth;\n\t\t\tconst windowHeight = windowRef.current.offsetHeight;\n\t\t\tconst SNAP_THRESHOLD = 7;\n\t\t\tconst CORNER_THRESHOLD = 40;\n\t\t\tconst atLeft = newX <= SNAP_THRESHOLD;\n\t\t\tconst atRight = newX + windowWidth >= window.innerWidth - SNAP_THRESHOLD;\n\t\t\tconst atTop = newY <= SNAP_THRESHOLD;\n\t\t\tconst atBottom = newY + windowHeight >= window.innerHeight - SNAP_THRESHOLD;\n\t\t\tif (atLeft && atTop && newX <= CORNER_THRESHOLD && newY <= CORNER_THRESHOLD) {\n\t\t\t\tsetX(0);\n\t\t\t\tsetY(0);\n\t\t\t\tsetSnapRegion(\"top-left\");\n\t\t\t\tonSnapPreview?.(\"top-left\");\n\t\t\t} else if (atRight && atTop && newX + windowWidth >= window.innerWidth - CORNER_THRESHOLD && newY <= CORNER_THRESHOLD) {\n\t\t\t\tsetX(window.innerWidth - windowWidth);\n\t\t\t\tsetY(0);\n\t\t\t\tsetSnapRegion(\"top-right\");\n\t\t\t\tonSnapPreview?.(\"top-right\");\n\t\t\t} else if (atLeft && atBottom && newX <= CORNER_THRESHOLD && newY + windowHeight >= window.innerHeight - CORNER_THRESHOLD) {\n\t\t\t\tsetX(0);\n\t\t\t\tsetY(window.innerHeight - windowHeight);\n\t\t\t\tsetSnapRegion(\"bottom-left\");\n\t\t\t\tonSnapPreview?.(\"bottom-left\");\n\t\t\t} else if (atRight && atBottom && newX + windowWidth >= window.innerWidth - CORNER_THRESHOLD && newY + windowHeight >= window.innerHeight - CORNER_THRESHOLD) {\n\t\t\t\tsetX(window.innerWidth - windowWidth);\n\t\t\t\tsetY(window.innerHeight - windowHeight);\n\t\t\t\tsetSnapRegion(\"bottom-right\");\n\t\t\t\tonSnapPreview?.(\"bottom-right\");\n\t\t\t} else if (atLeft) {\n\t\t\t\tsetX(0);\n\t\t\t\tsetSnapRegion(\"left\");\n\t\t\t\tonSnapPreview?.(\"left\");\n\t\t\t} else if (atRight) {\n\t\t\t\tsetX(window.innerWidth - windowWidth);\n\t\t\t\tsetSnapRegion(\"right\");\n\t\t\t\tonSnapPreview?.(\"right\");\n\t\t\t} else if (atTop) {\n\t\t\t\tsetY(0);\n\t\t\t\tsetSnapRegion(\"top\");\n\t\t\t\tonSnapPreview?.(\"top\");\n\t\t\t} else {\n\t\t\t\tif (snapRegion && originalSize.current) {\n\t\t\t\t\tsetWidth(originalSize.current.width);\n\t\t\t\t\tsetHeight(originalSize.current.height);\n\t\t\t\t}\n\t\t\t\tsetSnapRegion(null);\n\t\t\t\tonSnapDone?.();\n\t\t\t}\n\t\t}\n\t};\n\tuseEffect(() => {\n\t\tif (isDragging && isSnapped && originalSize.current && windowRef.current) {\n\t\t\twindowRef.current.style.width = `${originalSize.current.width}px`;\n\t\t\twindowRef.current.style.height = `${originalSize.current.height}px`;\n\t\t\tsetIsSnapped(false);\n\t\t\tsetSnapRegion(null);\n\t\t\tonSnapDone?.();\n\t\t}\n\t}, [isDragging, isSnapped]);\n\n\t// Disable pointer events on all iframes when dragging\n\tuseEffect(() => {\n\t\tif (isDragging || isResizing) {\n\t\t\tconst iframes = document.querySelectorAll(\"iframe\");\n\t\t\tiframes.forEach(iframe => {\n\t\t\t\tiframe.style.pointerEvents = \"none\";\n\t\t\t});\n\t\t} else {\n\t\t\tconst iframes = document.querySelectorAll(\"iframe\");\n\t\t\tiframes.forEach(iframe => {\n\t\t\t\tiframe.style.pointerEvents = \"auto\";\n\t\t\t});\n\t\t}\n\t}, [isDragging, isResizing]);\n\n\tuseEffect(() => {\n\t\tconst snap = () => {\n\t\t\tsetIsMouseDown(false);\n\t\t\tsetIsDragging(false);\n\t\t\tif (windowRef.current) {\n\t\t\t\tif (snapRegion === \"left\") {\n\t\t\t\t\twindowRef.current.style.left = \"0\";\n\t\t\t\t\twindowRef.current.style.width = \"50%\";\n\t\t\t\t\twindowRef.current.style.height = \"100%\";\n\t\t\t\t\twindowRef.current.style.top = \"0\";\n\t\t\t\t\tsetIsSnapped(true);\n\t\t\t\t} else if (snapRegion === \"right\") {\n\t\t\t\t\twindowRef.current.style.left = \"50%\";\n\t\t\t\t\twindowRef.current.style.width = \"50%\";\n\t\t\t\t\twindowRef.current.style.height = \"100%\";\n\t\t\t\t\twindowRef.current.style.top = \"0\";\n\t\t\t\t\tsetIsSnapped(true);\n\t\t\t\t} else if (snapRegion === \"top\") {\n\t\t\t\t\tsetMaximized(true);\n\t\t\t\t\tsetIsSnapped(true);\n\t\t\t\t} else if (snapRegion === \"top-left\") {\n\t\t\t\t\twindowRef.current.style.left = \"0\";\n\t\t\t\t\twindowRef.current.style.top = \"0\";\n\t\t\t\t\twindowRef.current.style.width = \"50%\";\n\t\t\t\t\twindowRef.current.style.height = \"50%\";\n\t\t\t\t\tsetIsSnapped(true);\n\t\t\t\t} else if (snapRegion === \"top-right\") {\n\t\t\t\t\twindowRef.current.style.left = \"50%\";\n\t\t\t\t\twindowRef.current.style.top = \"0\";\n\t\t\t\t\twindowRef.current.style.width = \"50%\";\n\t\t\t\t\twindowRef.current.style.height = \"50%\";\n\t\t\t\t\tsetIsSnapped(true);\n\t\t\t\t} else if (snapRegion === \"bottom-left\") {\n\t\t\t\t\twindowRef.current.style.left = \"0\";\n\t\t\t\t\twindowRef.current.style.top = \"50%\";\n\t\t\t\t\twindowRef.current.style.width = \"50%\";\n\t\t\t\t\twindowRef.current.style.height = \"50%\";\n\t\t\t\t\tsetIsSnapped(true);\n\t\t\t\t} else if (snapRegion === \"bottom-right\") {\n\t\t\t\t\twindowRef.current.style.left = \"50%\";\n\t\t\t\t\twindowRef.current.style.top = \"50%\";\n\t\t\t\t\twindowRef.current.style.width = \"50%\";\n\t\t\t\t\twindowRef.current.style.height = \"50%\";\n\t\t\t\t\tsetIsSnapped(true);\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetSnapRegion(null);\n\t\t\tonSnapDone?.();\n\t\t};\n\t\twindow.addEventListener(\"mouseup\", snap);\n\t\treturn () => window.removeEventListener(\"mouseup\", snap);\n\t}, [snapRegion, isDragging, maximized, isResizing]);\n\n\tconst handleMouseDown = (direction: \"top\" | \"left\" | \"right\" | \"bottom\" | \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\") => {\n\t\tlet animationFrameId: number | null = null;\n\t\tlet lastMouseEvent: MouseEvent | null = null;\n\n\t\tconst onMove = (e: MouseEvent) => {\n\t\t\tif (!optimizationsEnabled) {\n\t\t\t\t// Without optimizations: update immediately\n\t\t\t\tsetIsResizing(true);\n\t\t\t\tsetMaximized(false);\n\t\t\t\twindowRef.current!.style.transform = \"\";\n\n\t\t\t\tif (direction.includes(\"top\")) {\n\t\t\t\t\tconst offsetY = e.clientY - 65;\n\t\t\t\t\tconst newY = Math.max(offsetY, 0);\n\t\t\t\t\tconst newHeight = height + (typeof y === \"number\" ? y - newY : 0);\n\t\t\t\t\tif (newHeight >= (config.size?.minHeight ?? 224)) {\n\t\t\t\t\t\tsetHeight(newHeight);\n\t\t\t\t\t\tsetY(newY);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (direction.includes(\"left\")) {\n\t\t\t\t\tconst offsetX = e.clientX - 10;\n\t\t\t\t\tconst newX = Math.max(offsetX, 0);\n\t\t\t\t\tconst newWidth = width + (typeof x === \"number\" ? x - newX : 0);\n\t\t\t\t\tif (newWidth >= (config.size?.minWidth ?? 224)) {\n\t\t\t\t\t\tsetWidth(newWidth);\n\t\t\t\t\t\tsetX(newX);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (direction.includes(\"right\")) {\n\t\t\t\t\tconst offsetX = e.clientX - 5;\n\t\t\t\t\tconst newX = typeof x === \"number\" ? x : 0;\n\t\t\t\t\tconst newWidth = offsetX - newX;\n\t\t\t\t\tif (newWidth >= (config.size?.minWidth ?? 224)) {\n\t\t\t\t\t\tsetWidth(newWidth);\n\t\t\t\t\t\tsetX(newX);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (direction.includes(\"bottom\")) {\n\t\t\t\t\tconst offsetY = e.clientY - 55;\n\t\t\t\t\tconst newY = typeof y === \"number\" ? y : 0;\n\t\t\t\t\tconst newHeight = offsetY - newY;\n\t\t\t\t\tif (newHeight >= (config.size?.minHeight ?? 224)) {\n\t\t\t\t\t\tsetHeight(newHeight);\n\t\t\t\t\t\tsetY(newY);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// With optimizations: use requestAnimationFrame\n\t\t\tlastMouseEvent = e;\n\n\t\t\tif (!animationFrameId) {\n\t\t\t\tanimationFrameId = requestAnimationFrame(() => {\n\t\t\t\t\tif (!lastMouseEvent) return;\n\t\t\t\t\tconst e = lastMouseEvent;\n\n\t\t\t\t\tsetIsResizing(true);\n\t\t\t\t\tsetMaximized(false);\n\t\t\t\t\twindowRef.current!.style.transform = \"\";\n\n\t\t\t\t\tif (direction.includes(\"top\")) {\n\t\t\t\t\t\tconst offsetY = e.clientY - 65;\n\t\t\t\t\t\tconst newY = Math.max(offsetY, 0);\n\t\t\t\t\t\tconst newHeight = height + (typeof y === \"number\" ? y - newY : 0);\n\t\t\t\t\t\tif (newHeight >= (config.size?.minHeight ?? 224)) {\n\t\t\t\t\t\t\tsetHeight(newHeight);\n\t\t\t\t\t\t\tsetY(newY);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (direction.includes(\"left\")) {\n\t\t\t\t\t\tconst offsetX = e.clientX - 10;\n\t\t\t\t\t\tconst newX = Math.max(offsetX, 0);\n\t\t\t\t\t\tconst newWidth = width + (typeof x === \"number\" ? x - newX : 0);\n\t\t\t\t\t\tif (newWidth >= (config.size?.minWidth ?? 224)) {\n\t\t\t\t\t\t\tsetWidth(newWidth);\n\t\t\t\t\t\t\tsetX(newX);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (direction.includes(\"right\")) {\n\t\t\t\t\t\tconst offsetX = e.clientX - 5;\n\t\t\t\t\t\tconst newX = typeof x === \"number\" ? x : 0;\n\t\t\t\t\t\tconst newWidth = offsetX - newX;\n\t\t\t\t\t\tif (newWidth >= (config.size?.minWidth ?? 224)) {\n\t\t\t\t\t\t\tsetWidth(newWidth);\n\t\t\t\t\t\t\tsetX(newX);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (direction.includes(\"bottom\")) {\n\t\t\t\t\t\tconst offsetY = e.clientY - 55;\n\t\t\t\t\t\tconst newY = typeof y === \"number\" ? y : 0;\n\t\t\t\t\t\tconst newHeight = offsetY - newY;\n\t\t\t\t\t\tif (newHeight >= (config.size?.minHeight ?? 224)) {\n\t\t\t\t\t\t\tsetHeight(newHeight);\n\t\t\t\t\t\t\tsetY(newY);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tanimationFrameId = null;\n\t\t\t\t});\n\t\t\t}\n\t\t\toriginalSize.current = { width, height };\n\t\t};\n\n\t\tconst onUp = () => {\n\t\t\tif (animationFrameId) {\n\t\t\t\tcancelAnimationFrame(animationFrameId);\n\t\t\t\tanimationFrameId = null;\n\t\t\t}\n\t\t\twindow.removeEventListener(\"mousemove\", onMove);\n\t\t\twindow.removeEventListener(\"mouseup\", onUp);\n\t\t\twindow.removeEventListener(\"blur\", onUp);\n\t\t\tsetIsMouseDown(false);\n\t\t\tsetIsResizing(false);\n\t\t};\n\n\t\twindow.onmouseleave = () => {\n\t\t\tsetIsDragging(false);\n\t\t\tsetIsMouseDown(false);\n\t\t\twindow.removeEventListener(\"mousemove\", onMove);\n\t\t\twindow.removeEventListener(\"mouseup\", onUp);\n\t\t\twindow.removeEventListener(\"blur\", onUp);\n\t\t};\n\n\t\twindow.addEventListener(\"mousemove\", onMove);\n\t\twindow.addEventListener(\"mouseup\", onUp);\n\t\twindow.addEventListener(\"blur\", onUp);\n\t\tsetIsMouseDown(true);\n\t};\n\n\treturn (\n\t\t<div\n\t\t\tref={windowRef}\n\t\t\tid={config.wid}\n\t\t\t// @ts-ignore\n\t\t\tpid={config.pid}\n\t\t\tclassName={`\n            window-element\n            ${className ? className : \"\"}\n            absolute\n            bg-[${accent}]\n            rounded-lg shadow-window-shadow overflow-hidden\n            ${minimized ? \"translate-y-3 opacity-0 duration-150 hidden\" : \" translate-y-0 opacity-100\"}\n            ${maximized ? `left-0 right-0 top-0 bottom-0 opacity-100 w-full ${isFullscreen ? \"h-[106%]\" : \"h-full\"}` : `w-[${width}px] h-[${height}px]`}\n        `}\n\t\t\tstyle={{\n\t\t\t\tleft: maximized ? \"\" : x,\n\t\t\t\ttop: maximized ? \"\" : y,\n\t\t\t\theight: maximized ? undefined : height,\n\t\t\t\twidth: maximized ? undefined : width,\n\t\t\t\tzIndex: minimized ? 2 : zIndex,\n\t\t\t\t...(optimizationsEnabled && {\n\t\t\t\t\tcontain: \"layout style paint\",\n\t\t\t\t\twillChange: isDragging || isResizing ? \"transform, left, top\" : \"auto\",\n\t\t\t\t}),\n\t\t\t\tbackgroundColor: accent,\n\t\t\t}}\n\t\t\tonMouseDown={() => {\n\t\t\t\tupdateInfo({ appname: typeof config.title === \"string\" ? config.title : config.title?.text });\n\t\t\t}}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName=\"absolute left-0 top-0 size-full rounded-lg backdrop-blur-[20px] pointer-events-none shadow-tb-border -z-1 bg-[#00000048]\"\n\t\t\t\tstyle={{\n\t\t\t\t\tbackgroundImage: \"url(/assets/img/grain.png)\",\n\t\t\t\t}}\n\t\t\t></div>\n\t\t\t{isSnapped ? (\n\t\t\t\t<div\n\t\t\t\t\tref={focuserRef}\n\t\t\t\t\tclassName={`absolute rounded-lg ${config.focused ? \"inset-x-2 top-[calc(40px+0.5rem)] bottom-2 pointer-events-none opacity-0\" : \"inset-x-[1px] top-[40px] bottom-[1px] opacity-100\"} duration-150`}\n\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\twindowStore.arrange(config.wid);\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tsetZIndex(windowStore.getWindow(config.wid)?.zIndex);\n\t\t\t\t\t}}\n\t\t\t\t></div>\n\t\t\t) : (\n\t\t\t\t<div\n\t\t\t\t\tref={focuserRef}\n\t\t\t\t\tclassName={`absolute rounded-lg ${config.focused ? \"inset-x-2 top-[calc(40px+0.5rem)] bottom-2 pointer-events-none opacity-0\" : \"inset-x-[1px] top-[40px] bottom-[1px] backdrop-blur-[4px] opacity-100\"} duration-150`}\n\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\twindowStore.arrange(config.wid);\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tsetZIndex(windowStore.getWindow(config.wid)?.zIndex);\n\t\t\t\t\t}}\n\t\t\t\t></div>\n\t\t\t)}\n\t\t\t<div className=\"absolute left-0 right-0 h-[6px] cursor-n-resize z-10\" data-resizer=\"top\" onMouseDown={() => handleMouseDown(\"top\")} />\n\t\t\t<div className=\"absolute left-0 top-[6px] bottom-[6px] w-[6px] cursor-w-resize z-10\" data-resizer=\"left\" onMouseDown={() => handleMouseDown(\"left\")} />\n\t\t\t<div className=\"absolute right-0 top-[6px] bottom-[6px] w-[6px] cursor-e-resize z-10\" data-resizer=\"right\" onMouseDown={() => handleMouseDown(\"right\")} />\n\t\t\t<div className=\"absolute bottom-0 left-0 right-0 h-[6px] cursor-s-resize z-10\" data-resizer=\"bottom\" onMouseDown={() => handleMouseDown(\"bottom\")} />\n\t\t\t<div className=\"absolute top-0 left-0 size-4 cursor-nw-resize z-20\" data-resizer=\"top-left\" onMouseDown={() => handleMouseDown(\"top-left\")} />\n\t\t\t<div className=\"absolute top-0 right-0 size-4 cursor-ne-resize z-20\" data-resizer=\"top-right\" onMouseDown={() => handleMouseDown(\"top-right\")} />\n\t\t\t<div className=\"absolute bottom-0 left-0 size-4 cursor-sw-resize z-20\" data-resizer=\"bottom-left\" onMouseDown={() => handleMouseDown(\"bottom-left\")} />\n\t\t\t<div className=\"absolute bottom-0 right-0 size-4 cursor-se-resize z-20\" data-resizer=\"bottom-right\" onMouseDown={() => handleMouseDown(\"bottom-right\")} />\n\t\t\t<div\n\t\t\t\tref={regionRef}\n\t\t\t\tclassName=\"region flex justify-between items-center bg-[#ffffff10] p-2 min-w-[224px] select-none\"\n\t\t\t\tonMouseDown={(e: React.MouseEvent) => {\n\t\t\t\t\twindowStore.arrange(config.wid);\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tsetZIndex(windowStore.getWindow(config.wid)?.zIndex);\n\t\t\t\t\tif ((e.target as HTMLElement).classList.contains(\"no-drag\")) return;\n\t\t\t\t\tconst offsetX = e.clientX - windowRef.current!.offsetLeft;\n\t\t\t\t\tconst offsetY = e.clientY - windowRef.current!.offsetTop;\n\n\t\t\t\t\tlet animationFrameId: number | null = null;\n\t\t\t\t\tlet lastMouseEvent: MouseEvent | null = null;\n\n\t\t\t\t\tconst onMove = (e: MouseEvent) => {\n\t\t\t\t\t\tif (!optimizationsEnabled) {\n\t\t\t\t\t\t\t// Without optimizations: update immediately\n\t\t\t\t\t\t\tif (windowRef.current) windowRef.current.style.transform = \"\";\n\t\t\t\t\t\t\tsetIsDragging(true);\n\t\t\t\t\t\t\tsetMaximized(false);\n\t\t\t\t\t\t\tconst newX = e.clientX - offsetX;\n\t\t\t\t\t\t\tconst newY = e.clientY - offsetY;\n\t\t\t\t\t\t\thandleSnap(newX, newY);\n\t\t\t\t\t\t\tif (newY > 0 && newY < window.innerHeight - windowRef.current!.offsetHeight) setY(newY);\n\t\t\t\t\t\t\tif (newX > 0 && newX < window.innerWidth - windowRef.current!.offsetWidth) setX(newX);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// With optimizations: use requestAnimationFrame\n\t\t\t\t\t\tlastMouseEvent = e;\n\n\t\t\t\t\t\tif (!animationFrameId) {\n\t\t\t\t\t\t\tanimationFrameId = requestAnimationFrame(() => {\n\t\t\t\t\t\t\t\tif (!lastMouseEvent) return;\n\t\t\t\t\t\t\t\tconst e = lastMouseEvent;\n\n\t\t\t\t\t\t\t\tif (windowRef.current) windowRef.current.style.transform = \"\";\n\t\t\t\t\t\t\t\tsetIsDragging(true);\n\t\t\t\t\t\t\t\tsetMaximized(false);\n\t\t\t\t\t\t\t\tconst newX = e.clientX - offsetX;\n\t\t\t\t\t\t\t\tconst newY = e.clientY - offsetY;\n\t\t\t\t\t\t\t\thandleSnap(newX, newY);\n\t\t\t\t\t\t\t\tif (newY > 0 && newY < window.innerHeight - windowRef.current!.offsetHeight) setY(newY);\n\t\t\t\t\t\t\t\tif (newX > 0 && newX < window.innerWidth - windowRef.current!.offsetWidth) setX(newX);\n\n\t\t\t\t\t\t\t\tanimationFrameId = null;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tconst onUp = () => {\n\t\t\t\t\t\tif (animationFrameId) {\n\t\t\t\t\t\t\tcancelAnimationFrame(animationFrameId);\n\t\t\t\t\t\t\tanimationFrameId = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\twindow.removeEventListener(\"mousemove\", onMove);\n\t\t\t\t\t\twindow.removeEventListener(\"mouseup\", onUp);\n\t\t\t\t\t\twindow.removeEventListener(\"blur\", onUp);\n\t\t\t\t\t\tsetIsMouseDown(false);\n\t\t\t\t\t\tsetIsDragging(false);\n\t\t\t\t\t};\n\n\t\t\t\t\twindow.addEventListener(\"mousemove\", onMove);\n\t\t\t\t\twindow.addEventListener(\"mouseup\", onUp);\n\t\t\t\t\twindow.addEventListener(\"blur\", onUp);\n\n\t\t\t\t\twindow.onmouseleave = () => {\n\t\t\t\t\t\tsetIsDragging(false);\n\t\t\t\t\t\tsetIsMouseDown(false);\n\t\t\t\t\t\twindow.removeEventListener(\"mousemove\", onMove);\n\t\t\t\t\t\twindow.removeEventListener(\"mouseup\", onUp);\n\t\t\t\t\t\twindow.removeEventListener(\"blur\", onUp);\n\t\t\t\t\t};\n\n\t\t\t\t\tsetIsMouseDown(true);\n\t\t\t\t}}\n\t\t\t\tonMouseUp={() => {\n\t\t\t\t\tsetIsMouseDown(false);\n\t\t\t\t\tsetIsDragging(false);\n\t\t\t\t}}\n\t\t\t\tonMouseLeave={() => {\n\t\t\t\t\tif (isMouseDown) {\n\t\t\t\t\t\tsetIsDragging(false);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonMouseEnter={() => {\n\t\t\t\t\tif (isMouseDown) {\n\t\t\t\t\t\tsetIsDragging(true);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonDoubleClick={() => {\n\t\t\t\t\tif (config.maximizable !== false)\n\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"width, height, left, top\";\n\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"150ms\";\n\t\t\t\t\t\t}\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"\";\n\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 150);\n\t\t\t\t\tsetMaximized(!maximized);\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div className=\"flex gap-2 items-center flex-row w-full\">\n\t\t\t\t\t<img src={config.icon} alt=\"icon\" className=\"w-5 h-5 pointer-events-none select-none\" draggable={false} />\n\t\t\t\t\t<span ref={titleRef} className=\"font-[680] pointer-events-none select-none\">\n\t\t\t\t\t\t{title}\n\t\t\t\t\t</span>\n\t\t\t\t\t{titlebarhtml && <div className=\"w-[80%]\" ref={thtmlref} />}\n\t\t\t\t</div>\n\t\t\t\t{controls ? (\n\t\t\t\t\t<div className=\"controls flex gap-1\">\n\t\t\t\t\t\t{controls?.map((control, index) => {\n\t\t\t\t\t\t\tif (control === \"minimize\") {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\t\t\tref={miniRef}\n\t\t\t\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\t\t\t\tclassName={`group size-4 ${config.minimizable === false ? \"cursor-default\" : \"cursor-pointer\"} no-drag`}\n\t\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\t\t\t\tif (config.minimizable === false) return;\n\t\t\t\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"transform, opacity\";\n\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"150ms\";\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\t\t\t\tsetMinimized(true);\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                    ${config.minimizable === false ? \"fill-[#ffffff60]\" : \"fill-[#ffffffbb] group-hover:fill-white\"} duration-150 pointer-events-none\n                                                `}\n\t\t\t\t\t\t\t\t\t\t\tx=\"4\"\n\t\t\t\t\t\t\t\t\t\t\ty=\"10\"\n\t\t\t\t\t\t\t\t\t\t\twidth=\"16\"\n\t\t\t\t\t\t\t\t\t\t\theight=\"3\"\n\t\t\t\t\t\t\t\t\t\t\trx=\"2\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (control === \"maximize\") {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\t\t\tref={minMaxRef}\n\t\t\t\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\t\t\t\tclassName={`group size-4 ${config.maximizable === false ? \"cursor-default\" : \"cursor-pointer\"} no-drag`}\n\t\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\t\t\t\tif (config.maximizable === false) return;\n\t\t\t\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"width, height, left, top\";\n\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"150ms\";\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"\";\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\t\t\t\tsetMaximized(!maximized);\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{maximized ? (\n\t\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                                    ${config.maximizable === false ? \"fill-[#ffffff60]\" : \"fill-[#ffffffbb] group-hover:fill-white\"} duration-150 pointer-events-none\n                                                                `}\n\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M6 6C6 3.79086 7.79086 2 10 2H18C20.2091 2 22 3.79086 22 6V14C22 16.2091 20.2091 18 18 18H16V16H18C19.1046 16 20 15.1046 20 14V6C20 4.89543 19.1046 4 18 4H10C8.89543 4 8 4.89543 8 6V8H6V6Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"fill-[#ffffffbb] group-hover:fill-white duration-150 pointer-events-none\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M6 6C3.79086 6 2 7.79086 2 10V18C2 20.2091 3.79086 22 6 22H14C16.2091 22 18 20.2091 18 18V10C18 7.79086 16.2091 6 14 6H6ZM6 8C4.89543 8 4 8.89543 4 10V18C4 19.1046 4.89543 20 6 20H14C15.1046 20 16 19.1046 16 18V10C16 8.89543 15.1046 8 14 8H6Z\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                                ${config.maximizable === false ? \"fill-[#ffffff60]\" : \"fill-[#ffffffbb] group-hover:fill-white\"} duration-150 pointer-events-none\n                                                            `}\n\t\t\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\t\t\td=\"M8 4C5.79086 4 4 5.79086 4 8V16C4 18.2091 5.79086 20 8 20H16C18.2091 20 20 18.2091 20 16V8C20 5.79086 18.2091 4 16 4H8ZM8 6C6.89543 6 6 6.89543 6 8V16C6 17.1046 6.89543 18 8 18H16C17.1046 18 18 17.1046 18 16V8C18 6.89543 17.1046 6 16 6H8Z\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (control === \"close\") {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\t\t\tref={closeRef}\n\t\t\t\t\t\t\t\t\t\tkey={index}\n\t\t\t\t\t\t\t\t\t\tclassName={`group size-4 ${config.closable === false ? \"cursor-default\" : \"cursor-pointer\"} no-drag`}\n\t\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\t\t\t\tif (config.closable === false) return;\n\t\t\t\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"transform, opacity\";\n\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"150ms\";\n\t\t\t\t\t\t\t\t\t\t\t\twindowRef.current.classList.add(\"translate-y-3\", \"opacity-0\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\t\tclearInfo();\n\t\t\t\t\t\t\t\t\t\t\t\twindowStore.removeWindow(config.wid);\n\t\t\t\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                    ${config.closable === false ? \"stroke-[#ffffff60]\" : \"stroke-[#ffffffbb] group-hover:stroke-white\"} duration-150 pointer-events-none\n                                                `}\n\t\t\t\t\t\t\t\t\t\t\td=\"M6 18L18 6M6 6L18 18\"\n\t\t\t\t\t\t\t\t\t\t\tstroke=\"white\"\n\t\t\t\t\t\t\t\t\t\t\tstrokeWidth=\"2.5\"\n\t\t\t\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div className=\"controls flex gap-1\">\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tref={miniRef}\n\t\t\t\t\t\t\tclassName={`group size-4 ${config.minimizable === false ? \"cursor-default\" : \"cursor-pointer\"} no-drag`}\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tif (config.minimizable === false) return;\n\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"transform, opacity\";\n\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"150ms\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"\";\n\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"\";\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\twindowStore.minimize(config.wid);\n\t\t\t\t\t\t\t\twindow.dispatchEvent(new CustomEvent(\"min-win\", { detail: config.pid }));\n\t\t\t\t\t\t\t\tsetMinimized(true);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t\tclassName={`\n                                    ${config.minimizable === false ? \"fill-[#ffffff60]\" : \"fill-[#ffffffbb] group-hover:fill-white\"} duration-150 pointer-events-none\n                                `}\n\t\t\t\t\t\t\t\tx=\"4\"\n\t\t\t\t\t\t\t\ty=\"10\"\n\t\t\t\t\t\t\t\twidth=\"16\"\n\t\t\t\t\t\t\t\theight=\"3\"\n\t\t\t\t\t\t\t\trx=\"2\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tref={minMaxRef}\n\t\t\t\t\t\t\tclassName={`group size-4 ${config.maximizable === false ? \"cursor-default\" : \"cursor-pointer\"} no-drag`}\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tif (config.maximizable === false) return;\n\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"width, height, left, top\";\n\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"150ms\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"\";\n\t\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"\";\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t\tsetMaximized(!maximized);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{maximized ? (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\tclassName={`\n                                                ${config.maximizable === false ? \"fill-[#ffffff60]\" : \"fill-[#ffffffbb] group-hover:fill-white\"} duration-150 pointer-events-none\n                                            `}\n\t\t\t\t\t\t\t\t\t\td=\"M6 6C6 3.79086 7.79086 2 10 2H18C20.2091 2 22 3.79086 22 6V14C22 16.2091 20.2091 18 18 18H16V16H18C19.1046 16 20 15.1046 20 14V6C20 4.89543 19.1046 4 18 4H10C8.89543 4 8 4.89543 8 6V8H6V6Z\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\tclassName=\"fill-[#ffffffbb] group-hover:fill-white duration-150 pointer-events-none\"\n\t\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\t\td=\"M6 6C3.79086 6 2 7.79086 2 10V18C2 20.2091 3.79086 22 6 22H14C16.2091 22 18 20.2091 18 18V10C18 7.79086 16.2091 6 14 6H6ZM6 8C4.89543 8 4 8.89543 4 10V18C4 19.1046 4.89543 20 6 20H14C15.1046 20 16 19.1046 16 18V10C16 8.89543 15.1046 8 14 8H6Z\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\tclassName={`\n                                            ${config.maximizable === false ? \"fill-[#ffffff60]\" : \"fill-[#ffffffbb] group-hover:fill-white\"} duration-150 pointer-events-none\n                                        `}\n\t\t\t\t\t\t\t\t\tfillRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\tclipRule=\"evenodd\"\n\t\t\t\t\t\t\t\t\td=\"M8 4C5.79086 4 4 5.79086 4 8V16C4 18.2091 5.79086 20 8 20H16C18.2091 20 20 18.2091 20 16V8C20 5.79086 18.2091 4 16 4H8ZM8 6C6.89543 6 6 6.89543 6 8V16C6 17.1046 6.89543 18 8 18H16C17.1046 18 18 17.1046 18 16V8C18 6.89543 17.1046 6 16 6H8Z\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\tref={closeRef}\n\t\t\t\t\t\t\tclassName={`group size-4 ${config.closable === false ? \"cursor-default\" : \"cursor-pointer\"} no-drag`}\n\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\tonMouseDown={() => {\n\t\t\t\t\t\t\t\tif (config.closable === false) return;\n\t\t\t\t\t\t\t\tif (windowRef.current) {\n\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionProperty = \"transform, opacity\";\n\t\t\t\t\t\t\t\t\twindowRef.current.style.transitionDuration = \"150ms\";\n\t\t\t\t\t\t\t\t\twindowRef.current.classList.add(\"translate-y-3\", \"opacity-0\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tclearInfo();\n\t\t\t\t\t\t\t\t\twindowStore.removeWindow(config.wid);\n\t\t\t\t\t\t\t\t}, 150);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\tclassName={`\n                                    ${config.closable === false ? \"stroke-[#ffffff60]\" : \"stroke-[#ffffffbb] group-hover:stroke-white\"} duration-150 pointer-events-none\n                                `}\n\t\t\t\t\t\t\t\td=\"M6 18L18 6M6 6L18 18\"\n\t\t\t\t\t\t\t\tstroke=\"white\"\n\t\t\t\t\t\t\t\tstrokeWidth=\"2.5\"\n\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</svg>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t<div ref={contentRef} className=\"w-full h-full\" style={optimizationsEnabled ? { contain: \"strict\" } : {}}>\n\t\t\t\t<iframe\n\t\t\t\t\tkey={config.src}\n\t\t\t\t\tref={srcRef}\n\t\t\t\t\tsrc={src}\n\t\t\t\t\tid={`proc-${config.pid}`}\n\t\t\t\t\tloading={optimizationsEnabled && minimized ? \"lazy\" : \"eager\"}\n\t\t\t\t\tonLoad={() => {\n\t\t\t\t\t\tif (config.message) {\n\t\t\t\t\t\t\tsrcRef.current?.contentWindow!.postMessage(config.message, \"*\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst sr1 = document.createElement(\"script\");\n\t\t\t\t\t\tconst sr2 = document.createElement(\"script\");\n\t\t\t\t\t\tsr1.src = \"/cursor_changer.js\";\n\t\t\t\t\t\tsr2.src = \"/media_interactions.js\";\n\t\t\t\t\t\tsrcRef.current?.contentDocument?.head.appendChild(sr1);\n\t\t\t\t\t\tsrcRef.current?.contentDocument?.head.appendChild(sr2);\n\t\t\t\t\t}}\n\t\t\t\t\treferrerPolicy=\"no-referrer\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tborder: \"none\",\n\t\t\t\t\t\tall: \"initial\",\n\t\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\t\theight: \"calc(100% - 40px)\",\n\t\t\t\t\t\tpointerEvents: isMouseDown ? \"none\" : \"auto\",\n\t\t\t\t\t\tuserSelect: \"none\",\n\t\t\t\t\t\t...(optimizationsEnabled && { contain: \"strict\" }),\n\t\t\t\t\t}}\n\t\t\t\t></iframe>\n\t\t\t</div>\n\t\t</div>\n\t);\n};\n\n// Memoize WindowElement to prevent unnecessary re-renders\nconst MemoizedWindowElement = memo(WindowElement, (prevProps, nextProps) => {\n\t// Only re-render if config changes in meaningful ways\n\treturn prevProps.config.wid === nextProps.config.wid && prevProps.config.zIndex === nextProps.config.zIndex && prevProps.config.focused === nextProps.config.focused && prevProps.className === nextProps.className;\n});\n\nconst DesktopItems = () => {\n\tconst [items, setItems] = useState<any[]>([]);\n\tconst [dragging, setDragging] = useState<boolean>(false);\n\tconst draggedItemIndex = useRef<number | null>(null);\n\tconst [offset, setOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 });\n\tconst [dragradius, setDragradius] = useState<boolean>(false);\n\tconst [selected, setSelected] = useState<any>(null);\n\tconst selectedRef = useRef<HTMLDivElement>(null);\n\tconst user = sessionStorage.getItem(\"currAcc\");\n\n\tuseEffect(() => {\n\t\tconst addDesktopListener = async () => {\n\t\t\tlet desktopItems: string[] = await window.tb.fs.promises.readdir(`/home/${user}/desktop`);\n\n\t\t\tconst handleDesktopChange = async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst updatedItems = await window.tb.fs.promises.readdir(`/home/${user}/desktop`);\n\t\t\t\t\tconst addedItems = updatedItems.filter(item => !desktopItems.includes(item));\n\t\t\t\t\tconst removedItems = desktopItems.filter(item => !updatedItems.includes(item));\n\t\t\t\t\tvar desktopConfig = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\tif (addedItems.length > 0) {\n\t\t\t\t\t\tconst findLastItem = () => {\n\t\t\t\t\t\t\tfor (let i = desktopConfig.length - 1; i >= 0; i--) {\n\t\t\t\t\t\t\t\tif (!desktopConfig[i].position.custom) {\n\t\t\t\t\t\t\t\t\treturn desktopConfig[i];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst lastItem: any = findLastItem();\n\t\t\t\t\t\tconst highestLeft = Math.max(...desktopConfig.map((item: any) => item.position.left));\n\t\t\t\t\t\tlet topPos = 0;\n\t\t\t\t\t\tlet leftPos = 0;\n\n\t\t\t\t\t\tif (lastItem && lastItem.position.top < 11) {\n\t\t\t\t\t\t\ttopPos = Math.floor(lastItem.position.top + 1);\n\t\t\t\t\t\t\tleftPos = lastItem.position.left;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tleftPos = Math.floor(highestLeft + 1);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (const item of addedItems) {\n\t\t\t\t\t\t\tconst itemExists = desktopConfig.some((config: any) => config.item === `/home/${user}/desktop/${item}`);\n\t\t\t\t\t\t\tif (!itemExists) {\n\t\t\t\t\t\t\t\tconst type = (await window.tb.fs.promises.stat(`/home/${user}/desktop/${item}`))!.type.toLowerCase();\n\t\t\t\t\t\t\t\tif (type === \"symlink\") {\n\t\t\t\t\t\t\t\t\tconst isAppJson = (await window.tb.fs.promises.readFile(await window.tb.fs.promises.readlink(`/home/${user}/desktop/${item}`))).includes(\"config\");\n\t\t\t\t\t\t\t\t\tdesktopConfig.push({\n\t\t\t\t\t\t\t\t\t\tname: isAppJson ? JSON.parse(await window.tb.fs.promises.readFile(await window.tb.fs.promises.readlink(`/home/${user}/desktop/${item}`)))[\"config\"].title : item,\n\t\t\t\t\t\t\t\t\t\titem: `/home/${user}/desktop/${item}`,\n\t\t\t\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else if (type === \"file\") {\n\t\t\t\t\t\t\t\t\tconst ext = item.split(\".\").pop();\n\t\t\t\t\t\t\t\t\tconst icons = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\"));\n\t\t\t\t\t\t\t\t\tconst iconName = ext ? icons[\"ext-to-name\"][ext] : \"Unknown\";\n\t\t\t\t\t\t\t\t\tconst iconPath = iconName ? icons[\"name-to-path\"][iconName] : \"/system/etc/terbium/file-icons/Unknown.svg\";\n\t\t\t\t\t\t\t\t\tconst iconData = await window.tb.fs.promises.readFile(iconPath, \"utf8\");\n\n\t\t\t\t\t\t\t\t\tdesktopConfig.push({\n\t\t\t\t\t\t\t\t\t\tname: item,\n\t\t\t\t\t\t\t\t\t\titem: `/home/${user}/desktop/${item}`,\n\t\t\t\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\ticon: iconData,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (removedItems.length > 0) {\n\t\t\t\t\t\tfor (const item of removedItems) {\n\t\t\t\t\t\t\tconst index = desktopConfig.findIndex((config: any) => config.item === `/home/${user}/desktop/${item}`);\n\t\t\t\t\t\t\tdesktopConfig.splice(index, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tdesktopItems = updatedItems;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopConfig, null, 4));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Error while reading directory:\", error);\n\t\t\t\t}\n\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t};\n\n\t\t\thandleDesktopChange();\n\t\t};\n\n\t\taddDesktopListener();\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst getItems = async () => {\n\t\t\tvar allItems: any[] = [];\n\t\t\tconst items = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\tfor (const item of items) {\n\t\t\t\tconst type = (await window.tb.fs.promises.stat(item.item))!.type.toLowerCase();\n\t\t\t\tconst position = item.position;\n\t\t\t\tif (type === \"symlink\") {\n\t\t\t\t\tallItems.push({\n\t\t\t\t\t\tname: item.name,\n\t\t\t\t\t\ttype: \"symlink\",\n\t\t\t\t\t\titem: item.item,\n\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\tcustom: position.custom,\n\t\t\t\t\t\t\ttop: position.top,\n\t\t\t\t\t\t\tleft: position.left,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconfig: JSON.parse(await window.tb.fs.promises.readFile(await window.tb.fs.promises.readlink(item.item)))[\"config\"],\n\t\t\t\t\t});\n\t\t\t\t} else if (type === \"file\") {\n\t\t\t\t\tconst ext = item.name.split(\".\").pop();\n\t\t\t\t\tconst icons = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/file-icons.json\"));\n\t\t\t\t\tconst iconName = ext ? icons[\"ext-to-name\"][ext] : \"Unknown\";\n\t\t\t\t\tconst iconPath = iconName ? icons[\"name-to-path\"][iconName] : \"/system/etc/terbium/file-icons/Unknown.svg\";\n\t\t\t\t\tconst iconData = await window.tb.fs.promises.readFile(iconPath, \"utf8\");\n\t\t\t\t\tallItems.push({\n\t\t\t\t\t\tname: item.name,\n\t\t\t\t\t\ttype: \"file\",\n\t\t\t\t\t\titem: item.item,\n\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\tcustom: position.custom,\n\t\t\t\t\t\t\ttop: position.top,\n\t\t\t\t\t\t\tleft: position.left,\n\t\t\t\t\t\t},\n\t\t\t\t\t\ticon: iconData,\n\t\t\t\t\t});\n\t\t\t\t} else if (type === \"directory\") {\n\t\t\t\t\tallItems.push({\n\t\t\t\t\t\tname: item.name,\n\t\t\t\t\t\ttype: \"directory\",\n\t\t\t\t\t\titem: item.item,\n\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\tcustom: position.custom,\n\t\t\t\t\t\t\ttop: position.top,\n\t\t\t\t\t\t\tleft: position.left,\n\t\t\t\t\t\t},\n\t\t\t\t\t\ticon: `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"size-6\"><path d=\"M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z\" /></svg>`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsetItems(allItems);\n\t\t};\n\t\tgetItems();\n\t\twindow.addEventListener(\"upd-desktop\", getItems);\n\t\treturn () => window.removeEventListener(\"upd-desktop\", getItems);\n\t}, []);\n\n\tconst onMouseDown = (e: React.MouseEvent<HTMLDivElement>, index: number) => {\n\t\tlet holdTimeout: any | null = null;\n\t\tlet renamingIndex: number | null = null;\n\t\tconst startDragging = () => {\n\t\t\tsetDragradius(true);\n\t\t\tsetDragging(true);\n\t\t\tdraggedItemIndex.current = index;\n\t\t};\n\n\t\tconst saveName = async () => {\n\t\t\tif (selectedRef.current && renamingIndex !== null) {\n\t\t\t\tconst spanElement = selectedRef.current.querySelector(\"span\");\n\t\t\t\tif (spanElement) {\n\t\t\t\t\tconst newName = spanElement.innerText;\n\t\t\t\t\tconst oldName = items[renamingIndex].name;\n\t\t\t\t\tconst itemPath = items[renamingIndex].item;\n\t\t\t\t\tconst newPath = itemPath.replace(oldName, newName);\n\t\t\t\t\tif (selectedRef.current?.dataset.type === \"shortcut\") {\n\t\t\t\t\t\tconst desktopItems = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\t\tconst itemIndex = desktopItems.findIndex((item: any) => item.item === itemPath);\n\t\t\t\t\t\tif (itemIndex !== -1) {\n\t\t\t\t\t\t\tdesktopItems[itemIndex].name = newName;\n\t\t\t\t\t\t\tdesktopItems[itemIndex].item = newPath;\n\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopItems, null, 4));\n\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait window.tb.fs.promises.rename(itemPath, newPath);\n\t\t\t\t\t\tconst desktopItems = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\t\tconst itemIndex = desktopItems.findIndex((item: any) => item.item === itemPath);\n\t\t\t\t\t\tif (itemIndex !== -1) {\n\t\t\t\t\t\t\tdesktopItems[itemIndex].name = newName;\n\t\t\t\t\t\t\tdesktopItems[itemIndex].item = newPath;\n\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopItems, null, 4));\n\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t\tselectedRef.current = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\trenamingIndex = null;\n\t\t};\n\n\t\tif (selectedRef.current && selectedRef.current === e.currentTarget) {\n\t\t\tif (selectedRef.current && selectedRef.current !== null) {\n\t\t\t\tconst spanElement = selectedRef.current.querySelector(\"span\");\n\t\t\t\tif (spanElement) {\n\t\t\t\t\tspanElement.contentEditable = \"true\";\n\t\t\t\t\tconst range = document.createRange();\n\t\t\t\t\tconst selection = window.getSelection();\n\t\t\t\t\trange.selectNodeContents(spanElement);\n\t\t\t\t\trange.collapse(false);\n\t\t\t\t\tselection?.removeAllRanges();\n\t\t\t\t\tselection?.addRange(range);\n\t\t\t\t\tspanElement.addEventListener(\"keydown\", async e => {\n\t\t\t\t\t\tif (e.key === \"Enter\") {\n\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\tawait saveName();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tspanElement.focus();\n\t\t\t\t\trenamingIndex = index;\n\t\t\t\t}\n\t\t\t\tconst mouseDownHandler = async (e: MouseEvent) => {\n\t\t\t\t\tif (selectedRef.current && !selectedRef.current.contains(e.target as Node)) {\n\t\t\t\t\t\tsetSelected(null);\n\t\t\t\t\t\tconst spanElement = selectedRef.current.querySelector(\"span\");\n\t\t\t\t\t\tif (spanElement) {\n\t\t\t\t\t\t\tawait saveName();\n\t\t\t\t\t\t\tspanElement.contentEditable = \"false\";\n\t\t\t\t\t\t\tspanElement.blur();\n\t\t\t\t\t\t\tselectedRef.current = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tdocument.addEventListener(\"mousedown\", mouseDownHandler, { once: true });\n\t\t\t}\n\t\t} else {\n\t\t\tselectedRef.current = e.currentTarget;\n\t\t\trenamingIndex = index;\n\t\t}\n\n\t\tholdTimeout = setTimeout(startDragging, 300);\n\t\tconst clearHoldTimeout = () => {\n\t\t\tif (holdTimeout) {\n\t\t\t\tclearTimeout(holdTimeout);\n\t\t\t\tholdTimeout = null;\n\t\t\t}\n\t\t};\n\t\twindow.onmouseup = async (e: MouseEvent) => {\n\t\t\tsetDragging(false);\n\t\t\twindow.removeEventListener(\"mousemove\", onMouseMove);\n\t\t\tif (draggedItemIndex.current !== null && !holdTimeout) {\n\t\t\t\tconst draggedApp = items[draggedItemIndex.current];\n\t\t\t\tconst updatedApp = {\n\t\t\t\t\t...draggedApp,\n\t\t\t\t\tleftPos: e.clientX - 44,\n\t\t\t\t\ttopPos: e.clientY - 80,\n\t\t\t\t};\n\t\t\t\tawait savePos(draggedApp.item, updatedApp.leftPos, updatedApp.topPos);\n\t\t\t}\n\t\t\tdraggedItemIndex.current = null;\n\t\t\tclearHoldTimeout();\n\t\t\tsetDragradius(false);\n\t\t};\n\n\t\twindow.onmouseleave = async () => {\n\t\t\tclearHoldTimeout();\n\t\t\tsetDragging(false);\n\t\t\twindow.removeEventListener(\"mousemove\", onMouseMove);\n\t\t\tif (draggedItemIndex.current !== null && dragging) {\n\t\t\t\tconst draggedApp = items[draggedItemIndex.current];\n\t\t\t\tconst updatedApp = {\n\t\t\t\t\t...draggedApp,\n\t\t\t\t\tleftPos: draggedApp.position.left,\n\t\t\t\t\ttopPos: draggedApp.position.top,\n\t\t\t\t};\n\t\t\t\tawait savePos(draggedApp.item, updatedApp.leftPos, updatedApp.topPos);\n\t\t\t}\n\t\t\tdraggedItemIndex.current = null;\n\t\t};\n\n\t\te.preventDefault();\n\t\te.target.addEventListener(\"mouseup\", clearHoldTimeout, { once: true });\n\t};\n\n\tconst onMouseMove = (e: MouseEvent) => {\n\t\tif (dragging && draggedItemIndex !== null) {\n\t\t\tlet newX = e.clientX - offset.x - 44;\n\t\t\tlet newY = e.clientY - offset.y - 80;\n\n\t\t\tsetItems(prevApps => prevApps.map((app, index) => (index === draggedItemIndex.current ? { ...app, position: { ...app.position, left: newX, top: newY, custom: true } } : app)));\n\t\t}\n\t};\n\n\tconst savePos = async (item: string, left: number, top: number) => {\n\t\ttry {\n\t\t\tconst desktopConfig = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\tconst itemIndex = desktopConfig.findIndex((config: any) => config.item === item);\n\t\t\tif (itemIndex !== -1) {\n\t\t\t\tconst currentLeft = desktopConfig[itemIndex].position.left;\n\t\t\t\tconst currentTop = desktopConfig[itemIndex].position.top;\n\t\t\t\tif ((Math.abs(Math.round(currentLeft) - Math.round(left)) > 67 || Math.abs(Math.round(currentTop) - Math.round(top)) > 67) && (Math.round(currentLeft) !== Math.round(left) || Math.round(currentTop) !== Math.round(top))) {\n\t\t\t\t\tdesktopConfig[itemIndex].position.left = Math.round(left);\n\t\t\t\t\tdesktopConfig[itemIndex].position.top = Math.round(top);\n\t\t\t\t\tdesktopConfig[itemIndex].position.custom = true;\n\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopConfig, null, 4));\n\t\t\t\t\tconsole.log(\"Saved app position\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error saving app position:\", error);\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tdocument.addEventListener(\"mousemove\", onMouseMove);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", onMouseMove);\n\t\t};\n\t}, [dragging]);\n\n\treturn (\n\t\t<div className=\"flex gap-1 flex-wrap h-full\">\n\t\t\t{items.map((item: DesktopItem, i: any) => {\n\t\t\t\treturn item.type === \"file\" ? (\n\t\t\t\t\t<div\n\t\t\t\t\t\ttitle={item.name}\n\t\t\t\t\t\tkey={`${item.name}`}\n\t\t\t\t\t\tid=\"desktop-item\"\n\t\t\t\t\t\tclassName=\"group relative size-max min-w-16 min-h-16 flex flex-col items-center justify-center p-2 text-sm font-medium text-wrap select-none\"\n\t\t\t\t\t\tonDoubleClick={async () => {\n\t\t\t\t\t\t\tlet handlers = JSON.parse(await window.tb.fs.promises.readFile(\"/system/etc/terbium/settings.json\", \"utf8\"))[\"fileAssociatedApps\"];\n\t\t\t\t\t\t\thandlers = Object.entries(handlers).filter(([type, app]) => {\n\t\t\t\t\t\t\t\treturn !(type === \"text\" && app === \"text-editor\") && !(type === \"image\" && app === \"media-viewer\") && !(type === \"video\" && app === \"media-viewer\") && !(type === \"audio\" && app === \"media-viewer\");\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tlet hands = [];\n\t\t\t\t\t\t\tfor (const [type, app] of handlers) {\n\t\t\t\t\t\t\t\thands.push({ text: app, value: type });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tawait window.tb.dialog.Select({\n\t\t\t\t\t\t\t\ttitle: `Select a application to open: ${item.item.split(\"/\").pop()}`,\n\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Text Editor\",\n\t\t\t\t\t\t\t\t\t\tvalue: \"text\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Media Viewer\",\n\t\t\t\t\t\t\t\t\t\tvalue: \"media\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Webview\",\n\t\t\t\t\t\t\t\t\t\tvalue: \"webview\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t...hands,\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Other\",\n\t\t\t\t\t\t\t\t\t\tvalue: \"other\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\tconst data = await fetch(`/fs//system/etc/terbium/file-icons.json`).then(res => res.json());\n\t\t\t\t\t\t\t\t\tconst ext = item.name.split(\".\").pop();\n\t\t\t\t\t\t\t\t\tswitch (val) {\n\t\t\t\t\t\t\t\t\t\tcase \"text\":\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(item.item, \"text\");\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase \"media\":\n\t\t\t\t\t\t\t\t\t\t\tif (data[\"image\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(item.item, \"image\");\n\t\t\t\t\t\t\t\t\t\t\t} else if (data[\"video\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(item.item, \"video\");\n\t\t\t\t\t\t\t\t\t\t\t} else if (data[\"audio\"].includes(ext)) {\n\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(item.item, \"audio\");\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase \"webview\":\n\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(item.item, \"webpage\");\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tcase \"other\":\n\t\t\t\t\t\t\t\t\t\t\twindow.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\t\t\tfilter: \".tapp\",\n\t\t\t\t\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst app = JSON.parse(await window.tb.fs.promises.readFile(`${val}/.tbconfig`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\t\t\tcreateWindow({ ...app, message: { type: \"process\", path: item.item } });\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t\tif (hands.length === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(item.item, \"text\");\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tparent.window.tb.file.handler.openFile(item.item, val);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonMouseDown={(e: React.MouseEvent<HTMLDivElement>) => onMouseDown(e, i)}\n\t\t\t\t\t\tonContextMenuCapture={(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\t\t\t\tsetDragging(false);\n\t\t\t\t\t\t\tdraggedItemIndex.current = null;\n\t\t\t\t\t\t\tsetDragradius(false);\n\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\tconst { clientX, clientY } = e;\n\t\t\t\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\t\t\t\tx: clientX,\n\t\t\t\t\t\t\t\ty: clientY,\n\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Open\",\n\t\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"ldir\", item.item);\n\t\t\t\t\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Files\",\n\t\t\t\t\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/files.tapp/index.html\",\n\t\t\t\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\t\t\t\t\t\theight: 500,\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Delete Shortcut\",\n\t\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\t\tlet idx = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\tidx = idx.filter((entry: any) => entry.name !== item.name);\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(idx, null, 4));\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tleft: item.position.custom === true ? item.position.left : Math.floor(Number(item.position.left) * 80),\n\t\t\t\t\t\t\ttop: item.position.custom === true ? item.position.top : Math.floor(Number(item.position.top) * 66),\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"absolute z-1 size-full rounded-md bg-[#ffffff10] backdrop-blur-xl opacity-0 shadow-tb-border-shadow group-hover:opacity-100 focus:opacity-100 duration-150 ease-in pointer-events-none select-none\"></div>\n\t\t\t\t\t\t<div className=\"flex z-2 size-full flex-col items-center justify-center pointer-events-none\">\n\t\t\t\t\t\t\t{<div className=\"size-6 pointer-events-none select-none\" dangerouslySetInnerHTML={{ __html: item.icon }} />}\n\t\t\t\t\t\t\t<span className=\"leading-none bg-transparent text-white text-center select-none w-16\" style={{ textShadow: \"0 0 4px #00000052\" }}>\n\t\t\t\t\t\t\t\t{item.name.length > 12 ? `${item.name.slice(0, 10)}...` : item.name}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t) : item.type === \"directory\" ? (\n\t\t\t\t\t<div\n\t\t\t\t\t\ttitle={item.name}\n\t\t\t\t\t\tkey={`${item.name}`}\n\t\t\t\t\t\tid=\"desktop-item\"\n\t\t\t\t\t\tclassName=\"group relative size-max min-w-16 min-h-16 flex flex-col items-center justify-center p-2 text-sm font-medium text-wrap select-none\"\n\t\t\t\t\t\tonDoubleClick={() => {\n\t\t\t\t\t\t\tsessionStorage.setItem(\"ldir\", item.item);\n\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\ttitle: \"Files\",\n\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/files.tapp/index.html\",\n\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\t\theight: 500,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonMouseDown={(e: React.MouseEvent<HTMLDivElement>) => onMouseDown(e, i)}\n\t\t\t\t\t\tonContextMenuCapture={(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\t\t\t\tsetDragging(false);\n\t\t\t\t\t\t\tdraggedItemIndex.current = null;\n\t\t\t\t\t\t\tsetDragradius(false);\n\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\tconst { clientX, clientY } = e;\n\t\t\t\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\t\t\t\tx: clientX,\n\t\t\t\t\t\t\t\ty: clientY,\n\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Open\",\n\t\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\t\tsessionStorage.setItem(\"ldir\", item.item);\n\t\t\t\t\t\t\t\t\t\t\tcreateWindow({\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Files\",\n\t\t\t\t\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/files.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/files.tapp/index.html\",\n\t\t\t\t\t\t\t\t\t\t\t\tsize: {\n\t\t\t\t\t\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\t\t\t\t\t\theight: 500,\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Delete Shortcut\",\n\t\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\t\tlet idx = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t\tidx = idx.filter((entry: any) => entry.name !== item.name);\n\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(idx, null, 4));\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tleft: item.position.custom === true ? item.position.left : Math.floor(Number(item.position.left) * 80),\n\t\t\t\t\t\t\ttop: item.position.custom === true ? item.position.top : Math.floor(Number(item.position.top) * 66),\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"absolute z-[1] size-full rounded-md bg-[#ffffff10] backdrop-blur-xl opacity-0 shadow-tb-border-shadow group-hover:opacity-100 focus:opacity-100 duration-150 ease-in pointer-events-none select-none\"></div>\n\t\t\t\t\t\t<div className=\"flex z-[2] size-full flex-col items-center justify-center pointer-events-none\">\n\t\t\t\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"size-6 pointer-events-none select-none\">\n\t\t\t\t\t\t\t\t<path d=\"M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z\" />\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t<span className=\"leading-none bg-transparent text-white text-center select-none w-16\" style={{ textShadow: \"0 0 4px #00000052\" }}>\n\t\t\t\t\t\t\t\t{item.name.length > 12 ? `${item.name.slice(0, 10)}...` : item.name}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t) : (\n\t\t\t\t\t<div\n\t\t\t\t\t\tdata-type=\"shortcut\"\n\t\t\t\t\t\ttitle={item.name}\n\t\t\t\t\t\tkey={`${item.name}`}\n\t\t\t\t\t\tid=\"desktop-item\"\n\t\t\t\t\t\tclassName=\"group relative size-max min-w-16 min-h-16 flex flex-col items-center justify-center p-2 text-sm font-medium text-wrap select-none\"\n\t\t\t\t\t\tonDoubleClick={() => {\n\t\t\t\t\t\t\tcreateWindow({ ...item.config, wid: undefined });\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tleft: item.position.custom === true ? item.position.left : Math.floor(Number(item.position.left) * 80),\n\t\t\t\t\t\t\ttop: item.position.custom === true ? item.position.top : Math.floor(Number(item.position.top) * 66),\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonMouseDown={(e: React.MouseEvent<HTMLDivElement>) => onMouseDown(e, i)}\n\t\t\t\t\t\tonContextMenuCapture={(e: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\t\t\t\tsetDragging(false);\n\t\t\t\t\t\t\tdraggedItemIndex.current = null;\n\t\t\t\t\t\t\tsetDragradius(false);\n\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\t\t\t\tx: e.clientX - 50,\n\t\t\t\t\t\t\t\ty: e.clientY,\n\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Open\",\n\t\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\t\tcreateWindow(item.config);\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Pin to Dock\",\n\t\t\t\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\t\t\t\twindow.tb.desktop.dock.pin(item.config);\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttext: \"Delete Shortcut\",\n\t\t\t\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\t\t\t\tconst stat = await window.tb.fs.promises.stat(`/home/${user}/desktop/${item.item}`);\n\t\t\t\t\t\t\t\t\t\t\tif (stat!.isDirectory()) {\n\t\t\t\t\t\t\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\t\t\t\t\t\t\tawait new window.tb.fs.Shell().promises.rm(`/home/${user}/desktop/${item.item}`, { recursive: true });\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.unlink(`/home/${user}/desktop/${item.item}`);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"absolute z-1 size-full rounded-md bg-[#ffffff10] backdrop-blur-xl opacity-0 shadow-tb-border-shadow group-hover:opacity-100 focus:opacity-100 duration-150 ease-in pointer-events-none select-none\"></div>\n\t\t\t\t\t\t<div className=\"flex z-2 size-full flex-col items-center justify-center pointer-events-none\">\n\t\t\t\t\t\t\t<img src={item.config.icon} alt={item.name} className=\"size-6 pointer-events-none select-none\" />\n\t\t\t\t\t\t\t<span className=\"leading-none bg-transparent text-white text-center select-none w-16\" style={{ textShadow: \"0 0 4px #00000052\" }}>\n\t\t\t\t\t\t\t\t{item.name.length > 12 ? `${item.name.slice(0, 10)}...` : item.name}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\t})}\n\t\t</div>\n\t);\n};\n\ninterface WindowAreaProps {\n\tclassName: string;\n}\n\nconst WindowArea: React.FC<WindowAreaProps> = ({ className }) => {\n\tconst windowStore = useWindowStore();\n\tconst [prevShowing, showPrev] = useState(false);\n\tconst [direction, setDirection] = useState<string | null>(null);\n\tconst snapPrev = (pos: string) => {\n\t\tshowPrev(true);\n\t\tsetDirection(pos);\n\t};\n\tconst FinishSnap = () => {\n\t\tshowPrev(false);\n\t};\n\tconst setClass = () => {\n\t\tswitch (direction) {\n\t\t\tcase \"left\":\n\t\t\t\treturn `\n\t\t\t\t\tleft-0 w-6/12 h-full\n\t\t\t\t\t${prevShowing ? \"translate-x-0\" : \"-translate-x-4\"}\n\t\t\t\t`;\n\t\t\tcase \"right\":\n\t\t\t\treturn `\n\t\t\t\t\tright-0 w-6/12 h-full\n\t\t\t\t\t${prevShowing ? \"translate-x-0\" : \"translate-x-4\"}\n\t\t\t\t`;\n\t\t\tcase \"top\":\n\t\t\t\treturn `\n\t\t\t\t\tleft-0 right-0 w-full h-full\n\t\t\t\t\t${prevShowing ? \"translate-y-0\" : \"-translate-y-4\"}\n\t\t\t\t`;\n\t\t\tcase \"top-left\":\n\t\t\t\treturn `\n\t\t\t\t\tleft-0 top-0 w-6/12 h-6/12\n\t\t\t\t\t${prevShowing ? \"translate-x-0 translate-y-0\" : \"-translate-x-4 -translate-y-4\"}\n\t\t\t\t`;\n\t\t\tcase \"top-right\":\n\t\t\t\treturn `\n\t\t\t\t\tright-0 top-0 w-6/12 h-6/12\n\t\t\t\t\t${prevShowing ? \"translate-x-0 translate-y-0\" : \"translate-x-4 -translate-y-4\"}\n\t\t\t\t`;\n\t\t\tcase \"bottom-left\":\n\t\t\t\treturn `\n\t\t\t\t\tleft-0 top-[50%] w-6/12 h-6/12\n\t\t\t\t\t${prevShowing ? \"translate-x-0 translate-y-0\" : \"-translate-x-4 translate-y-4\"}\n\t\t\t\t`;\n\t\t\tcase \"bottom-right\":\n\t\t\t\treturn `\n\t\t\t\t\tright-0 top-[50%] w-6/12 h-6/12\n\t\t\t\t\t${prevShowing ? \"translate-x-0 translate-y-0\" : \"translate-x-4 translate-y-4\"}\n\t\t\t\t`;\n\t\t}\n\t};\n\n\treturn (\n\t\t// @ts-ignore\n\t\t<window-area\n\t\t\tclass={`${className ?? className} relative`}\n\t\t\t// @ts-ignore\n\t\t\tonContextMenuCapture={(e: MouseEvent) => {\n\t\t\t\tconst pos = { x: e.clientX, y: e.clientY };\n\t\t\t\twindow.tb.contextmenu.create({\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"Change Wallpaper\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindow.tb.window.create({\n\t\t\t\t\t\t\t\t\ttitle: \"Settings\",\n\t\t\t\t\t\t\t\t\ticon: \"/fs/apps/system/settings.tapp/icon.svg\",\n\t\t\t\t\t\t\t\t\tsrc: \"/fs/apps/system/settings.tapp/index.html\",\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"New Folder\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindow.tb.dialog.Message({\n\t\t\t\t\t\t\t\t\ttitle: \"Enter the new name of the folder\",\n\t\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\t\tconst user = sessionStorage.getItem(\"currAcc\");\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.mkdir(`/home/${user}/desktop/${val}`);\n\t\t\t\t\t\t\t\t\t\tconst desktopConfig = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\tconst getLastItem = () => {\n\t\t\t\t\t\t\t\t\t\t\tfor (let i = desktopConfig.length - 1; i >= 0; i--) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!desktopConfig[i].position.custom) {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn desktopConfig[i];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tconst lastItem = getLastItem();\n\t\t\t\t\t\t\t\t\t\tconst highestLeft = Math.max(...desktopConfig.map((item: any) => item.position.left));\n\t\t\t\t\t\t\t\t\t\tlet topPos = 0;\n\t\t\t\t\t\t\t\t\t\tlet leftPos = 0;\n\n\t\t\t\t\t\t\t\t\t\tif (lastItem && lastItem.position.top < 11) {\n\t\t\t\t\t\t\t\t\t\t\ttopPos = Math.floor(lastItem.position.top + 1);\n\t\t\t\t\t\t\t\t\t\t\tleftPos = lastItem.position.left;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tleftPos = Math.floor(highestLeft + 1);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tdesktopConfig.push({\n\t\t\t\t\t\t\t\t\t\t\tname: val,\n\t\t\t\t\t\t\t\t\t\t\titem: `/home/${user}/desktop/${val}`,\n\t\t\t\t\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopConfig, null, 4));\n\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"New File\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twindow.tb.dialog.Message({\n\t\t\t\t\t\t\t\t\ttitle: \"Enter the new name of the file\",\n\t\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\t\tconst user = sessionStorage.getItem(\"currAcc\");\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/${val}`, \"\", \"utf8\");\n\t\t\t\t\t\t\t\t\t\tconst desktopConfig = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\tconst getLastItem = () => {\n\t\t\t\t\t\t\t\t\t\t\tfor (let i = desktopConfig.length - 1; i >= 0; i--) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (!desktopConfig[i].position.custom) {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn desktopConfig[i];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tconst lastItem = getLastItem();\n\t\t\t\t\t\t\t\t\t\tconst highestLeft = Math.max(...desktopConfig.map((item: any) => item.position.left));\n\t\t\t\t\t\t\t\t\t\tlet topPos = 0;\n\t\t\t\t\t\t\t\t\t\tlet leftPos = 0;\n\n\t\t\t\t\t\t\t\t\t\tif (lastItem && lastItem.position.top < 11) {\n\t\t\t\t\t\t\t\t\t\t\ttopPos = Math.floor(lastItem.position.top + 1);\n\t\t\t\t\t\t\t\t\t\t\tleftPos = lastItem.position.left;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tleftPos = Math.floor(highestLeft + 1);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tdesktopConfig.push({\n\t\t\t\t\t\t\t\t\t\t\tname: val,\n\t\t\t\t\t\t\t\t\t\t\titem: `/home/${user}/desktop/${val}`,\n\t\t\t\t\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopConfig, null, 4));\n\t\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: \"New Shortcut\",\n\t\t\t\t\t\t\tclick: async () => {\n\t\t\t\t\t\t\t\tconst make = async (item: any) => {\n\t\t\t\t\t\t\t\t\tconst user = sessionStorage.getItem(\"currAcc\");\n\t\t\t\t\t\t\t\t\tconst desktopConfig = JSON.parse(await window.tb.fs.promises.readFile(`/home/${user}/desktop/.desktop.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\tconst getLastItem = () => {\n\t\t\t\t\t\t\t\t\t\tfor (let i = desktopConfig.length - 1; i >= 0; i--) {\n\t\t\t\t\t\t\t\t\t\t\tif (!desktopConfig[i].position.custom) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn desktopConfig[i];\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\tconst lastItem = getLastItem();\n\t\t\t\t\t\t\t\t\tconst highestLeft = Math.max(...desktopConfig.map((item: any) => item.position.left));\n\t\t\t\t\t\t\t\t\tlet topPos = 0;\n\t\t\t\t\t\t\t\t\tlet leftPos = 0;\n\n\t\t\t\t\t\t\t\t\tif (lastItem && lastItem.position.top < 11) {\n\t\t\t\t\t\t\t\t\t\ttopPos = Math.floor(lastItem.position.top + 1);\n\t\t\t\t\t\t\t\t\t\tleftPos = lastItem.position.left;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tleftPos = Math.floor(highestLeft + 1);\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif (topPos * 66 > window.innerHeight - 130) {\n\t\t\t\t\t\t\t\t\t\tleftPos = 1.3;\n\t\t\t\t\t\t\t\t\t\ttopPos = 0;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tconst aname = item.split(\"/\").pop();\n\t\t\t\t\t\t\t\t\tif (aname.includes(\".tapp\")) {\n\t\t\t\t\t\t\t\t\t\tlet tconf: any;\n\t\t\t\t\t\t\t\t\t\tif (await fileExists(`${item}/.tbconfig`)) {\n\t\t\t\t\t\t\t\t\t\t\ttconf = JSON.parse(await window.tb.fs.promises.readFile(`${item}/.tbconfig`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\ttconf = JSON.parse(await window.tb.fs.promises.readFile(`${item}/index.json`, \"utf8\"));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(\n\t\t\t\t\t\t\t\t\t\t\t`${item}/desktopcfg.json`,\n\t\t\t\t\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\t\t\t\t\tname: aname.replace(\".tapp\", \"\"),\n\t\t\t\t\t\t\t\t\t\t\t\tconfig: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t...(tconf.wmArgs ? tconf.wmArgs : tconf.config),\n\t\t\t\t\t\t\t\t\t\t\t\t\ticon: `/fs/${item}/${tconf.wmArgs ? tconf.wmArgs.icon : tconf.config.icon}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\tsrc: `/fs/${item}/${tconf.wmArgs ? tconf.wmArgs.src : tconf.config.src}`,\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\ticon: `/fs/${item}/${tconf.icon}`,\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.symlink(`${item}/desktopcfg.json`, `/home/${user}/desktop/${aname.replace(\".tapp\", \"\")}.lnk`, \"file\");\n\t\t\t\t\t\t\t\t\t\tdesktopConfig.push({\n\t\t\t\t\t\t\t\t\t\t\tname: aname.replace(\".tapp\", \"\"),\n\t\t\t\t\t\t\t\t\t\t\titem: `/home/${user}/desktop/${aname.replace(\".tapp\", \"\")}.lnk`,\n\t\t\t\t\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tdesktopConfig.push({\n\t\t\t\t\t\t\t\t\t\t\tname: aname.replace(\".tapp\", \"\"),\n\t\t\t\t\t\t\t\t\t\t\titem: item,\n\t\t\t\t\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\t\t\t\t\tcustom: false,\n\t\t\t\t\t\t\t\t\t\t\t\ttop: topPos,\n\t\t\t\t\t\t\t\t\t\t\t\tleft: leftPos,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tawait window.tb.fs.promises.writeFile(`/home/${user}/desktop/.desktop.json`, JSON.stringify(desktopConfig, null, 4));\n\t\t\t\t\t\t\t\t\twindow.dispatchEvent(new Event(\"upd-desktop\"));\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tawait window.tb.dialog.Select({\n\t\t\t\t\t\t\t\t\ttitle: \"Select the type of Shortcut\",\n\t\t\t\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttext: \"Application\",\n\t\t\t\t\t\t\t\t\t\t\tvalue: \"app\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttext: \"Folder\",\n\t\t\t\t\t\t\t\t\t\t\tvalue: \"dir\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttext: \"File\",\n\t\t\t\t\t\t\t\t\t\t\tvalue: \"file\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\t\tswitch (val) {\n\t\t\t\t\t\t\t\t\t\t\tcase \"app\":\n\t\t\t\t\t\t\t\t\t\t\t\twindow.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tfilter: \".tapp\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmake(val);\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\tcase \"dir\":\n\t\t\t\t\t\t\t\t\t\t\t\twindow.tb.dialog.DirectoryBrowser({\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmake(val);\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\tcase \"file\":\n\t\t\t\t\t\t\t\t\t\t\t\twindow.tb.dialog.FileBrowser({\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: \"Select a application\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tonOk: async (val: any) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmake(val);\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tx: pos.x,\n\t\t\t\t\ty: pos.y,\n\t\t\t\t});\n\t\t\t}}\n\t\t>\n\t\t\t<DesktopItems />\n\t\t\t{windowStore.windows.map((window: any) => {\n\t\t\t\treturn <MemoizedWindowElement key={window.wid} config={window} onSnapPreview={snapPrev} onSnapDone={FinishSnap} />;\n\t\t\t})}\n\t\t\t<div\n\t\t\t\tclassName={\n\t\t\t\t\t`\n                    absolute top-0 bottom-0 rounded-lg backdrop-blur bg-slate-700 bg-opacity-50 duration-150 bg-[url(/assets/img/grain.png)] pointer-events-none\n                    ${prevShowing ? \"opacity-100 duration-200\" : \"opacity-0\"}\n                ` +\n\t\t\t\t\t\" \" +\n\t\t\t\t\tsetClass()\n\t\t\t\t}\n\t\t\t/>\n\t\t</window-area>\n\t);\n};\n\nexport const createWindow = async (config: WindowConfig) => {\n\tconst windowStore = useWindowStore.getState();\n\tif (config.single) {\n\t\tconst eWindow = windowStore.windows.find(window => window.src === config.src);\n\t\tif (eWindow) {\n\t\t\tif (config.message) {\n\t\t\t\twindow.postMessage(config.message, \"*\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\n\tconst addWindow = useWindowStore.getState().addWindow;\n\taddWindow(config);\n\treturn true;\n};\n\nexport const removeWindow = (wid: string) => {\n\t// Did this for adding windows via COM\n\tconst removeWindow = useWindowStore.getState().removeWindow;\n\tremoveWindow(wid);\n};\n\nexport const killWindow = (wid: string) => {\n\t// Did this for adding windows via COM\n\tconst killWindow = useWindowStore.getState().killWindow;\n\tkillWindow(wid);\n};\n\nexport default WindowArea;\n"
  },
  {
    "path": "src/sys/gui/styles/boot.css",
    "content": "/**\n * Copyright 2026 snoot\n *\n * Licensed under the AGPL License, Version 3.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.gnu.org/licenses/agpl-3.0.en.html\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.opt .back {\n\tscale: 0.96;\n\ttransition: 120ms ease-in-out;\n\tborder: 1px solid transparent;\n}\n\n.opt {\n\tcursor: var(--cursor-pointer);\n}\n\n.opt:hover .back {\n\tscale: 0.97;\n\tbackground-color: #ffffff0a;\n\tborder: 1px solid #ffffff21;\n}\n\n.opt:hover .text {\n\tcolor: #6f6f6f;\n}\n\n.version {\n\tmargin-left: 4px;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/contextmenu.css",
    "content": "@keyframes fade-in {\n\t0% {\n\t\topacity: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes fade-out {\n\t0% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n\n.fade-in {\n\tanimation: fade-in 150ms ease-in-out forwards;\n}\n\n.fade-out {\n\tanimation: fade-out 150ms ease-in-out forwards;\n\tpointer-events: none;\n}\n\n.context-menu {\n\tposition: absolute;\n\tz-index: 9999;\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: max-content;\n\tborder-radius: 8px;\n\toverflow: hidden;\n\tbackground-color: #ffffff10;\n\tcolor: #ffffff;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n}\n\n.context-menu-button {\n\tdisplay: flex;\n\tfont-size: 18px;\n\tfont-weight: 700;\n\tline-height: 1;\n\tpadding: 10px 14px;\n\ttransition: 150ms ease-in-out;\n\twidth: 100%;\n\tuser-select: none;\n\tcursor: var(--cursor-pointer);\n}\n\n.context-menu-button:hover {\n\tbackground-color: #ffffff3c;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/cropper.css",
    "content": "/*\n * Cropper.js v1.6.1\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2023-09-17T03:44:17.565Z\n */\n\n.cropper-container {\n\tdirection: ltr;\n\tfont-size: 0;\n\tline-height: 0;\n\tposition: relative;\n\t-ms-touch-action: none;\n\ttouch-action: none;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\n.cropper-container img {\n\tbackface-visibility: hidden;\n\tdisplay: block;\n\theight: 100%;\n\timage-orientation: 0deg;\n\tmax-height: none !important;\n\tmax-width: none !important;\n\tmin-height: 0 !important;\n\tmin-width: 0 !important;\n\twidth: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n\tbottom: 0;\n\tleft: 0;\n\tposition: absolute;\n\tright: 0;\n\ttop: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n\toverflow: hidden;\n}\n\n.cropper-drag-box {\n\tbackground-color: #fff;\n\topacity: 0;\n}\n\n.cropper-modal {\n\tbackground-color: #000;\n\topacity: 0.5;\n}\n\n.cropper-view-box {\n\tdisplay: block;\n\theight: 100%;\n\toutline: 1px solid #39f;\n\toutline-color: rgba(51, 153, 255, 0.75);\n\toverflow: hidden;\n\twidth: 100%;\n}\n\n.cropper-dashed {\n\tborder: 0 dashed #eee;\n\tdisplay: block;\n\topacity: 0.5;\n\tposition: absolute;\n}\n\n.cropper-dashed.dashed-h {\n\tborder-bottom-width: 1px;\n\tborder-top-width: 1px;\n\theight: calc(100% / 3);\n\tleft: 0;\n\ttop: calc(100% / 3);\n\twidth: 100%;\n}\n\n.cropper-dashed.dashed-v {\n\tborder-left-width: 1px;\n\tborder-right-width: 1px;\n\theight: 100%;\n\tleft: calc(100% / 3);\n\ttop: 0;\n\twidth: calc(100% / 3);\n}\n\n.cropper-center {\n\tdisplay: block;\n\theight: 0;\n\tleft: 50%;\n\topacity: 0.75;\n\tposition: absolute;\n\ttop: 50%;\n\twidth: 0;\n}\n\n.cropper-center::before,\n.cropper-center::after {\n\tbackground-color: #eee;\n\tcontent: \" \";\n\tdisplay: block;\n\tposition: absolute;\n}\n\n.cropper-center::before {\n\theight: 1px;\n\tleft: -3px;\n\ttop: 0;\n\twidth: 7px;\n}\n\n.cropper-center::after {\n\theight: 7px;\n\tleft: 0;\n\ttop: -3px;\n\twidth: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n\tdisplay: block;\n\theight: 100%;\n\topacity: 0.1;\n\tposition: absolute;\n\twidth: 100%;\n}\n\n.cropper-face {\n\tbackground-color: #fff;\n\tleft: 0;\n\ttop: 0;\n}\n\n.cropper-line {\n\tbackground-color: #39f;\n}\n\n.cropper-line.line-e {\n\tcursor: var(--cursor-e-resize);\n\tright: -3px;\n\ttop: 0;\n\twidth: 5px;\n}\n\n.cropper-line.line-n {\n\tcursor: var(--cursor-n-resize);\n\theight: 5px;\n\tleft: 0;\n\ttop: -3px;\n}\n\n.cropper-line.line-w {\n\tcursor: var(--cursor-w-resize);\n\tleft: -3px;\n\ttop: 0;\n\twidth: 5px;\n}\n\n.cropper-line.line-s {\n\tbottom: -3px;\n\tcursor: var(--cursor-s-resize);\n\theight: 5px;\n\tleft: 0;\n}\n\n.cropper-point {\n\tbackground-color: #39f;\n\theight: 5px;\n\topacity: 0.75;\n\twidth: 5px;\n}\n\n.cropper-point.point-e {\n\tcursor: var(--cursor-e-resize);\n\tmargin-top: -3px;\n\tright: -3px;\n\ttop: 50%;\n}\n\n.cropper-point.point-n {\n\tcursor: var(--cursor-n-resize);\n\tleft: 50%;\n\tmargin-left: -3px;\n\ttop: -3px;\n}\n\n.cropper-point.point-w {\n\tcursor: var(--cursor-w-resize);\n\tleft: -3px;\n\tmargin-top: -3px;\n\ttop: 50%;\n}\n\n.cropper-point.point-s {\n\tbottom: -3px;\n\tcursor: var(--cursor-s-resize);\n\tleft: 50%;\n\tmargin-left: -3px;\n}\n\n.cropper-point.point-ne {\n\tcursor: var(--cursor-ne-resize);\n\tright: -3px;\n\ttop: -3px;\n}\n\n.cropper-point.point-nw {\n\tcursor: var(--cursor-nw-resize);\n\tleft: -3px;\n\ttop: -3px;\n}\n\n.cropper-point.point-sw {\n\tbottom: -3px;\n\tcursor: var(--cursor-sw-resize);\n\tleft: -3px;\n}\n\n.cropper-point.point-se {\n\tbottom: -3px;\n\tcursor: var(--cursor-se-resize);\n\theight: 20px;\n\topacity: 1;\n\tright: -3px;\n\twidth: 20px;\n}\n\n@media (min-width: 768px) {\n\t.cropper-point.point-se {\n\t\theight: 15px;\n\t\twidth: 15px;\n\t}\n}\n\n@media (min-width: 992px) {\n\t.cropper-point.point-se {\n\t\theight: 10px;\n\t\twidth: 10px;\n\t}\n}\n\n@media (min-width: 1200px) {\n\t.cropper-point.point-se {\n\t\theight: 5px;\n\t\topacity: 0.75;\n\t\twidth: 5px;\n\t}\n}\n\n.cropper-point.point-se::before {\n\tbackground-color: #39f;\n\tbottom: -50%;\n\tcontent: \" \";\n\tdisplay: block;\n\theight: 200%;\n\topacity: 0;\n\tposition: absolute;\n\tright: -50%;\n\twidth: 200%;\n}\n\n.cropper-invisible {\n\topacity: 0;\n}\n\n.cropper-bg {\n\tbackground-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC\");\n}\n\n.cropper-hide {\n\tdisplay: block;\n\theight: 0;\n\tposition: absolute;\n\twidth: 0;\n}\n\n.cropper-hidden {\n\tdisplay: none !important;\n}\n\n.cropper-move {\n\tcursor: var(--cursor-crosshair);\n}\n\n.cropper-crop {\n\tcursor: var(--cursor-crosshair);\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n\tcursor: not-allowed;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/dialog.css",
    "content": "@keyframes fade-in {\n\tfrom {\n\t\topacity: 0;\n\t}\n\tto {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes fade-out {\n\tfrom {\n\t\topacity: 1;\n\t}\n\tto {\n\t\topacity: 0;\n\t}\n}\n\n.dialog-container {\n\tposition: fixed;\n\tinset: 0;\n\tbackground-color: #00000078;\n\tbackdrop-filter: blur(6px);\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\ttransition: 150ms ease-in-out;\n\tz-index: 9999999999999999999999;\n}\n\n.dialog-container.fade-in {\n\tanimation: fade-in 150ms ease-in-out;\n}\n\n.dialog-container.fade-out {\n\tanimation: fade-out 150ms ease-in-out;\n}\n\n@media screen and (max-width: 1920px) {\n\t.dialog-container .dialog {\n\t\tmin-width: 600px;\n\t}\n}\n\n@media screen and (max-width: 800px) {\n\t.dialog-container .dialog {\n\t\tmin-width: 400px;\n\t}\n}\n\n@media screen and (max-width: 645px) {\n\t.dialog-container .dialog {\n\t\tmin-width: 340px;\n\t}\n}\n\n.dialog-container .dialog {\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground-color: #ffffff28;\n\tcolor: #ffffff;\n\tpadding: 10px;\n\tgap: 10px;\n\tbackdrop-filter: blur(100px);\n\tborder-radius: 12px;\n}\n\n.dialog-container .dialog .dialog-title {\n\tfont-family: Inter;\n\tfont-size: 20px;\n\tline-height: 20px;\n\tfont-weight: 700;\n\tpadding: 4px 0px 4px 4px;\n}\n\n.dialog-container .dialog .dialog-message {\n\tfont-family: Inter;\n\tfont-size: 20px;\n\tline-height: 20px;\n\tfont-weight: 700;\n\tpadding: 4px 0px 4px 4px;\n\ttext-align: center;\n}\n\n.dialog-container .dialog .dialog-files {\n\tposition: relative;\n\tmin-height: 100px;\n\tmax-height: 300px;\n\tbackground-color: #00000068;\n\tborder: 2px solid #ffffff28;\n\tborder-radius: 8px;\n}\n\n.dialog-container .dialog .dialog-buttons {\n\tdisplay: flex;\n\tjustify-content: space-between;\n}\n\n.dialog-container .dialog .dialog-buttons .disabled {\n\tbackground-color: #ffffff10;\n\tfont-family: Inter;\n\tfont-size: 16px;\n\tfont-weight: 800;\n\tline-height: 16px;\n\tpadding: 12px 12px;\n\tborder-radius: 8px;\n\tborder: 2px solid #ffffff28;\n\ttransition: 150ms ease-in-out;\n\tcursor: var(--cursor-normal);\n\tz-index: -999;\n}\n\n.dialog-container .dialog .dialog-buttons .dialog-action-buttons {\n\tdisplay: flex;\n\tgap: 10px;\n}\n\n.dialog-container .dialog .dialog-input {\n\tbackground-color: #ffffff38;\n\tborder: none;\n\toutline: none;\n\tcolor: #ffffff;\n\tpadding: 4px 8px;\n\tborder-radius: 6px;\n\ttransition: 150ms ease-in-out;\n\tcursor: var(--cursor-text);\n\tfont-weight: 600;\n}\n\n.dialog-container .dialog .file-info {\n\tmargin-top: 10px;\n\tbackground-color: #21212130;\n}\n\n.dialog-container .dialog .file-info:hover {\n\tmargin-top: 10px;\n\tbackground-color: #58585830;\n}\n\n.dialog-container .dialog .file-name {\n\tmargin-top: -34px;\n\tmargin-left: 56px;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/dock.css",
    "content": "::-webkit-scrollbar {\n\twidth: 8px;\n\theight: 8px;\n}\n\n::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff28;\n\tborder-radius: 8px;\n}\n\n::-webkit-scrollbar-track {\n\tbackground-color: #ffffff10;\n\tborder-radius: 8px;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/dropdown.css",
    "content": ".dropdown {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.dropdown .dropdown-title {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 6px 8px 12px;\n\tborder-radius: 8px;\n\tgap: 20px;\n\tcolor: #ffffff;\n\tbackdrop-filter: blur(20px);\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n\tbackground-color: #ffffff10;\n\tfont-weight: 700;\n\tcursor: var(--cursor-pointer);\n\ttransition: all 150ms ease-in-out;\n}\n\n.dropdown .dropdown-title:hover {\n\tbackground-color: #ffffff28;\n}\n\n.dropdown .dropdown-title svg {\n\tstroke-width: 0.8px;\n\tstroke: #ffffff;\n}\n\n.dropdown .dropdown-options {\n\tdisplay: flex;\n\tflex-direction: column;\n\tposition: absolute;\n\ttop: calc(100% + 6px);\n\tleft: 0;\n\twidth: 100%;\n\topacity: 1;\n\tpointer-events: all;\n\tborder-radius: 8px;\n\tcolor: #ffffff;\n\tbackdrop-filter: blur(20px);\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n\tbackground-color: #ffffff10;\n\ttransition: 150ms ease-in-out;\n\toverflow: hidden;\n}\n\n@keyframes not-active {\n\t100% {\n\t\theight: 0px;\n\t}\n}\n\n.dropdown .dropdown-options:not(.active) {\n\topacity: 0;\n\tpointer-events: none;\n\tanimation: not-active 160ms ease-in-out forwards;\n}\n\n.dropdown .dropdown-options .dropdown-option {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\twidth: 100%;\n\tpadding: 8px 12px;\n\tfont-weight: 600;\n\tcursor: var(--cursor-pointer);\n\ttransition: all 150ms ease-in-out;\n}\n\n.dropdown .dropdown-options .dropdown-option:hover {\n\tbackground-color: #ffffff28;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/liquor.css",
    "content": "@keyframes fade-in {\n\t0% {\n\t\topacity: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes fade-out {\n\t0% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n\n.fade-in {\n\tanimation: fade-in 150ms ease-in-out forwards;\n}\n\n.fade-out {\n\tanimation: fade-out 150ms ease-in-out forwards;\n}\n\nbody .context-menu {\n\tbackground-color: #2020208c;\n\tcolor: #ffffff;\n}\n\n.context-menu {\n\tbackdrop-filter: brightness(0.8) blur(100px);\n}\n\n.custom-menu {\n\tposition: absolute;\n\tbackground-color: #ffffff1f;\n\tcolor: #fff;\n\tz-index: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 200px;\n\tborder-radius: 8px;\n\toverflow: hidden;\n\tz-index: 99999;\n}\n\n.custom-menu-item {\n\tbackground-color: transparent;\n\tdisplay: flex;\n\tfont-size: 15.5px;\n\tfont-weight: 700;\n\tline-height: 18px;\n\tpadding: 8px 8px;\n\tborder-color: transparent;\n\ttransition: 150ms ease-in-out;\n}\n\nbody .custom-menu-item:hover {\n\tbackground-color: #ffffff1f;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/loader.css",
    "content": "@keyframes breathe {\n\t0%,\n\t100% {\n\t\ttransform: scale(1);\n\t\topacity: 0.1;\n\t}\n\t50% {\n\t\ttransform: scale(1.1);\n\t\topacity: 1;\n\t}\n}\n\n.breathe {\n\tanimation: breathe 2s infinite;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/login.css",
    "content": ".diffuse {\n\tfilter: blur(10px);\n\tbackground: linear-gradient(120deg, #ff8686, #e09dff, #7ca1ff, #e09dff, #ff8686);\n}\n\n.broken_button {\n\theight: 42px;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/mediaisland.css",
    "content": ".music-player {\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: space-between;\n\talign-items: flex-start;\n\tpadding: 20px;\n}\n\n.info {\n\ttext-align: left;\n\tmargin-top: -19px;\n}\n\n.playerctrl {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: center;\n\tmargin-bottom: 24px;\n\tmargin-top: -30px;\n\tmargin-left: 154px;\n}\n\n.playerctrl button {\n\tmargin-left: 10px;\n\twidth: 16px;\n}\n\n.seekbar {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\twidth: 100%;\n\tmargin-top: -12px;\n}\n\n.bar {\n\tflex-grow: 1;\n\theight: 5px;\n\tbackground-color: #7d7d7d;\n\tmargin: 0 10px;\n\tborder-radius: 6px;\n}\n\n#currenttime,\n#endtime {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: white;\n}\n\n.track {\n\tcolor: white;\n\tfont-size: 12px;\n\tfont-weight: bolder;\n}\n\n.artist {\n\tcolor: #b8b8b8;\n\tfont-weight: bolder;\n\tfont-size: 10px;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/notification.css",
    "content": ":root {\n\tfont-family: Inter;\n\tcolor-scheme: light dark;\n\tcolor: #ffffffde;\n\tbackground-color: #000000;\n\t--cursor-normal: url(\"/cursors/dark/normal.svg\") 6 0, default;\n\t--cursor-pointer: url(\"/cursors/dark/pointer.svg\") 6 0, pointer;\n\t--cursor-text: url(\"/cursors/dark/text.svg\") 10 0, text;\n\t--cursor-crosshair: url(\"/cursors/dark/crosshair.svg\") 0 0, crosshair;\n\t--cursor-wait: url(\"/cursors/dark/wait.svg\") 0 0, wait;\n\t--cursor-nw-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, nw-resize;\n\t--cursor-se-resize: url(\"/cursors/dark/resize-l.svg\") 0 0, se-resize;\n\t--cursor-sw-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, sw-resize;\n\t--cursor-ne-resize: url(\"/cursors/dark/resize-r.svg\") 0 0, ne-resize;\n\t--cursor-n-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, n-resize;\n\t--cursor-s-resize: url(\"/cursors/dark/resize-v.svg\") 0 0, s-resize;\n\t--cursor-e-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, e-resize;\n\t--cursor-w-resize: url(\"/cursors/dark/resize-h.svg\") 0 0, w-resize;\n}\n\n@keyframes anim0 {\n\t0% {\n\t\tleft: -35%;\n\t\tright: 100%;\n\t}\n\t60% {\n\t\tleft: 100%;\n\t\tright: -90%;\n\t}\n\t100% {\n\t\tleft: 100%;\n\t\tright: -90%;\n\t}\n}\n\n@keyframes anim1 {\n\t0% {\n\t\tleft: -200%;\n\t\tright: 100%;\n\t}\n\t60% {\n\t\tleft: 107%;\n\t\tright: -8%;\n\t}\n\n\t100% {\n\t\tleft: 107%;\n\t\tright: -8%;\n\t}\n}\n\n@keyframes fade-in {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(-20px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n@keyframes fade-out {\n\tfrom {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n\tto {\n\t\topacity: 0;\n\t\ttransform: translateY(-20px);\n\t}\n}\n\n.notification-grid {\n\tposition: fixed;\n\ttop: 60px;\n\tright: 10px;\n\twidth: 360px;\n\theight: max-content;\n\tdisplay: grid;\n\tgrid-template-columns: 300px;\n\tgrid-template-rows: auto;\n\tgap: 10px;\n\tz-index: 9999;\n}\n\n.notification {\n\theight: 150px;\n\twidth: inherit;\n\tfont-family: Inter;\n\tbackground-color: #20202066;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n\tpadding: 10px;\n\ttransition: 150ms ease-in-out;\n\tborder-radius: 12px;\n\tcolor: white;\n\tz-index: 9999;\n\tbackground-image: url(\"/assets/img/notif_bg.svg\");\n\tbackground-repeat: no-repeat;\n\tbackground-position: center;\n\tbackground-size: cover;\n\tbackdrop-filter: blur(100px);\n}\n\n.fade-in {\n\tanimation: fade-in 150ms ease-in-out;\n}\n\n.fade-out {\n\tanimation: fade-out 150ms ease-in-out;\n}\n\n.notification-info {\n\tdisplay: flex;\n\tjustify-content: space-between;\n}\n\n.notification .notification-buttons {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tposition: absolute;\n\tbottom: 10px;\n\tleft: 10px;\n\tright: 10px;\n}\n\n.notification .notification-buttons button {\n\tbackground-color: #ffffff48;\n\tfont-family: Inter;\n\tfont-size: 16px;\n\tfont-weight: 800;\n\tline-height: 16px;\n\tpadding: 8px 12px;\n\tborder-radius: 8px;\n\tborder: 2px solid #ffffff28;\n\tcursor: var(--cursor-pointer);\n\ttransition: 150ms ease-in-out;\n}\n\n.notification .notification-buttons button:hover {\n\tbackground-color: #ffffff68;\n\tborder: 2px solid #ffffff40;\n\tcursor: var(--cursor-pointer);\n}\n\n.notification .notification-buttons button.ok-button {\n\tbackground-color: #53f6748d;\n\tcursor: var(--cursor-pointer);\n}\n\n.notification .notification-buttons button.ok-button:hover {\n\tbackground-color: #53f674c5;\n\tcursor: var(--cursor-pointer);\n}\n\n.notification .notification-buttons .notification-action-buttons {\n\tdisplay: flex;\n\tgap: 10px;\n}\n\n.notification .installNoti {\n\tbackground-color: #00000036;\n\tbackground-repeat: no-repeat;\n\tbackground-image: url(\"/assets/img/notif_ins.gif\");\n\tbackground-size: contain;\n\tborder-radius: 13px;\n\twidth: 99%;\n\theight: 22px;\n\tmargin-top: 3.5px;\n}\n\n.notification .notification-input {\n\tbackground-color: #ffffff38;\n\tborder: none;\n\toutline: none;\n\tcolor: #ffffff;\n\tpadding: 4px 8px;\n\tborder-radius: 6px;\n\ttransition: 150ms ease-in-out;\n\tfont-weight: 600;\n\tcursor: var(--cursor-text);\n}\n\n.notification .notification-img {\n\twidth: 25px;\n\theight: 25px;\n}\n\n.notification .notification-application {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-weight: bolder;\n}\n\n.notification .notification-time {\n\tfont-weight: bolder;\n\tcolor: #ffffff54;\n}\n\n.notification .notification-message {\n\tfont-weight: bold;\n\tcolor: #ffffffb7;\n}\n\n.notification-center {\n\tposition: fixed;\n\ttop: 60px;\n\tright: 6px;\n\twidth: 400px;\n\theight: calc(100% - calc(60px + 6px));\n\tfont-family: Inter;\n\tbackground-color: #20202066;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n\ttransition: 150ms ease-in-out;\n\tbackdrop-filter: blur(100px);\n\tpadding: 10px;\n\tborder-radius: 12px;\n\tcolor: white;\n\tz-index: 9999;\n}\n\n.notification-center .notifications-list::-webkit-scrollbar {\n\tposition: relative;\n\twidth: 8px;\n\theight: calc(100% - 16px);\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: stretch;\n\tgap: 8px;\n\tpadding: 8px;\n\tpadding-right: 0;\n\toverflow: auto;\n\tscrollbar-width: thin;\n\tscrollbar-color: #ffffff30 transparent;\n}\n\n.notification-center .notifications-list::-webkit-scrollbar-track {\n\tbackground-color: transparent;\n}\n\n.notification-center .notifications-list::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff30;\n\tborder-radius: 8px;\n\tborder: 2px solid transparent;\n}\n\n.notification-center .notifications-list .notification-item .notification-application {\n\tposition: relative;\n\ttop: 0;\n\tleft: 30px;\n\tmargin-top: -25px;\n\tfont-weight: bolder;\n}\n\n.notification-center .notifications-list .notification-item .notification-time {\n\tposition: relative;\n\ttop: 0;\n\tleft: 200px;\n\tmargin-top: -25px;\n}\n\n.notification-center .hidden {\n\tdisplay: none;\n}\n\n.notification-center .notifications-list {\n\toverflow-y: auto;\n\toverflow-x: hidden;\n\tmax-height: 100%;\n}\n\n.notification-center .notifications-list .notification-item {\n\tposition: relative;\n}\n\n.notification-center .notifications-list .notification-item .select {\n\tposition: absolute;\n\ttop: 14px;\n\tright: 69px;\n\tbackground-color: #53f6748d;\n\tfont-family: Inter;\n\tfont-size: 10px;\n\tfont-weight: 800;\n\tline-height: 10px;\n\tpadding: 8px 8px;\n\tborder-radius: 8px;\n\tborder: 2px solid #ffffff28;\n\ttransition: 150ms ease-in-out;\n\tcursor: var(--cursor-pointer);\n}\n\n.notification-center .notifications-list .notification-item .normal {\n\tposition: absolute;\n\ttop: 14px;\n\tright: 4px;\n\tbackground-color: #ffffff48;\n\tfont-family: Inter;\n\tfont-size: 10px;\n\tfont-weight: 800;\n\tline-height: 10px;\n\tpadding: 8px 8px;\n\tborder-radius: 8px;\n\tborder: 2px solid #ffffff28;\n\ttransition: 150ms ease-in-out;\n\tcursor: var(--cursor-pointer);\n}\n"
  },
  {
    "path": "src/sys/gui/styles/oobe.css",
    "content": ".selector {\n\tposition: relative;\n}\n\n.selector .title {\n\tdisplay: flex;\n\tgap: 10px;\n\tjustify-content: space-between;\n\talign-items: center;\n\tbackground-color: #ffffff28;\n\tcolor: #c2bebe;\n\tborder-radius: 8px;\n\tpadding: 10px 10px 10px 10px;\n\tcursor: var(--cursor-pointer);\n\tfont-size: 18px;\n\tfont-weight: 600;\n}\n\n.options {\n\tposition: absolute;\n\ttop: calc(100% + 10px);\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n\tbackground-color: #ffffff28;\n\tcolor: #ffffff;\n\tfont-size: 18px;\n\tpadding: 6px;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\topacity: 0;\n\tpointer-events: none;\n\ttransition: 150ms ease-in-out;\n}\n\n.options.open {\n\topacity: 1;\n\tpointer-events: auto;\n}\n\n.option {\n\tpadding: 8px 8px 6px 10px;\n\tbackground-color: transparent;\n\tborder-radius: 8px;\n\tcolor: #c2bebe;\n\tcursor: var(--cursor-pointer);\n}\n\n.option:hover {\n\tbackground-color: #ffffff28;\n}\n\n.welcome {\n\tcolor: #ffffffa7;\n}\n\n.pag {\n\tbox-shadow: inset 0 0 8px -2px transparent;\n}\n\n.pag:not(:disabled):hover {\n\tbox-shadow: inset 0 0 8px -2px #00000075;\n\tbackground-color: #ffffff10;\n\tcolor: #ffffffcd;\n}\n\n.pag:disabled {\n\tbackground-color: transparent;\n\tborder: 1px solid transparent;\n\tcolor: #ffffff21;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/shell.css",
    "content": ".show_desk {\n\tbackground-color: #ffffff3e;\n}\n\n.island {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tjustify-content: center;\n\tbackdrop-filter: blur(8px);\n\tbackground-color: #2020208c;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n\tcolor: #ffffff;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n\ttransition: 150ms ease-in-out;\n}\n\n/*\n.island.visible {\n    min-height: 48px;\n    border-radius: 8px;\n} */\n\n/* .island.visible:not(.custom-padding):not(.text) {\n    padding: 6px 10px;\n} */\n\n.island.text {\n\tpadding: 6px 16px !important;\n}\n\n.island .app .app_name {\n\tfont-size: 20px;\n\tfont-weight: 700;\n}\n\n.island .app .app_controls {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n}\n\n.island .app .app_controls .app_control {\n\tbackground-color: transparent;\n\tborder: none;\n\tfont-size: 14px;\n\tline-height: 14px;\n\topacity: 0.8;\n\ttransition: 150ms ease-in-out;\n}\n\n.island .app .app_controls .app_control:hover {\n\topacity: 1;\n}\n\n.island .app .app_controls .app_control.hidden {\n\tdisplay: none;\n}\n\nbody.blurry .shell-menu {\n\tbackdrop-filter: blur(8px);\n}\n\n.shell-menu {\n\tbackground-color: #00000028;\n\tcolor: #ffffff;\n}\n\n.shell-menu .shell-menu-item {\n\tcolor: #ffffff;\n}\n\n.shell-menu .shell-menu-item:hover {\n\tcolor: #ffffff;\n\tbackground-color: #ffffff28;\n}\n\n.shell-menu .shell-menu-item {\n\ttransition: 150ms ease-in-out;\n\tcursor: var(--cursor-pointer);\n}\n"
  },
  {
    "path": "src/sys/gui/styles/wifi.css",
    "content": "@keyframes slideIn {\n\t0% {\n\t\topacity: 0;\n\t\ttransform: translateY(-20px);\n\t}\n\t100% {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n@keyframes slideOut {\n\t0% {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n\t100% {\n\t\topacity: 0;\n\t\ttransform: translateY(-20px);\n\t}\n}\n\n.wifi-list {\n\toverflow-y: auto;\n\toverflow-x: hidden;\n\tmax-height: 230px;\n\tdisplay: none;\n}\n\n.wifi-list.open {\n\tdisplay: block;\n\tposition: fixed;\n\ttop: 60px;\n\tright: 6px;\n\twidth: 400px;\n\theight: 230px;\n\tfont-family: Inter;\n\tbackground-color: #20202066;\n\tbox-shadow:\n\t\t0px 0px 6px 0px #00000052,\n\t\tinset 0 0 0 0.5px #ffffff38;\n\ttransition: 150ms ease-in-out;\n\t-webkit-backdrop-filter: blur(100px);\n\tbackdrop-filter: blur(100px);\n\tpadding: 10px;\n\tborder-radius: 12px;\n\tcolor: white;\n\tz-index: 9999;\n\tanimation: slideIn 150ms ease-in-out forwards;\n}\n\n.wifi-list.open .btn.select {\n\tposition: static;\n\tmargin-top: 22px;\n\tmargin-left: auto;\n\twidth: 75px;\n\theight: 28px;\n\tbackground-color: #53f6748d;\n\tfont-family: Inter;\n\tfont-size: 10px;\n\tfont-weight: 800;\n\tline-height: 10px;\n\tpadding: 8px 8px;\n\tborder-radius: 8px;\n\tborder: 2px solid #ffffff28;\n\ttransition: 150ms ease-in-out;\n}\n\n.wifi-list.open .btn {\n\tposition: static;\n\tbackground-color: #ffffff48;\n\tfont-family: Inter;\n\tfont-size: 10px;\n\tfont-weight: 800;\n\tline-height: 10px;\n\tpadding: 8px 8px;\n\tmargin-top: 22px;\n\tmargin-left: auto;\n\twidth: 75px;\n\theight: 28px;\n\tborder-radius: 8px;\n\tborder: 2px solid #ffffff28;\n\ttransition: 150ms ease-in-out;\n}\n\n.wifi-list.open .btn.net {\n\tbackground-color: #ffffff48;\n\tfont-family: Inter;\n\tfont-size: 10px;\n\tfont-weight: 800;\n\tline-height: 10px;\n\tpadding: 8px 8px;\n\tmargin-right: 37%;\n\twidth: 100px;\n\theight: 28px;\n\tborder-radius: 8px;\n\tborder: 2px solid #ffffff28;\n\ttransition: 150ms ease-in-out;\n}\n\n.wifi-list::-webkit-scrollbar {\n\tposition: relative;\n\twidth: 8px;\n\theight: calc(100% - 16px);\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: stretch;\n\tgap: 8px;\n\tpadding: 8px;\n\tpadding-right: 0;\n\toverflow: auto;\n\tscrollbar-width: thin;\n\tscrollbar-color: #ffffff30 transparent;\n}\n\n.wifi-list::-webkit-scrollbar-track {\n\tbackground-color: transparent;\n}\n\n.wifi-list::-webkit-scrollbar-thumb {\n\tbackground-color: #ffffff30;\n\tborder-radius: 8px;\n\tborder: 2px solid transparent;\n}\n"
  },
  {
    "path": "src/sys/gui/styles/win_switcher.css",
    "content": ".win-switcher-backdrop {\n\tposition: fixed;\n\tinset: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 2.25rem;\n\topacity: 0;\n\tpointer-events: none;\n\tbackdrop-filter: blur(8px);\n\tbackground: rgba(10, 10, 10, 0.18);\n\ttransition: opacity 120ms ease-in;\n\tz-index: 999999999;\n}\n\n.win-switcher-backdrop.visible {\n\topacity: 1;\n\tpointer-events: auto;\n}\n\n.win-switcher-panel {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(210px, 1fr));\n\tgap: 0.85rem;\n\twidth: 45%;\n\tmax-height: 100%;\n\toverflow-y: auto;\n\tpadding: 0.95rem;\n\tborder-radius: 0.95rem;\n\tbackground: #2020208c;\n\tbackground-image: url(\"/assets/img/grain.png\");\n\tborder: 1px solid rgba(255, 255, 255, 0.08);\n\tbox-shadow:\n\t\t0 22px 62px rgba(0, 0, 0, 0.56),\n\t\tinset 0 1px 0 rgba(255, 255, 255, 0.12);\n\ttransform: translateY(10px) scale(0.992);\n\ttransition: transform 120ms ease-in;\n}\n\n.win-switcher-panel.visible {\n\ttransform: translateY(0) scale(1);\n}\n\n.win-switcher-item {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0.45rem;\n\tpadding: 0.45rem;\n\tborder-radius: 0.5rem;\n\tbackground: #0f0f0fa8;\n\tborder: 1px solid rgba(255, 255, 255, 0.1);\n\tcolor: #ffffff;\n\ttext-align: left;\n\tcursor: var(--cursor-pointer);\n\ttransition:\n\t\ttransform 120ms ease,\n\t\tbox-shadow 120ms ease,\n\t\tbackground-color 120ms ease,\n\t\tborder-color 120ms ease;\n}\n\n.win-switcher-item:hover {\n\ttransform: translateY(-1px);\n\tbackground: color-mix(in srgb, #32ae62 22%, #0f0f0fa8);\n\tborder-color: color-mix(in srgb, #32ae62 46%, rgba(255, 255, 255, 0.08));\n}\n\n.win-switcher-item.active {\n\tbackground: color-mix(in srgb, #32ae62 30%, #0f0f0fa8);\n\tborder-color: #32ae6294;\n\tbox-shadow:\n\t\t0 0 0 1px #32ae6232,\n\t\t0 0 22px #32ae6248;\n\ttransform: translateY(-2px);\n}\n\n.thumb-frame {\n\tposition: relative;\n\twidth: 100%;\n\theight: 128px;\n\tborder-radius: 0.5rem;\n\toverflow: hidden;\n\tbackground: #1f1f1f;\n\tborder: 1px solid rgba(255, 255, 255, 0.13);\n}\n\n.thumb-image,\n.thumb-fallback {\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tobject-fit: contain;\n}\n\n.thumb-fallback {\n\tbackground: #1f1f1f;\n}\n\n.thumb-fallback-icon {\n\twidth: 58px;\n\theight: 58px;\n\tborder-radius: 0.75rem;\n\topacity: 0.94;\n\tfilter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.42));\n}\n\n.thumb-loading-overlay {\n\tposition: absolute;\n\tinset: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground: #111111b0;\n\tpointer-events: none;\n}\n\n.thumb-loading-spinner {\n\twidth: 34px;\n\theight: 34px;\n}\n\n.item-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 0.45rem;\n\tmin-width: 0;\n\tpadding-inline: 0.15rem;\n\tpadding-block: 0.15rem 0.1rem;\n}\n\n.item-icon {\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 0.2rem;\n\tflex: none;\n}\n\n.item-title {\n\tfont-size: 0.7rem;\n\tfont-weight: 500;\n\tline-height: 1.2;\n\tcolor: rgba(255, 255, 255, 0.9);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.win-switcher-hint {\n\tposition: absolute;\n\tbottom: 1.2rem;\n\tleft: 50%;\n\ttransform: translateX(-50%) translateY(4px);\n\topacity: 0;\n\tpointer-events: none;\n\tpadding: 0.45rem 0.7rem;\n\tfont-size: 0.72rem;\n\tcolor: rgba(231, 244, 255, 0.95);\n\tbackground: rgba(7, 14, 20, 0.6);\n\tborder: 1px solid rgba(192, 227, 255, 0.24);\n\tborder-radius: 0.45rem;\n\tletter-spacing: 0.02em;\n\ttransition:\n\t\topacity 120ms ease,\n\t\ttransform 120ms ease;\n}\n\n.win-switcher-hint.visible {\n\topacity: 1;\n\ttransform: translateX(-50%) translateY(0);\n}\n\n@media (max-width: 780px) {\n\t.win-switcher-backdrop {\n\t\tpadding: 1.15rem;\n\t}\n\n\t.win-switcher-panel {\n\t\tgrid-template-columns: repeat(auto-fit, minmax(165px, 1fr));\n\t\tmax-height: 78vh;\n\t}\n\n\t.thumb-frame {\n\t\theight: 112px;\n\t}\n}\n"
  },
  {
    "path": "src/sys/lemonade/app.ts",
    "content": "interface AppDetails {\n\tname: string;\n\tversion: string;\n}\n\nexport class App {\n\tprivate appDetails: AppDetails = {\n\t\tname: \"Lemonade App\",\n\t\tversion: \"1.0.0\",\n\t};\n\n\tgetName(): string {\n\t\treturn this.appDetails.name;\n\t}\n\n\tsetName(name: string): void {\n\t\tthis.appDetails.name = name;\n\t}\n\n\tgetVersion(): string {\n\t\treturn this.appDetails.version;\n\t}\n\n\tgetPath(name: \"home\" | \"appData\" | \"userData\" | \"temp\" | \"downloads\" | \"documents\" | \"desktop\"): string {\n\t\tconst username = sessionStorage.getItem(\"currAcc\") || \"user\";\n\t\tconst pathMap: Record<typeof name, string> = {\n\t\t\thome: `/home/${username}`,\n\t\t\tappData: `/apps/user/${username}/`,\n\t\t\tuserData: `/home/${username}/lemonade.conf`,\n\t\t\ttemp: `/system/tmp/`,\n\t\t\tdownloads: `/home/${username}/`,\n\t\t\tdocuments: `/home/${username}/Documents`,\n\t\t\tdesktop: `/home/${username}/Desktop`,\n\t\t};\n\t\treturn pathMap[name] || `/home/${username}`;\n\t}\n\n\tgetAppPath(): string {\n\t\treturn \"/\";\n\t}\n\n\tisReady(): boolean {\n\t\treturn true;\n\t}\n\n\twhenReady(): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tquit(): void {\n\t\tconsole.log(\"App quit requested - not implemented in web environment\");\n\t}\n\n\texit(exitCode: number = 0): void {\n\t\tconsole.log(`App exit requested with code ${exitCode} - not implemented in web environment`);\n\t}\n\n\trelaunch(options?: { args?: string[]; execPath?: string }): void {\n\t\tconsole.log(\"App relaunch requested - not implemented in web environment\", options);\n\t\twindow.location.reload();\n\t}\n\n\tfocus(): void {\n\t\twindow.focus();\n\t}\n\n\thide(): void {\n\t\tconsole.log(\"App hide requested - not implemented in web environment\");\n\t}\n\n\tshow(): void {\n\t\tconsole.log(\"App show requested - not implemented in web environment\");\n\t}\n\n\tsetAppLogsPath(path: string): void {\n\t\tconsole.log(`App logs path set to: ${path}`);\n\t}\n\n\tgetLocale(): string {\n\t\treturn navigator.language || \"en-US\";\n\t}\n\n\tgetSystemLocale(): string {\n\t\treturn navigator.language || \"en-US\";\n\t}\n\n\tisPackaged(): boolean {\n\t\treturn false;\n\t}\n\n\trequestSingleInstanceLock(): boolean {\n\t\treturn true;\n\t}\n\n\thasSingleInstanceLock(): boolean {\n\t\treturn true;\n\t}\n\n\treleaseSingleInstanceLock(): void {\n\t\tconsole.log(\"Single instance lock released\");\n\t}\n\n\tsetAsDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean {\n\t\tconsole.log(`Setting as default protocol client for ${protocol}`, path, args);\n\t\treturn false;\n\t}\n\n\tisDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean {\n\t\tconsole.log(`Checking if default protocol client for ${protocol}`, path, args);\n\t\treturn false;\n\t}\n}\n\nexport const app = new App();\n"
  },
  {
    "path": "src/sys/lemonade/clipboard.ts",
    "content": "export class Clipboard {\n\tasync readText(_type?: \"selection\" | \"clipboard\"): Promise<string> {\n\t\ttry {\n\t\t\treturn await navigator.clipboard.readText();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to read clipboard:\", error);\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\tasync writeText(text: string, _type?: \"selection\" | \"clipboard\"): Promise<void> {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(text);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to write to clipboard:\", error);\n\t\t}\n\t}\n\n\tasync readHTML(_type?: \"selection\" | \"clipboard\"): Promise<string> {\n\t\ttry {\n\t\t\tconst items = await navigator.clipboard.read();\n\t\t\tfor (const item of items) {\n\t\t\t\tif (item.types.includes(\"text/html\")) {\n\t\t\t\t\tconst blob = await item.getType(\"text/html\");\n\t\t\t\t\treturn await blob.text();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn \"\";\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to read HTML from clipboard:\", error);\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\tasync writeHTML(markup: string, _type?: \"selection\" | \"clipboard\"): Promise<void> {\n\t\ttry {\n\t\t\tconst blob = new Blob([markup], { type: \"text/html\" });\n\t\t\tawait navigator.clipboard.write([\n\t\t\t\tnew ClipboardItem({\n\t\t\t\t\t\"text/html\": blob,\n\t\t\t\t}),\n\t\t\t]);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to write HTML to clipboard:\", error);\n\t\t}\n\t}\n\n\tasync readImage(_type?: \"selection\" | \"clipboard\"): Promise<any> {\n\t\ttry {\n\t\t\tconst items = await navigator.clipboard.read();\n\t\t\tfor (const item of items) {\n\t\t\t\tfor (const mimeType of item.types) {\n\t\t\t\t\tif (mimeType.startsWith(\"image/\")) {\n\t\t\t\t\t\treturn await item.getType(mimeType);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to read image from clipboard:\", error);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync writeImage(image: any, _type?: \"selection\" | \"clipboard\"): Promise<void> {\n\t\tconsole.log(\"writeImage not fully implemented\", image);\n\t\t// Would need proper image handling\n\t}\n\n\tclear(_type?: \"selection\" | \"clipboard\"): void {\n\t\tthis.writeText(\"\");\n\t}\n\n\tavailableFormats(_type?: \"selection\" | \"clipboard\"): string[] {\n\t\tconsole.log(\"availableFormats called - limited in web environment\");\n\t\treturn [];\n\t}\n\n\thas(format: string, _type?: \"selection\" | \"clipboard\"): boolean {\n\t\tconsole.log(`has(${format}) called - limited in web environment`);\n\t\treturn false;\n\t}\n\n\tread(format: string): string {\n\t\tconsole.log(`read(${format}) called - limited in web environment`);\n\t\treturn \"\";\n\t}\n\n\twrite(data: any, type?: \"selection\" | \"clipboard\"): void {\n\t\tconsole.log(\"write called with data:\", data, type);\n\t\tif (data.text) {\n\t\t\tthis.writeText(data.text, type);\n\t\t}\n\t\tif (data.html) {\n\t\t\tthis.writeHTML(data.html, type);\n\t\t}\n\t}\n\n\treadFindText(): string {\n\t\treturn \"\";\n\t}\n\n\twriteFindText(text: string): void {\n\t\tconsole.log(\"writeFindText:\", text);\n\t}\n\n\treadBookmark(): { title: string; url: string } {\n\t\treturn { title: \"\", url: \"\" };\n\t}\n\n\twriteBookmark(title: string, url: string, type?: \"selection\" | \"clipboard\"): void {\n\t\tconsole.log(`writeBookmark: ${title} - ${url}`, type);\n\t}\n}\n\nexport const clipboard = new Clipboard();\n"
  },
  {
    "path": "src/sys/lemonade/dialog.ts",
    "content": "interface diagArgs {\n\ttitle?: string;\n\tdefaultPath?: string;\n\tproperties?: (\"openFile\" | \"openDirectory\" | \"multiSelections\" | \"showHiddenFiles\")[];\n\tbuttonLabel?: string;\n\tfilters?: { name: string; extensions: string[] }[];\n\tmessage?: string;\n}\n\ninterface MessageBoxOptions {\n\tmessage: string;\n\ttitle?: string;\n\ttype?: \"none\" | \"info\" | \"error\" | \"question\" | \"warning\";\n\tbuttons?: string[];\n\tdefaultId?: number;\n\tcancelId?: number;\n\tnoLink?: boolean;\n\tnormalizeAccessKeys?: boolean;\n}\n\nexport class Dialog {\n\tshowOpenDialogSync(win: any, options: diagArgs) {\n\t\tconsole.log(`property: ${win} wont be used sorry`);\n\t\treturn new Promise((resolve, reject) => {\n\t\t\twindow.tb.dialog.FileBrowser({\n\t\t\t\ttitle: options.title || \"Open File\",\n\t\t\t\tdefualtDir: options.defaultPath || \"/\",\n\t\t\t\tonOk: (path: string) => {\n\t\t\t\t\tresolve(path);\n\t\t\t\t\tconsole.log(path);\n\t\t\t\t},\n\t\t\t\tonCancel: () => reject(\"canceled\"),\n\t\t\t});\n\t\t});\n\t}\n\tshowOpenDialog(win: any, options: diagArgs) {\n\t\treturn this.showOpenDialogSync(win, options);\n\t}\n\tshowSaveDialogSync(win: any, options: diagArgs) {\n\t\tconsole.log(`property: ${win} wont be used sorry`);\n\t\treturn new Promise((resolve, reject) => {\n\t\t\twindow.tb.dialog.SaveFile({\n\t\t\t\ttitle: options.title || \"Save File\",\n\t\t\t\tdefualtDir: options.defaultPath || \"/\",\n\t\t\t\tonOk: (path: string) => {\n\t\t\t\t\tresolve(path);\n\t\t\t\t\tconsole.log(path);\n\t\t\t\t},\n\t\t\t\tonCancel: () => reject(\"canceled\"),\n\t\t\t});\n\t\t});\n\t}\n\tshowSaveDialog(win: any, options: diagArgs) {\n\t\treturn this.showSaveDialogSync(win, options);\n\t}\n\tshowMessageBoxSync(_win: any, options: MessageBoxOptions) {\n\t\tconsole.log(\"Using Terbium dialog system\");\n\t\treturn new Promise(resolve => {\n\t\t\twindow.tb.dialog.Message({\n\t\t\t\ttitle: options.title || \"Message\",\n\t\t\t\tdefaultValue: options.message,\n\t\t\t\tonOk: () => {\n\t\t\t\t\tresolve({ response: options.defaultId || 0, checkboxChecked: false });\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n\tshowMessageBox(win: any, options: MessageBoxOptions) {\n\t\treturn this.showMessageBoxSync(win, options);\n\t}\n\tshowErrorBox(title: string, content: string) {\n\t\twindow.tb.dialog.Alert({\n\t\t\ttitle: title,\n\t\t\tmessage: content,\n\t\t\tonOk: () => {},\n\t\t});\n\t}\n\n\tshowCertificateTrustDialog(_win: any, _options: { certificate: any; message: string }) {\n\t\tconsole.log(\"Certificate trust dialog not implemented in web environment\");\n\t\treturn Promise.resolve();\n\t}\n}\n"
  },
  {
    "path": "src/sys/lemonade/index.ts",
    "content": "import { Notification } from \"./notification\";\nimport { BrowserWindow } from \"./window\";\nimport { Dialog } from \"./dialog\";\nimport { Net } from \"./net\";\nimport { app, App } from \"./app\";\nimport { shell, Shell } from \"./shell\";\nimport { clipboard, Clipboard } from \"./clipboard\";\nimport { ipcRenderer, ipcMain, IpcRenderer, IpcMain } from \"./ipc\";\nimport { screen, Screen } from \"./screen\";\n\nexport class Lemonade {\n\tget version(): string {\n\t\treturn \"1.1.0\";\n\t}\n\tNotification = Notification;\n\tBrowserWindow = BrowserWindow;\n\tApp = App;\n\tNet = Net;\n\tDialog = Dialog;\n\tShell = Shell;\n\tClipboard = Clipboard;\n\tIpcRenderer = IpcRenderer;\n\tIpcMain = IpcMain;\n\tScreen = Screen;\n\n\tdialog = new Dialog();\n\tnet = new Net();\n\tapp = app;\n\tshell = shell;\n\tclipboard = clipboard;\n\tipcRenderer = ipcRenderer;\n\tipcMain = ipcMain;\n\tscreen = screen;\n}\n\nexport { Notification, BrowserWindow, Dialog, Net, app, shell, clipboard, ipcRenderer, ipcMain, screen };\n"
  },
  {
    "path": "src/sys/lemonade/ipc.ts",
    "content": "type IpcHandler = (event: any, ...args: any[]) => any;\n\nexport class IpcRenderer {\n\tprivate handlers: Map<string, IpcHandler[]> = new Map();\n\tprivate onceHandlers: Map<string, IpcHandler[]> = new Map();\n\n\ton(channel: string, listener: IpcHandler): this {\n\t\tif (!this.handlers.has(channel)) {\n\t\t\tthis.handlers.set(channel, []);\n\t\t}\n\t\tthis.handlers.get(channel)!.push(listener);\n\t\treturn this;\n\t}\n\n\tonce(channel: string, listener: IpcHandler): this {\n\t\tif (!this.onceHandlers.has(channel)) {\n\t\t\tthis.onceHandlers.set(channel, []);\n\t\t}\n\t\tthis.onceHandlers.get(channel)!.push(listener);\n\t\treturn this;\n\t}\n\n\toff(channel: string, listener: IpcHandler): this {\n\t\tconst handlers = this.handlers.get(channel);\n\t\tif (handlers) {\n\t\t\tconst index = handlers.indexOf(listener);\n\t\t\tif (index > -1) {\n\t\t\t\thandlers.splice(index, 1);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\n\tremoveListener(channel: string, listener: IpcHandler): this {\n\t\treturn this.off(channel, listener);\n\t}\n\n\tremoveAllListeners(channel?: string): this {\n\t\tif (channel) {\n\t\t\tthis.handlers.delete(channel);\n\t\t\tthis.onceHandlers.delete(channel);\n\t\t} else {\n\t\t\tthis.handlers.clear();\n\t\t\tthis.onceHandlers.clear();\n\t\t}\n\t\treturn this;\n\t}\n\n\tsend(channel: string, ...args: any[]): void {\n\t\tconsole.log(`IPC Send: ${channel}`, args);\n\t\twindow.postMessage(\n\t\t\t{\n\t\t\t\ttype: \"ipc-message\",\n\t\t\t\tchannel,\n\t\t\t\targs,\n\t\t\t},\n\t\t\t\"*\",\n\t\t);\n\t}\n\n\tsendSync(channel: string, ...args: any[]): any {\n\t\tconsole.log(`IPC SendSync: ${channel}`, args);\n\t\treturn null;\n\t}\n\n\tinvoke(channel: string, ...args: any[]): Promise<any> {\n\t\tconsole.log(`IPC Invoke: ${channel}`, args);\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst messageId = Math.random().toString(36).substring(7);\n\t\t\tconst responseHandler = (event: MessageEvent) => {\n\t\t\t\tif (event.data?.type === \"ipc-response\" && event.data?.messageId === messageId) {\n\t\t\t\t\twindow.removeEventListener(\"message\", responseHandler);\n\t\t\t\t\tif (event.data.error) {\n\t\t\t\t\t\treject(event.data.error);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresolve(event.data.result);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\twindow.addEventListener(\"message\", responseHandler);\n\t\t\twindow.postMessage(\n\t\t\t\t{\n\t\t\t\t\ttype: \"ipc-invoke\",\n\t\t\t\t\tchannel,\n\t\t\t\t\targs,\n\t\t\t\t\tmessageId,\n\t\t\t\t},\n\t\t\t\t\"*\",\n\t\t\t);\n\t\t\tsetTimeout(() => {\n\t\t\t\twindow.removeEventListener(\"message\", responseHandler);\n\t\t\t\treject(new Error(`IPC invoke timeout: ${channel}`));\n\t\t\t}, 30000);\n\t\t});\n\t}\n\n\tsendToHost(channel: string, ...args: any[]): void {\n\t\tconsole.log(`IPC SendToHost: ${channel}`, args);\n\t\tthis.send(channel, ...args);\n\t}\n\n\t_triggerEvent(channel: string, ...args: any[]): void {\n\t\tconst event = { sender: this };\n\t\tconst handlers = this.handlers.get(channel);\n\t\tif (handlers) {\n\t\t\thandlers.forEach(handler => handler(event, ...args));\n\t\t}\n\t\tconst onceHandlers = this.onceHandlers.get(channel);\n\t\tif (onceHandlers) {\n\t\t\tonceHandlers.forEach(handler => handler(event, ...args));\n\t\t\tthis.onceHandlers.delete(channel);\n\t\t}\n\t}\n}\n\nexport class IpcMain {\n\tprivate handlers: Map<string, IpcHandler[]> = new Map();\n\tprivate handlersOnce: Map<string, IpcHandler[]> = new Map();\n\tprivate invokeHandlers: Map<string, IpcHandler> = new Map();\n\ton(channel: string, listener: IpcHandler): this {\n\t\tif (!this.handlers.has(channel)) {\n\t\t\tthis.handlers.set(channel, []);\n\t\t}\n\t\tthis.handlers.get(channel)!.push(listener);\n\t\treturn this;\n\t}\n\tonce(channel: string, listener: IpcHandler): this {\n\t\tif (!this.handlersOnce.has(channel)) {\n\t\t\tthis.handlersOnce.set(channel, []);\n\t\t}\n\t\tthis.handlersOnce.get(channel)!.push(listener);\n\t\treturn this;\n\t}\n\tremoveListener(channel: string, listener: IpcHandler): this {\n\t\tconst handlers = this.handlers.get(channel);\n\t\tif (handlers) {\n\t\t\tconst index = handlers.indexOf(listener);\n\t\t\tif (index > -1) {\n\t\t\t\thandlers.splice(index, 1);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\tremoveAllListeners(channel?: string): this {\n\t\tif (channel) {\n\t\t\tthis.handlers.delete(channel);\n\t\t\tthis.handlersOnce.delete(channel);\n\t\t\tthis.invokeHandlers.delete(channel);\n\t\t} else {\n\t\t\tthis.handlers.clear();\n\t\t\tthis.handlersOnce.clear();\n\t\t\tthis.invokeHandlers.clear();\n\t\t}\n\t\treturn this;\n\t}\n\thandle(channel: string, listener: IpcHandler): void {\n\t\tthis.invokeHandlers.set(channel, listener);\n\t}\n\thandleOnce(channel: string, listener: IpcHandler): void {\n\t\tconst wrapper: IpcHandler = (event, ...args) => {\n\t\t\tthis.invokeHandlers.delete(channel);\n\t\t\treturn listener(event, ...args);\n\t\t};\n\t\tthis.invokeHandlers.set(channel, wrapper);\n\t}\n\tremoveHandler(channel: string): void {\n\t\tthis.invokeHandlers.delete(channel);\n\t}\n}\n\nexport const ipcRenderer = new IpcRenderer();\nexport const ipcMain = new IpcMain();\n"
  },
  {
    "path": "src/sys/lemonade/net.ts",
    "content": "interface RequestOptions {\n\tmethod?: string;\n\theaders?: Record<string, string>;\n\tbody?: any;\n\tredirect?: \"follow\" | \"error\" | \"manual\";\n\tsignal?: AbortSignal;\n\ttimeout?: number;\n}\n\nexport class Net {\n\tasync request(url: string, options: RequestOptions = {}): Promise<Response> {\n\t\tconst controller = options.timeout ? new AbortController() : null;\n\t\tconst timeoutId = options.timeout ? setTimeout(() => controller?.abort(), options.timeout) : null;\n\n\t\ttry {\n\t\t\tconst response = await window.tb.libcurl.fetch(url, {\n\t\t\t\tmethod: options.method || \"GET\",\n\t\t\t\theaders: options.headers || {},\n\t\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\t\tsignal: options.signal || controller?.signal,\n\t\t\t});\n\t\t\treturn response;\n\t\t} finally {\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t}\n\t}\n\n\tfetch(url: string, options: RequestOptions = {}): Promise<Response> {\n\t\treturn this.request(url, options);\n\t}\n\n\tisOnline(): boolean {\n\t\treturn navigator.onLine;\n\t}\n\n\tgetOnlineStatus(): \"online\" | \"offline\" {\n\t\treturn navigator.onLine ? \"online\" : \"offline\";\n\t}\n\n\tcreateClientRequest(options: { url?: string; method?: string; protocol?: string; host?: string; port?: number; path?: string }): any {\n\t\tconst url = options.url || `${options.protocol || \"https:\"}//${options.host}${options.port ? `:${options.port}` : \"\"}${options.path || \"/\"}`;\n\t\tconst method = options.method || \"GET\";\n\n\t\tconst request = {\n\t\t\turl,\n\t\t\tmethod,\n\t\t\theaders: {} as Record<string, string>,\n\t\t\tbody: null as any,\n\t\t\tsetHeader: (name: string, value: string) => {\n\t\t\t\trequest.headers[name] = value;\n\t\t\t},\n\t\t\twrite: (chunk: any) => {\n\t\t\t\trequest.body = request.body ? request.body + chunk : chunk;\n\t\t\t},\n\t\t\tend: async () => {\n\t\t\t\treturn await this.request(url, {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tbody: request.body,\n\t\t\t\t});\n\t\t\t},\n\t\t\tabort: () => {\n\t\t\t\tconsole.log(\"Request aborted\");\n\t\t\t},\n\t\t\ton: (event: string, _listener: Function) => {\n\t\t\t\tconsole.log(`Event listener added for: ${event}`);\n\t\t\t},\n\t\t};\n\n\t\treturn request;\n\t}\n}\n"
  },
  {
    "path": "src/sys/lemonade/notification.ts",
    "content": "interface NotificationOptions {\n\ttitle: string;\n\tsubtitle: string;\n\tbody: string;\n\tsilent: boolean;\n\ticon: string;\n}\n\ntype NotificationEvent = \"click\";\n\nexport class Notification {\n\tprivate eventHandlers: { [K in NotificationEvent]?: ((...args: any[]) => void)[] } = {};\n\n\tstatic isSupported(): boolean {\n\t\treturn true;\n\t}\n\n\tconstructor(options: NotificationOptions) {\n\t\twindow.tb.notification.Toast({\n\t\t\tmessage: options.body,\n\t\t\ticonSrc: options.icon || \"/assets/img/logo.png\",\n\t\t\tapplication: options.title || \"Lemonade Communicator\",\n\t\t\tonOk: (...args: any[]) => {\n\t\t\t\tthis.emit(\"click\", ...args);\n\t\t\t},\n\t\t});\n\t}\n\n\ton(event: NotificationEvent, handler: (...args: any[]) => void): void {\n\t\tif (!this.eventHandlers[event]) {\n\t\t\tthis.eventHandlers[event] = [];\n\t\t}\n\t\tthis.eventHandlers[event]!.push(handler);\n\t}\n\n\toff(event: NotificationEvent, handler: (...args: any[]) => void): void {\n\t\tconst handlers = this.eventHandlers[event];\n\t\tif (handlers) {\n\t\t\tthis.eventHandlers[event] = handlers.filter(h => h !== handler);\n\t\t}\n\t}\n\n\tprivate emit(event: NotificationEvent, ...args: any[]): void {\n\t\tconst handlers = this.eventHandlers[event];\n\t\tif (handlers) {\n\t\t\thandlers.forEach(handler => handler(...args));\n\t\t}\n\t}\n\n\tshow(): string {\n\t\treturn \"API Stub\";\n\t}\n\n\tclose(): string {\n\t\treturn \"API Stub\";\n\t}\n}\n"
  },
  {
    "path": "src/sys/lemonade/screen.ts",
    "content": "interface Display {\n\tid: number;\n\tbounds: { x: number; y: number; width: number; height: number };\n\tworkArea: { x: number; y: number; width: number; height: number };\n\tsize: { width: number; height: number };\n\tworkAreaSize: { width: number; height: number };\n\tscaleFactor: number;\n\trotation: number;\n\tinternal: boolean;\n\ttouchSupport: \"available\" | \"unavailable\" | \"unknown\";\n}\n\nexport class Screen {\n\tprivate eventHandlers: Map<string, Function[]> = new Map();\n\n\tgetCursorScreenPoint(): { x: number; y: number } {\n\t\treturn { x: 0, y: 0 };\n\t}\n\n\tgetPrimaryDisplay(): Display {\n\t\treturn {\n\t\t\tid: 1,\n\t\t\tbounds: {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: window.screen.width,\n\t\t\t\theight: window.screen.height,\n\t\t\t},\n\t\t\tworkArea: {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: window.screen.availWidth,\n\t\t\t\theight: window.screen.availHeight,\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\twidth: window.screen.width,\n\t\t\t\theight: window.screen.height,\n\t\t\t},\n\t\t\tworkAreaSize: {\n\t\t\t\twidth: window.screen.availWidth,\n\t\t\t\theight: window.screen.availHeight,\n\t\t\t},\n\t\t\tscaleFactor: window.devicePixelRatio,\n\t\t\trotation: 0,\n\t\t\tinternal: false,\n\t\t\ttouchSupport: \"ontouchstart\" in window ? \"available\" : \"unavailable\",\n\t\t};\n\t}\n\n\tgetAllDisplays(): Display[] {\n\t\treturn [this.getPrimaryDisplay()];\n\t}\n\n\tgetDisplayNearestPoint(_point: { x: number; y: number }): Display {\n\t\treturn this.getPrimaryDisplay();\n\t}\n\n\tgetDisplayMatching(_rect: { x: number; y: number; width: number; height: number }): Display {\n\t\treturn this.getPrimaryDisplay();\n\t}\n\n\ton(event: \"display-added\" | \"display-removed\" | \"display-metrics-changed\", listener: Function): this {\n\t\tif (!this.eventHandlers.has(event)) {\n\t\t\tthis.eventHandlers.set(event, []);\n\t\t}\n\t\tthis.eventHandlers.get(event)!.push(listener);\n\t\treturn this;\n\t}\n\n\tremoveListener(event: string, listener: Function): this {\n\t\tconst handlers = this.eventHandlers.get(event);\n\t\tif (handlers) {\n\t\t\tconst index = handlers.indexOf(listener);\n\t\t\tif (index > -1) {\n\t\t\t\thandlers.splice(index, 1);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n}\n\nexport const screen = new Screen();\n"
  },
  {
    "path": "src/sys/lemonade/shell.ts",
    "content": "export class Shell {\n\tasync openExternal(url: string, options?: { activate?: boolean }): Promise<void> {\n\t\tconsole.log(`Opening external URL: ${url}`, options);\n\t\twindow.open(url, \"_blank\");\n\t}\n\n\tasync openPath(path: string) {\n\t\tconsole.log(`Opening path: ${path}`);\n\t\twindow.tb.system.openApp(\"Files\");\n\t}\n\n\tshowItemInFolder(fullPath: string): void {\n\t\tconsole.log(`Showing item in folder: ${fullPath}`);\n\t\tconst lastSlash = fullPath.lastIndexOf(\"/\");\n\t\tconst directory = lastSlash > 0 ? fullPath.substring(0, lastSlash) : \"/\";\n\t\tthis.openPath(directory);\n\t}\n\n\tasync moveItemToTrash(fullPath: string): Promise<boolean> {\n\t\tconsole.log(`Moving item to trash: ${fullPath}`);\n\t\ttry {\n\t\t\tconst trashPath = `/system/trash`;\n\t\t\tconst fileName = fullPath.substring(fullPath.lastIndexOf(\"/\") + 1);\n\t\t\tconst destPath = `${trashPath}/${fileName}`;\n\t\t\ttry {\n\t\t\t\tawait window.tb.fs.promises.mkdir(trashPath);\n\t\t\t} catch {}\n\t\t\tawait window.tb.fs.promises.rename(fullPath, destPath);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to move item to trash:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync trashItem(path: string): Promise<void> {\n\t\tawait this.moveItemToTrash(path);\n\t}\n\n\tbeep(): void {\n\t\tconst audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();\n\t\tconst oscillator = audioContext.createOscillator();\n\t\tconst gainNode = audioContext.createGain();\n\t\toscillator.connect(gainNode);\n\t\tgainNode.connect(audioContext.destination);\n\t\toscillator.frequency.value = 800;\n\t\toscillator.type = \"sine\";\n\t\tgainNode.gain.setValueAtTime(0.3, audioContext.currentTime);\n\t\tgainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);\n\t\toscillator.start(audioContext.currentTime);\n\t\toscillator.stop(audioContext.currentTime + 0.1);\n\t}\n\n\twriteShortcutLink(shortcutPath: string, operation: string, options: any): boolean {\n\t\tconsole.log(`Writing shortcut link: ${shortcutPath}`, operation, options);\n\t\treturn false;\n\t}\n\n\treadShortcutLink(shortcutPath: string): any {\n\t\tconsole.log(`Reading shortcut link: ${shortcutPath}`);\n\t\treturn null;\n\t}\n}\n\nexport const shell = new Shell();\n"
  },
  {
    "path": "src/sys/lemonade/window.ts",
    "content": "import { UserSettings } from \"../types\";\n\ninterface ElectronWinArgs {\n\twidth?: number;\n\theight?: number;\n\tminWidth?: number;\n\tminHeight?: number;\n\tmaxWidth?: number;\n\tmaxHeight?: number;\n\tx?: number;\n\ty?: number;\n\ttitle?: string;\n\ticon?: string;\n\tresizable?: boolean;\n\tmaximizable?: boolean;\n\tminimizable?: boolean;\n\tclosable?: boolean;\n\talwaysOnTop?: boolean;\n\tfullscreen?: boolean;\n\tskipTaskbar?: boolean;\n\tbackgroundColor?: string;\n\tshow?: boolean;\n\tframe?: boolean;\n\tparent?: BrowserWindow;\n\tmodal?: boolean;\n\twebPreferences?: {\n\t\tnodeIntegration?: boolean;\n\t\tcontextIsolation?: boolean;\n\t\tpreload?: string;\n\t\tdevTools?: boolean;\n\t\twebSecurity?: boolean;\n\t\tallowRunningInsecureContent?: boolean;\n\t\tsandbox?: boolean;\n\t};\n}\n\nexport class BrowserWindow {\n\tprivate eventHandlers: Map<string, Function[]> = new Map();\n\tprivate _isDestroyed: boolean = false;\n\n\tconstructor(args: ElectronWinArgs = {}) {\n\t\twindow.tb.window.create({\n\t\t\ttitle: args.title || \"Lemonade Instance\",\n\t\t\tsize: {\n\t\t\t\twidth: args.width || 500,\n\t\t\t\theight: args.height || 500,\n\t\t\t\tminWidth: args.minWidth || 300,\n\t\t\t\tminHeight: args.minHeight || 300,\n\t\t\t},\n\t\t\ticon: args.icon || \"/assets/img/logo.png\",\n\t\t\tresizable: args.resizable !== false,\n\t\t\tmaximizable: args.maximizable !== false,\n\t\t\tminimizable: args.minimizable !== false,\n\t\t\tsrc: \"about:blank\",\n\t\t});\n\t}\n\n\tloadFile(path: string) {\n\t\twindow.tb.window.changeSrc(`/fs/${path}`);\n\t}\n\n\tasync loadURL(src: string) {\n\t\tconst settings: UserSettings = JSON.parse(await window.tb.fs.promises.readFile(`/home/${sessionStorage.getItem(\"currAcc\")}/settings.json`, \"utf8\"));\n\t\twindow.tb.window.changeSrc(settings.proxy === \"Ultraviolet\" ? `/uv/service/${await window.tb.proxy.encode(src, \"XOR\")}` : `/service/${await window.tb.proxy.encode(src, \"XOR\")}`);\n\t}\n\n\tdestroy() {\n\t\tif (this._isDestroyed) return;\n\t\tthis._isDestroyed = true;\n\t\tthis.emit(\"closed\");\n\t\twindow.tb.window.close();\n\t}\n\n\tclose() {\n\t\tif (this._isDestroyed) return;\n\t\tthis.emit(\"close\");\n\t\tthis.destroy();\n\t}\n\n\tshow() {\n\t\tthis.emit(\"show\");\n\t\tconsole.log(\"Window show\");\n\t}\n\n\tblur() {\n\t\tthis.emit(\"blur\");\n\t\twindow.blur();\n\t}\n\n\tfocus() {\n\t\tthis.emit(\"focus\");\n\t\twindow.focus();\n\t}\n\n\thide() {\n\t\tthis.emit(\"hide\");\n\t\twindow.tb.window.minimize();\n\t}\n\n\tminimize() {\n\t\tthis.emit(\"minimize\");\n\t\twindow.tb.window.minimize();\n\t}\n\n\tmaximize() {\n\t\tthis.emit(\"maximize\");\n\t\twindow.tb.window.maximize?.();\n\t}\n\n\tunmaximize() {\n\t\tthis.emit(\"unmaximize\");\n\t\tconsole.log(\"API Stub\");\n\t}\n\n\tisMaximized(): boolean {\n\t\treturn false;\n\t}\n\n\tisMinimized(): boolean {\n\t\treturn false;\n\t}\n\n\tisVisible(): boolean {\n\t\treturn !this.isMinimized();\n\t}\n\n\tisDestroyed(): boolean {\n\t\treturn this._isDestroyed;\n\t}\n\n\tsetTitle(title: string) {\n\t\twindow.tb.window.titlebar.setText(title);\n\t}\n\n\tgetTitle(): string {\n\t\treturn document.title;\n\t}\n\n\tsetSize(width: number, height: number, _animate?: boolean) {\n\t\tconsole.log(`setSize called: ${width}x${height}`);\n\t}\n\n\tgetSize(): [number, number] {\n\t\treturn [window.innerWidth, window.innerHeight];\n\t}\n\n\tsetPosition(x: number, y: number, _animate?: boolean) {\n\t\tconsole.log(`setPosition called: ${x}, ${y}`);\n\t}\n\n\tgetPosition(): [number, number] {\n\t\treturn [window.screenX, window.screenY];\n\t}\n\n\tsetAlwaysOnTop(flag: boolean, level?: string, relativeLevel?: number) {\n\t\tconsole.log(\"setAlwaysOnTop:\", flag, level, relativeLevel);\n\t}\n\n\tisAlwaysOnTop(): boolean {\n\t\treturn false;\n\t}\n\n\tcenter() {\n\t\tconst [width, height] = this.getSize();\n\t\tconst x = (window.screen.width - width) / 2;\n\t\tconst y = (window.screen.height - height) / 2;\n\t\tthis.setPosition(x, y);\n\t}\n\n\tsetFullScreen(flag: boolean) {\n\t\tif (flag) {\n\t\t\tdocument.documentElement.requestFullscreen?.();\n\t\t} else {\n\t\t\tdocument.exitFullscreen?.();\n\t\t}\n\t}\n\n\tisFullScreen(): boolean {\n\t\treturn !!document.fullscreenElement;\n\t}\n\n\tsetResizable(resizable: boolean) {\n\t\tconsole.log(\"setResizable:\", resizable);\n\t}\n\n\tisResizable(): boolean {\n\t\treturn true;\n\t}\n\n\tsetMovable(movable: boolean) {\n\t\tconsole.log(\"setMovable:\", movable);\n\t}\n\n\tisMovable(): boolean {\n\t\treturn true;\n\t}\n\n\tsetMinimizable(minimizable: boolean) {\n\t\tconsole.log(\"setMinimizable:\", minimizable);\n\t}\n\n\tisMinimizable(): boolean {\n\t\treturn true;\n\t}\n\n\tsetMaximizable(maximizable: boolean) {\n\t\tconsole.log(\"setMaximizable:\", maximizable);\n\t}\n\n\tisMaximizable(): boolean {\n\t\treturn true;\n\t}\n\n\tsetClosable(closable: boolean) {\n\t\tconsole.log(\"setClosable:\", closable);\n\t}\n\n\tisClosable(): boolean {\n\t\treturn true;\n\t}\n\n\tflashFrame(flag: boolean) {\n\t\tconsole.log(\"flashFrame:\", flag);\n\t}\n\n\ton(event: string, listener: Function): this {\n\t\tif (!this.eventHandlers.has(event)) {\n\t\t\tthis.eventHandlers.set(event, []);\n\t\t}\n\t\tthis.eventHandlers.get(event)!.push(listener);\n\t\treturn this;\n\t}\n\n\tonce(event: string, listener: Function): this {\n\t\tconst wrapper = (...args: any[]) => {\n\t\t\tlistener(...args);\n\t\t\tthis.removeListener(event, wrapper);\n\t\t};\n\t\treturn this.on(event, wrapper);\n\t}\n\n\tremoveListener(event: string, listener: Function): this {\n\t\tconst handlers = this.eventHandlers.get(event);\n\t\tif (handlers) {\n\t\t\tconst index = handlers.indexOf(listener);\n\t\t\tif (index > -1) {\n\t\t\t\thandlers.splice(index, 1);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\n\tremoveAllListeners(event?: string): this {\n\t\tif (event) {\n\t\t\tthis.eventHandlers.delete(event);\n\t\t} else {\n\t\t\tthis.eventHandlers.clear();\n\t\t}\n\t\treturn this;\n\t}\n\n\tprivate emit(event: string, ...args: any[]): void {\n\t\tconst handlers = this.eventHandlers.get(event);\n\t\tif (handlers) {\n\t\t\thandlers.forEach(handler => handler(...args));\n\t\t}\n\t}\n\n\twebContents = {\n\t\tsend: (channel: string, ...args: any[]) => {\n\t\t\tconsole.log(\"webContents.send:\", channel, args);\n\t\t},\n\t\topenDevTools: () => {\n\t\t\tconsole.log(\"DevTools opened (if available)\");\n\t\t},\n\t\tcloseDevTools: () => {\n\t\t\tconsole.log(\"DevTools closed\");\n\t\t},\n\t\tisDevToolsOpened: () => false,\n\t\tloadURL: (url: string) => this.loadURL(url),\n\t\tloadFile: (path: string) => this.loadFile(path),\n\t\texecuteJavaScript: async (code: string) => {\n\t\t\ttry {\n\t\t\t\treturn eval(code);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"executeJavaScript error:\", error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/sys/libcurl.d.ts",
    "content": "declare module \"libcurl.js\" {\n\texport * from \"libcurl.js/bundled\";\n\texport { default } from \"libcurl.js/bundled\";\n}\ndeclare module \"libcurl.js/bundled\" {\n\texport type WebsocketUrl = `wss://${string}` | `ws://${string}`;\n\texport type ProxyUrl = `socks5h://${string}` | `socks4a://${string}` | `http://${string}`;\n\texport interface LibcurlVersion {\n\t\tlib: string;\n\t\tcurl: string;\n\t\tssl: string;\n\t\tbrotli: string;\n\t\tnghttp2: string;\n\t\tprotocols: string[];\n\t\twisp: string;\n\t}\n\texport interface HTTPSessionOptions {\n\t\tenable_cookies?: boolean;\n\t\tcookie_jar?: string;\n\t\tproxy?: ProxyUrl;\n\t}\n\texport interface SessionOptions {\n\t\tproxy?: ProxyUrl;\n\t\tverbose?: boolean;\n\t\theaders?: Record<string, string> | Headers;\n\t}\n\texport interface WebSocketOptions extends SessionOptions {\n\t\tproxy?: ProxyUrl;\n\t}\n\texport interface TLSSocketOptions extends SessionOptions {\n\t\tverbose?: boolean;\n\t}\n\texport interface RequestCallbacks {\n\t\tend: (error: number) => void;\n\t\tdata: (chunk: Uint8Array) => void;\n\t\theaders: (chunk: Uint8Array) => void;\n\t}\n\texport class HeadersDict {\n\t\tconstructor(obj: Record<string, string>);\n\t\t[key: string]: string;\n\t}\n\texport class CurlSession {\n\t\tconstructor(options?: SessionOptions);\n\t\tsession_ptr: number;\n\t\tactive_requests: number;\n\t\tevent_loop: null | any;\n\t\trequests_list: unknown[];\n\t\tto_remove: unknown[];\n\t\tend_callback_ptr: number;\n\t\theaders_callback_ptr: number;\n\t\tdata_callback_ptr: number;\n\t\trequest_callbacks: Record<string, RequestCallbacks>;\n\t\tlast_request_id: number;\n\t\toptions: SessionOptions;\n\t\tassert_ready(): void;\n\t\tset_connections(connections_limit: number, cache_limit: number, host_conn_limit?: number): void;\n\t\tend_callback(request_id: number, error: number): void;\n\t\tdata_callback(request_id: number, chunk_ptr: number, chunk_size: number): void;\n\t\theaders_callback(request_id: number, chunk_ptr: number, chunk_size: number): void;\n\t\tcreate_request(url: string, js_data_callback: (chunk: Uint8Array) => void, js_end_callback: (error: number) => void, js_headers_callback: (chunk: Uint8Array) => void): number;\n\t\tremove_request_now(request_ptr: number): void;\n\t\tremove_request(request_ptr: number): void;\n\t\tstart_request(request_ptr: number): void;\n\t\tclose(): void;\n\t}\n\texport class HTTPSession extends CurlSession {\n\t\tconstructor(options?: HTTPSessionOptions);\n\t\tbase_url?: string;\n\t\tcookie_filename: string;\n\t\timport_cookies(): void;\n\t\texport_cookies(): string;\n\t\tclose(): void;\n\t\trequest_async(url: string, params: RequestInit, body?: Uint8Array): Promise<Response>;\n\t\tfetch(resource: string | URL | Request, params?: RequestInit): Promise<Response>;\n\t\tstatic create_response(response_data: Uint8Array | null, response_info: any): Response;\n\t\tstatic create_options(params: RequestInit): Promise<Uint8Array | null>;\n\t}\n\texport class CurlWebSocket extends CurlSession {\n\t\tconstructor(url: WebsocketUrl, protocols?: string[], options?: WebSocketOptions);\n\t\turl: WebsocketUrl;\n\t\tprotocols: string[];\n\t\tconnected: boolean;\n\t\trecv_loop: number | null;\n\t\thttp_handle: number | null;\n\t\trecv_buffer: unknown[];\n\t\tonopen: () => void;\n\t\tonerror: (error?: any) => void;\n\t\tonmessage: (data: string | Uint8Array) => void;\n\t\tonclose: () => void;\n\t\tconnect(): void;\n\t\tsend(data: string | Uint8Array): void;\n\t\trecv(): string | Uint8Array | null;\n\t\tclose(): void;\n\t\tcleanup(error?: boolean | number): void;\n\t}\n\texport class FakeWebSocket extends EventTarget {\n\t\tconstructor(url: WebsocketUrl, protocols?: string[], options?: WebSocketOptions);\n\t\treadonly CONNECTING: 0;\n\t\treadonly OPEN: 1;\n\t\treadonly CLOSING: 2;\n\t\treadonly CLOSED: 3;\n\t\turl: WebsocketUrl;\n\t\tprotocols: string[];\n\t\toptions: WebSocketOptions;\n\t\tbinaryType: \"blob\" | \"arraybuffer\";\n\t\tstatus: 0 | 1 | 2 | 3;\n\t\tsocket: CurlWebSocket | null;\n\t\tonopen: (event: Event) => void;\n\t\tonerror: (event: Event) => void;\n\t\tonmessage: (event: MessageEvent) => void;\n\t\tonclose: (event: CloseEvent) => void;\n\t\tconnect(): void;\n\t\tsend(data: string | Blob | ArrayBuffer | ArrayBufferView): void;\n\t\tclose(): void;\n\t}\n\texport class TLSSocket extends CurlSession {\n\t\tconstructor(hostname: string, port: number, options?: TLSSocketOptions);\n\t\thostname: string;\n\t\tport: number;\n\t\turl: string;\n\t\tconnected: boolean;\n\t\trecv_loop: number | null;\n\t\tonopen: () => void;\n\t\tonerror: (error?: any) => void;\n\t\tonmessage: (data: Uint8Array) => void;\n\t\tonclose: () => void;\n\t\tconnect(): void;\n\t\tsend(data: string | Uint8Array): void;\n\t\trecv(): Uint8Array | null;\n\t\tclose(): void;\n\t\tcleanup(error?: boolean | number): void;\n\t}\n\texport interface WispConnection {\n\t\t[key: string]: unknown;\n\t}\n\texport class WispWebSocket {\n\t\tconstructor(url: WebsocketUrl);\n\t}\n\texport function logger(type: \"log\" | \"warn\" | \"error\", text: string): void;\n\texport function log_msg(text: string): void;\n\texport function warn_msg(text: string): void;\n\texport function error_msg(text: string): void;\n\texport function data_to_array(data: string | ArrayBuffer | ArrayBufferView | Uint8Array): Uint8Array;\n\texport function merge_arrays(arrays: Uint8Array[]): Uint8Array;\n\texport function get_error_str(error_code: number): string;\n\texport function c_func(target: Function, args?: any[]): any;\n\texport function c_func_str(target: Function, args?: any[]): string;\n\texport const libcurl: {\n\t\tset_websocket: (url: WebsocketUrl) => void;\n\t\tfetch: (resource: string | URL | Request, params?: RequestInit) => Promise<Response>;\n\t\tload_wasm: (wasmPath: string) => Promise<void>;\n\t\tget_cacert: () => string;\n\t\tget_error_string: (error_code: number) => string;\n\t\tready: boolean;\n\t\twebsocket_url: WebsocketUrl | null;\n\t\ttransport: \"wisp\" | \"wsproxy\" | string | Function;\n\t\tcopyright: string;\n\t\tversion: LibcurlVersion | null;\n\t\tHTTPSession: typeof HTTPSession;\n\t\tWebSocket: typeof FakeWebSocket;\n\t\tCurlWebSocket: typeof CurlWebSocket;\n\t\tTLSSocket: typeof TLSSocket;\n\t\twisp: {\n\t\t\twisp_connections: Record<string, WispConnection>;\n\t\t\tWispConnection: typeof WispConnection;\n\t\t\tWispWebSocket: typeof WispWebSocket;\n\t\t};\n\t\tstdout: (text: string) => void;\n\t\tstderr: (text: string) => void;\n\t\tlogger: (type: \"log\" | \"warn\" | \"error\", text: string) => void;\n\t\tevents: EventTarget;\n\t\tonload: (callback?: () => void) => void;\n\t};\n\texport default libcurl;\n}\n"
  },
  {
    "path": "src/sys/liquor/AliceWM.ts",
    "content": "import { createWindow } from \"../gui/WindowArea\";\nimport { ExternalApp } from \"./coreapps/ExternalApp\";\n\nexport interface WindowInformation {\n\ttitle: string;\n\twidth: string | number;\n\tminwidth: number;\n\theight: string | number;\n\tminheight: number;\n\tallowMultipleInstance: boolean;\n\ticon?: string;\n\tsrc?: string | URL | any;\n\t/**\n\t * @description For Terbium Compatability only\n\t */\n\tmsg?: any;\n}\n\nexport const AliceWM = {\n\tcreate: function (givenWinInfo?: string | WindowInformation) {\n\t\tconsole.trace();\n\t\t// Default param\n\t\tlet wininfo: WindowInformation = {\n\t\t\ttitle: \"Generic Window\",\n\t\t\tminheight: 40,\n\t\t\tminwidth: 40,\n\t\t\twidth: \"1000px\",\n\t\t\theight: \"500px\",\n\t\t\tallowMultipleInstance: false,\n\t\t};\n\t\t// Param given in argument\n\t\tif (typeof givenWinInfo == \"object\") wininfo = givenWinInfo;\n\n\t\tif (typeof givenWinInfo == \"string\") {\n\t\t\t// Only title given\n\t\t\twininfo.title = givenWinInfo;\n\t\t}\n\t\tconsole.log(givenWinInfo);\n\t\tconsole.log(wininfo);\n\t\tconsole.log(wininfo);\n\t\tconst parseSize = (v: string | number) => {\n\t\t\tif (typeof v === \"number\") return v;\n\t\t\tconst num = parseFloat(String(v).replace(/[^0-9.\\-]/g, \"\"));\n\t\t\treturn Number.isFinite(num) ? num : 0;\n\t\t};\n\t\tlet resp: (val: any) => void = () => {};\n\t\tlet er: (err: any) => void = () => {};\n\t\tconst ready = new Promise<any>((resolve, reject) => {\n\t\t\tresp = resolve;\n\t\t\ter = reject;\n\t\t});\n\t\tconst obj: any = {\n\t\t\telement: null,\n\t\t\tcontent: null,\n\t\t\t// @ts-expect-error keep same behavior — ExternalApp may be nonstandard\n\t\t\tapp: new ExternalApp(wininfo),\n\t\t\tdragForceX: 0,\n\t\t\tdragForceY: 0,\n\t\t\tdragging: false,\n\t\t\theight: wininfo.height,\n\t\t\twidth: wininfo.width,\n\t\t\tpid: null,\n\t\t\tstate: null,\n\t\t\tmaximized: false,\n\t\t\tminimizing: false,\n\t\t\tmouseLeft: null,\n\t\t\tmouseTop: null,\n\t\t\tonclose: () => null,\n\t\t\tonfocus: () => null,\n\t\t\tonresize: (_w: number, _h: number) => null,\n\t\t\tonsnap: (_side: string) => null,\n\t\t\tonunmaximize: () => null,\n\t\t\trestoreSvg: null,\n\t\t\tkill: () => {\n\t\t\t\tif (obj.pid != null) window.tb.process.kill(obj.pid);\n\t\t\t},\n\t\t\tget alive() {\n\t\t\t\treturn window.tb.window.getId() === obj.pid;\n\t\t\t},\n\t\t\tmaximizeImg: null,\n\t\t\tmaximizeSvg: null,\n\t\t\twininfo,\n\t\t\ttitle: wininfo.title,\n\t\t};\n\n\t\tobj.then = (onFulfilled: (val: any) => any, onRejected?: (err: any) => any) => ready.then(onFulfilled, onRejected);\n\t\tobj.catch = (onRejected: (err: any) => any) => ready.catch(onRejected);\n\t\tobj.finally = (cb: () => void) => ready.finally(cb);\n\t\tcreateWindow({\n\t\t\ttitle: wininfo.title,\n\t\t\ticon: wininfo.icon || \"/assets/img/logo.png\",\n\t\t\tsrc: wininfo.src || \"about:blank\",\n\t\t\tsize: {\n\t\t\t\twidth: parseSize(wininfo.width),\n\t\t\t\theight: parseSize(wininfo.height),\n\t\t\t},\n\t\t\tsingle: false,\n\t\t\tresizable: true,\n\t\t\tmessage: wininfo.msg,\n\t\t})\n\t\t\t.then(n => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(`created ${n}`);\n\t\t\t\t\t\t// Sorry I had to use DOM it was like the only way for actual cross compatability\n\t\t\t\t\t\tconst currPID = window.tb.window.getId();\n\t\t\t\t\t\tconst elem = document.querySelector(`div[pid=\"${currPID}\"]`);\n\t\t\t\t\t\tconst winControls = elem?.querySelectorAll(\".controls.flex.gap-1\") ?? [];\n\t\t\t\t\t\twindow.tb.window.content.set(\"<div></div>\");\n\t\t\t\t\t\tobj.element = elem;\n\t\t\t\t\t\tobj.content = elem?.querySelector(\".w-full.h-full\");\n\t\t\t\t\t\t// @ts-expect-error types\n\t\t\t\t\t\tobj.app = new ExternalApp(wininfo);\n\t\t\t\t\t\tobj.dragForceX = 0;\n\t\t\t\t\t\tobj.dragForceY = 0;\n\t\t\t\t\t\tobj.dragging = false;\n\t\t\t\t\t\tobj.height = wininfo.height;\n\t\t\t\t\t\tobj.width = wininfo.width;\n\t\t\t\t\t\tobj.pid = currPID;\n\t\t\t\t\t\tobj.state = null;\n\t\t\t\t\t\tobj.maximized = false;\n\t\t\t\t\t\tobj.minimizing = false;\n\t\t\t\t\t\tobj.mouseLeft = null;\n\t\t\t\t\t\tobj.mouseTop = null;\n\t\t\t\t\t\tobj.onclose = () => null;\n\t\t\t\t\t\tobj.onfocus = () => null;\n\t\t\t\t\t\tobj.onresize = (_w: number, _h: number) => null;\n\t\t\t\t\t\tobj.onsnap = (_side: string) => null;\n\t\t\t\t\t\tobj.onunmaximize = () => null;\n\t\t\t\t\t\tobj.restoreSvg = winControls[0]?.childNodes[1] || null;\n\t\t\t\t\t\tobj.maximizeImg = winControls[0]?.childNodes[1] || null;\n\t\t\t\t\t\tobj.maximizeSvg = winControls[0]?.childNodes[1] || null;\n\t\t\t\t\t\tobj.wininfo = wininfo;\n\t\t\t\t\t\tobj.title = wininfo.title;\n\t\t\t\t\t\tresp(obj);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\ter(err);\n\t\t\t\t\t}\n\t\t\t\t}, 500);\n\t\t\t})\n\t\t\t.catch(err => {\n\t\t\t\ter(err);\n\t\t\t});\n\n\t\treturn obj;\n\t},\n};\n"
  },
  {
    "path": "src/sys/liquor/Anura.ts",
    "content": "// @ts-expect-error\nimport { WindowInformation, AliceWM } from \"./AliceWM\";\nimport { WMAPI } from \"./api/WmApi\";\nimport { ContextMenuAPI } from \"./api/ContextMenuAPI\";\nimport { FilesAPI } from \"./api/Files\";\nimport { NotificationService } from \"./api/NotificationService\";\nimport { Settings } from \"./api/Settings\";\nimport { App } from \"./coreapps/App\";\nimport { ExternalApp } from \"./coreapps/ExternalApp\";\nimport { Networking } from \"./api/Networking\";\nimport { URIHandlerAPI } from \"./api/URIHandler\";\nimport { ExternalLib } from \"./libs/ExternalLib\";\nimport { Lib } from \"./libs/lib\";\nimport { Processes } from \"./api/Process\";\nimport { Platform } from \"./api/Platform\";\nimport { Dialog } from \"./api/Dialog\";\nimport { Systray } from \"./api/Systray\";\nimport { AnuraFilesystem } from \"./api/Filesystem\";\nimport { TFSProvider } from \"./api/TFS\";\nimport { AnuraUI } from \"./api/UI\";\n\ndeclare global {\n\tinterface Window {\n\t\tanura: Anura;\n\t}\n}\n\nexport class Anura {\n\tversion = {\n\t\tsemantic: {\n\t\t\tmajor: \"2\",\n\t\t\tminor: \"1\",\n\t\t\tpatch: \"0\",\n\t\t},\n\t\tbuildstate: \"Stable\",\n\t\tcodename: `Liquor \"Starboy\" Stable`,\n\t\tget pretty() {\n\t\t\tconst semantic = this.semantic;\n\t\t\treturn `${semantic.major}.${semantic.minor}.${semantic.patch} ${this.buildstate}`;\n\t\t},\n\t};\n\tinitComplete = false;\n\t// x86: null | V86Backend;\n\tsettings: Settings;\n\tfs: AnuraFilesystem;\n\tconfig: any;\n\tnet: Networking;\n\tnotifications: NotificationService;\n\t// x86hdd: FakeFile;\n\tprocesses: Processes;\n\tui = new AnuraUI();\n\tdialog: Dialog;\n\tplatform: Platform;\n\tsystray: Systray;\n\n\tprivate constructor(\n\t\tfs: AnuraFilesystem,\n\t\tsettings: Settings,\n\t\tconfig: any,\n\t\t// hdd: FakeFile,\n\t) {\n\t\tthis.fs = fs;\n\t\tthis.settings = settings;\n\t\tthis.config = config;\n\t\t// this.x86hdd = hdd;\n\n\t\tthis.net = new Networking();\n\t\tthis.notifications = new NotificationService();\n\t\tthis.processes = new Processes();\n\t\tthis.ui = new AnuraUI();\n\t\tthis.platform = new Platform();\n\t\tthis.dialog = new Dialog();\n\t\tthis.systray = new Systray();\n\t\t// @ts-expect-error\n\t\tthis.fs.readdir(\"/apps/anura\", (err: Error, files: string[]) => {\n\t\t\t// Fixes a weird edgecase that I was facing where no user apps are installed, nothing breaks it just throws an error which I would like to mitigate.\n\t\t\tif (files == undefined) return;\n\t\t\tfiles.forEach(file => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.registerExternalApp(\"/fs/apps/anura/\" + file);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthis.logger.error(\"Anura failed to load an app \" + e);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\ttry {\n\t\t\t// @ts-expect-error\n\t\t\tthis.fs.readdir(\"/system/lib/anura/\", (err: Error, files: string[]) => {\n\t\t\t\t// Fixes a weird edgecase that I was facing where no user apps are installed, nothing breaks it just throws an error which I would like to mitigate.\n\t\t\t\tif (files == undefined) return;\n\t\t\t\tfiles.forEach(file => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.fs.readFile(\n\t\t\t\t\t\t\t\"/system/lib/anura//\" + file,\n\t\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\t\tfunction (err: Error, data: Uint8Array) {\n\t\t\t\t\t\t\t\tif (err) throw \"Failed to read file\";\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\teval(new TextDecoder(\"utf-8\").decode(data));\n\t\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\t\tconsole.error(e);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tthis.logger.error(\"Anura failed to load an app \" + e);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t} catch (e) {\n\t\t\tthis.logger.error(e);\n\t\t}\n\t\tthis.registerExternalApp(\"/apps/fsapp.app\");\n\t\tif (import.meta.env.DEV) {\n\t\t\tthis.registerExternalLib(\"/public/apps/libfileview.lib\");\n\t\t\tthis.registerExternalLib(\"/public/apps/libfilepicker.lib\");\n\t\t\tthis.registerExternalLib(\"/public/apps/libpersist.lib\");\n\t\t} else {\n\t\t\tthis.registerExternalLib(\"/apps/libfileview.lib\");\n\t\t\tthis.registerExternalLib(\"/apps/libfilepicker.lib\");\n\t\t\tthis.registerExternalLib(\"/apps/libpersist.lib\");\n\t\t}\n\t}\n\n\tstatic async new(config: any): Promise<Anura> {\n\t\t// File System Initialization //\n\t\tconst filerProvider = new TFSProvider(window.tb.fs);\n\t\t// @ts-expect-error\n\t\tconst fs = new AnuraFilesystem([filerProvider]);\n\t\t// @ts-expect-error\n\t\tconst settings = await Settings.new(fs, config.defaultsettings);\n\n\t\t// const hdd = await InitV86Hdd();\n\t\tconst anuraPartial = new Anura(fs, settings, config);\n\t\t(window as any).tb.liquor = anuraPartial;\n\t\treturn anuraPartial;\n\t}\n\n\twm = new WMAPI();\n\n\tapps: any = {};\n\tlibs: any = {};\n\tlogger = {\n\t\tlog: console.log.bind(console, \"anuraOS:\"),\n\t\tdebug: console.debug.bind(console, \"anuraOS:\"),\n\t\twarn: console.warn.bind(console, \"anuraOS:\"),\n\t\terror: console.error.bind(console, \"anuraOS:\"),\n\t};\n\t// net = new Networking();\n\tasync registerApp(app: App) {\n\t\tif (app.package in this.apps) {\n\t\t\tthrow \"Application already installed\";\n\t\t}\n\t\tconst apps: any = JSON.parse(await window.tb.fs.promises.readFile(\"/system/var/terbium/start.json\", \"utf8\"));\n\t\t// @ts-expect-error\n\t\tif (apps.system_apps.some(existingApp => existingApp.title === app.name)) {\n\t\t\tconsole.log(\"Application already installed\");\n\t\t} else {\n\t\t\tconsole.log(app);\n\t\t\tapps.system_apps.push({\n\t\t\t\ttitle: app.name,\n\t\t\t\ticon: app.icon,\n\t\t\t\t// @ts-expect-error\n\t\t\t\tsrc: `${app.source}/${app.manifest.index}`,\n\t\t\t});\n\t\t\tawait window.tb.fs.promises.writeFile(\"/system/var/terbium/start.json\", JSON.stringify(apps, null, 2));\n\t\t\twindow.dispatchEvent(new Event(\"updApps\"));\n\t\t\tawait window.tb.fs.promises.writeFile(`/system/etc/anura/configs/${app.name}.json`, JSON.stringify(app, null, 2));\n\t\t\tconst installedApps = JSON.parse(await window.tb.fs.promises.readFile(\"/apps/installed.json\", \"utf8\"));\n\t\t\tinstalledApps.push({\n\t\t\t\tname: app.name,\n\t\t\t\tconfig: `/system/etc/anura/configs/${app.name}.json`,\n\t\t\t\tuser: \"System\",\n\t\t\t});\n\t\t\tawait window.tb.fs.promises.writeFile(\"/apps/installed.json\", JSON.stringify(installedApps));\n\t\t}\n\t\tthis.apps[app.package] = {\n\t\t\ttitle: app.name,\n\t\t\ticon: app.icon,\n\t\t\tid: app.package,\n\t\t};\n\t\treturn app;\n\t}\n\tasync registerExternalApp(source: string): Promise<ExternalApp> {\n\t\tconst resp = await fetch(`${source}/manifest.json`);\n\t\tconst manifest = (await resp.json()) as AppManifest;\n\t\tif (manifest.type === \"auto\" || manifest.type === \"manual\") {\n\t\t\tconst app = new ExternalApp(manifest, source);\n\t\t\tawait this.registerApp(app);\n\t\t\treturn app;\n\t\t}\n\t\tconst handlers = this.settings.get(\"ExternalAppHandlers\");\n\t\tif (!handlers || !handlers[manifest.type]) {\n\t\t\tconst error = `Could not register external app from source: \"${source}\" because no external handlers are registered for type \"${manifest.type}\"`;\n\t\t\tthis.notifications.add({\n\t\t\t\ttitle: \"AnuraOS\",\n\t\t\t\tdescription: error,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t\tconst handler = handlers[manifest.type];\n\t\tconst handlerModule = await this.import(handler);\n\t\tif (!handlerModule) {\n\t\t\tconst error = `Failed to load external app handler ${handler}`;\n\t\t\tthis.notifications.add({\n\t\t\t\ttitle: \"AnuraOS\",\n\t\t\t\tdescription: error,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t\tif (!handlerModule.createApp) {\n\t\t\tconst error = `Handler ${handler} does not have a createApp function`;\n\t\t\tthis.notifications.add({\n\t\t\t\ttitle: \"AnuraOS\",\n\t\t\t\tdescription: error,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t\tconst app = handlerModule.createApp(manifest, source);\n\t\tawait this.registerApp(app); // This will let us capture error messages\n\t\treturn app;\n\t}\n\tregisterExternalAppHandler(id: string, handler: string) {\n\t\tconst handlers = this.settings.get(\"ExternalAppHandlers\") || {};\n\t\thandlers[handler] = id;\n\t\tthis.settings.set(\"ExternalAppHandlers\", handlers);\n\t}\n\tasync registerLib(lib: Lib) {\n\t\tif (lib.package in this.libs) {\n\t\t\tthrow \"Library already installed\";\n\t\t}\n\t\tthis.libs[lib.package] = lib;\n\t\treturn lib;\n\t}\n\tasync registerExternalLib(source: string): Promise<ExternalLib> {\n\t\tconst resp = await fetch(`${source}/manifest.json`);\n\t\tconst manifest = await resp.json();\n\t\tconst lib = new ExternalLib(manifest, source);\n\t\tawait this.registerLib(lib); // This will let us capture error messages\n\t\treturn lib;\n\t}\n\tContextMenu = ContextMenuAPI;\n\tremoveStaleApps() {\n\t\tfor (const appName in this.apps) {\n\t\t\tconst app = this.apps[appName];\n\t\t\tapp.windows.forEach((win: any) => {\n\t\t\t\tif (!win.element.parentElement) {\n\t\t\t\t\tapp.windows.splice(app.windows.indexOf(win));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\tasync import(packageName: string, searchPath?: string) {\n\t\tif (searchPath) {\n\t\t\t// Using node-style module resolution\n\t\t\tlet scope: string | null;\n\t\t\tlet name: string;\n\t\t\tlet filename: string;\n\t\t\tif (packageName.startsWith(\"@\")) {\n\t\t\t\tconst [_scope, _name, ...rest] = packageName.split(\"/\");\n\t\t\t\tscope = _scope!;\n\t\t\t\tname = _name!;\n\t\t\t\tfilename = rest.join(\"/\");\n\t\t\t} else {\n\t\t\t\tconst [_name, ...rest] = packageName.split(\"/\");\n\t\t\t\tscope = null;\n\t\t\t\tname = _name!;\n\t\t\t\tfilename = rest.join(\"/\");\n\t\t\t}\n\n\t\t\tif (!filename || filename === \"\") {\n\t\t\t\tconst data: any = await this.fs.promises.readFile(`${searchPath}/${scope}/${name}/package.json`);\n\t\t\t\tconst pkg = JSON.parse(data);\n\t\t\t\tconsole.log(\"pkg\", pkg);\n\t\t\t\tif (pkg.main) {\n\t\t\t\t\tfilename = pkg.main;\n\t\t\t\t} else {\n\t\t\t\t\tfilename = \"index.js\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst file = await this.fs.promises.readFile(`${searchPath}/${scope}/${name}/${filename}`);\n\t\t\t// @ts-ignore\n\t\t\tconst blob = new Blob([file], { type: \"application/javascript\" });\n\t\t\tconst url = URL.createObjectURL(blob);\n\t\t\t// @vite-ignore\n\t\t\treturn await import(/* @vite-ignore */ url);\n\t\t}\n\t\tconst splitName = packageName.split(\"@\");\n\t\tconst pkg: string = splitName[0]!;\n\t\tconst version = splitName[1] || null;\n\t\tif (this.libs[pkg]) {\n\t\t\treturn await this.libs[pkg].getImport(version);\n\t\t}\n\t}\n\turi = new URIHandlerAPI();\n\tfiles = new FilesAPI();\n\tget wsproxyURL() {\n\t\treturn this.settings.get(\"wisp-url\");\n\t}\n}\n\nexport interface AppManifest {\n\tname: string;\n\ttype: \"manual\" | \"auto\" | \"webview\" | string;\n\tpackage: string;\n\tindex?: string;\n\ticon: string;\n\thandler?: string;\n\tsrc?: string;\n\thidden?: boolean;\n\tbackground?: string;\n\twininfo: string; //| WindowInformation;\n\tuseIdbWrapper?: boolean;\n}\n"
  },
  {
    "path": "src/sys/liquor/Boot.ts",
    "content": "import { Anura } from \"./Anura\";\n\nconst channel = new BroadcastChannel(\"tab\");\n\n// send message to all tabs, after a new tab\nchannel.postMessage(\"newtab\");\nlet activetab = true;\nchannel.addEventListener(\"message\", msg => {\n\tif (msg.data === \"newtab\" && activetab) {\n\t\t// if there's a previously registered tab that can read the message, tell the other tab to kill itself\n\t\tchannel.postMessage(\"blackmanthunderstorm\");\n\t}\n\n\tif (msg.data === \"blackmanthunderstorm\") {\n\t\tactivetab = false;\n\t\t//@ts-ignore\n\t\tfor (const elm of [...document.children]) {\n\t\t\telm.remove();\n\t\t}\n\t\tdocument.open();\n\t\tdocument.write(\"you already have an anura tab open\");\n\t\tdocument.close();\n\t}\n});\n\n// global\nwindow.addEventListener(\"load\", async () => {\n\tawait navigator.serviceWorker.register(\"/anura-sw.js\");\n\tlet conf, milestone, instancemilestone;\n\ttry {\n\t\tconf = await (await fetch(\"/config.json\")).json();\n\t\tmilestone = await (await fetch(\"/MILESTONE\")).text();\n\t\tinstancemilestone = conf.milestone;\n\n\t\tconsole.log(\"writing config??\");\n\t\twindow.tb.fs.writeFile(\"/config_cached.json\", JSON.stringify(conf));\n\t} catch (e) {\n\t\tconf = JSON.parse(await new Promise(r => window.tb.fs.readFile(\"/config_cached.json\", (_: any, b: Uint8Array) => r(new TextDecoder().decode(b)))));\n\t}\n\n\twindow.anura = await Anura.new(conf);\n\tif (milestone) {\n\t\tconst stored = window.anura.settings.get(\"milestone\");\n\t\tif (!stored) await window.anura.settings.set(\"milestone\", milestone);\n\t\telse if (stored != milestone || window.anura.settings.get(\"instancemilestone\") != instancemilestone) {\n\t\t\tawait window.anura.settings.set(\"milestone\", milestone);\n\t\t\tawait window.anura.settings.set(\"instancemilestone\", instancemilestone);\n\t\t\tnavigator.serviceWorker.controller!.postMessage({\n\t\t\t\tanura_target: \"anura.cache.invalidate\",\n\t\t\t});\n\t\t\tconsole.log(\"invalidated cache\");\n\t\t\twindow.location.reload();\n\t\t}\n\t}\n\tif (!window.anura.settings.get(\"directories\")) {\n\t\tconst defaultDirectories = {\n\t\t\tapps: \"/apps/anura/\",\n\t\t\tlibs: \"/system/lib/anura/\",\n\t\t\tinit: \"/system/etc/anura/init/\",\n\t\t\tbin: \"/system/bin/anura/\",\n\t\t};\n\t\tawait window.anura.settings.set(\"directories\", defaultDirectories);\n\t}\n\n\tif (!window.anura.settings.get(\"handler-migration-complete\")) {\n\t\t// Convert legacy file handlers\n\t\t// This is a one-time migration\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\n\t\tconsole.log(\"migrating file handlers\");\n\t\tconsole.log(extHandlers);\n\n\t\tfor (const ext in extHandlers) {\n\t\t\tconst handler = extHandlers[ext];\n\t\t\tif (handler.handler_type === \"module\") continue;\n\t\t\tif (handler.handler_type === \"cjs\") continue;\n\t\t\tif (typeof handler === \"string\") {\n\t\t\t\tif (handler === \"/public/apps/libfileview.app/fileHandler.js\") {\n\t\t\t\t\textHandlers[ext] = {\n\t\t\t\t\t\thandler_type: \"module\",\n\t\t\t\t\t\tid: \"anura.fileviewer\",\n\t\t\t\t\t};\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\textHandlers[ext] = {\n\t\t\t\t\thandler_type: \"cjs\",\n\t\t\t\t\tpath: handler,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\twindow.anura.settings.set(\"FileExts\", extHandlers);\n\t\twindow.anura.settings.set(\"handler-migration-complete\", true);\n\t}\n\n\tsetTimeout(\n\t\t() => {\n\t\t\twindow.anura.logger.debug(\"boot completed\");\n\t\t\tdocument.dispatchEvent(new Event(\"anura-boot-completed\"));\n\t\t},\n\t\twindow.anura.settings.get(\"oobe-complete\") ? 1000 : 2000,\n\t);\n});\n\ndocument.addEventListener(\"anura-boot-completed\", async () => {\n\t// Anura OOBE code used to be here\n\twindow.anura.settings.set(\"handler-migration-complete\", true);\n});\n\ndocument.addEventListener(\"anura-login-completed\", async () => {\n\tfor (const app of window.anura.config.apps) {\n\t\twindow.anura.registerExternalApp(app);\n\t}\n\n\tfor (const lib of window.anura.config.libs) {\n\t\twindow.anura.registerExternalLib(lib);\n\t}\n\n\t// Load all persistent sideloaded apps\n\ttry {\n\t\t// @ts-expect-error\n\t\twindow.anura.fs.readdir(\"/apps/anura\", (err: Error, files: string[]) => {\n\t\t\t// Fixes a weird edgecase that I was facing where no user apps are installed, nothing breaks it just throws an error which I would like to mitigate.\n\t\t\tif (files == undefined) return;\n\t\t\tfiles.forEach(file => {\n\t\t\t\ttry {\n\t\t\t\t\twindow.anura.registerExternalApp(\"/fs/apps/anura/\" + file);\n\t\t\t\t} catch (e) {\n\t\t\t\t\twindow.anura.logger.error(\"Anura failed to load an app \" + e);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t} catch (e) {\n\t\twindow.anura.logger.error(e);\n\t}\n\t// Load all user provided init scripts\n\ttry {\n\t\t// @ts-expect-error\n\t\twindow.anura.fs.readdir(\"/userInit\", (err: Error, files: string[]) => {\n\t\t\t// Fixes a weird edgecase that I was facing where no user apps are installed, nothing breaks it just throws an error which I would like to mitigate.\n\t\t\tif (files == undefined) return;\n\t\t\tfiles.forEach(file => {\n\t\t\t\ttry {\n\t\t\t\t\twindow.anura.fs.readFile(\n\t\t\t\t\t\t\"/userInit/\" + file,\n\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\tfunction (err: Error, data: Uint8Array) {\n\t\t\t\t\t\t\tif (err) throw \"Failed to read file\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\teval(new TextDecoder(\"utf-8\").decode(data));\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\tconsole.error(e);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t} catch (e) {\n\t\t\t\t\twindow.anura.logger.error(\"Anura failed to load an app \" + e);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t} catch (e) {\n\t\twindow.anura.logger.error(e);\n\t}\n\tif ((await await fetch(\"/fs/\")).status === 404) {\n\t\twindow.anura.notifications.add({\n\t\t\ttitle: \"Anura Error\",\n\t\t\tdescription: \"Anura has encountered an error with the Filesystem HTTP bridge, click this notification to restart\",\n\t\t\ttimeout: 50000,\n\t\t\tcallback: () => window.location.reload(),\n\t\t});\n\t}\n\n\tdocument.addEventListener(\"contextmenu\", function (e) {\n\t\tif (e.shiftKey) return;\n\t\te.preventDefault();\n\t\t//     const menu: any = document.querySelector(\".custom-menu\");\n\t\t//     menu.style.removeProperty(\"display\");\n\t\t//     menu.style.top = `${e.clientY}px`;\n\t\t//     menu.style.left = `${e.clientX}px`;\n\t});\n\t//\n\t// document.addEventListener(\"click\", (e) => {\n\t//     if (e.button != 0) return;\n\t//     (\n\t//         document.querySelector(\".custom-menu\")! as HTMLElement\n\t//     ).style.setProperty(\"display\", \"none\");\n\t// });\n\n\twindow.anura.initComplete = true;\n});\n"
  },
  {
    "path": "src/sys/liquor/api/ContextMenuAPI.tsx",
    "content": "import \"../../gui/styles/liquor.css\";\n\nexport class ContextMenuAPI {\n\telement: HTMLDivElement;\n\titem(text: string, callback: VoidFunction) {\n\t\tconst menuItem = document.createElement(\"div\");\n\t\tmenuItem.className = \"custom-menu-item\";\n\t\tmenuItem.onclick = () => {\n\t\t\tcallback();\n\t\t};\n\t\tmenuItem.innerText = text;\n\t\treturn menuItem;\n\t}\n\tisShown = false;\n\tconstructor() {\n\t\tthis.element = document.createElement(\"div\");\n\t\tthis.element.className = \"custom-menu\";\n\t\tsetTimeout(\n\t\t\t() =>\n\t\t\t\tdocument.addEventListener(\"click\", event => {\n\t\t\t\t\tconst withinBoundaries = event.composedPath().includes(this.element);\n\n\t\t\t\t\tif (!withinBoundaries) {\n\t\t\t\t\t\tthis.element.remove();\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t100,\n\t\t);\n\t}\n\tremoveAllItems() {\n\t\tthis.element.innerHTML = \"\";\n\t}\n\taddItem(text: string, callback: VoidFunction) {\n\t\tthis.element.appendChild(\n\t\t\tthis.item(text, () => {\n\t\t\t\tthis.hide();\n\t\t\t\tcallback();\n\t\t\t}),\n\t\t);\n\t}\n\tshow(x: number, y: number) {\n\t\tthis.element.style.top = y.toString() + \"px\";\n\t\tthis.element.style.left = x.toString() + \"px\";\n\t\tdocument.body.appendChild(this.element);\n\t\tthis.isShown = true;\n\t\tthis.element.focus();\n\t\treturn this.element;\n\t}\n\thide() {\n\t\tif (this.isShown) {\n\t\t\tdocument.body.removeChild(this.element);\n\t\t\tthis.isShown = false;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Dialog.ts",
    "content": "import { setDialogFn } from \"../../apis/Dialogs\";\nexport class Dialog {\n\talert(message: string, title = \"Alert\") {\n\t\t// @ts-expect-error types\n\t\tsetDialogFn(\"alert\", {\n\t\t\tmessage: message,\n\t\t\ttitle: title,\n\t\t});\n\t}\n\tasync confirm(message: string, title = \"Confirmation\"): Promise<boolean> {\n\t\treturn new Promise(resolve => {\n\t\t\tsetDialogFn(\"permissions\", {\n\t\t\t\tmessage: message,\n\t\t\t\ttitle: title,\n\t\t\t\tonOk: () => {\n\t\t\t\t\tresolve(true);\n\t\t\t\t},\n\t\t\t\tonCancel: () => {\n\t\t\t\t\tresolve(false);\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n\tasync prompt(message: string, defaultValue?: any): Promise<any> {\n\t\treturn new Promise(resolve => {\n\t\t\tsetDialogFn(\"message\", {\n\t\t\t\ttitle: message,\n\t\t\t\tmessage: defaultValue,\n\t\t\t\tonOk: (val: any) => {\n\t\t\t\t\tresolve(val);\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/FilerFS.ts",
    "content": "// @ts-nocheck\nimport { FilerFS } from \"../types/Filer\";\nimport { Anura } from \"../Anura\";\nimport { AFSProvider } from \"./Filesystem\";\nconst AnuraFDSymbol = Symbol.for(\"AnuraFD\");\ntype AnuraFD = {\n\tfd: number;\n\t[AnuraFDSymbol]: string;\n};\n// @ts-expect-error\nconst Filer = window.Filer;\nlet anura: Anura;\n\nexport class FilerAFSProvider extends AFSProvider<any> {\n\tdomain = \"/\";\n\tname = \"Filer\";\n\tversion = \"1.0.0\";\n\n\tfs: FilerFS;\n\n\tconstructor(fs: FilerFS) {\n\t\tsuper();\n\t\tthis.fs = fs;\n\t}\n\n\trename(oldPath: string, newPath: string, callback?: (err: Error | null) => void) {\n\t\tthis.fs.rename(oldPath, newPath, callback);\n\t}\n\n\tftruncate(fd: AnuraFD, len: number, callback?: (err: Error | null, fd: AnuraFD) => void) {\n\t\tthis.fs.ftruncate(fd.fd, len, (err, fd) => callback!(err, { fd, [AnuraFDSymbol]: this.domain }));\n\t}\n\n\ttruncate(path: string, len: number, callback?: (err: Error | null) => void) {\n\t\tthis.fs.truncate(path, len, callback);\n\t}\n\n\tstat(path: string, callback?: (err: Error | null, stats: any) => void) {\n\t\tthis.fs.stat(path, callback);\n\t}\n\n\tfstat(fd: AnuraFD, callback?: ((err: Error | null, stats: any) => void) | undefined): void {\n\t\tthis.fs.fstat(fd.fd, callback);\n\t}\n\n\tlstat(path: string, callback?: (err: Error | null, stats: any) => void) {\n\t\tthis.fs.lstat(path, callback);\n\t}\n\n\t/** @deprecated fs.exists() is an anachronism and exists only for historical reasons. */\n\texists(path: string, callback?: (exists: boolean) => void) {\n\t\tthis.fs.exists(path, callback);\n\t}\n\n\tlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void) {\n\t\tthis.fs.link(srcPath, dstPath, callback);\n\t}\n\n\tsymlink(path: string, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.symlink(path, ...rest);\n\t}\n\n\treadlink(path: string, callback?: (err: Error | null, linkContents: string) => void) {\n\t\tthis.fs.readlink(path, callback);\n\t}\n\n\tunlink(path: string, callback?: (err: Error | null) => void) {\n\t\tthis.fs.unlink(path, callback);\n\t}\n\n\tmknod(path: string, mode: number, callback?: (err: Error | null) => void) {\n\t\tthis.fs.mknod(path, mode, callback);\n\t}\n\n\trmdir(path: string, callback?: (err: Error | null) => void) {\n\t\tthis.fs.rmdir(path, callback);\n\t}\n\n\tmkdir(path: string, ...rest: any[]) {\n\t\tthis.fs.mkdir(path, ...rest);\n\t}\n\n\taccess(path: string, ...rest: any[]) {\n\t\tthis.fs.access(path, ...rest);\n\t}\n\n\tmkdtemp(...args: any[]) {\n\t\t// Temp directories should remain in the root filesystem for now\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.mkdtemp(...args);\n\t}\n\n\treaddir(path: string, ...rest: any[]) {\n\t\tthis.fs.readdir(path, ...rest);\n\t}\n\n\tclose(fd: AnuraFD, callback?: ((err: Error | null) => void) | undefined): void {\n\t\tcallback ||= () => {};\n\t\tthis.fs.close(fd.fd, callback);\n\t}\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode: number, callback?: ((err: Error | null, fd: AnuraFD) => void) | undefined): void;\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", callback?: ((err: Error | null, fd: AnuraFD) => void) | undefined): void;\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: unknown, callback?: unknown): void {\n\t\tif (typeof mode === \"number\") {\n\t\t\tthis.fs.open(path, flags, mode, (err, fd) =>\n\t\t\t\t(callback as (err: Error | null, fd: AnuraFD) => void)!(err, {\n\t\t\t\t\tfd,\n\t\t\t\t\t[AnuraFDSymbol]: this.domain,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.fs.open(path, flags, (err, fd) =>\n\t\t\t\t(mode as (err: Error | null, fd: AnuraFD) => void)!(err, {\n\t\t\t\t\tfd,\n\t\t\t\t\t[AnuraFDSymbol]: this.domain,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\tutimes(path: string, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void) {\n\t\tthis.fs.utimes(path, atime, mtime, callback);\n\t}\n\n\tfutimes(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.futimes(fd.fd, ...rest);\n\t}\n\n\tchown(path: string, uid: number, gid: number, callback?: (err: Error | null) => void) {\n\t\tthis.fs.chown(path, uid, gid, callback);\n\t}\n\n\tfchown(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.fchown(fd.fd, ...rest);\n\t}\n\n\tchmod(path: string, mode: number, callback?: (err: Error | null) => void) {\n\t\tthis.fs.chmod(path, mode, callback);\n\t}\n\n\tfchmod(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.fchmod(fd.fd, ...rest);\n\t}\n\n\tfsync(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.fsync(fd.fd, ...rest);\n\t}\n\n\twrite(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.write(fd.fd, ...rest);\n\t}\n\n\tread(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.read(fd.fd, ...rest);\n\t}\n\n\treadFile(path: string, callback?: (err: Error | null, data: Uint8Array) => void) {\n\t\tthis.fs.readFile(path, callback);\n\t}\n\n\twriteFile(path: string, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.writeFile(path, ...rest);\n\t}\n\n\tappendFile(path: string, data: Uint8Array, callback?: (err: Error | null) => void) {\n\t\tthis.fs.appendFile(path, data, callback);\n\t}\n\n\tsetxattr(path: string, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.setxattr(path, ...rest);\n\t}\n\n\tfsetxattr(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.fsetxattr(fd.fd, ...rest);\n\t}\n\n\tgetxattr(path: string, name: string, callback?: (err: Error | null, value: string | object) => void) {\n\t\tthis.fs.getxattr(path, name, callback);\n\t}\n\n\tfgetxattr(fd: AnuraFD, name: string, callback?: (err: Error | null, value: string | object) => void) {\n\t\tthis.fs.fgetxattr(fd.fd, name, callback);\n\t}\n\n\tremovexattr(path: string, name: string, callback?: (err: Error | null) => void) {\n\t\tthis.fs.removexattr(path, name, callback);\n\t}\n\n\tfremovexattr(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.fremovexattr(fd.fd, ...rest);\n\t}\n\n\tpromises = {\n\t\tappendFile: (path: string, data: Uint8Array, options: { encoding: string; mode: number; flag: string }) => this.fs.promises.appendFile(path, data, options),\n\t\taccess: (path: string, mode?: number) => this.fs.promises.access(path, mode),\n\t\tchown: (path: string, uid: number, gid: number) => this.fs.promises.chown(path, uid, gid),\n\t\tchmod: (path: string, mode: number) => this.fs.promises.chmod(path, mode),\n\t\tgetxattr: (path: string, name: string) => this.fs.promises.getxattr(path, name),\n\t\tlink: (srcPath: string, dstPath: string) => this.fs.promises.link(srcPath, dstPath),\n\t\tlstat: (path: string) => this.fs.promises.lstat(path),\n\t\tmkdir: (path: string, mode?: number) => this.fs.promises.mkdir(path, mode),\n\t\tmkdtemp: (prefix: string, options?: { encoding: string }) => this.fs.promises.mkdtemp(prefix, options),\n\t\tmknod: (path: string, mode: number) => this.fs.promises.mknod(path, mode),\n\t\topen: async (path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number) => ({\n\t\t\tfd: await this.fs.promises.open(path, flags, mode),\n\t\t\t[AnuraFDSymbol]: this.domain,\n\t\t}),\n\t\treaddir: (path: string, options?: { encoding: string; withFileTypes: boolean }) => this.fs.promises.readdir(path, options),\n\t\treadFile: (path: string) => this.fs.promises.readFile(path),\n\t\treadlink: (path: string) => this.fs.promises.readlink(path),\n\t\tremovexattr: (path: string, name: string) => this.fs.promises.removexattr(path, name),\n\t\trename: (oldPath: string, newPath: string) => this.fs.promises.rename(oldPath, newPath),\n\t\trmdir: (path: string) => this.fs.promises.rmdir(path),\n\t\tsetxattr: (path: string, name: string, value: string | object, flag?: \"CREATE\" | \"REPLACE\") => this.fs.promises.setxattr(path, name, value, flag),\n\t\tstat: (path: string) => this.fs.promises.stat(path),\n\t\tsymlink: (srcPath: string, dstPath: string, type?: string) => this.fs.promises.symlink(srcPath, dstPath, type),\n\t\ttruncate: (path: string, len: number) => this.fs.promises.truncate(path, len),\n\n\t\tunlink: (path: string) => this.fs.promises.unlink(path),\n\t\tutimes: (path: string, atime: number | Date, mtime: number | Date) => this.fs.promises.utimes(path, atime, mtime),\n\t\twriteFile: (path: string, data: Uint8Array | string, options: { encoding: string; mode: number; flag: string }) => this.fs.promises.writeFile(path, data, options),\n\t};\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Files.ts",
    "content": "// Depends on Settings.ts, must be loaded AFTER\n\nexport class FilesAPI {\n\tfallbackIcon = \"/assets/img/missing_icon.svg\";\n\tfolderIcon = \"/assets/img/folder.svg\";\n\topen = async function (path: string): Promise<void> {\n\t\tconst ext = path.split(\"/\").pop()!.split(\".\").pop();\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\t\tif (extHandlers[ext!]) {\n\t\t\tconst handler = extHandlers[ext!];\n\t\t\tconsole.log(`Opening ${path} with ${handler}`);\n\t\t\tif (handler.handler_type === \"module\") {\n\t\t\t\tconst handlerModule = await window.anura.import(handler.id);\n\t\t\t\tif (!handlerModule) {\n\t\t\t\t\tconsole.log(`Failed to load handler ${handler}`);\n\t\t\t\t\t// @ts-expect-error stfu\n\t\t\t\t\tawait this.defaultOpen(path);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!handlerModule.openFile) {\n\t\t\t\t\tconsole.log(`Handler ${handler} does not have an openFile function`);\n\t\t\t\t\t// @ts-expect-error stfu\n\t\t\t\t\tawait this.defaultOpen(path);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn handlerModule.openFile(path);\n\t\t\t}\n\t\t\tif (handler.handler_type === \"cjs\") {\n\t\t\t\t// Legacy handler, eval it\n\t\t\t\teval((await (await fetch(handler.path)).text()) + `openFile(${JSON.stringify(path)})`); // here, JSON.stringify is used to properly escape the string\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\t// If no handler is found, try the default handler\n\t\t// @ts-expect-error stfu\n\t\tawait this.defaultOpen(path);\n\t\treturn;\n\t};\n\n\tasync defaultOpen(path: string): Promise<void> {\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\t\tif (extHandlers[\"default\"]) {\n\t\t\tconst handler = extHandlers[\"default\"];\n\t\t\tconsole.log(`Opening ${path} with ${handler}`);\n\t\t\tif (handler.handler_type === \"module\") {\n\t\t\t\tconst handlerModule = await window.anura.import(handler.id);\n\t\t\t\tif (!handlerModule) {\n\t\t\t\t\tconsole.log(`Failed to load handler ${handler}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!handlerModule.openFile) {\n\t\t\t\t\tconsole.log(`Handler ${handler} does not have an openFile function`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\thandlerModule.openFile(path);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (handler.handler_type === \"cjs\") {\n\t\t\t\teval((await (await fetch(handler.path)).text()) + `openFile(${JSON.stringify(path)})`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetIcon = async (path: string) => {\n\t\tconst ext = path.split(\"/\").pop()!.split(\".\").pop();\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\t\tif (extHandlers[ext!]) {\n\t\t\tconst handler = extHandlers[ext!];\n\t\t\tif (handler.handler_type === \"module\") {\n\t\t\t\tconst handlerModule = await window.anura.import(handler.id);\n\t\t\t\tif (!handlerModule) {\n\t\t\t\t\tconsole.log(`Failed to load handler ${handler}`);\n\t\t\t\t\treturn await this.defaultIcon(path);\n\t\t\t\t}\n\t\t\t\tif (!handlerModule.getIcon) {\n\t\t\t\t\tconsole.log(`Handler ${handler} does not have an getIcon function`);\n\t\t\t\t\treturn await this.defaultIcon(path);\n\t\t\t\t}\n\t\t\t\treturn handlerModule.getIcon(path);\n\t\t\t}\n\t\t\tif (handler.handler_type === \"cjs\") {\n\t\t\t\treturn eval(\n\t\t\t\t\t(await (await fetch(handler.path)).text()) +\n\t\t\t\t\t\t`if (getIcon) {\n                            getIcon(${JSON.stringify(path)})\n                        } else {\n                            ${JSON.stringify(await this.defaultIcon(path))}\n                        }`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn await this.defaultIcon(path);\n\t};\n\n\tasync defaultIcon(path: string) {\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\t\tif (extHandlers[\"default\"]) {\n\t\t\tconst handler = extHandlers[\"default\"];\n\t\t\tif (handler.handler_type === \"module\") {\n\t\t\t\tconst handlerModule = await window.anura.import(handler.id);\n\t\t\t\tif (!handlerModule) {\n\t\t\t\t\tconsole.log(`Failed to load handler ${handler}`);\n\t\t\t\t\treturn this.fallbackIcon;\n\t\t\t\t}\n\t\t\t\tif (!handlerModule.getIcon) {\n\t\t\t\t\tconsole.log(`Handler ${handler} does not have an getIcon function`);\n\t\t\t\t\treturn this.fallbackIcon;\n\t\t\t\t}\n\t\t\t\treturn handlerModule.getIcon(path);\n\t\t\t}\n\t\t\tif (handler.handler_type === \"cjs\") {\n\t\t\t\t// Legacy handler, eval it\n\t\t\t\treturn eval(\n\t\t\t\t\t(await (await fetch(handler.path)).text()) +\n\t\t\t\t\t\t`if (getIcon) {\n                            getIcon(${JSON.stringify(path)})\n                        } else {\n                            ${JSON.stringify(this.fallbackIcon)}\n                        }`,\n\t\t\t\t); // here, JSON.stringify is used to properly escape the string\n\t\t\t}\n\t\t}\n\t\treturn this.fallbackIcon;\n\t}\n\n\tasync getFileType(path: string) {\n\t\tconst ext = path.split(\"/\").pop()!.split(\".\").pop();\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\t\tif (extHandlers[ext!]) {\n\t\t\tconst handler = extHandlers[ext!];\n\t\t\tconsole.log(handler);\n\t\t\tif (handler.handler_type === \"module\") {\n\t\t\t\tconst handlerModule = await window.anura.import(handler.id);\n\t\t\t\tif (!handlerModule) {\n\t\t\t\t\tconsole.log(`Failed to load handler ${handler}`);\n\t\t\t\t\treturn \"Anura File\";\n\t\t\t\t}\n\t\t\t\tif (!handlerModule.getFileType) {\n\t\t\t\t\tconsole.log(`Handler ${handler} does not have an getFileType function`);\n\t\t\t\t\treturn \"Anura File\";\n\t\t\t\t}\n\t\t\t\treturn handlerModule.getFileType(path);\n\t\t\t}\n\t\t\tif (handler.handler_type === \"cjs\") {\n\t\t\t\t// Legacy handler, eval it\n\t\t\t\treturn eval(\n\t\t\t\t\t(await (await fetch(handler.path)).text()) +\n\t\t\t\t\t\t`if (getFileType) {\n                            getFileType(${JSON.stringify(path)})\n                        } else {\n                            \"Anura File\"\n                        }`,\n\t\t\t\t); // here, JSON.stringify is used to properly escape the string\n\t\t\t}\n\t\t}\n\t\t// If no handler is found, return \"Anura File\"\n\t\treturn \"Anura File\";\n\t}\n\n\tsetFolderIcon(path: string) {\n\t\tthis.folderIcon = path;\n\t}\n\n\tset(path: string, extension: string) {\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\t\textHandlers[extension] = {\n\t\t\thandler_type: \"cjs\",\n\t\t\tpath,\n\t\t};\n\t\twindow.anura.settings.set(\"FileExts\", extHandlers);\n\t}\n\tsetModule(id: string, extension: string) {\n\t\tconst extHandlers = window.anura.settings.get(\"FileExts\") || {};\n\t\textHandlers[extension] = {\n\t\t\thandler_type: \"module\",\n\t\t\tid,\n\t\t};\n\t\twindow.anura.settings.set(\"FileExts\", extHandlers);\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Filesystem.ts",
    "content": "import { Anura } from \"../Anura\";\nconst AnuraFDSymbol = Symbol.for(\"AnuraFD\");\nconst Filer = window.Filer;\n// @ts-expect-error\nconst anura: Anura = window.Anura;\n\nexport type AnuraFD = {\n\tfd: number;\n\t[AnuraFDSymbol]: string;\n};\n\nexport abstract class AnuraFSOperations<TStats> {\n\t/*\n\t * Synchronous FS operations\n\t */\n\n\tabstract rename(oldPath: string, newPath: string, callback?: (err: Error | null) => void): void;\n\n\tabstract ftruncate(fd: AnuraFD, len: number, callback?: (err: Error | null, fd: AnuraFD) => void): void;\n\n\tabstract truncate(path: string, len: number, callback?: (err: Error | null) => void): void;\n\n\tabstract stat(path: string, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\tabstract fstat(fd: AnuraFD, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\tabstract lstat(path: string, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\t/** @deprecated fs.exists() is an anachronism and exists only for historical reasons. */\n\tabstract exists(path: string, callback?: (exists: boolean) => void): void;\n\n\tabstract link(srcPath: string, dstPath: string, callback?: (err: Error | null) => void): void;\n\n\tabstract symlink(srcPath: string, dstPath: string, type: string, callback?: (err: Error | null) => void): void;\n\n\tabstract symlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void): void;\n\n\tabstract readlink(path: string, callback?: (err: Error | null, linkContents: string) => void): void;\n\n\tabstract unlink(path: string, callback?: (err: Error | null) => void): void;\n\n\tabstract rmdir(path: string, callback?: (err: Error | null) => void): void;\n\n\tabstract mkdir(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\tabstract mkdir(path: string, callback?: (err: Error | null) => void): void;\n\n\tabstract access(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\tabstract access(path: string, callback?: (err: Error | null) => void): void;\n\n\tabstract mkdtemp(prefix: string, options: { encoding: string } | string, callback?: (err: Error | null, path: string) => void): void;\n\n\tabstract mkdtemp(prefix: string, callback?: (err: Error | null, path: string) => void): void;\n\n\tabstract readdir(path: string, options: { encoding: string; withFileTypes: boolean } | string, callback?: (err: Error | null, files: string[]) => void): void;\n\n\tabstract readdir(path: string, callback?: (err: Error | null, files: string[]) => void): void;\n\n\tabstract close(fd: AnuraFD, callback?: (err: Error | null) => void): void;\n\n\tabstract open(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode: number, callback?: (err: Error | null, fd: AnuraFD) => void): void;\n\n\tabstract open(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", callback?: (err: Error | null, fd: AnuraFD) => void): void;\n\n\tabstract utimes(path: string, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void): void;\n\n\tabstract futimes(fd: AnuraFD, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void): void;\n\n\tabstract chown(path: string, uid: number, gid: number, callback?: (err: Error | null) => void): void;\n\n\tabstract fchown(fd: AnuraFD, uid: number, gid: number, callback?: (err: Error | null) => void): void;\n\n\tabstract chmod(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\tabstract fchmod(fd: AnuraFD, mode: number, callback?: (err: Error | null) => void): void;\n\n\tabstract fsync(fd: AnuraFD, callback?: (err: Error | null) => void): void;\n\n\tabstract write(fd: AnuraFD, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, nbytes: number) => void): void;\n\n\tabstract read(fd: AnuraFD, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, nbytes: number, buffer: Uint8Array) => void): void;\n\n\tabstract readFile(path: string, callback?: (err: Error | null, data: Uint8Array) => void): void;\n\n\tabstract writeFile(path: string, data: Uint8Array | string, options: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string, callback?: (err: Error | null) => void): void;\n\n\tabstract writeFile(path: string, data: Uint8Array | string, callback?: (err: Error | null) => void): void;\n\n\tabstract appendFile(path: string, data: Uint8Array, callback?: (err: Error | null) => void): void;\n\n\t/*\n\t * Asynchronous FS operations\n\t */\n\n\tabstract promises: {\n\t\tappendFile(path: string, data: Uint8Array, options: { encoding: string; mode: number; flag: string }): Promise<void>;\n\t\taccess(path: string, mode?: number): Promise<void>;\n\t\tchown(path: string, uid: number, gid: number): Promise<void>;\n\t\tchmod(path: string, mode: number): Promise<void>;\n\t\tlink(srcPath: string, dstPath: string): Promise<void>;\n\t\tlstat(path: string): Promise<TStats>;\n\t\tmkdir(path: string, mode?: number): Promise<void>;\n\t\tmkdtemp(prefix: string, options?: { encoding: string }): Promise<string>;\n\t\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number): Promise<AnuraFD>;\n\t\treaddir(path: string, options?: string | { encoding: string; withFileTypes: boolean }): Promise<string[]>;\n\t\treadFile(path: string): Promise<Uint8Array>;\n\t\treadlink(path: string): Promise<string>;\n\t\trename(oldPath: string, newPath: string): Promise<void>;\n\t\trmdir(path: string): Promise<void>;\n\t\tstat(path: string): Promise<TStats>;\n\t\tsymlink(srcPath: string, dstPath: string, type?: string): Promise<void>;\n\t\ttruncate(path: string, len: number): Promise<void>;\n\t\tunlink(path: string): Promise<void>;\n\t\tutimes(path: string, atime: number | Date, mtime: number | Date): Promise<void>;\n\t\twriteFile(path: string, data: Uint8Array | string, options?: { encoding: string; mode: number; flag: string }): Promise<void>;\n\t};\n}\n\n/**\n * Generic class for a filesystem provider\n * This should be extended by the various filesystem providers\n */\nexport abstract class AFSProvider<TStats> extends AnuraFSOperations<TStats> {\n\t/**\n\t * This is the domain that the filesystem provider is responsible\n\t * for. The provider with the most specific domain\n\t * will be used to handle a given path.\n\t *\n\t * @example \"/\" If you want to handle the root filesystem\n\t *\n\t * @example \"/tmp\" If you want to handle everything under /tmp.\n\t * This will take precedence over the root filesystem.\n\t */\n\tabstract domain: string;\n\n\t/**\n\t * The name of the filesystem provider\n\t */\n\tabstract name: string;\n\n\t/**\n\t * The filesystem provider's version\n\t */\n\tabstract version: string;\n}\n\nexport class AFSShell {\n\tenv = new Proxy({} as { [key: string]: string }, {\n\t\tget: (target: { [key: string]: string }, prop: string) => {\n\t\t\tif (prop === \"set\") {\n\t\t\t\treturn (key: string, value: string) => {\n\t\t\t\t\ttarget[key] = value;\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (prop === \"get\") {\n\t\t\t\treturn (key: string) => target[key];\n\t\t\t}\n\t\t\tif (prop in target) {\n\t\t\t\treturn target[prop];\n\t\t\t}\n\t\t\treturn undefined;\n\t\t},\n\t\tset: (target: any, prop: string, value: string) => {\n\t\t\tif (prop === \"set\" || prop === \"get\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\ttarget[prop] = value;\n\t\t\treturn true;\n\t\t},\n\t});\n\n\t#relativeToAbsolute(path: string) {\n\t\tif (path.startsWith(\"/\")) {\n\t\t\treturn path;\n\t\t}\n\t\treturn (this.env.PWD + \"/\" + path).replace(/\\/+/g, \"/\");\n\t}\n\n\tcat(files: string[], callback: (err: Error | null, contents: string) => void) {\n\t\tlet contents = \"\";\n\t\tlet remaining = files.length;\n\t\tfiles.forEach(file => {\n\t\t\tanura.fs.readFile(this.#relativeToAbsolute(file), (err, data) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tcallback(err, contents);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontents += data.toString() + \"\\n\";\n\t\t\t\tremaining--;\n\t\t\t\tif (remaining === 0) {\n\t\t\t\t\tcallback(null, contents.replace(/\\n$/, \"\"));\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\t// This differs from the Filer version, because here we can use the anura.files API to open the file\n\t// instead of evaluating the contents as js. The behaviour of the Filer version can be replicated by\n\t// registering a file provider that evaluates the contents as js.\n\texec(path: string) {\n\t\tanura.files.open(this.#relativeToAbsolute(path));\n\t}\n\tfind(\n\t\tpath: string,\n\t\toptions?: {\n\t\t\t/**\n\t\t\t * Regex to match file paths against\n\t\t\t */\n\t\t\tregex?: RegExp;\n\t\t\t/**\n\t\t\t * Base name to search for (match patern)\n\t\t\t */\n\t\t\tname?: string;\n\t\t\t/**\n\t\t\t * Folder to search in (match pattern)\n\t\t\t */\n\t\t\tpath?: string;\n\t\t\t/**\n\t\t\t * Callback to execute on each file.\n\t\t\t */\n\t\t\texec?: (path: string) => void;\n\t\t},\n\t\tcallback?: (err: Error | null, files: string[]) => void,\n\t): void;\n\tfind(path: string, callback?: (err: Error | null, files: string[]) => void): void;\n\tfind(path: string, options?: any, callback?: (err: Error | null, files: string[]) => void) {\n\t\tif (typeof options === \"function\") {\n\t\t\tcallback = options;\n\t\t\toptions = {};\n\t\t}\n\n\t\tcallback ||= () => {};\n\t\toptions ||= {};\n\n\t\tfunction walk(dir: string, done: (err: Error | null, files: string[]) => void) {\n\t\t\tconst results: string[] = [];\n\t\t\tanura.fs.readdir(dir, (err: Error | null, list: string[]) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tdone(err, results);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet pending = list.length;\n\t\t\t\tif (!pending) {\n\t\t\t\t\tdone(null, results);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlist.forEach(file => {\n\t\t\t\t\tfile = dir + \"/\" + file;\n\t\t\t\t\tanura.fs.stat(file, (err, stat) => {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\tdone(err, results);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (stat.isDirectory()) {\n\t\t\t\t\t\t\twalk(file, (err, res) => {\n\t\t\t\t\t\t\t\tresults.push(...res);\n\t\t\t\t\t\t\t\tpending--;\n\t\t\t\t\t\t\t\tif (!pending) {\n\t\t\t\t\t\t\t\t\tdone(null, results);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresults.push(file);\n\t\t\t\t\t\t\tpending--;\n\t\t\t\t\t\t\tif (!pending) {\n\t\t\t\t\t\t\t\tdone(null, results);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\twalk(this.#relativeToAbsolute(path), (err, results) => {\n\t\t\tif (err) {\n\t\t\t\tcallback!(err, []);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (options.regex) {\n\t\t\t\tresults = results.filter(file => options.regex!.test(file));\n\t\t\t}\n\t\t\tif (options.name) {\n\t\t\t\tresults = results.filter(file => file.includes(options.name!));\n\t\t\t}\n\t\t\tif (options.path) {\n\t\t\t\tresults = results.filter(file => file.includes(options.path!));\n\t\t\t}\n\t\t\tif (options.exec) {\n\t\t\t\tresults.forEach(file => options.exec!(file));\n\t\t\t} else {\n\t\t\t\tcallback!(null, results);\n\t\t\t}\n\t\t});\n\t}\n\tls(\n\t\tdir: string,\n\t\toptions?: {\n\t\t\trecursive?: boolean;\n\t\t},\n\t\tcallback?: (err: Error | null, entries: any[]) => void,\n\t): void;\n\tls(dir: string, callback?: (err: Error | null, entries: any[]) => void): void;\n\tls(dir: string, options?: any, callback?: (err: Error | null, entries: any[]) => void) {\n\t\tif (typeof options === \"function\") {\n\t\t\tcallback = options;\n\t\t\toptions = {};\n\t\t}\n\t\tcallback ||= () => {};\n\t\toptions ||= {};\n\n\t\tconst entries: any[] = [];\n\n\t\tif (options.recursive) {\n\t\t\tthis.find(dir, (err, files) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tcallback!(err, []);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcallback!(null, files);\n\t\t\t});\n\t\t} else {\n\t\t\tanura.fs.readdir(this.#relativeToAbsolute(dir), (err: Error | null, files: string[]) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tcallback!(err, []);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (files.length === 0) {\n\t\t\t\t\tcallback!(null, []);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet pending = files.length;\n\t\t\t\tfiles.forEach(file => {\n\t\t\t\t\tanura.fs.stat(this.#relativeToAbsolute(dir) + \"/\" + file, (err, stats: { isDirectory: () => boolean }) => {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\tcallback!(err, []);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tentries.push(stats);\n\t\t\t\t\t\tpending--;\n\t\t\t\t\t\tif (!pending) {\n\t\t\t\t\t\t\tcallback!(null, entries);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\tmkdirp(path: string, callback: (err: Error | null) => void) {\n\t\tthis.promises\n\t\t\t.mkdirp(path)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(err => {\n\t\t\t\tcallback(err);\n\t\t\t});\n\t}\n\trm(path: string, options?: { recursive?: boolean }, callback?: (err: Error | null) => void): void;\n\trm(path: string, callback?: (err: Error | null) => void): void;\n\trm(path: string, options?: any, callback?: (err: Error | null) => void) {\n\t\tpath = this.#relativeToAbsolute(path);\n\t\tif (typeof options === \"function\") {\n\t\t\tcallback = options;\n\t\t\toptions = {};\n\t\t}\n\t\tcallback ||= () => {};\n\t\toptions ||= {};\n\n\t\tfunction walk(dir: string, done: (err: Error | null) => void) {\n\t\t\tanura.fs.readdir(dir, (err: Error | null, list: string[]) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tdone(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet pending = list.length;\n\t\t\t\tif (!pending) {\n\t\t\t\t\tanura.fs.rmdir(dir, done);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlist.forEach((file: string) => {\n\t\t\t\t\tfile = dir + \"/\" + file;\n\t\t\t\t\tanura.fs.stat(\n\t\t\t\t\t\tfile,\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\terr,\n\t\t\t\t\t\t\tstats: {\n\t\t\t\t\t\t\t\tisDirectory: () => boolean;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\tdone(err);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\t\t\t\twalk(file, err => {\n\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tdone(err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tpending--;\n\t\t\t\t\t\t\t\t\tif (!pending) {\n\t\t\t\t\t\t\t\t\t\tanura.fs.rmdir(dir, done);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tanura.fs.unlink(file, err => {\n\t\t\t\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\t\t\t\tdone(err);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tpending--;\n\t\t\t\t\t\t\t\t\tif (!pending) {\n\t\t\t\t\t\t\t\t\t\tanura.fs.rmdir(dir, done);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\tanura.fs.stat(path, (err: Error | null, stats: { isDirectory: () => boolean }) => {\n\t\t\tif (err) {\n\t\t\t\tcallback!(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!stats.isDirectory()) {\n\t\t\t\tanura.fs.unlink(path, callback);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (options.recursive) {\n\t\t\t\twalk(path, callback!);\n\t\t\t} else {\n\t\t\t\tanura.fs.readdir(path, (err: Error | null, files: string[]) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tcallback!(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (files.length > 0) {\n\t\t\t\t\t\tcallback!(new Error(\"Directory not empty! Pass { recursive: true } instead to remove it and all its contents.\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\ttempDir(callback?: (err: Error | null, path: string) => void) {\n\t\tcallback ||= () => {};\n\t\tconst tmp = this.env.TMP;\n\t\tanura.fs.mkdir(tmp, () => {\n\t\t\tcallback!(null, tmp);\n\t\t});\n\t}\n\ttouch(path: string, options?: { updateOnly?: boolean; date?: Date }, callback?: (err: Error | null) => void): void;\n\ttouch(path: string, callback?: (err: Error | null) => void): void;\n\ttouch(path: string, options?: any, callback?: (err: Error | null) => void) {\n\t\tpath = this.#relativeToAbsolute(path);\n\t\tif (typeof options === \"function\") {\n\t\t\tcallback = options;\n\t\t\toptions = {\n\t\t\t\tupdateOnly: false,\n\t\t\t\tdate: Date.now(),\n\t\t\t};\n\t\t}\n\t\tcallback ||= () => {};\n\t\toptions ||= {\n\t\t\tupdateOnly: false,\n\t\t\tdate: Date.now(),\n\t\t};\n\n\t\tfunction createFile() {\n\t\t\tanura.fs.writeFile(path, \"\", callback);\n\t\t}\n\n\t\tfunction updateTimes() {\n\t\t\tanura.fs.utimes(path, options.date, options.date, callback);\n\t\t}\n\n\t\tanura.fs.stat(path, (err: Error | null) => {\n\t\t\tif (err) {\n\t\t\t\tif (options.updateOnly) {\n\t\t\t\t\tcallback!(new Error(\"File does not exist and updateOnly is true\"));\n\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\tcreateFile();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tupdateTimes();\n\t\t\t}\n\t\t});\n\t}\n\n\tcd(dir: string) {\n\t\tthis.env.PWD = this.#relativeToAbsolute(dir);\n\t}\n\n\tpwd() {\n\t\treturn this.env.PWD;\n\t}\n\n\tpromises = {\n\t\tcat: async (files: string[]) => {\n\t\t\tlet contents = \"\";\n\t\t\tfor (const file of files) {\n\t\t\t\tcontents += (await anura.fs.promises.readFile(this.#relativeToAbsolute(file))).toString();\n\t\t\t}\n\t\t\treturn contents;\n\t\t},\n\t\texec: async (path: string) => {\n\t\t\tanura.files.open(this.#relativeToAbsolute(path));\n\t\t},\n\t\tfind: (\n\t\t\tpath: string,\n\t\t\toptions?: {\n\t\t\t\tregex?: RegExp;\n\t\t\t\tname?: string;\n\t\t\t\tpath?: string;\n\t\t\t\texec?: (path: string) => void;\n\t\t\t},\n\t\t) => {\n\t\t\treturn new Promise<string[]>((resolve, reject) => {\n\t\t\t\tthis.find(path, options, (err, files) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve(files);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tls: (\n\t\t\tdir: string,\n\t\t\toptions?: {\n\t\t\t\trecursive?: boolean;\n\t\t\t},\n\t\t) => {\n\t\t\treturn new Promise<string[]>((resolve, reject) => {\n\t\t\t\tthis.ls(dir, options, (err, entries) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve(entries);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tcpr: async (src: string, dest: string, options?: any) => {\n\t\t\ttry {\n\t\t\t\tconst stat = await anura.fs.promises.stat(src);\n\t\t\t\tif (options?.createInnerFolder === true) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst destStat = await anura.fs.promises.stat(dest);\n\t\t\t\t\t\tif (destStat.type === \"DIRECTORY\") {\n\t\t\t\t\t\t\tdest = Filer.Path.join(dest, Filer.Path.basename(src));\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Destination does not exist; continue as-is\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (stat.type === \"FILE\") {\n\t\t\t\t\t// Make sure destination directory exists\n\t\t\t\t\tconst destDir = Filer.Path.dirname(dest);\n\n\t\t\t\t\tawait this.promises.mkdirp(destDir);\n\t\t\t\t\tawait anura.fs.promises.writeFile(dest, await anura.fs.promises.readFile(src));\n\t\t\t\t} else if (stat.type === \"DIRECTORY\") {\n\t\t\t\t\tawait this.promises.mkdirp(dest);\n\n\t\t\t\t\tconst items = await anura.fs.promises.readdir(src);\n\t\t\t\t\tfor (const item of items) {\n\t\t\t\t\t\tconst srcPath = Filer.Path.join(src, item);\n\t\t\t\t\t\tconst destPath = Filer.Path.join(dest, item);\n\t\t\t\t\t\tawait this.promises.cpr(srcPath, destPath);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(`Unsupported file type at path: ${src}`);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Error copying from ${src} to ${dest}:`, err);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\t\tmkdirp: async (path: string) => {\n\t\t\tconst parts = this.#relativeToAbsolute(path).split(\"/\");\n\t\t\tlet builder = \"\";\n\t\t\tfor (const part of parts) {\n\t\t\t\tif (part === \"\") continue;\n\t\t\t\tbuilder += \"/\" + part;\n\t\t\t\ttry {\n\t\t\t\t\tawait anura.fs.promises.mkdir(builder);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tif (e.code !== \"EEXIST\") throw e;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\trm: (\n\t\t\tpath: string,\n\t\t\toptions?: {\n\t\t\t\trecursive?: boolean;\n\t\t\t},\n\t\t) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.rm(path, options, err => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\ttouch: (\n\t\t\tpath: string,\n\t\t\toptions?: {\n\t\t\t\tupdateOnly?: boolean;\n\t\t\t\tdate?: Date;\n\t\t\t},\n\t\t) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.touch(path, options, err => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n\n\tconstructor(options?: { env?: { [key: string]: string } }) {\n\t\toptions ||= {\n\t\t\tenv: {\n\t\t\t\tPWD: \"/\",\n\t\t\t\tTMP: \"/tmp\",\n\t\t\t},\n\t\t};\n\t\tif (options?.env) {\n\t\t\tObject.entries(options.env).forEach(([key, value]) => {\n\t\t\t\tthis.env.set(key, value);\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Anura File System API\n *\n * This is fully compatible with Filer's filesystem API and,\n * by extension, most of the Node.js filesystem API. This is\n * a drop-in replacement for the legacy Filer API and should\n * be used in place of the Filer API in all new code.\n *\n * This API has the added benefit of type safety and a the ability\n * to register multiple filesystem providers. This allows for the\n * creation of virtual filesystems and the ability to mount filesystems\n * at arbitrary paths.\n */\nexport class AnuraFilesystem implements AnuraFSOperations<any> {\n\tproviders: Map<string, AFSProvider<any>> = new Map();\n\tproviderCache: { [path: string]: AFSProvider<any> } = {};\n\twhatwgfs = {\n\t\tfs: undefined,\n\t\tgetFolder: async () => {\n\t\t\t// @ts-expect-error\n\t\t\tconst fs = await import(/* @vite-ignore */ \"/public/apps/nfsadapter/nfsadapter.js\");\n\t\t\t// @ts-expect-error\n\t\t\treturn await this.whatwgfs.fs.getOriginPrivateDirectory(\n\t\t\t\t// @ts-expect-error\n\t\t\t\timport(/* @vite-ignore */ \"/public/apps/nfsadapter/adapters/anuraadapter.js\"),\n\t\t\t);\n\t\t},\n\t\tfileOrDirectoryFromPath: async (path: string) => {\n\t\t\ttry {\n\t\t\t\treturn await this.whatwgfs.directoryHandleFromPath(path);\n\t\t\t} catch (e1) {\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.whatwgfs.fileHandleFromPath(path);\n\t\t\t\t} catch (e2) {\n\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\tthrow e1 + e2;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tdirectoryHandleFromPath: async (path: string) => {\n\t\t\tconst picker = await window.anura.import(\"anura.filepicker\");\n\t\t\tconst selectedPath = (await picker.selectFolder()).split(\"/\");\n\t\t\t// prettier-ignore\n\t\t\tlet workingPath = await window.anura.fs.whatwgfs.getFolder();\n\t\t\tfor (const dir of selectedPath) {\n\t\t\t\tif (dir !== \"\") workingPath = await workingPath.getDirectoryHandle(dir);\n\t\t\t}\n\t\t\treturn workingPath;\n\t\t},\n\t\tfileHandleFromPath: async (givenPath: string) => {\n\t\t\tlet path: string | string[] = givenPath.split(\"/\");\n\t\t\tconst file = path.pop();\n\t\t\tpath = path.join(\"/\");\n\n\t\t\t// prettier-ignore\n\t\t\tconst workingPath = await anura.fs.whatwgfs.directoryHandleFromPath(path);\n\t\t\treturn await workingPath.getFileHandle(file);\n\t\t},\n\t\tasync showDirectoryPicker(options: object) {\n\t\t\tconst picker = await window.anura.import(\"anura.filepicker\");\n\t\t\tconst path = (await picker.selectFolder()).split(\"/\");\n\t\t\t// prettier-ignore\n\t\t\tlet workingPath = await window.anura.fs.whatwgfs.getFolder();\n\t\t\tfor (const dir of path) {\n\t\t\t\tif (dir !== \"\") workingPath = await workingPath.getDirectoryHandle(dir);\n\t\t\t}\n\t\t\treturn workingPath;\n\t\t},\n\t\tasync showOpenFilePicker(options: object) {\n\t\t\tconst picker = await window.anura.import(\"anura.filepicker\");\n\t\t\tconst path = (await picker.selectFile()).split(\"/\");\n\t\t\t// prettier-ignore\n\t\t\tlet workingPath = await window.anura.fs.whatwgfs.getFolder();\n\t\t\tfor (const dir of path) {\n\t\t\t\tif (dir !== \"\") workingPath = await workingPath.getFileHandle(dir);\n\t\t\t}\n\t\t\treturn workingPath;\n\t\t},\n\t};\n\n\t// Note: Intentionally aliasing the property to a class instead of an instance\n\tstatic Shell = AFSShell;\n\tShell = AFSShell;\n\n\tconstructor(providers: AFSProvider<any>[]) {\n\t\tproviders.forEach(provider => {\n\t\t\tthis.providers.set(provider.domain, provider);\n\t\t});\n\t\t// These paths must be TS ignore'd since they are in build/\n\n\t\t(async () => {\n\t\t\t// @ts-ignore\n\t\t\tconst fs = await import(\"/public/apps/nfsadapter/nfsadapter.js\");\n\t\t\t// @ts-ignore\n\t\t\tthis.whatwgfs.FileSystemDirectoryHandle = fs.FileSystemDirectoryHandle;\n\t\t\t// @ts-ignore\n\t\t\tthis.whatwgfs.FileSystemFileHandle = fs.FileSystemFileHandle;\n\t\t\t// @ts-ignore\n\t\t\tthis.whatwgfs.FileSystemHandle = fs.FileSystemHandle;\n\t\t\tthis.whatwgfs.fs = fs;\n\t\t})();\n\t}\n\n\tclearCache() {\n\t\tthis.providerCache = {};\n\t}\n\n\tinstallProvider(provider: AFSProvider<any>) {\n\t\tthis.providers.set(provider.domain, provider);\n\t\tthis.clearCache();\n\t}\n\n\tprocessPath(path: string): AFSProvider<any> {\n\t\tif (!path.startsWith(\"/\")) {\n\t\t\tthrow new Error(\"Path must be absolute\");\n\t\t}\n\t\tpath = path.replace(/^\\/+/, \"/\");\n\n\t\tlet provider = this.providerCache[path];\n\t\tif (provider) {\n\t\t\treturn provider;\n\t\t}\n\t\tif (this.providers.has(path)) {\n\t\t\tpath += \"/\";\n\t\t}\n\t\tconst parts = path.split(\"/\");\n\t\tparts.shift();\n\t\tparts.pop();\n\t\twhile (!provider && parts.length > 0) {\n\t\t\tconst checkPath = \"/\" + parts.join(\"/\");\n\t\t\t// @ts-expect-error\n\t\t\tprovider = this.providers.get(checkPath);\n\t\t\tparts.pop();\n\t\t}\n\t\tif (!provider) {\n\t\t\t// @ts-expect-error\n\t\t\tprovider = this.providers.get(\"/\");\n\t\t}\n\t\tthis.providerCache[path] = provider!;\n\t\treturn provider!;\n\t}\n\n\tprocessFD(fd: AnuraFD): AFSProvider<any> {\n\t\treturn this.processPath(fd[AnuraFDSymbol]);\n\t}\n\n\trename(oldPath: string, newPath: string, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(oldPath).rename(oldPath, newPath, callback);\n\t}\n\n\tftruncate(fd: AnuraFD, len: number, callback?: (err: Error | null, fd: AnuraFD) => void) {\n\t\tthis.processFD(fd).ftruncate(fd, len, callback);\n\t}\n\n\ttruncate(path: string, len: number, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(path).truncate(path, len, callback);\n\t}\n\n\tstat(path: string, callback?: (err: Error | null, stats: any) => void) {\n\t\tthis.processPath(path).stat(path, callback);\n\t}\n\n\tfstat(fd: AnuraFD, callback?: ((err: Error | null, stats: any) => void) | undefined): void {\n\t\tthis.processFD(fd).fstat(fd, callback);\n\t}\n\n\tlstat(path: string, callback?: (err: Error | null, stats: any) => void) {\n\t\tthis.processPath(path).lstat(path, callback);\n\t}\n\n\t/** @deprecated fs.exists() is an anachronism and exists only for historical reasons. */\n\texists(path: string, callback?: (exists: boolean) => void) {\n\t\tthis.processPath(path).exists(path, callback);\n\t}\n\n\tlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(srcPath).link(srcPath, dstPath, callback);\n\t}\n\n\tsymlink(path: string, ...rest: any[]) {\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processPath(rest[0]).symlink(path, ...rest);\n\t}\n\n\treadlink(path: string, callback?: (err: Error | null, linkContents: string) => void) {\n\t\tthis.processPath(path).readlink(path, callback);\n\t}\n\n\tunlink(path: string, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(path).unlink(path, callback);\n\t}\n\n\trmdir(path: string, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(path).rmdir(path, callback);\n\t}\n\n\tmkdir(path: string, ...rest: any[]) {\n\t\tthis.processPath(path).mkdir(path, ...rest);\n\t}\n\n\taccess(path: string, ...rest: any[]) {\n\t\tthis.processPath(path).access(path, ...rest);\n\t}\n\n\tmkdtemp(...args: any[]) {\n\t\t// Temp directories should remain in the root filesystem for now\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processPath(path).mkdtemp(...args);\n\t}\n\n\treaddir(path: string, ...rest: any[]) {\n\t\tthis.processPath(path).readdir(path, ...rest);\n\t}\n\n\tclose(fd: AnuraFD, callback?: ((err: Error | null) => void) | undefined): void {\n\t\tthis.processFD(fd).close(fd, callback);\n\t}\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode: number, callback?: ((err: Error | null, fd: AnuraFD) => void) | undefined): void;\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", callback?: ((err: Error | null, fd: AnuraFD) => void) | undefined): void;\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: unknown, callback?: unknown): void {\n\t\tif (typeof mode === \"number\") {\n\t\t\tthis.processPath(path as string).open(path, flags, mode as number, callback as (err: Error | null, fd: AnuraFD) => void);\n\t\t} else {\n\t\t\tthis.processPath(path as string).open(path, flags, mode as (err: Error | null, fd: AnuraFD) => void);\n\t\t}\n\t}\n\n\tutimes(path: string, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(path).utimes(path, atime, mtime, callback);\n\t}\n\n\tfutimes(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processFD(fd).futimes(fd, ...rest);\n\t}\n\n\tchown(path: string, uid: number, gid: number, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(path).chown(path, uid, gid, callback);\n\t}\n\n\tfchown(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processFD(fd).fchown(fd, ...rest);\n\t}\n\n\tchmod(path: string, mode: number, callback?: (err: Error | null) => void) {\n\t\tthis.processPath(path).chmod(path, mode, callback);\n\t}\n\n\tfchmod(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processFD(fd).fchmod(fd, ...rest);\n\t}\n\n\tfsync(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processFD(fd).fsync(fd, ...rest);\n\t}\n\n\twrite(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processFD(fd).write(fd, ...rest);\n\t}\n\n\tread(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-ignore - Overloaded methods are scary\n\t\tthis.processFD(fd).read(fd, ...rest);\n\t}\n\n\treadFile(path: string, callback?: (err: Error | null, data: Uint8Array) => void) {\n\t\tthis.processPath(path).readFile(path, callback);\n\t}\n\n\twriteFile(path: string, data: Uint8Array | string, ...rest: any[]) {\n\t\tif (data instanceof Uint8Array && !(data instanceof Filer.Buffer)) {\n\t\t\tdata = Filer.Buffer.from(data);\n\t\t}\n\n\t\tthis.processPath(path).writeFile(path, data, ...rest);\n\t}\n\n\tappendFile(path: string, data: Uint8Array, callback?: (err: Error | null) => void) {\n\t\tif (data instanceof Uint8Array && !(data instanceof Filer.Buffer)) {\n\t\t\tdata = Filer.Buffer.from(data);\n\t\t}\n\n\t\tthis.processPath(path).appendFile(path, data, callback);\n\t}\n\n\tpromises = {\n\t\tappendFile: (path: string, data: Uint8Array, options: { encoding: string; mode: number; flag: string }) => {\n\t\t\tif (data instanceof Uint8Array && !(data instanceof Filer.Buffer)) {\n\t\t\t\tdata = Filer.Buffer.from(data);\n\t\t\t}\n\n\t\t\treturn this.processPath(path).promises.appendFile(path, data, options);\n\t\t},\n\t\taccess: (path: string, mode?: number) => this.processPath(path).promises.access(path, mode),\n\t\tchown: (path: string, uid: number, gid: number) => this.processPath(path).promises.chown(path, uid, gid),\n\t\tchmod: (path: string, mode: number) => this.processPath(path).promises.chmod(path, mode),\n\t\tlink: (srcPath: string, dstPath: string) => this.processPath(srcPath).promises.link(srcPath, dstPath),\n\t\tlstat: (path: string) => this.processPath(path).promises.lstat(path),\n\t\tmkdir: (path: string, mode?: number) => this.processPath(path).promises.mkdir(path, mode),\n\t\tmkdtemp: (prefix: string, options?: { encoding: string }) => this.processPath(prefix).promises.mkdtemp(prefix, options),\n\t\topen: async (path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number) => this.processPath(path).promises.open(path, flags, mode),\n\t\treaddir: (path: string, options?: string | { encoding: string; withFileTypes: boolean }) => this.processPath(path).promises.readdir(path, options),\n\t\treadFile: (path: string) => this.processPath(path).promises.readFile(path),\n\t\treadlink: (path: string) => this.processPath(path).promises.readlink(path),\n\t\trename: (oldPath: string, newPath: string) => this.processPath(oldPath).promises.rename(oldPath, newPath),\n\t\trmdir: (path: string) => this.processPath(path).promises.rmdir(path),\n\t\tstat: (path: string) => this.processPath(path).promises.stat(path),\n\t\tsymlink: (srcPath: string, dstPath: string, type?: string) => this.processPath(dstPath).promises.symlink(srcPath, dstPath, type),\n\t\ttruncate: (path: string, len: number) => this.processPath(path).promises.truncate(path, len),\n\t\tunlink: (path: string) => this.processPath(path).promises.unlink(path),\n\t\tutimes: (path: string, atime: number | Date, mtime: number | Date) => this.processPath(path).promises.utimes(path, atime, mtime),\n\t\twriteFile: (path: string, data: Uint8Array | string, options?: { encoding: string; mode: number; flag: string }) => {\n\t\t\tif (data instanceof Uint8Array && !(data instanceof Filer.Buffer)) {\n\t\t\t\tdata = Filer.Buffer.from(data);\n\t\t\t}\n\n\t\t\treturn this.processPath(path).promises.writeFile(path, data, options);\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/sys/liquor/api/LocalFS.ts",
    "content": "import { AFSProvider, AnuraFD } from \"./Filesystem\";\nconst AnuraFDSymbol = Symbol.for(\"AnuraFD\");\nconst Filer = window.Filer;\n\nexport class LocalFSStats {\n\tname: string;\n\tsize: number;\n\tatime: Date;\n\tmtime: Date;\n\tctime: Date;\n\tatimeMs: number;\n\tmtimeMs: number;\n\tctimeMs: number;\n\tnode: string;\n\tnlinks: number;\n\tmode: number;\n\ttype: \"FILE\" | \"DIRECTORY\";\n\tuid: number;\n\tgid: number;\n\tdev: string;\n\n\tisFile() {\n\t\treturn this.type === \"FILE\";\n\t}\n\n\tisDirectory() {\n\t\treturn this.type === \"DIRECTORY\";\n\t}\n\n\tisSymbolicLink() {\n\t\treturn (this.mode & 0o170000) === 0o120000;\n\t}\n\n\tconstructor(data: Partial<LocalFSStats>) {\n\t\tthis.name = data.name!;\n\t\tthis.size = data.size || 0;\n\t\tthis.atimeMs = data.atimeMs || Date.now();\n\t\tthis.mtimeMs = data.mtimeMs || Date.now();\n\t\tthis.ctimeMs = data.ctimeMs || Date.now();\n\t\tthis.atime = new Date(this.atimeMs);\n\t\tthis.mtime = new Date(this.mtimeMs);\n\t\tthis.ctime = new Date(this.ctimeMs);\n\t\tthis.node = data.node || crypto.randomUUID();\n\t\tthis.nlinks = data.nlinks || 1;\n\t\tthis.mode = data.mode || 0o100777;\n\t\tthis.type = data.type || \"FILE\";\n\t\tthis.uid = data.uid || 0;\n\t\tthis.gid = data.gid || 0;\n\t\tthis.dev = data.dev || \"localfs\";\n\t}\n}\n\nexport class LocalFS extends AFSProvider<LocalFSStats> {\n\tdirHandle: FileSystemDirectoryHandle;\n\tdomain: string;\n\tname = \"LocalFS\";\n\tversion = \"1.0.0\";\n\tpath: any = Filer.Path; // replace with another polyfill\n\tstats: Map<string, any> = new Map();\n\tfds: FileSystemHandle[] = [];\n\tcursors: number[] = [];\n\n\tconstructor(dirHandle: FileSystemDirectoryHandle, domain: string) {\n\t\tsuper();\n\t\tthis.dirHandle = dirHandle;\n\t\tthis.domain = domain;\n\t\tthis.name += ` (${domain})`;\n\t}\n\n\trelativizePath(path: string) {\n\t\treturn path.replace(this.domain, \"\").replace(/^\\/+/, \"\");\n\t}\n\n\tasync getChildDirHandle(path: string, recurseCounter = 0): Promise<[FileSystemDirectoryHandle, string]> {\n\t\tif (recurseCounter > 20) {\n\t\t\tthrow {\n\t\t\t\tname: \"ELOOP\",\n\t\t\t\tcode: \"ELOOP\",\n\t\t\t\terrno: -40,\n\t\t\t\tmessage: \"no such file or directory\",\n\t\t\t\tpath: (this.domain + \"/\" + path).replace(\"//\", \"/\"),\n\t\t\t};\n\t\t}\n\n\t\tif (path === \"\") {\n\t\t\treturn [this.dirHandle, path];\n\t\t}\n\t\tif (path.endsWith(\"/\")) {\n\t\t\tpath = path.substring(0, path.length - 1);\n\t\t}\n\t\tlet acc = this.dirHandle;\n\t\tlet curr = \"\";\n\t\tfor await (const part of path.split(\"/\")) {\n\t\t\tif (part === \"\" || part === \".\") continue;\n\t\t\tcurr += \"/\" + part;\n\t\t\tif ((this.stats.get(curr)?.mode & 0o170000) === 0o120000) {\n\t\t\t\t// We ran into a path symlink, we're storing symlinks of all types as files who's content is the target.\n\t\t\t\tconst newPart = await (await (await acc.getFileHandle(path)).getFile()).text();\n\t\t\t\tif (newPart.startsWith(\"/\")) {\n\t\t\t\t\t// absolute\n\t\t\t\t\treturn this.getChildDirHandle(newPart, recurseCounter + 1);\n\t\t\t\t} else {\n\t\t\t\t\t// relative\n\t\t\t\t\treturn this.getChildDirHandle(this.path.resolve(curr, newPart), recurseCounter + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tacc = await acc.getDirectoryHandle(part);\n\t\t}\n\t\treturn [acc, curr];\n\t}\n\tasync getFileHandle(path: string, options?: FileSystemGetFileOptions, recurseCounter = 0): Promise<[FileSystemFileHandle, string]> {\n\t\tif (!path.includes(\"/\")) {\n\t\t\tpath = \"/\" + path;\n\t\t}\n\n\t\tconst parentFolder = this.path.dirname(path);\n\n\t\t// eslint-disable-next-line prefer-const\n\t\tlet [parentHandle, realPath] = await this.getChildDirHandle(parentFolder);\n\n\t\tconst fileName = this.path.basename(path);\n\n\t\tif (realPath[0] === \"/\") {\n\t\t\trealPath = realPath.slice(1);\n\t\t}\n\t\tif (this.stats.has(realPath + \"/\" + fileName) && (this.stats.get(realPath + \"/\" + fileName).mode & 0o170000) === 0o120000) {\n\t\t\t// is symlink\n\t\t\tlet realPath = await (await (await parentHandle.getFileHandle(fileName)).getFile()).text();\n\t\t\tif (realPath.startsWith(\"/\")) {\n\t\t\t\tif (realPath.startsWith(this.domain)) {\n\t\t\t\t\trealPath = this.relativizePath(realPath);\n\t\t\t\t\t// absolute\n\t\t\t\t\treturn this.getFileHandle(realPath, options, recurseCounter + 1);\n\t\t\t\t} else {\n\t\t\t\t\t// Okay so, this goes over the mount boundary, and is slightly problematic\n\t\t\t\t\t// for us since we need to handle this as an event OUTSIDE of LocalFS itself\n\t\t\t\t\t// so this is a bit of a cheat using the compatibility layer for FileSystemAccess API\n\t\t\t\t\tlet handle = await window.anura.fs.whatwgfs.getFolder();\n\t\t\t\t\tfor (const part in realPath.split(\"/\").slice(1, -1)) {\n\t\t\t\t\t\thandle = await handle.getDirectoryHandle(part);\n\t\t\t\t\t}\n\t\t\t\t\treturn [await handle.getFileHandle(this.path.basename(realPath)), \"foreign:\" + realPath];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// relative\n\t\t\t\treturn this.getFileHandle(this.path.resolve(parentFolder, realPath), options, recurseCounter + 1);\n\t\t\t}\n\t\t}\n\t\treturn [await parentHandle.getFileHandle(fileName, options), path];\n\t}\n\tstatic async newOPFS(anuraPath: string) {\n\t\tconst dirHandle = await navigator.storage.getDirectory();\n\n\t\ttry {\n\t\t\tawait window.anura.fs.promises.mkdir(anuraPath);\n\t\t} catch (e) {\n\t\t\t// Ignore, the directory already exists so we don't need to create it\n\t\t}\n\t\tconst fs = new LocalFS(dirHandle, anuraPath);\n\t\t// @ts-expect-error\n\t\twindow.anura.fs.installProvider(fs);\n\t\tconst textde = new TextDecoder();\n\t\ttry {\n\t\t\tfs.stats = new Map(JSON.parse(textde.decode(await fs.promises.readFile(anuraPath + \"/.anura_stats\"))));\n\t\t} catch (e: any) {\n\t\t\tconsole.log(\"Error on mount, probably first mount \", e);\n\t\t}\n\n\t\treturn fs;\n\t}\n\tstatic async newSwOPFS() {\n\t\tconst anuraPath = \"/\";\n\t\tconst dirHandle = await navigator.storage.getDirectory();\n\n\t\tconst fs = new LocalFS(dirHandle, anuraPath);\n\t\tconst textde = new TextDecoder();\n\t\ttry {\n\t\t\tfs.stats = new Map(JSON.parse(textde.decode(await fs.promises.readFile(anuraPath + \"/.anura_stats\"))));\n\t\t} catch (e: any) {\n\t\t\tconsole.log(\"Error on mount, probably first mount \", e);\n\t\t}\n\n\t\treturn fs;\n\t}\n\tstatic async new(anuraPath: string) {\n\t\tlet dirHandle;\n\t\ttry {\n\t\t\t// @ts-expect-error\n\t\t\tdirHandle = await window.showDirectoryPicker({\n\t\t\t\tid: `anura-${anuraPath.replace(/\\/|\\s|\\./g, \"-\")}`,\n\t\t\t});\n\t\t} catch (e: any) {\n\t\t\tif (e.name !== \"TypeError\") {\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t\t// The path may not be a valid id, fallback to less specific id\n\t\t\t// @ts-expect-error\n\t\t\tdirHandle = await window.showDirectoryPicker({\n\t\t\t\tid: \"anura-localfs\",\n\t\t\t});\n\t\t}\n\t\tdirHandle.requestPermission({ mode: \"readwrite\" });\n\t\ttry {\n\t\t\tawait window.anura.fs.promises.mkdir(anuraPath);\n\t\t} catch (e) {\n\t\t\t// Ignore, the directory already exists so we don't need to create it\n\t\t}\n\t\tconst fs = new LocalFS(dirHandle, anuraPath);\n\t\t// @ts-ignore\n\t\twindow.anura.fs.installProvider(fs);\n\t\treturn fs;\n\t}\n\treaddir(path: string, _options?: any, callback?: (err: Error | null, files: string[]) => void) {\n\t\tif (typeof _options === \"function\") {\n\t\t\tcallback = _options;\n\t\t}\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.readdir(path)\n\t\t\t.then(files => callback!(null, files))\n\t\t\t.catch(e => callback!(e, []));\n\t}\n\tstat(path: string, callback?: (err: Error | null, stats: any) => void): void {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.stat(path)\n\t\t\t.then(stats => callback!(null, stats))\n\t\t\t.catch(e => callback!(e, null));\n\t}\n\treadFile(path: string, callback?: (err: Error | null, data: typeof Filer.Buffer) => void) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.readFile(path)\n\t\t\t.then(data => callback!(null, data))\n\t\t\t.catch(e => callback!(e, new Filer.Buffer(0)));\n\t}\n\twriteFile(path: string, data: Uint8Array | string, _options?: any, callback?: (err: Error | null) => void) {\n\t\tif (typeof data === \"string\") {\n\t\t\tdata = new TextEncoder().encode(data);\n\t\t}\n\t\tif (typeof _options === \"function\") {\n\t\t\tcallback = _options;\n\t\t}\n\t\tcallback ||= () => {};\n\n\t\tthis.promises\n\t\t\t.writeFile(path, data)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\tappendFile(path: string, data: Uint8Array, callback?: (err: Error | null) => void) {\n\t\tthis.promises\n\t\t\t.appendFile(path, data)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\tunlink(path: string, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.unlink(path)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\tmkdir(path: string, _mode?: any, callback?: (err: Error | null) => void) {\n\t\tif (typeof _mode === \"function\") {\n\t\t\tcallback = _mode;\n\t\t}\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.mkdir(path)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\trmdir(path: string, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.rmdir(path)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\trename(srcPath: string, dstPath: string, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.rename(srcPath, dstPath)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\n\ttruncate(path: string, len: number, callback?: (err: Error | null) => void) {\n\t\tthis.promises\n\t\t\t.truncate(path, len)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\t/** @deprecated — fs.exists() is an anachronism and exists only for historical reasons. */\n\texists(path: string, callback?: (exists: boolean) => void) {\n\t\tthis.stat(path, (err, stats) => {\n\t\t\tif (err) {\n\t\t\t\tcallback!(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcallback!(true);\n\t\t});\n\t}\n\n\t// @ts-ignore\n\tpromises = {\n\t\tsaveStats: async () => {\n\t\t\tconst jsonStats = JSON.stringify(Array.from(this.stats.entries()));\n\t\t\tawait this.promises.writeFile(this.domain + \"/.anura_stats\", jsonStats);\n\t\t},\n\t\twriteFile: async (path: string, data: Uint8Array | string, options?: any) => {\n\t\t\tif (typeof data === \"string\") {\n\t\t\t\tdata = new TextEncoder().encode(data);\n\t\t\t}\n\t\t\tpath = this.relativizePath(path);\n\n\t\t\t// eslint-disable-next-line prefer-const\n\t\t\tlet [handle, realPath] = await this.getFileHandle(path, {\n\t\t\t\tcreate: true,\n\t\t\t});\n\n\t\t\tconst writer = await handle.createWritable();\n\t\t\tif (realPath.startsWith(\"/\")) {\n\t\t\t\trealPath = realPath.slice(1);\n\t\t\t}\n\t\t\tconst fileStats = this.stats.get(realPath) || {};\n\n\t\t\tif (fileStats && !realPath.startsWith(\"foreign:\")) {\n\t\t\t\tfileStats.mtimeMs = Date.now();\n\t\t\t\tfileStats.ctimeMs = Date.now();\n\t\t\t\tthis.stats.set(realPath, fileStats);\n\t\t\t}\n\t\t\t// @ts-expect-error\n\t\t\twriter.write(data);\n\t\t\twriter.close();\n\t\t},\n\t\treadFile: async (path: string) => {\n\t\t\tpath = this.relativizePath(path);\n\n\t\t\tconst [handle, realPath] = await this.getFileHandle(path);\n\t\t\tconst fileStats = this.stats.get(realPath) || {};\n\n\t\t\tif (fileStats && !realPath.startsWith(\"foreign:\")) {\n\t\t\t\tfileStats.atimeMs = Date.now();\n\t\t\t\tthis.stats.set(path, fileStats);\n\t\t\t}\n\n\t\t\treturn new Filer.Buffer(await (await handle.getFile()).arrayBuffer());\n\t\t},\n\t\treaddir: async (path: string) => {\n\t\t\tlet dirHandle, realPath;\n\t\t\ttry {\n\t\t\t\t[dirHandle, realPath] = await this.getChildDirHandle(this.relativizePath(path));\n\t\t\t} catch (e) {\n\t\t\t\tthrow {\n\t\t\t\t\tname: \"ENOENT\",\n\t\t\t\t\tcode: \"ENOENT\",\n\t\t\t\t\terrno: 34,\n\t\t\t\t\tmessage: \"no such file or directory\",\n\t\t\t\t\tpath: (this.domain + \"/\" + path).replace(\"//\", \"/\"),\n\t\t\t\t\tstack: e,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst nodes: string[] = [];\n\t\t\t// @ts-ignore\n\t\t\tfor await (const entry of dirHandle.values()) {\n\t\t\t\tif (entry.name !== \".anura_stats\")\n\t\t\t\t\t// internal file shouldn't appear on fs methods\n\t\t\t\t\tnodes.push(entry.name);\n\t\t\t}\n\t\t\treturn nodes;\n\t\t},\n\t\tappendFile: async (path: string, data: Uint8Array) => {\n\t\t\tconst existingData = await this.promises.readFile(path);\n\t\t\tawait this.promises.writeFile(path, new Uint8Array([...existingData, ...data]));\n\t\t},\n\t\tunlink: async (path: string) => {\n\t\t\tlet parentHandle = this.dirHandle;\n\t\t\tpath = this.relativizePath(path);\n\t\t\tif (path.includes(\"/\")) {\n\t\t\t\tconst parts = path.split(\"/\");\n\t\t\t\tconst finalFile = parts.pop();\n\t\t\t\tparentHandle = (await this.getChildDirHandle(parts.join(\"/\")))[0];\n\t\t\t\tpath = finalFile!;\n\t\t\t}\n\t\t\tawait parentHandle.removeEntry(path);\n\t\t},\n\t\tmkdir: async (path: string) => {\n\t\t\tif (path.endsWith(\"/\")) path = path.slice(0, -1);\n\t\t\tlet parentHandle = this.dirHandle;\n\t\t\tlet realParentPath = \"\";\n\t\t\tpath = this.relativizePath(path);\n\t\t\tif (path.includes(\"/\")) {\n\t\t\t\tconst parts = path.split(\"/\");\n\t\t\t\tconst finalDir = parts.pop();\n\t\t\t\t[parentHandle, realParentPath] = await this.getChildDirHandle(parts.join(\"/\"));\n\t\t\t\tpath = finalDir!;\n\t\t\t}\n\t\t\tif (realParentPath.startsWith(\"/\")) {\n\t\t\t\trealParentPath = realParentPath.slice(1);\n\t\t\t}\n\t\t\tconst fullPath = realParentPath + \"/\" + path;\n\t\t\tconst fileStats = this.stats.get(fullPath) || {};\n\t\t\tif (fileStats) {\n\t\t\t\tfileStats.ctimeMs = Date.now();\n\t\t\t\tthis.stats.set(fullPath, fileStats);\n\t\t\t}\n\n\t\t\tawait parentHandle.getDirectoryHandle(path, { create: true });\n\t\t},\n\t\trmdir: async (path: string) => {\n\t\t\tlet parentHandle = this.dirHandle;\n\t\t\tpath = this.relativizePath(path);\n\t\t\tif (path.includes(\"/\")) {\n\t\t\t\tconst parts = path.split(\"/\");\n\t\t\t\tconst finalDir = parts.pop();\n\t\t\t\tparentHandle = (await this.getChildDirHandle(parts.join(\"/\")))[0];\n\t\t\t\tpath = finalDir!;\n\t\t\t}\n\t\t\tawait parentHandle.removeEntry(path);\n\t\t},\n\t\trename: async (oldPath: string, newPath: string) => {\n\t\t\tconst data = await this.promises.readFile(oldPath);\n\t\t\tawait this.promises.writeFile(newPath, data);\n\t\t\tawait this.promises.unlink(oldPath);\n\t\t},\n\t\tstat: async (path: string) => {\n\t\t\tpath = this.relativizePath(path);\n\t\t\tconst requestPath = path;\n\n\t\t\tlet statPath = path; // when accessing this.stats dont have a trailing slash\n\t\t\tif (statPath.endsWith(\"/\")) statPath = statPath.slice(0, -1);\n\t\t\tconst currStats = this.stats.get(statPath) || {};\n\n\t\t\tlet handle;\n\t\t\ttry {\n\t\t\t\tif (path === \"\") {\n\t\t\t\t\thandle = await this.dirHandle.getFileHandle(path);\n\t\t\t\t} else {\n\t\t\t\t\t[handle, path] = await this.getFileHandle(path);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handleAndPath = await this.getChildDirHandle(path);\n\t\t\t\t\tconst handle = handleAndPath[0];\n\t\t\t\t\tpath = handleAndPath[1];\n\n\t\t\t\t\tlet rootName;\n\t\t\t\t\tif (!path) rootName = this.domain.split(\"/\").pop();\n\t\t\t\t\treturn new LocalFSStats({\n\t\t\t\t\t\tname: rootName || handle.name,\n\t\t\t\t\t\tmode: currStats.mode || 0o40777,\n\t\t\t\t\t\ttype: \"DIRECTORY\",\n\t\t\t\t\t\tatimeMs: currStats.atimeMs || Date.now(),\n\t\t\t\t\t\tmtimeMs: currStats.mtimeMs || Date.now(),\n\t\t\t\t\t\tctimeMs: currStats.ctimeMs || Date.now(),\n\t\t\t\t\t\tuid: currStats.uid || 0,\n\t\t\t\t\t\tgid: currStats.gid || 0,\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tname: \"ENOENT\",\n\t\t\t\t\t\tcode: \"ENOENT\",\n\t\t\t\t\t\terrno: 34,\n\t\t\t\t\t\tmessage: \"no such file or directory\",\n\t\t\t\t\t\tpath: (this.domain + \"/\" + path).replace(\"//\", \"/\"),\n\t\t\t\t\t\tstack: e,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst file = await handle.getFile();\n\t\t\treturn new LocalFSStats({\n\t\t\t\tname: this.path.basename(requestPath),\n\t\t\t\tsize: file.size,\n\t\t\t\ttype: \"FILE\",\n\t\t\t\tmode: currStats.mode || 0o100777,\n\t\t\t\tatimeMs: currStats.atimeMs || Date.now(),\n\t\t\t\tmtimeMs: currStats.mtimeMs || Date.now(),\n\t\t\t\tctimeMs: currStats.ctimeMs || Date.now(),\n\t\t\t\tuid: currStats.uid || 0,\n\t\t\t\tgid: currStats.gid || 0,\n\t\t\t});\n\t\t},\n\t\ttruncate: async (path: string, len: number) => {\n\t\t\tconst data = await this.promises.readFile(path);\n\t\t\tawait this.promises.writeFile(path, data.slice(0, len));\n\t\t},\n\t\taccess(path: string, mode: number): Promise<void> {\n\t\t\t// @ts-ignore\n\t\t\tpath = this.relativizePath(path);\n\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t// @ts-ignore\n\t\t\t\tthis.promises\n\t\t\t\t\t.stat(path)\n\t\t\t\t\t.then(() => resolve()) // File exists\n\t\t\t\t\t.catch(() =>\n\t\t\t\t\t\treject({\n\t\t\t\t\t\t\tname: \"ENOENT\",\n\t\t\t\t\t\t\tcode: \"ENOENT\",\n\t\t\t\t\t\t\terrno: 34,\n\t\t\t\t\t\t\tmessage: `No such file or directory`,\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tstack: \"Error: No such file or directory\",\n\t\t\t\t\t\t} as Error),\n\t\t\t\t\t); // File doesn't exist\n\t\t\t});\n\t\t},\n\t\tchown(path: string, uid: number, gid: number): Promise<void> {\n\t\t\t// @ts-ignore\n\t\t\tpath = this.relativizePath(path);\n\n\t\t\treturn new Promise(async (resolve, reject) => {\n\t\t\t\t// @ts-ignore\n\t\t\t\tconst type = (await this.promises.lstat(path)).type;\n\t\t\t\t// Check if the file exists\n\t\t\t\t// @ts-ignore\n\t\t\t\tconst stats = this.stats.get(path);\n\t\t\t\tif (!stats) {\n\t\t\t\t\treturn reject({\n\t\t\t\t\t\tname: \"ENOENT\",\n\t\t\t\t\t\tcode: \"ENOENT\",\n\t\t\t\t\t\terrno: 34,\n\t\t\t\t\t\tmessage: `No such file or directory`,\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tstack: \"Error: No such file or directory\",\n\t\t\t\t\t} as Error);\n\t\t\t\t}\n\t\t\t\tif (path.endsWith(\"/\")) path = path.slice(0, -1);\n\t\t\t\tif (type === \"DIRECTORY\") {\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tpath = (await this.getChildDirHandle(path))[1];\n\t\t\t\t} else {\n\t\t\t\t\tconst pathDir =\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t(await this.getChildDirHandle(this.path.dirname(path)))[1];\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tpath = pathDir + \"/\" + this.path.basename(path);\n\t\t\t\t}\n\t\t\t\tif (path.startsWith(\"/\")) {\n\t\t\t\t\tpath = path.slice(1);\n\t\t\t\t}\n\n\t\t\t\t// Update ownership in stats\n\t\t\t\tstats.uid = uid;\n\t\t\t\tstats.gid = gid;\n\n\t\t\t\t// Save updated stats\n\t\t\t\t// @ts-ignore\n\t\t\t\tthis.stats.set(path, stats);\n\t\t\t\t// @ts-ignore\n\t\t\t\tthis.promises\n\t\t\t\t\t.saveStats()\n\t\t\t\t\t.then(() => resolve())\n\t\t\t\t\t.catch(reject);\n\t\t\t});\n\t\t},\n\t\tchmod: async (fullPath: string, mode: number) => {\n\t\t\tconst stats = await this.promises.lstat(fullPath);\n\t\t\tconst type = stats.type;\n\t\t\tconst sym = (stats.mode & 0o170000) === 0o120000;\n\n\t\t\tlet path = this.relativizePath(fullPath);\n\n\t\t\tif (path.endsWith(\"/\")) path = path.slice(0, -1);\n\t\t\tif (type === \"DIRECTORY\") {\n\t\t\t\tpath = (await this.getChildDirHandle(path))[1];\n\t\t\t} else {\n\t\t\t\tconst pathDir = (await this.getChildDirHandle(this.path.dirname(path)))[1];\n\t\t\t\tpath = pathDir + \"/\" + this.path.basename(path);\n\t\t\t}\n\t\t\tif (path.startsWith(\"/\")) {\n\t\t\t\tpath = path.slice(1);\n\t\t\t}\n\n\t\t\tconst currStats = this.stats.get(path) || {};\n\t\t\tif (mode > 0o777) {\n\t\t\t\t// Needed for v86\n\n\t\t\t\tmode -= mode & 0o170000;\n\t\t\t}\n\t\t\tif (!sym) {\n\t\t\t\tif (type === \"FILE\") {\n\t\t\t\t\tcurrStats.mode = 0o100000 + mode;\n\t\t\t\t}\n\t\t\t\tif (type === \"DIRECTORY\") {\n\t\t\t\t\tcurrStats.mode = 0o40000 + mode;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcurrStats.mode = 0o120000 + mode;\n\t\t\t}\n\n\t\t\tthis.stats.set(path, currStats);\n\n\t\t\tawait this.promises.saveStats();\n\t\t},\n\t\tlink: (existingPath: string, newPath: string): Promise<void> => {\n\t\t\treturn this.promises.symlink(existingPath, newPath);\n\t\t},\n\t\tlstat: async (path: string) => {\n\t\t\tpath = this.relativizePath(path);\n\n\t\t\tlet statPath = path; // when accessing this.stats dont have a trailing slash\n\t\t\tif (statPath.endsWith(\"/\")) statPath = statPath.slice(0, -1);\n\t\t\tconst currStats = this.stats.get(statPath) || {};\n\n\t\t\tlet handle;\n\t\t\ttry {\n\t\t\t\tif (path === \"\") {\n\t\t\t\t\thandle = await this.dirHandle.getFileHandle(path);\n\t\t\t\t} else {\n\t\t\t\t\tconst parent = await this.getChildDirHandle(this.path.dirname(path));\n\t\t\t\t\thandle = parent[0];\n\t\t\t\t\tconst parentPath = parent[1];\n\t\t\t\t\thandle = await handle.getFileHandle(this.path.basename(path));\n\t\t\t\t\tpath += path.lastIndexOf(\"/\");\n\t\t\t\t\tpath = parentPath + this.path.basename(path);\n\t\t\t\t\t// [handle, path] = await this.getFileHandle(path);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\ttry {\n\t\t\t\t\tconst handleAndPath = await this.getChildDirHandle(path);\n\t\t\t\t\tconst handle = handleAndPath[0];\n\t\t\t\t\tpath = handleAndPath[1];\n\n\t\t\t\t\tlet rootName;\n\t\t\t\t\tif (!path) rootName = this.domain.split(\"/\").pop();\n\t\t\t\t\treturn new LocalFSStats({\n\t\t\t\t\t\tname: rootName || handle.name,\n\t\t\t\t\t\tmode: currStats.mode || 0o40777,\n\t\t\t\t\t\ttype: \"DIRECTORY\",\n\t\t\t\t\t\tatimeMs: currStats.atimeMs || Date.now(),\n\t\t\t\t\t\tmtimeMs: currStats.mtimeMs || Date.now(),\n\t\t\t\t\t\tctimeMs: currStats.ctimeMs || Date.now(),\n\t\t\t\t\t\tuid: currStats.uid || 0,\n\t\t\t\t\t\tgid: currStats.gid || 0,\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tname: \"ENOENT\",\n\t\t\t\t\t\tcode: \"ENOENT\",\n\t\t\t\t\t\terrno: 34,\n\t\t\t\t\t\tmessage: \"no such file or directory\",\n\t\t\t\t\t\tpath: (this.domain + \"/\" + path).replace(\"//\", \"/\"),\n\t\t\t\t\t\tstack: e,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst file = await handle.getFile();\n\t\t\treturn new LocalFSStats({\n\t\t\t\tname: file.name,\n\t\t\t\tsize: file.size,\n\t\t\t\ttype: \"FILE\",\n\t\t\t\tmode: currStats.mode || 0o100777,\n\t\t\t\tatimeMs: currStats.atimeMs || Date.now(),\n\t\t\t\tmtimeMs: currStats.mtimeMs || Date.now(),\n\t\t\t\tctimeMs: currStats.ctimeMs || Date.now(),\n\t\t\t\tuid: currStats.uid || 0,\n\t\t\t\tgid: currStats.gid || 0,\n\t\t\t});\n\t\t},\n\t\tmkdtemp: (template: string): Promise<string> => {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t// Check if template has 'XXXXXX'\n\t\t\t\tif (!template.includes(\"XXXXXX\")) {\n\t\t\t\t\treturn reject({\n\t\t\t\t\t\tname: \"EINVAL\",\n\t\t\t\t\t\tcode: \"EINVAL\",\n\t\t\t\t\t\terrno: 342,\n\t\t\t\t\t\tmessage: \"Invalid template, must contain 'XXXXXX'.\",\n\t\t\t\t\t\tstack: \"Error: Invalid template\",\n\t\t\t\t\t} as Error);\n\t\t\t\t}\n\n\t\t\t\t// Generate a random suffix\n\t\t\t\tconst randomSuffix = Math.random().toString(36).slice(2, 8); // 6-character random string\n\n\t\t\t\t// Replace 'XXXXXX' in the template with the random suffix\n\t\t\t\tconst newDir = template.replace(\"XXXXXX\", randomSuffix);\n\n\t\t\t\tthis.promises.mkdir(newDir);\n\t\t\t\t// Save the new stats\n\t\t\t\tthis.promises\n\t\t\t\t\t.saveStats()\n\t\t\t\t\t.then(() => resolve(newDir)) // Return the new directory path\n\t\t\t\t\t.catch(reject);\n\t\t\t});\n\t\t},\n\t\topen: async (path: string, _flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", _mode?: any) => {\n\t\t\tpath = this.relativizePath(path);\n\t\t\tconst stats = this.stats.get(path);\n\n\t\t\tconst parentHandle = this.dirHandle;\n\t\t\tconst [handle] = await this.getFileHandle(path, { create: true });\n\t\t\t(handle as any).path = path; // Hack\n\t\t\tthis.fds.push(handle);\n\t\t\treturn {\n\t\t\t\tfd: this.fds.length - 1,\n\t\t\t\t[AnuraFDSymbol]: this.domain,\n\t\t\t};\n\t\t},\n\t\treadlink: async (path: string) => {\n\t\t\t// Check if the path exists in stats\n\t\t\tpath = this.relativizePath(path);\n\t\t\tconst fileName = this.path.basename(path);\n\t\t\t// eslint-disable-next-line prefer-const\n\t\t\tlet [parentHandle, realParent] = await this.getChildDirHandle(this.path.dirname(path));\n\t\t\tif (realParent.startsWith(\"/\")) {\n\t\t\t\trealParent = realParent.slice(1);\n\t\t\t}\n\t\t\tconst stats = this.stats.get(realParent + \"/\" + fileName);\n\t\t\tif (!stats) {\n\t\t\t\tthrow {\n\t\t\t\t\tname: \"ENOENT\",\n\t\t\t\t\tcode: \"ENOENT\",\n\t\t\t\t\terrno: 34,\n\t\t\t\t\tmessage: `No such file or directory`,\n\t\t\t\t\tpath,\n\t\t\t\t\tstack: \"Error: No such file\",\n\t\t\t\t} as Error;\n\t\t\t}\n\t\t\tif (!((stats.mode & 0o170000) === 0o120000)) {\n\t\t\t\tthrow {\n\t\t\t\t\t// I think this is the wrong error type\n\t\t\t\t\tname: \"EINVAL\",\n\t\t\t\t\tcode: \"EINVAL\",\n\t\t\t\t\terrno: 342,\n\t\t\t\t\tmessage: `Is not a symbolic link: ${path}`,\n\t\t\t\t\tstack: \"Error: Is not a symbolic link\",\n\t\t\t\t} as Error;\n\t\t\t}\n\n\t\t\t// Return the target path\n\t\t\treturn await (await (await parentHandle.getFileHandle(fileName)).getFile()).text();\n\t\t},\n\t\tsymlink: async (target: string, path: string) => {\n\t\t\t// await this.promises.stat(path);\n\t\t\t// Save stats and resolve the promise\n\t\t\tpath = this.relativizePath(path);\n\t\t\t// if (target.startsWith(\"/\")) {\n\t\t\t//     target = this.relativizePath(target);\n\t\t\t// }\n\n\t\t\tconst fileName = this.path.basename(path);\n\n\t\t\t// eslint-disable-next-line prefer-const\n\t\t\tlet [parentHandle, realParent] = await this.getChildDirHandle(this.path.dirname(path));\n\t\t\tconst fileHandleWritable = await (await parentHandle.getFileHandle(fileName, { create: true })).createWritable();\n\t\t\tfileHandleWritable.write(target);\n\t\t\tfileHandleWritable.close();\n\n\t\t\tif (realParent.startsWith(\"/\")) realParent = realParent.slice(1);\n\n\t\t\tconst fullPath = realParent + \"/\" + fileName;\n\t\t\tconst fileStats = this.stats.get(fullPath) || {};\n\t\t\tif (fullPath) {\n\t\t\t\tfileStats.mode = 41380;\n\t\t\t\tfileStats.ctimeMs = Date.now();\n\t\t\t\tfileStats.mtimeMs = Date.now();\n\t\t\t\tthis.stats.set(fullPath, fileStats);\n\t\t\t}\n\n\t\t\tawait this.promises.saveStats();\n\t\t\treturn;\n\t\t},\n\t\tutimes: async (path: string, atime: Date | number, mtime: Date | number) => {\n\t\t\tconst type = (await this.promises.lstat(path)).type;\n\t\t\t// Ensure path is relative\n\t\t\tpath = this.relativizePath(path);\n\n\t\t\t// If the times are provided as numbers (timestamps), convert them to dates\n\t\t\tconst accessTime = typeof atime === \"number\" ? new Date(atime) : atime;\n\t\t\tconst modifiedTime = typeof mtime === \"number\" ? new Date(mtime) : mtime;\n\n\t\t\tif (type === \"DIRECTORY\") {\n\t\t\t\tpath = (await this.getChildDirHandle(path))[1];\n\t\t\t} else {\n\t\t\t\tconst pathDir = (await this.getChildDirHandle(this.path.dirname(path)))[1];\n\t\t\t\tpath = pathDir + \"/\" + this.path.basename(path);\n\t\t\t}\n\t\t\tif (path.startsWith(\"/\")) {\n\t\t\t\tpath = path.slice(1);\n\t\t\t}\n\t\t\t// Fetch the current stats for the file, or initialize them if not present\n\t\t\tlet fileStats = this.stats.get(path);\n\t\t\tif (!fileStats) {\n\t\t\t\t// Try to stat the file if not present in stats map\n\t\t\t\tfileStats = await this.promises.stat(path);\n\t\t\t}\n\n\t\t\t// Update the times in the file stats\n\t\t\tfileStats.atimeMs = accessTime.getTime();\n\t\t\tfileStats.mtimeMs = modifiedTime.getTime();\n\n\t\t\t// Save the updated stats back into the stats map\n\t\t\tthis.stats.set(path, fileStats);\n\t\t\tawait this.promises.saveStats();\n\t\t},\n\t};\n\n\tftruncate(fd: AnuraFD, len: number, callback?: (err: Error | null, fd: AnuraFD) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (!handle) {\n\t\t\tcallback(\n\t\t\t\t{\n\t\t\t\t\tname: \"EBADF\",\n\t\t\t\t\tcode: \"EBADF\",\n\t\t\t\t\terrno: 9,\n\t\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t\t} as Error,\n\t\t\t\tfd,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst path = (handle as any).path;\n\t\tthis.promises\n\t\t\t.truncate(path, len)\n\t\t\t.then(() => callback!(null, fd))\n\t\t\t.catch(err => {\n\t\t\t\tcallback(err, fd);\n\t\t\t});\n\t}\n\n\tfstat(fd: AnuraFD, callback: (err: Error | null, stats: any) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (handle === undefined) {\n\t\t\tcallback(\n\t\t\t\t{\n\t\t\t\t\tname: \"EBADF\",\n\t\t\t\t\tcode: \"EBADF\",\n\t\t\t\t\terrno: 9,\n\t\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t\t} as Error,\n\t\t\t\tnull,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (handle.kind === \"file\") {\n\t\t\t(handle as FileSystemFileHandle).getFile().then(file => {\n\t\t\t\tcallback(null, new LocalFSStats({ name: file.name, size: file.size }));\n\t\t\t});\n\t\t} else {\n\t\t\tcallback(\n\t\t\t\t{\n\t\t\t\t\tname: \"EISDIR\",\n\t\t\t\t\tcode: \"EISDIR\",\n\t\t\t\t\terrno: 28,\n\t\t\t\t\tmessage: \"Is a directory\",\n\t\t\t\t\tstack: \"Error: Is a directory\",\n\t\t\t\t} as Error,\n\t\t\t\tnull,\n\t\t\t);\n\t\t}\n\t}\n\n\tlstat(path: string, callback?: (err: Error | null, stats: any) => void): void {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.lstat(path)\n\t\t\t.then(stats => callback!(null, stats))\n\t\t\t.catch(e => callback!(e, null));\n\t}\n\n\tlink(existingPath: string, newPath: string, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.link(existingPath, newPath)\n\t\t\t.then(() => callback(null))\n\t\t\t.catch(callback);\n\t}\n\n\tsymlink(target: string, path: string, type: any, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.symlink(target, path)\n\t\t\t.then(() => callback(null))\n\t\t\t.catch(callback);\n\t}\n\n\treadlink(path: any, callback?: any) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.readlink(path)\n\t\t\t.then(linkString => callback(null, linkString))\n\t\t\t.catch(callback);\n\t}\n\n\taccess(path: string, mode: any, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\n\t\tthis.promises\n\t\t\t.access(path, mode)\n\t\t\t.then(() => callback(null))\n\t\t\t.catch(callback);\n\t}\n\n\tmkdtemp(prefix: string, options: any, callback?: (err: Error | null, path: string) => void): void {\n\t\tcallback ||= () => {};\n\n\t\tthis.promises\n\t\t\t.mkdtemp(prefix)\n\t\t\t.then(folder => callback(null, folder))\n\t\t\t.catch(err => {\n\t\t\t\tcallback(err, null!);\n\t\t\t});\n\t}\n\n\tfchown(fd: AnuraFD, uid: number, gid: number, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (!handle) {\n\t\t\tcallback({\n\t\t\t\tname: \"EBADF\",\n\t\t\t\tcode: \"EBADF\",\n\t\t\t\terrno: 9,\n\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t} as Error);\n\t\t\treturn;\n\t\t}\n\n\t\tconst path = (handle as any).path; // Retrieve the file path\n\n\t\t// Reuse the chown logic to update ownership by path\n\t\tthis.chown(path, uid, gid, callback);\n\t}\n\n\tchmod(path: string, mode: number, callback?: (err: Error | null) => void) {\n\t\tthis.promises\n\t\t\t.chmod(path, mode)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\n\tfchmod(fd: AnuraFD, mode: number, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (!handle) {\n\t\t\tcallback({\n\t\t\t\tname: \"EBADF\",\n\t\t\t\tcode: \"EBADF\",\n\t\t\t\terrno: 9,\n\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t} as Error);\n\t\t\treturn;\n\t\t}\n\n\t\tconst path = (handle as any).path; // Retrieve the file path\n\t\tthis.promises\n\t\t\t.chmod(path, mode)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\n\tfsync(fd: AnuraFD, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (!handle) {\n\t\t\tcallback({\n\t\t\t\tname: \"EBADF\",\n\t\t\t\tcode: \"EBADF\",\n\t\t\t\terrno: 9,\n\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t} as Error);\n\t\t\treturn;\n\t\t}\n\t\t// In the OPFS API, data is automatically flushed to disk, so fsync can be a no-op.\n\t\tcallback(null);\n\t}\n\n\twrite(fd: AnuraFD, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, nbytes: number) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (position !== null) {\n\t\t\tposition += this.cursors[fd.fd] || 0;\n\t\t} else {\n\t\t\tposition = this.cursors[fd.fd] || 0;\n\t\t}\n\t\tif (handle === undefined) {\n\t\t\tcallback(\n\t\t\t\t{\n\t\t\t\t\tname: \"EBADF\",\n\t\t\t\t\tcode: \"EBADF\",\n\t\t\t\t\terrno: 9,\n\t\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t\t} as Error,\n\t\t\t\t0,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tif (handle.kind === \"directory\") {\n\t\t\tcallback(\n\t\t\t\t{\n\t\t\t\t\tname: \"EISDIR\",\n\t\t\t\t\tcode: \"EISDIR\",\n\t\t\t\t\terrno: 28,\n\t\t\t\t\tmessage: \"Is a directory\",\n\t\t\t\t\tstack: \"Error: Is a directory\",\n\t\t\t\t} as Error,\n\t\t\t\t0,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst bufferSlice = buffer.slice(offset, offset + length);\n\t\t(handle as FileSystemFileHandle).createWritable().then(writer => {\n\t\t\twriter.seek(position || 0);\n\t\t\twriter.write(bufferSlice);\n\t\t\twriter.close();\n\t\t\tthis.cursors[fd.fd] = (position || 0) + bufferSlice.length;\n\t\t\tcallback!(null, bufferSlice.length);\n\t\t});\n\t}\n\n\tread(fd: AnuraFD, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, bytesRead: number, buffer: Uint8Array) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (handle === undefined) {\n\t\t\tcallback(\n\t\t\t\t{\n\t\t\t\t\tname: \"EBADF\",\n\t\t\t\t\tcode: \"EBADF\",\n\t\t\t\t\terrno: 9,\n\t\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t\t} as Error,\n\t\t\t\t0,\n\t\t\t\tnull!,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tif (handle.kind === \"directory\") {\n\t\t\tcallback(\n\t\t\t\t{\n\t\t\t\t\tname: \"EISDIR\",\n\t\t\t\t\tcode: \"EISDIR\",\n\t\t\t\t\terrno: 28,\n\t\t\t\t\tmessage: \"Is a directory\",\n\t\t\t\t\tstack: \"Error: Is a directory\",\n\t\t\t\t} as Error,\n\t\t\t\t0,\n\t\t\t\tnull!,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\t// Resolve symbolic link, if necessary\n\n\t\tconst fileHandle = handle as FileSystemFileHandle;\n\t\tfileHandle.getFile().then(file => {\n\t\t\tconst reader = new FileReader();\n\t\t\treader.onload = () => {\n\t\t\t\tconst data = new Uint8Array(reader.result as ArrayBuffer);\n\t\t\t\tbuffer.set(data.slice(offset, offset + length));\n\t\t\t\tcallback(null, data.length, buffer);\n\t\t\t};\n\t\t\treader.onerror = e => {\n\t\t\t\tcallback(new Error(\"Failed to read file\"), 0, null!);\n\t\t\t};\n\t\t\treader.readAsArrayBuffer(file.slice(position || 0, (position || 0) + length));\n\t\t});\n\t}\n\n\tutimes(path: string, atime: Date | number, mtime: Date | number, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.utimes(path, atime, mtime)\n\t\t\t.then(() => callback!(null))\n\t\t\t.catch(callback);\n\t}\n\n\tfutimes(fd: AnuraFD, atime: Date, mtime: Date, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (!handle) {\n\t\t\tcallback({\n\t\t\t\tname: \"EBADF\",\n\t\t\t\tcode: \"EBADF\",\n\t\t\t\terrno: 9,\n\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t} as Error);\n\t\t\treturn;\n\t\t}\n\n\t\tconst path = (handle as any).path; // Retrieve the file path\n\t\tthis.utimes(path, atime, mtime, callback);\n\t}\n\n\tchown(path: string, uid: number, gid: number, callback?: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\n\t\tthis.promises\n\t\t\t.chown(path, uid, gid)\n\t\t\t.then(() => callback(null))\n\t\t\t.catch(callback);\n\t}\n\n\tclose(fd: AnuraFD, callback: (err: Error | null) => void) {\n\t\tcallback ||= () => {};\n\t\tconst handle = this.fds[fd.fd];\n\t\tif (handle === undefined) {\n\t\t\tcallback({\n\t\t\t\tname: \"EBADF\",\n\t\t\t\tcode: \"EBADF\",\n\t\t\t\terrno: 9,\n\t\t\t\tmessage: \"bad file descriptor\",\n\t\t\t\tstack: \"Error: bad file descriptor\",\n\t\t\t} as Error);\n\t\t\treturn;\n\t\t}\n\t\tdelete this.fds[fd.fd];\n\t\tcallback(null);\n\t}\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: any, callback?: ((err: Error | null, fd: AnuraFD) => void) | undefined): void {\n\t\tif (typeof mode === \"function\") {\n\t\t\tcallback = mode;\n\t\t}\n\t\tcallback ||= () => {};\n\t\tthis.promises\n\t\t\t.open(path, flags, mode)\n\t\t\t.then(fd => {\n\t\t\t\t// @ts-ignore\n\t\t\t\tcallback!(null, fd);\n\t\t\t})\n\t\t\t// @ts-ignore\n\t\t\t.catch(e => callback!(e, { fd: -1, [AnuraFDSymbol]: this.domain }));\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Networking.ts",
    "content": "export class Networking {\n\tlibcurl: any;\n\t// @ts-expect-error\n\tWebSocket: typeof WebSocket;\n\tSocket: any;\n\tTLSSocket: any;\n\texternal = {\n\t\tfetch: window.fetch,\n\t};\n\tconstructor() {\n\t\tif (window.tb.libcurl) {\n\t\t\tthis.libcurl = window.tb.libcurl;\n\t\t\tthis.initLibcurl();\n\t\t} else {\n\t\t\tconsole.warn(\"Anura Networking failed to connect to the TB instance\");\n\t\t}\n\t}\n\tprivate initLibcurl = async () => {\n\t\ttry {\n\t\t\tthis.WebSocket = this.libcurl.WebSocket;\n\n\t\t\t// @ts-ignore\n\t\t\tthis.external.fetch = (...args) => {\n\t\t\t\treturn this.libcurl.fetch(...args);\n\t\t\t};\n\n\t\t\tthis.Socket = this.libcurl.WispConnection;\n\t\t\tthis.TLSSocket = this.libcurl.TLSSocket;\n\t\t\tconst wisp_server = JSON.parse(await window.tb.fs.promises.readFile(`/home/${await window.tb.user.username()}/settings.json`, \"utf8\")).wispServer;\n\t\t\tthis.setWispServer(wisp_server);\n\t\t\tconsole.log(\"libcurl.js ready!\");\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"Anura Networking Error:\", error);\n\t\t}\n\t};\n\tloopback = {\n\t\taddressMap: new Map(),\n\t\tcall: async (port: number, request: Request) => {\n\t\t\treturn await this.loopback.addressMap.get(port)(request);\n\t\t},\n\t\tset: async (port: number, handler: () => Response) => {\n\t\t\tthis.loopback.addressMap.set(port, handler);\n\t\t},\n\t\tderegister: async (port: number) => {\n\t\t\tthis.loopback.addressMap.delete(port);\n\t\t},\n\t};\n\tfetch = async (url: any, methods: any) => {\n\t\tconsole.log(this.libcurl.ready);\n\t\tlet requestObj: Request;\n\t\tif (url instanceof Request) {\n\t\t\trequestObj = url;\n\t\t} else {\n\t\t\tif (methods) requestObj = new Request(url, methods);\n\t\t\telse requestObj = new Request(url);\n\t\t}\n\t\tconst urlObj = new URL(requestObj.url);\n\t\tif (urlObj.hostname === \"localhost\") {\n\t\t\tconst port = Number(urlObj.port) || 80;\n\t\t\tif (this.loopback.addressMap.has(port)) return this.loopback.call(port, requestObj);\n\t\t\telse {\n\t\t\t\twindow.anura.notifications.add({\n\t\t\t\t\ttitle: \"Anura Networking Error\",\n\t\t\t\t\tdescription: \"fetch requested to non binded localhost port\",\n\t\t\t\t\ttimeout: 5000,\n\t\t\t\t});\n\t\t\t\treturn new Response();\n\t\t\t}\n\t\t} else {\n\t\t\treturn this.external.fetch(url, methods);\n\t\t}\n\t};\n\tsetWispServer = (wisp_server: string) => {\n\t\tthis.libcurl.set_websocket(wisp_server);\n\t};\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Notification.ts",
    "content": "//TODO\n"
  },
  {
    "path": "src/sys/liquor/api/NotificationService.tsx",
    "content": "interface NotifParams {\n\ttitle: string;\n\tdescription: string;\n\ttimeout?: number;\n\tcallback?: () => void;\n\tcloseIndicator?: boolean;\n\ticon?: string;\n\tbuttons?: Array<{ text: string; callback: Function }>;\n}\n\nexport class NotificationService {\n\telement: HTMLDivElement | null = null;\n\n\tconstructor() {\n\t\tconsole.log(\"Loading notifications API\");\n\t}\n\n\tadd(params: NotifParams) {\n\t\t// API STUB\n\t\tconsole.log(params);\n\t\twindow.parent.tb.notification.Toast({\n\t\t\tapplication: params.title,\n\t\t\ticonSrc: \"/assets/img/logo.png\",\n\t\t\tmessage: params.description,\n\t\t\ttime: params.timeout ? params.timeout : 10000,\n\t\t\tonOk: params.callback,\n\t\t});\n\t}\n\tremove(_notification: any) {\n\t\t// API STUB\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Platform.ts",
    "content": "export class Platform {\n\ttype: string;\n\ttouchInput: boolean;\n\n\tconstructor() {\n\t\tthis.type = \"desktop\";\n\t\tthis.touchInput = false;\n\t\tconst mobileRE =\n\t\t\t/(android|bb\\d+|meego).+mobile|armv7l|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series[46]0|samsungbrowser.*mobile|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i;\n\t\tconst notMobileRE = /CrOS/;\n\t\tconst tabletRE = /android|ipad|playbook|silk/i;\n\t\tconst ua = navigator.userAgent;\n\t\tif (typeof ua === \"string\") {\n\t\t\tif (mobileRE.test(ua) && !notMobileRE.test(ua)) {\n\t\t\t\tconsole.log(\"Mobile detected\");\n\t\t\t\tthis.type = \"mobile\";\n\t\t\t\tthis.touchInput = true;\n\t\t\t} else if (tabletRE.test(ua)) {\n\t\t\t\tconsole.log(\"Tablet detected\");\n\t\t\t\tthis.type = \"tablet\";\n\t\t\t\tthis.touchInput = true;\n\t\t\t}\n\n\t\t\tif (!mobileRE.test(ua) && navigator && navigator.maxTouchPoints > 1 && ua.indexOf(\"Macintosh\") !== -1 && ua.indexOf(\"Safari\") !== -1) {\n\t\t\t\tconsole.log(\"Mobile detected\");\n\t\t\t\tthis.type = \"mobile\";\n\t\t\t\tthis.touchInput = true;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Process.ts",
    "content": "export class Processes {\n\tprocessesDiv: HTMLDivElement | null;\n\tconstructor() {\n\t\tthis.processesDiv = document.querySelector(\"window-area\");\n\t}\n\tget procs() {\n\t\tconst wins = window.anura.wm.windows;\n\t\tconst arr: WeakRef<any>[] = wins.reduce((out: WeakRef<any>[], w: any) => {\n\t\t\tif (!w) return out;\n\t\t\tout.push(typeof w?.deref === \"function\" ? (w as WeakRef<any>) : new WeakRef(w));\n\t\t\treturn out;\n\t\t}, []);\n\t\tconst s1 = Symbol();\n\t\tconst s2 = Symbol();\n\t\t(arr as any)[s1] = [];\n\t\t(arr as any)[s2] = Array.from(arr);\n\t\treturn new Proxy(arr, {});\n\t}\n\n\tset procs(value) {\n\t\tconsole.log(`API Stub, ${value} will not be used`);\n\t\twindow.tb.process.create();\n\t}\n\n\tremove(pid: number) {\n\t\twindow.tb.process.kill(String(pid));\n\t}\n\n\tregister(proc: Process) {\n\t\tconsole.log(`API Stub, ${proc} will not be used`);\n\t\twindow.tb.process.create();\n\t}\n\n\tcreate(proc: any) {\n\t\tconsole.log(`API Stub, ${proc} will not be used`);\n\t\twindow.tb.process.create();\n\t}\n}\n\nabstract class Process {\n\tabstract pid: number;\n\tabstract title: string;\n\t// @ts-expect-error\n\tstdout: ReadableStream<Uint8Array>;\n\t// @ts-expect-error\n\tstderr: ReadableStream<Uint8Array>;\n\t// @ts-expect-error\n\tstdin: WritableStream<Uint8Array>;\n\n\tkill() {\n\t\twindow.tb.process.kill(String(this.pid));\n\t}\n\tabstract get alive(): boolean;\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Settings.ts",
    "content": "export class Settings {\n\tprivate cache: { [key: string]: any } = {};\n\tfs: FilerFS;\n\tprivate constructor(fs: FilerFS, inital: { [key: string]: any }) {\n\t\tthis.fs = fs;\n\t\tthis.cache = inital;\n\n\t\tnavigator.serviceWorker.ready.then(isReady => {\n\t\t\tisReady.active!.postMessage({\n\t\t\t\tanura_target: \"anura.cache\",\n\t\t\t\tvalue: this.cache[\"use-sw-cache\"],\n\t\t\t});\n\t\t\tisReady.active!.postMessage({\n\t\t\t\tanura_target: \"anura.bareurl\",\n\t\t\t\tvalue: this.cache[\"bare-url\"],\n\t\t\t});\n\t\t\tconsole.debug(\"ANURA-SW: For this boot, cache will be \" + (this.cache[\"use-sw-cache\"] ? \"enabled\" : \"disabled\"));\n\t\t\tthis.cache[\"FileExts\"] = {\n\t\t\t\ttxt: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tmp3: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tflac: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\twav: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\togg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tmp4: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tmov: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\twebm: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tgif: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tpng: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tjpg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tjpeg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tsvg: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tpdf: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tpy: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tjs: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tmjs: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tcjs: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tjson: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\thtml: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tcss: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t\tdefault: { handler_type: \"module\", id: \"anura.fileviewer\" },\n\t\t\t};\n\t\t});\n\t}\n\n\tstatic async new(fs: FilerFS, defaultsettings: { [key: string]: any }) {\n\t\tconst initial = defaultsettings;\n\n\t\tif (!initial[\"wisp-url\"]) {\n\t\t\tlet url = \"\";\n\t\t\tif (location.protocol == \"https:\") {\n\t\t\t\turl += \"wss://\";\n\t\t\t} else {\n\t\t\t\turl += \"ws://\";\n\t\t\t}\n\t\t\turl += window.location.origin.split(\"://\")[1];\n\t\t\turl += \"/\";\n\t\t\tinitial[\"wisp-url\"] = url;\n\t\t}\n\n\t\ttry {\n\t\t\tconst raw = await fs.promises.readFile(\"/system/etc/anura/anura_settings.json\");\n\t\t\t// This Uint8Array is actuallly a buffer, so JSON.parse can handle it\n\t\t\tObject.assign(initial, JSON.parse(raw as any));\n\t\t} catch (e) {\n\t\t\tfs.mkdir(\"/system/etc/anura/\");\n\t\t\tfs.mkdir(\"/system/etc/anura/configs/\");\n\t\t\tfs.mkdir(\"/system/etc/anura/init/\");\n\t\t\tfs.mkdir(\"/system/bin/anura/\");\n\t\t\tfs.writeFile(\n\t\t\t\t\"/system/etc/anura/theme.json\",\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tforeground: \"#ffffff\",\n\t\t\t\t\tsecondaryForeground: \"#ffffff38\",\n\t\t\t\t\tborder: \"#ffffff28\",\n\t\t\t\t\tdarkBorder: \"#333333\",\n\t\t\t\t\tbackground: \"#0e0e0e\",\n\t\t\t\t\tsecondaryBackground: \"#383838\",\n\t\t\t\t\tdarkBackground: \"#161616\",\n\t\t\t\t\taccent: \"#32ae62\",\n\t\t\t\t}),\n\t\t\t);\n\t\t\tfs.writeFile(\"/system/etc/anura/anura_settings.json\", JSON.stringify(initial));\n\t\t}\n\n\t\treturn new Settings(fs, initial);\n\t}\n\n\tget(prop: string): any {\n\t\treturn this.cache[prop];\n\t}\n\thas(prop: string): boolean {\n\t\treturn prop in this.cache;\n\t}\n\tasync set(prop: string, val: any, subprop?: string) {\n\t\tconsole.debug(\"Setting \" + prop + \" to \" + val);\n\t\tif (subprop) {\n\t\t\tthis.cache[prop][subprop] = val;\n\t\t} else {\n\t\t\tthis.cache[prop] = val;\n\t\t}\n\t\tthis.save();\n\t}\n\tasync save() {\n\t\tconsole.debug(\"Saving settings to fs\", this.cache);\n\t\tawait this.fs.promises.writeFile(\"/system/etc/anura/anura_settings.json\", JSON.stringify(this.cache));\n\t}\n\tasync remove(prop: string, subprop?: string) {\n\t\tconsole.warn(\"anura.settings.remove() is a debug feature, and should not be used outside of development.\");\n\t\tif (subprop) {\n\t\t\tdelete this.cache[prop][subprop];\n\t\t} else {\n\t\t\tdelete this.cache[prop];\n\t\t}\n\t\tthis.save();\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Systray.ts",
    "content": "import { useWindowStore } from \"../../Store\";\n\nexport class SystrayIcon {\n\tonclick = () => {};\n\tonrightclick = () => {};\n\tget icon() {\n\t\treturn \"This API is not supported.\";\n\t}\n\tget tooltip() {\n\t\treturn \"This API is not supported.\";\n\t}\n\tdestroy = () => {\n\t\twindow.tb.window.island.removeControl(\"com.anura.genericsystray\");\n\t};\n}\n\nexport class Systray {\n\ticon: SystrayIcon[] = [];\n\tcreate = (args: any) => {\n\t\tconst win = useWindowStore().windows.find((win: any) => win.pid === window.tb.window.getId());\n\t\tconst title = win ? win.title : \"Anura File Manager\";\n\t\twindow.tb.window.island.addControl({\n\t\t\ttext: `${args.tooltip}`,\n\t\t\t// @ts-expect-error\n\t\t\tclick: () => this.icon.onclick,\n\t\t\tappname: title,\n\t\t\tid: \"com.anura.genericsystray\",\n\t\t});\n\t};\n}\n"
  },
  {
    "path": "src/sys/liquor/api/TFS.ts",
    "content": "// @ts-nocheck\nimport { AFSProvider } from \"./Filesystem\";\nconst AnuraFDSymbol = Symbol.for(\"AnuraFD\");\nimport { FSType } from \"@terbiumos/tfs\";\ntype AnuraFD = {\n\tfd: number;\n\t[AnuraFDSymbol]: string;\n};\n\nexport class TFSProvider extends AFSProvider<any> {\n\tdomain = \"/\";\n\tname = \"TFS Anura Provider\";\n\tversion = window.tfs.version;\n\n\tfs: FSType;\n\n\tconstructor(fs: FSType) {\n\t\tsuper();\n\t\tthis.fs = fs;\n\t}\n\n\trename(oldPath: string, newPath: string, callback?: (err: Error | null) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(oldPath);\n\t\tfs.rename(oldPath, newPath, callback);\n\t}\n\n\tftruncate(fd: AnuraFD, len: number, callback?: (err: Error | null, fd: AnuraFD) => void) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\ttruncate(path: string, len: number, callback?: (err: Error | null) => void) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tstat(path: string, callback?: (err: Error | null, stats: any) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.stat(path, callback);\n\t}\n\n\tfstat(fd: AnuraFD, callback?: ((err: Error | null, stats: any) => void) | undefined): void {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tlstat(path: string, callback?: (err: Error | null, stats: any) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.lstat(path, callback);\n\t}\n\n\texists(path: string, callback?: (exists: boolean) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tthis.fs.exists(path, callback);\n\t}\n\n\tlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(srcPath);\n\t\tif (!fs.link) throw new Error(\"Linking not supported on this filesystem.\");\n\t\tfs.link(srcPath, dstPath, callback);\n\t}\n\n\tsymlink(path: string, callback?: (err: Error | null) => void, ...rest: any[]) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tif (!fs.symlink) throw new Error(\"Symlinking not supported on this filesystem.\");\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tfs.symlink(path, callback, ...rest);\n\t}\n\n\treadlink(path: string, callback?: (err: Error | null, linkContents: string) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tif (!fs.readlink) throw new Error(\"Reading links not supported on this filesystem.\");\n\t\tfs.readlink(path, callback);\n\t}\n\n\tunlink(path: string, callback?: (err: Error | null) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tif (!fs.unlink) throw new Error(\"Unlinking not supported on this filesystem.\");\n\t\tfs.unlink(path, callback);\n\t}\n\n\tmknod(path: string, mode: number, callback?: (err: Error | null) => void) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\trmdir(path: string, callback?: (err: Error | null) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.rmdir(path, callback);\n\t}\n\n\tmkdir(path: string, callback?: (err: Error | null) => void, ...rest: any[]) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.mkdir(path, callback, ...rest);\n\t}\n\n\taccess(path: string, callback?: (err: Error | null) => void, ...rest: any[]) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.access(path, callback, ...rest);\n\t}\n\n\tmkdtemp(...args: any[]) {\n\t\twindow.tfs.shell.tempDir(...args);\n\t}\n\n\treaddir(path: string, callback?: (err: Error | null, files?: string[] | any[]) => void, ...rest: any[]) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.readdir(path, callback, ...rest);\n\t}\n\n\tclose(fd: AnuraFD, callback?: ((err: Error | null) => void) | undefined): void {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode: number, callback?: ((err: Error | null, fd: AnuraFD) => void) | undefined): void;\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", callback?: ((err: Error | null, fd: AnuraFD) => void) | undefined): void;\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: unknown, callback?: unknown): void {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tutimes(path: string, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tfutimes(fd: AnuraFD, ...rest: any[]) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tchown(path: string, uid: number, gid: number, callback?: (err: Error | null) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tif (!fs.chown) throw new Error(\"Chowning not supported on this filesystem.\");\n\t\tfs.chown(path, uid, gid, callback);\n\t}\n\n\tfchown(fd: AnuraFD, ...rest: any[]) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tchmod(path: string, mode: number, callback?: (err: Error | null) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tif (!fs.chmod) throw new Error(\"Chmod not supported on this filesystem.\");\n\t\tfs.chmod(path, mode, callback);\n\t}\n\n\tfchmod(fd: AnuraFD, ...rest: any[]) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tfsync(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.fsync(fd.fd, ...rest);\n\t}\n\n\twrite(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.write(fd.fd, ...rest);\n\t}\n\n\tread(fd: AnuraFD, ...rest: any[]) {\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tthis.fs.read(fd.fd, ...rest);\n\t}\n\n\treadFile(path: string, callback?: (err: Error | null, data: Uint8Array) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.readFile(path, callback);\n\t}\n\n\twriteFile(path: string, ...rest: any[]) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\t// @ts-expect-error - Overloaded methods are scary\n\t\tfs.writeFile(path, ...rest);\n\t}\n\n\tappendFile(path: string, data: Uint8Array, callback?: (err: Error | null) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tfs.appendFile(path, data, callback);\n\t}\n\n\tsetxattr(path: string, ...rest: any[]) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tif (!fs.setxattr) throw new Error(\"Extended attributes not supported on this filesystem.\");\n\t\tfs.setxattr(path, ...rest);\n\t}\n\n\tfsetxattr(fd: AnuraFD, ...rest: any[]) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tgetxattr(path: string, name: string, callback?: (err: Error | null, value: string | object) => void) {\n\t\tconst fs = window.tb.vfs.whatFS(path);\n\t\tif (!fs.getxattr) throw new Error(\"Extended attributes not supported on this filesystem.\");\n\t\tfs.getxattr(path, name, callback);\n\t}\n\n\tfgetxattr(fd: AnuraFD, name: string, callback?: (err: Error | null, value: string | object) => void) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tremovexattr(path: string, name: string, callback?: (err: Error | null) => void) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tfremovexattr(fd: AnuraFD, ...rest: any[]) {\n\t\tthrow new Error(\"Method not implemented.\");\n\t}\n\n\tpromises = {\n\t\tappendFile: (path: string, data: Uint8Array, options: { encoding: string; mode: number; flag: string }) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.appendFile(path, data, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\taccess: (path: string, mode?: number) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.access(path, mode, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tchown: (path: string, uid: number, gid: number) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.chown(path, uid, gid, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tchmod: (path: string, mode: number) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.chmod(path, mode, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tgetxattr: (path: string, name: string) => {\n\t\t\treturn new Promise<string | object>((resolve, reject) => {\n\t\t\t\tthis.getxattr(path, name, (err: Error | null, value: string | object) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(value);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tlink: (srcPath: string, dstPath: string) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.link(srcPath, dstPath, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tlstat: (path: string) => {\n\t\t\treturn new Promise<any>((resolve, reject) => {\n\t\t\t\tthis.lstat(path, (err: Error | null, stats: any) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(stats);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tmkdir: (path: string, mode?: number) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.mkdir(path, mode, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tmkdtemp: (prefix: string, options?: { encoding: string }) => {\n\t\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\t\tthis.mkdtemp(prefix, options, (err: Error | null, folder: string) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(folder);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tmknod: (path: string, mode: number) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.mknod(path, mode, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\topen: async (path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number) => ({\n\t\t\tfd: await new Promise<number>((resolve, reject) => {\n\t\t\t\tthis.open(path, flags, mode as any, (err: Error | null, fd: AnuraFD) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(fd.fd);\n\t\t\t\t});\n\t\t\t}),\n\t\t\t[AnuraFDSymbol]: this.domain,\n\t\t}),\n\t\treaddir: (path: string, options?: { encoding: string; withFileTypes: boolean }) => {\n\t\t\treturn new Promise<string[] | any[]>((resolve, reject) => {\n\t\t\t\tthis.readdir(path, (err: Error | null, files: string[] | any[]) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(files);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\treadFile: (path: string) => {\n\t\t\treturn new Promise<Uint8Array>((resolve, reject) => {\n\t\t\t\tthis.readFile(path, (err: Error | null, data: Uint8Array) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(data);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\treadlink: (path: string) => {\n\t\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\t\tthis.readlink(path, (err: Error | null, linkString: string) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(linkString);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tremovexattr: (path: string, name: string) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.removexattr(path, name, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\trename: (oldPath: string, newPath: string) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.rename(oldPath, newPath, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\trmdir: (path: string) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.rmdir(path, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tsetxattr: (path: string, name: string, value: string | object, flag?: \"CREATE\" | \"REPLACE\") => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.setxattr(path, name, value, flag, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tstat: (path: string) => {\n\t\t\treturn new Promise<any>((resolve, reject) => {\n\t\t\t\tthis.stat(path, (err: Error | null, stats: any) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve(stats);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tsymlink: (srcPath: string, dstPath: string, type?: string) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.symlink(srcPath, dstPath, type, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\ttruncate: (path: string, len: number) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.truncate(path, len, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\n\t\tunlink: (path: string) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.unlink(path, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tutimes: (path: string, atime: number | Date, mtime: number | Date) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.utimes(path, atime, mtime, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\twriteFile: (path: string, data: Uint8Array | string, options: { encoding: string; mode: number; flag: string }) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tthis.writeFile(path, data, options, (err: Error | null) => {\n\t\t\t\t\tif (err) reject(err);\n\t\t\t\t\telse resolve();\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/sys/liquor/api/Theme.ts",
    "content": "interface ThemeProps {\n\tforeground: string;\n\tsecondaryForeground: string;\n\tborder: string;\n\tdarkBorder: string;\n\tbackground: string | any;\n\tsecondaryBackground: string;\n\tdarkBackground: string;\n\taccent: string | any;\n}\n\nexport class Theme implements ThemeProps {\n\t// @ts-expect-error\n\tsettings: ThemeProps = {};\n\n\tconstructor() {\n\t\twindow.tb.fs.promises\n\t\t\t.readFile(\"/system/etc/anura/theme.json\", \"utf8\")\n\t\t\t.then((data: string) => {\n\t\t\t\tthis.settings = JSON.parse(data);\n\t\t\t})\n\t\t\t.catch((err: any) => {\n\t\t\t\tif (localStorage.getItem(\"setup\")) console.warn(\"Error reading theme settings:\", err);\n\t\t\t});\n\t}\n\n\tget foreground() {\n\t\treturn this.settings.foreground;\n\t}\n\n\tset foreground(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.foreground = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tget secondaryForeground() {\n\t\treturn this.settings.secondaryForeground;\n\t}\n\n\tset secondaryForeground(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.secondaryForeground = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tget border() {\n\t\treturn this.settings.border;\n\t}\n\n\tset border(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.border = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tget darkBorder() {\n\t\treturn this.settings.darkBorder;\n\t}\n\n\tset darkBorder(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.darkBorder = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tget background() {\n\t\treturn this.settings.background;\n\t}\n\n\tset background(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.background = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tget secondaryBackground() {\n\t\treturn this.settings.secondaryBackground;\n\t}\n\n\tset secondaryBackground(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.secondaryBackground = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tget darkBackground() {\n\t\treturn this.settings.darkBackground;\n\t}\n\n\tset darkBackground(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.darkBackground = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tget accent() {\n\t\treturn this.settings.accent;\n\t}\n\n\tset accent(value) {\n\t\twindow.tb.fs.readFile(\"/system/etc/anura/theme.json\", (err: Error | null, data: Uint8Array) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst settings: ThemeProps = JSON.parse(data.toString());\n\t\t\tsettings.accent = value;\n\t\t\twindow.tb.fs.writeFile(\"/system/etc/anura/theme.json\", JSON.stringify(settings));\n\t\t});\n\t}\n\n\tcssPropMap: Record<keyof ThemeProps, string[]> = {\n\t\tbackground: [\"--theme-bg\", \"--material-bg\"],\n\t\tborder: [\"--theme-border\", \"--material-border\"],\n\t\tdarkBorder: [\"--theme-dark-border\"],\n\t\tforeground: [\"--theme-fg\"],\n\t\tsecondaryBackground: [\"--theme-secondary-bg\"],\n\t\tsecondaryForeground: [\"--theme-secondary-fg\"],\n\t\tdarkBackground: [\"--theme-dark-bg\"],\n\t\taccent: [\"--theme-accent\", \"--matter-helper-theme\"],\n\t};\n\n\tstate: ThemeProps = this.settings;\n\n\tcss(): string {\n\t\tconst lines = [];\n\t\tlines.push(\":root {\");\n\t\tfor (const key in this.state) {\n\t\t\tfor (const prop of this.cssPropMap[key as keyof ThemeProps]) {\n\t\t\t\tlines.push(`  ${prop}: ${this.state[key as keyof ThemeProps]};`);\n\t\t\t}\n\t\t}\n\t\tlines.push(\"}\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\treset() {\n\t\t(this.foreground = \"#ffffff\"), (this.secondaryForeground = \"#ffffff38\"), (this.border = \"#ffffff28\"), (this.darkBorder = \"#333333\"), (this.background = \"#0e0e0e\"), (this.secondaryBackground = \"#383838\"), (this.darkBackground = \"#161616\"), (this.accent = \"#32ae62\");\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/UI.ts",
    "content": "import { Theme } from \"./Theme\";\n\nexport class AnuraUI {\n\t/**\n\t * This map contains all the built-in components that have been registered.\n\t */\n\tbuiltins = new Map<string, any>();\n\n\t/**\n\t * This map contains all the components that have been registered from external libraries.\n\t */\n\tcomponents = new Map<string, { lib: string; name: string }>();\n\n\ttheme = new Theme();\n\n\t/**\n\t * This function allows you to register a component to the built-in components registry.\n\t * @param component - The name of the component to register.\n\t * @param element - A function component that returns an HTMLElement.\n\t */\n\tasync registerComponent(component: string, element: HTMLDivElement): Promise<void> {\n\t\tthis.builtins.set(component, element);\n\t}\n\t/**\n\t * This function allows you to register a component from an external library.\n\t * @param lib - The name of the library to import the component from.\n\t * @param component - The name of the component to register.\n\t * @param version - (Optional) The version of the library to import the component from.\n\t */\n\tasync registerExternalComponent(lib: string, component: string, version?: string): Promise<any> {\n\t\tif (version) {\n\t\t\tlib += \"@\" + version;\n\t\t}\n\n\t\tthis.components.set(component, {\n\t\t\tlib,\n\t\t\tname: component,\n\t\t});\n\n\t\twindow.anura.settings.set(\"anura.ui.components\", Array.from(this.components.entries()));\n\t}\n\n\t/**\n\t * This function allows you to import a component, whether it is a built-in component or a component from a library.\n\t * @param name - The name of the component to import.\n\t * @returns A promise that resolves to a function component that returns an HTMLElement.\n\t */\n\tasync get(name: string): Promise<any> {\n\t\tconst comp = this.components.get(name);\n\n\t\tif (!comp) {\n\t\t\tif (this.builtins.has(name)) {\n\t\t\t\treturn this.builtins.get(name)!;\n\t\t\t}\n\t\t\tthrow new Error(\"Component not registered\");\n\t\t}\n\n\t\tconst [lib, scope_name] = [comp.lib, comp.name];\n\n\t\tconst library = await window.anura.import(lib);\n\n\t\treturn library[scope_name];\n\t}\n\n\t/**\n\t * This function allows you to check if a component is registered.\n\t * @param component - The name of the component to check.\n\t * @returns Whether the component is registered or not.\n\t */\n\texists(component: string): boolean {\n\t\treturn this.components.has(component) || this.builtins.has(component);\n\t}\n\n\tasync use(components: string[] | string | \"*\" = []): Promise<{ [key: string]: any }> {\n\t\tconst result: {\n\t\t\t[key: string]: any;\n\t\t} = {};\n\n\t\tif (components === \"*\") {\n\t\t\tcomponents = Array.from(this.components.keys()).concat(Array.from(this.builtins.keys()));\n\t\t}\n\n\t\tif (typeof components === \"string\") {\n\t\t\tcomponents = [components];\n\t\t}\n\n\t\tfor (const component of components) {\n\t\t\tresult[component] = await this.get(component);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Install internal components\n\t */\n\tinit() {\n\t\tconst components = window.anura.settings.get(\"anura.ui.components\");\n\n\t\tif (components) {\n\t\t\ttry {\n\t\t\t\tthis.components = new Map(components);\n\t\t\t} catch (e) {\n\t\t\t\tthis.components = new Map();\n\t\t\t}\n\t\t}\n\n\t\t// API stub, Rest will not be implemented\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/URIHandler.ts",
    "content": "interface LibURIHandler {\n\ttag: \"lib\";\n\tpkg: string;\n\tversion?: string;\n\timport: string;\n}\n\ntype SplitArgMethod = {\n\ttag: \"split\";\n\tseparator: RegExp | string;\n};\n\ntype SingleArgMethod = {\n\ttag: \"single\";\n};\n\ninterface AppURIHandler {\n\ttag: \"app\";\n\tpkg: string;\n\tmethod: SplitArgMethod | SingleArgMethod;\n}\n\ninterface URIHandlerOptions {\n\thandler: LibURIHandler | AppURIHandler;\n\tprefix?: string;\n}\n\nexport class URIHandlerAPI {\n\t// Handles a URI like \"protocol:something/etc\" by opening the appropriate app or library.\n\tasync handle(uri: string): Promise<void> {\n\t\t// const url = new URL(uri);\n\t\t// const protocol = url.protocol.slice(0, -1);\n\t\tconst [protocol, ...path] = uri.split(\":\");\n\t\tconst pathname = path.join(\":\");\n\t\tconst handlers = window.anura.settings.get(\"URIHandlers\") || {};\n\t\tconst handler = handlers[protocol as string];\n\t\tif (!handler) {\n\t\t\tthrow new Error(`No handler for URI protocol ${protocol}`);\n\t\t}\n\t\tif (handler.handler.tag === \"lib\") {\n\t\t\tlet lib;\n\t\t\tif (handler.handler.version) {\n\t\t\t\tlib = await window.anura.import(handler.handler.pkg + \"@\" + handler.handler.version);\n\t\t\t} else {\n\t\t\t\tlib = await window.anura.import(handler.handler.pkg);\n\t\t\t}\n\t\t\tawait lib[handler.handler.import]((handler.prefix || \"\") + pathname);\n\t\t} else if (handler.handler.tag === \"app\") {\n\t\t\tconst app = handler.handler;\n\t\t\tif (app.method && app.method.tag !== undefined && app.method.tag === \"split\") {\n\t\t\t\tconst args = pathname.split(app.method.separator);\n\t\t\t\tawait window.anura.apps[app.pkg].open(handler.prefix ? [handler.prefix, ...args] : args);\n\t\t\t} else {\n\t\t\t\twindow.tb.window.create({\n\t\t\t\t\ttitle: \"Terbium Webview\",\n\t\t\t\t\tsrc: handler.prefix,\n\t\t\t\t\tsize: {\n\t\t\t\t\t\twidth: 460,\n\t\t\t\t\t\theight: 460,\n\t\t\t\t\t\tminWidth: 160,\n\t\t\t\t\t\tminHeight: 160,\n\t\t\t\t\t},\n\t\t\t\t\ticon: \"/apps/browser.tapp/icon.svg\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sets a handler for a URI protocol.\n\tset(protocol: string, options: URIHandlerOptions): void {\n\t\tconst handlers = window.anura.settings.get(\"URIHandlers\") || {};\n\t\thandlers[protocol] = options;\n\t\twindow.anura.settings.set(\"URIHandlers\", handlers);\n\t}\n\n\t// Removes a handler for a URI protocol.\n\tremove(protocol: string): void {\n\t\tconst handlers = window.anura.settings.get(\"URIHandlers\") || {};\n\t\tdelete handlers[protocol];\n\t\twindow.anura.settings.set(\"URIHandlers\", handlers);\n\t}\n\n\t// Determines if a handler is set for a URI protocol.\n\thas(protocol: string): boolean {\n\t\tconst handlers = window.anura.settings.get(\"URIHandlers\") || {};\n\t\treturn !!handlers[protocol];\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/api/WmApi.tsx",
    "content": "import { AnuraWMWeakRef } from \"@/sys/types\";\nimport { AliceWM, WindowInformation } from \"../AliceWM\";\nimport { App } from \"../coreapps/App\";\n\nexport class WMAPI {\n\twindows: WeakRef<any>[] = [];\n\tconstructor() {\n\t\tsetInterval(() => {\n\t\t\tfor (const proc of Object.values(window.tb.process.list())) {\n\t\t\t\tthis.convertProc(proc.pid);\n\t\t\t}\n\t\t}, 1);\n\t}\n\tasync create(ctx: App | any, _info: WindowInformation, _onfocus: (() => void) | null = null, _onresize: ((w: number, h: number) => void) | null = null) {\n\t\tconst win = await AliceWM.create(ctx);\n\t\tthis.windows.push(new WeakRef(win));\n\t\treturn win;\n\t}\n\tasync createGeneric(_ctx: App, info: object) {\n\t\tif (!info) {\n\t\t\tinfo = {\n\t\t\t\ttitle: \"Generic Window\",\n\t\t\t\ticon: \"/assets/img/logo.png\",\n\t\t\t\tminheight: 40,\n\t\t\t\tminwidth: 40,\n\t\t\t\twidth: \"1000px\",\n\t\t\t\theight: \"500px\",\n\t\t\t\tallowMultipleInstance: false,\n\t\t\t};\n\t\t}\n\t\t// @ts-expect-error\n\t\tconst win = await AliceWM.create(info);\n\t\t//ctx.windows.push(win); This was causing problems\n\t\tthis.windows.push(new WeakRef(win));\n\t\treturn win;\n\t}\n\n\t/** NON SPECED FOR TERBIUM COMPATABILITY ONLY */\n\tconvertProc(pid: number) {\n\t\tconst winInf = window.tb.process.list()[pid];\n\t\tif (!winInf) return;\n\t\tfor (const ref of this.windows) {\n\t\t\tconst r = ref?.deref?.() ?? null;\n\t\t\tif (!r) continue;\n\t\t\tif (r.pid === pid) return;\n\t\t\tif (typeof r.title === \"string\" && r.title === winInf.name) return;\n\t\t}\n\t\tconst tbanuraproperties: WindowInformation = {\n\t\t\ttitle: winInf.name,\n\t\t\ticon: winInf.icon,\n\t\t\tminheight: 40,\n\t\t\tminwidth: 40,\n\t\t\twidth: winInf.size?.width ? winInf.size.width : \"0px\",\n\t\t\theight: winInf.size?.height ? winInf.size.height : \"0px\",\n\t\t\tallowMultipleInstance: false,\n\t\t};\n\t\t// Sorry, DOM has to be used here for cross compatability\n\t\tconst elem = document.querySelector(`div[pid=\"${pid}\"]`);\n\t\tconst winControls = elem?.querySelectorAll(\".controls.flex.gap-1\") ?? [];\n\t\tconst obj: AnuraWMWeakRef = {\n\t\t\telement: elem,\n\t\t\tcontent: elem?.querySelector(\".w-full.h-full\"),\n\t\t\t// @ts-expect-error keep same behavior — ExternalApp may be nonstandard\n\t\t\tapp: new ExternalApp(winInf),\n\t\t\tdragForceX: 0,\n\t\t\tdragForceY: 0,\n\t\t\tdragging: false,\n\t\t\theight: winInf.size?.height ? winInf.size.height : 0,\n\t\t\twidth: winInf.size?.width ? winInf.size.width : 0,\n\t\t\tpid: pid,\n\t\t\tstate: null,\n\t\t\tmaximized: false,\n\t\t\tminimizing: false,\n\t\t\tmouseLeft: null,\n\t\t\tmouseTop: null,\n\t\t\tonclose: () => null,\n\t\t\tonfocus: () => null,\n\t\t\tonresize: (_w: number, _h: number) => null,\n\t\t\tonsnap: (_side: string) => null,\n\t\t\tonunmaximize: () => null,\n\t\t\trestoreSvg: winControls[0]?.childNodes[1] || null,\n\t\t\tkill() {\n\t\t\t\tif (obj.pid != null) window.tb.process.kill(obj.pid);\n\t\t\t},\n\t\t\tget alive() {\n\t\t\t\treturn window.tb.process.list()[pid] != null;\n\t\t\t},\n\t\t\tmaximizeImg: winControls[0]?.childNodes[1] || null,\n\t\t\tmaximizeSvg: winControls[0]?.childNodes[1] || null,\n\t\t\twininfo: tbanuraproperties,\n\t\t\ttitle: winInf.name,\n\t\t};\n\t\tthis.windows.push(new WeakRef(obj));\n\t}\n\n\tgetWeakRef(pid: number) {\n\t\tfor (const ref of this.windows) {\n\t\t\tconst r = ref.deref();\n\t\t\tif (!r) continue;\n\t\t\tif (Number(r.pid) === pid) return r;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/bcc.ts",
    "content": "export class AnuraBareClient {\n\tready = true;\n\n\tconstructor() {}\n\tasync init() {\n\t\tthis.ready = true;\n\t}\n\tasync meta() {}\n\n\tasync request(remote: URL, method: string, body: BodyInit | null, headers: any, signal: AbortSignal | undefined): Promise<any> {\n\t\tconst payload = await window.anura.net.fetch(remote.href, {\n\t\t\tmethod,\n\t\t\theaders: headers,\n\t\t\tbody,\n\t\t\tredirect: \"manual\",\n\t\t\tduplex: \"half\",\n\t\t});\n\n\t\tconst respheaders = {};\n\n\t\t//@ts-ignore\n\t\tif (payload.raw_headers)\n\t\t\tfor (const [key, value] of payload.raw_headers) {\n\t\t\t\t//@ts-ignore\n\t\t\t\tif (!respheaders[key]) {\n\t\t\t\t\t//@ts-ignore\n\t\t\t\t\trespheaders[key] = [value];\n\t\t\t\t} else {\n\t\t\t\t\t//@ts-ignore\n\t\t\t\t\trespheaders[key].push(value);\n\t\t\t\t}\n\t\t\t}\n\n\t\treturn {\n\t\t\tbody: payload.body!,\n\t\t\theaders: respheaders,\n\t\t\tstatus: payload.status,\n\t\t\tstatusText: payload.statusText,\n\t\t};\n\t}\n\n\tconnect(\n\t\turl: URL,\n\t\torigin: string,\n\t\tprotocols: string[],\n\t\trequestHeaders: any,\n\t\tonopen: (protocol: string) => void,\n\t\tonmessage: (data: Blob | ArrayBuffer | string) => void,\n\t\tonclose: (code: number, reason: string) => void,\n\t\tonerror: (error: string) => void,\n\t): [(data: Blob | ArrayBuffer | string) => void, (code: number, reason: string) => void] {\n\t\t//@ts-ignore\n\t\tconst socket = new window.anura.net.WebSocket(url.toString(), protocols, {\n\t\t\theaders: requestHeaders,\n\t\t});\n\t\t//bare client always expects an arraybuffer for some reason\n\t\tsocket.binaryType = \"arraybuffer\";\n\n\t\tsocket.onopen = (event: Event) => {\n\t\t\tonopen(\"\");\n\t\t};\n\t\tsocket.onclose = (event: CloseEvent) => {\n\t\t\tonclose(event.code, event.reason);\n\t\t};\n\t\tsocket.onerror = (event: Event) => {\n\t\t\tonerror(\"\");\n\t\t};\n\t\tsocket.onmessage = (event: MessageEvent) => {\n\t\t\tonmessage(event.data);\n\t\t};\n\n\t\treturn [\n\t\t\tdata => {\n\t\t\t\tsocket.send(data);\n\t\t\t},\n\t\t\t(code, reason) => {\n\t\t\t\tsocket.close(code, reason);\n\t\t\t},\n\t\t];\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/coreapps/App.tsx",
    "content": "// @ts-nocheck\nexport class App {\n\ticon: string;\n\tpackage: string;\n\tname: string;\n\thidden = false;\n\twindows: any[] = [];\n\topen(args: string[] = []): void {}\n}\n"
  },
  {
    "path": "src/sys/liquor/coreapps/ExternalApp.tsx",
    "content": "import { AliceWM, WindowInformation } from \"../AliceWM\";\nimport { AppManifest } from \"../Anura\";\nimport { App } from \"./App\";\nimport { LocalFS } from \"../api/LocalFS\";\nexport class ExternalApp extends App {\n\tmanifest: AppManifest;\n\tsource: string;\n\ticon = \"/assets/icons/generic.png\";\n\n\tconstructor(manifest: AppManifest, source: string) {\n\t\tsuper();\n\t\tthis.manifest = manifest;\n\t\tthis.name = manifest.name;\n\t\tif (manifest.icon) {\n\t\t\tthis.icon = source + \"/\" + manifest.icon;\n\t\t}\n\t\tthis.source = source;\n\t\tthis.package = manifest.package;\n\t\tthis.hidden = manifest.hidden || false;\n\t}\n\n\tstatic serializeArgs(args: string[]): string {\n\t\tconst encoder = new TextEncoder();\n\t\tconst encodedValues = args.map(value => {\n\t\t\tconst bytes = encoder.encode(value);\n\t\t\tconst binString = String.fromCodePoint(...bytes);\n\t\t\treturn btoa(binString);\n\t\t});\n\t\treturn encodeURIComponent(encodedValues.join(\",\"));\n\t}\n\n\tstatic deserializeArgs(args: string): string[] {\n\t\tconst decoder = new TextDecoder(\"utf-8\");\n\t\treturn decodeURIComponent(args)\n\t\t\t.split(\",\")\n\t\t\t.map(value => {\n\t\t\t\tconst binString = atob(value);\n\t\t\t\treturn decoder.decode(Uint8Array.from(binString, c => c.charCodeAt(0)));\n\t\t\t});\n\t}\n\t//@ts-expect-error manual apps exist\n\tasync open(args: string[] = []): Promise<WMWindow | undefined> {\n\t\t//  TODO: have a \"allowmultiinstance\" option in manifest? it might confuse users, some windows open a second, some focus\n\t\t// if (this.windowinstance) return;\n\t\tif (this.manifest.type === \"auto\") {\n\t\t\tconst win = await window.anura.wm.create(this, this.manifest.wininfo as unknown as WindowInformation);\n\n\t\t\tconst iframe = document.createElement(\"iframe\");\n\t\t\t// CSS injection here but it's no big deal\n\t\t\tconst bg = this.manifest.background || \"#202124\";\n\t\t\tiframe.setAttribute(\"style\", \"top:0; left:0; bottom:0; right:0; width:100%; height:100%; \" + `border: none; margin: 0; padding: 0; background-color: ${bg};`);\n\t\t\tiframe.setAttribute(\"src\", `${this.source}/${this.manifest.index}${this.manifest.index?.includes(\"?\") ? \"&\" : \"?\"}args=${ExternalApp.serializeArgs(args)}`);\n\t\t\twin.content.appendChild(iframe);\n\n\t\t\tif (this.manifest.useIdbWrapper) {\n\t\t\t\tconst idbWrapper = new Proxy(iframe.contentWindow!.indexedDB, {\n\t\t\t\t\tget: (target, prop, receiver) => {\n\t\t\t\t\t\tswitch (prop) {\n\t\t\t\t\t\t\tcase \"databases\":\n\t\t\t\t\t\t\t\treturn async () => {\n\t\t\t\t\t\t\t\t\tconst dbs = await target.databases();\n\t\t\t\t\t\t\t\t\treturn dbs\n\t\t\t\t\t\t\t\t\t\t.filter((db: any) => db.name.startsWith(this.package + \"-\"))\n\t\t\t\t\t\t\t\t\t\t.map((db: any) => {\n\t\t\t\t\t\t\t\t\t\t\tdb.name = db.name.slice(this.package.length + 1);\n\t\t\t\t\t\t\t\t\t\t\treturn db;\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tcase \"open\":\n\t\t\t\t\t\t\t\treturn (name: string, version: number) => {\n\t\t\t\t\t\t\t\t\treturn target.open(name.startsWith(this.package + \"-\") ? name : `${this.package}-${name}`, version);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tcase \"deleteDatabase\":\n\t\t\t\t\t\t\t\treturn (name: string) => {\n\t\t\t\t\t\t\t\t\treturn target.deleteDatabase(name.startsWith(this.package + \"-\") ? name : `${this.package}-${name}`);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\treturn Reflect.get(target, prop, receiver);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tObject.defineProperty(iframe.contentWindow!, \"indexedDB\", {\n\t\t\t\t\tvalue: idbWrapper,\n\t\t\t\t\twritable: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tObject.assign(iframe.contentWindow as any, {\n\t\t\t\tanura: window.anura,\n\t\t\t\tAliceWM,\n\t\t\t\tExternalApp,\n\t\t\t\tLocalFS,\n\t\t\t\tinstance: this,\n\t\t\t\tinstanceWindow: win,\n\t\t\t\tprint: (message: string) => {\n\t\t\t\t\tiframe.contentWindow!.window.postMessage({\n\t\t\t\t\t\ttype: \"stdout\",\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tprintln: (message: string) => {\n\t\t\t\t\tiframe.contentWindow!.postMessage({\n\t\t\t\t\t\ttype: \"stdout\",\n\t\t\t\t\t\tmessage: message + \"\\n\",\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tprinterr: (message: string) => {\n\t\t\t\t\tiframe.contentWindow!.postMessage({\n\t\t\t\t\t\ttype: \"stderr\",\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tprintlnerr: (message: string) => {\n\t\t\t\t\tiframe.contentWindow!.postMessage({\n\t\t\t\t\t\ttype: \"stderr\",\n\t\t\t\t\t\tmessage: message + \"\\n\",\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tread: () => {\n\t\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t\tiframe.contentWindow!.addEventListener(\n\t\t\t\t\t\t\t\"message\",\n\t\t\t\t\t\t\te => {\n\t\t\t\t\t\t\t\tif (e.data.type === \"stdin\") {\n\t\t\t\t\t\t\t\t\tresolve(e.data.message);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ once: true },\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\treadln: () => {\n\t\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t\t// Read until a newline\n\t\t\t\t\t\tlet buffer = \"\";\n\t\t\t\t\t\tconst listener = (e: MessageEvent<any>) => {\n\t\t\t\t\t\t\tif (e.data.type === \"stdin\") {\n\t\t\t\t\t\t\t\tbuffer += e.data.message;\n\t\t\t\t\t\t\t\tif (buffer.includes(\"\\n\")) {\n\t\t\t\t\t\t\t\t\tresolve(buffer);\n\t\t\t\t\t\t\t\t\tiframe.contentWindow!.removeEventListener(\"message\", listener);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tiframe.contentWindow!.addEventListener(\"message\", listener);\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tenv: {\n\t\t\t\t\tprocess: win,\n\t\t\t\t},\n\t\t\t\topen: async (url: string | URL) => {\n\t\t\t\t\tconst browser = await window.anura.import(\"anura.libbrowser\");\n\t\t\t\t\tbrowser.openTab(url);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\twin.stdin = new WritableStream({\n\t\t\t\twrite: message => {\n\t\t\t\t\tiframe.contentWindow!.postMessage({\n\t\t\t\t\t\ttype: \"stdin\",\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\n\t\t\twin.stderr = new ReadableStream({\n\t\t\t\tstart: controller => {\n\t\t\t\t\tiframe.contentWindow!.addEventListener(\"error\", e => {\n\t\t\t\t\t\tcontroller.enqueue(e.error);\n\t\t\t\t\t});\n\n\t\t\t\t\tiframe.contentWindow!.addEventListener(\"message\", e => {\n\t\t\t\t\t\tif (e.data.type === \"stderr\") {\n\t\t\t\t\t\t\tcontroller.enqueue(e.data.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\n\t\t\twin.stdout = new ReadableStream({\n\t\t\t\tstart: controller => {\n\t\t\t\t\tiframe.contentWindow!.addEventListener(\"message\", e => {\n\t\t\t\t\t\tif (e.data.type === \"stdout\") {\n\t\t\t\t\t\t\tcontroller.enqueue(e.data.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst matter = document.createElement(\"link\");\n\t\t\tmatter.setAttribute(\"rel\", \"stylesheet\");\n\t\t\tmatter.setAttribute(\"href\", \"/assets/matter.css\");\n\n\t\t\tiframe.contentWindow!.addEventListener(\"load\", () => {\n\t\t\t\tiframe.contentDocument!.head.appendChild(matter);\n\t\t\t});\n\n\t\t\treturn win;\n\t\t} else if (this.manifest.type === \"manual\") {\n\t\t\t// This type of application is reserved only for scripts meant for hacking anura internals\n\t\t\tconst req = await fetch(`${this.source}/${this.manifest.handler}`);\n\t\t\tconst data = await req.text();\n\t\t\ttop!.window.eval(data);\n\t\t\t// @ts-expect-error\n\t\t\tloadingScript(this.source, this);\n\n\t\t\treturn;\n\t\t} else if (this.manifest.type === \"webview\") {\n\t\t\t// FOR INTERNAL USE ONLY\n\t\t\tconst win = await window.anura.wm.create(this, this.manifest.wininfo as unknown as WindowInformation);\n\n\t\t\tconst iframe = document.createElement(\"iframe\");\n\t\t\t// CSS injection here but it's no big deal\n\t\t\tconst bg = this.manifest.background || \"var(--theme-bg)\";\n\t\t\tiframe.setAttribute(\"style\", \"top:0; left:0; bottom:0; right:0; width:100%; height:100%; \" + `border: none; margin: 0; padding: 0; background-color: ${bg};`);\n\t\t\tlet encoded = \"\";\n\t\t\tfor (let i = 0; i < this.manifest.src!.length; i++) {\n\t\t\t\tif (i % 2 === 0) {\n\t\t\t\t\tencoded += this.manifest.src![i];\n\t\t\t\t} else {\n\t\t\t\t\tencoded += String.fromCharCode(this.manifest.src!.charCodeAt(i) ^ 2);\n\t\t\t\t}\n\t\t\t}\n\t\t\tiframe.setAttribute(\"src\", `${\"/service/\" + encodeURIComponent(encoded)}`);\n\t\t\twin.content.appendChild(iframe);\n\t\t\treturn win;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/libs/ExternalLib.tsx",
    "content": "import { Lib } from \"./lib\";\n// API Stub: TODO Later\ninterface LibManifest {\n\tname: string;\n\ticon: string;\n\tpackage: string;\n\tversions: {\n\t\t[key: string]: string;\n\t};\n\tinstallHook?: string;\n\tcache?: boolean;\n\tcurrentVersion: string;\n}\n\nexport class ExternalLib extends Lib {\n\tsource: string;\n\tmanifest: LibManifest;\n\t// Import caching is optional\n\tcache: {\n\t\t[key: string]: any;\n\t} = {};\n\t// The installed libs at the time of the last cache\n\t// If more libs are installed, the cache is invalidated\n\t// This is to prevent a race condition where a lib is installed\n\t// before the dependency is installed\n\tinstalledLibs: string[] = [];\n\n\tconstructor(manifest: LibManifest, source: string) {\n\t\tsuper();\n\t\tthis.manifest = manifest;\n\t\tthis.name = manifest.name;\n\t\tthis.icon = source + \"/\" + manifest.icon;\n\t\tthis.source = source;\n\t\tthis.package = manifest.package;\n\t\tthis.latestVersion = manifest.currentVersion;\n\t\tObject.keys(manifest.versions).forEach(version => {\n\t\t\tthis.versions[version] = source + \"/\" + manifest.versions[version];\n\t\t\tconsole.log(this.versions[version]);\n\t\t});\n\t\tif (manifest.installHook) {\n\t\t\timport(/* @vite-ignore */ source + \"/\" + manifest.installHook).then(module => {\n\t\t\t\ttry {\n\t\t\t\t\tmodule.default(window.anura, this);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.warn(err);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\tasync getImport(version?: string): Promise<any> {\n\t\tif (!version) {\n\t\t\tversion = this.latestVersion;\n\t\t}\n\t\tif (this.manifest.cache && this.cache[version] && this.installedLibs == Object.keys(window.anura.libs)) {\n\t\t\treturn this.cache[version];\n\t\t}\n\t\tif (this.versions[version]) {\n\t\t\t// @vite-ignore\n\t\t\tconst mod = await import(/* @vite-ignore */ this.versions[version]);\n\t\t\tif (this.manifest.cache) {\n\t\t\t\tthis.cache[version] = mod;\n\t\t\t\tthis.installedLibs = Object.keys(window.anura.libs);\n\t\t\t}\n\t\t\treturn mod;\n\t\t} else {\n\t\t\tthrow new Error(`Library ${this.name} does not supply version ${version}`);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/sys/liquor/libs/lib.tsx",
    "content": "// @ts-nocheck\nexport class Lib {\n\ticon: string;\n\tpackage: string;\n\tname: string;\n\tversions: { [key: string]: any } = {};\n\tlatestVersion: string;\n\tasync getImport(version: string): Promise<any> {}\n}\n"
  },
  {
    "path": "src/sys/liquor/types/Filer.d.ts",
    "content": "declare let Filer: FilerType;\ndeclare let $el: any;\n\n// Note: this is different from the Anura Filesystem type because file descriptors are internally stored as numbers rather than the AnuraFD type.\n// This should still be fully compatible as file descriptors are obtained from other methods and are not created directly. This will only be a\n// problem if someone for some reason tries to create a file descriptor manually or did some external logic based on the file descriptor being a\n// number.\ntype FilerFS = {\n\tconstants: {\n\t\tF_OK: number;\n\t\tR_OK: number;\n\t\tW_OK: number;\n\t\tX_OK: number;\n\t};\n\twatch(filename: string, listener: (event: string, filename: string) => void, options?: { recursive: boolean }): void;\n\tShell: {\n\t\tnew (): {\n\t\t\tcd: (t: string, r?: any) => void;\n\t\t\tpwd: () => string;\n\t\t\tenv: () => Record<string, string>;\n\t\t\tfs: () => any;\n\t\t\trm: (directory: string, options?: { recursive: boolean; force: boolean }) => void;\n\t\t\tpromises: {\n\t\t\t\tcat: () => Promise<string>;\n\t\t\t\tcd: (t: string, r?: any) => Promise<void>;\n\t\t\t\texec: (command: string) => Promise<any>;\n\t\t\t\tfind: (\n\t\t\t\t\tpath: string,\n\t\t\t\t\toptions?: {\n\t\t\t\t\t\tname?: string;\n\t\t\t\t\t\tregex?: RegExp | string;\n\t\t\t\t\t\texec?: boolean | Function<any>;\n\t\t\t\t\t},\n\t\t\t\t) => Promise<any>;\n\t\t\t\tls: (dir: string) => Promise<string[]>;\n\t\t\t\tmkdirp: (dir: string) => Promise<void>;\n\t\t\t\trm: (path: string) => Promise<void>;\n\t\t\t\ttempDir: () => Promise<string>;\n\t\t\t\ttouch: (filePath: string) => Promise<void>;\n\t\t\t};\n\t\t};\n\t};\n\n\trename(oldPath: string, newPath: string, callback?: (err: Error | null) => void): void;\n\n\tftruncate(fd: number, len: number, callback?: (err: Error | null, fd: number) => void): void;\n\n\ttruncate(path: string, len: number, callback?: (err: Error | null) => void): void;\n\n\tstat(path: string, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\tfstat(fd: number, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\tlstat(path: string, callback?: (err: Error | null, stats: TStats) => void): void;\n\n\t/** @deprecated fs.exists() is an anachronism and exists only for historical reasons. */\n\texists(path: string, callback?: (exists: boolean) => void): void;\n\n\tlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void): void;\n\n\tsymlink(srcPath: string, dstPath: string, type: string, callback?: (err: Error | null) => void): void;\n\n\tsymlink(srcPath: string, dstPath: string, callback?: (err: Error | null) => void): void;\n\n\treadlink(path: string, callback?: (err: Error | null, linkContents: string) => void): void;\n\n\tunlink(path: string, callback?: (err: Error | null) => void): void;\n\n\tmknod(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\trmdir(path: string, callback?: (err: Error | null) => void): void;\n\n\tmkdir(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\tmkdir(path: string, callback?: (err: Error | null) => void): void;\n\n\taccess(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\taccess(path: string, callback?: (err: Error | null) => void): void;\n\n\tmkdtemp(prefix: string, options: { encoding: string } | string, callback?: (err: Error | null, path: string) => void): void;\n\n\tmkdtemp(prefix: string, callback?: (err: Error | null, path: string) => void): void;\n\n\treaddir(path: string, options: { encoding: string; withFileTypes: boolean } | string, callback?: (err: Error | null, files: string[]) => void): void;\n\n\treaddir(path: string, callback?: (err: Error | null, files: string[]) => void): void;\n\n\tclose(fd: number, callback?: (err: Error | null) => void): void;\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode: number, callback?: (err: Error | null, fd: number) => void): void;\n\n\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", callback?: (err: Error | null, fd: number) => void): void;\n\n\tutimes(path: string, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void): void;\n\n\tfutimes(fd: number, atime: number | Date, mtime: number | Date, callback?: (err: Error | null) => void): void;\n\n\tchown(path: string, uid: number, gid: number, callback?: (err: Error | null) => void): void;\n\n\tfchown(fd: number, uid: number, gid: number, callback?: (err: Error | null) => void): void;\n\n\tchmod(path: string, mode: number, callback?: (err: Error | null) => void): void;\n\n\tfchmod(fd: number, mode: number, callback?: (err: Error | null) => void): void;\n\n\tfsync(fd: number, callback?: (err: Error | null) => void): void;\n\n\twrite(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, nbytes: number) => void): void;\n\n\tread(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null, callback?: (err: Error | null, nbytes: number, buffer: Uint8Array) => void): void;\n\n\treadFile(path: string, callback?: (err: Error | null, data: Uint8Array) => void): void;\n\n\twriteFile(path: string, data: Uint8Array | string, options: { encoding: string; flag: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\" } | string, callback?: (err: Error | null) => void): void;\n\n\twriteFile(path: string, data: Uint8Array | string, callback?: (err: Error | null) => void): void;\n\n\tappendFile(path: string, data: Uint8Array, callback?: (err: Error | null) => void): void;\n\n\tsetxattr(path: string, name: string, value: string | object, flag: \"CREATE\" | \"REPLACE\", callback?: (err: Error | null) => void): void;\n\n\tsetxattr(path: string, name: string, value: string | object, callback?: (err: Error | null) => void): void;\n\n\tfsetxattr(fd: number, name: string, value: string | object, flag: \"CREATE\" | \"REPLACE\", callback?: (err: Error | null) => void): void;\n\n\tfsetxattr(fd: number, name: string, value: string | object, callback?: (err: Error | null) => void): void;\n\n\tgetxattr(path: string, name: string, callback?: (err: Error | null, value: string | object) => void): void;\n\n\tfgetxattr(fd: number, name: string, callback?: (err: Error | null, value: string | object) => void): void;\n\n\tremovexattr(path: string, name: string, callback?: (err: Error | null) => void): void;\n\n\tfremovexattr(fd: number, name: string, callback?: (err: Error | null) => void): void;\n\n\t/*\n\t * Asynchronous FS operations\n\t */\n\n\tpromises: {\n\t\tappendFile(path: string, data: Uint8Array, options: { encoding: string; mode: number; flag: string }): Promise<void>;\n\t\taccess(path: string, mode?: number): Promise<void>;\n\t\tchown(path: string, uid: number, gid: number): Promise<void>;\n\t\tchmod(path: string, mode: number): Promise<void>;\n\t\tgetxattr(path: string, name: string): Promise<string | object>;\n\t\tlink(srcPath: string, dstPath: string): Promise<void>;\n\t\tlstat(path: string): Promise<TStats>;\n\t\tmkdir(path: string, mode?: number): Promise<void>;\n\t\tmkdtemp(prefix: string, options?: { encoding: string }): Promise<string>;\n\t\tmknod(path: string, mode: number): Promise<void>;\n\t\topen(path: string, flags: \"r\" | \"r+\" | \"w\" | \"w+\" | \"a\" | \"a+\", mode?: number): Promise<number>;\n\t\treaddir(path: string, options?: string | { encoding: string; withFileTypes: boolean }): Promise<string[]>;\n\t\treadFile(path: string, encoding?: string): Promise<any>;\n\t\treadlink(path: string): Promise<string>;\n\t\tremovexattr(path: string, name: string): Promise<void>;\n\t\trename(oldPath: string, newPath: string): Promise<void>;\n\t\trmdir(path: string): Promise<void>;\n\t\tsetxattr(path: string, name: string, value: string | object, flag?: \"CREATE\" | \"REPLACE\"): Promise<void>;\n\t\tstat(path: string, callback?: void | any): Promise<TStats>;\n\t\tsymlink(srcPath: string, dstPath: string, type?: string): Promise<void>;\n\t\ttruncate(path: string, len: number): Promise<void>;\n\t\tunlink(path: string): Promise<void>;\n\t\tutimes(path: string, atime: number | Date, mtime: number | Date): Promise<void>;\n\t\twriteFile(path: string, data: any | string, encoding?: string, mode?: number, flag?: string): Promise<void>;\n\t};\n};\ntype FilerType = {\n\tfs: FilerFS;\n\tpromises: FilerFS.promises;\n\tBuffer: any;\n\tPath: any;\n\tFileSystem: FilerFS.constructor;\n};\n"
  },
  {
    "path": "src/sys/liquor/types/V86Starter.d.ts",
    "content": "declare let V86Starter: V86StarterType;\n// [todo]\ntype V86StarterType = any;\n"
  },
  {
    "path": "src/sys/types.ts",
    "content": "/**\n * @file src/sys/types.ts\n * @description This file contains all the types and interfaces used in the Terbium system.\n */\n\nimport { TFSType, FSType, ShellType } from \"@terbiumos/tfs\";\nimport { System } from \"./apis/System\";\nimport { ServerInfo, vFS } from \"./vFS\";\nimport { ExternalApp } from \"./liquor/coreapps/ExternalApp\";\nimport { WindowInformation } from \"./liquor/AliceWM\";\nimport { createAuthClient } from \"better-auth/client\";\nimport type { HTTPSession, libcurl } from \"libcurl.js\";\nimport * as fflate from \"fflate\";\n\ndeclare global {\n\tnamespace React.JSX {\n\t\tinterface IntrinsicElements {\n\t\t\t\"window-area\": React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;\n\t\t\twindow: React.HTMLAttributes<HTMLDivElement>;\n\t\t\tregion: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;\n\t\t\t\"window-body\": React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;\n\t\t\t\"dock-item\": React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;\n\t\t}\n\t}\n\tinterface Window {\n\t\tAliceWM: any;\n\t\tLocalFS: any;\n\t\tExternalApp: any;\n\t\tExternalLib: any;\n\t\telectron: any;\n\t\ttfs: TFSType;\n\t\tloadLock: boolean;\n\t\tlibcurlLock: boolean;\n\t\tlibcurlSession: HTTPSession;\n\t}\n}\n\nexport const isURL = /https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)/;\n\nexport const dirExists = async (path: string): Promise<boolean> => {\n\treturn new Promise(resolve => {\n\t\tif (!window.tb.fs) return resolve(false);\n\t\twindow.tb.fs.exists(path, (exists: boolean) => {\n\t\t\tresolve(exists);\n\t\t});\n\t});\n};\n\nexport const fileExists = async (path: string): Promise<boolean> => {\n\treturn new Promise(resolve => {\n\t\tif (!window.tb.fs) return resolve(false);\n\t\twindow.tb.fs.exists(path, (exists: boolean) => {\n\t\t\tresolve(exists);\n\t\t});\n\t});\n};\n\nexport async function unzip(path: string, target: string) {\n\tconst response = await fetch(\"/fs/\" + path);\n\tconst zipFileContent = await response.arrayBuffer();\n\tif (!(await dirExists(target))) {\n\t\t// @ts-expect-error types\n\t\tawait window.tb.fs.promises.mkdir(target, { recursive: true });\n\t}\n\tconst compressedFiles = fflate.unzipSync(new Uint8Array(zipFileContent));\n\tfor (const [relativePath, content] of Object.entries(compressedFiles)) {\n\t\tconst fullPath = `${target}/${relativePath}`;\n\t\tconst pathParts = fullPath.split(\"/\");\n\t\tlet currentPath = \"\";\n\t\tfor (let i = 0; i < pathParts.length; i++) {\n\t\t\tcurrentPath += pathParts[i] + \"/\";\n\t\t\tif (i === pathParts.length - 1 && !relativePath.endsWith(\"/\")) {\n\t\t\t\tawait window.tb.fs.promises.writeFile(currentPath.slice(0, -1), window.tb.buffer.from(content), \"arraybuffer\");\n\t\t\t} else if (!(await dirExists(currentPath))) {\n\t\t\t\tawait window.tb.fs.promises.mkdir(currentPath);\n\t\t\t}\n\t\t}\n\t\tif (relativePath.endsWith(\"/\")) {\n\t\t\tawait window.tb.fs.promises.mkdir(fullPath);\n\t\t}\n\t}\n\treturn \"Done!\";\n}\n\n/**\n * @interface User\n * @description The information about a user.\n * @property `id` The user ID.\n * @property `username` The username of the user.\n * @property `password` The password of the user.\n * @property `email` The email of the user.\n * @property `permissions` The permissions of the user.\n * @property `groups` The groups the user is in.\n */\nexport interface User {\n\tid: string;\n\tusername: string;\n\tpassword: string | boolean;\n\tpfp: string;\n\tperm: Perm[];\n\tsecurityQuestion?: { question: string; answer: string };\n\temail?: string;\n\tgroups?: string[];\n}\n\n/**\n * @interface Group\n * @description The information about a group.\n * @property `id` The group ID.\n * @property `name` The name of the group.\n * @property `permissions` The permissions of the group.\n * @property `users` The users in the group.\n */\nexport interface Group {\n\tid: string;\n\tname: string;\n\tperm: Perm[];\n\tusers: string[];\n}\n\n/**\n * @enum Perm\n * @description The permissions that can be assigned to a user or group.\n * @constant `sys` The system permission. This is the highest level of permission and can only be assigned to the system administrator.\n * @constant `usr` The user permission. This is the default permission level for a user.\n * @constant `grp` The group permission. This is the default permission level for a group.\n * @constant `pub` The public permission. This is the lowest level of permission and is assigned to all users by default.\n */\nexport enum Perm {\n\tsys,\n\tusr,\n\tgrp,\n\tpub,\n}\n\nexport enum Errors {\n\tENOENT = \"ENOENT\",\n\tEEXIST = \"EEXIST\",\n\tEISDIR = \"EISDIR\",\n\tENOTDIR = \"ENOTDIR\",\n\tEPERM = \"EPERM\",\n\tEACCES = \"EACCES\",\n\tENOTEMPTY = \"ENOTEMPTY\",\n\tEBUSY = \"EBUSY\",\n\tEROFS = \"EROFS\",\n\tENOTFOUND = \"ENOTFOUND\",\n\tEINVALID = \"EINVALID\",\n\tEUNKNOWN = \"EUNKNOWN\",\n\tECONFLICT = \"ECONFLICT\",\n\tEINVALIDARGS = \"EINVALIDARGS\",\n\tEINVALIDTYPE = \"EINVALIDTYPE\",\n\tEINVALIDNAME = \"EINVALIDNAME\",\n\tEINVALIDVALUE = \"EINVALIDVALUE\",\n\tEINVALIDPATH = \"EINVALIDPATH\",\n\tEINVALIDDATA = \"EINVALIDDATA\",\n\tEINVALIDSTATE = \"EINVALIDSTATE\",\n\tEINVALIDFORMAT = \"EINVALIDFORMAT\",\n\tEINVALIDLENGTH = \"EINVALIDLENGTH\",\n\tEINVALIDINDEX = \"EINVALIDINDEX\",\n\tEINVALIDKEY = \"EINVALIDKEY\",\n\tEINVALIDID = \"EINVALIDID\",\n\tEINVALIDDATE = \"EINVALIDDATE\",\n\tEINVALIDTIME = \"EINVALIDTIME\",\n\tEINVALIDDATETIME = \"EINVALIDDATETIME\",\n\tEINVALIDCHAR = \"EINVALIDCHAR\",\n\tEINVALIDCHARSET = \"EINVALIDCHARSET\",\n\tEINVALIDENCODING = \"EINVALIDENCODING\",\n}\n\nexport enum ExitCodes {\n\tSUCCESS = 0,\n\tFAILURE = 1,\n\tFORBIDDEN = 2,\n\tINVALID = 3,\n\tERROR = 4,\n\tTIMEOUT = 5,\n\tINTERRUPT = 6,\n\tABORTED = 7,\n}\n\n/**\n * @interface ProcessInfo\n * @description The information about a process.\n * @property `name` The name of the process.\n * @property `pid` The process ID.\n * @property `token` The token of the process.\n * @property `parent` The parent process ID.\n * @property `children` The child process IDs.\n * @property `status` The status of the process.\n * @property `memory` The memory usage of the process.\n * @property `cpu` The CPU usage of the process.\n * @property `uptime` The time the process has been running.\n * @property `startTime` The time the process started.\n * @property `exitCode` The exit code of the process.\n */\nexport interface ProcessInfo {\n\tname: string;\n\tpid: number;\n\tparent: number;\n\tchildren: number[];\n\tstatus: \"running\" | \"stopped\";\n\tmemory: number;\n\tcpu: number;\n\tuptime: number;\n\tstartTime: number;\n\texitCode: ExitCodes;\n}\n\n/**\n * @interface WindowConfig\n * @description The configuration for a window.\n * @property `title` The title of the window.\n * @property `icon` The icon of the window.\n * @property `src` The source of the window.\n * @property `size` The size of the window.\n * @property `controls` The controls of the window.\n * @property `resizable` Whether the window is resizable.\n * @property `maximizable` Whether the window is maximizable.\n * @property `minimizable` Whether the window is minimizable.\n * @property `closable` Whether the window is closable.\n * @property `snapable` Whether the window is snapable.\n */\nexport interface WindowConfig {\n\ttitle:\n\t\t| string\n\t\t| {\n\t\t\t\ttext: string;\n\t\t\t\tweight?: number;\n\t\t\t\thtml?: string;\n\t\t  };\n\tsrc: string;\n\ticon?: string;\n\tsize?: {\n\t\twidth?: number;\n\t\theight?: number;\n\t\tminWidth?: number;\n\t\tminHeight?: number;\n\t};\n\tsingle?: boolean;\n\tcontrols?: Array<\"minimize\" | \"maximize\" | \"close\">;\n\tresizable?: boolean;\n\tmaximizable?: boolean;\n\tminimizable?: boolean;\n\tclosable?: boolean;\n\tsnapable?: boolean;\n\tmessage?: any;\n\tproxy?: boolean;\n\n\t// non window affecting properties\n\tpid?: string;\n\twid?: string;\n\tzIndex?: number;\n\tfocused?: boolean;\n}\n\ndeclare let props: any;\n\nexport interface NotificationProps {\n\tmessage: string;\n\tapplication: string;\n\ticonSrc: string;\n\ttime?: number;\n\tonOk?: void | any;\n\tonCancel?: void | any;\n\ttxt?: string;\n}\n\nexport interface launcherProps {\n\tname: string;\n\ticon: string;\n\tsrc: string;\n\tuser?: string;\n}\n\nexport interface dialogProps {\n\ttitle: string;\n\toptions?: {\n\t\ttext: string;\n\t\tvalue: string;\n\t}[];\n\tmessage?: string;\n\tdefaultValue?: string;\n\tfilter?: string;\n\tdefaultUsername?: string;\n\tdefualtDir?: string;\n\tfilename?: string;\n\timg?: string;\n\tonOk: void | any;\n\tonCancel?: void | any;\n\tsudo?: boolean;\n\tlocal?: boolean;\n}\n\nexport interface cmprops {\n\ttitlebar?: string | React.ReactNode;\n\tx: number;\n\ty: number;\n\toptions: {\n\t\ttext: string;\n\t\tcolor?: string;\n\t\tclick: () => void;\n\t}[];\n\tiframe?: boolean;\n}\n\nexport interface AppData {\n\twmArgs: {\n\t\tapp_id: string;\n\t\ttitle: {\n\t\t\ttext: string;\n\t\t\tweight: number;\n\t\t};\n\t\ticon?: string;\n\t\tsrc: string;\n\t\tnative: boolean;\n\t\tsize: {\n\t\t\twidth: number | string;\n\t\t\theight: number | string;\n\t\t\tminWidth?: number | string;\n\t\t\tminHeight?: number | string;\n\t\t};\n\t\tsingle: boolean;\n\t\tresizable?: boolean;\n\t\tsnappable?: boolean;\n\t};\n\ticon: string;\n\tname: string;\n\ttitle: string;\n}\n\nexport interface MediaProps {\n\tartist: string;\n\ttrack_name: string;\n\tcreator: string;\n\tvideo_name: string;\n\talbum?: string;\n\ttime?: number;\n\tbackground?: string;\n\tendtime: number;\n\tonPausePlay: void;\n\tonSeek?: void;\n\tonNext?: void;\n\tonBack?: void;\n}\n\nexport type websocketUrl = `wss://${string}` | `ws://${string}`;\n\nexport interface UserSettings {\n\twallpaper: string;\n\twallpaperMode: \"cover\" | \"contain\" | \"stretch\";\n\tanimations: boolean;\n\tproxy: \"Ultraviolet\" | \"Scramjet\";\n\ttransport: string;\n\twispServer: websocketUrl | string | any;\n\t\"battery-percent\": boolean;\n\taccent: string;\n\twindowOptimizations?: boolean;\n\tshowFPS?: boolean;\n\ttimes: {\n\t\tformat: \"12h\" | \"24h\";\n\t\tinternet: boolean;\n\t\tshowSeconds: boolean;\n\t};\n\twindow: {\n\t\twinAccent: string;\n\t\tblurlevel: number;\n\t\talwaysMaximized: boolean;\n\t\talwaysFullscreen: boolean;\n\t};\n}\n\nexport interface SysSettings {\n\ttheme: string;\n\t\"system-blur\": boolean;\n\t\"dock-full\": boolean;\n\tfileAssociatedApps: {\n\t\ttext: string;\n\t\timage: string;\n\t\tvideo: string;\n\t\taudio: string;\n\t};\n\tlocation: string;\n\tweather: {\n\t\tunit: string;\n\t};\n\t\"host-name\": string;\n\tsetup: boolean;\n\tdefaultUser: string;\n}\n\nexport interface ProcInf {\n\tname: string;\n\tsize: { width: number | string; height: number | string };\n\ticon: string;\n\tpid: number;\n\tsrc: string;\n\ttype: \"window\" | \"runtime\";\n\tonKill?: () => void;\n}\n\nexport interface COM {\n\tregistry: any;\n\tsh: ShellType;\n\tbuffer: any;\n\tbattery: {\n\t\tshowPercentage(): void;\n\t\thidePercentage(): void;\n\t\tcanUse(): Promise<boolean>;\n\t};\n\tlauncher: {\n\t\taddApp(props: launcherProps): Promise<boolean>;\n\t\tremoveApp(name: string): Promise<boolean>;\n\t};\n\t/** @deprecated API Stub for legacy applications */\n\ttheme: {\n\t\tget(): Promise<any>;\n\t\tset(data: any): Promise<boolean>;\n\t};\n\tdesktop: {\n\t\tpreferences: {\n\t\t\tsetTheme(color: string): Promise<void>;\n\t\t\ttheme(): void;\n\t\t\tsetAccent(color: string): Promise<void>;\n\t\t\tgetAccent(): Promise<string>;\n\t\t};\n\t\twallpaper: {\n\t\t\tset(path: string): Promise<void>;\n\t\t\tcontain(): Promise<void>;\n\t\t\tstretch(): Promise<void>;\n\t\t\tcover(): Promise<void>;\n\t\t\tfillMode(): Promise<any>;\n\t\t};\n\t\tdock: {\n\t\t\tpin(app: any): void;\n\t\t\tunpin(app: any): void;\n\t\t};\n\t};\n\twindow: {\n\t\tgetId(): void;\n\t\tcreate(props: WindowConfig): void;\n\t\tcontent: {\n\t\t\tget(): void;\n\t\t\tset(html: string | HTMLElement): void;\n\t\t};\n\t\ttitlebar: {\n\t\t\tsetColor(hex: string): void;\n\t\t\tsetText(text: string): void;\n\t\t\tsetBackgroundColor(hex: string): void;\n\t\t};\n\t\tisland: {\n\t\t\taddControl(args: any): void;\n\t\t\tremoveControl(control_id: string): void;\n\t\t};\n\t\tchangeSrc(src: string): void;\n\t\treload(): void;\n\t\tminimize(): void;\n\t\tmaximize(): void;\n\t\tclose(): void;\n\t};\n\tcontextmenu: {\n\t\tcreate(props: cmprops): void;\n\t\tclose(): void;\n\t};\n\tuser: {\n\t\tusername(): Promise<string>;\n\t\tpfp(): Promise<string>;\n\t};\n\tproxy: {\n\t\tget(): Promise<\"Ultraviolet\" | \"Scramjet\">;\n\t\tset(proxy: string): Promise<boolean>;\n\t\tupdateSWs(): Promise<boolean>;\n\t\tencode(url: string, encoder: string): Promise<string>;\n\t\tdecode(url: string, decoder: string): Promise<string>;\n\t};\n\tnotification: {\n\t\tMessage(props: NotificationProps): void;\n\t\tToast(props: NotificationProps): void;\n\t\tInstalling<T>(props: NotificationProps, task?: Promise<T> | (() => Promise<T>), doneToast?: Partial<NotificationProps> | null, failToast?: Partial<NotificationProps> | null): Promise<T> | void;\n\t};\n\tdialog: {\n\t\tAlert(props: dialogProps): void;\n\t\tMessage(props: dialogProps): void;\n\t\tSelect(props: dialogProps): void;\n\t\tAuth(iprops: dialogProps, options: { sudo: boolean }): void;\n\t\tPermissions(props: dialogProps): void;\n\t\tFileBrowser(props: dialogProps): void;\n\t\tDirectoryBrowser(props: dialogProps): void;\n\t\tSaveFile(props: dialogProps): void;\n\t\tCropper(props: dialogProps): void;\n\t\tWebAuth(props: dialogProps): void;\n\t};\n\tsystem: {\n\t\tversion(): string | number | unknown;\n\t\tinstance: System[\"instance\"];\n\t\topenApp(pkg: string): Promise<void>;\n\t\tdownload(url: string, location: string): Promise<void>;\n\t\texportfs(): void;\n\t\tusers: {\n\t\t\tlist(): Promise<string[]>;\n\t\t\tadd(user: User): Promise<boolean>;\n\t\t\tremove(id: string): Promise<boolean>;\n\t\t\tupdate(user: User): Promise<void>;\n\t\t\trenameUser(olduser: string, newuser: string): Promise<void>;\n\t\t};\n\t\tbootmenu: {\n\t\t\taddEntry(name: string, file: string): void;\n\t\t\tremoveEntry(name: string): void;\n\t\t};\n\t\tstartup: {\n\t\t\taddProc(apporname: string, target: \"System\" | \"User\", cmd?: string): Promise<void>;\n\t\t\tremoveProc(apporname: string, target: \"System\" | \"User\"): Promise<void>;\n\t\t\tenable(apporname: string, target: \"System\" | \"User\"): Promise<void>;\n\t\t\tdisable(apporname: string, target: \"System\" | \"User\"): Promise<void>;\n\t\t\tlist(): Promise<string[]>;\n\t\t};\n\t};\n\tlibcurl: typeof libcurl;\n\tfflate: typeof fflate;\n\tfs: FSType;\n\tvfs: vFS;\n\ttauth: {\n\t\tclient: ReturnType<typeof createAuthClient>;\n\t\tsignIn(email: string, password: string): Promise<any>;\n\t\tsignOut(): Promise<void>;\n\t\tisTACC(username?: string): Promise<boolean>;\n\t\tupdateInfo(data: any): Promise<void>;\n\t\treauth(): Promise<void>;\n\t\tsync: {\n\t\t\tretreive: () => Promise<void>;\n\t\t\tupload: () => Promise<void>;\n\t\t\tisSyncing: boolean;\n\t\t};\n\t\tgetInfo(username?: string): Promise<any>;\n\t};\n\tcrypto(pass: string, file: string): Promise<string>;\n\tplatform: {\n\t\tgetPlatform(): Promise<\"desktop\" | \"mobile\">;\n\t};\n\tprocess: {\n\t\tprocs: Record<number, ProcInf>;\n\t\tkill(config: string | number | any): void;\n\t\tlist(): Record<number, ProcInf>;\n\t\tcreate(type: \"window\" | \"runtime\", config: any): void;\n\t\tparse: {\n\t\t\tbuild(src: string): void;\n\t\t};\n\t};\n\tscreen: {\n\t\tcaptureScreen(): Promise<void>;\n\t};\n\tmediaplayer: {\n\t\tmusic(props: MediaProps): void;\n\t\tvideo(props: MediaProps): void;\n\t\thide(): void;\n\t\tpauseplay(): void;\n\t\tisExisting(): void | boolean | Promise<boolean>;\n\t};\n\tfile: {\n\t\thandler: {\n\t\t\topenFile(path: string, type: string): void;\n\t\t\taddHandler(app: string, ext: string): void;\n\t\t\tremoveHandler(ext: string): void;\n\t\t};\n\t\ticons: {\n\t\t\tget(ext: string): Promise<string>;\n\t\t\tset(ext: string, iconPath: string): Promise<boolean>;\n\t\t\tremove(ext: string): Promise<boolean>;\n\t\t};\n\t};\n\tnode: {\n\t\twebContainer: import(\"@webcontainer/api\").WebContainer | {};\n\t\tservers: Map<number, string>;\n\t\tisReady: boolean;\n\t\tstart: () => void;\n\t\tstop(): boolean;\n\t};\n}\n\nexport interface AnuraWMWeakRef {\n\telement: HTMLDivElement | Element | null;\n\tcontent: HTMLDivElement | undefined | null;\n\tapp: ExternalApp;\n\tdragForceX: 0;\n\tdragForceY: 0;\n\tdragging: false;\n\theight: number | string;\n\twidth: number | string;\n\tpid: number | null;\n\tstate: null;\n\tmaximized: false;\n\tminimizing: false;\n\tmouseLeft: null;\n\tmouseTop: null;\n\tonclose: () => null;\n\tonfocus: () => null;\n\tonresize: (_w: number, _h: number) => null;\n\tonsnap: (_side: string) => null;\n\tonunmaximize: () => null;\n\trestoreSvg: null | SVGElement | ChildNode;\n\tkill: () => void;\n\talive: boolean;\n\tmaximizeImg: null | SVGElement | ChildNode;\n\tmaximizeSvg: null | SVGElement | ChildNode;\n\twininfo: WindowInformation;\n\ttitle: string;\n}\n\nexport interface TAuthReturnType {\n\tuser: any;\n\tsettings: [\n\t\t{\n\t\t\tsettings: UserSettings;\n\t\t\tapps: {\n\t\t\t\trepos: string[];\n\t\t\t\tinstalled: string[];\n\t\t\t};\n\t\t\tdavs: ServerInfo[];\n\t\t},\n\t];\n}\n\nexport interface TAuthSSData {\n\tsettings: {\n\t\tsettings: UserSettings;\n\t\tapps: {\n\t\t\trepos: string[];\n\t\t\tinstalled: string[];\n\t\t};\n\t\tdavs: ServerInfo[];\n\t}[];\n}\n"
  },
  {
    "path": "src/sys/vFS.ts",
    "content": "// @ts-expect-error: No types\nimport * as webdav from \"../../public/apps/files.tapp/webdav.js\";\nimport { FSType } from \"@terbiumos/tfs\";\n\nexport interface ServerInfo {\n\tname: string;\n\turl: string;\n\tusername: string;\n\tpassword: string;\n}\n\nexport interface ServerConnection {\n\tname: string;\n\tconnected: boolean;\n\tconnection: any | null;\n\turl: string;\n}\n\nexport class vFS {\n\tservers: Map<string, ServerConnection> = new Map();\n\tcurrentServer: ServerConnection | null = null;\n\n\tprivate constructor(servers: Map<string, ServerConnection>) {\n\t\tthis.servers = servers;\n\t\twindow.tb.fs.watch(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, { recursive: true }, async () => {\n\t\t\tconst data: ServerInfo[] = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\t\tthis.servers.clear();\n\t\t\tthis.servers = new Map<string, ServerConnection>(\n\t\t\t\tdata.map(info => [\n\t\t\t\t\tinfo.name,\n\t\t\t\t\t{\n\t\t\t\t\t\tname: info.name,\n\t\t\t\t\t\tconnected: false,\n\t\t\t\t\t\tconnection: null,\n\t\t\t\t\t\turl: info.url,\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t\t);\n\t\t\tawait this.mountAll();\n\t\t});\n\t}\n\n\tstatic async create(): Promise<vFS> {\n\t\tconst data: ServerInfo[] = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\tconst servers = new Map<string, ServerConnection>();\n\t\tfor (const info of data) {\n\t\t\tservers.set(info.name, {\n\t\t\t\tname: info.name,\n\t\t\t\tconnected: false,\n\t\t\t\tconnection: null,\n\t\t\t\turl: info.url,\n\t\t\t});\n\t\t}\n\t\tconst vfs = new vFS(servers);\n\t\twindow.addEventListener(\"libcurl_load\", async () => {\n\t\t\tawait vfs.mountAll();\n\t\t});\n\t\treturn vfs;\n\t}\n\n\tasync mount(serverName: string): Promise<any> {\n\t\tconst data: ServerInfo[] = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${sessionStorage.getItem(\"currAcc\")}/files/davs.json`, \"utf8\"));\n\t\tconst { url, username, password } = data.find(s => s.name === serverName) || {};\n\t\tif (!url) throw new Error(`Server \"${serverName}\" not found`);\n\t\ttry {\n\t\t\tconst client = webdav.createClient(url, { username, password });\n\t\t\tawait client.getDirectoryContents(\"/\");\n\t\t\tthis.servers.set(serverName, {\n\t\t\t\tname: serverName,\n\t\t\t\tconnected: true,\n\t\t\t\tconnection: new vFSOperations(client),\n\t\t\t\turl,\n\t\t\t});\n\t\t} catch (e) {\n\t\t\tthis.servers.set(serverName, {\n\t\t\t\tname: serverName,\n\t\t\t\tconnected: false,\n\t\t\t\tconnection: null,\n\t\t\t\turl,\n\t\t\t});\n\t\t\tconsole.warn(`Failed to connect to server \"${serverName}\":`, e);\n\t\t}\n\t}\n\n\tasync mountAll(): Promise<void> {\n\t\tfor (const serverName of this.servers.keys()) {\n\t\t\tawait this.mount(serverName);\n\t\t}\n\t}\n\n\tasync addServer(info: ServerInfo): Promise<void> {\n\t\tconst davjson = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${await window.tb.user.username()}/files/davs.json`, \"utf8\"));\n\t\tdavjson.push({\n\t\t\tname: info.name,\n\t\t\turl: info.url,\n\t\t\tusername: info.username,\n\t\t\tpassword: info.password,\n\t\t});\n\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${await window.tb.user.username()}/files/davs.json`, JSON.stringify(davjson, null, 2));\n\t\tconst config = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${await window.tb.user.username()}/files/config.json`, \"utf8\"));\n\t\tconfig.drives[info.name] = `/mnt/${info.name}/`;\n\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${await window.tb.user.username()}/files/config.json`, JSON.stringify(config, null, 2));\n\t\twindow.tb.notification.Toast({\n\t\t\tapplication: \"System\",\n\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\tmessage: \"New Dav Device has been added\",\n\t\t});\n\t}\n\n\tasync removeServer(serverName: string): Promise<void> {\n\t\tconst davjson = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${await window.tb.user.username()}/files/davs.json`, \"utf8\"));\n\t\tconst index = davjson.findIndex((entry: any) => entry.name.toLowerCase() === serverName.toLowerCase());\n\t\tif (index !== -1) {\n\t\t\tdavjson.splice(index, 1);\n\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${await window.tb.user.username()}/files/davs.json`, JSON.stringify(davjson, null, 2));\n\t\t\tconst config = JSON.parse(await window.tb.fs.promises.readFile(`/apps/user/${await window.tb.user.username()}/files/config.json`, \"utf8\"));\n\t\t\tdelete config.drives[serverName.toLowerCase()];\n\t\t\tawait window.tb.fs.promises.writeFile(`/apps/user/${await window.tb.user.username()}/files/config.json`, JSON.stringify(config, null, 2));\n\t\t\twindow.tb.notification.Toast({\n\t\t\t\tapplication: \"System\",\n\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\tmessage: \"Dav Drive has been removed\",\n\t\t\t});\n\t\t} else {\n\t\t\twindow.tb.notification.Toast({\n\t\t\t\tapplication: \"System\",\n\t\t\t\ticonSrc: \"/fs/apps/system/about.tapp/icon.svg\",\n\t\t\t\tmessage: \"Dav Drive not found\",\n\t\t\t});\n\t\t}\n\t}\n\n\tsetServer(serverName: string): boolean {\n\t\tconst server = this.servers.get(serverName);\n\t\tif (server && server.connected) {\n\t\t\tthis.currentServer = server;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\twhatFS(path: string): vFSOperations | FSType {\n\t\tif (path.startsWith(\"/mnt/\")) {\n\t\t\tconst parts = path.split(\"/\");\n\t\t\tif (parts.length > 2) {\n\t\t\t\treturn this.servers.get(parts[2])?.connection;\n\t\t\t}\n\t\t}\n\t\treturn window.tb.fs;\n\t}\n}\n\nexport class vFSOperations {\n\tclient: any;\n\n\tconstructor(client: any) {\n\t\tthis.client = client;\n\t}\n\n\tpathtourl(path: string): string {\n\t\tif (!path.startsWith(\"/mnt/\")) return path;\n\t\tconst parts = path.split(\"/\").filter(Boolean);\n\t\tif (parts.length < 2) return path;\n\t\tconst serverName = parts[1];\n\t\tconst servers: Map<string, any> | undefined = window.tb.vfs.servers;\n\t\tconst server = servers?.get(serverName);\n\t\tif (!server || !server.url) return path;\n\t\tconst rest = parts.slice(2).join(\"/\");\n\t\tconst base = server.url.replace(/\\/+$/, \"\");\n\t\treturn rest ? `${base}/${rest}` : `${base}/`;\n\t}\n\n\tpathtoFSPath(path: string): string {\n\t\tif (!path.startsWith(\"/mnt/\")) return path;\n\t\tconst parts = path.split(\"/\").filter(Boolean);\n\t\tif (parts.length < 2) return \"/\";\n\t\tconst rest = parts.slice(2).join(\"/\");\n\t\treturn rest ? `/${rest}` : \"/\";\n\t}\n\n\treaddir(path: string, callback: (err: any, files?: any[]) => void): void {\n\t\tthis.client\n\t\t\t.getDirectoryContents(this.pathtoFSPath(path))\n\t\t\t.then((files: any[]) => {\n\t\t\t\tconst basenames = files.map((f: any) => {\n\t\t\t\t\tif (typeof f === \"string\") return f.split(\"/\").filter(Boolean).pop() || \"\";\n\t\t\t\t\tif (f && typeof f.basename === \"string\") return f.basename;\n\t\t\t\t\tif (f && typeof f.filename === \"string\") return f.filename.split(\"/\").filter(Boolean).pop() || \"\";\n\t\t\t\t\treturn \"\";\n\t\t\t\t});\n\t\t\t\tcallback(null, basenames);\n\t\t\t})\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\treadFile(path: string, callback: (err: any, data?: string) => void): void {\n\t\ttry {\n\t\t\tthis.client\n\t\t\t\t.getFileContents(this.pathtoFSPath(path), { format: \"binary\" })\n\t\t\t\t.then((data: any) => {\n\t\t\t\t\tlet uint8: Uint8Array | null = null;\n\t\t\t\t\tif (data instanceof ArrayBuffer) {\n\t\t\t\t\t\tuint8 = new Uint8Array(data);\n\t\t\t\t\t} else if (ArrayBuffer.isView(data)) {\n\t\t\t\t\t\tuint8 = new Uint8Array((data as any).buffer, (data as any).byteOffset || 0, (data as any).byteLength || undefined);\n\t\t\t\t\t} else if (typeof data === \"string\") {\n\t\t\t\t\t\treturn callback(null, data);\n\t\t\t\t\t} else if (data && data.buffer instanceof ArrayBuffer) {\n\t\t\t\t\t\tuint8 = new Uint8Array(data.buffer);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst asBuffer = Buffer.isBuffer(data) ? data : null;\n\t\t\t\t\t\t\tif (asBuffer) uint8 = new Uint8Array(asBuffer);\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\treturn callback(null, data);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!uint8) return callback(null, data);\n\t\t\t\t\tconst len = Math.min(uint8.length, 512);\n\t\t\t\t\tlet nonText = 0;\n\t\t\t\t\tfor (let i = 0; i < len; i++) {\n\t\t\t\t\t\tconst ch = uint8[i];\n\t\t\t\t\t\tif (ch === 0) {\n\t\t\t\t\t\t\tnonText = Infinity;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ((ch >= 7 && ch <= 13) || (ch >= 32 && ch <= 126)) {\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnonText++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (nonText === Infinity || nonText / len > 0.3) {\n\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\treturn callback(null, uint8.buffer);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst text = new TextDecoder(\"utf-8\", { fatal: false }).decode(uint8);\n\t\t\t\t\t\t\treturn callback(null, text);\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t// @ts-expect-error\n\t\t\t\t\t\t\treturn callback(null, uint8.buffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch((err: any) => callback(err));\n\t\t} catch (err) {\n\t\t\tcallback(err);\n\t\t}\n\t}\n\n\twriteFile(path: string, data: string | ArrayBuffer, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.putFileContents(this.pathtoFSPath(path), data)\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\tdelete(path: string, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.deleteFile(this.pathtoFSPath(path))\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\trename(oldPath: string, newPath: string, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.moveFile(this.pathtoFSPath(oldPath), this.pathtoFSPath(newPath))\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\tmkdir(path: string, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.createDirectory(this.pathtoFSPath(path))\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\texists(path: string, callback: (err: any, exists?: boolean) => void): void {\n\t\tthis.client\n\t\t\t.exists(this.pathtoFSPath(path))\n\t\t\t.then((exists: boolean) => callback(null, exists))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\tstat(path: string, callback: (err: any, stat?: any) => void): void {\n\t\tthis.client\n\t\t\t.stat(this.pathtoFSPath(path))\n\t\t\t.then((stat: any) => callback(null, stat))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\tcopyFile(source: string, destination: string, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.copyFile(this.pathtoFSPath(source), this.pathtoFSPath(destination))\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\tunlink(path: string, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.deleteFile(this.pathtoFSPath(path))\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\tmove(source: string, destination: string, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.moveFile(this.pathtoFSPath(source), this.pathtoFSPath(destination))\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\tappendFile(path: string, data: string | ArrayBuffer, callback: (err: any) => void): void {\n\t\tthis.client\n\t\t\t.getFileContents(this.pathtoFSPath(path), { format: \"text\" })\n\t\t\t.then((existingData: string) => {\n\t\t\t\tconst newData = existingData + data;\n\t\t\t\treturn this.client.putFileContents(this.pathtoFSPath(path), newData);\n\t\t\t})\n\t\t\t.then(() => callback(null))\n\t\t\t.catch((err: any) => callback(err));\n\t}\n\n\taccess(path: string, ...rest: any[]): void {\n\t\tthis.exists(path, (err, exists) => {\n\t\t\tconst callback = rest.pop();\n\t\t\tif (err) return callback(err);\n\t\t\tif (!exists) return callback(new Error(\"File does not exist\"));\n\t\t\tcallback(null);\n\t\t});\n\t}\n\n\tpromises = {\n\t\treaddir: (path: string): Promise<any[]> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.readdir(path, (err, files) => (err ? reject(err) : resolve(files!)));\n\t\t\t}),\n\t\treadFile: (path: string): Promise<string> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.readFile(path, (err, data) => (err ? reject(err) : resolve(data!)));\n\t\t\t}),\n\t\twriteFile: (path: string, data: string | ArrayBuffer): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.writeFile(path, data, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\tdelete: (path: string): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.delete(path, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\trename: (oldPath: string, newPath: string): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.rename(oldPath, newPath, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\tmkdir: (path: string): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.mkdir(path, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\texists: (path: string): Promise<boolean> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.exists(path, (err, exists) => (err ? reject(err) : resolve(exists!)));\n\t\t\t}),\n\t\tstat: (path: string): Promise<any> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.stat(path, (err, stat) => (err ? reject(err) : resolve(stat!)));\n\t\t\t}),\n\t\tcopyFile: (source: string, destination: string): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.copyFile(source, destination, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\tunlink: (path: string): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.unlink(path, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\tmove: (source: string, destination: string): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.move(source, destination, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\tappendFile: (path: string, data: string | ArrayBuffer): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.appendFile(path, data, err => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t\taccess: (path: string, ...rest: any[]): Promise<void> =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tthis.access(path, ...rest, (err: any) => (err ? reject(err) : resolve()));\n\t\t\t}),\n\t};\n}\n"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "tsconfig.app.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ES2024\",\n\t\t\"useDefineForClassFields\": true,\n\t\t\"lib\": [\"ES2024\", \"DOM\", \"DOM.Iterable\"],\n\t\t\"module\": \"ESNext\",\n\t\t\"skipLibCheck\": true,\n\n\t\t/* Bundler mode */\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"moduleDetection\": \"force\",\n\t\t\"noEmit\": true,\n\t\t\"jsx\": \"react-jsx\",\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"./src/*\"],\n\t\t\t\"@sys/*\": [\"./sys/*\"],\n\t\t\t\"@assets/*\": [\"./src/assets/*\"],\n\t\t\t\"@structs/*\": [\"./src/structs/*\"]\n\t\t},\n\n\t\t/* Linting */\n\t\t\"strict\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\n\t\t\"typeRoots\": [\"./node_modules/@types\", \"./node_modules/@mercuryworkshop/scramjet/dist/types\"]\n\t},\n\t\"include\": [\"src\", \"node_modules/@mercuryworkshop/scramjet/dist/types/types.d.ts\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"files\": [],\n\t\"compilerOptions\": {\n\t\t\"target\": \"ESNext\",\n\t\t\"noUnusedLocals\": false,\n\t\t\"noUnusedParameters\": false\n\t},\n\t\"references\": [{ \"path\": \"./tsconfig.app.json\" }, { \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ES2024\",\n\t\t\"lib\": [\"ES2024\"],\n\t\t\"module\": \"ESNext\",\n\t\t\"skipLibCheck\": true,\n\n\t\t/* Bundler mode */\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"moduleDetection\": \"force\",\n\t\t\"noEmit\": true,\n\n\t\t/* Linting */\n\t\t\"strict\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noFallthroughCasesInSwitch\": true\n\t},\n\t\"include\": [\"vite.config.ts\", \"env.d.ts\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { baremuxPath } from \"@mercuryworkshop/bare-mux/node\";\n// @ts-expect-error no types\nimport { epoxyPath } from \"@mercuryworkshop/epoxy-transport\";\nimport { libcurlPath } from \"@mercuryworkshop/libcurl-transport\";\nimport { scramjetPath } from \"@mercuryworkshop/scramjet/path\";\n// @ts-expect-error no types\nimport { server as wisp } from \"@mercuryworkshop/wisp-js/server\";\nimport { uvPath } from \"@titaniumnetwork-dev/ultraviolet\";\nimport react from \"@vitejs/plugin-react-swc\";\nimport config from \"dotenv\";\nimport { defineConfig } from \"vite\";\nimport { viteStaticCopy } from \"vite-plugin-static-copy\";\nimport { tfsPath } from \"@terbiumos/tfs\";\n\nconfig.config();\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n\tplugins: [\n\t\treact(),\n\t\tviteStaticCopy({\n\t\t\ttargets: [\n\t\t\t\t// These are copied so that Terbium will still work statically\n\t\t\t\t{\n\t\t\t\t\tsrc: `${uvPath}/**/*`.replace(/\\\\/g, \"/\"),\n\t\t\t\t\tdest: \"uv\",\n\t\t\t\t\toverwrite: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tsrc: `${scramjetPath}/**/*`.replace(/\\\\/g, \"/\"),\n\t\t\t\t\tdest: \"scram\",\n\t\t\t\t\toverwrite: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tsrc: `${baremuxPath}/**/*`.replace(/\\\\/g, \"/\"),\n\t\t\t\t\tdest: \"baremux\",\n\t\t\t\t\toverwrite: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tsrc: `${epoxyPath}/**/*`.replace(/\\\\/g, \"/\"),\n\t\t\t\t\tdest: \"epoxy\",\n\t\t\t\t\toverwrite: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tsrc: `${libcurlPath}/**/*`.replace(/\\\\/g, \"/\"),\n\t\t\t\t\tdest: \"libcurl\",\n\t\t\t\t\toverwrite: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tsrc: `${tfsPath}/**/*`.replace(/\\\\/g, \"/\"),\n\t\t\t\t\tdest: \"tfs\",\n\t\t\t\t\toverwrite: false,\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t\t{\n\t\t\tname: \"vite-wisp-server\",\n\t\t\tconfigureServer(server) {\n\t\t\t\tserver.httpServer?.on(\"upgrade\", (req, socket, head) => (req.url?.startsWith(\"/wisp\") ? wisp.routeRequest(req, socket, head) : undefined));\n\t\t\t},\n\t\t},\n\t],\n\tserver: {\n\t\tport: process.env.port || 3001,\n\t\twatch: {\n\t\t\tignored: [\"**/public/apps/terminal.tapp/**\"],\n\t\t},\n\t},\n});\n"
  }
]