From 3ee804a543ead8e7a0ec015bab1d84029136d9ec Mon Sep 17 00:00:00 2001 From: Terncode Date: Wed, 22 Apr 2026 11:03:21 +0200 Subject: [PATCH] Inital commit --- LICENCE.md | 662 +++ README.md | 49 + eslint.config.js | 75 + index.html | 14 + package-lock.json | 5589 ++++++++++++++++++ package.json | 55 + public/icons/android-chrome-192x192.png | Bin 0 -> 7226 bytes public/icons/android-chrome-512x512.png | Bin 0 -> 18074 bytes public/icons/apple-touch-icon.png | Bin 0 -> 6940 bytes public/icons/favicon-16x16.png | Bin 0 -> 657 bytes public/icons/favicon-32x32.png | Bin 0 -> 1478 bytes public/icons/favicon.ico | Bin 0 -> 15406 bytes public/webmanifest.json | 1 + src/App.tsx | 68 + src/assets/fonts/Doto_Rounded-Regular.ttf | Bin 0 -> 173976 bytes src/assets/fonts/Tiny5-Regular.ttf | Bin 0 -> 131452 bytes src/calc.ts | 155 + src/components/a4-canvas.tsx | 235 + src/components/buttons/button-ribbon.tsx | 41 + src/components/download-buttons.tsx | 104 + src/components/drag-drop.tsx | 62 + src/components/image-prop-edit.tsx | 46 + src/components/image-scaler.tsx | 179 + src/components/image-tools/canvas-view.tsx | 38 + src/components/image-tools/paper-view.tsx | 106 + src/components/image-tools/selection-box.tsx | 269 + src/components/image-tools/svg-view.tsx | 38 + src/components/info-bar.tsx | 110 + src/components/layer-item.tsx | 84 + src/components/layer-name.tsx | 57 + src/components/layer-panel.tsx | 152 + src/components/number-input.tsx | 143 + src/components/numeric-labeld-input.tsx | 42 + src/components/paper-guide-selector.tsx | 32 + src/components/paper-orentation.tsx | 13 + src/components/paper-size-selector.tsx | 89 + src/components/popup.tsx | 213 + src/components/svg-tracer-options.tsx | 43 + src/components/toolbar.tsx | 29 + src/constants.ts | 36 + src/draw-canvas.ts | 43 + src/handler/handler-global-settings.ts | 54 + src/handler/handler-tools.ts | 40 + src/handler/image-editor.ts | 96 + src/handler/interfaces.ts | 21 + src/handler/popup.tsx | 149 + src/handler/tools.tsx | 26 + src/handler/vectrace-handler.ts | 511 ++ src/index.css | 30 + src/interface.tsx | 77 + src/main.tsx | 7 + src/nav-bar.tsx | 116 + src/render-page.tsx | 41 + src/styles.ts | 50 + src/svg-tracer.ts | 65 + src/types/imageTracker.d.ts | 154 + src/use/use-images.tsx | 68 + src/use/use-settings.tsx | 23 + src/use/use-tool.tsx | 21 + src/utils/collection.ts | 17 + src/utils/download.ts | 287 + src/utils/eventEmitter.ts | 72 + src/utils/generic.ts | 24 + src/utils/image.ts | 33 + tsconfig.app.json | 25 + tsconfig.json | 7 + tsconfig.node.json | 24 + vite.config.ts | 14 + 68 files changed, 10924 insertions(+) create mode 100644 LICENCE.md create mode 100644 README.md create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/icons/android-chrome-192x192.png create mode 100644 public/icons/android-chrome-512x512.png create mode 100644 public/icons/apple-touch-icon.png create mode 100644 public/icons/favicon-16x16.png create mode 100644 public/icons/favicon-32x32.png create mode 100644 public/icons/favicon.ico create mode 100644 public/webmanifest.json create mode 100644 src/App.tsx create mode 100644 src/assets/fonts/Doto_Rounded-Regular.ttf create mode 100644 src/assets/fonts/Tiny5-Regular.ttf create mode 100644 src/calc.ts create mode 100644 src/components/a4-canvas.tsx create mode 100644 src/components/buttons/button-ribbon.tsx create mode 100644 src/components/download-buttons.tsx create mode 100644 src/components/drag-drop.tsx create mode 100644 src/components/image-prop-edit.tsx create mode 100644 src/components/image-scaler.tsx create mode 100644 src/components/image-tools/canvas-view.tsx create mode 100644 src/components/image-tools/paper-view.tsx create mode 100644 src/components/image-tools/selection-box.tsx create mode 100644 src/components/image-tools/svg-view.tsx create mode 100644 src/components/info-bar.tsx create mode 100644 src/components/layer-item.tsx create mode 100644 src/components/layer-name.tsx create mode 100644 src/components/layer-panel.tsx create mode 100644 src/components/number-input.tsx create mode 100644 src/components/numeric-labeld-input.tsx create mode 100644 src/components/paper-guide-selector.tsx create mode 100644 src/components/paper-orentation.tsx create mode 100644 src/components/paper-size-selector.tsx create mode 100644 src/components/popup.tsx create mode 100644 src/components/svg-tracer-options.tsx create mode 100644 src/components/toolbar.tsx create mode 100644 src/constants.ts create mode 100644 src/draw-canvas.ts create mode 100644 src/handler/handler-global-settings.ts create mode 100644 src/handler/handler-tools.ts create mode 100644 src/handler/image-editor.ts create mode 100644 src/handler/interfaces.ts create mode 100644 src/handler/popup.tsx create mode 100644 src/handler/tools.tsx create mode 100644 src/handler/vectrace-handler.ts create mode 100644 src/index.css create mode 100644 src/interface.tsx create mode 100644 src/main.tsx create mode 100644 src/nav-bar.tsx create mode 100644 src/render-page.tsx create mode 100644 src/styles.ts create mode 100644 src/svg-tracer.ts create mode 100644 src/types/imageTracker.d.ts create mode 100644 src/use/use-images.tsx create mode 100644 src/use/use-settings.tsx create mode 100644 src/use/use-tool.tsx create mode 100644 src/utils/collection.ts create mode 100644 src/utils/download.ts create mode 100644 src/utils/eventEmitter.ts create mode 100644 src/utils/generic.ts create mode 100644 src/utils/image.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000..0a65910 --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,662 @@ + 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) 2026 Terncode + + 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 +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..99eb931 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Vectrace + +Vectrace is a lightweight tool for converting raster images into clean vector paths. It focuses on extracting outlines and shapes that can be directly used for laser cutting, CNC workflows, and vector-based design. + +## Features + +* Image outlining and shape detection +* Raster to vector conversion +* Export to SVG +* Export to PDF +* Batch export as ZIP +* Clean path generation optimized for cutting workflows + +## Use Cases + +* Laser cutting preparation +* CNC path generation +* Converting sketches to vector formats +* Simplifying images into scalable outlines + +## Supported Formats + +**Input** + +* PNG +* JPG +* BMP + +**Output** + +* SVG +* PDF +* ZIP (batch export) + +Vectrace is built to be fast, predictable, and practical. It avoids unnecessary complexity and focuses on producing usable vector paths with minimal cleanup. + +## Roadmap + +* GUI interface +* Advanced path smoothing +* Direct laser cutter integration + +## License + +GPL License + +## Author + +Terncode \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..6b800ae --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,75 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; +import pluginReact from "eslint-plugin-react"; +import stylistic from "@stylistic/eslint-plugin"; +import styledComponentsA11y from "eslint-plugin-styled-components-a11y"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node + } + } + }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + pluginReact.configs.flat.recommended, + { + settings: { + react: { + version: "detect" + } + } + }, + { + plugins: { + "@stylistic": stylistic, + "styled-components-a11y": styledComponentsA11y + }, + rules: { + "react/react-in-jsx-scope": "off", + "no-trailing-spaces": "warn", + "semi": ["warn", "always"], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-misused-new": "off", + "@stylistic/linebreak-style": ["error", "unix"], + "camelcase": "warn", + "max-len": ["warn", { "code": 150 }], + "radix": [ + "error", + ], + "quotes": [ + "warn", + "double", + { allowTemplateLiterals: true, avoidEscape: true }, + ], + "indent": ["warn", 4, + { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "MemberExpression": 1, + "FunctionDeclaration": { "parameters": "first", "body": 1 }, + "FunctionExpression": { "parameters": "first", "body": 1 } + } + ], + "@typescript-eslint/no-unused-vars": [ + "error", + { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + } + ] + } + } +]; diff --git a/index.html b/index.html new file mode 100644 index 0000000..8352689 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + + Vectrace + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4507895 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5589 @@ +{ + "name": "vectrace", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vectrace", + "version": "1.0.0", + "dependencies": { + "@stylistic/eslint-plugin": "^5.10.0", + "@types/lodash": "^4.17.24", + "eslint-plugin-react": "^7.37.5", + "fast-xml-builder": "^1.1.5", + "fast-xml-parser": "^5.7.1", + "imagetracerjs": "^1.2.6", + "jspdf": "^4.2.1", + "jszip": "^3.10.1", + "ldrs": "^1.1.9", + "localforage": "^1.10.0", + "lodash": "^4.18.1", + "pdf-lib": "^1.17.1", + "polygon-clipping": "^0.15.7", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-icons": "^5.6.0", + "styled-components": "^6.4.0", + "svg.js": "^2.7.1", + "svgson": "^5.3.1", + "uuid": "^14.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/node": "^24.12.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "eslint-plugin-styled-components-a11y": "^2.2.1", + "globals": "^17.4.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.58.0", + "vite": "^8.0.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.10" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.56.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.3.tgz", + "integrity": "sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", + "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" + }, + "node_modules/deep-rename-keys": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz", + "integrity": "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2", + "rename-keys": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dompurify": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", + "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.338", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.338.tgz", + "integrity": "sha512-KVQQ3xko9/coDX3qXLUEEbqkKT8L+1DyAovrtu0Khtrt9wjSZ+7CZV4GVzxFy9Oe1NbrIU1oVXCwHJruIA1PNg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-plugin-styled-components-a11y": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-styled-components-a11y/-/eslint-plugin-styled-components-a11y-2.2.1.tgz", + "integrity": "sha512-uIWb/eFXBPWNFqPRGj//Qtnxv/KumDqo7WEDtI+QJCq2bLl8sRl5gUf6ua0cyR/Xjc233qXC870STeLiIUVy9A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@babel/parser": "^7.8.4", + "eslint-plugin-jsx-a11y": "^6.2.3" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" + }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fast-png/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", + "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.1.tgz", + "integrity": "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imagetracerjs": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/imagetracerjs/-/imagetracerjs-1.2.6.tgz", + "integrity": "sha512-LKJlnKmXFzDdh6IZtXTyBxXcCLTAkwgKYS+NMiPXiXVnlTLjQC8fq7U89laUSgHtypJB3TdMMDK4ecG5NI/Cgw==", + "license": "Unlicense" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jspdf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz", + "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.3.1", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/ldrs": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ldrs/-/ldrs-1.1.9.tgz", + "integrity": "sha512-JKVdzboxeZrKecA8ovi5iIDgs59wUrtJ+LDrHCgZecNrR0AJi2cJ3ZANJ+UCNS/sHRol73/jlypNCAMCXS+Ung==", + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "license": "MIT", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/pdf-lib/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/polygon-clipping": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.7.tgz", + "integrity": "sha512-nhfdr83ECBg6xtqOAJab1tbksbBAOMUltN60bU+llHVOL0e5Onm1WpAXXWXVB39L8AJFssoIhEVuy/S90MmotA==", + "license": "MIT", + "dependencies": { + "robust-predicates": "^3.0.2", + "splaytree": "^3.1.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rename-keys": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rename-keys/-/rename-keys-1.2.0.tgz", + "integrity": "sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/splaytree": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.2.3.tgz", + "integrity": "sha512-7OXrNWzy6CK+r7Ch9OLPBDTKfB6XlWHjX4P0RU5B3IgFuWPeYN0XtRtlexGRjgbQxpfaUve6jTAwBGWuGntz/w==", + "license": "MIT", + "engines": { + "node": ">=18.20 || >=20" + } + }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/styled-components": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.0.tgz", + "integrity": "sha512-BL1EDFpt+q10eAeZB0q9ps6pSlPejaBQWBkiuM16pyoVTG4NhZrPrZK0cqNbrozxSsYwUsJ9SQYN6NyeKJYX9A==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "stylis": "4.3.6" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "css-to-react-native": ">= 3.2.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" + }, + "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==", + "license": "MIT" + }, + "node_modules/svgson": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/svgson/-/svgson-5.3.1.tgz", + "integrity": "sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA==", + "license": "MIT", + "dependencies": { + "deep-rename-keys": "^0.2.1", + "xml-reader": "2.4.3" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "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 + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xml-lexer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz", + "integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^2.0.0" + } + }, + "node_modules/xml-reader": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz", + "integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^2.0.0", + "xml-lexer": "^0.2.2" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b4f6399 --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "vectrace", + "private": true, + "version": "1.0.0", + "author": { + "name": "Terncode", + "email": "terncode@gmail.com", + "url": "https://terncode.dev" + }, + "type": "module", + "scripts": { + "dev": "vite --host", + "build": "tsc -b && vite build", + "lint": "eslint .", + "lint-fix": "eslint --fix \"src/**/*.{js,mjs,cjs,ts,jsx,tsx}\"", + "preview": "vite preview" + }, + "dependencies": { + "@stylistic/eslint-plugin": "^5.10.0", + "@types/lodash": "^4.17.24", + "eslint-plugin-react": "^7.37.5", + "fast-xml-builder": "^1.1.5", + "fast-xml-parser": "^5.7.1", + "imagetracerjs": "^1.2.6", + "jspdf": "^4.2.1", + "jszip": "^3.10.1", + "ldrs": "^1.1.9", + "localforage": "^1.10.0", + "lodash": "^4.18.1", + "pdf-lib": "^1.17.1", + "polygon-clipping": "^0.15.7", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-icons": "^5.6.0", + "styled-components": "^6.4.0", + "svg.js": "^2.7.1", + "svgson": "^5.3.1", + "uuid": "^14.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/node": "^24.12.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "eslint-plugin-styled-components-a11y": "^2.2.1", + "globals": "^17.4.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.58.0", + "vite": "^8.0.4" + } +} diff --git a/public/icons/android-chrome-192x192.png b/public/icons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..1330443382e5e9f171f7f2c99100ff36515598f1 GIT binary patch literal 7226 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

~megjw4ob)Pyf{1O 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 literal 0 HcmV?d00001 diff --git a/public/icons/android-chrome-512x512.png b/public/icons/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..133ae0d50f5083f090acb064651315d37cddfc66 GIT binary patch literal 18074 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 in*aa+21!IgR09A7+y_7W2<-9z0000vL1 zSf)MZrYR5za>eAOSoHdR67!U(6_;fxzbr2?X$P7oW5XDb7etb7Js~hQKF*t!74rE! z=48T!Bw_jWGKIoCLW?*!7kyF%a7lug8y6S6U0-K)byXmT1g55@*m$?W$ml5d#>OLg z{(korPMuUTc17@B~b8Bcw#71F$o=&^VUTKg0{SQ&O%{D6z=Y(VyU2CMip- z@F$SQNagtWn63TdD-Li>uI!Ods6AP zJG|fA1{)i+6I3 r@ecq1|Nr+H9~}Sy00v1!K~w_(kJ9cScE-~800000NkvXXu0mjf6sS1~ literal 0 HcmV?d00001 diff --git a/public/icons/favicon-32x32.png b/public/icons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..f6d01daf9a806f034a467b97c5dffca8499b72f2 GIT binary patch literal 1478 zcmV;%1v&bOP)yp@;>zfeaG=Dl=+$yz zLtMq>eU!l{FCd;U{A?JX*4J%KPfr(tMwaQ_Xl-r9;^Lz5*2#bH-~nHR{9sCyhK_YJ zPvK_VL%f81ze1irdyb>+?P%vZ`@>nBIddjYEPr~k?(S}!IB~*wZEbCM@!|!FlZ!(W zALWtUQF;fAkYRLm6blOr9!J9M+qcb=k`pq!x3?ETMf0)7#>On>!ubp6>+8e4zI!I6 zH(STQIS#1fj8(o;0Rek^dwBf#5p+T5$1k^Tp{0cs8X^Y({U22=4$1SY)@T+D3TSO@gYLy4wV%k)&=9t_x2b80Ey<-+xk-Z)`w^Gkq!TQF zgwo98i46{@%|ZfxymW~Oo65_Ve)|Tb7bThiJBU0TMy!QPu&?MeLyn9;L&PDQL8530 z9eMQ%tGvNrE6q*t?b|;wJ3C8_Gl5j0%O)9@|F4l7u9W$DEeNFo?9_lVXKQNBUjvoq1e2D zOiUptMi$i?zl`cax_mj!2)0FLAp-N6CqT7uLmUEJl|L^a|4YSvwv%e|<;#~CA0Nm2 z_kV$Tkq;95vYC>@ySsb%FkeUgL*2fp^y+7k`{R2ykryDZD$(^b literal 0 HcmV?d00001 diff --git a/public/icons/favicon.ico b/public/icons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2134d68e471298d50c242debb45242501f08f962 GIT binary patch literal 15406 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 jg)OQ-A$L+XL1+4yq_^e7Y49{UCIIpG new VectraceHandler(), []); + const [ready, setReady] = useState(handler.ready); + useEffect(() => { + handler.init("image_store"); + const unsub = handler.emitter.on("ready", () => setReady(true)); + const onSave = (e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + handler.exportProject(); + } + }; + + document.addEventListener("keydown", onSave, true); + return () => { + unsub(); + document.removeEventListener("keydown", onSave); + }; + }, []); + + if (!ready) { + return + + + Loading + + ; + } + return ( + <> + + + + + + ); +} + +export default App; diff --git a/src/assets/fonts/Doto_Rounded-Regular.ttf b/src/assets/fonts/Doto_Rounded-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..bf7884c051049bba9e6ea99d9f9288eae346d0d0 GIT binary patch literal 173976 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 PWf@>5KBn`KL6QFtb}?^{ literal 0 HcmV?d00001 diff --git a/src/assets/fonts/Tiny5-Regular.ttf b/src/assets/fonts/Tiny5-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..635fe079f77dc82fbe2628a62ef16da64412aafd GIT binary patch literal 131452 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 literal 0 HcmV?d00001 diff --git a/src/calc.ts b/src/calc.ts new file mode 100644 index 0000000..5cf5ccf --- /dev/null +++ b/src/calc.ts @@ -0,0 +1,155 @@ +import { last } from "lodash"; + +function isNumber(char: string) { + const number = parseInt(char, 10); + return !isNaN(number); +} + +const priorities = new Map(); +priorities.set("(", 4); +priorities.set(")", 4); +priorities.set("^", 3); +priorities.set("*", 2); +priorities.set("/", 2); +priorities.set("+", 1); +priorities.set("-", 1); + +function getPriority(char: string) { + return priorities.get(char) || -1; +} + +function filterOutNumbers(expression: string) { + const buffer: string[] = []; + const lastReadNumberQueue: string[] = []; + + const updateNumbers = () => { + const number = lastReadNumberQueue.join(""); + lastReadNumberQueue.length = 0; + if (number) { + if (!isNaN(parseFloat(number))) { + buffer.push(number); + } else { + throw new Error("malformed number"); + } + } + }; + + for (let i = 0; i < expression.length; i++) { + const char = expression[i]; + if (isNumber(char) || char === "." || char === "," || (i === 0 && char === "-") || + (priorities.has(last(buffer) || "") && char === "-")) { + lastReadNumberQueue.push(char); + } else { + updateNumbers(); + buffer.push(char); + } + } + updateNumbers(); + return buffer; +} + +function processExpression(expression: string) { + const preExpression = filterOutNumbers(expression); + const postExpression: string[] = []; + const stack: string[] = []; + while (preExpression.length) { + const read = preExpression.shift()!; + if (read !== ")") { + if (isNumber(read)) { + postExpression.push(read); + } + else if (read === "(") { + stack.push(read); + } else { + while (stack.length && getPriority(read) <= getPriority(last(stack)!) && + last(stack) !== "(") { + const op = last(stack)!; + stack.pop(); + postExpression.push(op); + } + stack.push(read); + } + } else { + let op = last(stack)!; + stack.pop(); + while (op && op !== "(") { + postExpression.push(op); + op = last(stack)!; + stack.pop(); + } + } + } + while (stack.length) { + const op = last(stack)!; + stack.pop(); + postExpression.push(op); + } + return postExpression; +} + +function getResult(postExpressionQueue: string[]) { + const stackCalc: number[] = []; + while (postExpressionQueue.length) { + const read = postExpressionQueue.shift()!; + if (isNumber(read)) { + const n = parseFloat(read); + stackCalc.push(n); + } else { + const second = stackCalc.pop()!; + const first = stackCalc.pop()!; + let result = 0; + + switch (read) { + case "+": + result = first + second; + break; + case "-": + result = first - second; + break; + case "*": + result = first * second; + break; + case "/": + result = first / second; + break; + case "^": + result = Math.pow(first, second); + break; + default: + break; + } + stackCalc.push(result); + } + } + if (stackCalc.length === 1) { + return stackCalc[0]; + } + throw new Error("Failed to calculate"); +} + +function validateNumber(number: number) { + if (isNaN(number)) { + throw new Error("Not a number"); + } + return number; +} + +export function fixStringForCalculation(expression: string) { + return expression.replace(/[^0-9()^*/+-.,]/g, ""); +} + +export function canEvaluateMathExpression(expression: string) { + const res = /[()^*/+-]/g.test(expression.substring(1)); + return res; +} + +export function calculateMathExpression(expression: string) { + expression = fixStringForCalculation(expression); + + if (/[()^*/+-]/g.test(expression)) { + const postFixed = processExpression(expression); + return validateNumber(getResult(postFixed)); + } else { + return validateNumber(parseFloat(expression)); + } +} diff --git a/src/components/a4-canvas.tsx b/src/components/a4-canvas.tsx new file mode 100644 index 0000000..e971e03 --- /dev/null +++ b/src/components/a4-canvas.tsx @@ -0,0 +1,235 @@ +import { useRef, useEffect, useState } from "react"; +import styled from "styled-components"; +import { useSettings } from "../use/use-settings"; +import type { CanvasHandlerProps } from "../handler/interfaces"; +import { TOOL_OBJECT_MOVE, TOOL_CANVAS_MOVE, TOOL_SELECT } from "../handler/tools"; +import { clamp } from "lodash"; +import { PaperView } from "./image-tools/paper-view"; + +const ZOOM_SENSITIVITY = 1000; + +const Container = styled.div<{ $cursor?: string }>` + width: 100%; + height: 100%; + overflow: hidden; + position: relative; + background-color: gray; + + cursor: ${({ $cursor }) => $cursor || ""}; + + image-rendering: -webkit-optimize-contrast; + backface-visibility: hidden; + perspective: 1000; +`; + +const CanvasWrapper = styled.div` + position: absolute; + transform-origin: 0 0; + will-change: transform; + box-shadow: 20px 20px 20px black; +`; + +export default function A4Canvas({ handler }: CanvasHandlerProps) { + const { settings, setSettings } = useSettings(handler); + const [cursor, setCursor] = useState(handler.toolHandler.tool === TOOL_CANVAS_MOVE.key ? "grab" : ""); + + const wrapperRef = useRef(null); + const containerRef = useRef(null); + const panRef = useRef({ offsetX: 0, offsetY: 0 }); + const isDragging = useRef(false); + const dragStart = useRef({ x: 0, y: 0 }); + const hasMoved = useRef(false); + + const applyTransform = () => { + const wrapper = wrapperRef.current; + if (!wrapper) return; + const { offsetX, offsetY } = panRef.current; + wrapper.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${settings.scale})`; + }; + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + panRef.current.offsetX = (container.offsetWidth - (settings.landscape ? settings.paperHeight : settings.paperWidth) * settings.scale) / 2; + panRef.current.offsetY = (container.offsetHeight - (settings.landscape ? settings.paperWidth : settings.paperHeight) * settings.scale) / 2; + applyTransform(); + + const unsub = handler.toolHandler.emitter.on("select", (tool) => { + setCursor(tool === TOOL_CANVAS_MOVE.key ? "grab" : ""); + }); + return () => { + unsub(); + }; + }, []); + + useEffect(() => { + applyTransform(); + }, [settings.scale]); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + const handleWheel = (e: WheelEvent) => { + if (!container) return; + e.preventDefault(); + + const delta = (1 - e.deltaY) / ZOOM_SENSITIVITY; + + const newScale = settings.scale + delta; + const scale = clamp(newScale, 0.1, 10); + setSettings({ scale }); + const rect = container.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + const currentScale = settings.scale; + const scaleRatio = newScale / currentScale; + + panRef.current.offsetX = mouseX - scaleRatio * (mouseX - panRef.current.offsetX); + panRef.current.offsetY = mouseY - scaleRatio * (mouseY - panRef.current.offsetY); + }; + + container.addEventListener("wheel", handleWheel, { passive: false }); + return () => container.removeEventListener("wheel", handleWheel); + }, [settings.scale]); + + useEffect(() => { + const update = async (ev: KeyboardEvent) => { + const shift = ev.shiftKey ? 10 : 1; + + let x = 0; + let y = 0; + switch (ev.key) { + case "ArrowUp": + case "w": + case "W": + y = -shift; + break; + case "ArrowDown": + case "s": + case "S": + y = shift; + break; + case "ArrowLeft": + case "a": + case "A": + x = -shift; + break; + case "ArrowRight": + case "d": + case "D": + x = shift; + break; + case "Delete": { + const copy = [...handler.selected]; + if (copy.length && await handler.popup.confirm("Title", "Are you sure you want to delete")) { + for (const id of copy) { + handler.deleteImage(id); + } + } + break; + } + } + if (x || y) { + handler.selected.forEach((e) => { + handler.move(e, x, y); + }); + + } + }; + + window.addEventListener("keyup", update); + return () => { + window.removeEventListener("keyup", update); + }; + }); + + + const handleMouseDown = (e: React.MouseEvent) => { + if (!isDragging.current && handler.toolHandler.isToolSelected(TOOL_CANVAS_MOVE)) { + setCursor("grabbing"); + } + isDragging.current = true; + hasMoved.current = false; + + dragStart.current = { + x: e.clientX - panRef.current.offsetX, + y: e.clientY - panRef.current.offsetY, + }; + }; + + const handleMouseMove = (e: React.MouseEvent) => { + if (!isDragging.current) return; + hasMoved.current = true; + + if (handler.toolHandler.isToolSelected(TOOL_CANVAS_MOVE) || e.buttons === 4) { + panRef.current.offsetX = Math.round(e.clientX - dragStart.current.x); + panRef.current.offsetY = Math.round(e.clientY - dragStart.current.y); + } else if (handler.toolHandler.isToolSelected(TOOL_OBJECT_MOVE)) { + // if (!handler.selected.length) { + // handler.popup.alert("Drag", "None of the images are selected"); + // stopDragging(); + // return; + // } + // const mx = e.clientX - panRef.current.offsetX; + // const my = e.clientY - panRef.current.offsetY; + + // const dx = mx - dragStart.current.x; + // const dy = my - dragStart.current.y; + + // const x = dx / settings.scale; + // const y = dy / settings.scale; + + // dragStart.current.x = mx; + // dragStart.current.y = my; + + // handler.selected.forEach((e) => { + // handler.move(e, x, y); + // }); + } + applyTransform(); + }; + + const stopDragging = () => { + if (isDragging.current && handler.toolHandler.isToolSelected(TOOL_CANVAS_MOVE)) { + setCursor("grab"); + } + isDragging.current = false; + }; + + const onClick = (ev: React.MouseEvent) => { + if ( + handler.toolHandler.isToolSelected(TOOL_SELECT) || + (handler.toolHandler.isToolSelected(TOOL_OBJECT_MOVE) && !hasMoved.current) + ) { + const container = containerRef.current; + if (!container) return; + + const rect = container.getBoundingClientRect(); + + const screenX = ev.clientX - rect.left; + const screenY = ev.clientY - rect.top; + + const a4X = (screenX - panRef.current.offsetX) / settings.scale; + const a4Y = (screenY - panRef.current.offsetY) / settings.scale; + + handler.selectNext(a4X, a4Y, !ev.shiftKey, ev.ctrlKey); + } + }; + + return ( + + + + + + ); +} diff --git a/src/components/buttons/button-ribbon.tsx b/src/components/buttons/button-ribbon.tsx new file mode 100644 index 0000000..f671753 --- /dev/null +++ b/src/components/buttons/button-ribbon.tsx @@ -0,0 +1,41 @@ +import styled from "styled-components"; + +const Button = styled.button` + margin: 10px; + padding: 8px; + width: 75px; + height: 75px; + border: 1px solid white; + border-radius: 0; + + cursor: pointer; + background: rgba(255, 255, 255, 0.05); + color: white; + + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + + &:hover { + background: rgba(255, 255, 255, 0.15); + } +`; + +interface RibbonButtonProps { + icon: () => React.ReactNode; + name: string; + onClick: (ev: React.MouseEvent) => void; + disabled?: boolean; +} + +export function RibbonButton(props: RibbonButtonProps) { + return ( + <> + + + ); +} diff --git a/src/components/download-buttons.tsx b/src/components/download-buttons.tsx new file mode 100644 index 0000000..4d5f424 --- /dev/null +++ b/src/components/download-buttons.tsx @@ -0,0 +1,104 @@ +import { FaBroom, FaDrawPolygon, FaFileExport, FaFileImport, FaFilePdf, FaFileZipper, FaImage } from "react-icons/fa6"; +import type { CanvasHandlerProps } from "../handler/interfaces"; +import { Button } from "../styles"; +import styled from "styled-components"; +import { createPdf, createSVGDoc, downloadBlob, downloadZip } from "../utils/download"; +import type { ImageEditor } from "../handler/image-editor"; + +const Btn = styled(Button)` + width: 100%; + height: 100%; + margin: 0px; + padding: 2px; + flex-grow: 1; +`; + +const Row = styled.div` + display: flex; + flex-direction: row; + width: 100%; + height: 100%; +`; +const Column = styled.div` + display: flex; + flex-direction: column; + width: 100%; + height: 100%; +`; + +const Box = styled.div<{ $width: number, $height: number }>` + width: ${({ $width }) => $width}px; + height: ${({ $height }) => $height}px; + padding: 10px; +`; + + +export function ImportExportButtons({ handler, selected }: CanvasHandlerProps & { selected: ImageEditor[] }) { + const boxSize = 90; + return <> + + + + + { downloadZip(handler); }}> + ZIP + + { createPdf(handler, true).save(`${handler.projectName}.pdf`); }}> + PDF + + { downloadBlob(await createSVGDoc(handler), `${handler.projectName}.svg`); }} > + SVG + + + + { + const v = handler.imagesRenders.find(e => e.id === selected[0].id); + if (v) { + const canvas = document.createElement("canvas"); + canvas.width = v.image.naturalWidth * v.scale; + canvas.height = v.image.naturalHeight * v.scale; + const ctx = canvas.getContext("2d")!; + ctx.drawImage(v.image, 0, 0, canvas.width, canvas.height); + canvas.toBlob(blob => { + if (blob) { + downloadBlob(blob, `${v.name}.png`); + } else { + handler.popup.alert("Error", "Cannot export image"); + } + }, "image/png"); + } else { + handler.popup.alert("Error", "Image not found"); + } + }}>Image + { + const v = handler.imagesRenders.find(e => e.id === selected[0].id); + if (v) { + if (v.svg.scale !== v.scale || v.svg.dirty) { + handler.redrawSvg(v.id); + } + downloadBlob(new Blob([v.svg.data], { type: "image/svg+xml" }), `${v.name}.svg`); + } else { + handler.popup.alert("Error", "Image not found"); + } + + }}> SVG + + + + + + + { handler.exportProject(); }}>Import + { handler.importProject(); }} >Export + + { + { + if (await handler.popup.confirm("Clear", "Are you sure you want to clear project? Any unsaved changes will be discarded")) { + handler.clear(); + } + } + }} >Clear + + + ; +} diff --git a/src/components/drag-drop.tsx b/src/components/drag-drop.tsx new file mode 100644 index 0000000..ee94525 --- /dev/null +++ b/src/components/drag-drop.tsx @@ -0,0 +1,62 @@ +import { useEffect, useState } from "react"; +import type { CanvasHandlerProps } from "../handler/interfaces"; +import styled from "styled-components"; + +const DragOverlay = styled.div` + position: absolute; + inset: 16px; + border-radius: 4px; + border: 2px dashed rgba(255, 255, 255, 0.6); + background: rgba(0, 0, 0, 0.5); + display: flex; + pointer-events: none; + align-items: center; + justify-content: center; + color: white; + font-size: 14px; + z-index: 99999; +`; + +export function DragDrop({ handler }: CanvasHandlerProps) { + const [dragOver, setDragOver] = useState(false); + + useEffect(() => { + const handleDragOver = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragOver(true); + }; + + const handleDragLeave = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragOver(false); + }; + + const handleDrop = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragOver(false); + + const dt = e.dataTransfer; + if (!dt) return; + + const files = dt.files && dt.files.length ? [...dt.files] : []; + for (const file of files) { + handler.importFile(file); + } + }; + + window.addEventListener("dragover", handleDragOver); + window.addEventListener("dragleave", handleDragLeave); + window.addEventListener("drop", handleDrop); + + return () => { + window.removeEventListener("dragover", handleDragOver); + window.removeEventListener("dragleave", handleDragLeave); + window.removeEventListener("drop", handleDrop); + }; + }, []); + + return dragOver ? Drop PNG files to place on page : null; +} diff --git a/src/components/image-prop-edit.tsx b/src/components/image-prop-edit.tsx new file mode 100644 index 0000000..e61358e --- /dev/null +++ b/src/components/image-prop-edit.tsx @@ -0,0 +1,46 @@ +import styled from "styled-components"; +import type { ImageEditor } from "../handler/image-editor"; + +const Wrapper = styled.div` + display: block; + padding: 5px; +`; + +const Input = styled.input` + width: 50px; + height: 10px; + background: #0d0d0d; + border: 1px solid #444; + border-top: none; + color: #e0e0e0; + font-size: 13px; + padding: 10px 12px; + outline: none; + margin-top: 12px; + + &::placeholder { + color: #555; + } + + &:focus { + border-color: #666; + } +`; + +export function ImagePropEditor({ propKey, selected }: { selected: ImageEditor[]; propKey: keyof ImageEditor }) { + if (selected.length !== 1) return null; + const item = selected[0]; + + return ( + + {propKey}: + { + item.onPropsChange({ [propKey]: parseInt(ev.target.value, 10) }); + }} + /> + + ); +} diff --git a/src/components/image-scaler.tsx b/src/components/image-scaler.tsx new file mode 100644 index 0000000..136dd66 --- /dev/null +++ b/src/components/image-scaler.tsx @@ -0,0 +1,179 @@ +import styled from "styled-components"; +import { FaMinus, FaPlus } from "react-icons/fa"; +import type { CanvasHandlerProps } from "../handler/interfaces"; +import { clamp } from "lodash"; +import type { ImageEditor } from "../handler/image-editor"; + +const SCALE_MIN = 0.1; +const SCALE_MAX = 3; +const SCALE_STEP = 0.01; + +const Wrapper = styled.div` + width: 200px; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + font-family: monospace; + user-select: none; +`; + +const IconBtn = styled.button` + background: var(--primary-color); + border: 1px solid #333; + color: #aaa; + width: 26px; + height: 26px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; + transition: + background 0.1s, + color 0.1s; + padding: 0; + + &:hover { + background: #2a2a2a; + color: #fff; + } + + &:active { + background: #0d0d0d; + } + + &:disabled { + opacity: 0.3; + cursor: not-allowed; + } +`; + +const Track = styled.div` + position: relative; + flex: 1; + height: 4px; + background: #2e2e2e; + min-width: 80px; +`; + +const Fill = styled.div<{ $pct: number }>` + position: absolute; + left: 0; + top: 0; + height: 100%; + width: ${({ $pct }) => $pct}%; + background: #e0e0e0; + pointer-events: none; +`; + +const Thumb = styled.input` + position: absolute; + inset: 0; + width: 100%; + height: 100%; + opacity: 0; + cursor: pointer; + margin: 0; + + &:disabled { + cursor: not-allowed; + } +`; + +const ThumbDot = styled.div<{ $pct: number; $disabled: boolean }>` + position: absolute; + top: 50%; + left: ${({ $pct }) => $pct}%; + transform: translate(-50%, -50%); + width: 12px; + height: 12px; + background: ${({ $disabled }) => ($disabled ? "#444" : "#fff")}; + border: 2px solid ${({ $disabled }) => ($disabled ? "#333" : "#888")}; + pointer-events: none; + transition: background 0.1s; +`; + +const Label = styled.span` + font-size: 11px; + color: #666; + min-width: 36px; + text-align: right; + letter-spacing: 0.05em; +`; + +export function ImageScaler({ handler, selected }: CanvasHandlerProps & { selected: ImageEditor[] }) { + const disabled = selected.length === 0; + const isMixed = selected.length > 1; + + const scale = disabled ? 1 : isMixed ? selected.reduce((acc, e) => acc + e.scale, 0) / selected.length : selected[0].scale; + + const pct = ((clamp(scale, SCALE_MIN, SCALE_MAX) - SCALE_MIN) / (SCALE_MAX - SCALE_MIN)) * 100; + + const setScale = (value: number, ignoreLimit = false) => { + const clamped = ignoreLimit ? value : Math.min(SCALE_MAX, Math.max(SCALE_MIN, value)); + + selected.forEach((e) => { + const oldScale = e.scale; + const newScale = clamped; + + const currentW = e.image.width * oldScale; + const currentH = e.image.height * oldScale; + + const newW = e.image.width * newScale; + const newH = e.image.height * newScale; + + const dx = (currentW - newW) / 2; + const dy = (currentH - newH) / 2; + + e.onPropsChange({ + scale: clamped, + x: e.x + dx, + y: e.y + dy, + }); + }); + }; + + const label = disabled ? "—" : isMixed ? "~" + scale.toFixed(2) : scale.toFixed(2); + + return ( + + setScale(scale - SCALE_STEP * 10)} title="Scale down"> + + + + + + + setScale(parseFloat(e.target.value))} + /> + + + setScale(scale + SCALE_STEP * 10, true)} title="Scale up"> + + + + + + ); +} diff --git a/src/components/image-tools/canvas-view.tsx b/src/components/image-tools/canvas-view.tsx new file mode 100644 index 0000000..72ff548 --- /dev/null +++ b/src/components/image-tools/canvas-view.tsx @@ -0,0 +1,38 @@ +import { useRef, useEffect } from "react"; +import { drawCanvas } from "../../draw-canvas"; +import type { CanvasHandlerProps } from "../../handler/interfaces"; +import styled from "styled-components"; + +const CanvasEl = styled.canvas` + background: white; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + display: block; +`; + +export function CanvasView({ handler }: CanvasHandlerProps) { + const ref = useRef(null); + + useEffect(() => { + const canvas = ref.current; + if (!canvas) return; + + const render = () => { + drawCanvas(canvas!, handler); + }; + + render(); + + const unsubs = [ + handler.emitter.on("update", render), + handler.emitter.on("array-length", render), + handler.emitter.on("select", render), + handler.globalSettingsHandler.emitter.on("settings-update", render), + ]; + + return function () { + unsubs.forEach((unsub) => unsub()); + }; + }, [handler]); + + return ; +} diff --git a/src/components/image-tools/paper-view.tsx b/src/components/image-tools/paper-view.tsx new file mode 100644 index 0000000..4a0bac6 --- /dev/null +++ b/src/components/image-tools/paper-view.tsx @@ -0,0 +1,106 @@ +import type { CanvasHandlerProps } from "../../handler/interfaces"; +import styled from "styled-components"; +import { MM_TO_INCH } from "../../constants"; +import { useImages } from "../../use/use-images"; +import { FreeTransform } from "./selection-box"; +import type { GlobalSettings, GridType } from "../../interface"; +import { SvgRenderer } from "./svg-view"; + + +const CanvasEl = styled.div` + background: white; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + display: block; +`; + +const Img = styled.img<{ $selected: boolean }>` + position: absolute; + display: block; + user-select: none; + opacity: ${({ $selected }) => $selected ? 0.5 : 1}; +`; + + +const Box = styled.div` + position: absolute; + top: 0; + left: 0; + background-color: #757575; +`; + +function getGridLines(settings: GridType, width: number, height: number, strokeWidth: number, strokeWidthHalf: number) { + if (settings === "none") return null; + const configs: Record = { + "none": { + horizontal: [1], + vertical: [1] + }, + "2x2": { + vertical: [1 / 2], + horizontal: [1 / 2], + }, + "3x3": { + vertical: [1 / 3, 2 / 3], + horizontal: [1 / 3, 2 / 3], + }, + }; + + const config = configs[settings]; + if (!config) return null; + + return <> + {config.vertical.map((fraction, i) => ( + + ))} + {config.horizontal.map((fraction, i) => ( + + ))} + ; +}; + +export function PaperView({ handler, settings }: CanvasHandlerProps & { settings: GlobalSettings }) { + const { images } = useImages(handler); + const dpi = settings.DPI; + const width = Math.round(((settings.landscape ? settings.paperHeight : settings.paperWidth) / MM_TO_INCH) * dpi) * settings.scale; + const height = Math.round(((settings.landscape ? settings.paperWidth : settings.paperHeight) / MM_TO_INCH) * dpi) * settings.scale; + + const renderGuides = () => { + const strokeWidth = 1; + const strokeWidthHalf = strokeWidth / 2; + return getGridLines(settings.grid, width, height, strokeWidth, strokeWidthHalf); + }; + + return + {images.map(e => { + if (!e.visible) return; + const width = e.image.naturalWidth * e.scale * settings.scale; + const height = e.image.naturalHeight * e.scale * settings.scale; + const x = e.x * settings.scale; + const y = e.y * settings.scale; + return
+ {e.id} + + {e.selected ? + { + const scale = Math.round(width / e.image.width / settings.scale * 100) / 100; + e.onPropsChange({ x: Math.round(x / settings.scale), y: Math.round(y / settings.scale), scale }); + }} /> : null} +
; + })} + {renderGuides()} +
; +} diff --git a/src/components/image-tools/selection-box.tsx b/src/components/image-tools/selection-box.tsx new file mode 100644 index 0000000..d99cead --- /dev/null +++ b/src/components/image-tools/selection-box.tsx @@ -0,0 +1,269 @@ +import React, { useRef, useCallback } from "react"; +import styled from "styled-components"; + +type Transform = { + x: number; + y: number; + width: number; + height: number; + rotation: number; +}; + +type DragState = + | { type: "none" } + | { type: "move"; startX: number; startY: number; originX: number; originY: number } + | { type: "resize"; handle: HandleKey; startX: number; startY: number; originTransform: Transform } + | { type: "rotate"; startAngle: number; originRotation: number; cx: number; cy: number }; + +type HandleKey = "nw" | "ne" | "se" | "sw"; + +const HANDLE_SIZE = 10; +const MIN_SIZE = 20; + +const HANDLE_CURSORS: Record = { + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize", +}; + +const HANDLE_POSITIONS: Record = { + nw: { x: 0, y: 0 }, + ne: { x: 1, y: 0 }, + se: { x: 1, y: 1 }, + sw: { x: 0, y: 1 }, +}; + +const Overlay = styled.div` + inset: 0; + overflow: hidden; + pointer-events: none; +`; + +const TransformBox = styled.div` + position: absolute; + pointer-events: auto; + cursor: move; + user-select: none; + position: fixed; + z-index: 999999; +`; + +const SelectionBorder = styled.div` + position: absolute; + inset: 0; + border: 2px dashed #00d4ff; + box-sizing: border-box; + pointer-events: none; +`; + +const ResizeHandle = styled.div<{ $x: number; $y: number; $cursor: string }>` + position: absolute; + left: calc(${({ $x }) => $x * 100}% - ${HANDLE_SIZE / 2}px); + top: calc(${({ $y }) => $y * 100}% - ${HANDLE_SIZE / 2}px); + width: ${HANDLE_SIZE}px; + height: ${HANDLE_SIZE}px; + background: white; + border: 2px solid #00d4ff; + border-radius: 2px; + cursor: ${({ $cursor }) => $cursor}; + pointer-events: auto; + z-index: 10; +`; + +// const RotateHandleWrapper = styled.div` +// position: absolute; +// left: 50%; +// top: -36px; +// transform: translateX(-50%); +// display: flex; +// flex-direction: column; +// align-items: center; +// pointer-events: auto; +// cursor: grab; +// z-index: 10; +// `; + +// const RotateStem = styled.div` +// width: 2px; +// height: 20px; +// background: #00d4ff; +// `; + +// const RotateKnob = styled.div` +// width: 14px; +// height: 14px; +// border-radius: 50%; +// background: white; +// border: 2px solid #00d4ff; +// margin-top: -1px; +// `; + +export type FreeTransformProps = { + transform: Transform; + scale?: number; + onTransformChange: (t: Transform) => void; +}; + +export function FreeTransform({ + transform, + scale = 1, + onTransformChange, +}: FreeTransformProps) { + const dragRef = useRef({ type: "none" }); + + const apply = useCallback( + (updater: (prev: Transform) => Transform) => { + onTransformChange(updater(transform)); + }, + [transform, onTransformChange] + ); + + const onMovePointerDown = useCallback( + (e: React.PointerEvent) => { + e.stopPropagation(); + e.currentTarget.setPointerCapture(e.pointerId); + dragRef.current = { + type: "move", + startX: e.clientX, + startY: e.clientY, + originX: transform.x, + originY: transform.y, + }; + }, + [transform.x, transform.y] + ); + + const onResizePointerDown = useCallback( + (handle: HandleKey) => (e: React.PointerEvent) => { + e.stopPropagation(); + e.currentTarget.setPointerCapture(e.pointerId); + dragRef.current = { + type: "resize", + handle, + startX: e.clientX, + startY: e.clientY, + originTransform: { ...transform }, + }; + }, + [transform] + ); + + // const onRotatePointerDown = useCallback( + // (e: React.PointerEvent) => { + // e.stopPropagation(); + // e.currentTarget.setPointerCapture(e.pointerId); + // const cx = transform.x * scale + (transform.width * scale) / 2; + // const cy = transform.y * scale + (transform.height * scale) / 2; + // const startAngle = Math.atan2(e.clientY - cy, e.clientX - cx) * (180 / Math.PI); + // dragRef.current = { + // type: "rotate", + // startAngle, + // originRotation: transform.rotation, + // cx, + // cy, + // }; + // }, + // [transform, scale] + // ); + + const onPointerMove = useCallback( + (e: React.PointerEvent) => { + if (e.buttons !== 1) return; + const drag = dragRef.current; + if (drag.type === "none") return; + + if (drag.type === "move") { + const dx = (e.clientX - drag.startX) / scale; + const dy = (e.clientY - drag.startY) / scale; + apply(() => ({ + ...transform, + x: drag.originX + dx, + y: drag.originY + dy, + })); + } + + if (drag.type === "rotate") { + const currentAngle = + Math.atan2(e.clientY - drag.cy, e.clientX - drag.cx) * (180 / Math.PI); + apply(() => ({ + ...transform, + rotation: drag.originRotation + (currentAngle - drag.startAngle), + })); + } + + if (drag.type === "resize") { + const { handle, startX, startY, originTransform: o } = drag; + const rad = (-o.rotation * Math.PI) / 180; + const rawDx = (e.clientX - startX) / scale; + const rawDy = (e.clientY - startY) / scale; + const dx = rawDx * Math.cos(rad) - rawDy * Math.sin(rad); + const dy = rawDx * Math.sin(rad) + rawDy * Math.cos(rad); + + let { x, y, width, height } = o; + + if (handle.includes("e")) width = Math.max(MIN_SIZE, o.width + dx); + if (handle.includes("s")) height = Math.max(MIN_SIZE, o.height + dy); + if (handle.includes("w")) { + const newW = Math.max(MIN_SIZE, o.width - dx); + x = o.x + o.width - newW; + width = newW; + } + if (handle.includes("n")) { + const newH = Math.max(MIN_SIZE, o.height - dy); + y = o.y + o.height - newH; + height = newH; + } + + apply(() => ({ ...transform, x, y, width, height })); + } + }, + [transform, scale, apply] + ); + + const onPointerUp = useCallback(() => { + dragRef.current = { type: "none" }; + }, []); + + const { x, y, width, height, rotation } = transform; + + return ( + + + + + {(Object.keys(HANDLE_POSITIONS) as HandleKey[]).map((key) => ( + + ))} + + {/* + + + */} + + + ); +}; diff --git a/src/components/image-tools/svg-view.tsx b/src/components/image-tools/svg-view.tsx new file mode 100644 index 0000000..e9ec1b5 --- /dev/null +++ b/src/components/image-tools/svg-view.tsx @@ -0,0 +1,38 @@ + +import styled from "styled-components"; + +const Wrapper = styled.div` + position: absolute; + display: inline-block; + line-height: 0; + + + svg { + width: 100%; + height: 100%; + display: block; + } +`; + +type Props = { + svg: string; + x: number; + y: number; + width: number; + height: number; +} + + +export function SvgRenderer(props: Props) { + const { svg, x, y, width, height } = props; + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/info-bar.tsx b/src/components/info-bar.tsx new file mode 100644 index 0000000..99b0fef --- /dev/null +++ b/src/components/info-bar.tsx @@ -0,0 +1,110 @@ +import { styled } from "styled-components"; +import type { CanvasHandlerProps } from "../handler/interfaces"; +import { Icon2525 } from "../styles"; +import { FaMinus, FaPlus } from "react-icons/fa6"; +import { useSettings } from "../use/use-settings"; +import { clamp } from "lodash"; +import { useEffect, useState } from "react"; + +const Wrapper = styled.div` + display: flex; + flex-direction: row; + background-color: var(--primary-color); + border-top: 1px solid var(--secondary-color); +`; + +const ScaleText = styled.span` + width: 50px; + margin-top: 4px; + text-align: center; +`; + +const ProjectName = styled.span` + margin: auto; +`; + +const DPI = [72, 96, 150, 300, 600]; + +export function InfoBar({ handler }: CanvasHandlerProps) { + const { settings, setSettings } = useSettings(handler); + const [projectName, setProjectName] = useState(handler.projectName); + + useEffect(() => { + return handler.emitter.on("name", name => { + setProjectName(name); + }); + }, [handler.projectName]); + + const setScale = (increment: boolean) => { + const amount = 0.05; + setSettings({ scale: settings.scale + (increment ? amount : -amount) }); + }; + const setDPI = (increment: boolean) => { + let index = DPI.indexOf(settings.DPI); + + if (index === -1) { + index = DPI.reduce((bestIdx, value, i) => { + const bestDiff = Math.abs(DPI[bestIdx] - settings.DPI); + const currentDiff = Math.abs(value - settings.DPI); + return currentDiff < bestDiff ? i : bestIdx; + }, 0); + } + + if (increment) { + if (index + 1 < DPI.length) index++; + } else { + if (index - 1 >= 0) index--; + } + + setSettings({ DPI: DPI[index] }); + }; + + return ( + + setScale(false)}> + + + { + const number = await handler.popup.prompt("DPI", "Enter your desired DPI", (settings.scale * 100).toString()); + if (number) { + const int = parseInt(number, 10) / 100; + if (!isNaN(int)) { + setSettings({ scale: clamp(int, 0, 100) }); + } + } + }} + > + {Math.round(settings.scale * 100)}% + + setScale(true)}> + + + + { + handler.promptSetName(); + }}> {projectName} + + + setDPI(false)}> + + + { + const number = await handler.popup.prompt("DPI", "Enter your desired DPI", settings.DPI.toString()); + if (number) { + const int = parseInt(number, 10); + if (!isNaN(int)) { + setSettings({ DPI: clamp(int, DPI[0], DPI[DPI.length - 1]) }); + } + } + }} + > + {Math.round(settings.DPI)}DPI + + setDPI(true)}> + + + + ); +} diff --git a/src/components/layer-item.tsx b/src/components/layer-item.tsx new file mode 100644 index 0000000..31bf22a --- /dev/null +++ b/src/components/layer-item.tsx @@ -0,0 +1,84 @@ +import styled from "styled-components"; +import type { CanvasHandleImageEditorProps } from "../handler/interfaces"; +import { FaEye, FaLock } from "react-icons/fa"; +import { NamePlate } from "./layer-name"; +import type { ImageEditor } from "../handler/image-editor"; + +const Item = styled.div<{ $selected: boolean }>` + width: 240px; + height: 30px; + border: 1px solid var(--secondary-color); + display: flex; + flex-direction: row; + + ${({ $selected }) => + $selected && + ` + background-color: var(--secondary-color); + `} +`; + +const Img = styled.img` + margin: 5px; + max-width: 25px; + max-height: 25px; +`; +const Icon = styled.span` + padding: 7px 5px; + cursor: pointer; +`; + +export function LayerItem({ image, handler }: CanvasHandleImageEditorProps) { + const selectLayer = (image: ImageEditor, ctrl: boolean) => { + if (!ctrl) { + const selected = handler.selected.filter(e => e !== image.id); + for (const sel of selected) { + handler.setSelect(sel, false); + } + } + image.toggleSelected(); + }; + return ( + { + ev.stopPropagation(); + ev.preventDefault(); + selectLayer(image, ev.ctrlKey); + }} + > + { + ev.stopPropagation(); + ev.preventDefault(); + image.setVisible(!image.visible); + }} + > + + + + { + ev.stopPropagation(); + ev.preventDefault(); + selectLayer(image, ev.ctrlKey); + }} + src={image.url} + alt={image.id} + /> + + { + ev.stopPropagation(); + ev.preventDefault(); + image.setLock(!image.locked); + }} + > + + + + ); +} diff --git a/src/components/layer-name.tsx b/src/components/layer-name.tsx new file mode 100644 index 0000000..f31cd6f --- /dev/null +++ b/src/components/layer-name.tsx @@ -0,0 +1,57 @@ +import styled from "styled-components"; +import type { ImageEditorProps } from "../handler/interfaces"; +import { useState } from "react"; + +const NamePlateDiv = styled.span` + width: 150px; + display: inline-block; + margin-top: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-grow: 1; +`; + +const Input = styled.input` + width: 140px; + background-color: var(--primary-color); + border: 1px solid var(--secondary-color); + border-radius: 0px; + color: white; +`; + +export function NamePlate({ image }: ImageEditorProps) { + const [name, setName] = useState(() => image.name); + const [editing, setEditing] = useState(false); + + const stopEditing = () => { + image.setName(name); + setEditing(false); + }; + + return ( + + {editing ? ( + setName(e.target.value)} + onBlur={stopEditing} + onKeyUp={(ev) => { + if (ev.key === "Enter") { + stopEditing(); + } + }} + /> + ) : ( + { + if (!image.locked) { + ev.stopPropagation(); + ev.preventDefault(); + setEditing(true); + } + }}>{image.name} + )} + + ); +} diff --git a/src/components/layer-panel.tsx b/src/components/layer-panel.tsx new file mode 100644 index 0000000..6680ef1 --- /dev/null +++ b/src/components/layer-panel.tsx @@ -0,0 +1,152 @@ +import type { CanvasHandlerProps } from "../handler/interfaces"; +import styled from "styled-components"; +import { LayerItem } from "./layer-item"; +import { FaFileImport, FaTrash } from "react-icons/fa"; +import { Icon2525 } from "../styles"; +import { useImages } from "../use/use-images"; + +const LayerPanelDiv = styled.div` + border-top: 1px solid var(--secondary-color); + border-left: 1px solid var(--secondary-color); + background-color: var(--primary-color); + display: flex; + width: 242px; + flex-direction: column; +`; + +const Gap = styled.div` + flex-grow: 1; +`; + +const Version = styled.span` + margin: 0 5px; +`; + + +const ToolBar = styled.div` + display: flex; + flex-direction: row; + border-top: 1px solid var(--secondary-color); +`; + + + +const Wrapper = styled.div` + width: 200px; + padding: 24px; + display: flex; + flex-direction: column; + gap: 10px; +`; + +const Title = styled.div` + font-size: 14px; + font-weight: 600; + opacity: 0.9; +`; + +const Row = styled.div` + display: flex; + justify-content: space-between; + gap: 12px; + font-size: 13px; +`; + +const Label = styled.div` + opacity: 0.6; +`; + +const Value = styled.div` + text-align: right; + word-break: break-word; +`; + +const Link = styled.a` + color: #4AF626; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +`; + +export function LayerPanel({ handler }: CanvasHandlerProps) { + const { images } = useImages(handler); + + return ( + + {images.map((e) => ( + + ))} + + + + { + handler.popup.custom("About", "", + Vectrace {handler.VERSION}v + + + + {handler.AUTHOR.name} + + + + + + {handler.AUTHOR.email} + + + + + + + {handler.AUTHOR.url} + + + + ); + }}> + {handler.VERSION}v + + + + { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.addEventListener("change", async () => { + if (input.files) { + for (const file of [...input.files]) { + await handler.importFile(file); + } + } + }); + input.click(); + }} + > + + + { + const c = [...handler.selected]; + if (c.length) { + if ( + await handler.popup.confirm( + `Delete (${c.length})`, + `Are you sure you want to delete ${c.length} items?`, + ) + ) { + for (const id of c) { + handler.deleteImage(id); + } + } + } + }} + > + + + + + ); +} diff --git a/src/components/number-input.tsx b/src/components/number-input.tsx new file mode 100644 index 0000000..ff5c612 --- /dev/null +++ b/src/components/number-input.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import { calculateMathExpression, canEvaluateMathExpression } from "../calc"; + +const StyledInput = styled.input<{ $isValid: boolean | null }>` + padding: 1px; + font-size: 1rem; + width: 80px; + border: 1px solid + ${({ $isValid }) => + $isValid === null + ? "#888" + : $isValid + ? "#ffffff" + : "#ef4444"}; + outline: none; + background-color: ${({ $isValid }) => + $isValid === null + ? "transparent" + : $isValid + ? "#000000" + : "#7e0000"}; + color: ${({ $isValid }) => + $isValid === null ? "inherit" : $isValid ? "#15803d" : "#b91c1c"}; + transition: border-color 0.2s ease, background-color 0.2s ease; + + &:focus { + box-shadow: 0 0 0 3px + ${({ $isValid }) => + $isValid === null + ? "#88888844" + : $isValid + ? "#22c55e44" + : "#ef444444"}; + } +`; + + +export interface NumberInputProps { + value?: number; + id?: string; + onChange?: (value: number) => void; + placeholder?: string; +} + + +export function NumberInput({ + value, + onChange, + id, + placeholder = "Enter a number...", +}: NumberInputProps) { + const [raw, setRaw] = useState(() => value !== undefined ? String(value) : "" + ); + const [isValid, setIsValid] = useState( + value !== undefined ? true : null + ); + + const isValidNumber = (val: string): boolean => { + if (val.trim() === "") return false; + const parsed = Number(val); + + return isFinite(parsed) && !isNaN(parsed); + }; + + const handleChange = (e: React.ChangeEvent) => { + const val = e.target.value; + setRaw(val); + + if (val.trim() === "") { + + setIsValid(null); + return; + } + + if (isValidNumber(val)) { + setIsValid(true); + onChange?.(Number(val)); + } else { + setIsValid(canEvaluateMathExpression(val)); + } + }; + const increment = (increment: boolean) => { + if (isValidNumber(raw)) { + const value = Number(raw) + (increment ? 1 : -1); + setRaw(value.toString()); + onChange?.(value); + } + }; + + + const executeEvaluate = () => { + if (canEvaluateMathExpression(raw)) { + const value = calculateMathExpression(raw); + + setRaw(value.toString()); + if (isValidNumber(value.toString())) { + setIsValid(true); + onChange?.(value); + } else { + setIsValid(false); + } + } + }; + + useEffect(() => { + if (value !== undefined) { + setRaw(value.toString()); + setIsValid(true); + } + }, [value]); + + return ( + { + switch (ev.key) { + case "Enter": + executeEvaluate(); + break; + case "ArrowUp": + ev.preventDefault(); + increment(true); + break; + case "ArrowDown": + ev.preventDefault(); + increment(false); + break; + default: + break; + } + }} + onChange={handleChange} + $isValid={isValid} + placeholder={placeholder} + id={id} + /> + ); +}; + diff --git a/src/components/numeric-labeld-input.tsx b/src/components/numeric-labeld-input.tsx new file mode 100644 index 0000000..8006fbd --- /dev/null +++ b/src/components/numeric-labeld-input.tsx @@ -0,0 +1,42 @@ +import styled from "styled-components"; +import { NumberInput, type NumberInputProps } from "./number-input"; + +const OptionDiv = styled.div` + margin: 2px; +`; + +export function NumericInputWithLabel({ + value, + onChange, + placeholder, + name, + id + +}: NumberInputProps & { name: string }) { + return + + + ; +} +export function NumericInputWithLabelTable({ + value, + onChange, + placeholder, + name, + id + +}: NumberInputProps & { name: string }) { + return + + + + + + + + ; +} diff --git a/src/components/paper-guide-selector.tsx b/src/components/paper-guide-selector.tsx new file mode 100644 index 0000000..937a90d --- /dev/null +++ b/src/components/paper-guide-selector.tsx @@ -0,0 +1,32 @@ +import styled from "styled-components"; +import { Button } from "../styles"; +import type { UseSettings } from "../use/use-settings"; +import { GRID } from "../interface"; + +const Wrapper = styled.div` + height: calc(100% - 8px); + border-right: 1px solid white; + margin: 4px 0px; + padding-right: 4px; + width: 40px; + display: flex; + flex-direction: column; + flex-wrap: wrap; + button { + flex-grow: 1; + } +`; + + +export function PaperGuideSelector({ setSettings, settings }: UseSettings) { + return + {GRID.map((e, i) => )} + ; +} diff --git a/src/components/paper-orentation.tsx b/src/components/paper-orentation.tsx new file mode 100644 index 0000000..ce01787 --- /dev/null +++ b/src/components/paper-orentation.tsx @@ -0,0 +1,13 @@ +import { FaFile } from "react-icons/fa6"; +import { RibbonButton } from "./buttons/button-ribbon"; +import type { UseSettings } from "../use/use-settings"; + +export function PaperOrientation({ setSettings, settings }: UseSettings) { + + return } + onClick={() => { + setSettings({ landscape: !settings.landscape }); + }} + name={settings.landscape ? "Landscape" : "Portrait"} />; +} diff --git a/src/components/paper-size-selector.tsx b/src/components/paper-size-selector.tsx new file mode 100644 index 0000000..168302f --- /dev/null +++ b/src/components/paper-size-selector.tsx @@ -0,0 +1,89 @@ +import styled from "styled-components"; +import { + A0_HEIGHT, A0_WIDTH, + A1_HEIGHT, A1_WIDTH, + A2_HEIGHT, A2_WIDTH, + A3_HEIGHT, A3_WIDTH, + A4_HEIGHT, A4_WIDTH, + A5_HEIGHT, A5_WIDTH, + A6_HEIGHT, A6_WIDTH, + LETTER_HEIGHT, LETTER_WIDTH, + LEGAL_HEIGHT, LEGAL_WIDTH, + TABLOID_HEIGHT, TABLOID_WIDTH, + LEDGER_HEIGHT, LEDGER_WIDTH, + +} from "../constants"; +import { Button } from "../styles"; +import type { UseSettings } from "../use/use-settings"; +import { Popup } from "../handler/popup"; + +const Wrapper = styled.div` + height: 100%; + width: 130px; + display: flex; + flex-wrap: wrap; + button { + flex-grow: 1; + } +`; + +interface Paper { + name: string; + width: number; + height: number; +} + +function createPaper(name: string, width: number, height: number): Paper { + return { name, width, height }; +} + +const papers = [ + createPaper("A0", A0_WIDTH, A0_HEIGHT), + createPaper("A1", A1_WIDTH, A1_HEIGHT), + createPaper("A2", A2_WIDTH, A2_HEIGHT), + createPaper("A3", A3_WIDTH, A3_HEIGHT), + createPaper("A4", A4_WIDTH, A4_HEIGHT), + createPaper("A5", A5_WIDTH, A5_HEIGHT), + createPaper("A6", A6_WIDTH, A6_HEIGHT), + + createPaper("Letter", LETTER_WIDTH, LETTER_HEIGHT), + createPaper("Legal", LEGAL_WIDTH, LEGAL_HEIGHT), + createPaper("Tabloid", TABLOID_WIDTH, TABLOID_HEIGHT), + createPaper("Ledger", LEDGER_WIDTH, LEDGER_HEIGHT), +]; + + +export function PaperSizeSelector({ setSettings, settings, popup }: UseSettings & { popup: Popup }) { + const custom = papers.find(e => settings.paperWidth === e.width && settings.paperHeight === e.height); + return + {papers.map((e, i) => )} + + ; +} diff --git a/src/components/popup.tsx b/src/components/popup.tsx new file mode 100644 index 0000000..ecf3d1b --- /dev/null +++ b/src/components/popup.tsx @@ -0,0 +1,213 @@ +import styled, { keyframes } from "styled-components"; +import { useState, type JSX } from "react"; +import { type PopupResolver, Popup } from "../handler/popup"; + +const fadeIn = keyframes` + from { opacity: 0; } + to { opacity: 1; } +`; + +const slideDown = keyframes` + from { transform: translateY(-24px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +`; + +const Overlay = styled.div` + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.65); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + animation: ${fadeIn} 0.15s ease-in-out; +`; + +const Dialog = styled.div` + background-color: var(--primary-color); + border: 1px solid var(--secondary-color); + min-width: 360px; + max-width: 520px; + width: 100%; + display: flex; + flex-direction: column; + animation: ${slideDown} 0.18s ease-out; +`; + +const TitleBar = styled.div` + border-bottom: 1px solid var(--secondary-color); + padding: 12px 16px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #888; + user-select: none; +`; + +const Body = styled.div` + padding: 24px 20px; + color: #d4d4d4; + font-size: 14px; + line-height: 1.6; + flex: 1; +`; + +const CustomBody = styled(Body)` + padding: 0; +`; + +const Input = styled.input` + width: calc(100% - 24px); + background: #0d0d0d; + border: 1px solid #444; + border-top: none; + color: #e0e0e0; + font-size: 13px; + padding: 10px 12px; + outline: none; + margin-top: 12px; + + &::placeholder { + color: #555; + } + + &:focus { + border-color: #666; + } +`; + +const Footer = styled.div` + display: flex; + border-top: 1px solid #2a2a2a; +`; + +const Btn = styled.button<{ $primary?: boolean }>` + flex: 1; + padding: 12px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + border: none; + cursor: pointer; + background: ${({ $primary }) => ($primary ? "#343434" : "#000000")}; + color: ${({ $primary }) => ($primary ? "#ffffff" : "#777")}; + border-right: 1px solid #2a2a2a; + transition: + background 0.1s, + color 0.1s; + + &:last-child { + border-right: none; + } + + &:hover { + background: ${({ $primary }) => ($primary ? "#6e6e6e" : "#2a2a2a")}; + color: ${({ $primary }) => ($primary ? "#000" : "#aaa")}; + } + + &:active { + background: ${({ $primary }) => ($primary ? "#ccc" : "#111")}; + } +`; + +function AlertPopup({ popup }: { popup: PopupResolver }) { + return ( + <> + {popup.message} +
+ popup.resolve(undefined)}> + OK + +
+ + ); +} + +function ConfirmPopup({ popup }: { popup: PopupResolver }) { + return ( + <> + {popup.message} +
+ popup.resolve(false)}>Cancel + popup.resolve(true)}> + Confirm + +
+ + ); +} + +function PromptPopup({ popup }: { popup: PopupResolver }) { + const typed = popup as typeof popup & { _default?: string }; + const [value, setValue] = useState(typed._default ?? ""); + + return ( + <> + + {popup.message} + setValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") popup.resolve(value); + if (e.key === "Escape") popup.resolve(null); + }} + /> + +
+ popup.resolve(null)}>Cancel + popup.resolve(value)}> + Submit + +
+ + ); +} + +function CustomPopup({ popup }: { popup: PopupResolver }) { + const typed = popup as typeof popup & { element: JSX.Element }; + return ( + <> + {typed.element} +
+ popup.resolve(undefined)}> + Close + +
+ + ); +} + +function PopupBody({ popup }: { popup: PopupResolver }) { + if (Popup.isAlert(popup)) return ; + if (Popup.isConfirm(popup)) return ; + if (Popup.isPrompt(popup)) return ; + if (Popup.isCustom(popup)) return ; + return null; +} + +interface PopupRendererProps { + popup: Popup; +} + +export function PopupRenderer({ popup }: PopupRendererProps) { + const { pendingPopups } = popup.use(); + + const current = pendingPopups[0]; + if (!current) return null; + + return ( + <> + + + {current.title} + + + + + ); +} diff --git a/src/components/svg-tracer-options.tsx b/src/components/svg-tracer-options.tsx new file mode 100644 index 0000000..5450dad --- /dev/null +++ b/src/components/svg-tracer-options.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import type { ImageEditor } from "../handler/image-editor"; +import type { TracerOptions } from "../interface"; +import { NumericInputWithLabelTable } from "./numeric-labeld-input"; + + +export function SvgTracerOptions({ selected }: { selected: ImageEditor[] }) { + const [options, setOptions] = useState(selected[0]?.tracerOptions ?? {}); + + useEffect(() => { + setOptions(selected[0]?.tracerOptions ?? {}); + }, [selected.map(e => e.id).join("")]); + + const setValue = (key: keyof TracerOptions, value: number) => { + setOptions({ ...options, [key]: value }); + selected.forEach(e => { + e.setTracerSvg({ ...options, [key]: value }); + }); + }; + + return + setValue("ltres", value)} + /> + setValue("qtres", value)} + /> + setValue("pathomit", value)} + /> + setValue("roundcoords", value)} + /> + ; +} \ No newline at end of file diff --git a/src/components/toolbar.tsx b/src/components/toolbar.tsx new file mode 100644 index 0000000..f6b682d --- /dev/null +++ b/src/components/toolbar.tsx @@ -0,0 +1,29 @@ +import { styled } from "styled-components"; +import type { CanvasHandlerProps } from "../handler/interfaces"; +import { useTool } from "../use/use-tool"; +import { Icon2525 } from "../styles"; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + background-color: var(--primary-color); + border-top: 1px solid var(--secondary-color); +`; + +export function Toolbar({ handler }: CanvasHandlerProps) { + const { currentTool, tools, setTool } = useTool(handler); + return ( + + {tools.map((tool) => ( + setTool(tool.key)} + className={currentTool === tool.key ? "active" : ""} + > + {tool.icon()} + + ))} + + ); +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..c847ffd --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,36 @@ +export const A0_WIDTH = 841; +export const A0_HEIGHT = 1189; + +export const A1_WIDTH = 594; +export const A1_HEIGHT = 841; + +export const A2_WIDTH = 420; +export const A2_HEIGHT = 594; + +export const A3_WIDTH = 297; +export const A3_HEIGHT = 420; + +export const A4_WIDTH = 210; +export const A4_HEIGHT = 297; + +export const A5_WIDTH = 148; +export const A5_HEIGHT = 210; + +export const A6_WIDTH = 105; +export const A6_HEIGHT = 148; + +export const LETTER_WIDTH = 216; +export const LETTER_HEIGHT = 279; + +export const LEGAL_WIDTH = 216; +export const LEGAL_HEIGHT = 356; + +export const TABLOID_WIDTH = 279; +export const TABLOID_HEIGHT = 432; + +export const LEDGER_WIDTH = 432; +export const LEDGER_HEIGHT = 279; + +export const MM_TO_INCH = 25.4; + +export const VERSION = "1.0.0"; diff --git a/src/draw-canvas.ts b/src/draw-canvas.ts new file mode 100644 index 0000000..ee73c63 --- /dev/null +++ b/src/draw-canvas.ts @@ -0,0 +1,43 @@ +import { MM_TO_INCH } from "./constants"; +import type { VectraceHandler } from "./handler/vectrace-handler"; + +export function drawCanvas(canvas: HTMLCanvasElement, handler: VectraceHandler) { + const settings = handler.globalSettingsHandler.settings; + const dpi = settings.DPI; + const width = Math.round((settings.paperWidth / MM_TO_INCH) * dpi); + const height = Math.round((settings.paperHeight / MM_TO_INCH) * dpi); + if (canvas.width !== width) { + canvas.width = width; + } + if (canvas.height !== height) { + canvas.height = height; + } + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.lineWidth = 5; + ctx.clearRect(0, 0, width, height); + ctx.strokeStyle = "#ccc"; + ctx.strokeRect(0, 0, width, height); + + ctx.strokeStyle = "#000000"; + //ctx.fillStyle = "#00bbff24"; + ctx.fillStyle = "#00bbffea"; + + for (let i = handler.imagesRenders.length - 1; i >= 0; i--) { + const image = handler.imagesRenders[i]; + if (image.visible) { + // const base64 = btoa(new TextEncoder().encode(image.svg).reduce((acc, byte) => acc + String.fromCharCode(byte), "")); + // const dataUrl = `data:image/svg+xml;base64,${base64}`; + // drawSVGOnCanvasScaled(image.svg, canvas); + ctx.drawImage(image.image, image.x, image.y, image.image.width * image.scale, image.image.height * image.scale); + //ctx.drawImage(image.image, image.x, image.y, image.image.naturalWidth * image.scale, image.image.naturalHeight * image.scale); + // if (handler.isSelected(image.id)) { + // ctx.strokeRect(image.x, image.y, image.image.width * image.scale, image.image.height * image.scale); + // ctx.fillRect(image.x, image.y, image.image.width * image.scale, image.image.height * image.scale); + // } + // ctx.drawImage(image.svgCanvas.image, image.x, image.y, image.image.width * image.scale, image.image.height * image.scale); + } + } +} + diff --git a/src/handler/handler-global-settings.ts b/src/handler/handler-global-settings.ts new file mode 100644 index 0000000..ceedf2f --- /dev/null +++ b/src/handler/handler-global-settings.ts @@ -0,0 +1,54 @@ +import { throttle } from "lodash"; +import type { GlobalSettings } from "../interface"; +import { BasicEventEmitter } from "../utils/eventEmitter"; +import { A4_HEIGHT, A4_WIDTH } from "../constants"; + +function defaultGlobalSetting(): GlobalSettings { + return { + DPI: 96, + scale: 1, + paperHeight: A4_HEIGHT, + paperWidth: A4_WIDTH, + grid: "none", + landscape: false, + }; +} + +export class GlobalSettingsHandler { + private STORAGE_SETTINGS_KEY = "settings"; + public readonly emitter = new BasicEventEmitter<{ + "settings-update": []; + }>(); + private _settings: GlobalSettings = defaultGlobalSetting(); + private store!: LocalForage; + + async init(store: LocalForage) { + this.store = store; + this._settings = (await this.store.getItem(this.STORAGE_SETTINGS_KEY)) || this._settings; + } + setSettings(settings: Partial) { + const entries = Object.entries(settings); + let emitUpdate = false; + for (const [key, value] of entries) { + const diff = (this._settings as any)[key] !== value; + (this._settings as any)[key] = value; + if (diff) { + emitUpdate = true; + } + } + if (emitUpdate) { + this.emitter.emit("settings-update"); + this.save(); + } + } + async clear() { + this._settings = defaultGlobalSetting(); + await this.store.setItem(this.STORAGE_SETTINGS_KEY, this._settings); + } + get settings() { + return { ...this._settings }; + } + private save = throttle(async () => { + await this.store.setItem(this.STORAGE_SETTINGS_KEY, this._settings); + }, 1000); +} diff --git a/src/handler/handler-tools.ts b/src/handler/handler-tools.ts new file mode 100644 index 0000000..e462903 --- /dev/null +++ b/src/handler/handler-tools.ts @@ -0,0 +1,40 @@ +import { BasicEventEmitter } from "../utils/eventEmitter"; +import type { DefinedTool } from "./interfaces"; +import { TOOL_OBJECT_MOVE, TOOLS } from "./tools"; + +export class ToolHandler { + private STORAGE_TOOL_KEY = "tool"; + public readonly emitter = new BasicEventEmitter<{ + select: [string]; + }>(); + public readonly tools = TOOLS; + private currentTool: string = TOOL_OBJECT_MOVE.key; + private store!: LocalForage; + + async init(store: LocalForage) { + this.store = store; + + const tool = await this.store.getItem(this.STORAGE_TOOL_KEY); + if (tool && this.tools.map((e) => e.key).includes(tool)) { + this.currentTool = tool; + } + } + + setTool(toolKey: string) { + if (this.currentTool !== toolKey) { + this.currentTool = toolKey; + this.emitter.emit("select", toolKey); + this.store.setItem(this.STORAGE_TOOL_KEY, this.currentTool); + } + } + isToolSelected(tool: DefinedTool) { + return this.currentTool === tool.key; + } + async clear() { + this.setTool(TOOL_OBJECT_MOVE.key); + } + + get tool() { + return this.currentTool; + } +} diff --git a/src/handler/image-editor.ts b/src/handler/image-editor.ts new file mode 100644 index 0000000..d2a9085 --- /dev/null +++ b/src/handler/image-editor.ts @@ -0,0 +1,96 @@ +import type { CanvasImageEx, TracerOptions } from "../interface"; + +export class ImageEditor { + public readonly id: string; + private query: () => CanvasImageEx; + onPropsChange: (data: Partial) => void; + private isSelected: () => boolean; + private select: () => void; + constructor( + id: string, + query: () => CanvasImageEx, + onPropsChange: (data: Partial) => void, + isSelected: () => boolean, + select: () => void, + ) { + this.id = id; + this.query = query; + this.onPropsChange = onPropsChange; + this.isSelected = isSelected; + this.select = select; + } + move(x: number, y: number) { + this.onPropsChange({ x, y }); + } + get selected() { + return this.isSelected(); + } + toggleSelected() { + return this.select(); + } + get image() { + return this.query().image; + } + get url() { + return this.query().url; + } + get canvas() { + return this.query().canvas; + } + get blob() { + return this.query().blob; + } + get name() { + return this.query().name; + } + get svg() { + return this.query().svg.data; + } + setName(name: string) { + this.onPropsChange({ name }); + } + get tracerOptions() { + return this.query().svg.options; + } + setTracerSvg(options: TracerOptions) { + const svg = this.query().svg; + this.onPropsChange({ + svg: { + options, + data: svg.data, + scale: svg.scale, + dirty: true, + } + }); + } + get x() { + return this.query().x; + } + set x(x: number) { + this.onPropsChange({ x }); + } + get y() { + return this.query().y; + } + set y(y: number) { + this.onPropsChange({ y }); + } + get scale() { + return this.query().scale; + } + setScale(scale: number) { + this.onPropsChange({ scale }); + } + get visible() { + return this.query().visible; + } + setVisible(visible: boolean) { + this.onPropsChange({ visible }); + } + get locked() { + return this.query().locked; + } + setLock(locked: boolean) { + this.onPropsChange({ locked }); + } +} diff --git a/src/handler/interfaces.ts b/src/handler/interfaces.ts new file mode 100644 index 0000000..1fb1c0a --- /dev/null +++ b/src/handler/interfaces.ts @@ -0,0 +1,21 @@ +import type { JSX } from "react"; +import type { VectraceHandler } from "./vectrace-handler"; +import type { ImageEditor } from "./image-editor"; + +export interface CanvasHandlerProps { + handler: VectraceHandler; +} +export interface ImageEditorProps { + image: ImageEditor; +} + +export interface CanvasHandleImageEditorProps extends CanvasHandlerProps, ImageEditorProps { } + +export type DefinedTool = { + key: K; + name: string; + icon: () => JSX.Element; + defaultSettings: () => T; +}; + +export type ReturnTypeTool> = T extends DefinedTool ? R : never; diff --git a/src/handler/popup.tsx b/src/handler/popup.tsx new file mode 100644 index 0000000..b381540 --- /dev/null +++ b/src/handler/popup.tsx @@ -0,0 +1,149 @@ +import { useEffect, useState, type JSX } from "react"; +import { BasicEventEmitter } from "../utils/eventEmitter"; +import { removeItem } from "../utils/collection"; + +interface PopupType { + type: string; + title: string; + message: string; +} + +export interface PopupAlert extends PopupType { + type: "alert"; +} + +export interface PopupConfirm extends PopupType { + type: "confirm"; +} + +export interface PopupPrompt extends PopupType { + type: "prompt"; + _default?: string; +} + +export interface PopupCustom extends PopupType { + type: "custom"; + element: JSX.Element; +} + +export type PopupTypes = PopupAlert | PopupConfirm | PopupPrompt | PopupCustom; + +export interface PopupResolver extends PopupType { + resolve: (value: any) => void; +} + +function promiseSpy() { + let _resolve!: (value: A | PromiseLike) => void; + let _reject!: (reason?: any) => void; + const promise = new Promise((resolve, reject) => { + _resolve = resolve; + _reject = reject; + }); + return { + promise, + resolve: _resolve, + reject: _reject, + }; +} + +export class Popup { + public readonly emitter = new BasicEventEmitter<{ + update: [PopupResolver[]]; + }>(); + + private list: PopupResolver[] = []; + + private push(ref: PopupResolver) { + this.list.push(ref); + this.emitter.emit("update", [...this.list]); + } + + async alert(title: string, message: string) { + const spy = promiseSpy(); + const ref: PopupResolver = { + type: "alert", + title, + message, + resolve: (value: any) => { + removeItem(this.list, ref); + this.emitter.emit("update", [...this.list]); + spy.resolve(value); + }, + }; + this.push(ref); + return spy.promise; + } + + async confirm(title: string, message: string) { + const spy = promiseSpy(); + const ref: PopupResolver = { + type: "confirm", + title, + message, + resolve: (value: any) => { + removeItem(this.list, ref); + this.emitter.emit("update", [...this.list]); + spy.resolve(value); + }, + }; + this.push(ref); + return spy.promise; + } + + async prompt(title: string, message: string, _default?: string): Promise { + const spy = promiseSpy(); + const ref = { + type: "prompt", + title, + message, + _default, + resolve: (value: any) => { + removeItem(this.list, ref); + this.emitter.emit("update", [...this.list]); + spy.resolve(value); + }, + } as PopupResolver; + this.push(ref); + return spy.promise; + } + + async custom(title: string, message: string, element: JSX.Element) { + const spy = promiseSpy(); + const ref = { + type: "custom", + title, + message, + element, + resolve: (value: any) => { + removeItem(this.list, ref); + this.emitter.emit("update", [...this.list]); + spy.resolve(value); + }, + } as PopupResolver; + this.push(ref); + return spy.promise; + } + + static isAlert(data: PopupType): data is PopupAlert { + return data.type === "alert"; + } + static isConfirm(data: PopupType): data is PopupConfirm { + return data.type === "confirm"; + } + static isPrompt(data: PopupType): data is PopupPrompt { + return data.type === "prompt"; + } + static isCustom(data: PopupType): data is PopupCustom { + return data.type === "custom"; + } + + use() { + const [pendingPopups, setPendingPopups] = useState([...this.list]); + useEffect(() => { + return this.emitter.on("update", (items) => { + setPendingPopups(items); + }); + }, []); + return { pendingPopups }; + } +} diff --git a/src/handler/tools.tsx b/src/handler/tools.tsx new file mode 100644 index 0000000..f699bbb --- /dev/null +++ b/src/handler/tools.tsx @@ -0,0 +1,26 @@ +import { FaArrowsUpDownLeftRight, FaExpand } from "react-icons/fa6"; +import type { DefinedTool } from "./interfaces"; +import type { JSX } from "react"; +import { FaMousePointer } from "react-icons/fa"; + +export const TOOLS: DefinedTool[] = []; + +export function defineTool( + key: K, + name: string, + icon: () => JSX.Element, + defaultSettings: () => T = () => null, +): DefinedTool { + const tool: DefinedTool = { + key, + name, + icon, + defaultSettings, + }; + TOOLS.push(tool); + return tool; +} + +export const TOOL_SELECT = defineTool("select", "Select", () => ); +export const TOOL_CANVAS_MOVE = defineTool("move", "move-canvas", () => ); +export const TOOL_OBJECT_MOVE = defineTool("move-object", "Move object", () => ); diff --git a/src/handler/vectrace-handler.ts b/src/handler/vectrace-handler.ts new file mode 100644 index 0000000..db977db --- /dev/null +++ b/src/handler/vectrace-handler.ts @@ -0,0 +1,511 @@ +import localforage from "localforage"; +import { BasicEventEmitter } from "../utils/eventEmitter"; +import type { CanvasImage, CanvasImageEx, ProjectConfig, TracerOptions } from "../interface"; +import { pushUnique, removeItem } from "../utils/collection"; +import { cloneDeep, isEqual, last, throttle } from "lodash"; +import { ImageEditor } from "./image-editor"; +import { ToolHandler } from "./handler-tools"; +import { GlobalSettingsHandler } from "./handler-global-settings"; +import { Popup } from "./popup"; +import { imageToLaserSVG } from "../svg-tracer"; +import { urlToImage, canvasToBlob, imageToCanvas } from "../utils/image"; +import { loadFileAsDataUrl, makeFilenameSafe } from "../utils/generic"; +import { v4 as uuidv4 } from "uuid"; +import JSZip from "jszip"; +import { downloadBlob } from "../utils/download"; + +declare const __APP_VERSION__: string; +declare const __AUTHOR__: { + name: string; + email: string; + url: string; +}; + +export class VectraceHandler { + public readonly VERSION = __APP_VERSION__; + public readonly AUTHOR = __AUTHOR__; + private readonly STORAGE_IMAGE_KEY = "images"; + private readonly STORAGE_NAME_KEY = "NAME"; + private _projectName = ""; + private readonly STROKE_WIDTH = 2; + public readonly popup = new Popup(); + private store!: LocalForage; + public readonly emitter = new BasicEventEmitter<{ + update: [string, string[]]; + add: [string]; + remove: [string]; + name: [string]; + select: [string[], string | undefined | "*", string | undefined | "*"]; + "array-length": [number]; + ready: []; + }>(); + + private _images: CanvasImage[] = []; + private _renderedImages: CanvasImageEx[] = []; + private _selected: string[] = []; + private _ready = false; + private _toolHandler = new ToolHandler(); + private _globalSettingsHandler = new GlobalSettingsHandler(); + private POOL_RATE = 250; + private queue: { id: string, action: () => Promise | void }[] = []; + private frame!: number; + private destroyed = false; + + private tick = async () => { + if (this.destroyed) return; + const process = this.queue.shift(); + if (process) { + await Promise.resolve(process.action()).catch(console.log); + } + this.frame = window.setTimeout(this.tick, this.POOL_RATE); + }; + + async init(name: string) { + if (this.store) return; + this._projectName = localStorage.getItem(this.STORAGE_NAME_KEY) || ""; + if (!this._projectName) { + await this.promptSetName(); + } + (window as any).c = this; + this.store = localforage.createInstance({ + name, + }); + const [images] = await Promise.all([ + this.store.getItem(this.STORAGE_IMAGE_KEY), + this._toolHandler.init(this.store), + this._globalSettingsHandler.init(this.store), + ]); + + this._images = images || []; + this._renderedImages = await Promise.all(this._images.map((e) => this.renderImage(e))); + this._ready = true; + this.tick(); + this.emitter.emit("ready"); + } + destroy() { + clearTimeout(this.frame); + this.destroyed = true; + this.save(); + } + + async promptSetName() { + const fn = (message: string) => this._ready ? this.popup.prompt("Project name", message) : Promise.resolve(window.prompt(message)); + this.setName(await fn("Enter your project name here") || ""); + + } + async setName(name: string) { + this._projectName = makeFilenameSafe(name); + this.emitter.emit("name", this._projectName); + localStorage.setItem(this.STORAGE_NAME_KEY, this._projectName); + + } + + async clear() { + for (const image of [...this._images]) { + this.deleteImage(image.id); + } + this._images = []; + this._renderedImages = []; + this._toolHandler.clear(); + await Promise.all([ + await this.store.setItem(this.STORAGE_IMAGE_KEY, this._images), + await this._globalSettingsHandler.clear() + ]); + this._selected = []; + localStorage.setItem(this.STORAGE_NAME_KEY, ""); + this.emitter.emit("select", [], "*", "*"); + } + + async exportProject() { + const zip = new JSZip(); + const images = zip.folder("images")!; + + for (const image of this._renderedImages) { + images.file(image.id, image.buffer); + } + const data: ProjectConfig = { + version: this.VERSION, + name: this._projectName, + settings: this._globalSettingsHandler.settings, + tool: this._toolHandler.tool, + images: this._images + }; + zip.file("config.json", new Blob([JSON.stringify(data)], { type: "application/json" })); + const blob = await zip.generateAsync({ type: "blob" }); + downloadBlob(blob, `${this.projectName}.ppd`); + } + + importProject() { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".ppd,application/octet-stream"; + input.addEventListener("change", async () => { + const file = [...(input.files || [])][0]; + if (file) { + const zip = await JSZip.loadAsync(file); + const config = JSON.parse(await zip.files["config.json"].async("string")) as ProjectConfig; + const map = new Map(); + + const entries = Object.keys(zip.files); + for (let i = 0; i < entries.length; i++) { + const name = entries[i]; + + if (!name.startsWith("images/")) + continue; + + const entry = zip.files[name]; + if (entry.dir) + continue; + + const buffer = await entry.async("string"); + + map.set(last(name.split("/"))!, buffer); + } + + const images: CanvasImage[] = []; + for (const img of config.images) { + if (map.has(img.id)) { + images.push({ + id: img.id, + buffer: map.get(img.id)!, + locked: img.locked, + name: img.name, + scale: img.scale, + svg: img.svg, + visible: img.visible, + x: img.x, + y: img.y + }); + } + } + if (await this.popup.confirm("Import", "Are you sure you want to import. Any unsaved changes will be discarded")) { + await this.clear(); + + const renderedImages = await Promise.all(images.map((e) => this.renderImage(e))); + + this._images = images; + this._renderedImages = renderedImages; + for (const add of images) { + this.emitter.emit("add", add.id); + //await new Promise(e => requestAnimationFrame(() => e())); + } + this.emitter.emit("array-length", images.length); + this._globalSettingsHandler.setSettings(config.settings); + this._toolHandler.setTool(config.tool); + this.setName(config.name); + this.save(); + } + } else { + this.popup.alert("Error", "File not selected"); + } + }); + input.click(); + } + + get projectName() { + return this._projectName; + } + + private async renderImage(image: CanvasImage): Promise { + const imageBuffer = await urlToImage(image.buffer); + const canvas = imageToCanvas(imageBuffer); + const blob = await canvasToBlob(canvas); + const url = URL.createObjectURL(blob); + + return { + id: image.id, + name: image.name, + buffer: image.buffer, + x: image.x, + y: image.y, + scale: image.scale, + locked: image.locked, + visible: image.visible, + blob, + url, + canvas, + svg: image.svg, + + image: imageBuffer, + }; + } + + move(id: string, x: number, y: number) { + const { image, renderedImage } = this.getImagesEx(id); + if (image.locked) return; + renderedImage.x = image.x = Math.round(image.x + x); + renderedImage.y = image.y = Math.round(image.y + y); + this.emitter.emit("update", image.id, ["x", "y"]); + this.save(); + } + + async importFile(file: File) { + const buffer = await loadFileAsDataUrl(file); + const imageElement = await urlToImage(buffer); + const options: TracerOptions = {}; + const svg = imageToLaserSVG(imageElement, { + ...options, + scale: 1, + strokewidth: this.STROKE_WIDTH + }); + const f = file.name.split("."); + f.pop(); + const fileName = f.join("."); + + const image: CanvasImage = { + id: uuidv4(), + buffer, + x: 0, + y: 0, + locked: false, + visible: true, + name: fileName, + svg: { + dirty: false, + data: svg, + scale: 1, + options + }, + scale: 1, + }; + const rendered = await this.renderImage(image); + this._images.push(image); + this._renderedImages.push(rendered); + + this.emitter.emit("array-length", this._images.length); + this.emitter.emit("add", image.id); + this.save(); + } + selectNext(x: number, y: number, reverse: boolean, add: boolean) { + const potentials = this._renderedImages.filter((e) => { + return e.visible && !e.locked && + x >= e.x * this.globalSettingsHandler.settings.scale && + x <= e.x * this.globalSettingsHandler.settings.scale + e.image.width * e.scale * this.globalSettingsHandler.settings.scale && + y >= e.y * this.globalSettingsHandler.settings.scale && + y <= e.y * this.globalSettingsHandler.settings.scale + e.image.height * e.scale * this.globalSettingsHandler.settings.scale; + }); + if (potentials.length === 0) { + const copy = [...this._selected]; + this._selected.length = 0; + if (copy.length) { + this.emitter.emit("select", this._selected, "*", "*"); + } + return; + } + + let lastSelectedIndex = -1; + for (let i = 0; i < potentials.length; i++) { + if (this._selected.includes(potentials[i].id)) { + lastSelectedIndex = i; + } + } + if (add) { + potentials.forEach((p) => { + if (!this._selected.includes(p.id)) { + this._selected.push(p.id); + } + }); + } else { + let nextIndex: number; + if (lastSelectedIndex === -1) { + nextIndex = reverse ? potentials.length - 1 : 0; + } else { + if (reverse) { + nextIndex = (lastSelectedIndex - 1 + potentials.length) % potentials.length; + } else { + nextIndex = (lastSelectedIndex + 1) % potentials.length; + } + } + + this._selected = [potentials[nextIndex].id]; + } + + this.emitter.emit("select", this._selected, "*", "*"); + } + + get imagesData() { + return [...this._images]; + } + get imagesRenders() { + return [...this._renderedImages]; + } + private getImageById(id: string) { + return this._renderedImages.find((e) => e.id === id) || null; + } + private getImageByIdEx(id: string) { + const image = this.getImageById(id); + if (image) { + return image; + } else { + throw new Error("Image does not exist!"); + } + } + private getImages(id: string) { + const image = this._images.find((e) => e.id == id); + const renderedImage = this._renderedImages.find((e) => e.id == id); + if (image && renderedImage) { + return { image, renderedImage }; + } else { + return null; + } + } + private getImagesEx(id: string) { + const obj = this.getImages(id); + if (obj) { + return obj; + } else { + throw new Error("Image does not exist!"); + } + } + enqueueForRedrawSvg(id: string) { + this.queue = this.queue.filter(e => e.id !== id); + + this.queue.push({ + id, + action: async () => { + this.redrawSvg(id); + } + }); + } + redrawSvg(id: string) { + const images = this.getImages(id); + if (!images) return; + + const rendered = images.renderedImage; + const base = images.image; + + const scale = rendered.scale; + const options: TracerOptions = { + ...rendered.svg.options, + scale, + strokewidth: this.STROKE_WIDTH + }; + + const prevOptions = cloneDeep(options); + + const svg = imageToLaserSVG(rendered.image, options); + + const dirty = !isEqual(prevOptions, options); + rendered.svg.dirty = dirty; + base.svg.dirty = dirty; + + base.svg.scale = scale; + rendered.svg.scale = scale; + + if (!dirty) { + rendered.svg.options = options; + base.svg.options = options; + } + + rendered.svg.data = svg; + base.svg.data = svg; + + this.emitter.emit( + "update", + id, + ["svg", "svgCanvas"], + ); + this.save(); + } + isSelected(id: string) { + return this._selected.indexOf(id) !== -1; + } + setSelect(id: string, value: boolean) { + if (this.isSelected(id)) { + if (!value) { + removeItem(this._selected, id); + this.emitter.emit("select", this._selected, undefined, id); + } + } else { + if (value) { + if (this.getImageByIdEx(id).visible) { + pushUnique(this._selected, id); + this.emitter.emit("select", this._selected, id, undefined); + } + } + } + } + get selected() { + return this._selected; + } + deleteImage(id: string) { + const images = this.getImages(id); + if (images) { + removeItem(this._images, images.image); + removeItem(this._renderedImages, images.renderedImage); + URL.revokeObjectURL(images.renderedImage.url); + this.emitter.emit("remove", id); + this.emitter.emit("array-length", this._images.length); + this.save(); + } + } + private save = throttle(async () => { + await this.store.setItem(this.STORAGE_IMAGE_KEY, this._images); + }, 1000); + + createImagesWithEmit() { + return this._images.map((e) => this.createImageWithEmit(e.id)); + } + + createImageWithEmit(id: string) { + if (this.getImageById(id)) { + return new ImageEditor( + id, + () => this.getImageByIdEx(id), + async (data) => { + const { image, renderedImage } = this.getImagesEx(id); + + if (image && renderedImage) { + let emitUpdate = false; + const entries = Object.entries(data); + for (const [key, value] of entries) { + + if (image.locked && key !== "locked") { + continue; + } + let v = value; + if (key === "x" || key === "y") { + v = Math.round(v as number); + } + (image as any)[key] = value as any; + const diff = (renderedImage as any)[key] !== v; + (renderedImage as any)[key] = v as any; + if ((key === "svg") && diff) { + this.enqueueForRedrawSvg(id); + } + if (diff) { + emitUpdate = true; + } + } + if (emitUpdate) { + this.emitter.emit( + "update", + id, + entries.map((e) => e[0]), + ); + } + this.save(); + } else { + throw new Error("Image does not exist!"); + } + }, + () => this.isSelected(id), + () => { + this.setSelect(id, !this.isSelected(id)); + }, + ); + } else { + throw new Error(`Image ${id} does not exist!`); + } + } + + get ready() { + return this._ready; + } + get toolHandler() { + return this._toolHandler; + } + get globalSettingsHandler() { + return this._globalSettingsHandler; + } +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..d00b979 --- /dev/null +++ b/src/index.css @@ -0,0 +1,30 @@ + +:root { + --primary-color: #474747; + --secondary-color: #252525; +} +@font-face { + font-family: 'tiny5'; + src: url('./assets/fonts/Tiny5-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} +* { + margin: 0; + padding: 0; + font-family: tiny5; +} + +html,body,#root { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + background: black; + color: white; +} + +#root { + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/src/interface.tsx b/src/interface.tsx new file mode 100644 index 0000000..954aca1 --- /dev/null +++ b/src/interface.tsx @@ -0,0 +1,77 @@ +import type { ImageTracerOptions } from "imagetracerjs"; + +export interface ObjectID { + id: string; +} + +export interface TracerOptions extends ImageTracerOptions { + transparencyThreshold?: number; +} + +export interface SvgData { + svg: string; + normalizedSvg: string; +} + +export interface ProjectConfig { + version: string; + name: string; + settings: GlobalSettings; + tool: string; + images: CanvasImage[]; +} + +export interface GridCell { + col: number; + row: number; + x: number; + y: number; + w: number; + h: number; +} + +export interface CanvasImage extends ObjectID { + name: string; + buffer: string; + x: number; + y: number; + scale: number; + visible: boolean; + locked: boolean; + svg: { + options: TracerOptions; + dirty: boolean; + data: string; + scale: number; + } +} + +export interface CanvasImageEx extends CanvasImage { + image: HTMLImageElement; + blob: Blob; + canvas: HTMLCanvasElement; + url: string; +} + +export const GRID = [ + "none", + "2x2", + "3x3" +] as const; + +export type GridType = typeof GRID[number]; +export interface GlobalSettings { + DPI: number; + scale: number; + paperWidth: number; + paperHeight: number; + landscape: boolean; + grid: GridType; +} + +export interface SVGCanvas { + svg: string; + image: HTMLImageElement, + url: string; + blob: Blob; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..3f8abf1 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,7 @@ +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( + , +); diff --git a/src/nav-bar.tsx b/src/nav-bar.tsx new file mode 100644 index 0000000..0e3e374 --- /dev/null +++ b/src/nav-bar.tsx @@ -0,0 +1,116 @@ +import styled from "styled-components"; +import type { CanvasHandlerProps } from "./handler/interfaces"; +import { useImages } from "./use/use-images"; +import { NumericInputWithLabelTable } from "./components/numeric-labeld-input"; +import { SvgTracerOptions } from "./components/svg-tracer-options"; +import { PaperSizeSelector } from "./components/paper-size-selector"; +import { useSettings } from "./use/use-settings"; +import { PaperOrientation } from "./components/paper-orentation"; +import { PaperGuideSelector } from "./components/paper-guide-selector"; +import { ImportExportButtons } from "./components/download-buttons"; + + +const Bar = styled.div` + width: 100%; + overflow: auto; + height: 110px; + background-color: var(--primary-color); + display: flex; + flex-direction: row; +`; + +const ImageBox = styled.table` + margin: 2px; + padding-right: 4px; +`; + +const SvgOption = styled.table` + margin: 2px; + padding-right: 4px; +`; +const Line = styled.div` + margin: 4px; + height: calc(100% - 8px); + border-right: 1px solid white; +`; + + + +type NumericKeys = { + [K in keyof T]: T[K] extends number ? K : never +}[keyof T] + +function getNumericValue(selected: T[], key: NumericKeys): number { + const disabled = selected.length === 0; + const isMixed = selected.length > 1; + + return disabled + ? 0 + : isMixed + ? selected.reduce((acc, e) => acc + (e[key] as number), 0) / selected.length + : (selected[0][key] as number); +} + +export function NavBar({ handler }: CanvasHandlerProps) { + const { selected } = useImages(handler); + const { settings, setSettings } = useSettings(handler); + + const setValue = (key: string, value: any) => { + selected.forEach((e) => { + e.onPropsChange({ + [key]: value + }); + }); + }; + const style: React.CSSProperties = { + opacity: selected.length ? 1 : 0.2, + pointerEvents: selected.length ? "all" : "none" + }; + + return ( + + + + + + + setValue("scale", value)} + /> + setValue("x", value)} + /> + setValue("y", value)} + /> + + + + + + + + {/* } name="Delete" onClick={async () => { + if (await handler.popup.confirm("Delete project", "Are you sure you want to delete this project?")) { + handler.clear(); + handler.promptSetName(); + } + }} /> + } name="Export" onClick={async () => { + handler.exportProject(); + }} /> + + } name="Import" onClick={async () => { + handler.importProject(); + }} /> */} + + + + ); +} diff --git a/src/render-page.tsx b/src/render-page.tsx new file mode 100644 index 0000000..6331f12 --- /dev/null +++ b/src/render-page.tsx @@ -0,0 +1,41 @@ +import styled from "styled-components"; +import type { CanvasHandlerProps } from "./handler/interfaces"; +import { LayerPanel } from "./components/layer-panel"; +import A4Canvas from "./components/a4-canvas"; +import { Toolbar } from "./components/toolbar"; +import { InfoBar } from "./components/info-bar"; + + +const Content = styled.div` + display: flex; + flex-grow: 1; +`; + +const Left = styled.div` + display: flex; + flex-direction: row; + overflow: auto; + flex-grow: 1; +`; + +const LeftInner = styled.div` + display: flex; + flex-direction: column; + overflow: auto; + flex-grow: 1; +`; + +export function RenderPage({ handler }: CanvasHandlerProps) { + return ( + + + + + + + + + + + ); +} diff --git a/src/styles.ts b/src/styles.ts new file mode 100644 index 0000000..92541ee --- /dev/null +++ b/src/styles.ts @@ -0,0 +1,50 @@ +import styled from "styled-components"; + +export const Icon2525 = styled.button<{ $selected?: boolean }>` + margin: 0; + padding: 5px; + width: 25px; + height: 25px; + border: none; + border-radius: 0; + + cursor: pointer; + background: transparent; + color: white; + + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + + ${(props) => props.$selected && "background: rgba(255, 255, 255, 0.25);"} + &:hover { + background: rgba(255, 255, 255, 0.15); + } +`; + +export const Button = styled.button<{ $active?: boolean }>` + margin: 2px; + padding: 2px; + border: 1px solid white; + border-radius: 0; + + cursor: pointer; + background: ${({ $active }) => $active ? "white" : "var(--primary-color)"}; + color: ${({ $active }) => $active ? "var(--primary-color)" : "white"};; + + display: flex; + flex-direction: column; + align-items: center; + + &:hover { + color: ${({ $active }) => $active ? "rgba(0, 0, 0, 0.25)" : "rgba(255, 255, 255, 0.50)"}; + background: ${({ $active }) => $active ? "rgba(255, 255, 255, 0.85)" : "rgba(255, 255, 255, 0.10)"}; + } + + &:disabled { + color: rgba(255, 255, 255, 0.25); + background: rgba(255, 255, 255, 0.10); + cursor: not-allowed; + } +`; \ No newline at end of file diff --git a/src/svg-tracer.ts b/src/svg-tracer.ts new file mode 100644 index 0000000..8b60aa9 --- /dev/null +++ b/src/svg-tracer.ts @@ -0,0 +1,65 @@ +import ImageTracer from "imagetracerjs"; +import type { TracerOptions } from "./interface"; +import { last } from "lodash"; +import { VERSION } from "./constants"; + + +export function imageToBlackWhiteImageData(img: HTMLImageElement, transparencyThreshold: number = 50): ImageData { + const canvas = document.createElement("canvas"); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + + const ctx = canvas.getContext("2d")!; + ctx.drawImage(img, 0, 0); + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + + for (let i = 0; i < data.length; i += 4) { + const a = data[i + 3]; + + const value = a > transparencyThreshold ? 255 : 0; + data[i] = 0; + data[i + 1] = 0; + data[i + 2] = 0; + data[i + 3] = value; + } + + return imageData; +} + +export function imageToLaserSVG(img: HTMLImageElement, options?: TracerOptions): string { + const bwData = imageToBlackWhiteImageData(img, options?.transparencyThreshold); + + const rawSVG = ImageTracer.imagedataToSVG(bwData, { + scale: 1, + ...options, + viewbox: true, + desc: false, + layering: 0 + }); + const parser = new DOMParser(); + + const doc = parser.parseFromString(rawSVG, "image/svg+xml"); + const svg = doc.querySelectorAll("svg")[0]; + const [_, __, width, height] = svg.getAttribute("viewBox")!.split(" "); + svg.setAttribute("width", width); + svg.setAttribute("height", height); + svg.setAttribute("desc", `Created with pathshop ${VERSION}v`); + + const paths = [...doc.querySelectorAll("path")]; + + // getting global path. Removing it as it creates noise + const longest = paths.sort((a, b) => (a.getAttribute("d") || "").length < (b.getAttribute("d") || "").length ? 1 : -1)[0]; + console.log(paths, longest); + for (const path of paths) { + path.setAttribute("fill", "none"); + path.setAttribute("opacity", "1"); + path.setAttribute("stroke", "#000000"); + if (longest === path) { + path.remove(); + } + } + + return new XMLSerializer().serializeToString(doc); +} diff --git a/src/types/imageTracker.d.ts b/src/types/imageTracker.d.ts new file mode 100644 index 0000000..613152a --- /dev/null +++ b/src/types/imageTracker.d.ts @@ -0,0 +1,154 @@ +declare module "imagetracerjs" { + export interface RGBAColor { + r: number; + g: number; + b: number; + a: number; + } + + export type Palette = RGBAColor[]; + + + export interface ImageDataLike { + width: number; + height: number; + data: Uint8ClampedArray | number[]; + } + + + export interface TracedSegment { + type: "L" | "Q"; + x1: number; + y1: number; + x2?: number; + y2?: number; + } + + export interface TracedPath { + isholepath: boolean; + holechildren: number[]; + segments: TracedSegment[]; + boundingbox: [number, number, number, number]; + prestyle: string; + } + + export interface TracedLayer { + pathnum: number; + paths: TracedPath[]; + } + + + export interface TraceData { + layers: TracedLayer[]; + palette: Palette; + width: number; + height: number; + } + + export interface ImageTracerOptions { + + colorsampling?: 0 | 1 | 2; + + + layering?: 0 | 1; + viewbox?: boolean; + desc?: boolean; + corsenabled?: boolean; + + rightangleenhance?: boolean; + + numberofcolors?: number; + mincolorratio?: number; + colorquantcycles?: number; + ltres?: number; + qtres?: number; + pathomit?: number; + scale?: number; + roundcoords?: number; + lcpr?: number; + qcpr?: number; + blurradius?: number; + blurdelta?: number; + strokewidth?: number; + + + pal?: Palette; + layercontainerid?: string; + } + + + export type OptionPreset = + | "default" + | "posterized1" + | "posterized2" + | "posterized3" + | "curvy" + | "sharp" + | "detailed" + | "smoothed" + | "grayscale" + | "fixedpalette" + | "randomsampling1" + | "randomsampling2" + | "artistic1" + | "artistic2" + | "artistic3" + | "artistic4"; + + export type OptionsOrPreset = ImageTracerOptions | OptionPreset; + + + export function imageToSVG( + imageUrl: string, + callback: (svgString: string) => void, + options?: OptionsOrPreset + ): void; + + + export function imagedataToSVG( + imageData: ImageDataLike, + options?: OptionsOrPreset + ): string; + + + export function imageToTracedata( + imageUrl: string, + callback: (traceData: TraceData) => void, + options?: OptionsOrPreset + ): void; + + export function imagedataToTracedata( + imageData: ImageDataLike, + options?: OptionsOrPreset + ): TraceData; + + + export function appendSVGString( + svgString: string, + parentId: string + ): void; + + + export function loadImage( + url: string, + callback: (canvas: HTMLCanvasElement) => void + ): void; + + + export function getImgdata( + canvas: HTMLCanvasElement + ): ImageData; + + + declare const ImageTracer: { + imageToSVG: typeof imageToSVG; + imagedataToSVG: typeof imagedataToSVG; + imageToTracedata: typeof imageToTracedata; + imagedataToTracedata: typeof imagedataToTracedata; + appendSVGString: typeof appendSVGString; + loadImage: typeof loadImage; + getImgdata: typeof getImgdata; + }; + + export default ImageTracer; +} \ No newline at end of file diff --git a/src/use/use-images.tsx b/src/use/use-images.tsx new file mode 100644 index 0000000..add0d8e --- /dev/null +++ b/src/use/use-images.tsx @@ -0,0 +1,68 @@ +import { compact } from "lodash"; +import { useState, useEffect, useMemo } from "react"; +import type { VectraceHandler } from "../handler/vectrace-handler"; +import { removeItem } from "../utils/collection"; + +export function useImages(handler: VectraceHandler) { + const [images, setImages] = useState(() => handler.createImagesWithEmit()); + + const selected = useMemo( + () => images.filter((e) => handler.isSelected(e.id)), + [images] + ); + + useEffect(() => { + const onAdd = (id: string) => { + setImages((images) => [...images, handler.createImageWithEmit(id)]); + }; + + const onRemove = (id: string) => { + setImages((images) => { + const clone = [...images]; + const image = clone.find((e) => e.id === id); + if (image) removeItem(clone, image); + return clone; + }); + }; + + const onUpdate = (id: string) => { + setImages((images) => { + const clone = [...images]; + const index = clone.findIndex((e) => e.id === id); + if (index !== -1) { + clone[index] = handler.createImageWithEmit(id); + } + return clone; + }); + }; + + const onSelect = (_ids: string[], added: string | undefined, remove: string | undefined) => { + setImages((images) => { + const clone = [...images]; + const indices = compact([added, remove]).map((a) => + clone.findIndex((e) => e.id === a) + ); + for (const index of indices) { + if (index !== -1) { + clone[index] = handler.createImageWithEmit(clone[index].id); + } + } + return clone; + }); + }; + + handler.emitter.on("add", onAdd); + handler.emitter.on("update", onUpdate); + handler.emitter.on("remove", onRemove); + handler.emitter.on("select", onSelect); + + return () => { + handler.emitter.off("add", onAdd); + handler.emitter.off("update", onUpdate); + handler.emitter.off("remove", onRemove); + handler.emitter.off("select", onSelect); + }; + }, []); + + return { images, selected }; +} \ No newline at end of file diff --git a/src/use/use-settings.tsx b/src/use/use-settings.tsx new file mode 100644 index 0000000..4324494 --- /dev/null +++ b/src/use/use-settings.tsx @@ -0,0 +1,23 @@ +import { useEffect, useState } from "react"; +import type { VectraceHandler } from "../handler/vectrace-handler"; +import type { GlobalSettings } from "../interface"; + +export type UseSettings = ReturnType; + +export function useSettings(handler: VectraceHandler) { + const [settings, setSettings] = useState(handler.globalSettingsHandler.settings); + + useEffect(() => { + const update = () => { + setSettings(handler.globalSettingsHandler.settings); + }; + handler.globalSettingsHandler.emitter.on("settings-update", update); + return () => { + handler.globalSettingsHandler.emitter.off("settings-update", update); + }; + }, []); + return { + settings, + setSettings: (settings: Partial) => handler.globalSettingsHandler.setSettings(settings), + }; +} diff --git a/src/use/use-tool.tsx b/src/use/use-tool.tsx new file mode 100644 index 0000000..da96f17 --- /dev/null +++ b/src/use/use-tool.tsx @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; +import type { VectraceHandler } from "../handler/vectrace-handler"; + +export function useTool(handler: VectraceHandler) { + const [currentTool, setCurrentTool] = useState(handler.toolHandler.tool); + + useEffect(() => { + const update = () => { + setCurrentTool(handler.toolHandler.tool); + }; + handler.toolHandler.emitter.on("select", update); + return () => { + handler.toolHandler.emitter.off("select", update); + }; + }, []); + return { + currentTool, + tools: handler.toolHandler.tools, + setTool: (toolKey: string) => handler.toolHandler.setTool(toolKey), + }; +} diff --git a/src/utils/collection.ts b/src/utils/collection.ts new file mode 100644 index 0000000..ffdffef --- /dev/null +++ b/src/utils/collection.ts @@ -0,0 +1,17 @@ +export function removeItem(items: T[], item: T) { + const index = items.indexOf(item); + if (index !== -1) { + items.splice(index, 1); + return true; + } + return false; +} + +export function pushUnique(items: T[], item: T) { + const index = items.indexOf(item); + if (index === -1) { + items.push(item); + return true; + } + return false; +} diff --git a/src/utils/download.ts b/src/utils/download.ts new file mode 100644 index 0000000..af9b388 --- /dev/null +++ b/src/utils/download.ts @@ -0,0 +1,287 @@ +import jsPDF from "jspdf"; +import { MM_TO_INCH } from "../constants"; +import type { VectraceHandler } from "../handler/vectrace-handler"; +import type { CanvasImageEx, GridCell } from "../interface"; +import JSZip from "jszip"; +import { parse, type INode } from "svgson"; + +export function downloadBlob(blob: Blob, filename: string) { + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); +} + +export async function prepareSVG( + handler: VectraceHandler, + imageRender: CanvasImageEx +): Promise { + if (imageRender.svg.scale !== imageRender.scale || imageRender.svg.dirty) { + handler.redrawSvg(imageRender.id); + } + return new Blob([imageRender.svg.data], { type: "image/svg+xml" }); +} + + + +function getGridCells( + paperWidth: number, + paperHeight: number, + grid: string +): GridCell[] { + const cols = grid === "3x3" ? 3 : grid === "2x2" ? 2 : 1; + const rows = grid === "3x3" ? 3 : grid === "2x2" ? 2 : 1; + const cellW = paperWidth / cols; + const cellH = paperHeight / rows; + const cells: GridCell[] = []; + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + cells.push({ + col, + row, + x: col * cellW, + y: row * cellH, + w: cellW, + h: cellH, + }); + } + } + return cells; +} + + +function createCellPdf( + handler: VectraceHandler, + cell: GridCell +): jsPDF { + + + const doc = new jsPDF({ + orientation: cell.w > cell.h ? "landscape" : "portrait", + unit: "mm", + format: [cell.w, cell.h], + }); + + for (const imageObject of handler.imagesRenders) { + if (!imageObject.visible) continue; + + const imgX = imageObject.x - cell.x; + const imgY = imageObject.y - cell.y; + const imgW = imageObject.image.naturalWidth * imageObject.scale; + const imgH = imageObject.image.naturalHeight * imageObject.scale; + + const overlapX = imgX + imgW > 0 && imgX < cell.w; + const overlapY = imgY + imgH > 0 && imgY < cell.h; + if (!overlapX || !overlapY) continue; + + doc.addImage( + imageObject.image, + "PNG", + imgX, + imgY, + imgW, + imgH + ); + } + + return doc; +} + +const gridConfigs: Record = { + "2x2": [1 / 2], + "3x3": [1 / 3, 2 / 3], +}; +export function createPdf(handler: VectraceHandler, withGuides: boolean): jsPDF { + const { DPI, paperHeight, paperWidth } = handler.globalSettingsHandler.settings; + const width = (paperWidth / MM_TO_INCH) * DPI; + const height = (paperHeight / MM_TO_INCH) * DPI; + + const doc = new jsPDF({ + orientation: paperWidth > paperHeight ? "landscape" : "portrait", + unit: "mm", + format: [width, height], + }); + + if (withGuides) { + const setLine = () => { + doc.setDrawColor(64, 64, 64); + doc.setLineWidth(1); + }; + const fractions = gridConfigs[handler.globalSettingsHandler.settings.grid]; + if (fractions) { + setLine(); + fractions.forEach(f => { + doc.line(width * f, 0, width * f, height); + doc.line(0, height * f, width, height * f); + }); + } + } + + for (const imageObject of handler.imagesRenders) { + if (imageObject.visible) { + doc.addImage( + imageObject.image, + "PNG", + imageObject.x, + imageObject.y, + imageObject.image.naturalWidth * imageObject.scale, + imageObject.image.naturalHeight * imageObject.scale + ); + } + } + + return doc; +} + + +export function createSVGDoc(handler: VectraceHandler) { + const { paperWidth, paperHeight, DPI } = handler.globalSettingsHandler.settings; + const width = (paperWidth / MM_TO_INCH) * DPI; + const height = (paperHeight / MM_TO_INCH) * DPI; + return createCellSvg(handler, { + col: 0, + row: 0, + h: height, + w: width, + x: 0, + y: 0, + }); +} + +export async function downloadZip(handler: VectraceHandler): Promise { + const zip = new JSZip(); + const { grid, paperWidth, paperHeight, DPI } = handler.globalSettingsHandler.settings; + + const width = (paperWidth / MM_TO_INCH) * DPI; + const height = (paperHeight / MM_TO_INCH) * DPI; + + const mainFolder = zip.folder(handler.projectName)!; + const images = mainFolder.folder("images")!; + const svgs = mainFolder.folder("svgs")!; + + for (let i = 0; i < handler.imagesRenders.length; i++) { + const render = handler.imagesRenders[i]; + if (render.scale === 1) { + images.file(`[${i + 1}]${render.name}.png`, render.blob); + } else { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d")!; + canvas.width = render.image.naturalWidth * render.scale; + canvas.height = render.image.naturalHeight * render.scale; + ctx.drawImage(render.image, 0, 0, canvas.width, canvas.height); + const blob = new Promise((resolve, reject) => { + canvas.toBlob(data => { + if (data) { + resolve(data); + } else { + reject(new Error("Cannot create blob")); + } + }, "image/png"); + }); + images.file(`[${i + 1}]${render.name}.png`, blob); + } + + const svgBlob = await prepareSVG(handler, render); + svgs.file(`[${i + 1}]${render.name}.svg`, svgBlob); + } + + mainFolder.file("document.pdf", createPdf(handler, false).output("blob")); + if (grid !== "none") { + mainFolder.file("document-grid.pdf", createPdf(handler, true).output("blob")); + } + + const svgBlob = await createCellSvg(handler, { + col: 0, + row: 0, + h: height, + w: width, + x: 0, + y: 0, + }); + mainFolder.file(`document.svg`, svgBlob); + + if (grid !== "none") { + const cells = getGridCells(width, height, grid); + const cellPdfs = mainFolder.folder("cells/pdfs")!; + const cellSvgs = mainFolder.folder("cells/svgs")!; + + for (const cell of cells) { + const label = `cell-r${cell.row}-c${cell.col}`; + + const cellPdf = createCellPdf(handler, cell); + cellPdfs.file(`${label}.pdf`, cellPdf.output("blob")); + + const cellSvgBlob = await createCellSvg(handler, cell); + cellSvgs.file(`${label}.svg`, cellSvgBlob); + } + } + + const content = await zip.generateAsync({ type: "blob" }); + downloadBlob(content, `${handler.projectName}.zip`); +} + +function collectPaths(node: INode): INode[] { + const results: INode[] = []; + if (node.name === "path") results.push(node); + for (const child of node.children ?? []) { + results.push(...collectPaths(child)); + } + return results; +} + +export async function createCellSvg( + handler: VectraceHandler, + cell: GridCell +) { + const pathElements: string[] = []; + for (const imageObject of handler.imagesRenders) { + if (!imageObject.visible) continue; + + if (imageObject.svg.scale !== imageObject.scale || imageObject.svg.dirty) { + handler.redrawSvg(imageObject.id); + } + + const imgX = imageObject.x; + const imgY = imageObject.y; + const imgW = imageObject.image.naturalWidth * imageObject.scale; + const imgH = imageObject.image.naturalHeight * imageObject.scale; + + // Does this image even overlap the cell at all? + const overlaps = + imgX + imgW > cell.x && + imgX < cell.x + cell.w && + imgY + imgH > cell.y && + imgY < cell.y + cell.h; + + if (!overlaps) continue; + + const parsed = await parse(imageObject.svg.data); + const paths = collectPaths(parsed); + + for (const pathNode of paths) { + const d = pathNode.attributes?.d; + if (!d) continue; + + const fill = pathNode.attributes?.fill ?? "none"; + const stroke = pathNode.attributes?.stroke ?? "#000000"; + const strokeWidth = pathNode.attributes?.["stroke-width"] ?? "1"; + + pathElements.push( + `` + + `` + + `` + ); + } + } + + const svgString = ` + + ${pathElements.join("\n ")} + `; + return new Blob([svgString], { type: "image/svg+xml" }); +} \ No newline at end of file diff --git a/src/utils/eventEmitter.ts b/src/utils/eventEmitter.ts new file mode 100644 index 0000000..ebf8edd --- /dev/null +++ b/src/utils/eventEmitter.ts @@ -0,0 +1,72 @@ +import { pushUnique, removeItem } from "./collection"; + +export type EventMap = Record | never; +export type DefaultEventMap = [never]; +export type AnyRest = [...args: any[]]; +export type Args = T extends DefaultEventMap ? AnyRest : K extends keyof T ? T[K] : never; +export type Key = T extends DefaultEventMap ? number : K | keyof T; +export type Listener = T extends DefaultEventMap + ? F + : K extends keyof T + ? T[K] extends unknown[] + ? (...args: T[K]) => void + : never + : never; +export type Listener1 = Listener void>; + +export class BasicEventEmitter> { + private listeners = new Map>, any>(); + + on(eventName: Key, listener: Listener1): () => void { + const arr = this.listeners.get(eventName) || []; + pushUnique(arr, listener); + this.listeners.set(eventName, arr); + return () => { + this.off(eventName, listener); + }; + } + + off(eventName: Key, listener: Listener1): boolean { + const arr = this.listeners.get(eventName) || []; + removeItem(arr, listener); + if (!arr.length) { + this.listeners.delete(eventName); + return true; + } + return false; + } + + emit(eventName: Key, ...args: Args): Promise { + const listeners = this.listeners.get(eventName); + const promises: Promise[] = []; + if (listeners) { + for (let i = 0; i < listeners.length; i++) { + try { + const listener = listeners[i]; + if (listener) { + const result = listener(...args); + if (result instanceof Promise) { + promises.push(result); + } + } + } catch (error) { + console.error(error); + } + } + } + return Promise.all(promises); + } + + listenersCount(type: Key) { + const arr = this.listeners.get(type); + return arr ? arr.length : 0; + } + + removeAllListeners() { + this.listeners.clear(); + } + + removeSpecificListeners(type: Key) { + this.listeners.delete(type); + } +} diff --git a/src/utils/generic.ts b/src/utils/generic.ts new file mode 100644 index 0000000..5d9a385 --- /dev/null +++ b/src/utils/generic.ts @@ -0,0 +1,24 @@ +export function loadFileAsDataUrl(file: File) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.addEventListener("load", () => { + resolve(reader.result as string); + }); + reader.addEventListener("error", () => { + reject(new Error("Failed to read file")); + }); + reader.readAsDataURL(file); + }); +} +export function makeFilenameSafe(input: string): string { + return input + .trim() + // eslint-disable-next-line no-control-regex + .replace(/[<>:"/\\|?*\x00-\x1F]/g, "") + .replace(/\s+/g, "_") + .replace(/\.+$/, "") + .replace(/^\.+/, "") + .replace(/_{2,}/g, "_") + .slice(0, 255) + || "unnamed"; +} \ No newline at end of file diff --git a/src/utils/image.ts b/src/utils/image.ts new file mode 100644 index 0000000..6ea0236 --- /dev/null +++ b/src/utils/image.ts @@ -0,0 +1,33 @@ +export function urlToImage(base64Str: string) { + return new Promise((resolve, reject) => { + const imageSrc = new Image(); + imageSrc.addEventListener("load", () => { + resolve(imageSrc); + }); + imageSrc.addEventListener("error", () => { + reject(new Error("Failed to load image")); + }); + imageSrc.src = base64Str; + }); +} + +export function imageToCanvas(image: HTMLImageElement) { + const canvas = document.createElement("canvas"); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + + const ctx = canvas.getContext("2d"); + if (!ctx) throw new Error("No canvas context"); + + ctx.drawImage(image, 0, 0); + return canvas; +} + +export function canvasToBlob(canvas: HTMLCanvasElement) { + return new Promise((resolve, reject) => { + canvas.toBlob((b) => { + if (!b) reject(new Error("Failed to create blob")); + else resolve(b); + }, "image/png"); + }); +} diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..1d29c88 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "esnext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..d3c52ea --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..569ea3b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vite"; +import pkg from "./package.json"; + +import react from "@vitejs/plugin-react"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + define: { + __APP_VERSION__: JSON.stringify(pkg.version), + __AUTHOR__: JSON.stringify(pkg.author) + } +}); +