diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e8f7928 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Typecheck, lint, test, build + # The repo is public and the runners are ours: never run code from fork PRs on them. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: + group: Default + labels: [self-hosted, linux, x64] + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + # Installs the pnpm version pinned in package.json's `packageManager`. + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v7 + with: + node-version-file: .node-version + cache: pnpm + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm typecheck + + - name: Lint + run: pnpm exec biome ci + + - name: Test + run: pnpm test + + - name: Build + run: pnpm build diff --git a/.gitignore b/.gitignore index 9f098e3..ae1c72d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,41 @@ +# Dependencies +node_modules/ +.pnpm-store/ -dist -.solid -.output -.vercel -.netlify -netlify +# Build output and caches +.output/ +.nitro/ +.vinxi/ +.solid/ +dist/ +*.tsbuildinfo +.cache/ -# Cloudflare Pages functions (https://developers.cloudflare.com/pages/platform/functions) -functions +# Secrets and local environment +.env +.env.* +!.env.example +# Cloudflare: `.dev.vars` holds local secrets for `wrangler dev` +.dev.vars +.dev.vars.* +.wrangler/ +*.local +*.pem +*.key -# dependencies -/node_modules +# Logs and test output +*.log +coverage/ -# IDEs and editors -/.idea -.project -.classpath -*.launch -.settings/ +# Claude Code session config (local dev servers, permissions) +.claude/ -# Temp -gitignore +# IDEs and editors +.idea/ +.vscode/* +!.vscode/extensions.json +*.swp -# System Files +# System files .DS_Store Thumbs.db diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 01282bf..97c32d7 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,49 @@ -# SolidStart +# ChatBloom -Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); +A Twitch chat overlay for streamers, built with [SolidStart](https://docs.solidjs.com/solid-start) and [SUID](https://suid.dev) (Material UI for Solid). -## Creating a project +## Requirements -```bash -# create a new project in the current directory -npm init solid@latest +- Node.js 24+ (see `.node-version`) +- pnpm (the version is pinned in `package.json` → `packageManager`) + +## Development -# create a new project in my-app -npm init solid@latest my-app +```bash +pnpm install +pnpm dev ``` -## Developing +The dev server runs on plain Node; no local Cloudflare tooling is needed. -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: +| Script | What it does | +| ---------------- | --------------------------------------------------- | +| `pnpm dev` | Start the dev server with HMR | +| `pnpm build` | Production build for Cloudflare Pages into `dist/` | +| `pnpm typecheck` | Type-check with TypeScript | +| `pnpm lint` | Lint and check formatting with Biome | +| `pnpm format` | Apply Biome formatting and safe fixes | +| `pnpm test` | Run unit tests (`test/`) with Node's test runner | +| `pnpm check` | `typecheck` + `lint` + `test` (run before pushing) | -```bash -npm run dev +## Deployment -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` +The app is built and deployed remotely by Cloudflare Pages: + +- Build command: `pnpm build` +- Build output directory: `dist` + +Pages are server-rendered on Cloudflare; `/v3` is prerendered to static HTML at build time. + +## Chat overlay -## Building +Add `https:///v3/chat/` as an OBS browser source. The page is +transparent and connects straight from the browser to Twitch chat (anonymously) and to 7TV, +BetterTTV and FrankerFaceZ for emotes, badges, name paints and live updates; no server-side +state or secrets are involved. -Solid apps are built with _adapters_, which optimise your project for deployment to different environments. +## CI -By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different adapter, add it to the `devDependencies` in `package.json` and specify in your `vite.config.js`. +`.github/workflows/ci.yml` runs install, typecheck, lint, test and build on the self-hosted +runners for pushes and pull requests to `main`. Pull requests from forks are skipped, since the +runners are ours and this repository is public. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..1313547 --- /dev/null +++ b/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": ["**", "!public"] + }, + "formatter": { + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 100 + }, + "json": { + "formatter": { + "indentWidth": 2 + } + }, + "linter": { + "rules": { + "preset": "recommended" + }, + "domains": { + "solid": "all" + } + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/package.json b/package.json index cc5da0c..02e7be3 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,32 @@ { "name": "chatis", - "scripts": { - "dev": "solid-start dev", - "build": "solid-start build", - "start": "solid-start start" - }, + "private": true, "type": "module", - "devDependencies": { - "@suid/vite-plugin": "0.1.1", - "autoprefixer": "^10.4.13", - "postcss": "^8.4.21", - "solid-start-cloudflare-pages": "^0.2.11", - "solid-start-node": "^0.2.11", - "tailwindcss": "^3.2.4", - "typescript": "^4.9.4", - "vite": "^3.0.0", - "vite-plugin-solid": "^2.5.0" + "license": "AGPL-3.0-or-later", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "typecheck": "tsc", + "lint": "biome check", + "format": "biome check --write", + "test": "node --import ./test/register.ts --test \"test/**/*.test.ts\"", + "check": "pnpm typecheck && pnpm lint && pnpm test" }, "dependencies": { - "@solidjs/meta": "^0.28.2", - "@solidjs/router": "^0.7.0", - "@suid/icons-material": "^0.5.5", - "@suid/material": "^0.8.2", - "solid-js": "^1.6.8", - "solid-start": "^0.2.11", - "undici": "^5.14.0" + "@solidjs/meta": "^0.29.4", + "@solidjs/router": "^1.0.0", + "@solidjs/start": "^2.0.5", + "@suid/material": "^0.19.0", + "solid-js": "^1.9.15" + }, + "devDependencies": { + "@biomejs/biome": "^2.5.14", + "nitro": "3.0.260903-beta", + "typescript": "^7.0.2", + "vite": "^8.3.1" }, "engines": { - "node": ">=16" - } + "node": ">=24" + }, + "packageManager": "pnpm@12.8.1" } diff --git a/patches/@suid__styled-engine@0.9.0.patch b/patches/@suid__styled-engine@0.9.0.patch new file mode 100644 index 0000000..bff397d --- /dev/null +++ b/patches/@suid__styled-engine@0.9.0.patch @@ -0,0 +1,25 @@ +diff --git a/createStyle.jsx b/createStyle.jsx +index 89127f2d1def94d6f04f513fc56e362255ded72b..5da72f1f99051044fc8fdaeab4672daf2c3c7cf9 100644 +--- a/createStyle.jsx ++++ b/createStyle.jsx +@@ -23,16 +23,10 @@ function normalizeStyleProps(props) { + function createStyleId() { + if (!sharedConfig.context) + return randomString(); +- const id = createUniqueId().replaceAll("-", ""); +- const chunkSize = 9; +- const chunks = Math.ceil(id.length / chunkSize); +- const ids = []; +- for (let chunk = 1; chunk <= chunks; ++chunk) { +- const index = (chunk - 1) * chunkSize; +- const number = Number(id.slice(index, index + chunkSize)); +- ids.push(number.toString(32)); +- } +- return ids.join("-"); ++ // Solid >= 1.8 hydration ids contain letters ("0-1-a10"), which the old ++ // Number() re-encoding turned into NaN, so unrelated components shared one ++ // class. The id is already unique and identical on server and client. ++ return createUniqueId(); + } + function createStyle(value) { + const context = useContext(StyledEngineContext); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8c24a2..a3974b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3832 +1,2564 @@ -lockfileVersion: 5.4 - -specifiers: - '@solidjs/meta': ^0.28.2 - '@solidjs/router': ^0.7.0 - '@suid/icons-material': ^0.5.5 - '@suid/material': ^0.8.2 - '@suid/vite-plugin': 0.1.1 - autoprefixer: ^10.4.13 - postcss: ^8.4.21 - solid-js: ^1.6.8 - solid-start: ^0.2.11 - solid-start-cloudflare-pages: ^0.2.11 - solid-start-node: ^0.2.11 - tailwindcss: ^3.2.4 - typescript: ^4.9.4 - undici: ^5.14.0 - vite: ^3.0.0 - vite-plugin-solid: ^2.5.0 - -dependencies: - '@solidjs/meta': 0.28.2_solid-js@1.6.9 - '@solidjs/router': 0.7.0_solid-js@1.6.9 - '@suid/icons-material': 0.5.6_solid-js@1.6.9 - '@suid/material': 0.8.2_solid-js@1.6.9 - solid-js: 1.6.9 - solid-start: 0.2.16_55umyvmfaswzxpylft3tvr3dvm - undici: 5.16.0 - -devDependencies: - '@suid/vite-plugin': 0.1.1_vite@3.2.5 - autoprefixer: 10.4.13_postcss@8.4.21 - postcss: 8.4.21 - solid-start-cloudflare-pages: 0.2.16_beln6p34t7mdfnglvydsh3cc6m - solid-start-node: 0.2.16_f63ddrunxocrtns5sxchmwmkce - tailwindcss: 3.2.4_postcss@8.4.21 - typescript: 4.9.4 - vite: 3.2.5 - vite-plugin-solid: 2.5.0_solid-js@1.6.9+vite@3.2.5 +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.8.1 + version: 12.8.1 packages: - /@ampproject/remapping/2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.17 + '@pnpm/exe.android-arm64@12.8.1': + resolution: {integrity: sha512-siDxcidfz5eM6L2tJfik+i2bbUEw8UC7MZ3Yl2HYdRmqbdhHGAOQACtOOu6QVW4Jul4/w5wVKm+wM8ACtgh1Pg==} + cpu: [arm64] + os: [android] - /@antfu/utils/0.7.2: - resolution: {integrity: sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g==} + '@pnpm/exe.android-x64@12.8.1': + resolution: {integrity: sha512-0F/uqPRc3aN4vLHsa0f+6fTtmsigFQ9/yxEU43fionF//pNHEd5e97b3w/nKIgqYMCUQCJphmBRMA9Q8/wlo9Q==} + cpu: [x64] + os: [android] - /@babel/code-frame/7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 + '@pnpm/exe.darwin-arm64@12.8.1': + resolution: {integrity: sha512-/rwavvMQJsl2RIxOHBnDF+4Gk+fhQFAY4M3PQZoKskJOEnaJcygG9p1xby6wcQcbS4d9dIubv09CQhufTgfmpw==} + cpu: [arm64] + os: [darwin] - /@babel/compat-data/7.20.10: - resolution: {integrity: sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==} - engines: {node: '>=6.9.0'} + '@pnpm/exe.darwin-x64@12.8.1': + resolution: {integrity: sha512-htYt2gN7zqJKLhC99Ft6EUiO7myjZAZXfmGCTJUL1v+o7vE9hZdE1+fAkk4Oj1ZHNRti6b2woOxEkRkAusi8OQ==} + cpu: [x64] + os: [darwin] - /@babel/core/7.20.12: - resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.20.7 - '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-module-transforms': 7.20.11 - '@babel/helpers': 7.20.13 - '@babel/parser': 7.20.13 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color + '@pnpm/exe.freebsd-x64@12.8.1': + resolution: {integrity: sha512-PB5BULNgytxvExoOXUUeseS/DcpPENs+cg3iA6xHHeaLT4ctEGKSGXEbuj7USeqvo8JBpAx039hivnbzGqDwnA==} + cpu: [x64] + os: [freebsd] - /@babel/generator/7.20.7: - resolution: {integrity: sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 - '@jridgewell/gen-mapping': 0.3.2 - jsesc: 2.5.2 + '@pnpm/exe.linux-arm64-musl@12.8.1': + resolution: {integrity: sha512-kKeyC/ArjrgdciQRBrOL0j9HtOI4gDRgoyP+eJ1CQKyadpKlf/DhDDyspXe07R3B9KagTVLUF2nlt6Dr5quFIg==} + cpu: [arm64] + os: [linux] + libc: [musl] - /@babel/helper-annotate-as-pure/7.18.6: - resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 + '@pnpm/exe.linux-arm64@12.8.1': + resolution: {integrity: sha512-q4s9a9X2O3N9aeUFooW24orNrxvu20+jyVUFR5RanPCqOKKPBrgs2iywMkBBx1aXkkzdWT54/mfz8Be8sJlWEw==} + cpu: [arm64] + os: [linux] + libc: [glibc] - /@babel/helper-builder-binary-assignment-operator-visitor/7.18.9: - resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.20.7 + '@pnpm/exe.linux-ppc64@12.8.1': + resolution: {integrity: sha512-F7XSjKJthwCh/L6abZiMpAfo2Cm61SZIT3e4dfgiIjESndlWaXCnfLKngSgZ/SEMsdmHT6dud3+kWIB3tw0/xQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] - /@babel/helper-compilation-targets/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.20.10 - '@babel/core': 7.20.12 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.4 - lru-cache: 5.1.1 - semver: 6.3.0 + '@pnpm/exe.linux-riscv64@12.8.1': + resolution: {integrity: sha512-z1ecpIK/ZY08Unk69KWWE9867oaKUytGUjrHnBoGFAAhsK+q+0cfvBH+f5fWobJ7FX97leMlLO+gkJolddM45Q==} + cpu: [riscv64] + os: [linux] + libc: [glibc] - /@babel/helper-create-class-features-plugin/7.20.12_@babel+core@7.20.12: - resolution: {integrity: sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-member-expression-to-functions': 7.20.7 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/helper-replace-supers': 7.20.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/helper-split-export-declaration': 7.18.6 - transitivePeerDependencies: - - supports-color + '@pnpm/exe.linux-s390x@12.8.1': + resolution: {integrity: sha512-KU6tnrBfu4uKXTq6N5qJwSm8FSrs9Wc8b5AUEsHK9bRXwNdsZ1/lRxG1++X29SKRW9OSNuDSjxASv5mAvGo1gA==} + cpu: [s390x] + os: [linux] + libc: [glibc] - /@babel/helper-create-regexp-features-plugin/7.20.5_@babel+core@7.20.12: - resolution: {integrity: sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-annotate-as-pure': 7.18.6 - regexpu-core: 5.2.2 + '@pnpm/exe.linux-x64-musl@12.8.1': + resolution: {integrity: sha512-eUE8BWJkDr2tJb/yrk0yVnszlmNqFarVxUlqiZ9BAkct28Nq/5g2H0tvMDmPA7fAQW3STNj+omJPPRVT+4s5sQ==} + cpu: [x64] + os: [linux] + libc: [musl] - /@babel/helper-define-polyfill-provider/0.3.3_@babel+core@7.20.12: - resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} - peerDependencies: - '@babel/core': ^7.4.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - debug: 4.3.4 - lodash.debounce: 4.0.8 - resolve: 1.22.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color + '@pnpm/exe.linux-x64@12.8.1': + resolution: {integrity: sha512-8bDZ0lZlCdi2rvnFul7EYgcC7zKeWSe76y8JV2oXobaS8p9i9d6PSf6LgLtTM0fI7R9Oh4eLCbveXyNDMmvZ+g==} + cpu: [x64] + os: [linux] + libc: [glibc] - /@babel/helper-environment-visitor/7.18.9: - resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} - engines: {node: '>=6.9.0'} + '@pnpm/exe.win32-arm64@12.8.1': + resolution: {integrity: sha512-MsT0dlrRxnCRCHCaosNbhWpEiAFnFuMEu3KuDJDU41BXgTwiINxU8goloUSUDejWZUqKaUqNC/qUTihl9nFtqg==} + cpu: [arm64] + os: [win32] - /@babel/helper-explode-assignable-expression/7.18.6: - resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 + '@pnpm/exe.win32-x64@12.8.1': + resolution: {integrity: sha512-SWtPe0PsbTTk//gJND10tMdOfCYjrkq5+qV49hzVhY7xz2iJ339Rc+xwASlcvbwTfrvH39YNqpKYibD+CRGLwA==} + cpu: [x64] + os: [win32] - /@babel/helper-function-name/7.19.0: - resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.20.7 + pnpm@12.8.1: + resolution: {integrity: sha512-9kupB1B/XOr+BsjTjmBS0BeURFgOwSed3Vv8EctIqoomRLZlmOB+dh2oSHKp/FfV+QK4f6SdAkGY1VhhKqu+RQ==} + engines: {node: '>=18.*'} + hasBin: true - /@babel/helper-hoist-variables/7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 +snapshots: - /@babel/helper-member-expression-to-functions/7.20.7: - resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 + '@pnpm/exe.android-arm64@12.8.1': + optional: true - /@babel/helper-module-imports/7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 + '@pnpm/exe.android-x64@12.8.1': + optional: true - /@babel/helper-module-transforms/7.20.11: - resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.20.2 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - transitivePeerDependencies: - - supports-color + '@pnpm/exe.darwin-arm64@12.8.1': + optional: true - /@babel/helper-optimise-call-expression/7.18.6: - resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 + '@pnpm/exe.darwin-x64@12.8.1': + optional: true - /@babel/helper-plugin-utils/7.20.2: - resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} - engines: {node: '>=6.9.0'} + '@pnpm/exe.freebsd-x64@12.8.1': + optional: true - /@babel/helper-remap-async-to-generator/7.18.9_@babel+core@7.20.12: - resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.20.7 - transitivePeerDependencies: - - supports-color + '@pnpm/exe.linux-arm64-musl@12.8.1': + optional: true - /@babel/helper-replace-supers/7.20.7: - resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-member-expression-to-functions': 7.20.7 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - transitivePeerDependencies: - - supports-color + '@pnpm/exe.linux-arm64@12.8.1': + optional: true - /@babel/helper-simple-access/7.20.2: - resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 + '@pnpm/exe.linux-ppc64@12.8.1': + optional: true - /@babel/helper-skip-transparent-expression-wrappers/7.20.0: - resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 + '@pnpm/exe.linux-riscv64@12.8.1': + optional: true + + '@pnpm/exe.linux-s390x@12.8.1': + optional: true + + '@pnpm/exe.linux-x64-musl@12.8.1': + optional: true + + '@pnpm/exe.linux-x64@12.8.1': + optional: true + + '@pnpm/exe.win32-arm64@12.8.1': + optional: true + + '@pnpm/exe.win32-x64@12.8.1': + optional: true + + pnpm@12.8.1: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.8.1 + '@pnpm/exe.android-x64': 12.8.1 + '@pnpm/exe.darwin-arm64': 12.8.1 + '@pnpm/exe.darwin-x64': 12.8.1 + '@pnpm/exe.freebsd-x64': 12.8.1 + '@pnpm/exe.linux-arm64': 12.8.1 + '@pnpm/exe.linux-arm64-musl': 12.8.1 + '@pnpm/exe.linux-ppc64': 12.8.1 + '@pnpm/exe.linux-riscv64': 12.8.1 + '@pnpm/exe.linux-s390x': 12.8.1 + '@pnpm/exe.linux-x64': 12.8.1 + '@pnpm/exe.linux-x64-musl': 12.8.1 + '@pnpm/exe.win32-arm64': 12.8.1 + '@pnpm/exe.win32-x64': 12.8.1 + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +patchedDependencies: + '@suid/styled-engine@0.9.0': ab821ddfe39ccc91ce94f345cc57d6a359089ed194be195fe894d67131a9ac90 + +importers: + + .: + dependencies: + '@solidjs/meta': + specifier: ^0.29.4 + version: 0.29.4(solid-js@1.9.15) + '@solidjs/router': + specifier: ^1.0.0 + version: 1.0.0(solid-js@1.9.15) + '@solidjs/start': + specifier: ^2.0.5 + version: 2.0.5(@solidjs/router@1.0.0(solid-js@1.9.15))(crossws@0.4.12(srvx@1.0.5))(ocache@0.3.0)(vite@8.3.1) + '@suid/material': + specifier: ^0.19.0 + version: 0.19.0(solid-js@1.9.15) + solid-js: + specifier: ^1.9.15 + version: 1.9.15 + devDependencies: + '@biomejs/biome': + specifier: ^2.5.14 + version: 2.5.14 + nitro: + specifier: 3.0.260903-beta + version: 3.0.260903-beta + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vite: + specifier: ^8.3.1 + version: 8.3.1 + +packages: - /@babel/helper-split-export-declaration/7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 - /@babel/helper-string-parser/7.19.4: - resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier/7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-option/7.18.6: - resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} - /@babel/helper-wrap-function/7.20.5: - resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-function-name': 7.19.0 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - transitivePeerDependencies: - - supports-color - /@babel/helpers/7.20.13: - resolution: {integrity: sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - transitivePeerDependencies: - - supports-color - /@babel/highlight/7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + '@babel/helper-module-imports@7.18.6': + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.19.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - /@babel/parser/7.20.13: - resolution: {integrity: sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.20.7 + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==} + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.12 - /@babel/plugin-proposal-async-generator-functions/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.12 - '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.12 - transitivePeerDependencies: - - supports-color - /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - transitivePeerDependencies: - - supports-color - /@babel/plugin-proposal-class-static-block/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.12 - transitivePeerDependencies: - - supports-color - /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.12 - /@babel/plugin-proposal-export-namespace-from/7.18.9_@babel+core@7.20.12: - resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.12 + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} + hasBin: true - /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.12 - /@babel/plugin-proposal-logical-assignment-operators/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.12 - /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.12 - /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.12 - /@babel/plugin-proposal-object-rest-spread/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.20.10 - '@babel/core': 7.20.12 - '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-transform-parameters': 7.20.7_@babel+core@7.20.12 + '@biomejs/biome@2.5.14': + resolution: {integrity: sha512-0FabLIjd4M/dm8VFI86RMaLLdgepzbdfiAL2R8cr7V81OYYrP1w7Z73KfAwPK5h9SrEBXTzNG2y+mBwPo9xRnw==} + engines: {node: '>=14.21.3'} + hasBin: true - /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.12 + '@biomejs/cli-darwin-arm64@2.5.14': + resolution: {integrity: sha512-UnzaXO65L4tsZimFITFP2M121GyhDcWFrT3pL5ZJ5U4XcS/0L5VHYytVohdCf/gnDUFHgl2I9xnt5bV/J1kxHQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] - /@babel/plugin-proposal-optional-chaining/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.12 + '@biomejs/cli-darwin-x64@2.5.14': + resolution: {integrity: sha512-kiy8qA16K93J7uvFfWi4LgjqDpnRKyePAna6A0Y4jxyUga35SYpIZ2cNWlhy0lsfgqRenCpfymnJWEZ6mgpRgA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] - /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - transitivePeerDependencies: - - supports-color + '@biomejs/cli-linux-arm64-musl@2.5.14': + resolution: {integrity: sha512-SJ9PrZkBnnH9dHJDnxk34vKs0GB2dbsioaft6/hPhWJ7AHpwYH83Isnhj0FSRpFkGgpVfJ1lds23ApB2czUDLQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] - /@babel/plugin-proposal-private-property-in-object/7.20.5_@babel+core@7.20.12: - resolution: {integrity: sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.12 - transitivePeerDependencies: - - supports-color + '@biomejs/cli-linux-arm64@2.5.14': + resolution: {integrity: sha512-vO/9BaU1n30CiFNLx49gMTMtbCAAqlo/EqFoG0BU3deQInTbJrmXSmTQ9e3FoDZIKQnvSLDVNL4lx1ci7y1T1Q==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] - /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} - engines: {node: '>=4'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@biomejs/cli-linux-x64-musl@2.5.14': + resolution: {integrity: sha512-2kI5PrMgW5dcEZYrstLPmUmCwkUwZY39rP4BN93Vxbwcs2O57LDQOktUZZYPvvspN5i0mhGr3TJtF5sU26NUWg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] - /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.12: - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@biomejs/cli-linux-x64@2.5.14': + resolution: {integrity: sha512-VHZRa7CCQBUWKxNwUvHJeuW03WFRgN5NWO//SDGOitc9QIeNyfA42V9zlKm+x82xFSG9Bzi5fi+hbhm9anO8yA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] - /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.20.12: - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@biomejs/cli-win32-arm64@2.5.14': + resolution: {integrity: sha512-pHgAFffmZtaYoxavEsWEYNvcA7NwOIjLyqw2HXyLbt16xYryX0L4fMV2u0G9ag6LNYPIoqWaWqzUcnc6rLGGXg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] - /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.20.12: - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@biomejs/cli-win32-x64@2.5.14': + resolution: {integrity: sha512-oJWmBhoHsnhUKIke+0gXDX0mltJrWHA1UyHsrTlXwX0TL64ilVZAo+TYZm97baecV0esBdpzy3k96q+09JaZUQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] - /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.20.12: - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} - /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.20.12: - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} - /@babel/plugin-syntax-import-assertions/7.20.0_@babel+core@7.20.12: - resolution: {integrity: sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.20.12: - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.20.12: - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.20.12: - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} - /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.20.12: - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.20.12: - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + '@napi-rs/wasm-runtime@1.2.4': + resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 - /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.20.12: - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} - /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.20.12: - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.20.12: - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} - /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.20.12: - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@oxc-parser/binding-android-arm-eabi@0.141.0': + resolution: {integrity: sha512-jk7086MFvR/T4DG9IY7MKBVt1PMxvSZoz/TvnifodvS0pjghVwJHRttnAExhlwdMOgHv1TmLdENnbNpYk2zjvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - /@babel/plugin-syntax-typescript/7.20.0_@babel+core@7.20.12: - resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@oxc-parser/binding-android-arm64@0.141.0': + resolution: {integrity: sha512-a4XDQ27ZT7e7zwAlxJDTiCA7IBGWDuy2+MhFq85Of7XlBSmpkfcBFml11q0Zx6f7RMuI0B4xCtt2ytBS4yOptg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] - /@babel/plugin-transform-arrow-functions/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@oxc-parser/binding-darwin-arm64@0.141.0': + resolution: {integrity: sha512-m/kVk6rzYmBeHYnz+1Y5fod00AVTTxMbC71azFfm/zjx1j9XxwKtA0+VfkKuVMC8rbghb9TtfevnuWZa9OuPEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] - /@babel/plugin-transform-async-to-generator/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.12 - transitivePeerDependencies: - - supports-color + '@oxc-parser/binding-darwin-x64@0.141.0': + resolution: {integrity: sha512-o0X+6KZlfucWU/v5oKRQPwdFXsXAjW8jmpo/Gpw/qyKsbKtlfkHoeH9Bjp/m13TwjewvJnCkwF0DWzgpC4HjTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] - /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@oxc-parser/binding-freebsd-x64@0.141.0': + resolution: {integrity: sha512-W5KbTnNkTMMMylqj6dYqnsXvkmESVPodPKYLJ5zdzIPdl9fUJtolkpUeSzYEbGGYB4a4A4avl3EePnZ/wLIdJg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] - /@babel/plugin-transform-block-scoping/7.20.11_@babel+core@7.20.12: - resolution: {integrity: sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@oxc-parser/binding-linux-arm-gnueabihf@0.141.0': + resolution: {integrity: sha512-g3dtbJa8zeOGK36Sr9cQavsdi5H/ie2hVjrSjIxsNAR1qZA40ZYVXnfdfoMAlq8CmB9qFL1yhsSCUHeNmdmt8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - /@babel/plugin-transform-classes/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-replace-supers': 7.20.7 - '@babel/helper-split-export-declaration': 7.18.6 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@oxc-parser/binding-linux-arm-musleabihf@0.141.0': + resolution: {integrity: sha512-e6hwQqd+3lvP13G2jxvFpoA7dzHcFLN+Mq47JCVMtdNHbbyBRo756JCtbbJH6ca8inTfyqZoqBmS3vhQlzAK2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - /@babel/plugin-transform-computed-properties/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/template': 7.20.7 + '@oxc-parser/binding-linux-arm64-gnu@0.141.0': + resolution: {integrity: sha512-vXz2BLAuypA+4MLyBg94pzEo6THVnzYnCtAjXoihIIQo0t2pnp/AmW+SH1EI+4VbuJnC//KplIJ5yyaCGua4jA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] - /@babel/plugin-transform-destructuring/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@oxc-parser/binding-linux-arm64-musl@0.141.0': + resolution: {integrity: sha512-jMkS/EztNW34HKsXIaT/SoHcmtocq/vWhwFOVduF9kduuuRIVwfwQ6uxzIO+qPKSXdd2TXt54of0BJ2zFMXnmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] - /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@oxc-parser/binding-linux-ppc64-gnu@0.141.0': + resolution: {integrity: sha512-vo+MR+n3zQJ6Mq92hiP084NZcgDv5iJlVR02gMf28neMvVT1tKVm7VeiW/DxhdqOi3QLeaXIk9cUcLL1qrkngw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] - /@babel/plugin-transform-duplicate-keys/7.18.9_@babel+core@7.20.12: - resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} - engines: {node: '>=6.9.0'} + '@oxc-parser/binding-linux-riscv64-gnu@0.141.0': + resolution: {integrity: sha512-oh80w+7RuiO5gBp9Jnoa/H8Qlt3JsHL2MkW+0dwEdlDMdslVZX/YsekSK6EeyEenY66/mhCfypsNATQ7Ph3qlQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.141.0': + resolution: {integrity: sha512-LOyEmFA8sCnYbEXP1+iQvCC/P1YXHMA/t6x1Ksp0Y9VwhLFsiBJFzV1zIxrOIE2LKaGGhDjQ29xq9cbq6omDXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.141.0': + resolution: {integrity: sha512-3wnwk/l1CvszVE5TJR1wSl/zSEfydRqrNhn6s7Vr9IzSJpUQIroqVsIoPARHRFA+FQwkxAFDAHDAasa7v8OobQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.141.0': + resolution: {integrity: sha512-qtyQVAAebFq57B2tifTlel3TgGqUtsYNI/e+p6aya9rN9lOZVTDvr215fGYSA9XWooxzMxDiVxkBLk2jQHbsOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.141.0': + resolution: {integrity: sha512-SkGV1nKw40roEc94pv5EaaeH2ay14G6+roe8Q0wIUC1LcEKxzKW921h7+ZuZX0D3q2Mb/7aSFmxEVqnko3lPRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.141.0': + resolution: {integrity: sha512-cVgDM7n8QziQqOaP5hNgUYfMG7S/ZeuPxFWXnnHRv7rh025COk0rfQ6eEdKG3j/GaUuyvNZN4ifF1J8KmuXLLA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.141.0': + resolution: {integrity: sha512-HggH++Fkn3OilBn+bs3jpgIFQa34oMAyUUHy0vpGum+gt1Eb5nyLc8dNU/RAPSw6lsLrx7ncKtHSZE+3Sp0l2g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.141.0': + resolution: {integrity: sha512-KLSEH9GwgbrqbJOjtGHt9STw96s+78yDzp7IDN8Lno+7Ut9sNBfZ4jYZIz4mD50qmWUjoOI7i9I6UENbhNbMZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.141.0': + resolution: {integrity: sha512-9UVWUOOCI/1YkiSSNjg2zyBJYM9E/t1A/8GNobd48JDn/fQ6mzxcVO3H08jb3rAaW/B1VBf8eCORTvSsO9T08g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.141.0': + resolution: {integrity: sha512-HI/wsvbWT5RHHw5c37D0fEgeTd8/1Q4OJs5jUmEBc17VZFG6SsCIe4barq7NsAPPks/JW+3ayi3Rp+PQI5h4Kg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.141.0': + resolution: {integrity: sha512-S4as7z0j0xQkXcJlyY5ehntwK8/wRkQb9Cyqw+J/N2rkWGQGK0SxD6X6DhQTc7qsxVTBxXbxZtBJh3mr3PtIzQ==} + + '@oxc-project/types@0.151.0': + resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@rolldown/binding-android-arm-eabi@1.2.11': + resolution: {integrity: sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.11': + resolution: {integrity: sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.11': + resolution: {integrity: sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.11': + resolution: {integrity: sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.11': + resolution: {integrity: sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + resolution: {integrity: sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.11': + resolution: {integrity: sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.11': + resolution: {integrity: sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + resolution: {integrity: sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.11': + resolution: {integrity: sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.11': + resolution: {integrity: sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.11': + resolution: {integrity: sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.11': + resolution: {integrity: sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.11': + resolution: {integrity: sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.11': + resolution: {integrity: sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@shikijs/core@4.4.3': + resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.4.3': + resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.4.3': + resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} + engines: {node: '>=20'} + + '@shikijs/langs@4.4.3': + resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.4.3': + resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} + engines: {node: '>=20'} + + '@shikijs/themes@4.4.3': + resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} + engines: {node: '>=20'} + + '@shikijs/types@4.4.3': + resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@solidjs/meta@0.29.4': + resolution: {integrity: sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + solid-js: '>=1.8.4' - /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} - engines: {node: '>=6.9.0'} + '@solidjs/router@1.0.0': + resolution: {integrity: sha512-cCSk1hvgCowiMa9bzzYWHiLu1U4E22+DfJe6/rOwAyECKrxc3jrd5QnoW3sDDJtW+e077cz/M67bPl3DqOBw1Q==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 - '@babel/helper-plugin-utils': 7.20.2 + solid-js: ^1.8.6 - /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.20.12: - resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==} - engines: {node: '>=6.9.0'} + '@solidjs/start@2.0.5': + resolution: {integrity: sha512-Kji79Kan867rKAx8iG0W4BXzj/J2nXyGwgS6+mNA4vqLabqLMeXfY/O910JnMLHRKMZZ9Xfr4hh+DYUDuSSPCw==} + engines: {node: '>=24'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@solidjs/router': '>=0.16.0 <2.0.0-0' + vite: ^8 || ^9 + peerDependenciesMeta: + '@solidjs/router': + optional: true - /@babel/plugin-transform-function-name/7.18.9_@babel+core@7.20.12: - resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} - engines: {node: '>=6.9.0'} + '@suid/base@0.11.0': + resolution: {integrity: sha512-jNe+LlXuxfkSZo8/MP9koqYYWswucDWSCwc7ViqUhQ0Y/V7sP2RiQ/Bnms+ePSMBZsk5k1b9fAjvj7DtNbbHXw==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-plugin-utils': 7.20.2 + solid-js: ^1.9.7 - /@babel/plugin-transform-literals/7.18.9_@babel+core@7.20.12: - resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} - engines: {node: '>=6.9.0'} + '@suid/css@0.4.1': + resolution: {integrity: sha512-Hsi4O3dBOm7rrlqKoWfNoTeRFAXm/7TPaeEmyxNx+wFaT3eROjMVdhadAIiagFT+PsHrq/6fDauUI5TkL+5Zvg==} + + '@suid/material@0.19.0': + resolution: {integrity: sha512-vfudxYpHdur5CWTjd3eBb7q1b6A9X/pDWTEf2twc0gXVTcErS9VtY/VPBLa65AzO2SPJsdjAE+BCdVZiXASBbA==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + solid-js: ^1.9.7 - /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} - engines: {node: '>=6.9.0'} + '@suid/styled-engine@0.9.0': + resolution: {integrity: sha512-IfNHjQ3Im63mFIjFl/doiwdn5qbwgcwi/vUXnX7dmIUC/Cw1f3LPhzVT9V8Z3eqyvvFToy53O+BsuLy2e/WmDw==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + solid-js: ^1.9.7 - /@babel/plugin-transform-modules-amd/7.20.11_@babel+core@7.20.12: - resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==} - engines: {node: '>=6.9.0'} + '@suid/system@0.14.0': + resolution: {integrity: sha512-aRVilPP53hHkqyAyQp2pasT/u8aQCcELwU4kFDnt3b+rj4fsPQRlhMumlX5mZ5aijIboH1CngU6TDG6Z9Mr3UA==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-module-transforms': 7.20.11 - '@babel/helper-plugin-utils': 7.20.2 - transitivePeerDependencies: - - supports-color + solid-js: ^1.9.7 - /@babel/plugin-transform-modules-commonjs/7.20.11_@babel+core@7.20.12: - resolution: {integrity: sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==} - engines: {node: '>=6.9.0'} + '@suid/types@0.8.0': + resolution: {integrity: sha512-/Z2abkbypMjF6ygSpnjqnWohcmPqvgw8Xpx1wPPHeh+LajBP2imNT6uEa5dBqNEkJY8O3wEUCVqErAad/rmn5Q==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-module-transforms': 7.20.11 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-simple-access': 7.20.2 - transitivePeerDependencies: - - supports-color + solid-js: ^1.9.7 - /@babel/plugin-transform-modules-systemjs/7.20.11_@babel+core@7.20.12: - resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==} - engines: {node: '>=6.9.0'} + '@suid/utils@0.11.0': + resolution: {integrity: sha512-dk+6YJkex9kcU2qQHCOk8J0/zkOKKbng0SsjC0LBLyBrf2OC3OtDQq7o22pH3m/8CU/0M6uyM7tnyzZA4eWF3Q==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-module-transforms': 7.20.11 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-validator-identifier': 7.19.1 - transitivePeerDependencies: - - supports-color + solid-js: ^1.9.7 - /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==} - engines: {node: '>=6.9.0'} + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/braces@3.0.5': + resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/micromatch@4.0.10': + resolution: {integrity: sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + babel-plugin-jsx-dom-expressions@0.40.10: + resolution: {integrity: sha512-lxve6Y02YiZTldB7efKpnbf1BH00XCFZNYYW235jSGsYaJNFtHrYlKV6/O+miHbjqpIr9FTe5+0no4hofAMbfA==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-module-transforms': 7.20.11 - '@babel/helper-plugin-utils': 7.20.2 - transitivePeerDependencies: - - supports-color + '@babel/core': ^7.20.12 - /@babel/plugin-transform-named-capturing-groups-regex/7.20.5_@babel+core@7.20.12: - resolution: {integrity: sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==} - engines: {node: '>=6.9.0'} + babel-preset-solid@1.9.15: + resolution: {integrity: sha512-GBmg1OiPb+OwcH51XbDAKPtvrPfQW7rCJTJxcp8+yhtWwN+kqnbEJk2SgVybd+uhTxTKAvjaFyiQSr/eUZBwzg==} peerDependencies: '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + solid-js: ^1.9.15 + peerDependenciesMeta: + solid-js: + optional: true - /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + baseline-browser-mapping@2.11.26: + resolution: {integrity: sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==} + engines: {node: '>=6.0.0'} + hasBin: true - /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-replace-supers': 7.20.7 - transitivePeerDependencies: - - supports-color + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} - /@babel/plugin-transform-parameters/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + browserslist@4.29.2: + resolution: {integrity: sha512-/u9r8k8ue4ZhHCw9ovDtltpWdXeXzjhdxCGj6vm1RGLPXb8lu33EMdoCHF5Sx8oYBS+Q/FYYNmAqlHncxR3RmA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true - /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + caniuse-lite@1.0.30001813: + resolution: {integrity: sha512-zfjJo4rM0+fUomGDBW/xcDjhIwz/210DGvip2MAMDZ8KHcRPnOHmEgHPZP0UHlZoxr8fYKVJOqcORhYQcG4FKQ==} - /@babel/plugin-transform-regenerator/7.20.5_@babel+core@7.20.12: - resolution: {integrity: sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - regenerator-transform: 0.15.1 + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - /@babel/plugin-transform-spread/7.20.7_@babel+core@7.20.12: - resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} - /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - /@babel/plugin-transform-template-literals/7.18.9_@babel+core@7.20.12: - resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} - /@babel/plugin-transform-typeof-symbol/7.18.9_@babel+core@7.20.12: - resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - /@babel/plugin-transform-typescript/7.20.13_@babel+core@7.20.12: - resolution: {integrity: sha512-O7I/THxarGcDZxkgWKMUrk7NK1/WbHAg3Xx86gqS6x9MTrNL6AwIluuZ96ms4xeDe6AVx6rjHbWHP7x26EPQBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.20.12 - transitivePeerDependencies: - - supports-color + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - /@babel/plugin-transform-unicode-escapes/7.18.10_@babel+core@7.20.12: - resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==} - engines: {node: '>=6.9.0'} + crossws@0.4.12: + resolution: {integrity: sha512-aypfsr6t0uNvkqaZc6zvBfXzC6pLI0/sIulpkV6RwCVtZqG5ebBzv4weImKK0VNCj91Wl9F5j7p5WU4MNrybng==} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + srvx: '>=0.11.5' + peerDependenciesMeta: + srvx: + optional: true - /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - /@babel/preset-env/7.20.2_@babel+core@7.20.12: - resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.20.10 - '@babel/core': 7.20.12 - '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-validator-option': 7.18.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-proposal-async-generator-functions': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-proposal-class-static-block': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-proposal-export-namespace-from': 7.18.9_@babel+core@7.20.12 - '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-proposal-logical-assignment-operators': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-proposal-object-rest-spread': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-proposal-private-property-in-object': 7.20.5_@babel+core@7.20.12 - '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.12 - '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.20.12 - '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.12 - '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-syntax-import-assertions': 7.20.0_@babel+core@7.20.12 - '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.12 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.12 - '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.12 - '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.12 - '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.20.12 - '@babel/plugin-transform-arrow-functions': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-transform-async-to-generator': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-block-scoping': 7.20.11_@babel+core@7.20.12 - '@babel/plugin-transform-classes': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-transform-computed-properties': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-transform-destructuring': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-duplicate-keys': 7.18.9_@babel+core@7.20.12 - '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.20.12 - '@babel/plugin-transform-function-name': 7.18.9_@babel+core@7.20.12 - '@babel/plugin-transform-literals': 7.18.9_@babel+core@7.20.12 - '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-modules-amd': 7.20.11_@babel+core@7.20.12 - '@babel/plugin-transform-modules-commonjs': 7.20.11_@babel+core@7.20.12 - '@babel/plugin-transform-modules-systemjs': 7.20.11_@babel+core@7.20.12 - '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5_@babel+core@7.20.12 - '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-parameters': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-regenerator': 7.20.5_@babel+core@7.20.12 - '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-spread': 7.20.7_@babel+core@7.20.12 - '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.20.12 - '@babel/plugin-transform-typeof-symbol': 7.18.9_@babel+core@7.20.12 - '@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.20.12 - '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.20.12 - '@babel/preset-modules': 0.1.5_@babel+core@7.20.12 - '@babel/types': 7.20.7 - babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.20.12 - babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.20.12 - babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.20.12 - core-js-compat: 3.27.2 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color + db0@0.4.1: + resolution: {integrity: sha512-6RBY/bSn42UrqATwsiULj2uYFyEykB3XeA/NLIVQeHNXlYV6D4Idxx3/aa6k5Y45Dwzx7sygeLotnqHWSeURYA==} - /@babel/preset-modules/0.1.5_@babel+core@7.20.12: - resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.12 - '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.12 - '@babel/types': 7.20.7 - esutils: 2.0.3 + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - /@babel/preset-typescript/7.18.6_@babel+core@7.20.12: - resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==} - engines: {node: '>=6.9.0'} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + electron-to-chromium@1.5.439: + resolution: {integrity: sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + env-runner@0.2.3: + resolution: {integrity: sha512-S8EFBKyznRwPVwayK187cYjvnpgC92xy+q0BIF++V965pfjMVVRbQ/HoTYGA/1EHmZ7E68k6Heq+4WnvVhOL5w==} + hasBin: true + + error-stack-parser-es@2.0.1: + resolution: {integrity: sha512-J36ntO+rMQVRuR/umlmxmfLi4TpWwtmTnHoJoXTiC2xDNazs0VDPKcX6pbhZMDd2HFtH9isMBJXMdbki+A++Pg==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-validator-option': 7.18.6 - '@babel/plugin-transform-typescript': 7.20.13_@babel+core@7.20.12 - transitivePeerDependencies: - - supports-color + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true - /@babel/runtime/7.20.13: - resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.11 + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} - /@babel/template/7.20.7: - resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/parser': 7.20.13 - '@babel/types': 7.20.7 + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] - /@babel/traverse/7.20.13: - resolution: {integrity: sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.20.7 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.20.13 - '@babel/types': 7.20.7 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - /@babel/types/7.20.7: - resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.19.4 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} - /@cloudflare/kv-asset-handler/0.2.0: - resolution: {integrity: sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==} - dependencies: - mime: 3.0.0 - dev: true + h3@2.0.1-rc.32: + resolution: {integrity: sha512-Epg1E85L8yIKd9UeAQAVdm6QVrnHbppMsQ641EjpNi4c0IlAg+PJlwp4Xw03Z1loRXPQaH9k64gJT5qDELqm+g==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.12 + ocache: '>=0.3.0' + peerDependenciesMeta: + crossws: + optional: true + ocache: + optional: true - /@cloudflare/workers-types/3.19.0: - resolution: {integrity: sha512-0FRcsz7Ea3jT+gc5gKPIYciykm1bbAaTpygdzpCwGt0RL+V83zWnYN30NWDW4rIHj/FHtz+MIuBKS61C8l7AzQ==} - dev: true + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} - /@esbuild/android-arm/0.15.18: - resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - /@esbuild/linux-loong64/0.14.54: - resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - optional: true + hookable@6.1.2: + resolution: {integrity: sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==} - /@esbuild/linux-loong64/0.15.18: - resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - optional: true + html-entities@2.3.3: + resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} - /@hapi/hoek/9.3.0: - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + html-to-image@1.11.13: + resolution: {integrity: sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==} - /@hapi/topo/5.1.0: - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - dependencies: - '@hapi/hoek': 9.3.0 + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + httpxy@0.5.5: + resolution: {integrity: sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - /@iarna/toml/2.2.5: - resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} - dev: true + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} - /@jridgewell/gen-mapping/0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - /@jridgewell/gen-mapping/0.3.2: - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.17 + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true - /@jridgewell/resolve-uri/3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true - /@jridgewell/set-array/1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] - /@jridgewell/source-map/0.3.2: - resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} - dependencies: - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.17 - - /@jridgewell/sourcemap-codec/1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - - /@jridgewell/trace-mapping/0.3.17: - resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - - /@miniflare/cache/2.11.0: - resolution: {integrity: sha512-L/kc9AzidPwFuk2fwHpAEePi0kNBk6FWUq3ln+9beRCDrPEpfVrDRFpNleF1NFZz5//oeVMuo8F0IVUQGzR7+Q==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/core': 2.11.0 - '@miniflare/shared': 2.11.0 - http-cache-semantics: 4.1.0 - undici: 5.9.1 - dev: true - - /@miniflare/cli-parser/2.11.0: - resolution: {integrity: sha512-JUmyRzEGAS6CouvXJwBh8p44onfw3KRpfq5JGXEuHModOGjTp6li7PQyCTNPV2Hv/7StAXWnTFGXeAqyDHuTig==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/shared': 2.11.0 - kleur: 4.1.5 - dev: true - - /@miniflare/core/2.11.0: - resolution: {integrity: sha512-UFMFiCG0co36VpZkgFrSBnrxo71uf1x+cjlzzJi3khmMyDlnLu4RuIQsAqvKbYom6fi3G9Q8lTgM7JuOXFyjhw==} - engines: {node: '>=16.13'} - dependencies: - '@iarna/toml': 2.2.5 - '@miniflare/queues': 2.11.0 - '@miniflare/shared': 2.11.0 - '@miniflare/watcher': 2.11.0 - busboy: 1.6.0 - dotenv: 10.0.0 - kleur: 4.1.5 - set-cookie-parser: 2.5.1 - undici: 5.9.1 - urlpattern-polyfill: 4.0.3 - dev: true - - /@miniflare/d1/2.11.0: - resolution: {integrity: sha512-aDdBVQZ2C0Zs3+Y9ZbRctmuQxozPfpumwJ/6NG6fBadANvune/hW7ddEoxyteIEU9W3IgzVj8s4by4VvasX90A==} - engines: {node: '>=16.7'} - dependencies: - '@miniflare/core': 2.11.0 - '@miniflare/shared': 2.11.0 - dev: true - - /@miniflare/durable-objects/2.11.0: - resolution: {integrity: sha512-0cKJaMgraTEU1b4kqK8cjD2oTeOjA6QU3Y+lWiZT/k1PMHZULovrSFnjii7qZ8npf4VHSIN6XYPxhyxRyEM65Q==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/core': 2.11.0 - '@miniflare/shared': 2.11.0 - '@miniflare/storage-memory': 2.11.0 - undici: 5.9.1 - dev: true - - /@miniflare/html-rewriter/2.11.0: - resolution: {integrity: sha512-olTqmuYTHnoTNtiA0vjQ/ixRfbwgPzDrAUbtXDCYW45VFbHfDVJrJGZX3Jg0HpSlxy86Zclle1SUxGbVDzxsBg==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/core': 2.11.0 - '@miniflare/shared': 2.11.0 - html-rewriter-wasm: 0.4.1 - undici: 5.9.1 - dev: true - - /@miniflare/http-server/2.11.0: - resolution: {integrity: sha512-sMLcrDFzqqAvnQmAUH0hRTo8sBjW79VZYfnIH5FAGSGcKX6kdAGs9RStdYZ4CftQCBAEQScX0KBsMx5FwJRe9Q==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/core': 2.11.0 - '@miniflare/shared': 2.11.0 - '@miniflare/web-sockets': 2.11.0 - kleur: 4.1.5 - selfsigned: 2.1.1 - undici: 5.9.1 - ws: 8.12.0 - youch: 2.2.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: true + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - /@miniflare/kv/2.11.0: - resolution: {integrity: sha512-3m9dL2HBBN170V1JvwjjucR5zl4G3mlcsV6C1E7A2wLl2Z2TWvIx/tSY9hrhkD96dFnejwJ9qmPMbXMMuynhjg==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/shared': 2.11.0 - dev: true + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - /@miniflare/queues/2.11.0: - resolution: {integrity: sha512-fLHjdrNLKhn0LZM/aii/9GsAttFd+lWlGzK8HOg1R0vhfKBwEub4zntjMmOfFbDm1ntc21tdMK7n3ldUphwh5w==} - engines: {node: '>=16.7'} - dependencies: - '@miniflare/shared': 2.11.0 - dev: true + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] - /@miniflare/r2/2.11.0: - resolution: {integrity: sha512-MKuyJ/gGNsK3eWbGdygvozqcyaZhM3C6NGHvoaZwH503dwN569j5DpatTWiHGFeDeSu64VqcIsGehz05GDUaag==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/shared': 2.11.0 - undici: 5.9.1 - dev: true + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] - /@miniflare/runner-vm/2.11.0: - resolution: {integrity: sha512-bkVSuvCf5+VylqN8lTiLxIYqYcKFbl+BywZGwGQndPC/3wh42J00mM0jw4hRbvXgwuBhlUyCVpEXtYlftFFT/g==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/shared': 2.11.0 - dev: true + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] - /@miniflare/scheduler/2.11.0: - resolution: {integrity: sha512-DPdzINhdWeS99eIicGoluMsD4pLTTAWNQbgCv3CTwgdKA3dxdvMSCkNqZzQLiALzvk9+rSfj46FlH++HE7o7/w==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/core': 2.11.0 - '@miniflare/shared': 2.11.0 - cron-schedule: 3.0.6 - dev: true + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] - /@miniflare/shared/2.11.0: - resolution: {integrity: sha512-fWMqq3ZkWAg+k7CnyzMV/rZHugwn+/JxvVzCxrtvxzwotTN547THlOxgZe8JAP23U9BiTxOfpTfnLvFEjAmegw==} - engines: {node: '>=16.13'} - dependencies: - '@types/better-sqlite3': 7.6.3 - kleur: 4.1.5 - npx-import: 1.1.4 - picomatch: 2.3.1 - dev: true + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] - /@miniflare/sites/2.11.0: - resolution: {integrity: sha512-qbefKdWZUJgsdLf+kCw03sn3h/92LZgJAbkOpP6bCrfWkXlJ37EQXO4KWdhn4Ghc7A6GwU1s1I/mdB64B3AewQ==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/kv': 2.11.0 - '@miniflare/shared': 2.11.0 - '@miniflare/storage-file': 2.11.0 - dev: true + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] - /@miniflare/storage-file/2.11.0: - resolution: {integrity: sha512-beWF/lTX74x7AiaSB+xQxywPSNdhtEKvqDkRui8eOJ5kqN2o4UaleLKQGgqmCw3WyHRIsckV7If1qpbNiLtWMw==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/shared': 2.11.0 - '@miniflare/storage-memory': 2.11.0 - dev: true + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] - /@miniflare/storage-memory/2.11.0: - resolution: {integrity: sha512-s0AhPww7fq/Jz80NbPb+ffhcVRKnfPi7H1dHTRTre2Ud23EVJjAWl2gat42x8NOT/Fu3/o/7A72DWQQJqfO98A==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/shared': 2.11.0 - dev: true + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] - /@miniflare/watcher/2.11.0: - resolution: {integrity: sha512-RUfjz2iYcsQXLcGySemJl98CJ2iierbWsPGWZhIVZI+NNhROkEy77g/Q+lvP2ATwexG3/dUSfdJ3P8aH+sI4Ig==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/shared': 2.11.0 - dev: true + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} - /@miniflare/web-sockets/2.11.0: - resolution: {integrity: sha512-NC8RKrmxrO0hZmwpzn5g4hPGA2VblnFTIBobmWoxuK95eW49zfs7dtE/PyFs+blsGv3CjTIjHVSQ782K+C6HFA==} - engines: {node: '>=16.13'} - dependencies: - '@miniflare/core': 2.11.0 - '@miniflare/shared': 2.11.0 - undici: 5.9.1 - ws: 8.12.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: true + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - /@nodelib/fs.scandir/2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - /@nodelib/fs.stat/2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + merge-anything@5.1.7: + resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} + engines: {node: '>=12.13'} - /@nodelib/fs.walk/1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - /@polka/url/1.0.0-next.21: - resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - /@popperjs/core/2.11.6: - resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} - dev: false + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - /@rollup/plugin-commonjs/24.0.1_rollup@3.10.1: - resolution: {integrity: sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.68.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@rollup/pluginutils': 5.0.2_rollup@3.10.1 - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 8.1.0 - is-reference: 1.2.1 - magic-string: 0.27.0 - rollup: 3.10.1 - dev: true - - /@rollup/plugin-json/6.0.0_rollup@3.10.1: - resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@rollup/pluginutils': 5.0.2_rollup@3.10.1 - rollup: 3.10.1 - dev: true + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - /@rollup/plugin-node-resolve/15.0.1_rollup@3.10.1: - resolution: {integrity: sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@rollup/pluginutils': 5.0.2_rollup@3.10.1 - '@types/resolve': 1.20.2 - deepmerge: 4.2.2 - is-builtin-module: 3.2.0 - is-module: 1.0.0 - resolve: 1.22.1 - rollup: 3.10.1 - dev: true - - /@rollup/pluginutils/5.0.2_rollup@3.10.1: - resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.0 - estree-walker: 2.0.2 - picomatch: 2.3.1 - rollup: 3.10.1 + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - /@sideway/address/4.1.4: - resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} - dependencies: - '@hapi/hoek': 9.3.0 + micromark-util-types@2.0.3: + resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} - /@sideway/formula/3.0.1: - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} - /@sideway/pinpoint/2.0.0: - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /@solidjs/meta/0.28.2_solid-js@1.6.9: - resolution: {integrity: sha512-avlLgBPdk4KVxzRGFlYp/MIJo8B5jVgXPgk6OUnUP8km21Z+ovO+DUd7ZPA7ejv8PBdWi9GE3zCzw8RU2YuV2Q==} - peerDependencies: - solid-js: '>=1.4.0' - dependencies: - solid-js: 1.6.9 + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true - /@solidjs/router/0.7.0_solid-js@1.6.9: - resolution: {integrity: sha512-8HI84twe5FjYRebSLMAhtkL9bRuTDIlxJK56kjfjU9WKGoUCTaWpCnkuj8Hqde1bWZ0X+GOZxKDfNkn1CjtjxA==} - peerDependencies: - solid-js: ^1.5.3 - dependencies: - solid-js: 1.6.9 + nf3@0.3.24: + resolution: {integrity: sha512-HxLK4bo+5jNsEETZp4w3tJblHOA9MCBY14IN9nZJJV8JDxt9yNIYxuBuLMjTAR5GFa3HL61+8VQDUrXv3/C8fw==} - /@suid/base/0.5.1_solid-js@1.6.9: - resolution: {integrity: sha512-0IbaD/KoJUuT4F6EsKDB1dMX0gAZAGzyUz52RyXRTbEv/e2mgK+00wYRRgxaFcjtfHFutFTkIQ4c0EU+GRECWg==} - peerDependencies: - solid-js: ^1.6.2 - dependencies: - '@popperjs/core': 2.11.6 - '@suid/css': 0.1.7_solid-js@1.6.9 - '@suid/system': 0.7.1_solid-js@1.6.9 - '@suid/types': 0.3.0_solid-js@1.6.9 - '@suid/utils': 0.6.0_solid-js@1.6.9 - clsx: 1.2.1 - solid-js: 1.6.9 - dev: false - - /@suid/base/0.6.2_solid-js@1.6.9: - resolution: {integrity: sha512-ZDKWGLdSAVrIuEo0DvrIqWJ6Ngyrt0aB17CX1oY2YscXipgQmpND/0HAplifoNa2bbmLUYBRUSVtBcs/XoSYXA==} - peerDependencies: - solid-js: ^1.6.7 - dependencies: - '@popperjs/core': 2.11.6 - '@suid/css': 0.2.1 - '@suid/system': 0.8.2_solid-js@1.6.9 - '@suid/types': 0.4.0_solid-js@1.6.9 - '@suid/utils': 0.6.1_solid-js@1.6.9 - clsx: 1.2.1 - solid-js: 1.6.9 - dev: false + nitro@3.0.260903-beta: + resolution: {integrity: sha512-54gANPi62O8rfMvepiJUVuIzEIinYfpeHbebywlLxvVTgIYXDwSvpaU9Id+0sJOBjBx0wC1/CVYXJkH7SY+l0g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true - /@suid/css/0.1.7_solid-js@1.6.9: - resolution: {integrity: sha512-SBRGHwBndin52tgLAyVlu0cNcDeBJVYA0E4cfo7TO0Jmicmn3xgWXG8Ie7kSDnS8tSf9IZuVnXHaq9deZfdvdA==} - dependencies: - '@suid/utils': 0.6.0_solid-js@1.6.9 - transitivePeerDependencies: - - solid-js - dev: false + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} + engines: {node: '>=18'} - /@suid/css/0.2.1: - resolution: {integrity: sha512-ictV73loNG7BH9ID5PRGnuMryrAHOfyspaCZsQUzz5cTeLttFYgD/QKUcWr4FEG/1O7GVzgJlqtIT1r55LbCtw==} - dev: false + ocache@0.3.0: + resolution: {integrity: sha512-RS/9P0zeBb0gDJmadGERLH8lZNXK7xbufTDhclkXGvFGTsj0G4M5/cLk+mizcERH29mLXNnocfB5wjcK70wGJg==} - /@suid/icons-material/0.5.6_solid-js@1.6.9: - resolution: {integrity: sha512-nmcR7eNrIMbb7i7LfOqDKLzMfCgRkZQA8Sm7QLWdU0e2Tjjd8XiOoiZL8wcnfbWUWkVANjQbNPlxJ7ewpum2ZQ==} - peerDependencies: - solid-js: ^1.6.7 - dependencies: - '@suid/material': 0.9.2_solid-js@1.6.9 - solid-js: 1.6.9 - dev: false + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} - /@suid/material/0.8.2_solid-js@1.6.9: - resolution: {integrity: sha512-4gA23zlINdC7Wqe77tD4HeERmaL+I0n9JZdTV0RodU7WTfuxFhTxRroR3aSH+6J3yhwHhsYBla2QNmX4nfTbpg==} - peerDependencies: - solid-js: ^1.6.2 - dependencies: - '@suid/base': 0.5.1_solid-js@1.6.9 - '@suid/css': 0.1.7_solid-js@1.6.9 - '@suid/system': 0.7.1_solid-js@1.6.9 - '@suid/types': 0.3.0_solid-js@1.6.9 - '@suid/utils': 0.6.0_solid-js@1.6.9 - clsx: 1.2.1 - solid-js: 1.6.9 - dev: false - - /@suid/material/0.9.2_solid-js@1.6.9: - resolution: {integrity: sha512-5gtv6gUuBFXl2syBD0u/Aa7ye4NNzmv2fs7LrnR+ph/Cp1C8jtBGZThx/Xwfo22igfBwyR/yXdFJ/5ITmmyDdg==} - peerDependencies: - solid-js: ^1.6.7 - dependencies: - '@suid/base': 0.6.2_solid-js@1.6.9 - '@suid/css': 0.2.1 - '@suid/system': 0.8.2_solid-js@1.6.9 - '@suid/types': 0.4.0_solid-js@1.6.9 - '@suid/utils': 0.6.1_solid-js@1.6.9 - clsx: 1.2.1 - solid-js: 1.6.9 - dev: false - - /@suid/styled-engine/0.3.0_solid-js@1.6.9: - resolution: {integrity: sha512-TGpmRizJzDoBHcKPR5MUmK5v3Jkfx4mkVGxskEeGXUoMHSIkDWiNstEmYlLFOKqT2Ku2t0lJ8fOS4K777+dUiA==} - peerDependencies: - solid-js: ^1.6.2 - dependencies: - '@suid/css': 0.1.7_solid-js@1.6.9 - '@suid/utils': 0.6.0_solid-js@1.6.9 - solid-js: 1.6.9 - dev: false + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - /@suid/styled-engine/0.4.1_solid-js@1.6.9: - resolution: {integrity: sha512-TzY/2zuspzVnx8q5PG04FgNzkJlESLZwxMYQK4VkIuqTm4CrC8VNMunBBUdv+4DVpQKArtO1ZnTGNNtE34rD8Q==} - peerDependencies: - solid-js: ^1.6.7 - dependencies: - '@suid/css': 0.2.1 - '@suid/utils': 0.6.1_solid-js@1.6.9 - solid-js: 1.6.9 - dev: false + oxc-parser@0.141.0: + resolution: {integrity: sha512-uFkGGr1KMWd6aWv9UAqooYrN78trw8MWWmoPvgWokfBEUq1+eiIQ+qfj3wokhy0fxtZWZk+0dHoS7/yRTJtd6w==} + engines: {node: ^20.19.0 || >=22.12.0} - /@suid/system/0.7.1_solid-js@1.6.9: - resolution: {integrity: sha512-ltWJULVcbLLTmEWdoy33d5Aa4C37V6nk0d0YoqvGJc8ujZ4SDR5hrszWixQG5i/j76+s++ep+9Z9W1xuw/IlTA==} - peerDependencies: - solid-js: ^1.6.2 - dependencies: - '@suid/css': 0.1.7_solid-js@1.6.9 - '@suid/styled-engine': 0.3.0_solid-js@1.6.9 - '@suid/types': 0.3.0_solid-js@1.6.9 - '@suid/utils': 0.6.0_solid-js@1.6.9 - clsx: 1.2.1 - csstype: 3.1.1 - solid-js: 1.6.9 - dev: false - - /@suid/system/0.8.2_solid-js@1.6.9: - resolution: {integrity: sha512-ElvntlETvPjOPzg5VFQjk0dUomx4ffXF1GI5w16okdyFNSYTtdlK2PXafGoO3MTRI3tXrso8efODWPpKuH0iXw==} - peerDependencies: - solid-js: ^1.6.7 - dependencies: - '@suid/css': 0.2.1 - '@suid/styled-engine': 0.4.1_solid-js@1.6.9 - '@suid/types': 0.4.0_solid-js@1.6.9 - '@suid/utils': 0.6.1_solid-js@1.6.9 - clsx: 1.2.1 - csstype: 3.1.1 - solid-js: 1.6.9 - dev: false - - /@suid/types/0.3.0_solid-js@1.6.9: - resolution: {integrity: sha512-MrfSnnI8oyPy+TtSIG6qVZyqVN7GMH49uRbw8ACLEek2T1keG2NtLNgNWhiWv51bP0CwBAalwt+QGcN4b/3OSw==} - peerDependencies: - solid-js: ^1.6.2 - dependencies: - solid-js: 1.6.9 - dev: false + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - /@suid/types/0.4.0_solid-js@1.6.9: - resolution: {integrity: sha512-rafpWrES+G8peLp1L7rm6QHGd+yhMV01jIzFDTzLIYgv8cQk4AsoKfgzZVfPysptF24owD9491Dv4BAzQY+XFA==} - peerDependencies: - solid-js: ^1.6.7 - dependencies: - solid-js: 1.6.9 - dev: false + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - /@suid/utils/0.6.0_solid-js@1.6.9: - resolution: {integrity: sha512-1s5mRYbYCFzzo/t1tCvP72IizDNMjAIsV7SsBHqAQRayfQIlAaQv9D2HAQ0QW2WxHIQTsi4JLPZI/0MdiU12Vw==} - peerDependencies: - solid-js: ^1.6.2 - dependencies: - '@suid/types': 0.3.0_solid-js@1.6.9 - solid-js: 1.6.9 - dev: false + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - /@suid/utils/0.6.1_solid-js@1.6.9: - resolution: {integrity: sha512-Fg6oblOZIjZYnbF/kigE3GcqfVxN2kpmaVPpAeqTDvHNeHWzDI/rEWEXZI4nJ5WzToXDYOLHhYiQYWPDDsXPHA==} - peerDependencies: - solid-js: ^1.6.7 - dependencies: - '@suid/types': 0.4.0_solid-js@1.6.9 - solid-js: 1.6.9 - dev: false + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - /@suid/vite-plugin/0.1.1_vite@3.2.5: - resolution: {integrity: sha512-nyFElkDO4cM1MmBV+cwSQVGycWAZOSkPv2wp2XzTxCBOGbwMTNuzUYaab3pkT1izACzwiDyVRh7+OWMrNudrEg==} - peerDependencies: - vite: ^3.0.0 - dependencies: - '@babel/generator': 7.20.7 - '@babel/parser': 7.20.13 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - '@types/babel__generator': 7.6.4 - '@types/babel__traverse': 7.18.3 - vite: 3.2.5 - transitivePeerDependencies: - - supports-color - dev: true + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} - /@types/babel__generator/7.6.4: - resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} - dependencies: - '@babel/types': 7.20.7 - dev: true + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} - /@types/babel__traverse/7.18.3: - resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} - dependencies: - '@babel/types': 7.20.7 - dev: true + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} - /@types/better-sqlite3/7.6.3: - resolution: {integrity: sha512-YS64N9SNDT/NAvou3QNdzAu3E2om/W/0dhORimtPGLef+zSK5l1vDzfsWb4xgXOgfhtOI5ZDTRxnvRPb22AIVQ==} - dependencies: - '@types/node': 18.11.18 - dev: true + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} - /@types/cookie/0.5.1: - resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - /@types/estree/1.0.0: - resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - /@types/node/18.11.18: - resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} - dev: true + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} - /@types/resolve/1.20.2: - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - dev: true + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - /@types/stack-trace/0.0.29: - resolution: {integrity: sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==} - dev: true + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - /@types/ws/8.5.4: - resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} - dependencies: - '@types/node': 18.11.18 - dev: true + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - /accepts/1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 + rolldown@1.2.11: + resolution: {integrity: sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true - /acorn-node/1.8.2: - resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} - dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 - xtend: 4.0.2 - dev: true + rou3@0.9.2: + resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==} - /acorn-walk/7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} - dev: true + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - /acorn/7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - dev: true - /acorn/8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} - engines: {node: '>=0.4.0'} - hasBin: true + seroval-plugins@1.5.6: + resolution: {integrity: sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 - /ansi-regex/5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + seroval-plugins@1.6.7: + resolution: {integrity: sha512-4Nk35ttD3DTDJW4hgw5StsVAPeU6qnDFnULAouw6tQ7oLTV/ICXrWpsXo2EE52eSP2joUMazbVf52mFEcADqRw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 - /ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 + seroval@1.5.6: + resolution: {integrity: sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA==} + engines: {node: '>=10'} - /ansi-styles/4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 + seroval@1.6.7: + resolution: {integrity: sha512-AeDcLh0yO2SFm9W71essgnSzLV9DI8ZH0x0knXn2DMnUZj728mpLbxjlbB6IqKCmqh8JA3cEqRyGoNkt584JcQ==} + engines: {node: '>=10'} - /anymatch/3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 + shiki@4.4.3: + resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} + engines: {node: '>=20'} - /arg/5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: true + solid-js@1.9.15: + resolution: {integrity: sha512-EeiY2xfpZJqPLjXspVEKjAII4yv8NyG//NxZ3IpOFHdUNnnTyL0uJOeS9LWGvA7cFCz5y94cjFwYlmw5Luncsg==} - /autoprefixer/10.4.13_postcss@8.4.21: - resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true + solid-refresh@0.6.3: + resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.4 - caniuse-lite: 1.0.30001448 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.21 - postcss-value-parser: 4.2.0 - dev: true - - /axios/0.25.0_debug@4.3.4: - resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} - dependencies: - follow-redirects: 1.15.2_debug@4.3.4 - transitivePeerDependencies: - - debug + solid-js: ^1.3 - /babel-plugin-jsx-dom-expressions/0.35.14_@babel+core@7.20.12: - resolution: {integrity: sha512-Eywfw/7cNbBsStTgj46JRvyGTb+RLyF2EJ0AV3/W2cUwbw3R3syOBqdzFLdHN2MPOs4nJA80XtGl9kSMjEekhA==} + solid-use@0.9.1: + resolution: {integrity: sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw==} + engines: {node: '>=10'} peerDependencies: - '@babel/core': ^7.20.12 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.20.12 - '@babel/types': 7.20.7 - html-entities: 2.3.3 - validate-html-nesting: 1.2.0 + solid-js: ^1.7 - /babel-plugin-polyfill-corejs2/0.3.3_@babel+core@7.20.12: - resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.20.10 - '@babel/core': 7.20.12 - '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.12 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} - /babel-plugin-polyfill-corejs3/0.6.0_@babel+core@7.20.12: - resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.12 - core-js-compat: 3.27.2 - transitivePeerDependencies: - - supports-color + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - /babel-plugin-polyfill-regenerator/0.4.1_@babel+core@7.20.12: - resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.12 - transitivePeerDependencies: - - supports-color + srvx@0.12.8: + resolution: {integrity: sha512-7k9HoDhluTlD7UJ+F+OopZoPZXB2J8f/5nW2VMLaB8KsSlZlRQxyyoX0hWEQCxB+F+3ZvB5QX3LqB6xSBRfxQQ==} + engines: {node: '>=20.16.0'} + hasBin: true - /babel-preset-solid/1.6.9_@babel+core@7.20.12: - resolution: {integrity: sha512-Dz4xROTGtAZ2B9+79KYUzi/bhjNGsx+8c+AD3VO/Cg1CisM1qq29XsnkWrRJeTMMn3XZkAI/Bf5Rz37d/gvPVQ==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.12 - babel-plugin-jsx-dom-expressions: 0.35.14_@babel+core@7.20.12 + srvx@1.0.5: + resolution: {integrity: sha512-KvSKRpgPG/oaq3cyT614OQ2bAa7DynuGamivCZeoZzIUULOdbQz6wUtnnVuC4+DCx2Zglzo8v5gBYmWIWYhDxA==} + engines: {node: '>=20.16.0'} + hasBin: true - /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - /binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} + terracotta@1.3.0: + resolution: {integrity: sha512-krNzs5NqamllVGbzJMiBg7QTDTWrZAFI53quBf5Ug29c6Mo6IYHqKc68V59wcFRKK8yYSk78d+VukXanAD/kDg==} + engines: {node: '>=20'} + peerDependencies: + solid-js: ^1.8 - /brace-expansion/2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - dev: true + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} - /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} - /browserslist/4.21.4: - resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001448 - electron-to-chromium: 1.4.284 - node-releases: 2.0.8 - update-browserslist-db: 1.0.10_browserslist@4.21.4 + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - /buffer-from/1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - /builtin-modules/3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - dev: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true - /builtins/5.0.1: - resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} - dependencies: - semver: 7.3.8 - dev: true + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - /busboy/1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - dependencies: - streamsearch: 1.1.0 + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - /bytes/3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} - engines: {node: '>= 0.8'} + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - /camelcase-css/2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - dev: true + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - /caniuse-lite/1.0.30001448: - resolution: {integrity: sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==} + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - /chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - /chokidar/3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 + unstorage@2.0.0-alpha.10: + resolution: {integrity: sha512-6h1veZp8gnp4dGllf0tDijoXxDYymJu251IpKKkrSVH+QHL6tXFbwG85KM+CBHSgpkkgtPuIiZ9oLCLP5+1Evw==} - /cliui/8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' - /clsx/1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - dev: false + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - /color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - /color-convert/2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 + vite-plugin-solid@2.11.14: + resolution: {integrity: sha512-7ZVBt8rpoyqmlwin2kRIUveaHoF6/kulY7gsnD+qFh4nS29V4OPAnw+ojoAspXIjObiL9o1xh9a/nTuYHm02Rw==} + peerDependencies: + '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.0.0 || ^7.0.0 + solid-js: ^1.7.2 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + peerDependenciesMeta: + '@testing-library/jest-dom': + optional: true - /color-name/1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + vite@8.3.1: + resolution: {integrity: sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true - /color-name/1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true - /commander/2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - /commondir/1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - /compressible/2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 +snapshots: - /compression/1.7.4: - resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} - engines: {node: '>= 0.8.0'} + '@babel/code-frame@7.29.7': dependencies: - accepts: 1.3.8 - bytes: 3.0.0 - compressible: 2.0.18 - debug: 2.6.9 - on-headers: 1.0.2 - safe-buffer: 5.1.2 - vary: 1.1.2 + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 transitivePeerDependencies: - supports-color - /connect/3.7.0: - resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} - engines: {node: '>= 0.10.0'} + '@babel/generator@7.29.8': dependencies: - debug: 2.6.9 - finalhandler: 1.1.2 - parseurl: 1.3.3 - utils-merge: 1.0.1 - transitivePeerDependencies: - - supports-color + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 - /convert-source-map/1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - /cookie/0.4.2: - resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} - engines: {node: '>= 0.6'} - dev: true - - /core-js-compat/3.27.2: - resolution: {integrity: sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg==} + '@babel/helper-compilation-targets@7.29.7': dependencies: - browserslist: 4.21.4 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.29.2 + lru-cache: 5.1.1 + semver: 6.3.1 - /cron-schedule/3.0.6: - resolution: {integrity: sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==} - dev: true + '@babel/helper-globals@7.29.7': {} - /cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + '@babel/helper-module-imports@7.18.6': dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true + '@babel/types': 7.29.8 - /cssesc/3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - dev: true - - /csstype/3.1.1: - resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} - - /debug/2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + '@babel/helper-module-imports@7.29.7': dependencies: - ms: 2.0.0 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color - /debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - ms: 2.1.2 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color - /deepmerge/4.2.2: - resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} - engines: {node: '>=0.10.0'} - dev: true + '@babel/helper-plugin-utils@7.29.7': {} - /define-lazy-prop/2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} + '@babel/helper-string-parser@7.29.7': {} - /defined/1.0.1: - resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} - dev: true + '@babel/helper-validator-identifier@7.29.7': {} - /dequal/2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} + '@babel/helper-validator-option@7.29.7': {} - /detective/5.2.1: - resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} - engines: {node: '>=0.8.0'} - hasBin: true + '@babel/helpers@7.29.7': dependencies: - acorn-node: 1.8.2 - defined: 1.0.1 - minimist: 1.2.7 - dev: true - - /didyoumean/1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: true + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 - /dlv/1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dev: true - - /dotenv/10.0.0: - resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} - engines: {node: '>=10'} - dev: true + '@babel/parser@7.29.9': + dependencies: + '@babel/types': 7.29.8 - /dotenv/16.0.3: - resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} - engines: {node: '>=12'} + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - /ee-first/1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 - /electron-to-chromium/1.4.284: - resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color - /emoji-regex/8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 - /encodeurl/1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} + '@biomejs/biome@2.5.14': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.5.14 + '@biomejs/cli-darwin-x64': 2.5.14 + '@biomejs/cli-linux-arm64': 2.5.14 + '@biomejs/cli-linux-arm64-musl': 2.5.14 + '@biomejs/cli-linux-x64': 2.5.14 + '@biomejs/cli-linux-x64-musl': 2.5.14 + '@biomejs/cli-win32-arm64': 2.5.14 + '@biomejs/cli-win32-x64': 2.5.14 + + '@biomejs/cli-darwin-arm64@2.5.14': + optional: true - /es-module-lexer/1.1.0: - resolution: {integrity: sha512-fJg+1tiyEeS8figV+fPcPpm8WqJEflG3yPU0NOm5xMvrNkuiy7HzX/Ljng4Y0hAoiw4/3hQTCFYw+ub8+a2pRA==} + '@biomejs/cli-darwin-x64@2.5.14': + optional: true - /esbuild-android-64/0.14.54: - resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true + '@biomejs/cli-linux-arm64-musl@2.5.14': optional: true - /esbuild-android-64/0.15.18: - resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true + '@biomejs/cli-linux-arm64@2.5.14': optional: true - /esbuild-android-arm64/0.14.54: - resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true + '@biomejs/cli-linux-x64-musl@2.5.14': optional: true - /esbuild-android-arm64/0.15.18: - resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true + '@biomejs/cli-linux-x64@2.5.14': optional: true - /esbuild-darwin-64/0.14.54: - resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true + '@biomejs/cli-win32-arm64@2.5.14': optional: true - /esbuild-darwin-64/0.15.18: - resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true + '@biomejs/cli-win32-x64@2.5.14': optional: true - /esbuild-darwin-arm64/0.14.54: - resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 optional: true - /esbuild-darwin-arm64/0.15.18: - resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 optional: true - /esbuild-freebsd-64/0.14.54: - resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 optional: true - /esbuild-freebsd-64/0.15.18: - resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.4 optional: true - /esbuild-freebsd-arm64/0.14.54: - resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.3 + + '@oxc-parser/binding-android-arm-eabi@0.141.0': optional: true - /esbuild-freebsd-arm64/0.15.18: - resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true + '@oxc-parser/binding-android-arm64@0.141.0': optional: true - /esbuild-linux-32/0.14.54: - resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-darwin-arm64@0.141.0': optional: true - /esbuild-linux-32/0.15.18: - resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-darwin-x64@0.141.0': optional: true - /esbuild-linux-64/0.14.54: - resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-freebsd-x64@0.141.0': optional: true - /esbuild-linux-64/0.15.18: - resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.141.0': optional: true - /esbuild-linux-arm/0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-arm-musleabihf@0.141.0': optional: true - /esbuild-linux-arm/0.15.18: - resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-arm64-gnu@0.141.0': optional: true - /esbuild-linux-arm64/0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-arm64-musl@0.141.0': optional: true - /esbuild-linux-arm64/0.15.18: - resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-ppc64-gnu@0.141.0': optional: true - /esbuild-linux-mips64le/0.14.54: - resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-riscv64-gnu@0.141.0': optional: true - /esbuild-linux-mips64le/0.15.18: - resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-riscv64-musl@0.141.0': optional: true - /esbuild-linux-ppc64le/0.14.54: - resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-s390x-gnu@0.141.0': optional: true - /esbuild-linux-ppc64le/0.15.18: - resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-x64-gnu@0.141.0': optional: true - /esbuild-linux-riscv64/0.14.54: - resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-linux-x64-musl@0.141.0': optional: true - /esbuild-linux-riscv64/0.15.18: - resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-openharmony-arm64@0.141.0': optional: true - /esbuild-linux-s390x/0.14.54: - resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-wasm32-wasi@0.141.0': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) optional: true - /esbuild-linux-s390x/0.15.18: - resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true + '@oxc-parser/binding-win32-arm64-msvc@0.141.0': optional: true - /esbuild-netbsd-64/0.14.54: - resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true + '@oxc-parser/binding-win32-ia32-msvc@0.141.0': optional: true - /esbuild-netbsd-64/0.15.18: - resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true + '@oxc-parser/binding-win32-x64-msvc@0.141.0': optional: true - /esbuild-openbsd-64/0.14.54: - resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true + '@oxc-project/types@0.141.0': {} + + '@oxc-project/types@0.151.0': {} + + '@popperjs/core@2.11.8': {} + + '@rolldown/binding-android-arm-eabi@1.2.11': optional: true - /esbuild-openbsd-64/0.15.18: - resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true + '@rolldown/binding-android-arm64@1.2.11': optional: true - /esbuild-plugin-solid/0.4.2_e63rwa75r2qslo2sobfndo3ih4: - resolution: {integrity: sha512-T5GphLoud3RumjeNYO3K9WVjWDzVKG5evlS7hUEUI0n9tiCL+CnbvJh3SSwFi3xeeXpZRrnZc1gd6FWQsVobTg==} - peerDependencies: - esbuild: '>=0.12' - solid-js: '>= 1.0' - dependencies: - '@babel/core': 7.20.12 - '@babel/preset-typescript': 7.18.6_@babel+core@7.20.12 - babel-preset-solid: 1.6.9_@babel+core@7.20.12 - esbuild: 0.14.54 - solid-js: 1.6.9 - transitivePeerDependencies: - - supports-color + '@rolldown/binding-darwin-arm64@1.2.11': + optional: true - /esbuild-sunos-64/0.14.54: - resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true + '@rolldown/binding-darwin-x64@1.2.11': optional: true - /esbuild-sunos-64/0.15.18: - resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true + '@rolldown/binding-freebsd-x64@1.2.11': optional: true - /esbuild-windows-32/0.14.54: - resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': optional: true - /esbuild-windows-32/0.15.18: - resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true + '@rolldown/binding-linux-arm64-gnu@1.2.11': optional: true - /esbuild-windows-64/0.14.54: - resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true + '@rolldown/binding-linux-arm64-musl@1.2.11': optional: true - /esbuild-windows-64/0.15.18: - resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true + '@rolldown/binding-linux-ppc64-gnu@1.2.11': optional: true - /esbuild-windows-arm64/0.14.54: - resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true + '@rolldown/binding-linux-s390x-gnu@1.2.11': optional: true - /esbuild-windows-arm64/0.15.18: - resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true + '@rolldown/binding-linux-x64-gnu@1.2.11': optional: true - /esbuild/0.14.54: - resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - - /esbuild/0.15.18: - resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.15.18 - '@esbuild/linux-loong64': 0.15.18 - esbuild-android-64: 0.15.18 - esbuild-android-arm64: 0.15.18 - esbuild-darwin-64: 0.15.18 - esbuild-darwin-arm64: 0.15.18 - esbuild-freebsd-64: 0.15.18 - esbuild-freebsd-arm64: 0.15.18 - esbuild-linux-32: 0.15.18 - esbuild-linux-64: 0.15.18 - esbuild-linux-arm: 0.15.18 - esbuild-linux-arm64: 0.15.18 - esbuild-linux-mips64le: 0.15.18 - esbuild-linux-ppc64le: 0.15.18 - esbuild-linux-riscv64: 0.15.18 - esbuild-linux-s390x: 0.15.18 - esbuild-netbsd-64: 0.15.18 - esbuild-openbsd-64: 0.15.18 - esbuild-sunos-64: 0.15.18 - esbuild-windows-32: 0.15.18 - esbuild-windows-64: 0.15.18 - esbuild-windows-arm64: 0.15.18 - - /escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} + '@rolldown/binding-linux-x64-musl@1.2.11': + optional: true - /escape-html/1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + '@rolldown/binding-openharmony-arm64@1.2.11': + optional: true - /escape-string-regexp/1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + '@rolldown/binding-win32-arm64-msvc@1.2.11': + optional: true - /estree-walker/2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + '@rolldown/binding-win32-x64-msvc@1.2.11': + optional: true - /esutils/2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + '@rolldown/pluginutils@1.0.1': {} - /execa/6.1.0: - resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 3.0.1 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.1.0 - onetime: 6.0.0 - signal-exit: 3.0.7 - strip-final-newline: 3.0.0 - dev: true - - /fast-glob/3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} + '@shikijs/core@4.4.3': dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 + '@shikijs/primitive': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 - /fastq/1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + '@shikijs/engine-javascript@4.4.3': dependencies: - reusify: 1.0.4 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 - /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + '@shikijs/engine-oniguruma@4.4.3': dependencies: - to-regex-range: 5.0.1 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 - /finalhandler/1.1.2: - resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} - engines: {node: '>= 0.8'} - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.3.0 - parseurl: 1.3.3 - statuses: 1.5.0 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - /follow-redirects/1.15.2_debug@4.3.4: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + '@shikijs/langs@4.4.3': dependencies: - debug: 4.3.4 + '@shikijs/types': 4.4.3 - /fraction.js/4.2.0: - resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} - dev: true - - /fs-extra/11.1.0: - resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} - engines: {node: '>=14.14'} + '@shikijs/primitive@4.4.3': dependencies: - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.0 - - /fs.realpath/1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - optional: true + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - - /gensync/1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - /get-caller-file/2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} + '@shikijs/themes@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 - /get-port/6.1.2: - resolution: {integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + '@shikijs/types@4.4.3': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 - /get-stream/6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - dev: true + '@shikijs/vscode-textmate@10.0.2': {} - /glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + '@solidjs/meta@0.29.4(solid-js@1.9.15)': dependencies: - is-glob: 4.0.3 + solid-js: 1.9.15 - /glob-parent/6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + '@solidjs/router@1.0.0(solid-js@1.9.15)': dependencies: - is-glob: 4.0.3 - dev: true + solid-js: 1.9.15 - /glob/8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} + '@solidjs/start@2.0.5(@solidjs/router@1.0.0(solid-js@1.9.15))(crossws@0.4.12(srvx@1.0.5))(ocache@0.3.0)(vite@8.3.1)': dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - dev: true - - /globals/11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - - /graceful-fs/4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - - /has-flag/3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/trace-mapping': 0.3.31 + '@solidjs/meta': 0.29.4(solid-js@1.9.15) + '@types/babel__traverse': 7.28.0 + '@types/micromatch': 4.0.10 + cookie-es: 3.1.1 + defu: 6.1.7 + error-stack-parser-es: 2.0.1 + fast-glob: 3.3.3 + h3: 2.0.1-rc.32(crossws@0.4.12(srvx@1.0.5))(ocache@0.3.0) + html-to-image: 1.11.13 + micromatch: 4.0.8 + oxc-parser: 0.141.0 + path-to-regexp: 8.4.2 + pathe: 2.0.3 + radix3: 1.1.2 + seroval: 1.6.7 + seroval-plugins: 1.6.7(seroval@1.6.7) + shiki: 4.4.3 + solid-js: 1.9.15 + srvx: 0.12.8 + terracotta: 1.3.0(solid-js@1.9.15) + vite: 8.3.1 + vite-plugin-solid: 2.11.14(solid-js@1.9.15)(vite@8.3.1) + optionalDependencies: + '@solidjs/router': 1.0.0(solid-js@1.9.15) + transitivePeerDependencies: + - '@testing-library/jest-dom' + - crossws + - ocache + - supports-color - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} + '@suid/base@0.11.0(solid-js@1.9.15)': dependencies: - function-bind: 1.1.1 - - /html-entities/2.3.3: - resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} + '@popperjs/core': 2.11.8 + '@suid/css': 0.4.1 + '@suid/system': 0.14.0(solid-js@1.9.15) + '@suid/types': 0.8.0(solid-js@1.9.15) + '@suid/utils': 0.11.0(solid-js@1.9.15) + clsx: 2.1.1 + solid-js: 1.9.15 - /html-rewriter-wasm/0.4.1: - resolution: {integrity: sha512-lNovG8CMCCmcVB1Q7xggMSf7tqPCijZXaH4gL6iE8BFghdQCbaY5Met9i1x2Ex8m/cZHDUtXK9H6/znKamRP8Q==} - dev: true + '@suid/css@0.4.1': {} - /http-cache-semantics/4.1.0: - resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} - dev: true - - /human-signals/3.0.1: - resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} - engines: {node: '>=12.20.0'} - dev: true - - /inflight/1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + '@suid/material@0.19.0(solid-js@1.9.15)': dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true + '@suid/base': 0.11.0(solid-js@1.9.15) + '@suid/css': 0.4.1 + '@suid/system': 0.14.0(solid-js@1.9.15) + '@suid/types': 0.8.0(solid-js@1.9.15) + '@suid/utils': 0.11.0(solid-js@1.9.15) + clsx: 2.1.1 + solid-js: 1.9.15 - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /is-binary-path/2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + '@suid/styled-engine@0.9.0(patch_hash=ab821ddfe39ccc91ce94f345cc57d6a359089ed194be195fe894d67131a9ac90)(solid-js@1.9.15)': dependencies: - binary-extensions: 2.2.0 + '@suid/css': 0.4.1 + '@suid/utils': 0.11.0(solid-js@1.9.15) + solid-js: 1.9.15 - /is-builtin-module/3.2.0: - resolution: {integrity: sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==} - engines: {node: '>=6'} + '@suid/system@0.14.0(solid-js@1.9.15)': dependencies: - builtin-modules: 3.3.0 - dev: true + '@suid/css': 0.4.1 + '@suid/styled-engine': 0.9.0(patch_hash=ab821ddfe39ccc91ce94f345cc57d6a359089ed194be195fe894d67131a9ac90)(solid-js@1.9.15) + '@suid/types': 0.8.0(solid-js@1.9.15) + '@suid/utils': 0.11.0(solid-js@1.9.15) + clsx: 2.1.1 + csstype: 3.2.3 + solid-js: 1.9.15 - /is-core-module/2.11.0: - resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + '@suid/types@0.8.0(solid-js@1.9.15)': dependencies: - has: 1.0.3 - - /is-docker/2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true + solid-js: 1.9.15 - /is-extglob/2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - /is-fullwidth-code-point/3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + '@suid/utils@0.11.0(solid-js@1.9.15)': + dependencies: + '@suid/types': 0.8.0(solid-js@1.9.15) + solid-js: 1.9.15 - /is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + '@tybys/wasm-util@0.10.4': dependencies: - is-extglob: 2.1.1 + tslib: 2.8.1 + optional: true - /is-module/1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - dev: true + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 - /is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 - /is-reference/1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + '@types/babel__template@7.4.4': dependencies: - '@types/estree': 1.0.0 - dev: true + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 - /is-stream/3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 - /is-what/4.1.8: - resolution: {integrity: sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==} - engines: {node: '>=12.13'} + '@types/braces@3.0.5': {} - /is-wsl/2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} + '@types/hast@3.0.5': dependencies: - is-docker: 2.2.1 + '@types/unist': 3.0.3 - /isexe/2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 - /joi/17.7.0: - resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==} + '@types/micromatch@4.0.10': dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.4 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 + '@types/braces': 3.0.5 - /js-tokens/4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + '@types/unist@3.0.3': {} - /jsesc/0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true - /jsesc/2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true - /json5/2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true - /jsonfile/6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.10 + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true - /kleur/4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - dev: true + '@typescript/typescript-linux-arm64@7.0.2': + optional: true - /kolorist/1.6.0: - resolution: {integrity: sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ==} + '@typescript/typescript-linux-arm@7.0.2': + optional: true - /lilconfig/2.0.6: - resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} - engines: {node: '>=10'} - dev: true + '@typescript/typescript-linux-loong64@7.0.2': + optional: true - /lodash.debounce/4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true - /lru-cache/5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - dependencies: - yallist: 3.1.1 + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true - /lru-cache/6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: true + '@typescript/typescript-linux-s390x@7.0.2': + optional: true - /magic-string/0.27.0: - resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true + '@typescript/typescript-linux-x64@7.0.2': + optional: true - /merge-anything/5.1.4: - resolution: {integrity: sha512-7PWKwGOs5WWcpw+/OvbiFiAvEP6bv/QHiicigpqMGKIqPPAtGhBLR8LFJW+Zu6m9TXiR/a8+AiPlGG0ko1ruoQ==} - engines: {node: '>=12.13'} - dependencies: - is-what: 4.1.8 + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true - /merge-stream/2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true - /merge2/1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true - /micromatch/4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true - /mime-db/1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + '@typescript/typescript-sunos-x64@7.0.2': + optional: true - /mime-types/2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 + '@typescript/typescript-win32-arm64@7.0.2': + optional: true - /mime/3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - dev: true + '@typescript/typescript-win32-x64@7.0.2': + optional: true - /mimic-fn/4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - dev: true + '@ungap/structured-clone@1.4.0': {} - /miniflare/2.11.0: - resolution: {integrity: sha512-QA18I1VQXdCo4nBtPJUcUDxW8c9xbc5ex5F61jwhkGVOISSnYdEheolESmjr8MYk28xwi0XD1ozS4rLaTONd+w==} - engines: {node: '>=16.13'} - hasBin: true - peerDependencies: - '@miniflare/storage-redis': 2.11.0 - cron-schedule: ^3.0.4 - ioredis: ^4.27.9 - peerDependenciesMeta: - '@miniflare/storage-redis': - optional: true - cron-schedule: - optional: true - ioredis: - optional: true + babel-plugin-jsx-dom-expressions@0.40.10(@babel/core@7.29.7): dependencies: - '@miniflare/cache': 2.11.0 - '@miniflare/cli-parser': 2.11.0 - '@miniflare/core': 2.11.0 - '@miniflare/d1': 2.11.0 - '@miniflare/durable-objects': 2.11.0 - '@miniflare/html-rewriter': 2.11.0 - '@miniflare/http-server': 2.11.0 - '@miniflare/kv': 2.11.0 - '@miniflare/queues': 2.11.0 - '@miniflare/r2': 2.11.0 - '@miniflare/runner-vm': 2.11.0 - '@miniflare/scheduler': 2.11.0 - '@miniflare/shared': 2.11.0 - '@miniflare/sites': 2.11.0 - '@miniflare/storage-file': 2.11.0 - '@miniflare/storage-memory': 2.11.0 - '@miniflare/web-sockets': 2.11.0 - kleur: 4.1.5 - semiver: 1.1.0 - source-map-support: 0.5.21 - undici: 5.9.1 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: true + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.8 + html-entities: 2.3.3 + parse5: 7.3.0 - /minimatch/5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} + babel-preset-solid@1.9.15(@babel/core@7.29.7)(solid-js@1.9.15): dependencies: - brace-expansion: 2.0.1 - dev: true + '@babel/core': 7.29.7 + babel-plugin-jsx-dom-expressions: 0.40.10(@babel/core@7.29.7) + optionalDependencies: + solid-js: 1.9.15 - /minimist/1.2.7: - resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + baseline-browser-mapping@2.11.26: {} - /mri/1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} + braces@3.0.3: + dependencies: + fill-range: 7.1.1 - /mrmime/1.0.1: - resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} - engines: {node: '>=10'} + browserslist@4.29.2: + dependencies: + baseline-browser-mapping: 2.11.26 + caniuse-lite: 1.0.30001813 + electron-to-chromium: 1.5.439 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.2) - /ms/2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + caniuse-lite@1.0.30001813: {} - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ccount@2.0.1: {} - /mustache/4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - dev: true + character-entities-html4@2.1.0: {} - /nanoid/3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + character-entities-legacy@3.0.0: {} - /negotiator/0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} + clsx@2.1.1: {} - /node-forge/1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} - engines: {node: '>= 6.13.0'} - dev: true + comma-separated-tokens@2.0.3: {} - /node-releases/2.0.8: - resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==} + consola@3.4.2: {} - /normalize-path/3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} + convert-source-map@2.0.0: {} - /normalize-range/0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - dev: true + cookie-es@3.1.1: {} - /npm-run-path/5.1.0: - resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - path-key: 4.0.0 - dev: true + crossws@0.4.12(srvx@1.0.5): + optionalDependencies: + srvx: 1.0.5 - /npx-import/1.1.4: - resolution: {integrity: sha512-3ShymTWOgqGyNlh5lMJAejLuIv3W1K3fbI5Ewc6YErZU3Sp0PqsNs8UIU1O8z5+KVl/Du5ag56Gza9vdorGEoA==} - dependencies: - execa: 6.1.0 - parse-package-name: 1.0.0 - semver: 7.3.8 - validate-npm-package-name: 4.0.0 - dev: true + csstype@3.2.3: {} - /object-hash/3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - dev: true + db0@0.4.1: {} - /on-finished/2.3.0: - resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} - engines: {node: '>= 0.8'} + debug@4.4.3: dependencies: - ee-first: 1.1.1 + ms: 2.1.3 - /on-headers/1.0.2: - resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} - engines: {node: '>= 0.8'} + defu@6.1.7: {} - /once/1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true + dequal@2.0.3: {} - /onetime/6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - dependencies: - mimic-fn: 4.0.0 - dev: true + detect-libc@2.1.2: {} - /open/8.4.0: - resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} - engines: {node: '>=12'} + devlop@1.1.0: dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 + dequal: 2.0.3 - /parse-multipart-data/1.5.0: - resolution: {integrity: sha512-ck5zaMF0ydjGfejNMnlo5YU2oJ+pT+80Jb1y4ybanT27j+zbVP/jkYmCrUGsEln0Ox/hZmuvgy8Ra7AxbXP2Mw==} + electron-to-chromium@1.5.439: {} - /parse-package-name/1.0.0: - resolution: {integrity: sha512-kBeTUtcj+SkyfaW4+KBe0HtsloBJ/mKTPoxpVdA57GZiPerREsUWJOhVj9anXweFiJkm5y8FG1sxFZkZ0SN6wg==} - dev: true + entities@6.0.1: {} - /parseurl/1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} + env-runner@0.2.3: + dependencies: + crossws: 0.4.12(srvx@1.0.5) + exsolve: 1.1.1 + httpxy: 0.5.5 + srvx: 1.0.5 - /path-key/3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true + error-stack-parser-es@2.0.1: {} - /path-key/4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - dev: true + escalade@3.2.0: {} - /path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + exsolve@1.1.1: {} - /picocolors/1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 - /picomatch/2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + fastq@1.20.3: + dependencies: + reusify: 1.1.0 - /pify/2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - dev: true + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 - /polka/1.0.0-next.22: - resolution: {integrity: sha512-a7tsZy5gFbJr0aUltZS97xCkbPglXuD67AMvTyZX7BTDBH384FWf0ZQF6rPvdutSxnO1vUlXM2zSLf5tCKk5RA==} - engines: {node: '>=8'} + fill-range@7.1.1: dependencies: - '@polka/url': 1.0.0-next.21 - trouter: 3.2.0 - dev: true + to-regex-range: 5.0.1 - /postcss-import/14.1.0_postcss@8.4.21: - resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} - engines: {node: '>=10.0.0'} - peerDependencies: - postcss: ^8.0.0 - dependencies: - postcss: 8.4.21 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.1 - dev: true + fsevents@2.3.3: + optional: true - /postcss-js/4.0.0_postcss@8.4.21: - resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.3.3 + gensync@1.0.0-beta.2: {} + + glob-parent@5.1.2: dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.21 - dev: true + is-glob: 4.0.3 - /postcss-load-config/3.1.4_postcss@8.4.21: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true + h3@2.0.1-rc.32(crossws@0.4.12(srvx@1.0.5))(ocache@0.3.0): dependencies: - lilconfig: 2.0.6 - postcss: 8.4.21 - yaml: 1.10.2 - dev: true + rou3: 0.9.2 + srvx: 1.0.5 + optionalDependencies: + crossws: 0.4.12(srvx@1.0.5) + ocache: 0.3.0 - /postcss-nested/6.0.0_postcss@8.4.21: - resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 + hast-util-to-html@9.0.5: dependencies: - postcss: 8.4.21 - postcss-selector-parser: 6.0.11 - dev: true + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 - /postcss-selector-parser/6.0.11: - resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} - engines: {node: '>=4'} + hast-util-whitespace@3.0.0: dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: true + '@types/hast': 3.0.5 - /postcss-value-parser/4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: true + hookable@6.1.2: {} - /postcss/8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 + html-entities@2.3.3: {} - /queue-microtask/1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + html-to-image@1.11.13: {} - /quick-lru/5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - dev: true + html-void-elements@3.0.0: {} - /read-cache/1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - dependencies: - pify: 2.3.0 - dev: true + httpxy@0.5.5: {} - /readdirp/3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 + is-extglob@2.1.1: {} - /regenerate-unicode-properties/10.1.0: - resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} - engines: {node: '>=4'} + is-glob@4.0.3: dependencies: - regenerate: 1.4.2 + is-extglob: 2.1.1 - /regenerate/1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + is-number@7.0.0: {} - /regenerator-runtime/0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + is-what@4.1.16: {} - /regenerator-transform/0.15.1: - resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==} - dependencies: - '@babel/runtime': 7.20.13 + js-tokens@4.0.0: {} - /regexparam/1.3.0: - resolution: {integrity: sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==} - engines: {node: '>=6'} - dev: true + jsesc@3.1.0: {} - /regexpu-core/5.2.2: - resolution: {integrity: sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==} - engines: {node: '>=4'} - dependencies: - regenerate: 1.4.2 - regenerate-unicode-properties: 10.1.0 - regjsgen: 0.7.1 - regjsparser: 0.9.1 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.1.0 + json5@2.2.3: {} - /regjsgen/0.7.1: - resolution: {integrity: sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==} + lightningcss-android-arm64@1.33.0: + optional: true - /regjsparser/0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} - hasBin: true - dependencies: - jsesc: 0.5.0 + lightningcss-darwin-arm64@1.33.0: + optional: true - /require-directory/2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + lightningcss-darwin-x64@1.33.0: + optional: true - /resolve/1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.11.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + lightningcss-freebsd-x64@1.33.0: + optional: true - /reusify/1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true - /rollup-plugin-visualizer/5.9.0_rollup@3.10.1: - resolution: {integrity: sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - rollup: 2.x || 3.x - peerDependenciesMeta: - rollup: - optional: true - dependencies: - open: 8.4.0 - picomatch: 2.3.1 - rollup: 3.10.1 - source-map: 0.7.4 - yargs: 17.6.2 + lightningcss-linux-arm64-gnu@1.33.0: + optional: true - /rollup-route-manifest/1.0.0_rollup@3.10.1: - resolution: {integrity: sha512-3CmcMmCLAzJDUXiO3z6386/Pt8/k9xTZv8gIHyXI8hYGoAInnYdOsFXiGGzQRMy6TXR1jUZme2qbdwjH2nFMjg==} - engines: {node: '>=8'} - peerDependencies: - rollup: '>=2.0.0' - dependencies: - rollup: 3.10.1 - route-sort: 1.0.0 + lightningcss-linux-arm64-musl@1.33.0: + optional: true - /rollup/2.79.1: - resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 + lightningcss-linux-x64-gnu@1.33.0: + optional: true - /rollup/3.10.1: - resolution: {integrity: sha512-3Er+yel3bZbZX1g2kjVM+FW+RUWDxbG87fcqFM5/9HbPCTpbVp6JOLn7jlxnNlbu7s/N/uDA4EV/91E2gWnxzw==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 + lightningcss-linux-x64-musl@1.33.0: + optional: true - /route-sort/1.0.0: - resolution: {integrity: sha512-SFgmvjoIhp5S4iBEDW3XnbT+7PRuZ55oRuNjY+CDB1SGZkyCG9bqQ3/dhaZTctTBYMAvDxd2Uy9dStuaUfgJqQ==} - engines: {node: '>= 6'} + lightningcss-win32-arm64-msvc@1.33.0: + optional: true - /run-parallel/1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: dependencies: - queue-microtask: 1.2.3 + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 - /rxjs/7.8.0: - resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + mdast-util-to-hast@13.2.1: dependencies: - tslib: 2.4.1 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 - /sade/1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} + merge-anything@5.1.7: dependencies: - mri: 1.2.0 + is-what: 4.1.16 - /safe-buffer/5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + merge2@1.4.1: {} - /selfsigned/2.1.1: - resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==} - engines: {node: '>=10'} + micromark-util-character@2.1.1: dependencies: - node-forge: 1.3.1 - dev: true - - /semiver/1.1.0: - resolution: {integrity: sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==} - engines: {node: '>=6'} - dev: true + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 - /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true + micromark-util-encode@2.0.1: {} - /semver/7.3.8: - resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} - engines: {node: '>=10'} - hasBin: true + micromark-util-sanitize-uri@2.0.1: dependencies: - lru-cache: 6.0.0 - dev: true + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 - /set-cookie-parser/2.5.1: - resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} + micromark-util-symbol@2.0.1: {} - /shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + micromark-util-types@2.0.3: {} + + micromatch@4.0.8: dependencies: - shebang-regex: 3.0.0 - dev: true + braces: 3.0.3 + picomatch: 2.3.2 - /shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true + ms@2.1.3: {} - /signal-exit/3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true + nanoid@3.3.19: {} - /sirv/2.0.2: - resolution: {integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==} - engines: {node: '>= 10'} - dependencies: - '@polka/url': 1.0.0-next.21 - mrmime: 1.0.1 - totalist: 3.0.0 + nf3@0.3.24: {} - /solid-js/1.6.9: - resolution: {integrity: sha512-kV3fMmm+1C2J95c8eDOPKGfZHnuAkHUBLG4hX1Xu08bXeAIPqmxuz/QdH3B8SIdTp3EatBVIyA6RCes3hrGzpg==} + nitro@3.0.260903-beta: dependencies: - csstype: 3.1.1 + consola: 3.4.2 + crossws: 0.4.12(srvx@1.0.5) + db0: 0.4.1 + env-runner: 0.2.3 + h3: 2.0.1-rc.32(crossws@0.4.12(srvx@1.0.5))(ocache@0.3.0) + hookable: 6.1.2 + nf3: 0.3.24 + ocache: 0.3.0 + rolldown: 1.2.11 + rou3: 0.9.2 + srvx: 1.0.5 + unenv: 2.0.0-rc.24 + unstorage: 2.0.0-alpha.10 - /solid-refresh/0.4.3_solid-js@1.6.9: - resolution: {integrity: sha512-7+4/gYsVi0BlM4PzT1PU1TB5nW3Hv8FWuB+Kw/ofWui7KQkWBf+dVZOrReQYHEmLCzytHUa2JysUXgzVALJmSw==} - peerDependencies: - solid-js: ^1.3 - dependencies: - '@babel/generator': 7.20.7 - '@babel/helper-module-imports': 7.18.6 - '@babel/types': 7.20.7 - solid-js: 1.6.9 + node-releases@2.0.57: {} - /solid-start-cloudflare-pages/0.2.16_beln6p34t7mdfnglvydsh3cc6m: - resolution: {integrity: sha512-AoKos338LNHEYMc2zDWPVeU+XGsfaFGSMIG2uufRkrTdFJm7KI/keD4Elwru0ryC/ws5yo+zh5D5WJJodN3EEQ==} - peerDependencies: - solid-start: '*' - vite: '*' - dependencies: - '@cloudflare/kv-asset-handler': 0.2.0 - '@cloudflare/workers-types': 3.19.0 - '@miniflare/core': 2.11.0 - '@miniflare/http-server': 2.11.0 - '@miniflare/shared': 2.11.0 - '@miniflare/web-sockets': 2.11.0 - '@rollup/plugin-commonjs': 24.0.1_rollup@3.10.1 - '@rollup/plugin-json': 6.0.0_rollup@3.10.1 - '@rollup/plugin-node-resolve': 15.0.1_rollup@3.10.1 - '@types/ws': 8.5.4 - miniflare: 2.11.0 - rollup: 3.10.1 - solid-start: 0.2.16_55umyvmfaswzxpylft3tvr3dvm - terser: 5.16.1 - vite: 3.2.5 - ws: 8.12.0 - transitivePeerDependencies: - - '@miniflare/storage-redis' - - bufferutil - - cron-schedule - - ioredis - - utf-8-validate - dev: true - - /solid-start-node/0.2.16_f63ddrunxocrtns5sxchmwmkce: - resolution: {integrity: sha512-kOxrBl1G18Y9z3KDr/ZMnJhUQDe9PhmkxaXbKxYRxtyG1hW7ZeAw49Hw8fPVLX+CbQVA6Zq8Mv1SUXA41Ct2IA==} - peerDependencies: - solid-start: '*' - undici: ^5.8.0 - vite: '*' - dependencies: - '@rollup/plugin-commonjs': 24.0.1_rollup@3.10.1 - '@rollup/plugin-json': 6.0.0_rollup@3.10.1 - '@rollup/plugin-node-resolve': 15.0.1_rollup@3.10.1 - compression: 1.7.4 - polka: 1.0.0-next.22 - rollup: 3.10.1 - sirv: 2.0.2 - solid-start: 0.2.16_55umyvmfaswzxpylft3tvr3dvm - terser: 5.16.1 - undici: 5.16.0 - vite: 3.2.5 - transitivePeerDependencies: - - supports-color - dev: true + ocache@0.3.0: {} - /solid-start/0.2.16_55umyvmfaswzxpylft3tvr3dvm: - resolution: {integrity: sha512-gkc14UFGBWF61qPu3nGVaB/wWKzX0r46BiOmwuMqIDJKpWAiOjzd+wDPGQOLkzVuobVGGFFcTbIUBvVZRUZ/qw==} - hasBin: true - peerDependencies: - '@solidjs/meta': ^0.28.0 - '@solidjs/router': ^0.7.0 - solid-js: ^1.6.2 - vite: ^3.1.8 - dependencies: - '@babel/core': 7.20.12 - '@babel/generator': 7.20.7 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.20.12 - '@babel/preset-env': 7.20.2_@babel+core@7.20.12 - '@babel/preset-typescript': 7.18.6_@babel+core@7.20.12 - '@babel/template': 7.20.7 - '@solidjs/meta': 0.28.2_solid-js@1.6.9 - '@solidjs/router': 0.7.0_solid-js@1.6.9 - '@types/cookie': 0.5.1 - chokidar: 3.5.3 - compression: 1.7.4 - connect: 3.7.0 - debug: 4.3.4 - dequal: 2.0.3 - dotenv: 16.0.3 - es-module-lexer: 1.1.0 - esbuild: 0.14.54 - esbuild-plugin-solid: 0.4.2_e63rwa75r2qslo2sobfndo3ih4 - fast-glob: 3.2.12 - get-port: 6.1.2 - parse-multipart-data: 1.5.0 - picocolors: 1.0.0 - rollup: 3.10.1 - rollup-plugin-visualizer: 5.9.0_rollup@3.10.1 - rollup-route-manifest: 1.0.0_rollup@3.10.1 - sade: 1.8.1 - set-cookie-parser: 2.5.1 - sirv: 2.0.2 - solid-js: 1.6.9 - terser: 5.16.1 - undici: 5.16.0 - vite: 3.2.5 - vite-plugin-inspect: 0.7.14_rollup@3.10.1+vite@3.2.5 - vite-plugin-solid: 2.5.0_solid-js@1.6.9+vite@3.2.5 - wait-on: 6.0.1_debug@4.3.4 - transitivePeerDependencies: - - supports-color + oniguruma-parser@0.12.2: {} - /source-map-js/1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 - /source-map-support/0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + oxc-parser@0.141.0: dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 + '@oxc-project/types': 0.141.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.141.0 + '@oxc-parser/binding-android-arm64': 0.141.0 + '@oxc-parser/binding-darwin-arm64': 0.141.0 + '@oxc-parser/binding-darwin-x64': 0.141.0 + '@oxc-parser/binding-freebsd-x64': 0.141.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.141.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.141.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.141.0 + '@oxc-parser/binding-linux-arm64-musl': 0.141.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.141.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.141.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.141.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.141.0 + '@oxc-parser/binding-linux-x64-gnu': 0.141.0 + '@oxc-parser/binding-linux-x64-musl': 0.141.0 + '@oxc-parser/binding-openharmony-arm64': 0.141.0 + '@oxc-parser/binding-wasm32-wasi': 0.141.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.141.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.141.0 + '@oxc-parser/binding-win32-x64-msvc': 0.141.0 - /source-map/0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + parse5@7.3.0: + dependencies: + entities: 6.0.1 - /source-map/0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} + path-to-regexp@8.4.2: {} - /stack-trace/0.0.10: - resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} - dev: true + pathe@2.0.3: {} - /statuses/1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} + picocolors@1.1.1: {} - /streamsearch/1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} + picomatch@2.3.2: {} - /string-width/4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 + picomatch@4.0.7: {} - /strip-ansi/6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + postcss@8.5.28: dependencies: - ansi-regex: 5.0.1 + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.1 - /strip-final-newline/3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - dev: true + property-information@7.2.0: {} - /supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 + queue-microtask@1.2.3: {} - /supports-preserve-symlinks-flag/1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + radix3@1.1.2: {} - /tailwindcss/3.2.4_postcss@8.4.21: - resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} - engines: {node: '>=12.13.0'} - hasBin: true - peerDependencies: - postcss: ^8.0.9 - dependencies: - arg: 5.0.2 - chokidar: 3.5.3 - color-name: 1.1.4 - detective: 5.2.1 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.2.12 - glob-parent: 6.0.2 - is-glob: 4.0.3 - lilconfig: 2.0.6 - micromatch: 4.0.5 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.21 - postcss-import: 14.1.0_postcss@8.4.21 - postcss-js: 4.0.0_postcss@8.4.21 - postcss-load-config: 3.1.4_postcss@8.4.21 - postcss-nested: 6.0.0_postcss@8.4.21 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - quick-lru: 5.1.1 - resolve: 1.22.1 - transitivePeerDependencies: - - ts-node - dev: true + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 - /terser/5.16.1: - resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==} - engines: {node: '>=10'} - hasBin: true + regex-utilities@2.3.0: {} + + regex@6.1.0: dependencies: - '@jridgewell/source-map': 0.3.2 - acorn: 8.8.2 - commander: 2.20.3 - source-map-support: 0.5.21 + regex-utilities: 2.3.0 - /to-fast-properties/2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} + reusify@1.1.0: {} - /to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + rolldown@1.2.11: dependencies: - is-number: 7.0.0 + '@oxc-project/types': 0.151.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.11 + '@rolldown/binding-android-arm64': 1.2.11 + '@rolldown/binding-darwin-arm64': 1.2.11 + '@rolldown/binding-darwin-x64': 1.2.11 + '@rolldown/binding-freebsd-x64': 1.2.11 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.11 + '@rolldown/binding-linux-arm64-gnu': 1.2.11 + '@rolldown/binding-linux-arm64-musl': 1.2.11 + '@rolldown/binding-linux-ppc64-gnu': 1.2.11 + '@rolldown/binding-linux-s390x-gnu': 1.2.11 + '@rolldown/binding-linux-x64-gnu': 1.2.11 + '@rolldown/binding-linux-x64-musl': 1.2.11 + '@rolldown/binding-openharmony-arm64': 1.2.11 + '@rolldown/binding-win32-arm64-msvc': 1.2.11 + '@rolldown/binding-win32-x64-msvc': 1.2.11 + + rou3@0.9.2: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 - /totalist/3.0.0: - resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==} - engines: {node: '>=6'} + semver@6.3.1: {} - /trouter/3.2.0: - resolution: {integrity: sha512-rLLXbhTObLy2MBVjLC+jTnoIKw99n0GuJs9ov10J870vDw5qhTurPzsDrudNtBf5w/CZ9ctZy2p2IMmhGcel2w==} - engines: {node: '>=6'} + seroval-plugins@1.5.6(seroval@1.5.6): dependencies: - regexparam: 1.3.0 - dev: true + seroval: 1.5.6 - /tslib/2.4.1: - resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + seroval-plugins@1.6.7(seroval@1.6.7): + dependencies: + seroval: 1.6.7 - /typescript/4.9.4: - resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true + seroval@1.5.6: {} - /ufo/1.0.1: - resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} + seroval@1.6.7: {} - /undici/5.16.0: - resolution: {integrity: sha512-KWBOXNv6VX+oJQhchXieUznEmnJMqgXMbs0xxH2t8q/FUAWSJvOSr/rMaZKnX5RIVq7JDn0JbP4BOnKG2SGXLQ==} - engines: {node: '>=12.18'} + shiki@4.4.3: dependencies: - busboy: 1.6.0 + '@shikijs/core': 4.4.3 + '@shikijs/engine-javascript': 4.4.3 + '@shikijs/engine-oniguruma': 4.4.3 + '@shikijs/langs': 4.4.3 + '@shikijs/themes': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 - /undici/5.9.1: - resolution: {integrity: sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg==} - engines: {node: '>=12.18'} - dev: true + solid-js@1.9.15: + dependencies: + csstype: 3.2.3 + seroval: 1.5.6 + seroval-plugins: 1.5.6(seroval@1.5.6) - /unicode-canonical-property-names-ecmascript/2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} - engines: {node: '>=4'} + solid-refresh@0.6.3(solid-js@1.9.15): + dependencies: + '@babel/generator': 7.29.8 + '@babel/helper-module-imports': 7.29.7 + '@babel/types': 7.29.8 + solid-js: 1.9.15 + transitivePeerDependencies: + - supports-color - /unicode-match-property-ecmascript/2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} + solid-use@0.9.1(solid-js@1.9.15): dependencies: - unicode-canonical-property-names-ecmascript: 2.0.0 - unicode-property-aliases-ecmascript: 2.1.0 + solid-js: 1.9.15 - /unicode-match-property-value-ecmascript/2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} - engines: {node: '>=4'} + source-map-js@1.2.1: {} - /unicode-property-aliases-ecmascript/2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} - engines: {node: '>=4'} + space-separated-tokens@2.0.2: {} - /universalify/2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} - engines: {node: '>= 10.0.0'} + srvx@0.12.8: {} - /unpipe/1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} + srvx@1.0.5: {} - /update-browserslist-db/1.0.10_browserslist@4.21.4: - resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + stringify-entities@4.0.4: dependencies: - browserslist: 4.21.4 - escalade: 3.1.1 - picocolors: 1.0.0 - - /urlpattern-polyfill/4.0.3: - resolution: {integrity: sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ==} - dev: true + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 - /util-deprecate/1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true - - /utils-merge/1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} + terracotta@1.3.0(solid-js@1.9.15): + dependencies: + solid-js: 1.9.15 + solid-use: 0.9.1(solid-js@1.9.15) - /validate-html-nesting/1.2.0: - resolution: {integrity: sha512-sI65QUd3T/e5wbQkdPKjikFsIVLPIaOQK+9uowPp6/k609SN8hs5eqBLrnN5DeW9Kd932Q4Imo0fzK2dxoOsCA==} + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 - /validate-npm-package-name/4.0.0: - resolution: {integrity: sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + to-regex-range@5.0.1: dependencies: - builtins: 5.0.1 - dev: true + is-number: 7.0.0 - /vary/1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} + trim-lines@3.0.1: {} - /vite-plugin-inspect/0.7.14_rollup@3.10.1+vite@3.2.5: - resolution: {integrity: sha512-C9V93Yy2yUf941oVxIq93K6T1o0SZxoG8MdmyJsnoNDijOAGHT1rVSVjzF/uKFYvgnvLvaXioaoy6ica6aOS0g==} - engines: {node: '>=14'} - peerDependencies: - vite: ^3.1.0 || ^4.0.0 - dependencies: - '@antfu/utils': 0.7.2 - '@rollup/pluginutils': 5.0.2_rollup@3.10.1 - debug: 4.3.4 - fs-extra: 11.1.0 - kolorist: 1.6.0 - sirv: 2.0.2 - ufo: 1.0.1 - vite: 3.2.5 - transitivePeerDependencies: - - rollup - - supports-color + tslib@2.8.1: + optional: true - /vite-plugin-solid/2.5.0_solid-js@1.6.9+vite@3.2.5: - resolution: {integrity: sha512-VneGd3RyFJvwaiffsqgymeMaofn0IzQLPwDzafTV2f1agoWeeJlk5VrI5WqT9BTtLe69vNNbCJWqLhHr9fOdDw==} - peerDependencies: - solid-js: ^1.3.17 || ^1.4.0 || ^1.5.0 || ^1.6.0 - vite: ^3.0.0 || ^4.0.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/preset-typescript': 7.18.6_@babel+core@7.20.12 - babel-preset-solid: 1.6.9_@babel+core@7.20.12 - merge-anything: 5.1.4 - solid-js: 1.6.9 - solid-refresh: 0.4.3_solid-js@1.6.9 - vite: 3.2.5 - vitefu: 0.2.4_vite@3.2.5 - transitivePeerDependencies: - - supports-color + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 - /vite/3.2.5: - resolution: {integrity: sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true + unenv@2.0.0-rc.24: dependencies: - esbuild: 0.15.18 - postcss: 8.4.21 - resolve: 1.22.1 - rollup: 2.79.1 - optionalDependencies: - fsevents: 2.3.2 + pathe: 2.0.3 - /vitefu/0.2.4_vite@3.2.5: - resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 - peerDependenciesMeta: - vite: - optional: true + unist-util-is@6.0.1: dependencies: - vite: 3.2.5 + '@types/unist': 3.0.3 - /wait-on/6.0.1_debug@4.3.4: - resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} - engines: {node: '>=10.0.0'} - hasBin: true + unist-util-position@5.0.0: dependencies: - axios: 0.25.0_debug@4.3.4 - joi: 17.7.0 - lodash: 4.17.21 - minimist: 1.2.7 - rxjs: 7.8.0 - transitivePeerDependencies: - - debug + '@types/unist': 3.0.3 - /which/2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + unist-util-stringify-position@4.0.0: dependencies: - isexe: 2.0.0 - dev: true + '@types/unist': 3.0.3 - /wrap-ansi/7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + unist-util-visit-parents@6.0.2: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 - /wrappy/1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 - /ws/8.12.0: - resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true + unstorage@2.0.0-alpha.10: {} - /xtend/4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true + update-browserslist-db@1.3.3(browserslist@4.29.2): + dependencies: + browserslist: 4.29.2 + escalade: 3.2.0 + picocolors: 1.1.1 - /y18n/5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 - /yallist/3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-plugin-solid@2.11.14(solid-js@1.9.15)(vite@8.3.1): + dependencies: + '@babel/core': 7.29.7 + '@types/babel__core': 7.20.5 + babel-preset-solid: 1.9.15(@babel/core@7.29.7)(solid-js@1.9.15) + merge-anything: 5.1.7 + solid-js: 1.9.15 + solid-refresh: 0.6.3(solid-js@1.9.15) + vite: 8.3.1 + vitefu: 1.1.3(vite@8.3.1) + transitivePeerDependencies: + - supports-color - /yallist/4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true + vite@8.3.1: + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.11 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 - /yaml/1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - dev: true + vitefu@1.1.3(vite@8.3.1): + optionalDependencies: + vite: 8.3.1 - /yargs-parser/21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + yallist@3.1.1: {} - /yargs/17.6.2: - resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} - engines: {node: '>=12'} - dependencies: - cliui: 8.0.1 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - /youch/2.2.2: - resolution: {integrity: sha512-/FaCeG3GkuJwaMR34GHVg0l8jCbafZLHiFowSjqLlqhC6OMyf2tPJBu8UirF7/NI9X/R5ai4QfEKUCOxMAGxZQ==} - dependencies: - '@types/stack-trace': 0.0.29 - cookie: 0.4.2 - mustache: 4.2.0 - stack-trace: 0.0.10 - dev: true + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..6a0576e --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +patchedDependencies: + '@suid/styled-engine@0.9.0': patches/@suid__styled-engine@0.9.0.patch diff --git a/postcss.config.cjs b/postcss.config.cjs deleted file mode 100644 index 33ad091..0000000 --- a/postcss.config.cjs +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..1974242 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/bloom.svg b/public/bloom.svg new file mode 100644 index 0000000..df6109c --- /dev/null +++ b/public/bloom.svg @@ -0,0 +1 @@ + diff --git a/public/favicon.ico b/public/favicon.ico index 1d3b5fc..66a4253 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/fonts/jetbrains-mono/OFL.txt b/public/fonts/jetbrains-mono/OFL.txt new file mode 100644 index 0000000..8f7ed67 --- /dev/null +++ b/public/fonts/jetbrains-mono/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/jetbrains-mono/jetbrains-mono-latin-wght-normal.woff2 b/public/fonts/jetbrains-mono/jetbrains-mono-latin-wght-normal.woff2 new file mode 100644 index 0000000..cd5102a Binary files /dev/null and b/public/fonts/jetbrains-mono/jetbrains-mono-latin-wght-normal.woff2 differ diff --git a/public/img/Peepo.png b/public/img/Peepo.png deleted file mode 100644 index 2719f13..0000000 Binary files a/public/img/Peepo.png and /dev/null differ diff --git a/public/og.png b/public/og.png new file mode 100644 index 0000000..e845521 Binary files /dev/null and b/public/og.png differ diff --git a/renovate.json b/renovate.json index 53ac71c..e4da9a1 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,5 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base" - ], + "extends": ["config:recommended"], "lockFileMaintenance": { "enabled": true } } diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..8969c14 --- /dev/null +++ b/src/app.css @@ -0,0 +1,5 @@ +@font-face { + font-family: Alsina; + src: url("/v3/font/Alsina_Ultrajada.ttf") format("truetype"); + font-display: swap; +} diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 0000000..2eb3881 --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,29 @@ +import "./brand.css"; +import "./app.css"; + +import { MetaProvider, Title } from "@solidjs/meta"; +import { Router } from "@solidjs/router"; +import { FileRoutes } from "@solidjs/start/router"; +import CssBaseline from "@suid/material/CssBaseline"; +import { ThemeProvider } from "@suid/material/styles"; +import { Suspense } from "solid-js"; + +import { theme } from "~/theme"; + +export default function App() { + return ( + ( + + ChatBloom — Twitch chat overlay by shiftbloom studio + + + {props.children} + + + )} + > + + + ); +} diff --git a/src/brand.css b/src/brand.css new file mode 100644 index 0000000..78ae899 --- /dev/null +++ b/src/brand.css @@ -0,0 +1,81 @@ +/* + shiftbloom studio — Brand Book v2.0 tokens. + White mode only. The gradient lives inside the mark and its petals, + never on UI, buttons, banners or type. +*/ + +/* JetBrains Mono, SIL Open Font License 1.1: public/fonts/jetbrains-mono/OFL.txt. + Clash Display and General Sans load from Fontshare, see entry-server.tsx. */ +@font-face { + font-family: "JetBrains Mono"; + src: url("/fonts/jetbrains-mono/jetbrains-mono-latin-wght-normal.woff2") format("woff2"); + font-weight: 100 800; + font-style: normal; + font-display: swap; +} + +:root { + --bloom: #ff2e52; /* petals, seeds, CTAs, statements from 24px up */ + --root: #c81040; /* hover states and small red text (5.8:1 on white) */ + --petal: #ff8fab; /* gradient tip, highlights on red fields */ + --blush: #ffe9ef; /* tags, tints, selection fills. Never text */ + --ink: #1a1216; /* body text, wordmark, glyphs */ + --ink-60: rgb(26 18 22 / 0.6); + --ink-40: rgb(26 18 22 / 0.4); + --hairline: rgb(26 18 22 / 0.12); + --paper: #ffffff; + + --font-display: "Clash Display", "General Sans", ui-sans-serif, system-ui, sans-serif; + --font-sans: "General Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace; + + /* The Bloom Ease dips below its start: that's the overshoot. Settle is the calm exit. */ + --ease-bloom: cubic-bezier(0.22, 1.1, 0.36, 1); + --ease-settle: cubic-bezier(0.4, 0, 0.2, 1); +} + +::selection { + background: var(--blush); + color: var(--ink); +} + +:focus-visible { + outline: 2px solid var(--root); + outline-offset: 3px; +} + +/* Links grow a seed when you hover them: seven pixels of Bloom Red. */ +.seed-link { + position: relative; + color: inherit; + text-decoration: none; + transition: color 240ms var(--ease-settle); +} + +.seed-link::after { + content: ""; + position: absolute; + right: -11px; + bottom: 0.3em; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--bloom); + transform: scale(0); + transition: transform 240ms var(--ease-bloom); +} + +.seed-link:hover { + color: var(--root); +} + +.seed-link:hover::after { + transform: scale(1); +} + +@media (prefers-reduced-motion: reduce) { + .seed-link, + .seed-link::after { + transition: none; + } +} diff --git a/src/components/Counter.tsx b/src/components/Counter.tsx deleted file mode 100644 index ae4946d..0000000 --- a/src/components/Counter.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { createSignal } from "solid-js"; - -export default function Counter() { - const [count, setCount] = createSignal(0); - return ( - - ); -} diff --git a/src/components/FeatureCard.tsx b/src/components/FeatureCard.tsx index ae7ec99..0b1191c 100644 --- a/src/components/FeatureCard.tsx +++ b/src/components/FeatureCard.tsx @@ -1,36 +1,27 @@ -import { - Button, - Card, - CardActionArea, - CardActions, - CardContent, - CardHeader, - CardMedia, - Typography, -} from "@suid/material" +import Card from "@suid/material/Card"; +import CardContent from "@suid/material/CardContent"; +import Typography from "@suid/material/Typography"; +import type { JSX, ParentProps } from "solid-js"; -export default function FeatureCard(props) { +type FeatureCardProps = ParentProps<{ + /** Numbers are data, and data wears mono. */ + number: string; + title: JSX.Element; +}>; + +// Flat, hairline-bordered and square: paper and ink, no shadow. +export default function FeatureCard(props: FeatureCardProps) { return ( - - - {/* - Word of the Day - */} - - {props.title} + + + + {props.number} - {/* - adjective - */} - {/* */} - - {props.description} + + {props.title} {props.children} - {/* - - */} ); -} \ No newline at end of file +} diff --git a/src/components/FeaturesGrid.tsx b/src/components/FeaturesGrid.tsx index 5ce001a..34cffaa 100644 --- a/src/components/FeaturesGrid.tsx +++ b/src/components/FeaturesGrid.tsx @@ -1,183 +1,134 @@ -import { - Grid, - List, - ListItem, - ListItemIcon, - ListItemText, - Skeleton, - Stack, - Typography, -} from "@suid/material" -import FeatureCard from "~/components/FeatureCard"; +import Grid from "@suid/material/Grid"; +import List from "@suid/material/List"; +import ListItem from "@suid/material/ListItem"; +import ListItemIcon from "@suid/material/ListItemIcon"; +import ListItemText from "@suid/material/ListItemText"; +import Stack from "@suid/material/Stack"; +import type { SvgIconProps } from "@suid/material/SvgIcon"; +import Typography from "@suid/material/Typography"; +import { type Component, For } from "solid-js"; -import SevenTVIcon from "~/components/icon/SevenTVIcon"; -import TwitchIcon from "~/components/icon/TwitchIcon"; +import FeatureCard from "~/components/FeatureCard"; import BTTVIcon from "~/components/icon/BTTVIcon"; -import FFZIcon from "~/components/icon/FFZIcon"; import ChatterinoIcon from "~/components/icon/ChatterinoIcon"; +import FFZIcon from "~/components/icon/FFZIcon"; +import SevenTVIcon from "~/components/icon/SevenTVIcon"; +import TwitchIcon from "~/components/icon/TwitchIcon"; import SevenTVNamepaint from "~/components/SevenTVNamepaint"; +type Provider = { + name: string; + note?: string; + Icon: Component; +}; -export default function FeaturesGrid(props) { - return ( - +const twitch: Provider = { name: "Twitch", Icon: TwitchIcon }; +const sevenTV: Provider = { name: "7TV", Icon: SevenTVIcon }; +const bttv: Provider = { name: "BTTV", Icon: BTTVIcon }; +const ffz: Provider = { name: "FFZ", Icon: FFZIcon }; - - +const emoteProviders = [twitch, sevenTV, bttv, ffz]; - - - The following emote providers are supported: - - - - - - - - - - - - - - - - - - - - - - - - - - - +const badgeProviders = [ + twitch, + sevenTV, + bttv, + { ...ffz, note: "And FFZ:AP too." }, + { name: "Chatterino", Icon: ChatterinoIcon }, +]; + +// Provider logos are glyphs, and glyphs wear ink. Rows are ruled with hairlines. +function ProviderList(props: { providers: Provider[] }) { + return ( + + + {(provider) => ( + + + + + + + )} + + + ); +} + +export default function FeaturesGrid() { + return ( + + + + + Supported emote providers: + - + - Say hello to name paints from 7TV! - Like Festive Gold, 80's Pool and many more! + Say hello to name paints from 7TV, like{" "} + + Festive Gold + + ,{" "} + + 80's Pool + {" "} + and many more. - - - - - The following badge providers are supported: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* - - - - Chatterino Homies - } secondary="(optional)" /> - */} - - + + + Supported badge providers: + - - - - + + + - Any font you have on your system can be used! - We have some sweet defaults though, check out the VSauce font for example. + Any font you have on your system can be used. We have some sweet + defaults too, like the{" "} + + VSauce font + + . - - And commands! + + + And commands. + - Hide messages from bots and messages with commands. No more gambling addicts in your chat! + Hide messages from bots and messages with commands. No more gambling + spam in your chat. - - - {/* - - - Any font you have on your system can be used! - We have some sweet defaults though, check out the VSauce font for example. - - - - - - - - Any font you have on your system can be used! - We have some sweet defaults though, check out the VSauce font for example. - - - - - - - - Any font you have on your system can be used! - We have some sweet defaults though, check out the VSauce font for example. - - - */} - ); -} \ No newline at end of file +} diff --git a/src/components/MySiteTitle.tsx b/src/components/MySiteTitle.tsx index eab8f17..ccebca8 100644 --- a/src/components/MySiteTitle.tsx +++ b/src/components/MySiteTitle.tsx @@ -1,5 +1,7 @@ -import {Title} from "solid-start"; +import { Title } from "@solidjs/meta"; -export default function MySiteTitle(props) { - return {props.children} • ChatIS by IS2511; +// A single string: only holds text, and mixed children would be +// stringified with commas during SSR ("#,channel"). +export default function MySiteTitle(props: { children: string }) { + return <Title>{`${props.children} — ChatBloom`}; } diff --git a/src/components/SetupHero.module.css b/src/components/SetupHero.module.css new file mode 100644 index 0000000..6a6e26e --- /dev/null +++ b/src/components/SetupHero.module.css @@ -0,0 +1,39 @@ +.hero { + position: relative; + overflow: hidden; + padding: clamp(40px, 8vh, 88px) 16px clamp(64px, 11vh, 120px); + text-align: center; +} + +.bloom { + width: clamp(200px, 34vw, 340px); + aspect-ratio: 1; + margin: 0 auto clamp(32px, 6vh, 56px); +} + +/* Bloom Red text only from 24px up. */ +.red { + color: var(--bloom); +} + +/* Vertical marginalia carries the open call. */ +.vertical { + position: absolute; + top: 50%; + right: clamp(8px, 2vw, 24px); + margin: 0; + transform: translateY(-50%); + writing-mode: vertical-rl; + color: var(--ink-60); + font-family: var(--font-mono); + font-size: 0.65625rem; + letter-spacing: 0.22em; + text-transform: uppercase; + white-space: nowrap; +} + +@media (max-width: 1100px) { + .vertical { + display: none; + } +} diff --git a/src/components/SetupHero.tsx b/src/components/SetupHero.tsx new file mode 100644 index 0000000..96b4976 --- /dev/null +++ b/src/components/SetupHero.tsx @@ -0,0 +1,28 @@ +import Typography from "@suid/material/Typography"; + +import Bloom from "~/components/brand/Bloom"; +import Sprinkles from "~/components/brand/Sprinkles"; +import styles from "./SetupHero.module.css"; + +// One page, one flower, one promise: the flower owns the center, the statement +// orbits it, and the sky stays in the margins. +export default function SetupHero() { + return ( +
+ + + + ChatBloom — setup + + + Put your chat on screen. + + + ChatBloom is a Twitch chat overlay for streamers. + +

+ An open call for contributors, volunteers and curious coders +

+
+ ); +} diff --git a/src/components/SevenTVNamepaint.tsx b/src/components/SevenTVNamepaint.tsx index cf659e7..29fe1a1 100644 --- a/src/components/SevenTVNamepaint.tsx +++ b/src/components/SevenTVNamepaint.tsx @@ -1,21 +1,31 @@ +import type { JSX, ParentProps } from "solid-js"; +type SevenTVNamepaintProps = ParentProps<{ + /** Fallback text color, shown where the paint's background is transparent. */ + paintColor?: string; + paintBackground: string; + paintFilter?: string; + style?: JSX.CSSProperties; +}>; -export default function SevenTVNamepaint(props) { - // TODO: Fetch 7TV cosmetics from `https://7tv.io/v2/cosmetics?user_identifier=login` for example, - // construct styles from that, and memoize the result (`useMemo()`). - // Apply those styles to the `span` below based on the `props.login` prop or `props.paint` prop. - +// TODO: Fetch 7TV cosmetics for a user (by `login`) with `createResource` +// and derive the paint props from the result instead of passing them in. +export default function SevenTVNamepaint(props: SevenTVNamepaintProps) { return ( - {props.children} + + {props.children} + ); } diff --git a/src/components/SiteFooter.module.css b/src/components/SiteFooter.module.css new file mode 100644 index 0000000..1f40b1d --- /dev/null +++ b/src/components/SiteFooter.module.css @@ -0,0 +1,48 @@ +.footer { + padding: clamp(48px, 8vh, 88px) 0 28px; + border-top: 1px solid var(--hairline); +} + +.wordmark { + display: inline-block; + font-size: 1.75rem; + text-decoration: none; +} + +.lede { + max-width: 52ch; + margin: 18px 0 28px; + font-size: 1rem; + line-height: 1.65; +} + +/* Small red text wears Root Red: Bloom Red is for 24px and up. */ +.credit { + color: var(--root); + text-decoration: underline; + text-decoration-thickness: 1px; + text-underline-offset: 3px; +} + +.links { + display: flex; + flex-wrap: wrap; + gap: 12px 36px; + margin-bottom: clamp(48px, 8vh, 80px); + font-weight: 500; +} + +/* Marginalia: small because it is true, not because it is unimportant. */ +.margins { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 8px 24px; + padding-top: 20px; + border-top: 1px solid var(--hairline); + color: var(--ink-60); + font-family: var(--font-mono); + font-size: 0.6875rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} diff --git a/src/components/SiteFooter.tsx b/src/components/SiteFooter.tsx new file mode 100644 index 0000000..f144946 --- /dev/null +++ b/src/components/SiteFooter.tsx @@ -0,0 +1,45 @@ +import Container from "@suid/material/Container"; +import { For } from "solid-js"; + +import Wordmark from "~/components/brand/Wordmark"; +import styles from "./SiteFooter.module.css"; + +const links = [ + { label: "Source on GitHub", href: "https://github.com/shiftbloom-studio/ChatBloom" }, + { label: "shiftbloom.studio", href: "https://shiftbloom.studio" }, + { label: "Open Collective", href: "https://opencollective.com/shiftbloom-studio" }, + { label: "hello@shiftbloom.studio", href: "mailto:hello@shiftbloom.studio" }, +]; + +export default function SiteFooter() { + return ( + +
+ + + +

+ ChatBloom is tended by shiftbloom studio, an open digital studio in Hamburg. It + grew from{" "} + + ChatIS by IS2511 + + . +

+ +
+ Open by default · A little informal · Based in Hamburg + chat.shiftbloom.studio — 53.55°N 9.99°E +
+
+
+ ); +} diff --git a/src/components/SiteHeader.module.css b/src/components/SiteHeader.module.css new file mode 100644 index 0000000..dd98435 --- /dev/null +++ b/src/components/SiteHeader.module.css @@ -0,0 +1,35 @@ +.bar { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 16px; + padding: 22px 0; + border-bottom: 1px solid var(--hairline); +} + +.product { + color: var(--ink); + font-family: var(--font-display); + font-size: 1.375rem; + font-weight: 600; + letter-spacing: -0.02em; + text-decoration: none; +} + +.seed { + color: var(--bloom); +} + +.studio { + font-size: 0.9375rem; + text-decoration: none; + white-space: nowrap; +} + +.by { + color: var(--ink-60); + font-family: var(--font-mono); + font-size: 0.6875rem; + letter-spacing: 0.16em; + text-transform: uppercase; +} diff --git a/src/components/SiteHeader.tsx b/src/components/SiteHeader.tsx new file mode 100644 index 0000000..3950b6f --- /dev/null +++ b/src/components/SiteHeader.tsx @@ -0,0 +1,19 @@ +import Container from "@suid/material/Container"; + +import Wordmark from "~/components/brand/Wordmark"; +import styles from "./SiteHeader.module.css"; + +export default function SiteHeader() { + return ( + + + + ); +} diff --git a/src/components/brand/Bloom.module.css b/src/components/brand/Bloom.module.css new file mode 100644 index 0000000..b40960a --- /dev/null +++ b/src/components/brand/Bloom.module.css @@ -0,0 +1,51 @@ +.bloom { + display: block; + overflow: visible; +} + +/* Unfold, don't slide: the flower opens outward from its center on the Bloom + Ease, then breathes between 1.00 and 1.02 in six-second cycles. */ +.alive { + animation: + unfurl 600ms var(--ease-bloom) both, + breathe 3s ease-in-out 600ms infinite alternate; +} + +/* Its rings counter-rotate, slowly, like it has time. The view box is centered + on the seed, so its origin is the flower's center. */ +.alive .ring { + transform-box: view-box; + transform-origin: 0 0; + animation: turn 480s linear infinite; +} + +.alive .ring:nth-of-type(even) { + animation-duration: 600s; + animation-direction: reverse; +} + +@keyframes unfurl { + from { + opacity: 0; + transform: scale(0.6); + } +} + +@keyframes breathe { + to { + transform: scale(1.02); + } +} + +@keyframes turn { + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: reduce) { + .alive, + .alive .ring { + animation: none; + } +} diff --git a/src/components/brand/Bloom.tsx b/src/components/brand/Bloom.tsx new file mode 100644 index 0000000..9928b55 --- /dev/null +++ b/src/components/brand/Bloom.tsx @@ -0,0 +1,105 @@ +import { createUniqueId, For, Show } from "solid-js"; + +import styles from "./Bloom.module.css"; + +// The Bloom, drawn from the construction spec in Brand Book v2.0 §04: four +// petal rings and a seed, symmetry 36°. Same geometry as the studio's +// bloom.svg master. When in doubt, rebuild from the numbers. +type Ring = { + /** Distance from the center to the petal tip (half the ring diameter). */ + tip: number; + petals: number; + /** Half-width of the petal's control polygon. */ + width: number; + /** Root to tip. */ + length: number; + /** Stem to tip: darker at the center, lighter at the edge. Flip it and the flower wilts. */ + from: string; + to: string; + /** Rotation in degrees, so neighbouring rings interleave. */ + offset: number; +}; + +// biome-ignore format: one ring per row, so it reads like the spec table +const RINGS: Ring[] = [ + { tip: 100, petals: 10, width: 32, length: 90, from: "#D8163F", to: "#FF2E52", offset: 0 }, + { tip: 74, petals: 10, width: 25.16, length: 66.6, from: "#FF2E52", to: "#FF7E9E", offset: 18 }, + { tip: 50, petals: 8, width: 19, length: 45, from: "#FF7E9E", to: "#FFC2D3", offset: 40.5 }, + { tip: 27, petals: 6, width: 12.96, length: 24.3, from: "#FFB4CA", to: "#FFDCE7", offset: 70.5 }, +]; + +const BLOOM_RED = "#FF2E52"; +const ROOT_RED = "#C81040"; + +/** One cubic pair, mirrored: root at the origin, tip at (0, -length). */ +function petal({ width: w, length: l }: Ring) { + const c = Math.round(55 * l) / 100; + return `M 0,0 C ${w},0 ${w},${-c} 0,${-l} C ${-w},${-c} ${-w},0 0,0 Z`; +} + +function angles(ring: Ring) { + return Array.from({ length: ring.petals }, (_, i) => ring.offset + (360 / ring.petals) * i); +} + +type BloomProps = { + /** Full gradient from 32px up. Smaller than that, only the solid bloom reads. */ + variant?: "gradient" | "solid"; + /** Unfurl on load, then breathe while idle as the rings counter-rotate. */ + alive?: boolean; + /** Accessible name. Without one the mark is decorative. */ + title?: string; + class?: string; +}; + +export default function Bloom(props: BloomProps) { + const id = `bloom-${createUniqueId()}`; + const solid = () => props.variant === "solid"; + + return ( + + + + + {(ring, i) => ( + + + + + )} + + + + + {(ring, i) => ( + + + {(angle) => ( + + )} + + + )} + + {/* The seed. In the solid bloom it darkens to Root Red so it stays visible. */} + + + ); +} diff --git a/src/components/brand/Sprinkles.module.css b/src/components/brand/Sprinkles.module.css new file mode 100644 index 0000000..df35243 --- /dev/null +++ b/src/components/brand/Sprinkles.module.css @@ -0,0 +1,67 @@ +.sky { + position: absolute; + inset: 0; + pointer-events: none; +} + +/* They twinkle at 4–5s intervals, staggered. A sprinkle that blinks in sync + with another is a crowd, not a sky. */ +.sprinkle { + position: absolute; + display: block; + overflow: visible; + animation: twinkle 4.5s ease-in-out infinite; +} + +.sprinkle:nth-child(2) { + animation-duration: 4s; + animation-delay: -1.3s; +} + +.sprinkle:nth-child(3) { + animation-duration: 5s; + animation-delay: -2.6s; +} + +.sprinkle:nth-child(4) { + animation-duration: 4.2s; + animation-delay: -0.7s; +} + +.sprinkle:nth-child(5) { + animation-duration: 4.7s; + animation-delay: -3.4s; +} + +.sprinkle:nth-child(6) { + animation-duration: 4.4s; + animation-delay: -1.9s; +} + +.bloom { + fill: var(--bloom); +} + +.ink { + fill: var(--ink); +} + +/* Dimmed, never so far that red reads as blush: invisible means absent. */ +@keyframes twinkle { + 50% { + opacity: 0.45; + } +} + +/* On narrow screens the margins are too tight to hold a sky. */ +@media (max-width: 760px) { + .sky { + display: none; + } +} + +@media (prefers-reduced-motion: reduce) { + .sprinkle { + animation: none; + } +} diff --git a/src/components/brand/Sprinkles.tsx b/src/components/brand/Sprinkles.tsx new file mode 100644 index 0000000..2d11d84 --- /dev/null +++ b/src/components/brand/Sprinkles.tsx @@ -0,0 +1,51 @@ +import { For, type JSX } from "solid-js"; + +import styles from "./Sprinkles.module.css"; + +// The sky around the flower (Brand Book v2.0 §07): a spark, a cross and a dot. +// At most seven per composition, red or ink only on white, margins only, and +// never inside the mark's clearspace (a quarter of its height on every side). +const glyphs = { + spark: () => , + cross: () => , + dot: () => , +}; + +type Sprinkle = { + glyph: keyof typeof glyphs; + color: "bloom" | "ink"; + /** Spark 8–24px, cross 8–16px, dot 6–10px. */ + size: number; + /** Where it sits in the margin, as CSS insets. */ + at: JSX.CSSProperties; +}; + +const sky: Sprinkle[] = [ + { glyph: "spark", color: "bloom", size: 24, at: { top: "14%", left: "15%" } }, + { glyph: "dot", color: "bloom", size: 7, at: { top: "34%", left: "6%" } }, + { glyph: "cross", color: "ink", size: 12, at: { top: "11%", right: "20%" } }, + { glyph: "dot", color: "ink", size: 6, at: { top: "37%", right: "12%" } }, + { glyph: "spark", color: "ink", size: 15, at: { top: "57%", right: "19%" } }, + { glyph: "cross", color: "bloom", size: 10, at: { top: "61%", left: "11%" } }, +]; + +export default function Sprinkles() { + return ( +
+ + {(sprinkle) => ( + + )} + +
+ ); +} diff --git a/src/components/brand/Wordmark.module.css b/src/components/brand/Wordmark.module.css new file mode 100644 index 0000000..ee03779 --- /dev/null +++ b/src/components/brand/Wordmark.module.css @@ -0,0 +1,16 @@ +.wordmark { + font-family: var(--font-display); + font-weight: 600; + letter-spacing: -0.02em; + white-space: nowrap; + color: var(--ink); +} + +.studio { + font-weight: 400; + color: var(--ink-60); +} + +.seed { + color: var(--bloom); +} diff --git a/src/components/brand/Wordmark.tsx b/src/components/brand/Wordmark.tsx new file mode 100644 index 0000000..af99d64 --- /dev/null +++ b/src/components/brand/Wordmark.tsx @@ -0,0 +1,13 @@ +import styles from "./Wordmark.module.css"; + +// "shiftbloom" display semibold, "studio" display regular at 60% ink, and the +// seed, which is always Bloom Red. Never capitalize, hyphenate, split across +// lines or drop the seed. +export default function Wordmark(props: { class?: string }) { + return ( + + shiftbloom studio + . + + ); +} diff --git a/src/components/chat/Chat.module.css b/src/components/chat/Chat.module.css new file mode 100644 index 0000000..81c660f --- /dev/null +++ b/src/components/chat/Chat.module.css @@ -0,0 +1,148 @@ +.overlay { + /* --emote-height and --badge-height come from ChatOverlay.tsx, which also needs them. */ + --chat-font-size: 20px; + + position: fixed; + inset: 0; + display: flex; + flex-direction: column; + justify-content: flex-end; + overflow: hidden; + padding: 12px; + color: #fff; + font: 600 var(--chat-font-size) / 1.5 var(--chat-font, "Roboto", system-ui, sans-serif); + text-shadow: + 0 0 2px #000, + 0 0 2px #000, + 1px 1px 2px #000; +} + +.line { + padding: 2px 0; + overflow-wrap: anywhere; + animation: line-in 150ms ease-out; +} + +@keyframes line-in { + from { + opacity: 0; + translate: 0 6px; + } +} + +.system { + color: #bfb3ff; + font-style: italic; +} + +.action { + font-style: italic; +} + +.badges { + display: inline; +} + +.badge { + height: var(--badge-height); + margin-right: 4px; + vertical-align: -4px; + border-radius: 3px; +} + +.name { + font-weight: 700; +} + +.mention { + font-weight: 700; +} + +.emote { + display: inline-grid; + place-items: center; + vertical-align: middle; + /* Emotes may be taller than a line of text; don't let them push lines apart. */ + margin: -8px 0; +} + +.emote > * { + grid-area: 1 / 1; + height: var(--emote-height); +} + +.slide { + display: inline-block; + background-repeat: repeat-x; + background-size: auto 100%; +} + +/* 7TV paints: every layer repeats the name, clipped to its text, stacked in one grid cell. */ +.paint { + display: inline-grid; + text-shadow: none; +} + +.paintLayer { + grid-area: 1 / 1; + background-color: currentColor; + background-size: 100% 100%; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +/* BTTV Pro username effects. */ +.glow { + text-shadow: 0 0 20px color-mix(in srgb, currentColor 80%, transparent); +} + +@keyframes flare-sweep { + 0% { + background-position: + 200% center, + center; + } + 50%, + 100% { + background-position: + -200% center, + center; + } +} + +.flare { + background-image: + linear-gradient( + 75deg, + transparent 40%, + color-mix(in srgb, currentColor 20%, white) 50%, + transparent 60% + ), + linear-gradient(currentColor, currentColor); + background-size: + 200% 100%, + 100% 100%; + background-repeat: no-repeat; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + text-shadow: none; + animation: flare-sweep 8s linear infinite; +} + +@keyframes texture-sweep { + to { + background-position: -96px -96px; + } +} + +.texture { + background-size: 96px 96px; + background-repeat: repeat; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + text-shadow: none; + animation: texture-sweep 16s linear infinite; +} diff --git a/src/components/chat/ChatLine.tsx b/src/components/chat/ChatLine.tsx new file mode 100644 index 0000000..4133143 --- /dev/null +++ b/src/components/chat/ChatLine.tsx @@ -0,0 +1,63 @@ +import { createMemo, For, Show } from "solid-js"; + +import type { ChatMessage, ChatSession } from "~/lib/chat/session"; +import type { Badge } from "~/lib/chat/types"; + +import styles from "./Chat.module.css"; +import EmoteView, { sizedSrcset } from "./EmoteView"; +import Username from "./Username"; + +export const BADGE_HEIGHT = 22; + +function BadgeImage(props: { badge: Badge }) { + return ( + {props.badge.title} + ); +} + +export default function ChatLine(props: { session: ChatSession; message: ChatMessage }) { + const parts = createMemo(() => props.session.parts(props.message)); + const badges = createMemo(() => props.session.badges(props.message)); + const user = () => props.session.user(props.message); + + return ( +
+ +
{props.message.system}
+
+ + + {(badge) => } + + + {props.message.action ? " " : ": "} + + + {(part) => { + if (part.type === "text") return part.text; + if (part.type === "mention") { + return {part.text}; + } + return ; + }} + + + +
+ ); +} diff --git a/src/components/chat/ChatOverlay.tsx b/src/components/chat/ChatOverlay.tsx new file mode 100644 index 0000000..f052a00 --- /dev/null +++ b/src/components/chat/ChatOverlay.tsx @@ -0,0 +1,25 @@ +import "./overlay.css"; + +import { For } from "solid-js"; + +import type { ChatSession } from "~/lib/chat/session"; + +import styles from "./Chat.module.css"; +import ChatLine, { BADGE_HEIGHT } from "./ChatLine"; +import { EMOTE_HEIGHT } from "./EmoteView"; +import { BTTVEffectFilters } from "./Username"; + +export default function ChatOverlay(props: { session: ChatSession }) { + return ( +
+ + + {(message) => } + +
+ ); +} diff --git a/src/components/chat/EmoteView.tsx b/src/components/chat/EmoteView.tsx new file mode 100644 index 0000000..395645a --- /dev/null +++ b/src/components/chat/EmoteView.tsx @@ -0,0 +1,103 @@ +import { createSignal, For, type JSX, Show } from "solid-js"; + +import type { EmotePart } from "~/lib/chat/tokenize"; +import type { Emote, ImageSet } from "~/lib/chat/types"; + +import styles from "./Chat.module.css"; + +export const EMOTE_HEIGHT = 32; + +/** + * Width descriptors scaled from a base height, so the browser picks an image for the rendered + * size rather than only for the device pixel ratio. Aspect ratio doesn't matter here: it is the + * same at every density. + */ +export function sizedSrcset(images: ImageSet, baseHeight: number): string { + return Object.entries(images) + .map(([density, url]) => `${url} ${Math.round(baseHeight * Number(density))}w`) + .join(", "); +} + +function EmoteImage(props: { + emote: Emote; + style?: JSX.CSSProperties; + onAspect?: (a: number) => void; +}) { + return ( + {props.emote.name} { + const img = event.currentTarget; + props.onAspect?.(img.naturalWidth / img.naturalHeight); + }} + /> + ); +} + +export default function EmoteView(props: { part: EmotePart }) { + const [loadedAspect, setLoadedAspect] = createSignal(); + const effects = () => props.part.effects; + + const aspect = () => { + const { width, height } = props.part.emote; + return width && height ? width / height : loadedAspect(); + }; + /** Explicit width in px, only when an effect changes it. */ + const width = () => { + const fx = effects(); + if (fx.aspectRatio) return EMOTE_HEIGHT * fx.aspectRatio; + if (fx.widthScale === 1 && !fx.slide) return undefined; + // A sliding emote is a background with nothing to measure, so assume square if unknown. + const ratio = aspect() ?? (fx.slide ? 1 : undefined); + return ratio ? EMOTE_HEIGHT * ratio * fx.widthScale : undefined; + }; + + const style = (): JSX.CSSProperties => { + const fx = effects(); + const filter = fx.filters.join(" ") || undefined; + const w = width(); + const animations = [...fx.animations]; + if (fx.slide && w) + animations.push(`cb-fx-slide ${(w / EMOTE_HEIGHT) * 1.5}s linear infinite`); + return { + width: w === undefined ? undefined : `${w}px`, + "object-fit": w === undefined ? undefined : "fill", + transform: fx.transforms.join(" ") || undefined, + "transform-origin": fx.transformOrigin, + filter, + "--cb-fx-f": filter, + "--cb-slide-width": fx.slide && w ? `${w}px` : undefined, + animation: animations.join(", ") || undefined, + }; + }; + + return ( + + + } + > + + + {(overlay) => } + + ); +} diff --git a/src/components/chat/Username.tsx b/src/components/chat/Username.tsx new file mode 100644 index 0000000..d70e156 --- /dev/null +++ b/src/components/chat/Username.tsx @@ -0,0 +1,135 @@ +import { For, Match, Show, Switch } from "solid-js"; + +import type { BTTVUsernameEffect } from "~/lib/chat/providers/bttv"; +import type { Paint } from "~/lib/chat/providers/seventv/paint"; + +import styles from "./Chat.module.css"; + +/** BTTV's texture effects, with the outline color each one's SVG stroke filter uses. */ +export const BTTV_TEXTURES: Partial> = { + iridescence: "#2f395f", + supernova: "#702838", + glacier: "#166258", + intergalactic: "#4e316c", + midas: "#674911", +}; + +function PaintedText(props: { paint: Paint; text: string }) { + return ( + 0} + fallback={ + + {props.text} + + } + > + + + {(layer, index) => ( + 0} + style={{ + opacity: layer.opacity, + "background-image": layer.image, + "background-color": layer.color, + // Shadows go on the first layer only, like 7TV renders them. + filter: index() === 0 ? props.paint.filter : undefined, + }} + > + {props.text} + + )} + + + + ); +} + +function BTTVEffectText(props: { effect: BTTVUsernameEffect; text: string }) { + const texture = () => BTTV_TEXTURES[props.effect] !== undefined; + return ( + + {props.text} + + ); +} + +/** A chatter's name in their color, wearing their 7TV paint or else their BTTV effect. */ +export default function Username(props: { + name: string; + color: string; + paint?: Paint; + effect?: BTTVUsernameEffect; +}) { + return ( + + + + {(paint) => } + + + {(effect) => } + + + + ); +} + +/** SVG outline filters the BTTV texture effects reference; rendered once per overlay. */ +export function BTTVEffectFilters() { + return ( + + ); +} diff --git a/src/components/chat/overlay.css b/src/components/chat/overlay.css new file mode 100644 index 0000000..a3fac95 --- /dev/null +++ b/src/components/chat/overlay.css @@ -0,0 +1,164 @@ +/* Global on purpose: emote effects reference these keyframes from inline styles, and OBS needs + the page itself transparent. The chat route sets `data-chat-overlay` while it is mounted; + the selector outranks SUID's CssBaseline `body` rule, which is injected later at runtime. */ +html[data-chat-overlay], +html[data-chat-overlay] body { + /* biome-ignore lint/complexity/noImportantStyles: must beat the runtime-injected theme background */ + background: transparent !important; +} + +/* Animations use the individual `translate` / `rotate` / `scale` properties so they compose with + the static `transform` (flips, rotations) instead of replacing it. Filter animations prepend + `--cb-fx-f`, the emote's static filters. */ + +@keyframes cb-fx-rainbow { + from { + filter: var(--cb-fx-f,) hue-rotate(0deg); + } + to { + filter: var(--cb-fx-f,) hue-rotate(360deg); + } +} + +@keyframes cb-fx-party { + from { + filter: var(--cb-fx-f,) sepia(0.5) hue-rotate(0deg) saturate(2.5); + } + to { + filter: var(--cb-fx-f,) sepia(0.5) hue-rotate(360deg) saturate(2.5); + } +} + +@keyframes cb-fx-shake { + 0% { + translate: 1px 1px; + } + 25% { + translate: -2px -1px; + } + 50% { + translate: 2px 2px; + } + 75% { + translate: -1px 2px; + } + 100% { + translate: 1px -1px; + } +} + +@keyframes cb-fx-bttv-shake { + 0% { + translate: 0 1px; + } + 20% { + translate: 2px 0; + } + 40% { + translate: 1px -2px; + } + 60% { + translate: -2px 1px; + } + 80% { + translate: 0 -1px; + } +} + +@keyframes cb-fx-jam { + 0%, + 100% { + translate: -2px -2px; + rotate: -6deg; + } + 30% { + translate: 3px 2px; + rotate: -5deg; + } + 50% { + translate: 2px 4px; + rotate: 3deg; + } + 75% { + translate: -1px 2px; + rotate: 1deg; + } +} + +@keyframes cb-fx-bounce { + 0%, + 100% { + scale: 0.8 1; + } + 25% { + scale: 1.2 0.4; + } + 50% { + scale: 0.9 1.15; + } + 75% { + scale: 1 0.9; + } +} + +@keyframes cb-fx-spin { + from { + rotate: 0deg; + } + to { + rotate: 360deg; + } +} + +@keyframes cb-fx-appear { + 0%, + 20% { + translate: -18px 0; + scale: 0; + } + 65%, + 100% { + translate: 0 0; + scale: 1; + } +} + +@keyframes cb-fx-leave { + 0%, + 40% { + translate: 0 0; + scale: 1; + } + 85%, + 100% { + translate: -18px 0; + scale: 0; + } +} + +@keyframes cb-fx-in-out { + 0%, + 10% { + translate: -18px 0; + scale: 0; + } + 32%, + 70% { + translate: 0 0; + scale: 1; + } + 92%, + 100% { + translate: -18px 0; + scale: 0; + } +} + +@keyframes cb-fx-slide { + from { + background-position-x: 0; + } + to { + background-position-x: calc(-1 * var(--cb-slide-width)); + } +} diff --git a/src/components/icon/BTTVBadIcon.tsx b/src/components/icon/BTTVBadIcon.tsx deleted file mode 100644 index b8daecd..0000000 --- a/src/components/icon/BTTVBadIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { SvgIcon } from "@suid/material"; - -export default function BTTVBadIcon(props) { - return ( - - - - - - - - - - ); -} diff --git a/src/components/icon/BTTVIcon.tsx b/src/components/icon/BTTVIcon.tsx index 4ea5e96..fb5029e 100644 --- a/src/components/icon/BTTVIcon.tsx +++ b/src/components/icon/BTTVIcon.tsx @@ -1,14 +1,9 @@ -import { SvgIcon } from "@suid/material"; +import SvgIcon, { type SvgIconProps } from "@suid/material/SvgIcon"; -export default function BTTVBadIcon(props) { +export default function BTTVIcon(props: SvgIconProps) { return ( - - - - + + ); } diff --git a/src/components/icon/ChatterinoIcon.tsx b/src/components/icon/ChatterinoIcon.tsx index ddba191..0409098 100644 --- a/src/components/icon/ChatterinoIcon.tsx +++ b/src/components/icon/ChatterinoIcon.tsx @@ -1,21 +1,16 @@ -import { SvgIcon } from "@suid/material"; +import SvgIcon, { type SvgIconProps } from "@suid/material/SvgIcon"; -// The SVG was generated from the original Chatterino icon using https://www.vectorizer.io/ -// Hand-edited to make it look better - -export default function ChatterinoIcon(props) { +// Vectorized from the original Chatterino icon with https://www.vectorizer.io/, +// then hand-edited into a single stroked outline. +export default function ChatterinoIcon(props: SvgIconProps) { return ( - - {/* */} - - - - - - - + + ); } diff --git a/src/components/icon/ChatterinoIconColored.tsx b/src/components/icon/ChatterinoIconColored.tsx deleted file mode 100644 index 4b61ff6..0000000 --- a/src/components/icon/ChatterinoIconColored.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { SvgIcon } from "@suid/material"; - -// The SVG was generated from the original Chatterino icon using https://www.vectorizer.io/ - -export default function ChatterinoIconColored(props) { - return ( - - - - - - - - - - - - - - - ); -} diff --git a/src/components/icon/FFZIcon.tsx b/src/components/icon/FFZIcon.tsx index f5de458..2c77a7c 100644 --- a/src/components/icon/FFZIcon.tsx +++ b/src/components/icon/FFZIcon.tsx @@ -1,16 +1,9 @@ -import { SvgIcon } from "@suid/material"; +import SvgIcon, { type SvgIconProps } from "@suid/material/SvgIcon"; -export default function FFZIcon(props) { +export default function FFZIcon(props: SvgIconProps) { return ( - - - - - - + + ); } diff --git a/src/components/icon/SevenTVIcon.tsx b/src/components/icon/SevenTVIcon.tsx index ae8e1b8..64f67f2 100644 --- a/src/components/icon/SevenTVIcon.tsx +++ b/src/components/icon/SevenTVIcon.tsx @@ -1,21 +1,11 @@ -import { SvgIcon } from "@suid/material"; +import SvgIcon, { type SvgIconProps } from "@suid/material/SvgIcon"; -export default function SevenTVIcon(props) { +export default function SevenTVIcon(props: SvgIconProps) { return ( - - - - - - - - + + + + ); } diff --git a/src/components/icon/TwitchIcon.tsx b/src/components/icon/TwitchIcon.tsx index 909788d..8dc2af7 100644 --- a/src/components/icon/TwitchIcon.tsx +++ b/src/components/icon/TwitchIcon.tsx @@ -1,34 +1,13 @@ -import { SvgIcon } from "@suid/material"; +import SvgIcon, { type SvgIconProps } from "@suid/material/SvgIcon"; -// The SVG code is from https://brand.twitch.tv/ (official Twitch brand assets) -// In particular from "TwitchGlitchWhite.svg" -// I've removed the XML header, the title tag and the XML namespace declarations. - -export default function TwitchIcon(props) { +// Paths from the official Twitch brand assets (https://brand.twitch.tv/), +// "TwitchGlitchWhite.svg", reduced to the glyph so it inherits `currentColor`. +export default function TwitchIcon(props: SvgIconProps) { return ( - - {/* - */} - {/* xmlns:xlink="http://www.w3.org/1999/xlink" */} - {/* xml:space="preserve" */} - - - {/* Asset 3 */} - - {/* */} - - - - - - - - - + + + + ); } diff --git a/src/components/icon/TwitchIconPurple.tsx b/src/components/icon/TwitchIconPurple.tsx deleted file mode 100644 index 94febcb..0000000 --- a/src/components/icon/TwitchIconPurple.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { SvgIcon } from "@suid/material"; - -// The SVG code is from https://brand.twitch.tv/ (official Twitch brand assets) -// In particular from "TwitchGlitchPurple.svg" -// I've removed the XML header, the title tag and the XML namespace declarations. - -export default function TwitchIconPurple(props) { - return ( - - {/* - */} - {/* xmlns:xlink="http://www.w3.org/1999/xlink" */} - {/* xml:space="preserve" */} - - - {/* Asset 2 */} - - - - - - - - - - - - - ); -} diff --git a/src/entry-client.tsx b/src/entry-client.tsx index 9422848..4d1d418 100644 --- a/src/entry-client.tsx +++ b/src/entry-client.tsx @@ -1,3 +1,5 @@ -import { mount, StartClient } from "solid-start/entry-client"; +// @refresh reload +import { mount, StartClient } from "@solidjs/start/client"; -mount(() => , document); +// biome-ignore lint/style/noNonNullAssertion: #app is always rendered by entry-server.tsx +mount(() => , document.getElementById("app")!); diff --git a/src/entry-server.tsx b/src/entry-server.tsx index 11c445b..7375673 100644 --- a/src/entry-server.tsx +++ b/src/entry-server.tsx @@ -1,9 +1,43 @@ -import { - StartServer, - createHandler, - renderAsync, -} from "solid-start/entry-server"; +// @refresh reload +import { createHandler, StartServer } from "@solidjs/start/server"; -export default createHandler( - renderAsync((event) => ) -); +export default createHandler(() => ( + ( + + + + + + + + + + + {/* Clash Display and General Sans come from Fontshare: the ITF Free Font + License allows self-hosting, but not redistributing the files in a + public repository. One family per request: combined requests only + return the first family. */} + + + + + {assets} + + +
{children}
+ {scripts} + + + )} + /> +)); diff --git a/src/lib/chat/effects.ts b/src/lib/chat/effects.ts new file mode 100644 index 0000000..9626dcd --- /dev/null +++ b/src/lib/chat/effects.ts @@ -0,0 +1,133 @@ +/** + * Emote modifier effects from BTTV (`w!`, `h!`, ...) and FFZ (`ffzX`, `ffzHyper`, ...), reduced to + * CSS. Static transforms and filters are also exposed as custom properties so the animation + * keyframes in `effects.css` can compose with them instead of overwriting them. + */ +export interface EmoteEffects { + transforms: string[]; + filters: string[]; + animations: string[]; + transformOrigin?: string; + /** FFZ GrowX: multiply the natural width. */ + widthScale: number; + /** BTTV `w!`: force this width-to-height ratio. */ + aspectRatio?: number; + /** Removes the space before the emote. */ + noSpace: boolean; + /** FFZ Slide: the image scrolls horizontally inside its box. */ + slide: boolean; +} + +export const NO_EFFECTS: EmoteEffects = { + transforms: [], + filters: [], + animations: [], + widthScale: 1, + noSpace: false, + slide: false, +}; + +export function hasEffects(effects: EmoteEffects): boolean { + return effects !== NO_EFFECTS; +} + +const CURSED = "grayscale(1) brightness(0.7) contrast(2.5)"; + +type Change = Partial> & { + transform?: string; + filter?: string; + animation?: string; +}; + +function apply(effects: EmoteEffects, change: Change): EmoteEffects { + const { transform, filter, animation, ...rest } = change; + return { + ...effects, + ...rest, + transforms: transform ? [...effects.transforms, transform] : effects.transforms, + filters: filter ? [...effects.filters, filter] : effects.filters, + animations: animation ? [...effects.animations, animation] : effects.animations, + }; +} + +const BTTV_MODIFIERS: Record = { + "w!": { aspectRatio: 4 }, + "h!": { transform: "scaleX(-1)" }, + "v!": { transform: "scaleY(-1)" }, + "z!": { noSpace: true }, + "c!": { filter: CURSED }, + "l!": { transform: "rotate(-90deg)" }, + "r!": { transform: "rotate(90deg)" }, + "p!": { animation: "cb-fx-party 1.5s linear infinite" }, + "s!": { animation: "cb-fx-bttv-shake 0.5s step-start infinite" }, +}; + +export const BTTV_MODIFIER_CODES = new Set(Object.keys(BTTV_MODIFIERS)); + +export function applyBttvModifier(effects: EmoteEffects, code: string): EmoteEffects { + const change = BTTV_MODIFIERS[code]; + return change ? apply(effects, change) : effects; +} + +/** FFZ `modifier_flags` bits, in FFZ's order. */ +export const FfzFlag = { + Hidden: 1 << 0, + FlipX: 1 << 1, + FlipY: 1 << 2, + GrowX: 1 << 3, + Slide: 1 << 4, + Appear: 1 << 5, + Leave: 1 << 6, + Rotate: 1 << 7, + Rotate90: 1 << 8, + Greyscale: 1 << 9, + Sepia: 1 << 10, + Rainbow: 1 << 11, + HyperRed: 1 << 12, + Shake: 1 << 13, + Cursed: 1 << 14, + Jam: 1 << 15, + Bounce: 1 << 16, + NoSpace: 1 << 17, +} as const; + +const FFZ_EFFECTS: [flag: number, change: Change][] = [ + [FfzFlag.FlipX, { transform: "scaleX(-1)" }], + [FfzFlag.FlipY, { transform: "scaleY(-1)" }], + [FfzFlag.GrowX, { widthScale: 2 }], + [FfzFlag.Slide, { slide: true }], + [FfzFlag.Rotate90, { transform: "rotate(90deg)" }], + [FfzFlag.Greyscale, { filter: "grayscale(1)" }], + [FfzFlag.Sepia, { filter: "sepia(1)" }], + [ + FfzFlag.HyperRed, + { filter: "brightness(0.2) sepia(1) brightness(2.2) contrast(3) saturate(8)" }, + ], + [FfzFlag.Cursed, { filter: CURSED }], + [FfzFlag.Rainbow, { animation: "cb-fx-rainbow 2s linear infinite" }], + [FfzFlag.Shake, { animation: "cb-fx-shake 0.1s linear infinite" }], + [FfzFlag.Jam, { animation: "cb-fx-jam 0.6s linear infinite" }], + [ + FfzFlag.Bounce, + { animation: "cb-fx-bounce 0.5s linear infinite", transformOrigin: "bottom center" }, + ], + [FfzFlag.NoSpace, { noSpace: true }], +]; + +export function applyFfzFlags(effects: EmoteEffects, flags: number): EmoteEffects { + let result = effects; + for (const [flag, change] of FFZ_EFFECTS) { + if (flags & flag) result = apply(result, change); + } + + // These three animate the whole transform, so they are exclusive like in FFZ. + const appear = (flags & FfzFlag.Appear) !== 0; + const leave = (flags & FfzFlag.Leave) !== 0; + if (appear && leave) result = apply(result, { animation: "cb-fx-in-out 6s linear infinite" }); + else if (appear) result = apply(result, { animation: "cb-fx-appear 3s linear infinite" }); + else if (leave) result = apply(result, { animation: "cb-fx-leave 3s linear infinite" }); + else if (flags & FfzFlag.Rotate && !(flags & FfzFlag.Slide)) { + result = apply(result, { animation: "cb-fx-spin 1.5s linear infinite" }); + } + return result; +} diff --git a/src/lib/chat/emote-set.ts b/src/lib/chat/emote-set.ts new file mode 100644 index 0000000..a0b49ab --- /dev/null +++ b/src/lib/chat/emote-set.ts @@ -0,0 +1,49 @@ +import type { Emote } from "./types"; + +/** A mutable set of emotes, keyed by id for live updates and by name for tokenizing. */ +export class EmoteSet { + readonly #byId = new Map(); + #byName: Map | undefined; + + constructor(emotes: Iterable = []) { + for (const emote of emotes) this.#byId.set(emote.id, emote); + } + + get size(): number { + return this.#byId.size; + } + + get(name: string): Emote | undefined { + if (!this.#byName) { + this.#byName = new Map(); + for (const emote of this.#byId.values()) { + // The first emote to claim a name wins, like on Twitch. + if (!this.#byName.has(emote.name)) this.#byName.set(emote.name, emote); + } + } + return this.#byName.get(name); + } + + getById(id: string): Emote | undefined { + return this.#byId.get(id); + } + + add(emote: Emote): void { + this.#byId.set(emote.id, emote); + this.#byName = undefined; + } + + remove(id: string): Emote | undefined { + const emote = this.#byId.get(id); + if (emote) { + this.#byId.delete(id); + this.#byName = undefined; + } + return emote; + } + + rename(id: string, name: string): void { + const emote = this.#byId.get(id); + if (emote) this.add({ ...emote, name }); + } +} diff --git a/src/lib/chat/irc/client.ts b/src/lib/chat/irc/client.ts new file mode 100644 index 0000000..6ddd693 --- /dev/null +++ b/src/lib/chat/irc/client.ts @@ -0,0 +1,62 @@ +import { ReconnectingSocket } from "../socket"; +import { type IrcMessage, parseIrcLine } from "./parse"; + +const IRC_URL = "wss://irc-ws.chat.twitch.tv:443"; +const PING_INTERVAL_MS = 60_000; + +export type IrcStatus = "connecting" | "connected"; + +export interface TwitchIrcOptions { + channel: string; + onMessage: (message: IrcMessage) => void; + onStatus?: (status: IrcStatus) => void; +} + +/** Read-only, anonymous (`justinfan`) Twitch chat connection for a single channel. */ +export class TwitchIrc { + #socket: ReconnectingSocket; + #pingTimer: ReturnType | undefined; + + constructor(options: TwitchIrcOptions) { + const channel = options.channel.toLowerCase(); + this.#socket = new ReconnectingSocket({ + label: "twitch-irc", + url: () => IRC_URL, + // Our own PINGs get a PONG well within this window on a healthy connection. + idleTimeoutMs: PING_INTERVAL_MS * 1.5, + onClose: () => options.onStatus?.("connecting"), + onOpen: (socket) => { + socket.send("CAP REQ :twitch.tv/tags twitch.tv/commands"); + socket.send(`NICK justinfan${10_000 + Math.floor(Math.random() * 89_999)}`); + socket.send(`JOIN #${channel}`); + }, + onMessage: (data, socket) => { + for (const line of data.split("\r\n")) { + if (!line) continue; + const message = parseIrcLine(line); + if (!message) continue; + switch (message.command) { + case "PING": + socket.send(`PONG :${message.params[0] ?? "tmi.twitch.tv"}`); + break; + case "RECONNECT": + socket.reconnect(true); + break; + case "JOIN": + options.onStatus?.("connected"); + break; + default: + options.onMessage(message); + } + } + }, + }); + this.#socket.start(); + this.#pingTimer = setInterval(() => this.#socket.send("PING :chatbloom"), PING_INTERVAL_MS); + } + + close(): void { + clearInterval(this.#pingTimer); + this.#socket.stop(); + } +} diff --git a/src/lib/chat/irc/parse.ts b/src/lib/chat/irc/parse.ts new file mode 100644 index 0000000..9953d11 --- /dev/null +++ b/src/lib/chat/irc/parse.ts @@ -0,0 +1,97 @@ +export interface IrcMessage { + tags: Record; + /** Nick from a `nick!user@host` prefix. */ + nick?: string; + command: string; + params: string[]; +} + +const TAG_ESCAPES: Record = { ":": ";", s: " ", "\\": "\\", r: "\r", n: "\n" }; + +/** Undoes IRCv3 tag value escaping; an unknown escape keeps the escaped character. */ +export function unescapeTag(value: string): string { + return value.replace(/\\(.?)/g, (_, char: string) => TAG_ESCAPES[char] ?? char); +} + +export function parseIrcLine(line: string): IrcMessage | undefined { + const tags: Record = {}; + let pos = 0; + + if (line.startsWith("@")) { + const end = line.indexOf(" "); + if (end === -1) return undefined; + for (const tag of line.slice(1, end).split(";")) { + const eq = tag.indexOf("="); + if (eq === -1) tags[tag] = ""; + else tags[tag.slice(0, eq)] = unescapeTag(tag.slice(eq + 1)); + } + pos = end + 1; + } + + let nick: string | undefined; + if (line[pos] === ":") { + const end = line.indexOf(" ", pos); + if (end === -1) return undefined; + const prefix = line.slice(pos + 1, end); + const bang = prefix.indexOf("!"); + if (bang !== -1) nick = prefix.slice(0, bang); + pos = end + 1; + } + + const trailingAt = line.indexOf(" :", pos); + const middle = trailingAt === -1 ? line.slice(pos) : line.slice(pos, trailingAt); + const [command, ...params] = middle.split(" ").filter(Boolean); + if (!command) return undefined; + if (trailingAt !== -1) params.push(line.slice(trailingAt + 2)); + + return { tags, nick, command, params }; +} + +export interface EmoteRange { + id: string; + /** Inclusive Unicode code point indices, as Twitch sends them. */ + start: number; + end: number; +} + +/** Parses the `emotes` tag (`25:0-4,12-16/1902:6-10`) into ranges sorted by position. */ +export function parseEmotesTag(tag: string | undefined): EmoteRange[] { + if (!tag) return []; + const ranges: EmoteRange[] = []; + for (const entry of tag.split("/")) { + const colon = entry.indexOf(":"); + if (colon === -1) continue; + const id = entry.slice(0, colon); + for (const span of entry.slice(colon + 1).split(",")) { + const [start, end] = span.split("-").map(Number); + if (Number.isInteger(start) && Number.isInteger(end)) ranges.push({ id, start, end }); + } + } + return ranges.sort((a, b) => a.start - b.start); +} + +export interface BadgeRef { + set: string; + version: string; +} + +/** Parses the `badges` / `source-badges` tag (`moderator/1,subscriber/12`). */ +export function parseBadgesTag(tag: string | undefined): BadgeRef[] { + if (!tag) return []; + return tag.split(",").flatMap((badge) => { + const slash = badge.indexOf("/"); + return slash === -1 + ? [] + : [{ set: badge.slice(0, slash), version: badge.slice(slash + 1) }]; + }); +} + +const CTCP = "\u0001"; +const ACTION_PREFIX = `${CTCP}ACTION `; + +/** Splits a `/me` CTCP action from its text. */ +export function parseAction(text: string): { text: string; action: boolean } { + if (!text.startsWith(ACTION_PREFIX)) return { text, action: false }; + const body = text.slice(ACTION_PREFIX.length); + return { text: body.endsWith(CTCP) ? body.slice(0, -1) : body, action: true }; +} diff --git a/src/lib/chat/providers/bttv.ts b/src/lib/chat/providers/bttv.ts new file mode 100644 index 0000000..81366ae --- /dev/null +++ b/src/lib/chat/providers/bttv.ts @@ -0,0 +1,193 @@ +import { BTTV_MODIFIER_CODES } from "../effects"; +import { ReconnectingSocket } from "../socket"; +import { type Badge, type Emote, fetchJson } from "../types"; + +const API = "https://api.betterttv.net/3/cached"; +const CDN = "https://cdn.betterttv.net"; +const SOCKET_URL = "wss://sockets.betterttv.net/ws"; + +// BTTV's API has no zero-width flag; these are the emotes clients overlay by convention. +const ZERO_WIDTH = new Set([ + "cvHazmat", + "cvMask", + "IceCold", + "SoSnowy", + "SantaHat", + "TopHat", + "ReinDeer", + "CandyCane", +]); + +interface BTTVEmoteData { + id: string; + code: string; + width?: number; + height?: number; + modifier?: boolean; +} + +export function bttvEmote(data: BTTVEmoteData): Emote { + return { + provider: "bttv", + id: data.id, + name: data.code, + images: { + 1: `${CDN}/emote/${data.id}/1x.webp`, + 2: `${CDN}/emote/${data.id}/2x.webp`, + 4: `${CDN}/emote/${data.id}/3x.webp`, + }, + width: data.width, + height: data.height, + zeroWidth: ZERO_WIDTH.has(data.code), + bttvModifier: (data.modifier && BTTV_MODIFIER_CODES.has(data.code)) || undefined, + }; +} + +export async function fetchBTTVGlobalEmotes(): Promise { + return ((await fetchJson(`${API}/emotes/global`)) ?? []).map(bttvEmote); +} + +/** Channel and shared emotes; undefined when the channel has no BTTV account. */ +export async function fetchBTTVChannelEmotes(twitchId: string): Promise { + const channel = await fetchJson<{ + channelEmotes: BTTVEmoteData[]; + sharedEmotes: BTTVEmoteData[]; + }>(`${API}/users/twitch/${encodeURIComponent(twitchId)}`); + return channel && [...channel.channelEmotes, ...channel.sharedEmotes].map(bttvEmote); +} + +/** Staff and supporter badges, keyed by Twitch user id. */ +export async function fetchBTTVBadges(): Promise> { + const users = + (await fetchJson<{ providerId: string; badge: { description: string; svg: string } }[]>( + `${API}/badges/twitch`, + )) ?? []; + return new Map( + users.map((user) => [ + user.providerId, + { + provider: "bttv", + id: user.badge.svg, + title: user.badge.description, + images: { 1: user.badge.svg }, + }, + ]), + ); +} + +/** BTTV Pro username effects. `glow` and `flare` tint the chat color; the rest are textures. */ +export type BTTVUsernameEffect = + | "glow" + | "flare" + | "iridescence" + | "supernova" + | "midas" + | "glacier" + | "intergalactic"; + +export interface BTTVUser { + twitchId: string; + badge?: Badge; + effect?: BTTVUsernameEffect; + /** BTTV Pro personal emotes. */ + emotes: Emote[]; +} + +export interface BTTVSocketHandlers { + onEmoteAdd: (channelId: string, emote: Emote) => void; + onEmoteRename: (channelId: string, emoteId: string, name: string) => void; + onEmoteRemove: (channelId: string, emoteId: string) => void; + onUser: (user: BTTVUser) => void; +} + +interface LookupUser { + providerId: string; + pro: boolean; + glow?: boolean; + usernameEffect?: BTTVUsernameEffect | null; + badge?: { url: string } | null; + emotes?: BTTVEmoteData[]; +} + +export function bttvUser(data: LookupUser): BTTVUser { + return { + twitchId: data.providerId, + badge: data.badge + ? { + provider: "bttv", + id: data.badge.url, + title: "BetterTTV Pro", + images: { 1: data.badge.url }, + } + : undefined, + effect: data.usernameEffect ?? (data.glow ? "glow" : undefined), + emotes: data.pro ? (data.emotes ?? []).map(bttvEmote) : [], + }; +} + +type SocketEvent = + | { name: "emote_create"; data: { channel: string; emote: BTTVEmoteData } } + | { name: "emote_update"; data: { channel: string; emote: { id: string; code: string } } } + | { name: "emote_delete"; data: { channel: string; emoteId: string } } + | { name: "lookup_user"; data: LookupUser }; + +const channelId = (name: string) => name.replace(/^twitch:/, ""); + +/** + * The BTTV socket pushes live channel emote edits and announces BTTV users as they chat + * (`lookup_user`), which carries their Pro badge, personal emotes and username effect. + */ +export class BTTVSocket { + #socket: ReconnectingSocket; + #channels = new Set(); + + constructor(handlers: BTTVSocketHandlers) { + this.#socket = new ReconnectingSocket({ + label: "bttv-socket", + url: () => SOCKET_URL, + onOpen: () => { + for (const id of this.#channels) + this.#send("join_channel", { name: `twitch:${id}` }); + }, + onMessage: (raw) => { + const event = JSON.parse(raw) as SocketEvent; + switch (event.name) { + case "emote_create": + handlers.onEmoteAdd( + channelId(event.data.channel), + bttvEmote(event.data.emote), + ); + break; + case "emote_update": + handlers.onEmoteRename( + channelId(event.data.channel), + event.data.emote.id, + event.data.emote.code, + ); + break; + case "emote_delete": + handlers.onEmoteRemove(channelId(event.data.channel), event.data.emoteId); + break; + case "lookup_user": + handlers.onUser(bttvUser(event.data)); + break; + } + }, + }); + this.#socket.start(); + } + + join(twitchId: string): void { + if (this.#channels.has(twitchId)) return; + this.#channels.add(twitchId); + this.#send("join_channel", { name: `twitch:${twitchId}` }); + } + + close(): void { + this.#socket.stop(); + } + + #send(name: string, data: unknown): void { + this.#socket.send(JSON.stringify({ name, data })); + } +} diff --git a/src/lib/chat/providers/chatterino.ts b/src/lib/chat/providers/chatterino.ts new file mode 100644 index 0000000..547a682 --- /dev/null +++ b/src/lib/chat/providers/chatterino.ts @@ -0,0 +1,26 @@ +import { type Badge, fetchJson } from "../types"; + +/** Chatterino donator and contributor badges, keyed by Twitch user id. */ +export async function fetchChatterinoBadges(): Promise> { + const data = await fetchJson<{ + badges: { + tooltip: string; + image1: string; + image2: string; + image3: string; + users: string[]; + }[]; + }>("https://api.chatterino.com/badges"); + const badges = new Map(); + for (const badge of data?.badges ?? []) { + for (const user of badge.users) { + badges.set(user, { + provider: "chatterino", + id: badge.image1, + title: badge.tooltip, + images: { 1: badge.image1, 2: badge.image2, 3: badge.image3 }, + }); + } + } + return badges; +} diff --git a/src/lib/chat/providers/ffz.ts b/src/lib/chat/providers/ffz.ts new file mode 100644 index 0000000..b6e347b --- /dev/null +++ b/src/lib/chat/providers/ffz.ts @@ -0,0 +1,241 @@ +import { ReconnectingSocket } from "../socket"; +import { type Badge, type Emote, fetchJson, type ImageSet } from "../types"; + +const API = "https://api.frankerfacez.com/v1"; +const PUBSUB_URL = "wss://pubsub.workers.frankerfacez.com/ws"; + +type FFZUrls = Record; + +interface FFZEmoteData { + id: number; + name: string; + width: number; + height: number; + modifier?: boolean; + modifier_flags?: number; + urls: FFZUrls; + animated?: FFZUrls | null; +} + +interface FFZSet { + id: number; + emoticons: FFZEmoteData[]; +} + +function imageSet(urls: FFZUrls | null | undefined): ImageSet { + const images: ImageSet = {}; + for (const [density, url] of Object.entries(urls ?? {})) { + if (url && ["1", "2", "4"].includes(density)) { + images[Number(density) as 1 | 2 | 4] = url.startsWith("//") ? `https:${url}` : url; + } + } + return images; +} + +export function ffzEmote(data: FFZEmoteData): Emote { + return { + provider: "ffz", + id: String(data.id), + name: data.name, + images: imageSet(data.animated ?? data.urls), + width: data.width, + height: data.height, + ffzModifierFlags: data.modifier ? (data.modifier_flags ?? 0) : undefined, + }; +} + +const setEmotes = (sets: Record) => + new Map(Object.entries(sets).map(([id, set]) => [id, set.emoticons.map(ffzEmote)])); + +export interface FFZGlobal { + sets: Map; + /** Sets everyone can use. */ + defaultSets: string[]; + /** Sets granted to specific users, keyed by login. */ + userSets: Map; +} + +export async function fetchFFZGlobal(): Promise { + const data = await fetchJson<{ + default_sets: number[]; + sets: Record; + users: Record; + }>(`${API}/set/global`); + const userSets = new Map(); + for (const [setId, logins] of Object.entries(data?.users ?? {})) { + for (const login of logins) userSets.set(login, [...(userSets.get(login) ?? []), setId]); + } + return { + sets: setEmotes(data?.sets ?? {}), + defaultSets: (data?.default_sets ?? []).map(String), + userSets, + }; +} + +export interface FFZRoom { + sets: Map; + /** Custom moderator badge; replaces Twitch's in this channel. */ + moderatorBadge?: Badge; + vipBadge?: Badge; + /** Per-channel FFZ badges (e.g. bot), badge id to Twitch user ids. */ + userBadges: Map; +} + +/** Undefined when the channel has no FFZ room. */ +export async function fetchFFZRoom(twitchId: string): Promise { + const data = await fetchJson<{ + room: { + mod_urls?: FFZUrls | null; + vip_badge?: FFZUrls | null; + user_badge_ids?: Record | null; + }; + sets: Record; + }>(`${API}/room/id/${encodeURIComponent(twitchId)}`); + if (!data) return undefined; + const { room } = data; + return { + sets: setEmotes(data.sets), + // FFZ draws these over Twitch's moderator green and VIP pink. + moderatorBadge: room.mod_urls + ? { + provider: "ffz", + id: "moderator", + title: "Moderator", + images: imageSet(room.mod_urls), + background: "#34ae0a", + } + : undefined, + vipBadge: room.vip_badge + ? { + provider: "ffz", + id: "vip", + title: "VIP", + images: imageSet(room.vip_badge), + background: "#e005b9", + } + : undefined, + userBadges: new Map( + Object.entries(room.user_badge_ids ?? {}).map(([id, users]) => [id, users.map(String)]), + ), + }; +} + +export interface FFZBadge extends Badge { + /** Twitch badge set this badge takes the place of, e.g. the bot badge replaces `moderator`. */ + replaces?: string; +} + +export interface FFZBadges { + badges: Map; + /** Badge ids keyed by Twitch user id. */ + users: Map; +} + +export async function fetchFFZBadges(): Promise { + const data = await fetchJson<{ + badges: { + id: number; + title: string; + color: string; + replaces: string | null; + urls: FFZUrls; + }[]; + users: Record; + }>(`${API}/badges/ids`); + const users = new Map(); + for (const [badgeId, ids] of Object.entries(data?.users ?? {})) { + for (const id of ids) users.set(String(id), [...(users.get(String(id)) ?? []), badgeId]); + } + return { + badges: new Map( + (data?.badges ?? []).map((badge) => [ + String(badge.id), + { + provider: "ffz", + id: String(badge.id), + title: badge.title, + images: imageSet(badge.urls), + background: badge.color, + replaces: badge.replaces ?? undefined, + }, + ]), + ), + users, + }; +} + +/** FFZ Add-On Pack supporter badges, keyed by Twitch user id. */ +export async function fetchFFZAPBadges(): Promise> { + const supporters = + (await fetchJson< + { id: string; tier: number; badge_color?: string; badge_is_colored?: number }[] + >("https://api.ffzap.com/v1/supporters")) ?? []; + const badges = new Map(); + for (const supporter of supporters) { + if (!supporter.tier) continue; + const url = (size: number) => `https://api.ffzap.com/v1/user/badge/${supporter.id}/${size}`; + badges.set(String(supporter.id), { + provider: "ffzap", + id: String(supporter.id), + title: "FFZ:AP Supporter", + images: { 1: url(1), 2: url(2), 4: url(3) }, + // Same rules as the add-on: tier 2 picks a color, tier 3 may ship a colored image. + background: + supporter.tier >= 3 && supporter.badge_is_colored + ? undefined + : (supporter.tier >= 2 && supporter.badge_color) || "#755000", + }); + } + return badges; +} + +export interface FFZPubSubHandlers { + onEmoteAdd: (setId: string, emote: Emote) => void; + onEmoteRemove: (setId: string, emoteId: string) => void; +} + +/** FFZ's pubsub (since FFZ 4.76) pushes live emote set edits for subscribed rooms. */ +export class FFZPubSub { + #socket: ReconnectingSocket; + #topics = new Set(["global"]); + + constructor(handlers: FFZPubSubHandlers) { + this.#socket = new ReconnectingSocket({ + label: "ffz-pubsub", + url: () => { + const url = new URL(PUBSUB_URL); + for (const topic of this.#topics) url.searchParams.append("t", topic); + return url.toString(); + }, + onMessage: (raw) => { + const packet = JSON.parse(raw) as { + topic?: string; + data?: { cmd?: string; data?: Record }; + }; + const payload = packet.data?.data; + if (!packet.topic || !payload) return; + if (packet.data?.cmd === "add_emote") { + handlers.onEmoteAdd( + String(payload.set_id), + ffzEmote(payload.emote as FFZEmoteData), + ); + } else if (packet.data?.cmd === "remove_emote") { + handlers.onEmoteRemove(String(payload.set_id), String(payload.emote_id)); + } + }, + }); + this.#socket.start(); + } + + /** Topics live in the URL, so a new one means reconnecting. */ + subscribe(twitchId: string): void { + const topic = `twitch/${twitchId}`; + if (this.#topics.has(topic)) return; + this.#topics.add(topic); + this.#socket.reconnect(true); + } + + close(): void { + this.#socket.stop(); + } +} diff --git a/src/lib/chat/providers/seventv/api.ts b/src/lib/chat/providers/seventv/api.ts new file mode 100644 index 0000000..cd9e987 --- /dev/null +++ b/src/lib/chat/providers/seventv/api.ts @@ -0,0 +1,112 @@ +import { type Badge, type Emote, fetchJson } from "../../types"; +import { type Paint, paintFromV4, V4_PAINT_FIELDS, type V4Paint } from "./paint"; + +const API = "https://7tv.io/v3"; +const GQL_V4 = "https://7tv.io/v4/gql"; + +export interface SevenTVHost { + url: string; + files: { name: string; width: number; height: number; format: string }[]; +} + +export interface SevenTVActiveEmote { + id: string; + name: string; + flags: number; + data?: { id: string; name: string; flags: number; animated: boolean; host: SevenTVHost } | null; +} + +export interface SevenTVEmoteSet { + id: string; + name: string; + flags: number; + emotes?: SevenTVActiveEmote[] | null; +} + +/** Active emote flag: zero-width, rendered over the previous emote. */ +const ACTIVE_ZERO_WIDTH = 1 << 0; +/** Emote set flag: a user's personal set, usable in any channel. */ +export const SET_PERSONAL = 1 << 2; + +function hostImages(host: SevenTVHost, format = "WEBP") { + const files = host.files.filter((file) => file.format === format); + const images: Emote["images"] = {}; + for (const file of files) { + const density = Number.parseInt(file.name, 10); + if (density >= 1 && density <= 4) { + images[density as 1 | 2 | 3 | 4] = `https:${host.url}/${file.name}`; + } + } + return { images, first: files[0] }; +} + +export function sevenTVEmote(active: SevenTVActiveEmote): Emote | undefined { + if (!active.data) return undefined; + const { images, first } = hostImages(active.data.host); + if (!first) return undefined; + return { + provider: "7tv", + id: active.id, + name: active.name, + images, + width: first.width, + height: first.height, + zeroWidth: (active.flags & ACTIVE_ZERO_WIDTH) !== 0, + }; +} + +export function sevenTVEmotes(set: SevenTVEmoteSet | null | undefined): Emote[] { + return (set?.emotes ?? []).flatMap((active) => sevenTVEmote(active) ?? []); +} + +export function sevenTVBadge(data: { id: string; tooltip: string; host: SevenTVHost }): Badge { + return { + provider: "7tv", + id: data.id, + title: data.tooltip, + images: hostImages(data.host).images, + }; +} + +export async function fetchSevenTVGlobalEmotes(): Promise { + return sevenTVEmotes(await fetchJson(`${API}/emote-sets/global`)); +} + +export async function fetchSevenTVEmoteSet(id: string): Promise { + return fetchJson(`${API}/emote-sets/${encodeURIComponent(id)}`); +} + +export interface SevenTVChannel { + /** The broadcaster's 7TV user id, for `user.*` EventAPI subscriptions. */ + userId: string; + emoteSet?: SevenTVEmoteSet; +} + +/** Returns undefined when the channel has no 7TV account. */ +export async function fetchSevenTVChannel(twitchId: string): Promise { + const connection = await fetchJson<{ user: { id: string }; emote_set: SevenTVEmoteSet | null }>( + `${API}/users/twitch/${encodeURIComponent(twitchId)}`, + ); + if (!connection) return undefined; + return { userId: connection.user.id, emoteSet: connection.emote_set ?? undefined }; +} + +/** + * Fetches paints from the v4 GraphQL API, which carries every layer. The v3 shape the EventAPI + * sends only has the first one. + */ +export async function fetchSevenTVPaints(ids: string[]): Promise { + if (ids.length === 0) return []; + const variables = Object.fromEntries(ids.map((id, i) => [`i${i}`, id])); + const query = `query(${ids.map((_, i) => `$i${i}: Id!`).join(", ")}) { paints { + ${ids.map((_, i) => `p${i}: paint(id: $i${i}) { ${V4_PAINT_FIELDS} }`).join("\n")} + } }`; + const result = await fetchJson<{ data?: { paints: Record } }>(GQL_V4, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ query, variables }), + }); + return Object.values(result?.data?.paints ?? {}).flatMap((paint) => + paint ? [paintFromV4(paint)] : [], + ); +} diff --git a/src/lib/chat/providers/seventv/events.ts b/src/lib/chat/providers/seventv/events.ts new file mode 100644 index 0000000..04a8cb9 --- /dev/null +++ b/src/lib/chat/providers/seventv/events.ts @@ -0,0 +1,195 @@ +import { ReconnectingSocket } from "../../socket"; +import type { Badge, Emote } from "../../types"; +import { type SevenTVActiveEmote, sevenTVBadge, sevenTVEmote } from "./api"; +import { type Paint, paintFromV3, type V3PaintData } from "./paint"; + +const EVENTS_URL = "wss://events.7tv.io/v3"; + +const Op = { + Dispatch: 0, + Hello: 1, + Heartbeat: 2, + Reconnect: 4, + Error: 6, + EndOfStream: 7, + Subscribe: 35, + Unsubscribe: 36, +} as const; + +export type SevenTVCondition = Record; + +export const channelCondition = (twitchId: string): SevenTVCondition => ({ + ctx: "channel", + platform: "TWITCH", + id: twitchId, +}); + +export interface EmoteSetChange { + added: Emote[]; + /** Emote ids; a rename shows up as a removal followed by an addition. */ + removed: string[]; +} + +export interface Entitlement { + kind: "PAINT" | "BADGE" | "EMOTE_SET"; + refId: string; + twitchId: string; +} + +export interface SevenTVEventHandlers { + onEmoteSetChange: (setId: string, change: EmoteSetChange) => void; + onEmoteSetCreate: (setId: string, flags: number) => void; + onPaint: (paint: Paint) => void; + onBadge: (badge: Badge) => void; + onEntitlement: (entitlement: Entitlement, granted: boolean) => void; + /** A channel owner switched their active emote set. */ + onUserEmoteSet: (sevenTVUserId: string, setId: string) => void; +} + +interface ChangeField { + key: string; + index?: number; + value?: unknown; + old_value?: unknown; +} + +interface DispatchBody { + id: string; + object?: Record; + pushed?: ChangeField[]; + pulled?: ChangeField[]; + updated?: ChangeField[]; +} + +export function emoteSetChange(body: DispatchBody): EmoteSetChange { + const added: Emote[] = []; + const removed: string[] = []; + for (const field of body.pulled ?? []) { + if (field.key === "emotes") removed.push((field.old_value as SevenTVActiveEmote).id); + } + for (const field of body.updated ?? []) { + if (field.key !== "emotes") continue; + removed.push((field.old_value as SevenTVActiveEmote).id); + const emote = sevenTVEmote(field.value as SevenTVActiveEmote); + if (emote) added.push(emote); + } + for (const field of body.pushed ?? []) { + if (field.key !== "emotes") continue; + const emote = sevenTVEmote(field.value as SevenTVActiveEmote); + if (emote) added.push(emote); + } + return { added, removed }; +} + +function twitchConnection(user: unknown): string | undefined { + const connections = (user as { connections?: { platform: string; id: string }[] })?.connections; + return connections?.find((c) => c.platform === "TWITCH")?.id; +} + +/** Client for the 7TV EventAPI; subscriptions survive reconnects. */ +export class SevenTVEvents { + #socket: ReconnectingSocket; + #subscriptions = new Map(); + #handlers: SevenTVEventHandlers; + + constructor(handlers: SevenTVEventHandlers) { + this.#handlers = handlers; + this.#socket = new ReconnectingSocket({ + label: "7tv-events", + url: () => EVENTS_URL, + onMessage: (data) => this.#onMessage(data), + }); + this.#socket.start(); + } + + subscribe(type: string, condition: SevenTVCondition): void { + const key = JSON.stringify([type, condition]); + if (this.#subscriptions.has(key)) return; + this.#subscriptions.set(key, { type, condition }); + this.#send(Op.Subscribe, { type, condition }); + } + + unsubscribe(type: string, condition: SevenTVCondition): void { + if (this.#subscriptions.delete(JSON.stringify([type, condition]))) { + this.#send(Op.Unsubscribe, { type, condition }); + } + } + + close(): void { + this.#socket.stop(); + } + + #send(op: number, d: unknown): void { + this.#socket.send(JSON.stringify({ op, d })); + } + + #onMessage(data: string): void { + const message = JSON.parse(data) as { op: number; d: Record }; + switch (message.op) { + case Op.Hello: { + const interval = Number(message.d.heartbeat_interval) || 45_000; + this.#socket.keepAlive(interval * 3); + for (const subscription of this.#subscriptions.values()) { + this.#send(Op.Subscribe, subscription); + } + break; + } + case Op.Reconnect: + case Op.EndOfStream: + this.#socket.reconnect(); + break; + case Op.Error: + console.warn("[7tv-events]", message.d); + break; + case Op.Dispatch: + this.#dispatch(message.d.type as string, message.d.body as DispatchBody); + break; + } + } + + #dispatch(type: string, body: DispatchBody): void { + const handlers = this.#handlers; + const object = body.object; + switch (type) { + case "emote_set.update": + handlers.onEmoteSetChange(body.id, emoteSetChange(body)); + break; + case "emote_set.create": + handlers.onEmoteSetCreate(body.id, Number(object?.flags) || 0); + break; + case "cosmetic.create": + case "cosmetic.update": { + if (object?.kind === "PAINT") + handlers.onPaint(paintFromV3(object.data as V3PaintData)); + if (object?.kind === "BADGE") { + handlers.onBadge( + sevenTVBadge(object.data as Parameters[0]), + ); + } + break; + } + case "entitlement.create": + case "entitlement.delete": { + const twitchId = twitchConnection(object?.user); + const kind = object?.kind as Entitlement["kind"]; + if (!twitchId || !["PAINT", "BADGE", "EMOTE_SET"].includes(kind)) break; + handlers.onEntitlement( + { kind, refId: object?.ref_id as string, twitchId }, + type === "entitlement.create", + ); + break; + } + case "user.update": + for (const field of body.updated ?? []) { + if (field.key !== "connections") continue; + for (const nested of (field.value as ChangeField[]) ?? []) { + const setId = (nested.value as { id?: string } | undefined)?.id; + if (nested.key === "emote_set" && setId) { + handlers.onUserEmoteSet(body.id, setId); + } + } + } + break; + } + } +} diff --git a/src/lib/chat/providers/seventv/paint.ts b/src/lib/chat/providers/seventv/paint.ts new file mode 100644 index 0000000..f3064b9 --- /dev/null +++ b/src/lib/chat/providers/seventv/paint.ts @@ -0,0 +1,170 @@ +/** + * 7TV name paints, normalised to the layered v4 model: each layer is a text-clipped background + * stacked over the previous one, and the drop shadows apply to the first layer only. This is + * how 7TV's own website renders them. + */ +export interface Paint { + id: string; + name: string; + layers: PaintLayer[]; + /** `drop-shadow(...)` chain for the shadows, if any. */ + filter?: string; +} + +export interface PaintLayer { + opacity: number; + image?: string; + color?: string; +} + +/** 7TV v3 encodes colors as signed 32-bit RGBA integers. */ +export function colorFromInt(color: number): string { + return `#${(color >>> 0).toString(16).padStart(8, "0")}`; +} + +export interface V3PaintData { + id: string; + name: string; + function: "LINEAR_GRADIENT" | "RADIAL_GRADIENT" | "URL"; + color: number | null; + repeat: boolean; + angle: number; + shape?: string; + image_url?: string; + stops: { at: number; color: number }[]; + shadows: { x_offset: number; y_offset: number; radius: number; color: number }[] | null; +} + +function stopList(stops: { at: number; color: string }[]): string { + return stops.map((stop) => `${stop.color} ${stop.at * 100}%`).join(", "); +} + +function linearGradient(angle: number, repeating: boolean, stops: { at: number; color: string }[]) { + return `${repeating ? "repeating-" : ""}linear-gradient(${angle}deg, ${stopList(stops)})`; +} + +function radialGradient(shape: string, repeating: boolean, stops: { at: number; color: string }[]) { + return `${repeating ? "repeating-" : ""}radial-gradient(${shape}, ${stopList(stops)})`; +} + +function dropShadows(shadows: { x: number; y: number; blur: number; color: string }[]) { + if (shadows.length === 0) return undefined; + return shadows.map((s) => `drop-shadow(${s.color} ${s.x}px ${s.y}px ${s.blur}px)`).join(" "); +} + +/** Converts the v3 shape sent by the EventAPI. v3 only carries a paint's first layer. */ +export function paintFromV3(data: V3PaintData): Paint { + const stops = data.stops.map((stop) => ({ at: stop.at, color: colorFromInt(stop.color) })); + let image: string | undefined; + if (data.function === "URL") { + if (data.image_url) image = `url("${data.image_url}")`; + } else if (stops.length > 0) { + image = + data.function === "LINEAR_GRADIENT" + ? linearGradient(data.angle, data.repeat, stops) + : radialGradient(data.shape || "circle", data.repeat, stops); + } + const color = data.color === null ? undefined : colorFromInt(data.color); + + return { + id: data.id, + name: data.name, + layers: image || color ? [{ opacity: 1, image, color }] : [], + filter: dropShadows( + (data.shadows ?? []).map((s) => ({ + x: s.x_offset, + y: s.y_offset, + blur: s.radius, + color: colorFromInt(s.color), + })), + ), + }; +} + +interface V4Stop { + at: number; + color: { hex: string }; +} + +export interface V4Paint { + id: string; + name: string; + data: { + layers: { + opacity: number; + ty: + | { __typename: "PaintLayerTypeSingleColor"; color: { hex: string } } + | { + __typename: "PaintLayerTypeLinearGradient"; + angle: number; + repeating: boolean; + stops: V4Stop[]; + } + | { + __typename: "PaintLayerTypeRadialGradient"; + shape: "CIRCLE" | "ELLIPSE"; + repeating: boolean; + stops: V4Stop[]; + } + | { + __typename: "PaintLayerTypeImage"; + images: { url: string; scale: number; frameCount: number }[]; + }; + }[]; + shadows: { color: { hex: string }; offsetX: number; offsetY: number; blur: number }[]; + }; +} + +/** Selection set for {@link V4Paint}, for use in v4 GraphQL queries. */ +export const V4_PAINT_FIELDS = `id name data { + layers { opacity ty { __typename + ... on PaintLayerTypeSingleColor { color { hex } } + ... on PaintLayerTypeLinearGradient { angle repeating stops { at color { hex } } } + ... on PaintLayerTypeRadialGradient { shape repeating stops { at color { hex } } } + ... on PaintLayerTypeImage { images { url scale frameCount } } + } } + shadows { color { hex } offsetX offsetY blur } +}`; + +function layerFromV4(layer: V4Paint["data"]["layers"][number]): PaintLayer | undefined { + const ty = layer.ty; + const stops = "stops" in ty ? ty.stops.map((s) => ({ at: s.at, color: s.color.hex })) : []; + switch (ty.__typename) { + case "PaintLayerTypeSingleColor": + return { opacity: layer.opacity, color: ty.color.hex }; + case "PaintLayerTypeLinearGradient": + if (stops.length === 0) return undefined; + return { opacity: layer.opacity, image: linearGradient(ty.angle, ty.repeating, stops) }; + case "PaintLayerTypeRadialGradient": + if (stops.length === 0) return undefined; + return { + opacity: layer.opacity, + image: radialGradient(ty.shape.toLowerCase(), ty.repeating, stops), + }; + case "PaintLayerTypeImage": { + const animated = ty.images.some((img) => img.frameCount > 1); + const oneX = ty.images.find( + (img) => img.scale === 1 && img.frameCount > 1 === animated, + ); + return oneX ? { opacity: layer.opacity, image: `url("${oneX.url}")` } : undefined; + } + default: + return undefined; + } +} + +export function paintFromV4(paint: V4Paint): Paint { + return { + id: paint.id, + name: paint.name, + layers: paint.data.layers.flatMap((layer) => layerFromV4(layer) ?? []), + filter: dropShadows( + paint.data.shadows.map((s) => ({ + x: s.offsetX, + y: s.offsetY, + blur: s.blur, + color: s.color.hex, + })), + ), + }; +} diff --git a/src/lib/chat/providers/twitch.ts b/src/lib/chat/providers/twitch.ts new file mode 100644 index 0000000..bbefb78 --- /dev/null +++ b/src/lib/chat/providers/twitch.ts @@ -0,0 +1,107 @@ +import type { BadgeRef } from "../irc/parse"; +import { type Badge, type Emote, fetchJson } from "../types"; + +export function twitchEmote(id: string, name: string): Emote { + // `default` serves the animated variant when there is one. + const base = `https://static-cdn.jtvnw.net/emoticons/v2/${id}/default/dark`; + return { + provider: "twitch", + id, + name, + images: { 1: `${base}/1.0`, 2: `${base}/2.0`, 4: `${base}/3.0` }, + width: 28, + height: 28, + }; +} + +interface IvrBadgeSet { + set_id: string; + versions: { + id: string; + title: string; + image_url_1x: string; + image_url_2x: string; + image_url_4x: string; + }[]; +} + +/** Twitch badges keyed by `set/version`. */ +export type TwitchBadges = Map; + +// Helix's badge endpoints need an app token and the legacy anonymous endpoint is gone, so +// badges come from IVR, a public community API that mirrors Helix with CORS enabled. +const IVR_BADGES = "https://api.ivr.fi/v2/twitch/badges"; + +async function fetchIvrBadges(url: string): Promise { + const sets = (await fetchJson(url)) ?? []; + const badges: TwitchBadges = new Map(); + for (const set of sets) { + for (const version of set.versions) { + badges.set(`${set.set_id}/${version.id}`, { + provider: "twitch", + id: `${set.set_id}/${version.id}`, + title: version.title, + images: { + 1: version.image_url_1x, + 2: version.image_url_2x, + 4: version.image_url_4x, + }, + }); + } + } + return badges; +} + +export const fetchTwitchGlobalBadges = () => fetchIvrBadges(`${IVR_BADGES}/global`); + +export const fetchTwitchChannelBadges = (roomId: string) => + fetchIvrBadges(`${IVR_BADGES}/channel?id=${encodeURIComponent(roomId)}`); + +export function resolveTwitchBadges( + refs: BadgeRef[], + channel: TwitchBadges | undefined, + global: TwitchBadges, +): Badge[] { + return refs.flatMap(({ set, version }) => { + const badge = channel?.get(`${set}/${version}`) ?? global.get(`${set}/${version}`); + return badge ? [badge] : []; + }); +} + +// The palette Twitch assigns to users who never picked a color. +const DEFAULT_COLORS = [ + "#FF0000", + "#0000FF", + "#008000", + "#B22222", + "#FF7F50", + "#9ACD32", + "#FF4500", + "#2E8B57", + "#DAA520", + "#D2691E", + "#5F9EA0", + "#1E90FF", + "#FF69B4", + "#8A2BE2", + "#00FF7F", +]; + +export function defaultColor(login: string): string { + let hash = 0; + for (const char of login) hash = (hash * 31 + char.charCodeAt(0)) >>> 0; + return DEFAULT_COLORS[hash % DEFAULT_COLORS.length]; +} + +/** Lightens colors too dark to read on a stream, keeping their hue. */ +export function readableColor(hex: string): string { + const match = /^#?([0-9a-f]{6})$/i.exec(hex); + if (!match) return hex; + const value = Number.parseInt(match[1], 16); + const channels = [value >> 16, (value >> 8) & 0xff, value & 0xff]; + const luminance = (0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]) / 255; + if (luminance >= 0.3) return `#${match[1]}`; + const mix = 0.3 - luminance + 0.25; + const lifted = channels.map((c) => Math.round(c + (255 - c) * mix)); + return `#${lifted.map((c) => c.toString(16).padStart(2, "0")).join("")}`; +} diff --git a/src/lib/chat/session.ts b/src/lib/chat/session.ts new file mode 100644 index 0000000..e8a1f46 --- /dev/null +++ b/src/lib/chat/session.ts @@ -0,0 +1,528 @@ +import { createSignal } from "solid-js"; +import { createStore } from "solid-js/store"; + +import { EmoteSet } from "./emote-set"; +import { TwitchIrc } from "./irc/client"; +import { + type BadgeRef, + type EmoteRange, + type IrcMessage, + parseAction, + parseBadgesTag, + parseEmotesTag, +} from "./irc/parse"; +import { + BTTVSocket, + type BTTVUser, + type BTTVUsernameEffect, + fetchBTTVBadges, + fetchBTTVChannelEmotes, + fetchBTTVGlobalEmotes, +} from "./providers/bttv"; +import { fetchChatterinoBadges } from "./providers/chatterino"; +import { + type FFZBadges, + FFZPubSub, + type FFZRoom, + fetchFFZAPBadges, + fetchFFZBadges, + fetchFFZGlobal, + fetchFFZRoom, +} from "./providers/ffz"; +import { + fetchSevenTVChannel, + fetchSevenTVEmoteSet, + fetchSevenTVGlobalEmotes, + fetchSevenTVPaints, + SET_PERSONAL, + type SevenTVEmoteSet, + sevenTVEmotes, +} from "./providers/seventv/api"; +import { channelCondition, type Entitlement, SevenTVEvents } from "./providers/seventv/events"; +import type { Paint } from "./providers/seventv/paint"; +import { + defaultColor, + fetchTwitchChannelBadges, + fetchTwitchGlobalBadges, + readableColor, + resolveTwitchBadges, + type TwitchBadges, +} from "./providers/twitch"; +import { type MessagePart, tokenize } from "./tokenize"; +import type { Badge } from "./types"; + +export interface ChatMessage { + id: string; + /** Room whose emotes and badges apply: the source channel for Shared Chat messages. */ + roomId: string; + userId: string; + login: string; + displayName: string; + color: string; + action: boolean; + text: string; + emoteRanges: EmoteRange[]; + badgeRefs: BadgeRef[]; + /** USERNOTICE text such as "x subscribed with Prime." */ + system?: string; +} + +/** Per-user cosmetics that arrive over provider sockets, often after the user's first message. */ +export interface ChatUser { + paintId?: string; + sevenTVBadgeId?: string; + bttvBadge?: Badge; + bttvEffect?: BTTVUsernameEffect; + /** Bumped whenever the user's personal emotes change so their messages re-tokenize. */ + personalEmotes: number; +} + +interface Room { + id: string; + sevenTV: EmoteSet; + sevenTVSetId?: string; + sevenTVUserId?: string; + bttv: EmoteSet; + ffz?: FFZRoom; + ffzSetIds: string[]; + twitchBadges?: TwitchBadges; +} + +export interface ChatSessionOptions { + maxMessages?: number; +} + +export type ChatSession = ReturnType; + +/** + * Connects to a channel's chat and every emote and cosmetics provider (Twitch, 7TV, BTTV, FFZ, + * FFZ:AP, Chatterino), keeping all of it live. Provider data lives in plain maps; the version + * signals and the store tell components when to re-derive message parts and badges. + */ +export function createChatSession(channel: string, options: ChatSessionOptions = {}) { + const maxMessages = options.maxMessages ?? 100; + + const [state, setState] = createStore({ + status: "connecting" as "connecting" | "connected", + roomId: undefined as string | undefined, + messages: [] as ChatMessage[], + users: {} as Record, + paints: {} as Record, + sevenTVBadges: {} as Record, + }); + + const [emotesVersion, setEmotesVersion] = createSignal(0); + const [badgesVersion, setBadgesVersion] = createSignal(0); + // Coalesce bursts (e.g. a provider loading) into one re-derivation. + const coalesce = (bump: () => void) => { + let queued = false; + return () => { + if (queued) return; + queued = true; + queueMicrotask(() => { + queued = false; + bump(); + }); + }; + }; + const emotesChanged = coalesce(() => setEmotesVersion((v) => v + 1)); + const badgesChanged = coalesce(() => setBadgesVersion((v) => v + 1)); + + const global = { + sevenTV: new EmoteSet(), + bttv: new EmoteSet(), + ffzDefaultSets: [] as string[], + ffzUserSets: new Map(), + twitchBadges: new Map() as TwitchBadges, + bttvBadges: new Map(), + ffzBadges: { badges: new Map(), users: new Map() } as FFZBadges, + ffzapBadges: new Map(), + chatterinoBadges: new Map(), + }; + const rooms = new Map(); + /** Every FFZ set by id (global, room and user sets), so pubsub edits find their set. */ + const ffzSets = new Map(); + /** Every 7TV set by id (channel and personal), so EventAPI edits find their set. */ + const sevenTVSets = new Map(); + const personalSetIds = new Map>(); + const personalSetOwners = new Map>(); + const bttvPersonal = new Map(); + + const load = (label: string, task: () => Promise) => + task().catch((error) => console.warn(`[chat] loading ${label} failed`, error)); + + const patchUser = (twitchId: string, patch: (user: ChatUser) => Partial) => + setState("users", twitchId, (user = { personalEmotes: 0 }) => ({ + ...user, + ...patch(user), + })); + const personalEmotesChanged = (twitchId: string) => + patchUser(twitchId, (user) => ({ personalEmotes: user.personalEmotes + 1 })); + + // 7TV only sends a paint's first layer over v3, so each new paint is re-fetched from v4. + const paintQueue = new Set(); + const upgradePaints = coalesce(() => { + const ids = [...paintQueue].slice(0, 25); + for (const id of ids) paintQueue.delete(id); + if (paintQueue.size > 0) upgradePaints(); + load("7TV v4 paints", async () => { + for (const paint of await fetchSevenTVPaints(ids)) { + const current = state.paints[paint.id]; + if (JSON.stringify(current?.layers) === JSON.stringify(paint.layers)) continue; + setState("paints", paint.id, paint); + } + }); + }); + + const sevenTV = new SevenTVEvents({ + onEmoteSetChange(setId, change) { + const set = sevenTVSets.get(setId); + if (!set) return; + for (const id of change.removed) set.remove(id); + for (const emote of change.added) set.add(emote); + const owners = personalSetOwners.get(setId); + if (owners) for (const owner of owners) personalEmotesChanged(owner); + else emotesChanged(); + }, + onEmoteSetCreate(setId, flags) { + if (flags & SET_PERSONAL && !sevenTVSets.has(setId)) + sevenTVSets.set(setId, new EmoteSet()); + }, + onPaint(paint) { + if (!state.paints[paint.id]) { + paintQueue.add(paint.id); + upgradePaints(); + } + setState("paints", paint.id, paint); + }, + onBadge(badge) { + setState("sevenTVBadges", badge.id, badge); + }, + onEntitlement: entitlementChanged, + onUserEmoteSet(sevenTVUserId, setId) { + for (const room of rooms.values()) { + if (room.sevenTVUserId !== sevenTVUserId || room.sevenTVSetId === setId) continue; + load("7TV emote set", async () => { + const set = await fetchSevenTVEmoteSet(setId); + if (set) useSevenTVSet(room, set); + }); + } + }, + }); + + function entitlementChanged({ kind, refId, twitchId }: Entitlement, granted: boolean) { + if (kind === "PAINT") { + patchUser(twitchId, (user) => ({ + paintId: granted ? refId : user.paintId === refId ? undefined : user.paintId, + })); + } else if (kind === "BADGE") { + patchUser(twitchId, (user) => ({ + sevenTVBadgeId: granted + ? refId + : user.sevenTVBadgeId === refId + ? undefined + : user.sevenTVBadgeId, + })); + } else { + const ids = personalSetIds.get(twitchId) ?? new Set(); + const owners = personalSetOwners.get(refId) ?? new Set(); + if (granted) { + personalSetIds.set(twitchId, ids.add(refId)); + personalSetOwners.set(refId, owners.add(twitchId)); + if (!sevenTVSets.has(refId)) { + sevenTVSets.set(refId, new EmoteSet()); + // The EventAPI usually follows up with the set's contents; fetch in case not. + load("7TV personal emotes", async () => { + const set = await fetchSevenTVEmoteSet(refId); + for (const emote of sevenTVEmotes(set)) sevenTVSets.get(refId)?.add(emote); + for (const owner of personalSetOwners.get(refId) ?? []) { + personalEmotesChanged(owner); + } + }); + } + } else { + ids.delete(refId); + owners.delete(twitchId); + } + personalEmotesChanged(twitchId); + } + } + + function useSevenTVSet(room: Room, set: SevenTVEmoteSet) { + if (room.sevenTVSetId) sevenTV.unsubscribe("emote_set.*", { object_id: room.sevenTVSetId }); + room.sevenTVSetId = set.id; + room.sevenTV = new EmoteSet(sevenTVEmotes(set)); + sevenTVSets.set(set.id, room.sevenTV); + sevenTV.subscribe("emote_set.*", { object_id: set.id }); + emotesChanged(); + } + + const bttv = new BTTVSocket({ + onEmoteAdd(channelId, emote) { + rooms.get(channelId)?.bttv.add(emote); + emotesChanged(); + }, + onEmoteRename(channelId, emoteId, name) { + rooms.get(channelId)?.bttv.rename(emoteId, name); + emotesChanged(); + }, + onEmoteRemove(channelId, emoteId) { + rooms.get(channelId)?.bttv.remove(emoteId); + emotesChanged(); + }, + onUser(user: BTTVUser) { + patchUser(user.twitchId, () => ({ bttvBadge: user.badge, bttvEffect: user.effect })); + if (user.emotes.length > 0) { + bttvPersonal.set(user.twitchId, new EmoteSet(user.emotes)); + personalEmotesChanged(user.twitchId); + } + }, + }); + + const ffz = new FFZPubSub({ + onEmoteAdd(setId, emote) { + ffzSets.get(setId)?.add(emote); + emotesChanged(); + }, + onEmoteRemove(setId, emoteId) { + ffzSets.get(setId)?.remove(emoteId); + emotesChanged(); + }, + }); + + function loadRoom(roomId: string) { + if (rooms.has(roomId)) return; + const room: Room = { + id: roomId, + sevenTV: new EmoteSet(), + bttv: new EmoteSet(), + ffzSetIds: [], + }; + rooms.set(roomId, room); + + load("7TV channel", async () => { + const channel = await fetchSevenTVChannel(roomId); + if (!channel) return; + room.sevenTVUserId = channel.userId; + sevenTV.subscribe("user.*", { object_id: channel.userId }); + if (channel.emoteSet) useSevenTVSet(room, channel.emoteSet); + }); + // Cosmetics, entitlements and personal emote sets of the people chatting here. + for (const type of ["cosmetic.*", "entitlement.*", "emote_set.*"]) { + sevenTV.subscribe(type, channelCondition(roomId)); + } + + load("BTTV channel", async () => { + const emotes = await fetchBTTVChannelEmotes(roomId); + if (!emotes) return; + for (const emote of emotes) room.bttv.add(emote); + emotesChanged(); + }); + bttv.join(roomId); + + load("FFZ room", async () => { + const ffzRoom = await fetchFFZRoom(roomId); + if (!ffzRoom) return; + for (const [id, emotes] of ffzRoom.sets) ffzSets.set(id, new EmoteSet(emotes)); + room.ffz = ffzRoom; + room.ffzSetIds = [...ffzRoom.sets.keys()]; + emotesChanged(); + badgesChanged(); + }); + ffz.subscribe(roomId); + + load("Twitch channel badges", async () => { + room.twitchBadges = await fetchTwitchChannelBadges(roomId); + badgesChanged(); + }); + } + + load("7TV global emotes", async () => { + global.sevenTV = new EmoteSet(await fetchSevenTVGlobalEmotes()); + emotesChanged(); + }); + load("BTTV global emotes", async () => { + global.bttv = new EmoteSet(await fetchBTTVGlobalEmotes()); + emotesChanged(); + }); + load("FFZ global emotes", async () => { + const data = await fetchFFZGlobal(); + for (const [id, emotes] of data.sets) ffzSets.set(id, new EmoteSet(emotes)); + global.ffzDefaultSets = data.defaultSets; + global.ffzUserSets = data.userSets; + emotesChanged(); + }); + const loadBadges = (label: string, task: () => Promise) => + load(label, async () => { + await task(); + badgesChanged(); + }); + loadBadges("Twitch global badges", async () => { + global.twitchBadges = await fetchTwitchGlobalBadges(); + }); + loadBadges("BTTV badges", async () => { + global.bttvBadges = await fetchBTTVBadges(); + }); + loadBadges("FFZ badges", async () => { + global.ffzBadges = await fetchFFZBadges(); + }); + loadBadges("FFZ:AP badges", async () => { + global.ffzapBadges = await fetchFFZAPBadges(); + }); + loadBadges("Chatterino badges", async () => { + global.chatterinoBadges = await fetchChatterinoBadges(); + }); + + function addMessage(message: ChatMessage) { + loadRoom(message.roomId); + setState("messages", (messages) => [...messages.slice(1 - maxMessages), message]); + } + + function removeMessages(predicate: (message: ChatMessage) => boolean) { + setState("messages", (messages) => messages.filter((message) => !predicate(message))); + } + + function chatMessage(message: IrcMessage, body: string, system?: string): ChatMessage { + const { tags } = message; + const { text, action } = parseAction(body); + const login = tags.login || message.nick || ""; + // Shared Chat: messages from the other channels carry their own room and badges. + const sourceRoom = tags["source-room-id"]; + const shared = !!sourceRoom && sourceRoom !== tags["room-id"]; + return { + id: tags.id || crypto.randomUUID(), + roomId: shared ? sourceRoom : tags["room-id"], + userId: tags["user-id"] ?? "", + login, + displayName: tags["display-name"] || login, + color: readableColor(tags.color || defaultColor(login)), + action, + text, + emoteRanges: parseEmotesTag(tags.emotes), + badgeRefs: parseBadgesTag( + shared ? (tags["source-badges"] ?? tags.badges) : tags.badges, + ), + system, + }; + } + + const irc = new TwitchIrc({ + channel, + onStatus: (status) => setState("status", status), + onMessage(message) { + const { tags } = message; + switch (message.command) { + case "ROOMSTATE": + if (tags["room-id"] && !state.roomId) { + setState("roomId", tags["room-id"]); + loadRoom(tags["room-id"]); + } + break; + case "PRIVMSG": + addMessage(chatMessage(message, message.params[1] ?? "")); + break; + case "USERNOTICE": + addMessage(chatMessage(message, message.params[1] ?? "", tags["system-msg"])); + break; + case "CLEARCHAT": { + const target = tags["target-user-id"]; + removeMessages((m) => !target || m.userId === target); + break; + } + case "CLEARMSG": + removeMessages((m) => m.id === tags["target-msg-id"]); + break; + } + }, + }); + + /** Message parts; re-derived when any emote source the message draws from changes. */ + function parts(message: ChatMessage): MessagePart[] { + // Read purely to subscribe: global/room set changes, and this user's personal emotes. + emotesVersion(); + void state.users[message.userId]?.personalEmotes; + + const room = rooms.get(message.roomId); + const sets = [ + ...[...(personalSetIds.get(message.userId) ?? [])].map((id) => sevenTVSets.get(id)), + bttvPersonal.get(message.userId), + room?.sevenTV, + room?.bttv, + ...(room?.ffzSetIds ?? []).map((id) => ffzSets.get(id)), + ...(global.ffzUserSets.get(message.login) ?? []).map((id) => ffzSets.get(id)), + global.sevenTV, + global.bttv, + ...global.ffzDefaultSets.map((id) => ffzSets.get(id)), + ].filter((set) => set !== undefined); + + return tokenize(message.text, message.emoteRanges, (name) => { + for (const set of sets) { + const emote = set.get(name); + if (emote) return emote; + } + return undefined; + }); + } + + /** Badges in display order; re-derived when badge data or the user's cosmetics change. */ + function badges(message: ChatMessage): Badge[] { + badgesVersion(); + const room = rooms.get(message.roomId); + // Each slot remembers its Twitch badge set, which FFZ badges may take over. + const slots = message.badgeRefs.flatMap((ref) => { + const [badge] = resolveTwitchBadges([ref], room?.twitchBadges, global.twitchBadges); + if (!badge) return []; + if (ref.set === "moderator" && room?.ffz?.moderatorBadge) { + return [{ set: ref.set, badge: room.ffz.moderatorBadge }]; + } + if (ref.set === "vip" && room?.ffz?.vipBadge) { + return [{ set: ref.set, badge: room.ffz.vipBadge }]; + } + return [{ set: ref.set, badge }]; + }); + + const ffzIds = new Set(global.ffzBadges.users.get(message.userId)); + for (const [id, users] of room?.ffz?.userBadges ?? []) { + if (users.includes(message.userId)) ffzIds.add(id); + } + for (const id of ffzIds) { + const badge = global.ffzBadges.badges.get(id); + if (!badge) continue; + const replaced = badge.replaces ? slots.findIndex((s) => s.set === badge.replaces) : -1; + if (replaced === -1) slots.push({ set: "", badge }); + else slots[replaced] = { set: slots[replaced].set, badge }; + } + const result = slots.map((slot) => slot.badge); + + const user = state.users[message.userId]; + const sevenTVBadge = user?.sevenTVBadgeId + ? state.sevenTVBadges[user.sevenTVBadgeId] + : undefined; + const extra = [ + sevenTVBadge, + user?.bttvBadge ?? global.bttvBadges.get(message.userId), + global.ffzapBadges.get(message.userId), + global.chatterinoBadges.get(message.userId), + ]; + return [...result, ...extra.filter((badge) => badge !== undefined)]; + } + + function paint(message: ChatMessage): Paint | undefined { + const id = state.users[message.userId]?.paintId; + return id ? state.paints[id] : undefined; + } + + return { + state, + parts, + badges, + paint, + user: (message: ChatMessage): ChatUser | undefined => state.users[message.userId], + dispose() { + irc.close(); + sevenTV.close(); + bttv.close(); + ffz.close(); + }, + }; +} diff --git a/src/lib/chat/socket.ts b/src/lib/chat/socket.ts new file mode 100644 index 0000000..484d265 --- /dev/null +++ b/src/lib/chat/socket.ts @@ -0,0 +1,100 @@ +export interface ReconnectingSocketOptions { + /** Evaluated on every (re)connect, so a socket whose URL encodes its topics can change them. */ + url: () => string; + onOpen?: (socket: ReconnectingSocket) => void; + onMessage: (data: string, socket: ReconnectingSocket) => void; + onClose?: () => void; + /** Reconnect when nothing has been received for this long. */ + idleTimeoutMs?: number; + label: string; +} + +const MAX_BACKOFF_MS = 60_000; + +/** A WebSocket that reconnects with jittered exponential backoff until stopped. */ +export class ReconnectingSocket { + #options: ReconnectingSocketOptions; + #ws: WebSocket | undefined; + #attempt = 0; + #stopped = true; + #retryTimer: ReturnType | undefined; + #idleTimer: ReturnType | undefined; + + constructor(options: ReconnectingSocketOptions) { + this.#options = options; + } + + get open(): boolean { + return this.#ws?.readyState === WebSocket.OPEN; + } + + start(): void { + if (!this.#stopped) return; + this.#stopped = false; + this.#connect(); + } + + stop(): void { + this.#stopped = true; + clearTimeout(this.#retryTimer); + clearTimeout(this.#idleTimer); + this.#teardown(); + } + + send(data: string): void { + if (this.open) this.#ws?.send(data); + } + + /** Drops the connection and connects again, immediately or after backoff. */ + reconnect(immediate = false): void { + if (this.#stopped) return; + this.#teardown(); + clearTimeout(this.#retryTimer); + if (immediate) { + this.#connect(); + return; + } + const delay = Math.min(MAX_BACKOFF_MS, 1000 * 2 ** this.#attempt) * (0.5 + Math.random()); + this.#attempt++; + this.#retryTimer = setTimeout(() => this.#connect(), delay); + } + + /** Pushes the idle deadline out; call on any sign of life, e.g. a heartbeat. */ + keepAlive(timeoutMs = this.#options.idleTimeoutMs): void { + clearTimeout(this.#idleTimer); + if (timeoutMs === undefined || this.#stopped) return; + this.#idleTimer = setTimeout(() => { + console.warn(`[${this.#options.label}] no traffic for ${timeoutMs}ms, reconnecting`); + this.reconnect(true); + }, timeoutMs); + } + + #connect(): void { + if (this.#stopped) return; + const ws = new WebSocket(this.#options.url()); + this.#ws = ws; + ws.onopen = () => { + this.#attempt = 0; + this.keepAlive(); + this.#options.onOpen?.(this); + }; + ws.onmessage = (event) => { + this.keepAlive(); + if (typeof event.data === "string") this.#options.onMessage(event.data, this); + }; + ws.onclose = () => { + if (this.#ws !== ws) return; + this.#ws = undefined; + this.#options.onClose?.(); + this.reconnect(); + }; + } + + #teardown(): void { + const ws = this.#ws; + this.#ws = undefined; + if (!ws) return; + ws.onopen = ws.onmessage = ws.onclose = null; + ws.close(); + } +} diff --git a/src/lib/chat/tokenize.ts b/src/lib/chat/tokenize.ts new file mode 100644 index 0000000..5d63c87 --- /dev/null +++ b/src/lib/chat/tokenize.ts @@ -0,0 +1,142 @@ +import { + applyBttvModifier, + applyFfzFlags, + type EmoteEffects, + FfzFlag, + NO_EFFECTS, +} from "./effects"; +import type { EmoteRange } from "./irc/parse"; +import { twitchEmote } from "./providers/twitch"; +import type { Emote } from "./types"; + +export interface EmotePart { + type: "emote"; + emote: Emote; + /** Zero-width emotes and visible FFZ modifiers, stacked on top of `emote`. */ + overlays: Emote[]; + effects: EmoteEffects; +} + +export type MessagePart = + | { type: "text"; text: string } + | { type: "mention"; text: string } + | EmotePart; + +export type EmoteLookup = (name: string) => Emote | undefined; + +interface Atom { + word: string; + /** Set for Twitch emotes, which come pre-located in the IRC tags. */ + emote?: Emote; + spaceBefore: boolean; +} + +/** Splits a message into space-separated words, cutting Twitch emotes out at their ranges. */ +function atomize(text: string, ranges: EmoteRange[]): Atom[] { + // Twitch counts code points, not UTF-16 units, so emoji before an emote shift nothing. + const chars = Array.from(text); + const atoms: Atom[] = []; + let space = false; + const pushWords = (segment: string) => { + segment.split(" ").forEach((word, i) => { + if (i > 0) space = true; + if (word) { + atoms.push({ word, spaceBefore: space }); + space = false; + } + }); + }; + + let cursor = 0; + for (const range of ranges) { + if (range.start < cursor || range.end < range.start || range.end >= chars.length) continue; + pushWords(chars.slice(cursor, range.start).join("")); + const name = chars.slice(range.start, range.end + 1).join(""); + atoms.push({ word: name, emote: twitchEmote(range.id, name), spaceBefore: space }); + space = false; + cursor = range.end + 1; + } + pushWords(chars.slice(cursor).join("")); + return atoms; +} + +/** + * Turns a chat message into text, mention and emote parts. Third-party emotes are matched per + * word through `lookup`, which decides provider priority. On top of that: + * - zero-width emotes (7TV, BTTV) stack onto the preceding emote; + * - FFZ modifier emotes (`ffzX`, `ffzHyper`, ...) apply their flags to the preceding emote; + * - BTTV prefix modifiers (`w! h! Kappa`) apply to the next emote, or stay text without one. + */ +export function tokenize(text: string, ranges: EmoteRange[], lookup: EmoteLookup): MessagePart[] { + const parts: MessagePart[] = []; + let base: EmotePart | undefined; + let prefixes: Atom[] = []; + + const pushText = (value: string) => { + const last = parts.at(-1); + if (last?.type === "text") last.text += value; + else parts.push({ type: "text", text: value }); + }; + const pushSpace = (atom: Atom) => { + if (atom.spaceBefore && parts.length > 0) pushText(" "); + }; + const flushPrefixes = () => { + for (const prefix of prefixes) { + pushSpace(prefix); + pushText(prefix.word); + } + prefixes = []; + }; + const dropSpaceBefore = (part: EmotePart) => { + const index = parts.lastIndexOf(part); + const previous = parts[index - 1]; + if (previous?.type !== "text" || !previous.text.endsWith(" ")) return; + previous.text = previous.text.slice(0, -1); + if (!previous.text) parts.splice(index - 1, 1); + }; + + for (const atom of atomize(text, ranges)) { + const emote = atom.emote ?? lookup(atom.word); + + if (emote?.bttvModifier) { + prefixes.push(atom); + base = undefined; + continue; + } + + if (emote && base && prefixes.length === 0) { + if (emote.ffzModifierFlags !== undefined) { + base.effects = applyFfzFlags(base.effects, emote.ffzModifierFlags); + if (!(emote.ffzModifierFlags & FfzFlag.Hidden)) base.overlays.push(emote); + if (base.effects.noSpace) dropSpaceBefore(base); + continue; + } + if (emote.zeroWidth) { + base.overlays.push(emote); + continue; + } + } + + if (emote) { + let effects = NO_EFFECTS; + for (const prefix of prefixes) effects = applyBttvModifier(effects, prefix.word); + const first = prefixes[0] ?? atom; + prefixes = []; + if (!effects.noSpace) pushSpace(first); + base = { type: "emote", emote, overlays: [], effects }; + parts.push(base); + continue; + } + + flushPrefixes(); + pushSpace(atom); + if (atom.word.length > 1 && atom.word.startsWith("@")) { + parts.push({ type: "mention", text: atom.word }); + } else { + pushText(atom.word); + } + base = undefined; + } + flushPrefixes(); + return parts; +} diff --git a/src/lib/chat/types.ts b/src/lib/chat/types.ts new file mode 100644 index 0000000..08c18d9 --- /dev/null +++ b/src/lib/chat/types.ts @@ -0,0 +1,48 @@ +/** Image URLs keyed by pixel density, e.g. `{ 1: ".../1x.webp", 2: ".../2x.webp", 4: ".../4x.webp" }`. */ +export type ImageSet = Partial>; + +export type EmoteProvider = "twitch" | "7tv" | "bttv" | "ffz"; + +export interface Emote { + provider: EmoteProvider; + id: string; + name: string; + images: ImageSet; + /** Size at 1x, when the provider reports it. */ + width?: number; + height?: number; + /** Stacks on top of the previous emote instead of taking its own space (7TV, BTTV). */ + zeroWidth?: boolean; + /** FFZ modifier emote: its flags apply to the previous emote. */ + ffzModifierFlags?: number; + /** BTTV prefix modifier such as `w!`: applies to the next emote and never renders itself. */ + bttvModifier?: boolean; +} + +export type BadgeProvider = "twitch" | "7tv" | "bttv" | "ffz" | "ffzap" | "chatterino"; + +export interface Badge { + provider: BadgeProvider; + id: string; + title: string; + images: ImageSet; + /** Painted behind the badge image; FFZ ships white-on-transparent badges. */ + background?: string; +} + +export function srcset(images: ImageSet): string { + return Object.entries(images) + .map(([density, url]) => `${url} ${density}x`) + .join(", "); +} + +export function smallestImage(images: ImageSet): string | undefined { + return images[1] ?? images[2] ?? images[3] ?? images[4]; +} + +export async function fetchJson(url: string, init?: RequestInit): Promise { + const response = await fetch(url, init); + if (response.status === 404) return undefined; + if (!response.ok) throw new Error(`${url} answered ${response.status}`); + return (await response.json()) as T; +} diff --git a/src/root.css b/src/root.css deleted file mode 100644 index 4a01896..0000000 --- a/src/root.css +++ /dev/null @@ -1,8 +0,0 @@ -/* @tailwind base; -@tailwind components; -@tailwind utilities; */ - -@font-face { - font-family: Alsina; - src: url(/v3/font/Alsina_Ultrajada.ttf); -} diff --git a/src/root.tsx b/src/root.tsx deleted file mode 100644 index e56b8b0..0000000 --- a/src/root.tsx +++ /dev/null @@ -1,55 +0,0 @@ -// @refresh reload -import { createTheme, CssBaseline, ThemeProvider } from "@suid/material"; -import { Suspense } from "solid-js"; -import { - useLocation, - A, - Body, - ErrorBoundary, - FileRoutes, - Head, - Html, - Meta, - Routes, - Scripts, - Title, -} from "solid-start"; -import "./root.css"; - -export default function Root() { - const darkTheme = createTheme({ - palette: { - mode: "dark", - }, - }); - return ( - - - ChatIS by IS2511 - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/src/routes/[...404].tsx b/src/routes/[...404].tsx index 4177ecf..afa356e 100644 --- a/src/routes/[...404].tsx +++ b/src/routes/[...404].tsx @@ -1,9 +1,47 @@ +import { HttpStatusCode } from "@solidjs/start"; +import Box from "@suid/material/Box"; +import Container from "@suid/material/Container"; +import Typography from "@suid/material/Typography"; + +import MySiteTitle from "~/components/MySiteTitle"; +import SiteFooter from "~/components/SiteFooter"; +import SiteHeader from "~/components/SiteHeader"; + export default function NotFound() { - return ( -
-

- Not Found -

-
- ); + return ( + <> + Not found + + + + {/* Below the smallest bloom, only the seed remains. */} + + + + ); } diff --git a/src/routes/v3/chat/[channel].tsx b/src/routes/v3/chat/[channel].tsx index a9e2c07..b5d2816 100644 --- a/src/routes/v3/chat/[channel].tsx +++ b/src/routes/v3/chat/[channel].tsx @@ -1,19 +1,31 @@ -import { useParams } from "solid-start"; +import { useParams } from "@solidjs/router"; +import { createEffect, createSignal, onCleanup, onMount, Show } from "solid-js"; + +import ChatOverlay from "~/components/chat/ChatOverlay"; import MySiteTitle from "~/components/MySiteTitle"; +import { type ChatSession, createChatSession } from "~/lib/chat/session"; export default function Chat() { - let params = useParams<{ channel: string }>(); - let channel = params.channel; + const params = useParams<{ channel: string }>(); + const [session, setSession] = createSignal(); + + onMount(() => { + // Lets overlay.css make the page transparent for OBS browser sources. + document.documentElement.dataset.chatOverlay = ""; + onCleanup(() => delete document.documentElement.dataset.chatOverlay); + }); + + // Effects only run in the browser, which is the only place the sockets can live. + createEffect(() => { + const chat = createChatSession(params.channel); + setSession(chat); + onCleanup(() => chat.dispose()); + }); return ( <> - #{channel} -
- {/*

- Hello world! -

*/} - -
+ {`#${params.channel}`} + {(chat) => } ); } diff --git a/src/routes/v3/index.tsx b/src/routes/v3/index.tsx index 150d9a0..b62c35b 100644 --- a/src/routes/v3/index.tsx +++ b/src/routes/v3/index.tsx @@ -1,65 +1,55 @@ -import { - Box, - Container, - createTheme, - Stack, - ThemeProvider, - Typography -} from "@suid/material"; +import { Link, Meta } from "@solidjs/meta"; +import Container from "@suid/material/Container"; +import Typography from "@suid/material/Typography"; -import MySiteTitle from "~/components/MySiteTitle"; import FeaturesGrid from "~/components/FeaturesGrid"; +import MySiteTitle from "~/components/MySiteTitle"; +import SetupHero from "~/components/SetupHero"; +import SiteFooter from "~/components/SiteFooter"; +import SiteHeader from "~/components/SiteHeader"; -export default function ChatSetup() { - let theme = createTheme({ - palette: { - mode: "dark", - // primary: { - // main: '#3f51b5', - // }, - // secondary: { - // main: '#f50057', - // }, - } - }); +const url = "https://chat.shiftbloom.studio/v3"; +export default function ChatSetup() { return ( <> Setup - {/*
*/} - - {/*
*/} -
- - {/* A spacer. Adding a margin makes the container (document.body) move down, ew, breaks background */} - - - - ChatIS logo (mascot Peepo) - - ChatIS - by IS2511 - Setup - - - - - ChatIS is a Twitch chat overlay for streamers. Put your chat on screen! - - - Some features: - + + + + + + + + + + + - - - - - {/*

- Hello world! -

- */} -
- + +
+ + + + 01 / Features + + + Some features. + + + +
+ ); } diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 0000000..38ecf7d --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,72 @@ +import { createTheme } from "@suid/material/styles"; + +// shiftbloom Brand Book v2.0, §05 Color: a white garden, one loud flower. +// Bloom Red does ten percent of the area and ninety percent of the talking; +// everything else is paper and ink. There is no second accent and no dark mode. +export const brand = { + bloom: "#FF2E52", + root: "#C81040", + petal: "#FF8FAB", + blush: "#FFE9EF", + ink: "#1A1216", + paper: "#FFFFFF", +} as const; + +// §06 Typography: Clash Display makes the statements, General Sans does the +// explaining, JetBrains Mono keeps the books. The families live in brand.css. +const display = "var(--font-display)"; +const sans = "var(--font-sans)"; +const mono = "var(--font-mono)"; + +const type = ( + fontFamily: string, + fontWeight: number, + fontSize: string, + lineHeight: number, + letterSpacing = "0", +) => ({ fontFamily, fontWeight, fontSize, lineHeight, letterSpacing }); + +export const theme = createTheme({ + palette: { + mode: "light", + // Root Red is the hover state and the only red small text may wear. + primary: { + main: brand.bloom, + dark: brand.root, + light: brand.petal, + contrastText: brand.paper, + }, + secondary: { main: brand.ink, contrastText: brand.paper }, + text: { + primary: brand.ink, + secondary: "rgba(26, 18, 22, 0.6)", + disabled: "rgba(26, 18, 22, 0.4)", + }, + background: { default: brand.paper, paper: brand.paper }, + divider: "rgba(26, 18, 22, 0.12)", + action: { active: brand.ink }, + }, + typography: { + fontFamily: sans, + // Display is tight (0.95–1.1), body is roomy (1.6–1.7). Never the other way around. + h1: type(display, 600, "clamp(2.75rem, 7vw, 5.25rem)", 1.02, "-0.02em"), + h2: type(display, 600, "clamp(2rem, 4.4vw, 3.25rem)", 1.05, "-0.02em"), + h3: type(display, 600, "clamp(1.625rem, 3vw, 2.125rem)", 1.1, "-0.015em"), + h4: type(display, 600, "1.625rem", 1.1, "-0.01em"), + h5: type(display, 600, "1.375rem", 1.1, "-0.01em"), + h6: type(display, 600, "1.125rem", 1.2), + subtitle1: type(sans, 500, "1.125rem", 1.5), + subtitle2: type(sans, 600, "0.9375rem", 1.5), + body1: type(sans, 400, "1rem", 1.65), + body2: type(sans, 400, "0.9375rem", 1.6), + // Labels, specs and metadata wear mono. A label whispers: small caps, +16% tracking. + button: type(mono, 600, "0.8125rem", 1.75, "0.08em"), + caption: type(mono, 400, "0.75rem", 1.5, "0.02em"), + overline: type(mono, 500, "0.71875rem", 1.6, "0.16em"), + }, + shape: { borderRadius: 4 }, + components: { + // Flat Bloom Red, no glow. The gradient lives inside the mark only. + MuiButton: { defaultProps: { disableElevation: true } }, + }, +}); diff --git a/tailwind.config.cjs b/tailwind.config.cjs deleted file mode 100644 index 14d3e24..0000000 --- a/tailwind.config.cjs +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["./src/**/*.{html,js,jsx,ts,tsx}"], - theme: { - extend: {} - }, - plugins: [] -}; diff --git a/test/chat/irc.test.ts b/test/chat/irc.test.ts new file mode 100644 index 0000000..8359e0a --- /dev/null +++ b/test/chat/irc.test.ts @@ -0,0 +1,76 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { + parseAction, + parseBadgesTag, + parseEmotesTag, + parseIrcLine, + unescapeTag, +} from "../../src/lib/chat/irc/parse"; + +describe("parseIrcLine", () => { + it("parses a tagged PRIVMSG captured from Twitch", () => { + const line = + "@badge-info=;badges=premium/1;color=#1E90FF;display-name=viewer_one;emotes=425618:12-14;" + + "id=00000000-0000-4000-8000-000000000001;room-id=552120296;tmi-sent-ts=1790708943647;" + + "user-id=100000001;user-type= :viewer_one!viewer_one@viewer_one.tmi.twitch.tv PRIVMSG #zackrawrr :1 more hour LUL"; + const message = parseIrcLine(line); + assert.ok(message); + assert.equal(message.command, "PRIVMSG"); + assert.equal(message.nick, "viewer_one"); + assert.deepEqual(message.params, ["#zackrawrr", "1 more hour LUL"]); + assert.equal(message.tags["display-name"], "viewer_one"); + assert.equal(message.tags["user-type"], ""); + assert.equal(message.tags["badge-info"], ""); + }); + + it("unescapes tag values", () => { + const message = parseIrcLine( + "@system-msg=viewer_two\\ssubscribed\\swith\\sPrime.;msg-id=sub :tmi.twitch.tv USERNOTICE #jynxzi", + ); + assert.equal(message?.tags["system-msg"], "viewer_two subscribed with Prime."); + assert.equal(message?.nick, undefined); + assert.deepEqual(message?.params, ["#jynxzi"]); + assert.equal(unescapeTag("a\\:b\\\\c\\"), "a;b\\c"); + }); + + it("parses untagged commands and trailing-only params", () => { + assert.deepEqual(parseIrcLine("PING :tmi.twitch.tv"), { + tags: {}, + nick: undefined, + command: "PING", + params: ["tmi.twitch.tv"], + }); + assert.deepEqual(parseIrcLine(":tmi.twitch.tv CLEARCHAT #zackrawrr :some_user")?.params, [ + "#zackrawrr", + "some_user", + ]); + assert.equal(parseIrcLine(""), undefined); + }); +}); + +describe("tag helpers", () => { + it("parses and sorts emote ranges", () => { + assert.deepEqual(parseEmotesTag("25:12-16/1902:0-4,6-10"), [ + { id: "1902", start: 0, end: 4 }, + { id: "1902", start: 6, end: 10 }, + { id: "25", start: 12, end: 16 }, + ]); + assert.deepEqual(parseEmotesTag(""), []); + assert.deepEqual(parseEmotesTag(undefined), []); + }); + + it("parses badges", () => { + assert.deepEqual(parseBadgesTag("moderator/1,subscriber/3012"), [ + { set: "moderator", version: "1" }, + { set: "subscriber", version: "3012" }, + ]); + assert.deepEqual(parseBadgesTag(""), []); + }); + + it("unwraps /me actions", () => { + assert.deepEqual(parseAction("\u0001ACTION waves\u0001"), { text: "waves", action: true }); + assert.deepEqual(parseAction("hello"), { text: "hello", action: false }); + }); +}); diff --git a/test/chat/providers.test.ts b/test/chat/providers.test.ts new file mode 100644 index 0000000..b6ae77d --- /dev/null +++ b/test/chat/providers.test.ts @@ -0,0 +1,290 @@ +import assert from "node:assert/strict"; +import { afterEach, describe, it } from "node:test"; + +import { applyFfzFlags, FfzFlag, hasEffects, NO_EFFECTS } from "../../src/lib/chat/effects"; +import { + bttvUser, + fetchBTTVBadges, + fetchBTTVChannelEmotes, + fetchBTTVGlobalEmotes, +} from "../../src/lib/chat/providers/bttv"; +import { fetchChatterinoBadges } from "../../src/lib/chat/providers/chatterino"; +import { + fetchFFZAPBadges, + fetchFFZBadges, + fetchFFZGlobal, + fetchFFZRoom, +} from "../../src/lib/chat/providers/ffz"; +import { fetchSevenTVChannel } from "../../src/lib/chat/providers/seventv/api"; +import { + defaultColor, + fetchTwitchChannelBadges, + fetchTwitchGlobalBadges, + readableColor, + resolveTwitchBadges, +} from "../../src/lib/chat/providers/twitch"; +import { stubFetch } from "../helpers"; + +let restoreFetch = () => {}; +afterEach(() => restoreFetch()); + +const BTTV = "https://api.betterttv.net/3/cached"; +const FFZ = "https://api.frankerfacez.com/v1"; + +describe("BTTV", () => { + it("maps global emotes, flagging only known prefix modifiers", async () => { + restoreFetch = stubFetch({ + [`${BTTV}/emotes/global`]: [ + { id: "a", code: "w!", modifier: true }, + { id: "b", code: "x!", modifier: true }, + { id: "c", code: "cvHazmat" }, + ], + }); + const [wide, unknown, hazmat] = await fetchBTTVGlobalEmotes(); + assert.equal(wide.bttvModifier, true); + assert.equal(unknown.bttvModifier, undefined); + assert.equal(hazmat.zeroWidth, true); + assert.deepEqual(hazmat.images, { + 1: "https://cdn.betterttv.net/emote/c/1x.webp", + 2: "https://cdn.betterttv.net/emote/c/2x.webp", + 4: "https://cdn.betterttv.net/emote/c/3x.webp", + }); + }); + + it("merges channel and shared emotes, and treats 404 as no account", async () => { + restoreFetch = stubFetch({ + [`${BTTV}/users/twitch/1`]: { + channelEmotes: [{ id: "a", code: "Mine" }], + sharedEmotes: [{ id: "b", code: "Shared" }], + }, + [`${BTTV}/users/twitch/2`]: 404, + }); + assert.deepEqual( + (await fetchBTTVChannelEmotes("1"))?.map((e) => e.name), + ["Mine", "Shared"], + ); + assert.equal(await fetchBTTVChannelEmotes("2"), undefined); + }); + + it("keys badges by Twitch user id", async () => { + restoreFetch = stubFetch({ + [`${BTTV}/badges/twitch`]: [ + { + providerId: "100000004", + badge: { description: "NightDev Developer", svg: "https://cdn/dev.svg" }, + }, + ], + }); + const badge = (await fetchBTTVBadges()).get("100000004"); + assert.equal(badge?.title, "NightDev Developer"); + assert.deepEqual(badge?.images, { 1: "https://cdn/dev.svg" }); + }); + + it("falls back to the legacy glow flag and ignores emotes of non-Pro users", () => { + const user = bttvUser({ + providerId: "1", + pro: false, + glow: true, + emotes: [{ id: "a", code: "A" }], + }); + assert.equal(user.effect, "glow"); + assert.deepEqual(user.emotes, []); + }); +}); + +describe("FFZ", () => { + it("reads default sets, per-user sets, modifiers and animated images", async () => { + restoreFetch = stubFetch({ + [`${FFZ}/set/global`]: { + default_sets: [3], + sets: { + 3: { + id: 3, + emoticons: [ + { + id: 1, + name: "ffzW", + width: 18, + height: 18, + modifier: true, + modifier_flags: 9, + urls: { 1: "//cdn.frankerfacez.com/emote/1/1" }, + }, + ], + }, + 1532818: { + id: 1532818, + emoticons: [ + { + id: 2, + name: "Anim", + width: 28, + height: 28, + urls: { 1: "https://cdn/2/1", 2: "https://cdn/2/2" }, + animated: { 1: "https://cdn/2/animated/1" }, + }, + ], + }, + }, + users: { 1532818: ["alice", "bob"] }, + }, + }); + const global = await fetchFFZGlobal(); + assert.deepEqual(global.defaultSets, ["3"]); + assert.deepEqual(global.userSets.get("alice"), ["1532818"]); + const [ffzW] = global.sets.get("3") ?? []; + assert.equal(ffzW.ffzModifierFlags, FfzFlag.Hidden | FfzFlag.GrowX); + assert.equal(ffzW.images[1], "https://cdn.frankerfacez.com/emote/1/1"); + const [anim] = global.sets.get("1532818") ?? []; + assert.deepEqual(anim.images, { 1: "https://cdn/2/animated/1" }); + assert.equal(anim.ffzModifierFlags, undefined); + }); + + it("reads a room's custom badges and per-channel badge holders", async () => { + restoreFetch = stubFetch({ + [`${FFZ}/room/id/1`]: { + room: { + mod_urls: { 1: "https://cdn/mod/1", 2: "https://cdn/mod/2" }, + vip_badge: null, + user_badge_ids: { 2: [123, 456] }, + }, + sets: { 99: { id: 99, emoticons: [] } }, + }, + [`${FFZ}/room/id/2`]: 404, + }); + const room = await fetchFFZRoom("1"); + assert.equal(room?.moderatorBadge?.background, "#34ae0a"); + assert.equal(room?.vipBadge, undefined); + assert.deepEqual(room?.userBadges.get("2"), ["123", "456"]); + assert.deepEqual([...(room?.sets.keys() ?? [])], ["99"]); + assert.equal(await fetchFFZRoom("2"), undefined); + }); + + it("maps global badges to their holders", async () => { + restoreFetch = stubFetch({ + [`${FFZ}/badges/ids`]: { + badges: [ + { + id: 2, + title: "Bot", + color: "#595959", + replaces: "moderator", + urls: { 1: "https://cdn/b/1" }, + }, + ], + users: { 2: [42] }, + }, + }); + const { badges, users } = await fetchFFZBadges(); + assert.deepEqual(users.get("42"), ["2"]); + assert.equal(badges.get("2")?.replaces, "moderator"); + assert.equal(badges.get("2")?.background, "#595959"); + }); + + it("applies the FFZ:AP tier rules to badge colors", async () => { + restoreFetch = stubFetch({ + "https://api.ffzap.com/v1/supporters": [ + { id: "1", tier: 1 }, + { id: "2", tier: 2, badge_color: "#812FA8" }, + { id: "3", tier: 3, badge_color: "#FFD700", badge_is_colored: 1 }, + { id: "4", tier: 0 }, + ], + }); + const badges = await fetchFFZAPBadges(); + assert.equal(badges.get("1")?.background, "#755000"); + assert.equal(badges.get("2")?.background, "#812FA8"); + assert.equal(badges.get("3")?.background, undefined); + assert.equal(badges.has("4"), false); + assert.equal(badges.get("2")?.images[4], "https://api.ffzap.com/v1/user/badge/2/3"); + }); + + it("stacks exclusive animations the way FFZ does", () => { + const inOut = applyFfzFlags(NO_EFFECTS, FfzFlag.Appear | FfzFlag.Leave | FfzFlag.Rotate); + assert.deepEqual(inOut.animations, ["cb-fx-in-out 6s linear infinite"]); + const slide = applyFfzFlags(NO_EFFECTS, FfzFlag.Slide | FfzFlag.Rotate); + assert.deepEqual([slide.slide, slide.animations], [true, []]); + assert.equal(hasEffects(applyFfzFlags(NO_EFFECTS, FfzFlag.Hidden)), false); + }); +}); + +describe("7TV", () => { + it("reads the channel's 7TV user and emote set, and treats 404 as no account", async () => { + restoreFetch = stubFetch({ + "https://7tv.io/v3/users/twitch/1": { + user: { id: "01FE9D" }, + emote_set: { id: "set", name: "xqc", flags: 0, emotes: [] }, + }, + "https://7tv.io/v3/users/twitch/2": 404, + }); + const channel = await fetchSevenTVChannel("1"); + assert.equal(channel?.userId, "01FE9D"); + assert.equal(channel?.emoteSet?.id, "set"); + assert.equal(await fetchSevenTVChannel("2"), undefined); + }); +}); + +describe("Twitch and Chatterino", () => { + const IVR = "https://api.ivr.fi/v2/twitch/badges"; + const version = (id: string, title: string) => ({ + id, + title, + image_url_1x: `https://cdn/${title}/1`, + image_url_2x: `https://cdn/${title}/2`, + image_url_4x: `https://cdn/${title}/3`, + }); + + it("prefers channel badges over global ones", async () => { + restoreFetch = stubFetch({ + [`${IVR}/global`]: [ + { set_id: "subscriber", versions: [version("0", "Global Sub")] }, + { set_id: "moderator", versions: [version("1", "Moderator")] }, + ], + [`${IVR}/channel?id=7`]: [ + { set_id: "subscriber", versions: [version("0", "Channel Sub")] }, + ], + }); + const global = await fetchTwitchGlobalBadges(); + const channel = await fetchTwitchChannelBadges("7"); + const badges = resolveTwitchBadges( + [ + { set: "moderator", version: "1" }, + { set: "subscriber", version: "0" }, + { set: "unknown", version: "1" }, + ], + channel, + global, + ); + assert.deepEqual( + badges.map((b) => b.title), + ["Moderator", "Channel Sub"], + ); + assert.equal(badges[0].images[4], "https://cdn/Moderator/3"); + }); + + it("assigns stable default colors and lifts unreadably dark ones", () => { + assert.equal(defaultColor("forsen"), defaultColor("forsen")); + assert.match(defaultColor("forsen"), /^#[0-9A-F]{6}$/); + assert.equal(readableColor("#FF4500"), "#FF4500"); + assert.notEqual(readableColor("#0000FF"), "#0000FF"); + assert.equal(readableColor("not a color"), "not a color"); + }); + + it("maps Chatterino badges to each listed user", async () => { + restoreFetch = stubFetch({ + "https://api.chatterino.com/badges": { + badges: [ + { + tooltip: "Chatterino Top Donator", + image1: "https://fourtf.com/1.png", + image2: "https://fourtf.com/2.png", + image3: "https://fourtf.com/3.png", + users: ["100000005", "1"], + }, + ], + }, + }); + const badges = await fetchChatterinoBadges(); + assert.equal(badges.get("100000005")?.title, "Chatterino Top Donator"); + assert.equal(badges.get("1")?.images[3], "https://fourtf.com/3.png"); + }); +}); diff --git a/test/chat/seventv.test.ts b/test/chat/seventv.test.ts new file mode 100644 index 0000000..42189f3 --- /dev/null +++ b/test/chat/seventv.test.ts @@ -0,0 +1,175 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { sevenTVEmote } from "../../src/lib/chat/providers/seventv/api"; +import { emoteSetChange } from "../../src/lib/chat/providers/seventv/events"; +import { + colorFromInt, + paintFromV3, + paintFromV4, + type V4Paint, +} from "../../src/lib/chat/providers/seventv/paint"; + +// Trimmed from frames the 7TV EventAPI sent on 2026-09-29. +const PINK_PRINCESS = { + id: "01H9902AVR000BVQ7MEDFXVNTG", + name: "Pink Princess", + color: null, + function: "LINEAR_GRADIENT" as const, + repeat: false, + angle: 0, + shape: "circle", + image_url: "", + stops: [ + { at: 0.2, color: -214919681 }, + { at: 0.7, color: -3673601 }, + ], + shadows: [{ x_offset: 0, y_offset: 0, radius: 0.1, color: -6369025 }], +}; + +const host = { + url: "//cdn.7tv.app/emote/01FGJ0HW0G00021TQRJX35N1ZK", + files: [ + { name: "1x.webp", width: 32, height: 32, format: "WEBP" }, + { name: "2x.webp", width: 64, height: 64, format: "WEBP" }, + { name: "1x.avif", width: 32, height: 32, format: "AVIF" }, + { name: "4x.webp", width: 128, height: 128, format: "WEBP" }, + ], +}; +const active = (id: string, name: string, flags = 0) => ({ + id, + name, + flags, + data: { id, name, flags: 0, animated: false, host }, +}); + +describe("7TV paints", () => { + it("decodes signed RGBA integers", () => { + assert.equal(colorFromInt(-1), "#ffffffff"); + assert.equal(colorFromInt(102), "#00000066"); + assert.equal(colorFromInt(-214919681), "#f33095ff"); + }); + + it("converts a v3 gradient paint", () => { + const paint = paintFromV3(PINK_PRINCESS); + assert.deepEqual(paint.layers, [ + { + opacity: 1, + image: "linear-gradient(0deg, #f33095ff 20%, #ffc7f1ff 70%)", + color: undefined, + }, + ]); + assert.equal(paint.filter, "drop-shadow(#ff9ed0ff 0px 0px 0.1px)"); + }); + + it("converts repeating radial and image paints", () => { + const radial = paintFromV3({ + ...PINK_PRINCESS, + function: "RADIAL_GRADIENT", + repeat: true, + shadows: null, + }); + assert.match(radial.layers[0].image ?? "", /^repeating-radial-gradient\(circle, /); + assert.equal(radial.filter, undefined); + + const image = paintFromV3({ + ...PINK_PRINCESS, + function: "URL", + stops: [], + image_url: "https://cdn.7tv.app/paint/x/layer/y/1x.webp", + }); + assert.equal(image.layers[0].image, 'url("https://cdn.7tv.app/paint/x/layer/y/1x.webp")'); + }); + + it("keeps shadow-only paints layerless", () => { + assert.deepEqual(paintFromV3({ ...PINK_PRINCESS, stops: [] }).layers, []); + }); + + it("renders every v4 layer with its opacity and prefers animated images", () => { + const v4: V4Paint = { + id: "p", + name: "Layered", + data: { + layers: [ + { + opacity: 1, + ty: { + __typename: "PaintLayerTypeLinearGradient", + angle: 66, + repeating: true, + stops: [ + { at: 0.5, color: { hex: "#CA804EFF" } }, + { at: 1, color: { hex: "#503611FF" } }, + ], + }, + }, + { + opacity: 0.5, + ty: { + __typename: "PaintLayerTypeImage", + images: [ + { url: "https://cdn/1x_static.webp", scale: 1, frameCount: 1 }, + { url: "https://cdn/1x.webp", scale: 1, frameCount: 24 }, + { url: "https://cdn/2x.webp", scale: 2, frameCount: 24 }, + ], + }, + }, + { + opacity: 1, + ty: { + __typename: "PaintLayerTypeSingleColor", + color: { hex: "#FF0000FF" }, + }, + }, + ], + shadows: [{ color: { hex: "#000000FF" }, offsetX: 1, offsetY: 2, blur: 3 }], + }, + }; + const paint = paintFromV4(v4); + assert.deepEqual(paint.layers, [ + { + opacity: 1, + image: "repeating-linear-gradient(66deg, #CA804EFF 50%, #503611FF 100%)", + }, + { opacity: 0.5, image: 'url("https://cdn/1x.webp")' }, + { opacity: 1, color: "#FF0000FF" }, + ]); + assert.equal(paint.filter, "drop-shadow(#000000FF 1px 2px 3px)"); + }); +}); + +describe("7TV emotes", () => { + it("builds WebP image sets and reads the zero-width flag", () => { + const emote = sevenTVEmote(active("e1", "RainTime", 1)); + assert.deepEqual(emote?.images, { + 1: "https://cdn.7tv.app/emote/01FGJ0HW0G00021TQRJX35N1ZK/1x.webp", + 2: "https://cdn.7tv.app/emote/01FGJ0HW0G00021TQRJX35N1ZK/2x.webp", + 4: "https://cdn.7tv.app/emote/01FGJ0HW0G00021TQRJX35N1ZK/4x.webp", + }); + assert.equal(emote?.zeroWidth, true); + assert.equal(emote?.width, 32); + assert.equal(sevenTVEmote({ id: "gone", name: "gone", flags: 0, data: null }), undefined); + }); + + it("reads EventAPI emote set changes, treating renames as remove + add", () => { + const change = emoteSetChange({ + id: "set", + pushed: [{ key: "emotes", index: 0, value: active("new", "Laughge") }], + pulled: [{ key: "emotes", index: 1, old_value: active("old", "Old") }], + updated: [ + { + key: "emotes", + index: 2, + old_value: active("ren", "Before"), + value: active("ren", "After"), + }, + { key: "name", value: "ignored" }, + ], + }); + assert.deepEqual(change.removed, ["old", "ren"]); + assert.deepEqual( + change.added.map((e) => e.name), + ["After", "Laughge"], + ); + }); +}); diff --git a/test/chat/sockets.test.ts b/test/chat/sockets.test.ts new file mode 100644 index 0000000..2a1a66a --- /dev/null +++ b/test/chat/sockets.test.ts @@ -0,0 +1,296 @@ +import assert from "node:assert/strict"; +import { afterEach, beforeEach, describe, it } from "node:test"; + +import { TwitchIrc } from "../../src/lib/chat/irc/client"; +import type { IrcMessage } from "../../src/lib/chat/irc/parse"; +import { BTTVSocket, type BTTVUser } from "../../src/lib/chat/providers/bttv"; +import { FFZPubSub } from "../../src/lib/chat/providers/ffz"; +import { channelCondition, SevenTVEvents } from "../../src/lib/chat/providers/seventv/events"; +import type { Paint } from "../../src/lib/chat/providers/seventv/paint"; +import { ReconnectingSocket } from "../../src/lib/chat/socket"; +import type { Badge, Emote } from "../../src/lib/chat/types"; +import { FakeWebSocket, recorder, useFakeWebSocket } from "../helpers"; + +let restoreWebSocket: () => void; +beforeEach(() => { + restoreWebSocket = useFakeWebSocket(); +}); +afterEach(() => restoreWebSocket()); + +describe("ReconnectingSocket", () => { + it("reconnects with backoff after a drop until stopped", (t) => { + t.mock.timers.enable({ apis: ["setTimeout"] }); + const socket = new ReconnectingSocket({ + label: "test", + url: () => "wss://x", + onMessage() {}, + }); + socket.start(); + FakeWebSocket.latest.accept(); + FakeWebSocket.latest.drop(); + assert.equal(FakeWebSocket.instances.length, 1, "waits before retrying"); + + t.mock.timers.tick(1500); // first retry: 0.5–1.5 s + assert.equal(FakeWebSocket.instances.length, 2); + + FakeWebSocket.latest.drop(); + socket.stop(); + t.mock.timers.tick(120_000); + assert.equal(FakeWebSocket.instances.length, 2, "stop() cancels the pending retry"); + }); + + it("reconnects immediately when the idle deadline passes", (t) => { + t.mock.timers.enable({ apis: ["setTimeout"] }); + const socket = new ReconnectingSocket({ + label: "test", + url: () => "wss://x", + idleTimeoutMs: 1000, + onMessage() {}, + }); + socket.start(); + FakeWebSocket.latest.accept(); + t.mock.timers.tick(900); + FakeWebSocket.latest.receive("heartbeat"); // pushes the deadline out + t.mock.timers.tick(900); + assert.equal(FakeWebSocket.instances.length, 1); + t.mock.timers.tick(200); + assert.equal(FakeWebSocket.instances.length, 2); + socket.stop(); + }); +}); + +describe("TwitchIrc", () => { + it("logs in anonymously, answers PING and forwards messages", () => { + const messages = recorder<[IrcMessage]>(); + const status = recorder<[string]>(); + const irc = new TwitchIrc({ channel: "Forsen", onMessage: messages, onStatus: status }); + const ws = FakeWebSocket.latest; + ws.accept(); + + assert.equal(ws.sent[0], "CAP REQ :twitch.tv/tags twitch.tv/commands"); + assert.match(ws.sent[1], /^NICK justinfan\d+$/); + assert.equal(ws.sent[2], "JOIN #forsen"); + + ws.receive( + ":justinfan1!justinfan1@justinfan1.tmi.twitch.tv JOIN #forsen\r\n" + + "PING :tmi.twitch.tv\r\n" + + "@room-id=22484632 :tmi.twitch.tv ROOMSTATE #forsen\r\n", + ); + assert.deepEqual(status.calls, [["connected"]]); + assert.equal(ws.sent.at(-1), "PONG :tmi.twitch.tv"); + assert.deepEqual( + messages.calls.map(([m]) => [m.command, m.tags["room-id"]]), + [["ROOMSTATE", "22484632"]], + ); + irc.close(); + }); +}); + +describe("7TV EventAPI", () => { + const handlers = () => ({ + onEmoteSetChange: recorder(), + onEmoteSetCreate: recorder<[string, number]>(), + onPaint: recorder<[Paint]>(), + onBadge: recorder<[Badge]>(), + onEntitlement: recorder(), + onUserEmoteSet: recorder<[string, string]>(), + }); + + it("sends subscriptions once the server says hello, and after every reconnect", (t) => { + t.mock.timers.enable({ apis: ["setTimeout"] }); + const events = new SevenTVEvents(handlers()); + events.subscribe("entitlement.*", channelCondition("22484632")); + const ws = FakeWebSocket.latest; + ws.accept(); + assert.deepEqual(ws.sent, [], "nothing before the hello"); + + ws.receive({ op: 1, d: { heartbeat_interval: 45_000, session_id: "s" } }); + assert.deepEqual(ws.sentJson, [ + { + op: 35, + d: { + type: "entitlement.*", + condition: { ctx: "channel", platform: "TWITCH", id: "22484632" }, + }, + }, + ]); + + events.subscribe("emote_set.*", { object_id: "set" }); + events.subscribe("emote_set.*", { object_id: "set" }); + assert.equal(ws.sent.length, 2, "live subscribe is sent once"); + + ws.receive({ op: 4, d: {} }); // server asks us to reconnect + t.mock.timers.tick(1500); + const next = FakeWebSocket.latest; + assert.notEqual(next, ws); + next.accept(); + next.receive({ op: 1, d: { heartbeat_interval: 45_000, session_id: "s2" } }); + assert.deepEqual( + next.sentJson.map((frame) => (frame as { d: { type: string } }).d.type), + ["entitlement.*", "emote_set.*"], + ); + events.close(); + }); + + it("routes dispatches to the right handlers", () => { + const h = handlers(); + const events = new SevenTVEvents(h); + const ws = FakeWebSocket.latest; + ws.accept(); + const dispatch = (type: string, body: unknown) => ws.receive({ op: 0, d: { type, body } }); + const user = { id: "u", connections: [{ platform: "TWITCH", id: "100000003" }] }; + + dispatch("entitlement.create", { id: "0", object: { kind: "PAINT", ref_id: "p1", user } }); + dispatch("entitlement.delete", { id: "0", object: { kind: "BADGE", ref_id: "b1", user } }); + dispatch("cosmetic.create", { + id: "p1", + object: { + kind: "PAINT", + data: { + id: "p1", + name: "Pink Princess", + function: "LINEAR_GRADIENT", + color: null, + repeat: false, + angle: 0, + stops: [{ at: 0, color: -1 }], + shadows: [], + }, + }, + }); + dispatch("cosmetic.create", { + id: "b1", + object: { + kind: "BADGE", + data: { + id: "b1", + tooltip: "7TV Subscriber", + host: { + url: "//cdn.7tv.app/badge/b1", + files: [{ name: "1x.webp", width: 18, height: 18, format: "WEBP" }], + }, + }, + }, + }); + dispatch("emote_set.create", { id: "personal", object: { id: "personal", flags: 4 } }); + dispatch("user.update", { + id: "7tv-user", + updated: [ + { + key: "connections", + index: 0, + value: [{ key: "emote_set", old_value: { id: "old" }, value: { id: "new" } }], + }, + ], + }); + + assert.deepEqual(h.onEntitlement.calls, [ + [{ kind: "PAINT", refId: "p1", twitchId: "100000003" }, true], + [{ kind: "BADGE", refId: "b1", twitchId: "100000003" }, false], + ]); + assert.equal(h.onPaint.calls[0][0].name, "Pink Princess"); + assert.deepEqual(h.onBadge.calls[0][0].images, { + 1: "https://cdn.7tv.app/badge/b1/1x.webp", + }); + assert.deepEqual(h.onEmoteSetCreate.calls, [["personal", 4]]); + assert.deepEqual(h.onUserEmoteSet.calls, [["7tv-user", "new"]]); + events.close(); + }); +}); + +describe("BTTV socket", () => { + it("joins channels on connect and routes emote and user events", () => { + const h = { + onEmoteAdd: recorder<[string, Emote]>(), + onEmoteRename: recorder(), + onEmoteRemove: recorder(), + onUser: recorder<[BTTVUser]>(), + }; + const socket = new BTTVSocket(h); + socket.join("22484632"); + const ws = FakeWebSocket.latest; + ws.accept(); + assert.deepEqual(ws.sentJson, [ + { name: "join_channel", data: { name: "twitch:22484632" } }, + ]); + + const channel = "twitch:22484632"; + ws.receive({ name: "emote_create", data: { channel, emote: { id: "e1", code: "New" } } }); + ws.receive({ + name: "emote_update", + data: { channel, emote: { id: "e1", code: "Renamed" } }, + }); + ws.receive({ name: "emote_delete", data: { channel, emoteId: "e1" } }); + // Trimmed from a lookup_user frame captured on 2026-09-29. + ws.receive({ + name: "lookup_user", + data: { + providerId: "100000002", + pro: true, + glow: false, + usernameEffect: "iridescence", + badge: { url: "https://cdn.betterttv.net/badges/pro/x.webp" }, + emotes: [ + { id: "659898afda49e66ae974ab03", code: "uhmWait", width: 40, height: 28 }, + ], + }, + }); + + assert.equal(h.onEmoteAdd.calls[0][0], "22484632"); + assert.equal(h.onEmoteAdd.calls[0][1].name, "New"); + assert.deepEqual(h.onEmoteRename.calls, [["22484632", "e1", "Renamed"]]); + assert.deepEqual(h.onEmoteRemove.calls, [["22484632", "e1"]]); + const [user] = h.onUser.calls[0]; + assert.equal(user.twitchId, "100000002"); + assert.equal(user.effect, "iridescence"); + assert.equal(user.badge?.images[1], "https://cdn.betterttv.net/badges/pro/x.webp"); + assert.deepEqual( + user.emotes.map((e) => [e.name, e.width]), + [["uhmWait", 40]], + ); + socket.close(); + }); +}); + +describe("FFZ pubsub", () => { + it("puts topics in the URL, reconnects to add a room and routes emote edits", () => { + const h = { onEmoteAdd: recorder<[string, Emote]>(), onEmoteRemove: recorder() }; + const pubsub = new FFZPubSub(h); + assert.equal(FakeWebSocket.latest.url, "wss://pubsub.workers.frankerfacez.com/ws?t=global"); + + pubsub.subscribe("22484632"); + pubsub.subscribe("22484632"); + assert.equal(FakeWebSocket.instances.length, 2, "one reconnect per new topic"); + assert.equal( + FakeWebSocket.latest.url, + "wss://pubsub.workers.frankerfacez.com/ws?t=global&t=twitch%2F22484632", + ); + + const ws = FakeWebSocket.latest; + ws.accept(); + ws.receive({ msg: "pong", time: 1 }); + const topic = "twitch/22484632"; + ws.receive({ + topic, + data: { + cmd: "add_emote", + data: { + set_id: 1234, + emote: { + id: 5, + name: "ffzNew", + width: 28, + height: 28, + urls: { 1: "https://cdn.frankerfacez.com/emote/5/1" }, + }, + }, + }, + }); + ws.receive({ topic, data: { cmd: "remove_emote", data: { set_id: 1234, emote_id: 5 } } }); + + assert.equal(h.onEmoteAdd.calls[0][0], "1234"); + assert.equal(h.onEmoteAdd.calls[0][1].name, "ffzNew"); + assert.deepEqual(h.onEmoteRemove.calls, [["1234", "5"]]); + pubsub.close(); + }); +}); diff --git a/test/chat/tokenize.test.ts b/test/chat/tokenize.test.ts new file mode 100644 index 0000000..5bc61d0 --- /dev/null +++ b/test/chat/tokenize.test.ts @@ -0,0 +1,133 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { FfzFlag, NO_EFFECTS } from "../../src/lib/chat/effects"; +import { EmoteSet } from "../../src/lib/chat/emote-set"; +import { parseEmotesTag } from "../../src/lib/chat/irc/parse"; +import { type MessagePart, tokenize } from "../../src/lib/chat/tokenize"; +import type { Emote } from "../../src/lib/chat/types"; + +const emote = (name: string, extra: Partial = {}): Emote => ({ + provider: "7tv", + id: `id-${name}`, + name, + images: { 1: `https://cdn.example/${name}/1x.webp` }, + ...extra, +}); + +const set = new EmoteSet([ + emote("Clap"), + emote("RainTime", { zeroWidth: true }), + emote("SteerR", { zeroWidth: true }), + emote("w!", { provider: "bttv", bttvModifier: true }), + emote("h!", { provider: "bttv", bttvModifier: true }), + emote("ffzW", { provider: "ffz", ffzModifierFlags: FfzFlag.Hidden | FfzFlag.GrowX }), + emote("ffzHyper", { + provider: "ffz", + ffzModifierFlags: FfzFlag.Hidden | FfzFlag.HyperRed | FfzFlag.Shake, + }), + emote("ffzHat", { provider: "ffz", ffzModifierFlags: 0 }), +]); +const lookup = (name: string) => set.get(name); + +/** Compact rendering of parts for readable assertions. */ +function show(parts: MessagePart[]): string { + return parts + .map((part) => { + if (part.type === "text") return part.text; + if (part.type === "mention") return `<${part.text}>`; + const overlays = part.overlays.map((o) => `+${o.name}`).join(""); + return `[${part.emote.provider}:${part.emote.name}${overlays}]`; + }) + .join(""); +} + +describe("tokenize", () => { + it("cuts Twitch emotes by code point, not UTF-16 unit", () => { + const text = "👋 hi Kappa there"; + const parts = tokenize(text, parseEmotesTag("25:5-9"), lookup); + assert.equal(show(parts), "👋 hi [twitch:Kappa] there"); + const kappa = parts[1]; + assert.equal( + kappa.type === "emote" && kappa.emote.images[1], + "https://static-cdn.jtvnw.net/emoticons/v2/25/default/dark/1.0", + ); + }); + + it("ignores ranges that overlap or run past the message", () => { + assert.equal( + show(tokenize("Kappa", parseEmotesTag("25:0-4,2-6/1:0-40"), lookup)), + "[twitch:Kappa]", + ); + }); + + it("matches third-party emotes per word and keeps punctuation as text", () => { + assert.equal(show(tokenize("Clap Clap, clap", [], lookup)), "[7tv:Clap] Clap, clap"); + }); + + it("stacks zero-width emotes onto the previous emote", () => { + assert.equal( + show(tokenize("Kappa RainTime SteerR hi", parseEmotesTag("25:0-4"), lookup)), + "[twitch:Kappa+RainTime+SteerR] hi", + ); + }); + + it("renders a zero-width emote on its own when nothing precedes it", () => { + assert.equal(show(tokenize("RainTime hi", [], lookup)), "[7tv:RainTime] hi"); + assert.equal(show(tokenize("hi RainTime", [], lookup)), "hi [7tv:RainTime]"); + }); + + it("applies BTTV prefix modifiers to the next emote", () => { + const parts = tokenize("look w! h! Clap", [], lookup); + assert.equal(show(parts), "look [7tv:Clap]"); + const clap = parts[1]; + assert.ok(clap.type === "emote"); + assert.equal(clap.effects.aspectRatio, 4); + assert.deepEqual(clap.effects.transforms, ["scaleX(-1)"]); + }); + + it("keeps BTTV prefix modifiers as text when no emote follows", () => { + assert.equal(show(tokenize("w! hello h!", [], lookup)), "w! hello h!"); + }); + + it("applies FFZ modifier flags to the previous emote", () => { + const parts = tokenize("Clap ffzW ffzHyper done", [], lookup); + assert.equal(show(parts), "[7tv:Clap] done"); + const clap = parts[0]; + assert.ok(clap.type === "emote"); + assert.equal(clap.effects.widthScale, 2); + assert.equal(clap.effects.filters.length, 1); + assert.deepEqual(clap.effects.animations, ["cb-fx-shake 0.1s linear infinite"]); + }); + + it("overlays FFZ modifiers that are not hidden", () => { + assert.equal(show(tokenize("Clap ffzHat", [], lookup)), "[7tv:Clap+ffzHat]"); + }); + + it("renders a modifier with nothing to modify as a plain emote", () => { + const parts = tokenize("ffzW", [], lookup); + assert.equal(show(parts), "[ffz:ffzW]"); + assert.equal(parts[0].type === "emote" && parts[0].effects, NO_EFFECTS); + }); + + it("does not carry modifiers across text", () => { + assert.equal(show(tokenize("Clap and ffzW", [], lookup)), "[7tv:Clap] and [ffz:ffzW]"); + }); + + it("marks mentions and collapses repeated spaces", () => { + assert.equal(show(tokenize("@forsen hi @ x", [], lookup)), "<@forsen> hi @ x"); + }); +}); + +describe("EmoteSet", () => { + it("supports live add, rename and remove", () => { + const live = new EmoteSet([emote("A")]); + live.add(emote("B")); + live.rename("id-A", "A2"); + assert.equal(live.get("A"), undefined); + assert.equal(live.get("A2")?.id, "id-A"); + live.remove("id-B"); + assert.equal(live.get("B"), undefined); + assert.equal(live.size, 1); + }); +}); diff --git a/test/helpers.ts b/test/helpers.ts new file mode 100644 index 0000000..ba78abd --- /dev/null +++ b/test/helpers.ts @@ -0,0 +1,89 @@ +/** In-memory stand-in for the browser WebSocket, driven by the test. */ +export class FakeWebSocket { + static readonly OPEN = 1; + static instances: FakeWebSocket[] = []; + + readonly url: string; + readyState = 0; + /** Raw frames the client sent. */ + sent: string[] = []; + onopen: (() => void) | null = null; + onmessage: ((event: { data: string }) => void) | null = null; + onclose: (() => void) | null = null; + + constructor(url: string) { + this.url = url; + FakeWebSocket.instances.push(this); + } + + /** Frames the client sent, parsed as JSON. */ + get sentJson(): unknown[] { + return this.sent.map((frame) => JSON.parse(frame)); + } + + send(data: string): void { + this.sent.push(data); + } + + close(): void { + this.readyState = 3; + } + + /** Test side: the server accepts the connection. */ + accept(): void { + this.readyState = FakeWebSocket.OPEN; + this.onopen?.(); + } + + /** Test side: the server sends a frame; non-strings are sent as JSON. */ + receive(frame: unknown): void { + this.onmessage?.({ data: typeof frame === "string" ? frame : JSON.stringify(frame) }); + } + + /** Test side: the server drops the connection. */ + drop(): void { + this.readyState = 3; + this.onclose?.(); + } + + static get latest(): FakeWebSocket { + const socket = FakeWebSocket.instances.at(-1); + if (!socket) throw new Error("no socket was opened"); + return socket; + } +} + +/** Swaps in {@link FakeWebSocket} for one test; returns the restore function. */ +export function useFakeWebSocket(): () => void { + const original = globalThis.WebSocket; + FakeWebSocket.instances = []; + globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket; + return () => { + globalThis.WebSocket = original; + }; +} + +/** Answers fetch() from a URL-to-body table; a number body is sent as that status. */ +export function stubFetch(routes: Record): () => void { + const original = globalThis.fetch; + globalThis.fetch = (async (input: string | URL | Request) => { + const url = String(input instanceof Request ? input.url : input); + if (!(url in routes)) throw new Error(`unexpected fetch: ${url}`); + const body = routes[url]; + return typeof body === "number" + ? new Response("{}", { status: body }) + : new Response(JSON.stringify(body), { status: 200 }); + }) as typeof fetch; + return () => { + globalThis.fetch = original; + }; +} + +/** A mock handler that records the arguments of every call. */ +export function recorder() { + const calls: Args[] = []; + const fn = (...args: Args) => { + calls.push(args); + }; + return Object.assign(fn, { calls }); +} diff --git a/test/register.ts b/test/register.ts new file mode 100644 index 0000000..02cf448 --- /dev/null +++ b/test/register.ts @@ -0,0 +1,12 @@ +// `node --test` runs the TypeScript sources directly (Node strips the types), but the sources use +// bundler-style extensionless imports, which Node's ESM resolver rejects. This maps them to `.ts`. +import { registerHooks } from "node:module"; + +registerHooks({ + resolve(specifier, context, nextResolve) { + if (/^\.\.?\//.test(specifier) && !/\.[cm]?[jt]sx?$/.test(specifier)) { + return nextResolve(`${specifier}.ts`, context); + } + return nextResolve(specifier, context); + }, +}); diff --git a/tsconfig.json b/tsconfig.json index 566d53c..14058cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,22 @@ { "compilerOptions": { - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, "target": "ESNext", "module": "ESNext", - "moduleResolution": "node", - "jsxImportSource": "solid-js", + "moduleResolution": "bundler", "jsx": "preserve", + "jsxImportSource": "solid-js", "strict": true, - "types": ["vite/client"], - "baseUrl": "./", + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["@solidjs/start/env"], "paths": { "~/*": ["./src/*"] } - } + }, + "include": ["src", "vite.config.ts"] } diff --git a/vite.config.ts b/vite.config.ts index 7f5d37c..1a1c3c6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,12 +1,46 @@ -import cloudflare from "solid-start-cloudflare-pages"; -import solid from "solid-start/vite"; -import suidPlugin from "@suid/vite-plugin"; +import { solidStart } from "@solidjs/start/config"; +import { nitro } from "nitro/vite"; import { defineConfig } from "vite"; +// SUID ships uncompiled Solid JSX, so it must go through the Solid compiler +// instead of being pre-bundled (dev) or externalized (SSR). +const suidPackages = [ + "@suid/base", + "@suid/css", + "@suid/material", + "@suid/styled-engine", + "@suid/system", + "@suid/types", + "@suid/utils", +]; + export default defineConfig({ - plugins: [ - suidPlugin(), - // TODO: Turn on SSR when SUID adds support for it (upstream moment) - solid({ ssr: false, adapter: cloudflare({})}), - ], + plugins: [solidStart(), nitro()], + build: { + // Never inline fonts as base64: it bloats the render-blocking CSS with + // subsets that `unicode-range` would otherwise only fetch on demand. + assetsInlineLimit: (file) => (/\.woff2?$/.test(file) ? false : undefined), + }, + optimizeDeps: { + exclude: suidPackages, + // @solidjs/start is excluded from pre-bundling (it ships JSX), so the UMD dependency + // of its dev overlay's error viewer must be pre-bundled explicitly to load in dev. + include: ["@solidjs/start > @jridgewell/trace-mapping"], + }, + ssr: { + noExternal: suidPackages, + }, + nitro: { + preset: "cloudflare-pages", + // Pinned so builds are reproducible; bump deliberately to opt into new runtime behavior. + compatibilityDate: "2026-09-29", + // Develop on plain Node. The preset's default Cloudflare emulation would + // auto-install Miniflare; the app only ever runs on Cloudflare remotely. + devServer: { + runner: "node-worker", + }, + prerender: { + routes: ["/v3"], + }, + }, });