From 34ce6d702de541cff934f13aec32e10a0771cc14 Mon Sep 17 00:00:00 2001 From: fabianzimber Date: Tue, 29 Sep 2026 22:13:02 +0200 Subject: [PATCH 1/4] chore: ignore secrets, caches and local tooling Adds Cloudflare's .dev.vars, key files, logs, coverage, build caches and Claude Code's local .claude/ session config to .gitignore. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 52 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 19 deletions(-) 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 From 2dccad936549432507077074e7be7fcb91fa4bed Mon Sep 17 00:00:00 2001 From: fabianzimber Date: Tue, 29 Sep 2026 22:13:02 +0200 Subject: [PATCH 2/4] chore: move to SolidStart 2, rebrand as ChatBloom, relicense under AGPL - Stack: SolidStart 2 (Vite 8 + Nitro), SUID 0.19, TypeScript 7, Biome for linting and formatting, pnpm 12 with a lockfile and a patch for @suid/styled-engine, Node 24. Tailwind/PostCSS and unused components are gone; root.tsx/root.css became app.tsx/app.css. - Hosting: Nitro's cloudflare-pages preset, with /v3 prerendered at build time. - Branding: shiftbloom theme, header, hero, footer and brand components for the /v3 setup page, plus favicon, touch icon and OG image. - License: AGPL-3.0-or-later. - Renovate extends config:recommended. Co-Authored-By: Claude Opus 5.5 --- .node-version | 1 + LICENSE | 661 +++ README.md | 43 +- biome.json | 36 + package.json | 47 +- patches/@suid__styled-engine@0.9.0.patch | 25 + pnpm-lock.yaml | 5234 +++++++---------- pnpm-workspace.yaml | 2 + postcss.config.cjs | 6 - public/apple-touch-icon.png | Bin 0 -> 15313 bytes public/bloom.svg | 1 + public/favicon.ico | Bin 67646 -> 3031 bytes public/fonts/jetbrains-mono/OFL.txt | 93 + .../jetbrains-mono-latin-wght-normal.woff2 | Bin 0 -> 40404 bytes public/img/Peepo.png | Bin 4117 -> 0 bytes public/og.png | Bin 0 -> 81391 bytes renovate.json | 4 +- src/app.css | 5 + src/app.tsx | 29 + src/brand.css | 81 + src/components/Counter.tsx | 13 - src/components/FeatureCard.tsx | 47 +- src/components/FeaturesGrid.tsx | 257 +- src/components/MySiteTitle.tsx | 8 +- src/components/SetupHero.module.css | 39 + src/components/SetupHero.tsx | 28 + src/components/SevenTVNamepaint.tsx | 42 +- src/components/SiteFooter.module.css | 48 + src/components/SiteFooter.tsx | 45 + src/components/SiteHeader.module.css | 35 + src/components/SiteHeader.tsx | 19 + src/components/brand/Bloom.module.css | 51 + src/components/brand/Bloom.tsx | 105 + src/components/brand/Sprinkles.module.css | 67 + src/components/brand/Sprinkles.tsx | 51 + src/components/brand/Wordmark.module.css | 16 + src/components/brand/Wordmark.tsx | 13 + src/components/icon/BTTVBadIcon.tsx | 15 - src/components/icon/BTTVIcon.tsx | 13 +- src/components/icon/ChatterinoIcon.tsx | 27 +- src/components/icon/ChatterinoIconColored.tsx | 26 - src/components/icon/FFZIcon.tsx | 15 +- src/components/icon/SevenTVIcon.tsx | 22 +- src/components/icon/TwitchIcon.tsx | 37 +- src/components/icon/TwitchIconPurple.tsx | 34 - src/entry-client.tsx | 6 +- src/entry-server.tsx | 50 +- src/root.css | 8 - src/root.tsx | 55 - src/routes/[...404].tsx | 52 +- src/routes/v3/chat/[channel].tsx | 15 +- src/routes/v3/index.tsx | 98 +- src/theme.ts | 72 + tailwind.config.cjs | 8 - tsconfig.json | 19 +- vite.config.ts | 50 +- 56 files changed, 3935 insertions(+), 3839 deletions(-) create mode 100644 .node-version create mode 100644 LICENSE create mode 100644 biome.json create mode 100644 patches/@suid__styled-engine@0.9.0.patch create mode 100644 pnpm-workspace.yaml delete mode 100644 postcss.config.cjs create mode 100644 public/apple-touch-icon.png create mode 100644 public/bloom.svg create mode 100644 public/fonts/jetbrains-mono/OFL.txt create mode 100644 public/fonts/jetbrains-mono/jetbrains-mono-latin-wght-normal.woff2 delete mode 100644 public/img/Peepo.png create mode 100644 public/og.png create mode 100644 src/app.css create mode 100644 src/app.tsx create mode 100644 src/brand.css delete mode 100644 src/components/Counter.tsx create mode 100644 src/components/SetupHero.module.css create mode 100644 src/components/SetupHero.tsx create mode 100644 src/components/SiteFooter.module.css create mode 100644 src/components/SiteFooter.tsx create mode 100644 src/components/SiteHeader.module.css create mode 100644 src/components/SiteHeader.tsx create mode 100644 src/components/brand/Bloom.module.css create mode 100644 src/components/brand/Bloom.tsx create mode 100644 src/components/brand/Sprinkles.module.css create mode 100644 src/components/brand/Sprinkles.tsx create mode 100644 src/components/brand/Wordmark.module.css create mode 100644 src/components/brand/Wordmark.tsx delete mode 100644 src/components/icon/BTTVBadIcon.tsx delete mode 100644 src/components/icon/ChatterinoIconColored.tsx delete mode 100644 src/components/icon/TwitchIconPurple.tsx delete mode 100644 src/root.css delete mode 100644 src/root.tsx create mode 100644 src/theme.ts delete mode 100644 tailwind.config.cjs 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..d409309 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,35 @@ -# 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`) -# create a new project in my-app -npm init solid@latest my-app -``` +## Development -## Developing +```bash +pnpm install +pnpm dev +``` -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: +The dev server runs on plain Node; no local Cloudflare tooling is needed. -```bash -npm run dev +| 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 check` | `typecheck` + `lint` (run before pushing) | -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` +## Deployment -## Building +The app is built and deployed remotely by Cloudflare Pages: -Solid apps are built with _adapters_, which optimise your project for deployment to different environments. +- Build command: `pnpm build` +- Build output directory: `dist` -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`. +Pages are server-rendered on Cloudflare; `/v3` is prerendered to static HTML at build time. 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..fb82db5 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,31 @@ { "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", + "check": "pnpm typecheck && pnpm lint" }, "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 0000000000000000000000000000000000000000..1974242d78a2bcfdc7ce4ecf9e94104158f304fa GIT binary patch literal 15313 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 fy!l^s?!}LzI+mI<2gvl0=1=alrop8f|U@ literal 0 HcmV?d00001 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 1d3b5fc1c4ff2bdd1043666670063ed350184255..66a4253b6fdea78a53654c3313100ac7210ae3fb 100644 GIT binary patch literal 3031 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 A8Ob4S33%u?pm-n{qoyX?y0#0(R^L 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 diff --git a/public/og.png b/public/og.png new file mode 100644 index 0000000000000000000000000000000000000000..e845521278bc705427988114dc83facfdb8242e9 GIT binary patch literal 81391 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 D;gQ$e literal 0 HcmV?d00001 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/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/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..a0f6d44 100644 --- a/src/routes/v3/chat/[channel].tsx +++ b/src/routes/v3/chat/[channel].tsx @@ -1,19 +1,14 @@ -import { useParams } from "solid-start"; +import { useParams } from "@solidjs/router"; + import MySiteTitle from "~/components/MySiteTitle"; export default function Chat() { - let params = useParams<{ channel: string }>(); - let channel = params.channel; + const params = useParams<{ channel: string }>(); return ( <> - #{channel} -
- {/*

- Hello world! -

*/} - -
+ {`#${params.channel}`} +
); } 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/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"], + }, + }, }); From c3be00cafbb6845362ca0a722f6955d174aac0a4 Mon Sep 17 00:00:00 2001 From: fabianzimber Date: Tue, 29 Sep 2026 22:13:02 +0200 Subject: [PATCH 3/4] feat(chat): Twitch chat overlay with 7TV, BTTV and FFZ /v3/chat/ renders a transparent OBS overlay that connects from the browser to anonymous Twitch chat and to every emote and cosmetics provider, keeping all of it live: - 7TV: global, channel and personal emotes, zero-width emotes, badges and name paints (layered v4 model via the v4 GraphQL API; v3 EventAPI for live emote sets, cosmetics and entitlements) - BTTV: emotes with live edits, prefix modifiers (w! h! v! z! c! l! r! p! s!), Pro personal emotes, badges and username effects - FFZ: global, room and per-user sets, modifier flags, custom moderator/VIP and bot badges, live edits over FFZ's pubsub - Twitch badges via IVR, FFZ:AP and Chatterino badges, Shared Chat source rooms Co-Authored-By: Claude Opus 5.5 --- README.md | 7 + src/components/chat/Chat.module.css | 148 +++++++ src/components/chat/ChatLine.tsx | 63 +++ src/components/chat/ChatOverlay.tsx | 25 ++ src/components/chat/EmoteView.tsx | 103 +++++ src/components/chat/Username.tsx | 135 ++++++ src/components/chat/overlay.css | 164 +++++++ src/lib/chat/effects.ts | 133 ++++++ src/lib/chat/emote-set.ts | 49 +++ src/lib/chat/irc/client.ts | 62 +++ src/lib/chat/irc/parse.ts | 97 +++++ src/lib/chat/providers/bttv.ts | 193 +++++++++ src/lib/chat/providers/chatterino.ts | 26 ++ src/lib/chat/providers/ffz.ts | 241 +++++++++++ src/lib/chat/providers/seventv/api.ts | 112 +++++ src/lib/chat/providers/seventv/events.ts | 195 +++++++++ src/lib/chat/providers/seventv/paint.ts | 170 ++++++++ src/lib/chat/providers/twitch.ts | 107 +++++ src/lib/chat/session.ts | 528 +++++++++++++++++++++++ src/lib/chat/socket.ts | 100 +++++ src/lib/chat/tokenize.ts | 142 ++++++ src/lib/chat/types.ts | 48 +++ src/routes/v3/chat/[channel].tsx | 19 +- 23 files changed, 2866 insertions(+), 1 deletion(-) create mode 100644 src/components/chat/Chat.module.css create mode 100644 src/components/chat/ChatLine.tsx create mode 100644 src/components/chat/ChatOverlay.tsx create mode 100644 src/components/chat/EmoteView.tsx create mode 100644 src/components/chat/Username.tsx create mode 100644 src/components/chat/overlay.css create mode 100644 src/lib/chat/effects.ts create mode 100644 src/lib/chat/emote-set.ts create mode 100644 src/lib/chat/irc/client.ts create mode 100644 src/lib/chat/irc/parse.ts create mode 100644 src/lib/chat/providers/bttv.ts create mode 100644 src/lib/chat/providers/chatterino.ts create mode 100644 src/lib/chat/providers/ffz.ts create mode 100644 src/lib/chat/providers/seventv/api.ts create mode 100644 src/lib/chat/providers/seventv/events.ts create mode 100644 src/lib/chat/providers/seventv/paint.ts create mode 100644 src/lib/chat/providers/twitch.ts create mode 100644 src/lib/chat/session.ts create mode 100644 src/lib/chat/socket.ts create mode 100644 src/lib/chat/tokenize.ts create mode 100644 src/lib/chat/types.ts diff --git a/README.md b/README.md index d409309..60452c2 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,10 @@ The app is built and deployed remotely by Cloudflare Pages: - Build output directory: `dist` Pages are server-rendered on Cloudflare; `/v3` is prerendered to static HTML at build time. + +## Chat overlay + +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. 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/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/routes/v3/chat/[channel].tsx b/src/routes/v3/chat/[channel].tsx index a0f6d44..b5d2816 100644 --- a/src/routes/v3/chat/[channel].tsx +++ b/src/routes/v3/chat/[channel].tsx @@ -1,14 +1,31 @@ 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() { 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 ( <> {`#${params.channel}`} -
+ {(chat) => } ); } From e611359272463788af0c9f0aee80ab874a182c4c Mon Sep 17 00:00:00 2001 From: fabianzimber Date: Tue, 29 Sep 2026 22:13:02 +0200 Subject: [PATCH 4/4] test: add unit tests and CI on the self-hosted runners - 46 node:test tests for the IRC parser, tokenizer, emote effects, 7TV paints, provider API mapping (stubbed fetch) and every live-update socket (fake WebSocket). `pnpm test` runs them with Node's native TypeScript support, and `pnpm check` now includes it. - .github/workflows/ci.yml runs install, typecheck, lint, test and build on the Default self-hosted runner group. Pull requests from forks are skipped because the repository is public. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 52 +++++++ README.md | 9 +- package.json | 3 +- test/chat/irc.test.ts | 76 +++++++++ test/chat/providers.test.ts | 290 +++++++++++++++++++++++++++++++++++ test/chat/seventv.test.ts | 175 +++++++++++++++++++++ test/chat/sockets.test.ts | 296 ++++++++++++++++++++++++++++++++++++ test/chat/tokenize.test.ts | 133 ++++++++++++++++ test/helpers.ts | 89 +++++++++++ test/register.ts | 12 ++ 10 files changed, 1133 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 test/chat/irc.test.ts create mode 100644 test/chat/providers.test.ts create mode 100644 test/chat/seventv.test.ts create mode 100644 test/chat/sockets.test.ts create mode 100644 test/chat/tokenize.test.ts create mode 100644 test/helpers.ts create mode 100644 test/register.ts 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/README.md b/README.md index 60452c2..97c32d7 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ The dev server runs on plain Node; no local Cloudflare tooling is needed. | `pnpm typecheck` | Type-check with TypeScript | | `pnpm lint` | Lint and check formatting with Biome | | `pnpm format` | Apply Biome formatting and safe fixes | -| `pnpm check` | `typecheck` + `lint` (run before pushing) | +| `pnpm test` | Run unit tests (`test/`) with Node's test runner | +| `pnpm check` | `typecheck` + `lint` + `test` (run before pushing) | ## Deployment @@ -40,3 +41,9 @@ Add `https:///v3/chat/` as an OBS browser source. The 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. + +## CI + +`.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/package.json b/package.json index fb82db5..02e7be3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "typecheck": "tsc", "lint": "biome check", "format": "biome check --write", - "check": "pnpm typecheck && pnpm lint" + "test": "node --import ./test/register.ts --test \"test/**/*.test.ts\"", + "check": "pnpm typecheck && pnpm lint && pnpm test" }, "dependencies": { "@solidjs/meta": "^0.29.4", 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); + }, +});