commit 83a36c7a229ca869720489bed7ed53fb0d983d82 Author: Konrad Neitzel Date: Fri Oct 9 04:20:40 2026 +0200 First version of Karten App diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8ef306 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +Karten/ +node_modules/ +dist/ +dev-dist/ +*.local +public/ diff --git a/.scratch/erste-version/issues/01-open-the-karte.md b/.scratch/erste-version/issues/01-open-the-karte.md new file mode 100644 index 0000000..61f36d6 --- /dev/null +++ b/.scratch/erste-version/issues/01-open-the-karte.md @@ -0,0 +1,18 @@ +# 01: Open the Karte + +**What to build:** A German app opens north-up at 1:25,000 on the middle of the Übersicht. The person can pan and pinch-zoom. Zoom stops at 36 km by 36 km and at one sheet pixel. A place the Übersicht knows is blank until its Kachel is on the device. + +**Blocked by:** None (can start immediately). + +**Status:** done + +- [x] Opening the app shows the Karte in German, north-up, centered on the middle of the Übersicht at 1:25,000. +- [x] The person can pan the Karte by touch. +- [x] Pinch-zoom stops at the configured ground span, 36 km by 36 km unless that shipped value is changed, and the same span applies on phone and tablet. +- [x] Pinch-zoom stops at one sheet pixel, 1.25 m on the ground. +- [x] The person using the phone cannot edit the 36 km span. +- [x] A place the Übersicht knows, whose Kachel is not in the Cache, is blank. + +## Comments + +The Karte opens north-up at 1:25,000 through the session. Pinch limits are the shipped 36 km span and 1.25 m per pixel. A known Kachel missing from the Cache stays blank. diff --git a/.scratch/erste-version/issues/02-draw-a-kachel-from-the-cache.md b/.scratch/erste-version/issues/02-draw-a-kachel-from-the-cache.md new file mode 100644 index 0000000..3fc5f1f --- /dev/null +++ b/.scratch/erste-version/issues/02-draw-a-kachel-from-the-cache.md @@ -0,0 +1,18 @@ +# 02: Draw a Kachel from the Cache + +**What to build:** One Blatt is prepared into Kacheln at every scale from the furthest view down to the native sheet pixel. A Kachel already in the Cache is drawn. A Kachel the Übersicht knows that is not in the Cache stays blank. The same preparation is the one that will later cover every Blatt. The phone never reads the original sheets. + +**Blocked by:** 01: Open the Karte + +**Status:** done + +- [x] Preparing one Blatt writes its Kacheln at every scale from the 36 km view down to 1.25 m per pixel, and records them in the Übersicht. +- [x] The same preparation accepts the rest of the Blätter on hand. +- [x] A Kachel in the Cache is drawn on the Karte. +- [x] A Kachel the Übersicht knows that is not in the Cache stays blank. +- [x] With no Standort and with Kacheln already in the Cache, opening centers on the middle of those Kacheln at 1:25,000. +- [x] The phone draws only Kacheln from the Cache and the Übersicht. It does not read the original Blätter. + +## Comments + +`prepare/prepare_kacheln.py` checks every Blatt and writes the Übersicht. One Blatt is cut into Kacheln. The session draws a cached Kachel and opens on the middle of the Cache when no Standort is known. diff --git a/.scratch/erste-version/issues/03-fetch-a-missing-kachel.md b/.scratch/erste-version/issues/03-fetch-a-missing-kachel.md new file mode 100644 index 0000000..e9a74d8 --- /dev/null +++ b/.scratch/erste-version/issues/03-fetch-a-missing-kachel.md @@ -0,0 +1,21 @@ +# 03: Fetch a missing Kachel while online + +**What to build:** A Kachel the Übersicht knows, that is not in the Cache, is blank and then downloads while the app is open and the device is online. It stays in the Cache even if the view moves away before the download ends. Offline, it stays blank and the app says it is not on the device. A place the Übersicht does not know stays blank and is never downloaded. Leaving the app pauses the fetch. Opening the app again while online continues a fetch the current view still needs. + +**Blocked by:** 02: Draw a Kachel from the Cache + +**Status:** done + +- [x] While the app is open and online, a known Kachel missing from the Cache is shown blank, then drawn once the download finishes. +- [x] That Kachel remains in the Cache if the person pans away before the download finishes, and is drawn when the view returns to it. +- [x] While offline, a known Kachel missing from the Cache stays blank and the app says, in German, that it is not on the device. +- [x] When the app is later open and online and the view needs that Kachel, the download starts. +- [x] A place the Übersicht does not know stays blank and is never downloaded. +- [x] Only the Kacheln of the current view at the current scale are fetched this way. +- [x] Leaving the app pauses an in-progress fetch. +- [x] Opening the app again while online continues a fetch the current view still needs. +- [x] A finished Kachel is still in the Cache after the app has been left and opened again. + +## Comments + +On-demand fetch runs only while the app is open and online, for the current view at the current scale. A finished Kachel stays in the Cache across leaving the app. Offline, the notice is "Nicht auf dem Gerät." diff --git a/.scratch/erste-version/issues/04-name-a-ziel.md b/.scratch/erste-version/issues/04-name-a-ziel.md new file mode 100644 index 0000000..ca95bb2 --- /dev/null +++ b/.scratch/erste-version/issues/04-name-a-ziel.md @@ -0,0 +1,23 @@ +# 04: Name a Ziel + +**What to build:** The person types one Koordinate as Gitter, Rechtswert, and Hochwert. The Gitter starts as `32U MA`. A valid Ziel is shown in red and the view centers on it at 1:25,000, even while its Kacheln are blank. A bad Koordinate, or one that no Blatt covers, is explained in German and leaves the previous Ziel and the view unchanged. A second Koordinate replaces the first. The Ziel is gone after the page is left. + +**Blocked by:** 01: Open the Karte + +**Status:** done + +- [x] The entry is three fields labelled Gitter, Rechtswert, and Hochwert, and Gitter starts as `32U MA`. +- [x] `32u ma` and `32UMA` are accepted as the same Gitter as `32U MA`. +- [x] Rechtswert and Hochwert must be exactly five digits. Any other length is rejected with a German explanation. +- [x] An impossible Gitter is rejected with a German explanation. +- [x] A rejected Koordinate leaves the previous Ziel and the view unchanged. +- [x] A Koordinate that no Blatt covers, including a hole between Blätter, is rejected and leaves the previous Ziel and the view unchanged. +- [x] A valid Ziel is drawn in red and the view centers on it at 1:25,000, even when its Kacheln are still blank. +- [x] The typed Gitter, Rechtswert, and Hochwert stay visible after a successful entry. +- [x] Entering another Koordinate replaces the Ziel. There is only one Ziel. +- [x] The Koordinate is decoded on the device, with no network required. +- [x] Leaving or reloading the page drops the Ziel. It is not stored. + +## Comments + +The screen offers Gitter, Rechtswert, and Hochwert, starting at `32U MA`. A valid Ziel is red and centers the view at 1:25,000. A rejected Koordinate leaves the previous Ziel. A new session does not keep the Ziel. diff --git a/.scratch/erste-version/issues/05-show-the-standort-and-the-distance.md b/.scratch/erste-version/issues/05-show-the-standort-and-the-distance.md new file mode 100644 index 0000000..4f00b4b --- /dev/null +++ b/.scratch/erste-version/issues/05-show-the-standort-and-the-distance.md @@ -0,0 +1,23 @@ +# 05: Show the Standort and the distance + +**What to build:** The Standort is the blue mark while the app is open. Opening centers on it when it is already known. A late first fix centers the view once, unless the person has already panned or entered a Ziel. After that the mark moves and the view stays put. The person can center on the Standort or the Ziel on purpose, at 1:25,000. When both exist, the straight-line distance is shown, in metres below 1000 m and in kilometres from 1000 m, and it updates as the Standort moves. + +**Blocked by:** 01: Open the Karte. 04: Name a Ziel + +**Status:** done + +- [x] While the app is open, the device position is shown as a blue Standort. +- [x] When a Standort is already known at opening, the view centers on it at 1:25,000. +- [x] When the first Standort arrives after opening, and the person has not panned and has not entered a Ziel, the view centers on it once. +- [x] When the first Standort arrives after the person has panned or entered a Ziel, only the blue mark moves. +- [x] A denied Standort leaves the view on its opening center. +- [x] Later Standort updates move the blue mark and do not move the view. +- [x] No Standort is shown or updated while the app is closed. +- [x] The person can center on the Standort, and on the Ziel, at 1:25,000. +- [x] When both Ziel and Standort exist, the straight-line distance is shown: metres below 1000 m, kilometres from 1000 m. +- [x] The distance updates as the Standort moves, including while the Kacheln under the Ziel are blank. +- [x] The distance is hidden when either the Ziel or the Standort is missing. + +## Comments + +The Standort is the device position while the app is open, drawn blue. The first fix centers the view once unless the person has panned or named a Ziel. Distance is planar in EPSG:25832, in metres below 1000 m and kilometres from 1000 m. diff --git a/.scratch/erste-version/issues/06-download-hessen.md b/.scratch/erste-version/issues/06-download-hessen.md new file mode 100644 index 0000000..719f462 --- /dev/null +++ b/.scratch/erste-version/issues/06-download-hessen.md @@ -0,0 +1,22 @@ +# 06: Download Hessen + +**What to build:** The person starts Hessen. It is marked heruntergeladen immediately, progress is shown, the current view is fetched first, then every other Kachel at every scale. Finished Kacheln stay. Closing the app pauses the rest. Opening it while online continues. After Hessen has arrived, panning and zooming work offline. While Hessen is not heruntergeladen, looking around still fetches only the current view at the current scale. + +**Blocked by:** 03: Fetch a missing Kachel while online + +**Status:** done + +- [x] The screen offers Hessen as the one Gebiet to download. Hessen is every Kachel in the Übersicht, at every scale from the 36 km view down to the native sheet pixel. +- [x] Starting Hessen marks it heruntergeladen before the last Kachel has arrived, and shows how much has arrived. +- [x] Kacheln needed by the current view are fetched before the rest of Hessen. +- [x] Kacheln already in the Cache can be used while the rest of Hessen is still downloading. +- [x] Finished Kacheln stay on the device across a dropped connection and across leaving the app. +- [x] Leaving the app pauses the remaining Hessen download. Opening the app while online continues it. +- [x] Nothing is fetched while the app is closed or the device is offline. +- [x] After Hessen has fully arrived, the person can pan and zoom offline across those Kacheln. +- [x] While Hessen is not heruntergeladen, looking around fetches only the Kacheln of the current view at the current scale. +- [x] The installed app keeps a finished download available offline, including decoding a Koordinate. + +## Comments + +Starting Hessen marks it heruntergeladen immediately and fetches the current view before the rest. Closing the app or losing the network pauses the queue. Koordinate decoding stays on the device. diff --git a/.scratch/erste-version/issues/07-remove-hessen-or-the-whole-cache.md b/.scratch/erste-version/issues/07-remove-hessen-or-the-whole-cache.md new file mode 100644 index 0000000..52910ef --- /dev/null +++ b/.scratch/erste-version/issues/07-remove-hessen-or-the-whole-cache.md @@ -0,0 +1,20 @@ +# 07: Remove Hessen or the whole Cache + +**What to build:** Removing Hessen, or deleting the whole Cache, asks first and names what will be removed. Removing Hessen stops the rest of its download and drops its Kacheln, except a Kachel that also lies in another Gebiet still marked heruntergeladen. Deleting the whole Cache clears every Kachel and every heruntergeladen mark, and does not start Hessen again. Hessen can be started again afterwards. + +**Blocked by:** 06: Download Hessen + +**Status:** done + +- [x] Before Hessen is removed, the app asks in German and names Hessen. Declining leaves the Cache and the heruntergeladen mark unchanged. +- [x] Removing Hessen stops the rest of its download. +- [x] Removing Hessen removes its Kacheln from the Cache. +- [x] A Kachel that also lies in another Gebiet still marked heruntergeladen stays. A Kachel that arrived only because the person looked at it has no protection of its own. +- [x] Before the whole Cache is deleted, the app asks in German and names the whole Cache. Declining leaves everything unchanged. +- [x] Deleting the whole Cache removes every Kachel and clears every heruntergeladen mark. +- [x] After that delete, while online, the current view may fill again by on-demand fetch. Hessen does not start itself. +- [x] Hessen can be started again after it has been removed. + +## Comments + +The screen asks in German and names Hessen, or names the ganzen Cache, before it tells the session to delete. Removing Hessen keeps a Kachel that still lies in another heruntergeladenes Gebiet. Deleting the Cache does not start Hessen again. diff --git a/.scratch/erste-version/spec.md b/.scratch/erste-version/spec.md new file mode 100644 index 0000000..5b18dd3 --- /dev/null +++ b/.scratch/erste-version/spec.md @@ -0,0 +1,146 @@ +# Erste Version der Karte + +Status: done + +## Problem Statement + +The DTK25 Blätter are on the Mac as separate sheets. On a phone or tablet they cannot be panned, zoomed, or used once the computer is left behind. A person in the terrain needs the Karte, one Ziel entered as a Koordinate, and their own Standort when the device can provide it. + +## Solution + +An installable German app for phone and tablet shows the Karte, keeps downloaded Kacheln on the device, and works offline after that. The person types one Koordinate, sees that Ziel on the Karte, and sees their Standort and the straight-line distance when both exist. Hessen is the one Gebiet they can download. The Übersicht ships with the app, so the Karte knows where a Kachel exists before its pixels are on the device. + +## User Stories + +1. As a person using the Karte, I want to open it on my phone or tablet, so that I can read the DTK25 sheets in the terrain. +2. As a person using the Karte, I want the app in German, so that the words match the Koordinate I type. +3. As a person using the Karte, I want to install the app, so that I can open it without an app store. +4. As a person using the Karte, I want the app to start offline after the Kacheln I need are on the device, so that I can work with the computer left behind. +5. As a person using the Karte, I want Koordinate conversion to work offline, so that I can name a Ziel with no network. +6. As a person using the Karte, I want only the Kacheln inside the current view to be drawn, so that the phone does not load the whole Karte at once. +7. As a person using the Karte, I want the Übersicht to be present as soon as the app is installed, so that the Karte knows which Kacheln exist before I download their pixels. +8. As a person using the Karte, I want a Kachel that is already in the Cache to be drawn, so that I see the map I already have. +9. As a person using the Karte, I want a Kachel the Übersicht knows, that is not in the Cache, to appear blank and then download while I am online and the app is open, so that the place I am looking at fills in. +10. As a person using the Karte, I want that download to finish and stay in the Cache even if I pan away before it ends, so that the Kachel is there when I come back. +11. As a person using the Karte, I want a Kachel the Übersicht knows, that is not in the Cache, to stay blank when I am offline, and to be told it is not on the device, so that a blank view is not presented as a finished map. +12. As a person using the Karte, I want that offline Kachel to download the next time the app is open, the device is online, and the Karte needs it, so that I do not have to start it by hand. +13. As a person using the Karte, I want a place the Übersicht does not know to stay blank and never download, so that I am not offered map data that does not exist. +14. As a person using the Karte, I want the view to open at 1:25,000, so that the sheets look like the printed map. +15. As a person using the Karte, I want the Karte to stay north-up, so that the sheets keep their usual orientation. +16. As a person using the Karte, I want to pan with touch, so that I can move across the Karte. +17. As a person using the Karte, I want to pinch-zoom, so that I can see more ground or more detail. +18. As a person using the Karte, I want pinch-zoom to stop at 36 km by 36 km, so that the view stays within a readable span. +19. As a person using the Karte, I want that furthest span to be the same on phone and tablet, so that both show the same ground limit. +20. As a person using the Karte, I want pinch-zoom to stop at one sheet pixel, 1.25 m on the ground, so that I cannot zoom past the detail in the Blätter. +21. As a person preparing the app, I want the 36 km span to be a value shipped with the app, so that I can tune it without preparing the Kacheln again. +22. As a person using the Karte, I want the opening view to center on my Standort when the device already has one, so that I see where I am. +23. As a person using the Karte, I want my Standort drawn in blue, so that I can tell it from the Ziel. +24. As a person using the Karte, I want the blue Standort to move as I move, so that it stays my current position. +25. As a person using the Karte, I want the view to stay where I left it while the Standort moves, so that I can pan and read the Karte. +26. As a person using the Karte, I want the view, when I have no Standort and the Cache already holds Kacheln, to open on the middle of those Kacheln, so that I see map I already have. +27. As a person using the Karte, I want the view, when I have no Standort and the Cache is empty, to open on the middle of the Übersicht, so that I am not looking at an undefined place. +28. As a person using the Karte, I want the first Standort, if it arrives after opening and I have not panned or entered a Ziel, to center the view once, so that the late fix still does the opening center. +29. As a person using the Karte, I want a late first Standort, after I have panned or entered a Ziel, to move only the blue mark, so that my place on the Karte is not yanked away. +30. As a person using the Karte, I want a denied Standort to leave the view on its opening center, so that refusing the device position still shows a Karte. +31. As a person using the Karte, I want to center on my Standort on purpose, at 1:25,000, so that I can return to myself after looking elsewhere. +32. As a person using the Karte, I want to center on the Ziel on purpose, at 1:25,000, so that I can return to it after panning. +33. As a person using the Karte, I want to type a Koordinate as Gitter, Rechtswert, and Hochwert, so that I can name a Ziel in the MGRS form I already use. +34. As a person using the Karte, I want the Gitter field to start as `32U MA`, so that the usual square is ready. +35. As a person using the Karte, I want to type a different Gitter, so that I can name a Ziel on any square the sheets cover. +36. As a person using the Karte, I want `32u ma` and `32U MA` to be the same Gitter, so that letter case does not reject a valid Koordinate. +37. As a person using the Karte, I want `32U MA` and `32UMA` to be the same Gitter, so that a missing space does not reject a valid Koordinate. +38. As a person using the Karte, I want a Rechtswert and a Hochwert of exactly five digits, so that the Koordinate is a 1 m cell. +39. As a person using the Karte, I want a shorter or longer number rejected, with a clear German explanation, so that I know the Koordinate was not accepted. +40. As a person using the Karte, I want an impossible Gitter rejected, with a clear German explanation, so that a bad combination is not drawn. +41. As a person using the Karte, I want the Koordinate I typed to stay visible, so that I can check it against the red mark. +42. As a person using the Karte, I want a valid Ziel drawn in red and the view centered on it at 1:25,000, so that I can see the place I named. +43. As a person using the Karte, I want that centering to happen even while the Kacheln are still blank, so that the Ziel is not refused just because the pixels are still arriving. +44. As a person using the Karte, I want a second Koordinate to replace the first, so that there is only one Ziel. +45. As a person using the Karte, I want a rejected Koordinate to leave the previous Ziel and the view unchanged, so that a mistake does not wipe the place I already had. +46. As a person using the Karte, I want a Koordinate that no Blatt covers to be refused, so that I am not sent to a hole or past the sheets. +47. As a person using the Karte, I want the Ziel to disappear when I leave or reload the page, so that this version does not keep a Ziel I did not ask to store. +48. As a person using the Karte, I want the straight-line distance between Standort and Ziel when both exist, so that I know how far the Ziel is. +49. As a person using the Karte, I want that distance in metres below 1000 m and in kilometres from 1000 m, so that the number stays readable. +50. As a person using the Karte, I want the distance to update as the Standort moves, so that it stays current while I walk. +51. As a person using the Karte, I want the distance hidden when either the Ziel or the Standort is missing, so that I am not shown a distance to nowhere. +52. As a person using the Karte, I want the distance while the Kacheln under the Ziel are still blank, so that the number does not wait for pixels. +53. As a person using the Karte, I want to see Hessen as a Gebiet I can download, so that I can put every current Kachel on the device without visiting each place. +54. As a person using the Karte, I want Hessen to contain every Kachel in the Übersicht, at every scale from the 36 km view down to the native sheet pixel, so that offline zoom has the pixels it needs. +55. As a person using the Karte, I want Hessen marked heruntergeladen when I start it, so that the choice counts before the last Kachel arrives. +56. As a person using the Karte, I want to see how much of Hessen has arrived, so that a long download is not a silent wait. +57. As a person using the Karte, I want the Kacheln of the current view to arrive before the rest of Hessen, so that I can read the place I am looking at while the rest continues. +58. As a person using the Karte, I want to pan and zoom offline after Hessen has fully arrived, so that the Karte works with no network. +59. As a person using the Karte, I want on-demand download, when Hessen is not heruntergeladen, to fetch only the Kacheln the current view needs at the current scale, so that looking around does not pull the whole Gebiet. +60. As a person using the Karte, I want Kacheln that have arrived to stay on the device, so that a dropped connection does not throw away finished work. +61. As a person using the Karte, I want a paused Hessen download to continue the next time the app is open and the device is online, so that I do not start from zero. +62. As a person using the Karte, I want downloads to pause when I leave the app, so that the phone does not keep fetching with the app closed. +63. As a person using the Karte, I want no download while the device is offline, so that the Karte waits until a connection exists. +64. As a person using the Karte, I want to keep using Kacheln already in the Cache while Hessen is still downloading, so that the finished part of the Karte is usable. +65. As a person using the Karte, I want to be asked before Hessen is removed, with Hessen named in the question, so that I do not wipe it by mistake. +66. As a person using the Karte, I want removing Hessen to stop the rest of its download, so that a cancelled Gebiet does not keep fetching. +67. As a person using the Karte, I want removing Hessen to remove its Kacheln from the Cache, so that the space can be given back. +68. As a person using the Karte, I want a Kachel that also lies in another Gebiet still marked heruntergeladen to remain, so that removing one Gebiet does not break another. +69. As a person using the Karte, I want a Kachel that arrived only because I looked at it to be removed with the Gebiet that contains it, when no other heruntergeladenes Gebiet contains it, so that panning is not a second way to keep Hessen. +70. As a person using the Karte, I want to be asked before the whole Cache is deleted, with the whole Cache named in the question, so that I do not wipe every Kachel by mistake. +71. As a person using the Karte, I want deleting the whole Cache to remove every Kachel and clear every heruntergeladen mark, so that the device matches an empty start. +72. As a person using the Karte, I want the current view, after the whole Cache is deleted while I am online, to fill again only by on-demand download, so that Hessen does not start itself again. +73. As a person using the Karte, I want to start Hessen again after I have removed it, so that a delete is reversible by downloading once more. +74. As a person using the Karte, I want the Standort to be the device position only, and only while the app is open, so that this version has one blue mark and no tracking with the app closed. +75. As a person preparing the Karte, I want the Blätter turned into Kacheln once on the Mac, so that the phone never has to read the original sheets. +76. As a person preparing the Karte, I want those Kacheln and the app published as static files, so that the phone can download them with no live map server. + +## Implementation Decisions + +- Follow ADR 0001 and ADR 0002. The app is an installable PWA. The Mac prepares Kacheln once. The phone loads them from static hosting. There is no live map server and no runtime dependency on an external CDN. +- The screen talks only to a Karte session. That session is the one test seam. It accepts a Koordinate, a Standort report, online or offline, whether the app is open, pan and zoom, center-on-Standort, center-on-Ziel, start of a Gebiet, removal of a Gebiet, and deletion of the whole Cache. It exposes the view center and scale, the Ziel, the Standort, the distance, the German notice, the Koordinate text, the heruntergeladen marks and their progress, and the appearance of each Kachel in the current view: drawn, blank and downloading, blank and not on the device, or blank because the Übersicht does not know it. +- Map drawing, the browser position API, and Kachel byte storage sit behind the session. The session does not know about a map library or a browser. +- The UI is German. The field labels are Gitter, Rechtswert, and Hochwert. The Gebiet the screen offers is Hessen. The Ziel mark is red. The Standort mark is blue. +- A Koordinate is parsed as MGRS and then transformed into EPSG:25832. The metres inside the Gitter are not treated as already being EPSG:25832. +- The Gitter is a zone number, a latitude band, and a 100 km square. Surrounding space is ignored. Letter case is ignored. Rechtswert and Hochwert are exactly five digits, including a leading zero. Any other shape, or a combination that is not a real MGRS pair, is rejected. The notice explains the rejection in German. The previous Ziel and the view stay as they were. +- A decoded Koordinate is rejected when no Blatt covers the point, including a hole between Blätter. The previous Ziel and the view stay as they were. +- A valid Ziel replaces any previous Ziel, is kept only in the session, and is dropped when the session ends. The view centers on it at 1:25,000 even when its Kacheln are blank. +- The typed Gitter, Rechtswert, and Hochwert stay on screen after a successful entry. +- The Standort is a position handed to the session by the device adapter, or an explicit denial. The session does not ask the device itself. While the app is open, later positions move the Standort and the distance. They move the view only for the first position, and only when the person has not panned and has not entered a Ziel since opening. +- Distance is the planar distance in EPSG:25832. Below 1000 m it is metres. From 1000 m it is kilometres. It is absent unless both Ziel and Standort exist. +- Opening centers at 1:25,000. With a Standort already known, the center is that position. With none, and with Kacheln in the Cache, the center is the middle of what the Cache holds. With none and an empty Cache, the center is the middle of the Übersicht. A denied Standort uses that same fallback. +- The Karte stays north-up. Pinch-zoom stops at the configured ground span, default 36 km by 36 km, and at a ground resolution of 1.25 m per screen pixel. The span is data shipped with the app. Changing it does not require preparing the Kacheln again. The person using the phone cannot edit it. +- The Übersicht is a static catalog shipped with the app. It lists every Kachel at every prepared scale and the Blatt coverage used to accept or refuse a Koordinate. It contains no pixels. +- Kachel bytes live in the device cache used by the installed app. Heruntergeladen marks and download progress live in the device database. Both survive leaving the app. The Ziel does not. +- Prepared scales run from the configured furthest view down to the native sheet pixel. Each coarser scale covers four times the ground of the next finer one. Hessen is the set of every such Kachel in the Übersicht. +- Starting Hessen marks it heruntergeladen immediately and queues every scale. Kacheln needed by the current view are fetched first, then the rest. The session exposes progress. An on-demand fetch, used when the view needs a Kachel and Hessen is not heruntergeladen, queues only that view at the current scale. +- Fetches run only when the session is told that the app is open and the device is online. Closing the app pauses them. Opening it again continues an unfinished Gebiet and any Kachel the current view still needs. +- Removing a Gebiet stops its remaining fetches, then deletes every cached Kachel that lies in it and lies in no other Gebiet still marked heruntergeladen. Kacheln that arrived by viewing have no extra protection. Deleting the whole Cache deletes every Kachel, clears every heruntergeladen mark, and does not start Hessen again. A later on-demand fetch may refill the current view if the device is online. +- The screen asks for confirmation before it tells the session to remove a Gebiet or delete the Cache. The question names the Gebiet, or names the whole Cache. The session itself applies the delete when asked. Tests drive the session after that confirmation. +- The session's Gebiet rules are general, so overlap can be tested by giving it two Gebiete. The screen in this version offers only Hessen. Usingen and Wehrheim are the overlap examples from ADR 0002, not Gebiete the screen can create. +- The Mac preparation reads the Blätter on hand, checks them against 12 km sheets of 9600 by 9600 pixels at 1.25 m in EPSG:25832, and writes the Kacheln plus the Übersicht. The phone never reads the original sheets. +- The stack is TypeScript and Vite for the app, OpenLayers for drawing, proj4js plus a separate MGRS library for the Koordinate, a service worker for the app shell, Cache Storage for Kachel bytes, and IndexedDB for marks and progress. Python and GDAL prepare the Kacheln. Library versions and the static host are chosen at implementation time. They must satisfy ADR 0001. +- An installed app should request persistent device storage so the Cache is not cleared out from under a finished Hessen download. + +## Testing Decisions + +- A good test drives the Karte session the way the screen will, and asserts what the screen would show. It does not assert how a Kachel is stored, how a library is called, or how a map is drawn. +- The only module under test is the Karte session. There is no prior test suite in this repo. +- Tests supply a fake Übersicht, a fake Cache, a fake clock for progress, and Standort and online reports. They do not open a browser, run GDAL, or draw with OpenLayers. +- Cover the four Kachel appearances, opening centers, the late first Standort, Koordinate acceptance and both kinds of refusal, replacement of the Ziel, distance and its absence, Hessen progress and view-first order, pause and resume, the overlap delete, and deletion of the whole Cache. +- Overlap is tested by handing the session two Gebiete even though the screen offers only Hessen. +- The German confirmation question is specified as screen copy. It is not a second test seam. Session tests start from a delete the screen has already confirmed. + +## Out of Scope + +- Storing the Ziel, and any list of recent coordinates. +- MGRS strings with fewer than five digits on each axis, and any drawing of a larger cell. +- Entering a Ziel as UTM or WGS84. +- Typing a Standort by hand. +- Routing, turn-by-turn guidance, and a Standort update while the app is closed. +- Downloads while the app is closed. +- Defining new Gebiete in the app. The overlap rule is in the session; the screen offers Hessen only. +- A control for the 36 km span on the phone. +- A macOS desktop app, a live map server, and publishing through an app store. + +## Further Notes + +- The sample Koordinate `32U MA 67030 78109` decodes to UTM zone 32, easting 467030 m, northing 5578109 m, and a Blatt in the Übersicht covers it. +- The Blätter on hand are about 1.3 GB. The finest Kacheln are most of the prepared size. The coarser scales add about a third more. +- Hessen publishes these DTK25 sheets as open data, so preparing them for this app is allowed. +- `Erste Planung.md` is the wider note from before this cut. Where it disagrees with this spec, this spec is the first version. +- ADR 0001 and ADR 0002 are accepted. This spec does not reopen them. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b24e2f6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +## Agent skills + +### Issue tracker + +Issues and specs live as markdown files in `.scratch/`. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Five canonical roles, label strings equal to role names (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`. + +### Domain docs + +Single-context: one `CONTEXT.md` and `docs/adr/` at the repo root. See `docs/agents/domain.md`. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..5e71619 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,57 @@ +# KartenApp + +A topographic map of these DTK25 sheets. The user names one Ziel on it and, when the device can provide it, sees their own Standort. + +## Language + +**Karte**: +The topographic map formed by the Blätter on hand. +_Avoid_: Layer, basemap + +**Blatt**: +One DTK25 sheet. A Koordinate is on the Karte only when a Blatt covers that point. +_Avoid_: Map, Sheet + +**Kachel**: +One prepared raster image: a small piece of the Karte at one scale. A Blatt is cut into many Kacheln. +_Avoid_: Blatt, Tile + +**Übersicht**: +The catalog of every Kachel that exists. It is part of the app and contains no pixels. +_Avoid_: Vorschau, Minimap + +**Cache**: +The Kacheln stored on the device. +_Avoid_: Verlauf, Speicher + +**Gebiet**: +A named set of Kacheln. Gebiete may overlap. Hessen is the Gebiet that contains every Kachel currently in the Übersicht. +_Avoid_: Area, Region, Ausschnitt + +**Heruntergeladen**: +A Gebiet the user has chosen to keep. The mark belongs to the Gebiet, not to a single Kachel. +_Avoid_: Gecacht, Im Cache + +**Ziel**: +The one position the user names on the Karte. +_Avoid_: Standort, Wegpunkt + +**Koordinate**: +The MGRS writing of the Ziel: Gitter, Rechtswert, and Hochwert. It is not a bare UTM easting and northing. +_Avoid_: Standort, UTM-Zahlen, GPS, Punkt + +**Gitter**: +The first part of a Koordinate: UTM zone, latitude band, and 100 km square. Example: 32U MA. +_Avoid_: Zone, prefix, part 1 + +**Rechtswert**: +The second part of a Koordinate: five digits, the easting in metres inside the Gitter. Example: 67030. +_Avoid_: Easting, X, part 2 + +**Hochwert**: +The third part of a Koordinate: five digits, the northing in metres inside the Gitter. Example: 78109. +_Avoid_: Northing, Y, part 3 + +**Standort**: +The user's own position, when the device can provide it. +_Avoid_: Koordinate, GPS-Punkt, location diff --git a/Erste Planung.md b/Erste Planung.md new file mode 100644 index 0000000..1e793a7 --- /dev/null +++ b/Erste Planung.md @@ -0,0 +1,96 @@ +# Offline-Karten- und Ortungsanwendung + +Stand: 8. Oktober 2026. Diese Datei dokumentiert den bisherigen Planungsstand. Eine Anwendung wurde noch nicht implementiert. + +## Ziel und Plattformen + +Eine offlinefähige Kartenanwendung für **iOS und Android** soll amtliche DTK25-Karten anzeigen und eingegebene Zielkoordinaten darauf markieren. Die Verteilung soll ohne App Store über eine installierbare **Progressive Web App (PWA)** erfolgen. + +Der Mac dient als Entwicklungsrechner und zur Aufbereitung der Kartendaten. Die ursprünglich erwogene macOS-Desktop-App und deren Python/Qt-Architektur sind verworfen. + +**Ortung bei geöffneter App reicht aus.** Hintergrundortung und Tracking bei gesperrtem Bildschirm sind nicht erforderlich. + +## Wichtigste Funktion: MGRS-Zieleingabe + +Der Nutzer hat die Eingabe von Koordinaten in dieser Form als wichtigste Funktion festgelegt: + +```text +32U MA 67030 78109 +``` + +Die eingegebene Position muss auf der passenden Karte sichtbar sein. MGRS ist ein eigenes Koordinatenformat und darf nicht als einfache UTM-Zahlenangabe interpretiert werden. + +Geplantes Verhalten: + +- Eingaben mit und ohne Leerzeichen akzeptieren. +- Zone, Buchstaben, Ziffernanzahl und die Gültigkeit der Kombination prüfen; ungültige Angaben verständlich zurückweisen. +- MGRS lokal dekodieren und die Position nach EPSG:25832 umrechnen. +- Passendes bereits heruntergeladenes Kartenmaterial auswählen. +- Auf das Ziel zentrieren und eine deutliche Zielmarkierung anzeigen. +- Die eingegebene MGRS-Angabe zur Kontrolle sichtbar lassen. +- Fehlendes Offline-Kartenmaterial ausdrücklich melden, statt eine leere Ansicht als erfolgreiche Kartenanzeige darzustellen. +- Als geplante Ergänzung auch kürzere MGRS-Angaben unterstützen und deren größere Bezugsfläche sichtbar darstellen. Die genaue Konvention für Marker und Flächendarstellung ist noch festzulegen. + +Fünf Ziffern je Achse, wie im Beispiel, beschreiben eine MGRS-Zelle von 1 × 1 Meter. Diese Auflösung ist keine Zusage über die Genauigkeit der zugrunde liegenden Position oder Karte. + +## Weitere Anforderungen + +- Karte mit Touch-Gesten verschieben und zoomen. +- Benötigte Karten automatisch anhand des sichtbaren Ausschnitts beziehungsweise der Zielposition laden. +- Zusätzlich UTM- und WGS84-Koordinaten als Eingabe anbieten. +- Eigenen Standort mit Betriebssystem-/Browserfreigabe anzeigen; manuelle Standortangabe als Alternative. +- Luftlinienentfernung zwischen eigenem Standort und Ziel anzeigen. +- Auf eigenen Standort oder Ziel zentrieren. +- Nach vollständigem Download ohne Internet starten und arbeiten, einschließlich Koordinatenumrechnung. +- Große Kartendaten effizient verarbeiten und den Arbeitsspeicherverbrauch begrenzen. + +Routenberechnung, Navigation entlang von Wegen und Hintergrundtracking gehören nicht zum bisher vereinbarten Umfang. + +## Quelldaten + +Der vorgesehene Kartenordner lautet **`./Karten`**, relativ zum künftigen Projektverzeichnis. Bei der letzten Prüfung war dieser Ordner im aktuellen Arbeitsverzeichnis noch nicht vorhanden. Es wurden noch keine Originaldateien geprüft. + +Nach Angaben des Nutzers liegen für Hessen deutlich mehr als 1 GB Kartenmaterial vor: + +- DTK25, Maßstab 1:25.000. +- TIFF-Bilddateien mit zugehörigen TFW-Dateien. +- Originalkacheln von 12 × 12 km. +- Vorgesehenes Koordinatenreferenzsystem: ETRS89 / UTM Zone 32N, **EPSG:25832**. +- Beispielname: `DTK25_584000_5660000_col.tif` und zugehörige `.tfw`. + +Beispiel einer TFW-Datei aus der bisherigen Unterhaltung: + +```text +1.2500000000 +0.0000000000 +0.0000000000 +-1.2500000000 +584000.6250000000 +5671999.3750000000 +``` + +Die sechs Zeilen enthalten Pixelmaß und affine Georeferenzierung; die letzten beiden Werte bezeichnen den Mittelpunkt des oberen linken Pixels. Die TFW-Datei selbst benennt kein Koordinatenreferenzsystem. + +Für dieses Beispiel ergeben sich bei 12 km Kantenlänge rechnerisch 9.600 × 9.600 Pixel mit 1,25 m Pixelgröße. Die äußeren Grenzen wären Ostwerte 584000 bis 596000 und Nordwerte 5660000 bis 5672000. Diese Annahmen sind beim Import gegen Bildabmessungen und Metadaten zu prüfen. + +## Vorgesehene Architektur + +| Baustein | Festgelegte Richtung | +| --- | --- | +| Anwendung | Smartphone-taugliche PWA für iOS und Android | +| Entwicklung | TypeScript und Vite | +| Kartenanzeige | OpenLayers | +| Kartenprojektion | EPSG:25832 zunächst beibehalten | +| Koordinatenumrechnung | Proj4js plus separate MGRS-Bibliothek | +| Offline-Programm | Service Worker und Cache Storage für Oberfläche und Programmressourcen | +| Offline-Kartendaten | Kleine Rasterkacheln in Cache Storage | +| Katalog und Downloadstatus | IndexedDB | +| Kartenaufbereitung | Python und GDAL auf dem Mac | +| Bereitstellung | Statisches HTTPS-Hosting; kein laufender Kartenserver erforderlich | + +Konkrete Bibliotheksversionen, MGRS-Bibliothek und Hosting-Anbieter sind noch nicht ausgewählt. Alle für den Offlinebetrieb nötigen Bibliotheken, Projektionsdefinitionen und Ressourcen müssen mit der Anwendung verfügbar sein; Laufzeitabhängigkeiten von externen CDNs sind zu vermeiden. + +```text +./Karten: TIFF + TFW + ↓ einmalige Aufbereitung auf dem Mac +Kleine Rasterkacheln in mehreren Auflösungen + Gebietskatalog \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..efb9808 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..6fcba2e --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Karten Website incl. PWA + +Diese Webanwendung stellt die Topografische Karte von Hessen bereit. Hierzu wird Kartenmaterial vom Hessen Geodatenmanagement verwendet, welches die Karten in Form von vielen Einzelbildern mit jeweils 12x12 km bereitstellt. + +Die Webanwendung lässt sich als PWA installieren. Kartenmaterial wird auf dem gerät gespeichert und steht dann auch offline auf dem Handy bereit. + +So das Gerät den Standort bekommt, wird dieser als blauer Punkt dargestellt. +Koordinaten können, z.B. von einem Motorola BOS / TETRA Funkgerät eingegeben um dann als roter Punkt auf der Karte dargestellt zu werden. + +## Karten Sourcen / Nutzungsbedingungen + +Das Kartenmaterial für Hessen wird bereitgestellt von Hessen Geodatenmanagement. + +Verwendet werden die DTK25 Karten. Weitere Informationen zu diesen finden sich unter: +- Produktseite: https://hvbg.hessen.de/landesvermessung/geotopographie/karten/dtk25 +- Download: Unter https://gds.hessen.de/INTERSHOP/web/WFS/HLBG-Geodaten-Site/de_DE/-/EUR/ViewDownloadcenter-Start werden die Downloads angeboten. Unter Landeskartenwerke finden sich die Digitale Topographische Karte (DTK25) zum Download +- Die Open Data Richtlinien finden sich unter https://hvbg.hessen.de/geoinformation/open-data + +### Hinweis zu Aktualisierungen + +Die DTK25 wird im 3-jährigen Zyklus, im sogenannten integrierten Prozess, gemeinsam mit dem Basis-DLM flächendeckend grundaktualisiert. Daneben garantiert die Spitzenaktualität eine kontinuierliche, fallbezogene Bearbeitung von Veränderungen an prägenden topografischen Objekten. Hierzu zählen vor allem neue oder in ihrem Verlauf wesentlich veränderte Verkehrswege sowie Siedlungserweiterungen oder die Errichtung von Windrädern und Funkmasten. + +### Open Data Nutzungsbedingungen + +Jede Nutzung der Geobasisdaten, der zugehörigen Metadaten und der Daten der Immobilienwertermittlung ist ohne Einschränkung oder Bedingung erlaubt. Die bereitgestellten Geobasisdaten und Metadaten dürfen für die kommerzielle und nicht kommerzielle Nutzung insbesondere + +1. vervielfältigt, ausgedruckt, präsentiert, verändert, bearbeitet sowie an Dritte übermittelt werden, +2. mit eigenen Daten und Daten Anderer zusammengeführt und zu neuen selbständigen Datensätzen verbunden werden, +3. in interne und externe Geschäftsprozesse, Produkte und Anwendungen in öffentlichen und nicht öffentlichen Netzwerken eingebunden werden. + + +Wird bei der Nutzung der Geobasisdaten oder Metadaten ein Quellenvermerk beigegeben, ist in diesem auf Veränderungen, Bearbeitungen, neue Gestaltungen oder sonstige Abwandlungen der Geobasisdaten oder Metadaten hinzuweisen. +Eine Ausnahme besteht für personenbezogene Daten. Diese dürfen nur für den Zweck genutzt werden, der das berechtigte Interesse am Zugang zu diesen Daten begründet und zu dessen Erfüllung die betreffenden Daten übermittelt wurden. Eine Weiterverwendung für andere Zwecke ist unzulässig. + diff --git a/docs/adr/0001-offline-pwa-prepared-tiles.md b/docs/adr/0001-offline-pwa-prepared-tiles.md new file mode 100644 index 0000000..a4f0d64 --- /dev/null +++ b/docs/adr/0001-offline-pwa-prepared-tiles.md @@ -0,0 +1,9 @@ +--- +status: accepted +--- + +# Offline PWA with prepared raster tiles + +The Karte is an installable app for phone and tablet, with no app store and no live map server. The Mac turns the DTK25 Blätter into Kacheln once. A Kachel reaches the device because the user downloads a Gebiet, or because the open Karte needs it and the device is online. The open Karte reads only the Kacheln inside the current view. The furthest zoom-out is a ground span shipped in the app configuration, 36 km by 36 km unless that file is changed. The prepared Kacheln cover every scale from that span down to the native sheet pixel. Downloading a Gebiet fetches all of those scales, the current view first. Downloads run only while that app is open and the device is online; leaving the app pauses them, and opening it again continues them. Which of those Kacheln a delete keeps is [ADR 0002](0002-cached-tiles-follow-downloaded-gebiete.md). + +A macOS desktop app was rejected. Serving the original GeoTIFFs from the Mac while the phone is on the same network was rejected, because the Karte has to work after the download, with that computer left behind. diff --git a/docs/adr/0002-cached-tiles-follow-downloaded-gebiete.md b/docs/adr/0002-cached-tiles-follow-downloaded-gebiete.md new file mode 100644 index 0000000..aaae0b9 --- /dev/null +++ b/docs/adr/0002-cached-tiles-follow-downloaded-gebiete.md @@ -0,0 +1,9 @@ +--- +status: accepted +--- + +# Cached Kacheln follow the Gebiete still marked downloaded + +A Gebiet is marked heruntergeladen when its download starts, before every Kachel has arrived. Deleting it stops the rest of that download, then removes every Kachel of that Gebiet from the Cache, except a Kachel that also lies in another Gebiet still marked heruntergeladen. Deleting the whole Cache removes every Kachel and clears every heruntergeladen mark. On-demand Kacheln have no protection of their own. Usingen overlapping Wehrheim is the test: after Wehrheim is removed, a Kachel in both stays because Usingen is still heruntergeladen. After Hessen is removed, a Kachel stays only where a smaller Gebiet such as Usingen or Wehrheim is still heruntergeladen. + +The alternative was to remember which download fetched each Kachel, and to keep every on-demand Kachel until the whole Cache is deleted. That was rejected: removing Hessen clears Hessen even when those Kacheln arrived by panning. diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 0000000..3524904 --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,51 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the codebase. + +## Before exploring, read these + +- **`CONTEXT.md`** at the repo root, or +- **`CONTEXT-MAP.md`** at the repo root if it exists: it points at one `CONTEXT.md` per context. Read each one relevant to the topic. +- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src//docs/adr/` for context-scoped decisions. + +If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. + +## File structure + +Single-context repo (most repos): + +``` +/ +├── CONTEXT.md +├── docs/adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +Multi-context repo (presence of `CONTEXT-MAP.md` at the root): + +``` +/ +├── CONTEXT-MAP.md +├── docs/adr/ ← system-wide decisions +└── src/ + ├── ordering/ + │ ├── CONTEXT.md + │ └── docs/adr/ ← context-specific decisions + └── billing/ + ├── CONTEXT.md + └── docs/adr/ +``` + +## Use the glossary's vocabulary + +When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. + +If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`). + +## Flag ADR conflicts + +If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 0000000..0209a19 --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,30 @@ +# Issue tracker: Local Markdown + +Issues and specs for this repo live as markdown files in `.scratch/`. + +## Conventions + +- One feature per directory: `.scratch//` +- The spec is `.scratch//spec.md` +- Implementation issues are one file per ticket at `.scratch//issues/-.md`, numbered from `01`, never a single combined tickets file +- Triage state is recorded as a `Status:` line near the top of each issue file (see `triage-labels.md` for the role strings) +- Comments and conversation history append to the bottom of the file under a `## Comments` heading + +## When a skill says "publish to the issue tracker" + +Create a new file under `.scratch//` (creating the directory if needed). + +## When a skill says "fetch the relevant ticket" + +Read the file at the referenced path. The user will normally pass the path or the issue number directly. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a file with one **child** file per ticket. + +- **Map**: `.scratch//map.md` (the Notes / Decisions-so-far / Fog body). +- **Child ticket**: `.scratch//issues/NN-.md`, numbered from `01`, with the question in the body. A `Type:` line records the ticket type (`research`/`prototype`/`grilling`/`task`); a `Status:` line records `claimed`/`resolved`. +- **Blocking**: a `Blocked by: NN, NN` line near the top. A ticket is unblocked when every file it lists is `resolved`. +- **Frontier**: scan `.scratch//issues/` for files that are open, unblocked, and unclaimed; first by number wins. +- **Claim**: set `Status: claimed` and save before any work. +- **Resolve**: append the answer under an `## Answer` heading, set `Status: resolved`, then append a context pointer (gist + link) to the map's Decisions-so-far in `map.md`. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 0000000..b716855 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,15 @@ +# Triage Labels + +The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker. + +| Label in mattpocock/skills | Label in our tracker | Meaning | +| -------------------------- | -------------------- | ---------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | +| `needs-info` | `needs-info` | Waiting on reporter for more information | +| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | +| `ready-for-human` | `ready-for-human` | Requires human implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table. + +Edit the right-hand column to match whatever vocabulary you actually use. diff --git a/index.html b/index.html new file mode 100644 index 0000000..6d84af4 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Karte + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1a9f4ed --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7029 @@ +{ + "name": "karten", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "karten", + "dependencies": { + "mgrs": "^2.2.0", + "ol": "^10.11.0", + "proj4": "^2.22.0" + }, + "devDependencies": { + "typescript": "^7.0.2", + "vite": "^8.3.4", + "vite-plugin-pwa": "^2.0.0", + "vitest": "^5.0.3" + } + }, + "node_modules/@apideck/better-ajv-errors": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz", + "integrity": "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonpointer": "^5.0.1", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@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-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.10.tgz", + "integrity": "sha512-/YY9soQuWa8TURtHXHT+GbklpbAA+dZErxFGHIr6qgDg96xuGWrTAs1+MfjgVJPF0YLQsOVsOYs8lhgsPbOinQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.5.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.8.tgz", + "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "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/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@petamoriken/float16": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz", + "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", + "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.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", + "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.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", + "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.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", + "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.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", + "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.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", + "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.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", + "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.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^7.0.3", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.2.tgz", + "integrity": "sha512-gjNDUwMswogkw3fNqV00u54uZlgmInuw5EU/1TeqQ6K0bEagoGvqjPdKzy112lKyhe7PkGudZ/kSQHsc9qroIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.2.tgz", + "integrity": "sha512-YkAW8q7p5SCLSaGWaUkmsRnBEb3MGA1pB5nI/8k0lAK5fZsQKRBqkdR1h4UU9p8u3c7ZGdUF2Zcx7mK+E/pUKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.2.tgz", + "integrity": "sha512-hNmuIHv+LnrP/+q/44vsmIMWfoh7X2YXOp5FLtjGuvqN52VdtFpOpnshTc1k28tWv1wE/0D5gLjsozBUowEGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.2.tgz", + "integrity": "sha512-B0t5CoxgF2KL7R5FHAsyN8yVEQCSIkh4XVs8MCDjuBLNjEa8l3zd5tVKaFMv53LfUtHs9p2rnqeavPTOunV+Ww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.2.tgz", + "integrity": "sha512-QQdYnfwrPmc1DoPlddjFvQPzUHFbStKXsHrG6EBYsWHhBVUCjsCRdcfGGXGAvhDpDtTzKK4vy1UrGFJ45rQaiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.2.tgz", + "integrity": "sha512-1xRxtGWif6BB58VSfMavi0Yfs2M4x6CtRp4o7ScY4W7AGlgvf6GSPKdDNdw7TX3tMiQwZY5W/mvyrOJTq/7AKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.2.tgz", + "integrity": "sha512-CXFLEt384BUwwOLR9DURWNHl1bQOVLd1ibOee+yKBJm/fZtvrSAOZ0JbxjAxszVMIovYMwFMS+oP+Q1aPcx8Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.2.tgz", + "integrity": "sha512-T1dorY0WHm5mMNpMHFl5rYRZ02gLSZcr6J45r66tYb3S3R3DyIRGiGWVdLtF+zOQjratNJaObl9jYumueRiL1A==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.2.tgz", + "integrity": "sha512-ia6bfaCIc0ldp6bvs9oEkFrJVJKNUMoiDF0+LBZsQAZg+vE0znXnzGqH+ew/FYswE30cruAWPTO3dPw7keaVkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.2.tgz", + "integrity": "sha512-mB/gwfBfXtT0EBpbSssqKCqMFuX8b21Zy2TGoIhehSJEssZ5ksybxymwp1BkEM5Nczh/AAxpmh5q+MkhtundGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.2.tgz", + "integrity": "sha512-VPd0a+XlHoNRI9jIPRHww2UKhDkD6XjbtcKYXZijAnhJS1jLLbiNtVl6a5LyEfNN+eyiyWcJOZXPzBo69oTsew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.2.tgz", + "integrity": "sha512-10N1I6oBpERqzGtHKcCLrEpHlbXSBbMK9TURHvqgWbIFZuU3oJROxJ0H2nHfq96TVI0XJHQ9qzolJMFd2tz0LA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.2.tgz", + "integrity": "sha512-dhJrLVZ3wNNhwCmy9/VTg6Pq6J8a8eZ0n15dVRlbSWcDb+8V/1mDfbnvis+vt8W6Pbxzv6gUbTEl4piOT3bbUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.2.tgz", + "integrity": "sha512-tPd2QuwxkTxgju5eARyb3RdgBWWi/O9dn/NFH0VkvGbLzoQeXrH6qMABtW8IODb5lUeBzB3sy2U7qmI4MtQ72g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.2.tgz", + "integrity": "sha512-njtDdPvSQWI9CDhIPPxsiEMeHayCfu7wwcbSQ7c2kYPhGVviL+WGVhq6Jfj/b+VuhSTkFuQ0z4ZFbFjkWwBFmg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.2.tgz", + "integrity": "sha512-EbOBo3dqCj9ecEV9Z7zvulB6AieMP2ztnKQYGVOSN0pzIyOu+3JH2YJZnLmbPeVvfn6yP6Qi7NbBM27EzGbj2w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.2.tgz", + "integrity": "sha512-8wLt2Q5n4qmfiE2Gk8V6KNLSf+oJuBX7Kdw5joFDtnZIZvirS0hLzBc79OiJjV71oYDmBUZLNy6pDgvylo4qiQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.2.tgz", + "integrity": "sha512-aIChbkhC4d33bxkVP0LSi2/2lUSGRXE6k4Qa0G7tgpgTOK7kxHTixmx5qC3SKUJ6yUMznHBtQoWaG4QMqLUnhw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.2.tgz", + "integrity": "sha512-gQ+TkVZRfjZVcl/UOAxxfWw+sHjzIXoieNwxEyJlD3tRBmBhR8ZDq11w+QEgQDcpslEB+fhN/OEpaTsDoIIHhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.2.tgz", + "integrity": "sha512-sUaJpMKS2JBCvUk8fYimG7aMq2jHVT4kw/uVHxPolKLrMmkKy0KXirmZyZehdNpL7PffqAlPAWUELQcTiD8ChA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.2.tgz", + "integrity": "sha512-j1RdJWb0CrlSuwt12OPjLucPlwfLtA/KMOKVvh5rXSJEtl/WDoIGR3CSykK/jLGTo9RqNfW5Ub6kY5Z5ccYl9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.2.tgz", + "integrity": "sha512-i3r04aHCIY3eggTBLUp/4IxiBjDpAgQaK89Ue2nMJ2MKuIXX5JfoeZjHw+kPjGGNVDlhi8FT8xzuGMJZe3aehg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.2.tgz", + "integrity": "sha512-kCQ15h/uwNMGdN8gjXBZGf+aGE+ODzHfbi90/cAm4+Q89WoMiLODNoj8HoGZmYx87HPxfu5CBNPsZ+ZG4uIAoA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.2.tgz", + "integrity": "sha512-yYAuySSs+0X2gSMJPw/Q4jHsHZHyMq1dpAE+XnN790Jg1WuReUAD/+j+e4vuDvS4PXJ4PA4NCzTR4OYtDf18/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.2.tgz", + "integrity": "sha512-JY1rQdWOJrKutORqKO1uzrHXV86jFiQzfLFKx2s8S1CjLXL9fC5LOIvk0q7Z7PXJvsByt+sdoE1Q2a9gBr4gLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@trickfilm400/rollup-plugin-off-main-thread": { + "version": "3.0.0-pre1", + "resolved": "https://registry.npmjs.org/@trickfilm400/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-3.0.0-pre1.tgz", + "integrity": "sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.10", + "json5": "^2.2.3", + "magic-string": "^0.30.21", + "string.prototype.matchall": "^4.0.12" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@trickfilm400/rollup-plugin-off-main-thread/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/rbush": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-4.0.0.tgz", + "integrity": "sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==", + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitest/mocker": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz", + "integrity": "sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.3", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz", + "integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@zarrita/storage": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@zarrita/storage/-/storage-0.2.0.tgz", + "integrity": "sha512-855ZXqtnds7spnT8vNvD+MXa3QExP1m2GqShe8yt7uZXHnQLgJHgkpVwFjE1B0KDDRO0ki09hmk6OboTaIfPsQ==", + "license": "MIT", + "dependencies": { + "reference-spec-reader": "^0.2.0", + "unzipit": "2.0.0" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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==", + "dev": true, + "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/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==", + "dev": true, + "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/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "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/baseline-browser-mapping": { + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "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.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/caniuse-lite": { + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", + "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/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "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-compat": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "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==", + "dev": true, + "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/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "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/earcut": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.2.4.tgz", + "integrity": "sha512-FHc6yNpnTORB0tM5kmZ03iD2mDuNakDE2lvf4Hw1XkfxtEHIGVi01B/GqUSwx+NcC1WGjidAfpTXzeALtPtfpg==", + "license": "ISC" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.451", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.451.tgz", + "integrity": "sha512-vUNKPrUzw8ByEMOxloq0a71iD/mPuPxVkbDm3G5YhnxTzOw/I6qAg2MUS0cYj/c267lWmw26yW4Vv7foxTxPLg==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "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-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "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==", + "dev": true, + "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-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.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==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "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.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/geotiff": { + "version": "3.1.0-beta.0", + "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-3.1.0-beta.0.tgz", + "integrity": "sha512-wbVwOaQYuN+ywly5NoMXn37nHslwkQg5EFHqMeUTtjDACrSag0EEVlFsc+2DK3Pzmmp6XzJW2RTZOamPsBM6ng==", + "license": "MIT", + "dependencies": { + "@petamoriken/float16": "^3.9.3", + "lerc": "^3.0.0", + "pako": "^2.0.4", + "parse-headers": "^2.0.2", + "quick-lru": "^6.1.1", + "web-worker": "^1.5.0", + "xml-utils": "^1.10.2", + "zstddec": "^0.2.0" + }, + "engines": { + "node": ">=10.19" + } + }, + "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==", + "dev": true, + "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-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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-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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "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-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "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/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lerc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", + "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==", + "license": "Apache-2.0" + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "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.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "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.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "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/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "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/magic-string": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mgrs": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-2.2.0.tgz", + "integrity": "sha512-BHeewhEJ1n2MOFKvGXFPMxgfczWArFGltBYSZ+JTzlNfHSwJ7gEsXGXFjaU619yLmpmtyoLHu+d8Sd6ACsd0QA==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "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/node-releases": { + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/numcodecs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/numcodecs/-/numcodecs-0.3.2.tgz", + "integrity": "sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==", + "license": "MIT", + "dependencies": { + "fflate": "^0.8.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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ol": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/ol/-/ol-10.11.0.tgz", + "integrity": "sha512-OJ+lrx7kx/1O7Xx18Ff9QkVnwZIEL0OuhJQSy2sYgzzDvyZ2qHvuZEGAajbSstUyBwaS1OtQ4RWnY84tRMDdqg==", + "license": "BSD-2-Clause", + "dependencies": { + "@types/rbush": "4.0.0", + "earcut": "^3.0.0", + "geotiff": "^3.1.0-beta.0", + "pbf": "5.1.2", + "rbush": "^4.0.0", + "zarrita": "^0.7.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/openlayers" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", + "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/pbf": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-5.1.2.tgz", + "integrity": "sha512-mnvGdvOrIvJOBGUEdGkrVXjN8E/VkIJCkf2eS1DH2yv82ORUlLttmDt0rWY38yYZmVwciZwBUvHM20qxBZf40w==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "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.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "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.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proj4": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.22.0.tgz", + "integrity": "sha512-MVvH09QrYm3A235rcX39WDiCKanrBiblu28l0KYIn/B2DofmYhvP/LLS43kE66jNDfx/P1l/nah3JmsAq8UgbQ==", + "license": "MIT", + "dependencies": { + "mgrs": "1.0.0", + "wkt-parser": "^1.5.5" + }, + "funding": { + "url": "https://github.com/sponsors/ahocevar" + }, + "peerDependencies": { + "geotiff": "*" + }, + "peerDependenciesMeta": { + "geotiff": { + "optional": true + } + } + }, + "node_modules/proj4/node_modules/mgrs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz", + "integrity": "sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA==", + "license": "MIT" + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", + "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, + "node_modules/rbush": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz", + "integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==", + "license": "MIT", + "dependencies": { + "quickselect": "^3.0.0" + } + }, + "node_modules/reference-spec-reader": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz", + "integrity": "sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==", + "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==", + "dev": true, + "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/regenerate": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.5.0.tgz", + "integrity": "sha512-1s+CxXjJ71i7LNVy8nYwg47XV4y6lAnOEhsMtSKq3WpmPfhl9LuKlTqKIn/3yUpE8lBz+W+TT88Bxyhnr0U4yw==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.3.0.tgz", + "integrity": "sha512-9ns8odR8e9q3otKeHj3DE80jZYKg6zVeZKE+zQmRKXFoVUcuiTGeO6iusDhPRoIQQPVPRvH+SfBhrTud4eczuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "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/regexpu-core": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.5.3.tgz", + "integrity": "sha512-E8VgUoSK+LMQrLboDxavm76MTBGrQhx9RiNuomRmYnZ10Uw12HrI8fkxOOncktb2hBpzIZ9SV/347euQHG5z+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.5.0", + "regenerate-unicode-properties": "^10.3.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.4.tgz", + "integrity": "sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.13", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.13.tgz", + "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.17.0", + "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-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/rolldown": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.153.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" + } + }, + "node_modules/rollup": { + "version": "4.64.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.2.tgz", + "integrity": "sha512-RxXm0XVVV1hFI3dYe12ruaOgsYQ725lUcUWeMSQqkF9tTxVErY+fy2Ets6vWhItL9MZMSNZGbvZFc7rH/VHp2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.2", + "@rollup/rollup-android-arm64": "4.64.2", + "@rollup/rollup-darwin-arm64": "4.64.2", + "@rollup/rollup-darwin-x64": "4.64.2", + "@rollup/rollup-freebsd-arm64": "4.64.2", + "@rollup/rollup-freebsd-x64": "4.64.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.2", + "@rollup/rollup-linux-arm-musleabihf": "4.64.2", + "@rollup/rollup-linux-arm64-gnu": "4.64.2", + "@rollup/rollup-linux-arm64-musl": "4.64.2", + "@rollup/rollup-linux-loong64-gnu": "4.64.2", + "@rollup/rollup-linux-loong64-musl": "4.64.2", + "@rollup/rollup-linux-ppc64-gnu": "4.64.2", + "@rollup/rollup-linux-ppc64-musl": "4.64.2", + "@rollup/rollup-linux-riscv64-gnu": "4.64.2", + "@rollup/rollup-linux-riscv64-musl": "4.64.2", + "@rollup/rollup-linux-s390x-gnu": "4.64.2", + "@rollup/rollup-linux-x64-gnu": "4.64.2", + "@rollup/rollup-linux-x64-musl": "4.64.2", + "@rollup/rollup-openbsd-x64": "4.64.2", + "@rollup/rollup-openharmony-arm64": "4.64.2", + "@rollup/rollup-win32-arm64-msvc": "4.64.2", + "@rollup/rollup-win32-ia32-msvc": "4.64.2", + "@rollup/rollup-win32-x64-gnu": "4.64.2", + "@rollup/rollup-win32-x64-msvc": "4.64.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.2.tgz", + "integrity": "sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smob": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", + "integrity": "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz", + "integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "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-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "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==", + "dev": true, + "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/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tinybench": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.1.tgz", + "integrity": "sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "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/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "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==", + "dev": true, + "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/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-2.0.0.tgz", + "integrity": "sha512-DVeVIWUZCAQPNzm5sB0hpsG1GygTTdBnzNtYYEpInkttx5evkyqRgZi6rTczoySqp8hO5jHVKzrH0f23X8FZLg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "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/vite": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" + }, + "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.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-pwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-2.0.0.tgz", + "integrity": "sha512-6G9dsPKT9QKYpeTuTLJM28zZL3HsXUTUM/QUZBEgQk2jbfrPJDq2i8gTewAKqi1c9Mt6MiidrL4SFI+HthiQMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.6", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.10", + "workbox-build": "^7.4.1", + "workbox-window": "^7.4.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^1.0.0 || ^2.0.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "workbox-build": "^7.4.1", + "workbox-window": "^7.4.1" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.3.tgz", + "integrity": "sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.3", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "^6.1.4", + "tinyexec": "^1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.3", + "@vitest/browser-preview": "5.0.3", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.3", + "@vitest/coverage-v8": "5.0.3", + "@vitest/ui": "5.0.3", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "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/why-is-node-running": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", + "dev": true, + "license": "MIT", + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=20.11" + } + }, + "node_modules/wkt-parser": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.5.6.tgz", + "integrity": "sha512-cqHU3lzGt/gt2OqIORP0uVy5yeOX43WABmgFkmJsmcqH3HhQo9PiJG6ftytwGKmpQUbegeX7+pQc2BuNCRfcrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ahocevar" + } + }, + "node_modules/workbox-background-sync": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.1.tgz", + "integrity": "sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.1.tgz", + "integrity": "sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-build": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.1.tgz", + "integrity": "sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-replace": "^6.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "eta": "^4.5.1", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "pretty-bytes": "^5.3.0", + "rollup": "^4.53.3", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.1", + "workbox-broadcast-update": "7.4.1", + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-google-analytics": "7.4.1", + "workbox-navigation-preload": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-range-requests": "7.4.1", + "workbox-recipes": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1", + "workbox-streams": "7.4.1", + "workbox-sw": "7.4.1", + "workbox-window": "7.4.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.1.tgz", + "integrity": "sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-core": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.1.tgz", + "integrity": "sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.1.tgz", + "integrity": "sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.1.tgz", + "integrity": "sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-background-sync": "7.4.1", + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.1.tgz", + "integrity": "sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-precaching": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.1.tgz", + "integrity": "sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.1.tgz", + "integrity": "sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-recipes": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.1.tgz", + "integrity": "sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-routing": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.1.tgz", + "integrity": "sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-strategies": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.1.tgz", + "integrity": "sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-streams": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.1.tgz", + "integrity": "sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1" + } + }, + "node_modules/workbox-sw": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.1.tgz", + "integrity": "sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-window": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.1.tgz", + "integrity": "sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.1" + } + }, + "node_modules/xml-utils": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", + "integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==", + "license": "CC0-1.0" + }, + "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/zarrita": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/zarrita/-/zarrita-0.7.5.tgz", + "integrity": "sha512-ZriOSEne+Wf2iuM0yjDZvUKpHNlug3dO7qJ6mAxXoOgsYV18DPOkAkwB9e0rf21bHcKcOffFuwoZwn5g6pvUeA==", + "license": "MIT", + "dependencies": { + "@zarrita/storage": "^0.2.0", + "numcodecs": "^0.3.2" + } + }, + "node_modules/zstddec": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz", + "integrity": "sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==", + "license": "MIT AND BSD-3-Clause" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7551a1 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "karten", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview", + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "mgrs": "^2.2.0", + "ol": "^10.11.0", + "proj4": "^2.22.0" + }, + "devDependencies": { + "typescript": "^7.0.2", + "vite": "^8.3.4", + "vite-plugin-pwa": "^2.0.0", + "vitest": "^5.0.3" + } +} diff --git a/prepare/__pycache__/prepare_kacheln.cpython-314.pyc b/prepare/__pycache__/prepare_kacheln.cpython-314.pyc new file mode 100644 index 0000000..c2b9957 Binary files /dev/null and b/prepare/__pycache__/prepare_kacheln.cpython-314.pyc differ diff --git a/prepare/prepare_kacheln.py b/prepare/prepare_kacheln.py new file mode 100755 index 0000000..0d08f15 --- /dev/null +++ b/prepare/prepare_kacheln.py @@ -0,0 +1,619 @@ +#!/usr/bin/env python3 +"""Prepare DTK25 Blätter into an Übersicht and optional Kacheln. + +Reads each Blatt's GeoTIFF tags with the standard library (no shell, no +GDAL). Every Blatt must be 9600 by 9600 pixels at 1.25 m in EPSG:25832, +spanning 12 km by 12 km, and must have a matching world file. The Übersicht +lists every Blatt. Kacheln are written only when ``--tiles`` is passed, for +one Blatt unless ``--all`` is given. ``--tiles public`` writes +``public/kacheln``. + +``gdalwarp`` and ``gdal_translate`` are used when both are on PATH. Otherwise +Pillow opens the LZW palette TIFF, keeps the native pixels at 1.25 m, and +resizes each coarser step. + +Resolutions are metres per pixel, finest first. Each coarser step doubles +the ground resolution, from the native sheet pixel up to 160 m. The phone's +furthest ground span is app configuration and is not read here; changing +that span does not require a new tile set. +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import shutil +import struct +import subprocess +import sys +import tempfile +from dataclasses import dataclass +from pathlib import Path + +TILE_SIZE_PX = 256 +NATIVE_RESOLUTION = 1.25 +EXPECTED_PIXELS = 9600 +EXPECTED_SPAN_M = 12_000 +EXPECTED_EPSG = 25832 +SCALE_TOLERANCE = 1e-6 +SPAN_TOLERANCE_M = 0.01 +PIXEL_TOLERANCE = 1e-4 + +RESOLUTIONS = [1.25, 2.5, 5, 10, 20, 40, 80, 160] + +TAG_WIDTH = 256 +TAG_HEIGHT = 257 +TAG_PIXEL_SCALE = 33550 +TAG_TIEPOINT = 33922 +TAG_GEOKEY = 34735 +GEOKEY_PROJECTED_CS = 3072 + +TYPE_SIZE = { + 1: 1, + 2: 1, + 3: 2, + 4: 4, + 5: 8, + 6: 1, + 7: 1, + 8: 2, + 9: 4, + 10: 8, + 11: 4, + 12: 8, +} + + +@dataclass(frozen=True) +class Blatt: + path: Path + min_e: float + min_n: float + max_e: float + max_n: float + + def as_json(self) -> dict[str, int | float]: + return { + "minE": json_number(self.min_e), + "minN": json_number(self.min_n), + "maxE": json_number(self.max_e), + "maxN": json_number(self.max_n), + } + + +def json_number(value: float) -> int | float: + rounded = round(value) + if abs(value - rounded) <= SCALE_TOLERANCE: + return int(rounded) + return round(value, 6) + + +def snap(value: float, what: str) -> int: + nearest = round(value) + if abs(value - nearest) > PIXEL_TOLERANCE: + raise SystemExit(f"{what}: {value} is not an integer pixel") + return nearest + + +def floor_index(value: float, size: float) -> int: + quotient = value / size + nearest = round(quotient) + if abs(quotient - nearest) <= SCALE_TOLERANCE: + return nearest + return math.floor(quotient) + + +def ceil_index(value: float, size: float) -> int: + quotient = value / size + nearest = round(quotient) + if abs(quotient - nearest) <= SCALE_TOLERANCE: + return nearest + return math.ceil(quotient) + + +def tile_indices(min_v: float, max_v: float, ground: float) -> range: + """Columns or rows whose tiles intersect [min_v, max_v).""" + start = floor_index(min_v, ground) + stop = ceil_index(max_v, ground) - 1 + return range(start, stop + 1) + + +def intersects(min_e: float, min_n: float, max_e: float, max_n: float, blatt: Blatt) -> bool: + return min_e < blatt.max_e and max_e > blatt.min_e and min_n < blatt.max_n and max_n > blatt.min_n + + +def read_values(handle, endian: str, typ: int, count: int, raw: bytes) -> tuple: + if typ not in TYPE_SIZE: + raise ValueError(f"unsupported TIFF type {typ}") + size = TYPE_SIZE[typ] * count + if size <= 4: + blob = raw[:size] + else: + (offset,) = struct.unpack(endian + "I", raw) + handle.seek(offset) + blob = handle.read(size) + if len(blob) != size: + raise ValueError(f"TIFF value at {offset} is short ({len(blob)} of {size} bytes)") + if typ == 3: + return struct.unpack(endian + f"{count}H", blob) + if typ == 4: + return struct.unpack(endian + f"{count}I", blob) + if typ == 8: + return struct.unpack(endian + f"{count}h", blob) + if typ == 12: + return struct.unpack(endian + f"{count}d", blob) + if typ == 11: + return struct.unpack(endian + f"{count}f", blob) + raise ValueError(f"unsupported TIFF type {typ}") + + +def read_ifd(path: Path) -> dict[int, tuple]: + with path.open("rb") as handle: + header = handle.read(8) + if len(header) < 8 or header[:2] not in (b"II", b"MM"): + raise ValueError(f"{path.name}: not a TIFF (header {header[:4]!r})") + endian = "<" if header[:2] == b"II" else ">" + magic, ifd_offset = struct.unpack(endian + "HI", header[2:8]) + if magic != 42: + raise ValueError(f"{path.name}: TIFF version {magic} is not 42") + handle.seek(ifd_offset) + count_bytes = handle.read(2) + if len(count_bytes) != 2: + raise ValueError(f"{path.name}: IFD at {ifd_offset} is unreadable") + (count,) = struct.unpack(endian + "H", count_bytes) + entries: list[tuple[int, int, int, bytes]] = [] + for _ in range(count): + entry = handle.read(12) + if len(entry) != 12: + raise ValueError(f"{path.name}: truncated IFD entry") + tag, typ, field_count = struct.unpack(endian + "HHI", entry[:8]) + entries.append((tag, typ, field_count, entry[8:12])) + needed = {TAG_WIDTH, TAG_HEIGHT, TAG_PIXEL_SCALE, TAG_TIEPOINT, TAG_GEOKEY} + values: dict[int, tuple] = {} + for tag, typ, field_count, raw in entries: + if tag not in needed: + continue + values[tag] = read_values(handle, endian, typ, field_count, raw) + return values + + +def projected_epsg(path: Path, geokeys: tuple) -> int: + if len(geokeys) < 4: + raise ValueError(f"{path.name}: GeoKeyDirectory is shorter than its header") + key_count = geokeys[3] + needed = 4 + key_count * 4 + if len(geokeys) < needed: + raise ValueError( + f"{path.name}: GeoKeyDirectory has {len(geokeys)} shorts, needs {needed}" + ) + for index in range(key_count): + base = 4 + index * 4 + key_id, location, _count, value = geokeys[base : base + 4] + if key_id != GEOKEY_PROJECTED_CS: + continue + if location != 0: + raise ValueError( + f"{path.name}: ProjectedCSTypeGeoKey location {location} is not an inline value" + ) + return int(value) + raise ValueError(f"{path.name}: missing ProjectedCSTypeGeoKey {GEOKEY_PROJECTED_CS}") + + +def read_blatt(path: Path) -> tuple[Blatt | None, list[str]]: + """Return the Blatt, or validation errors that name the file and the bad value.""" + errors: list[str] = [] + try: + tags = read_ifd(path) + except (OSError, ValueError, struct.error) as exc: + return None, [str(exc) if str(exc).startswith(path.name) else f"{path.name}: {exc}"] + + def require(tag: int, label: str) -> tuple | None: + if tag not in tags: + errors.append(f"{path.name}: missing GeoTIFF tag {tag} ({label})") + return None + return tags[tag] + + width_values = require(TAG_WIDTH, "ImageWidth") + height_values = require(TAG_HEIGHT, "ImageLength") + scale_values = require(TAG_PIXEL_SCALE, "ModelPixelScale") + tie_values = require(TAG_TIEPOINT, "ModelTiepoint") + key_values = require(TAG_GEOKEY, "GeoKeyDirectory") + if errors: + return None, errors + + assert width_values and height_values and scale_values and tie_values and key_values + width = int(width_values[0]) + height = int(height_values[0]) + if width != EXPECTED_PIXELS: + errors.append(f"{path.name}: width {width} is not {EXPECTED_PIXELS}") + if height != EXPECTED_PIXELS: + errors.append(f"{path.name}: height {height} is not {EXPECTED_PIXELS}") + + if len(scale_values) < 2: + errors.append(f"{path.name}: pixel scale {scale_values} is not 1.25") + scale_x = scale_y = None + else: + scale_x = float(scale_values[0]) + scale_y = float(scale_values[1]) + if abs(scale_x - NATIVE_RESOLUTION) > SCALE_TOLERANCE or abs(scale_y - NATIVE_RESOLUTION) > SCALE_TOLERANCE: + errors.append(f"{path.name}: pixel scale ({scale_x}, {scale_y}) is not 1.25") + + if len(tie_values) < 6: + errors.append(f"{path.name}: tiepoint {tie_values} does not have six values") + return None, errors + pixel_i, pixel_j, pixel_k = (float(tie_values[0]), float(tie_values[1]), float(tie_values[2])) + if ( + abs(pixel_i) > SCALE_TOLERANCE + or abs(pixel_j) > SCALE_TOLERANCE + or abs(pixel_k) > SCALE_TOLERANCE + ): + errors.append( + f"{path.name}: tiepoint pixel ({pixel_i}, {pixel_j}, {pixel_k}) is not (0, 0, 0)" + ) + tie_x = float(tie_values[3]) + tie_y = float(tie_values[4]) + + try: + epsg = projected_epsg(path, key_values) + except ValueError as exc: + errors.append(str(exc)) + epsg = None + if epsg is not None and epsg != EXPECTED_EPSG: + errors.append(f"{path.name}: EPSG {epsg} is not {EXPECTED_EPSG}") + + if scale_x is None or scale_y is None: + return None, errors + + span_e = width * scale_x + span_n = height * scale_y + if abs(span_e - EXPECTED_SPAN_M) > SPAN_TOLERANCE_M or abs(span_n - EXPECTED_SPAN_M) > SPAN_TOLERANCE_M: + errors.append( + f"{path.name}: ground span {span_e} m by {span_n} m is not " + f"{EXPECTED_SPAN_M} m by {EXPECTED_SPAN_M} m" + ) + + world = path.with_suffix(".tfw") + if not world.is_file(): + errors.append(f"{path.name}: missing world file {world.name}") + + if errors: + return None, errors + + # Tiepoint (0, 0) is the upper-left corner. The world file's origin is + # the center of that pixel; the tags are enough once they validate. + return Blatt( + path=path, + min_e=tie_x, + max_n=tie_y, + max_e=tie_x + span_e, + min_n=tie_y - span_n, + ), [] + + +def load_blaetter(directory: Path) -> tuple[list[Blatt], list[str]]: + if not directory.is_dir(): + return [], [f"{directory} is not a directory"] + paths = sorted(directory.glob("DTK25_*_col.tif")) + if not paths: + return [], [f"no Blätter matching DTK25_*_col.tif in {directory}"] + sheets: list[Blatt] = [] + errors: list[str] = [] + for path in paths: + sheet, sheet_errors = read_blatt(path) + errors.extend(sheet_errors) + if sheet is not None: + sheets.append(sheet) + sheets.sort(key=lambda sheet: (sheet.min_n, sheet.min_e, sheet.path.name)) + return sheets, errors + + +def write_uebersicht(path: Path, sheets: list[Blatt]) -> None: + document = { + "tileSizePx": TILE_SIZE_PX, + "resolutions": RESOLUTIONS, + "blaetter": [sheet.as_json() for sheet in sheets], + "gebiete": [{"name": "Hessen", "covers": "all"}], + } + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(document, indent=2) + "\n", encoding="utf-8") + + +def gdal_available() -> bool: + return shutil.which("gdalwarp") is not None and shutil.which("gdal_translate") is not None + + +def write_kachel(incoming, out: Path) -> None: + """Store one Kachel. A shared edge keeps opaque pixels from both Blätter. + + A tile often crosses a Blatt boundary. Writing the second Blatt used to + replace the file, so one side of the Kachel stayed transparent and the + map showed the background through that strip. + """ + from PIL import Image + + layer = incoming.convert("RGBA") + if layer is incoming: + layer = incoming.copy() + try: + if out.is_file() and out.stat().st_size > 0: + with Image.open(out) as existing: + base = existing.convert("RGBA") + if base is existing: + base = existing.copy() + base.alpha_composite(layer) + base.save(out, format="PNG") + base.close() + else: + layer.save(out, format="PNG") + finally: + layer.close() + + +def run_gdal(command: list[str]) -> None: + env = os.environ.copy() + env["GDAL_PAM_ENABLED"] = "NO" + result = subprocess.run(command, capture_output=True, text=True, env=env) + if result.returncode != 0: + detail = (result.stderr or result.stdout).strip() + raise SystemExit(f"{command[0]} failed ({result.returncode}): {detail}") + + +def tile_with_gdal(sheets: list[Blatt], dest: Path) -> int: + written = 0 + with tempfile.TemporaryDirectory(prefix="kacheln-") as tmp: + for sheet in sheets: + for z, resolution in enumerate(RESOLUTIONS): + ground = resolution * TILE_SIZE_PX + cols = list(tile_indices(sheet.min_e, sheet.max_e, ground)) + rows = list(tile_indices(sheet.min_n, sheet.max_n, ground)) + if not cols or not rows: + continue + te_min_e = cols[0] * ground + te_max_e = (cols[-1] + 1) * ground + te_min_n = rows[0] * ground + te_max_n = (rows[-1] + 1) * ground + warped = Path(tmp) / f"{sheet.path.stem}-z{z}.tif" + resample = "near" if z == 0 else "lanczos" + run_gdal( + [ + "gdalwarp", + "-q", + "-t_srs", + f"EPSG:{EXPECTED_EPSG}", + "-te", + str(te_min_e), + str(te_min_n), + str(te_max_e), + str(te_max_n), + "-tr", + str(resolution), + str(resolution), + "-r", + resample, + "-dstalpha", + "-of", + "GTiff", + "-co", + "TILED=YES", + "-co", + "COMPRESS=DEFLATE", + str(sheet.path), + str(warped), + ] + ) + tags = read_ifd(warped) + width = int(tags[TAG_WIDTH][0]) + height = int(tags[TAG_HEIGHT][0]) + expect_w = len(cols) * TILE_SIZE_PX + expect_h = len(rows) * TILE_SIZE_PX + if width != expect_w or height != expect_h: + raise SystemExit( + f"{sheet.path.name}: gdalwarp size {width} by {height} " + f"is not {expect_w} by {expect_h} at {resolution} m" + ) + for col in cols: + column_dir = dest / "kacheln" / str(z) / str(col) + column_dir.mkdir(parents=True, exist_ok=True) + x_off = int(round((col * ground - te_min_e) / resolution)) + for row in rows: + y_off = int(round((te_max_n - (row + 1) * ground) / resolution)) + out = column_dir / f"{row}.png" + fresh = Path(tmp) / f"tile-{z}-{col}-{row}.png" + run_gdal( + [ + "gdal_translate", + "-q", + "-srcwin", + str(x_off), + str(y_off), + str(TILE_SIZE_PX), + str(TILE_SIZE_PX), + "-of", + "PNG", + str(warped), + str(fresh), + ] + ) + from PIL import Image + + with Image.open(fresh) as incoming: + write_kachel(incoming, out) + fresh.unlink(missing_ok=True) + written += 1 + print( + f" z={z} {resolution} m {len(cols) * len(rows)} tiles {sheet.path.name}", + flush=True, + ) + return written + + +def paste_tile(level, sheet: Blatt, resolution: float, col: int, row: int): + from PIL import Image + + ground = resolution * TILE_SIZE_PX + tile_min_e = col * ground + tile_max_n = (row + 1) * ground + what = f"{sheet.path.name}: tile {resolution} m {col}/{row}" + src_x = snap((tile_min_e - sheet.min_e) / resolution, what) + src_y = snap((sheet.max_n - tile_max_n) / resolution, what) + src_w, src_h = level.size + left = max(src_x, 0) + upper = max(src_y, 0) + right = min(src_x + TILE_SIZE_PX, src_w) + lower = min(src_y + TILE_SIZE_PX, src_h) + if right <= left or lower <= upper: + raise SystemExit(f"{what} intersects the Blatt but has no pixels") + tile = Image.new("RGBA", (TILE_SIZE_PX, TILE_SIZE_PX), (0, 0, 0, 0)) + crop = level.crop((left, upper, right, lower)) + tile.paste(crop, (left - src_x, upper - src_y)) + return tile + + +def tile_with_pillow(sheets: list[Blatt], dest: Path) -> int | None: + try: + from PIL import Image + except ImportError as exc: + print( + "Pillow could not be imported, and gdalwarp/gdal_translate are not on PATH. " + f"The Übersicht was written. No tiles were written. ({exc})", + file=sys.stderr, + ) + return None + + # A Blatt is 9600² pixels, just over Pillow's default bomb limit. + Image.MAX_IMAGE_PIXELS = EXPECTED_PIXELS * EXPECTED_PIXELS + + written = 0 + for sheet in sheets: + try: + with Image.open(sheet.path) as opened: + opened.load() + native = opened.convert("RGBA") + except Exception as exc: + print( + f"Pillow could not decode {sheet.path.name}: {exc}. " + "The Übersicht was written. No tiles were written.", + file=sys.stderr, + ) + return None + if native.size != (EXPECTED_PIXELS, EXPECTED_PIXELS): + print( + f"{sheet.path.name}: Pillow size {native.size[0]} by {native.size[1]} " + f"is not {EXPECTED_PIXELS} by {EXPECTED_PIXELS}. " + "The Übersicht was written. No tiles were written.", + file=sys.stderr, + ) + native.close() + return None + try: + for z, resolution in enumerate(RESOLUTIONS): + scale = NATIVE_RESOLUTION / resolution + level_size = ( + snap(native.width * scale, f"{sheet.path.name}: resized width at {resolution} m"), + snap(native.height * scale, f"{sheet.path.name}: resized height at {resolution} m"), + ) + level = native if z == 0 else native.resize(level_size, Image.Resampling.LANCZOS) + ground = resolution * TILE_SIZE_PX + cols = list(tile_indices(sheet.min_e, sheet.max_e, ground)) + rows = list(tile_indices(sheet.min_n, sheet.max_n, ground)) + level_count = 0 + try: + for col in cols: + column_dir = dest / "kacheln" / str(z) / str(col) + column_dir.mkdir(parents=True, exist_ok=True) + for row in rows: + tile_min_e = col * ground + tile_min_n = row * ground + if not intersects( + tile_min_e, + tile_min_n, + tile_min_e + ground, + tile_min_n + ground, + sheet, + ): + continue + out = column_dir / f"{row}.png" + tile = paste_tile(level, sheet, resolution, col, row) + write_kachel(tile, out) + tile.close() + written += 1 + level_count += 1 + finally: + if level is not native: + level.close() + print( + f" z={z} {resolution} m {level_count} tiles {sheet.path.name}", + flush=True, + ) + finally: + native.close() + return written + + +def select_sheets(sheets: list[Blatt], only: str | None, tile_all: bool) -> tuple[list[Blatt], str | None]: + if tile_all and only: + return [], "pass either --only or --all, not both" + if tile_all: + return sheets, None + if only: + name = Path(only).name + chosen = [sheet for sheet in sheets if sheet.path.name == name] + if not chosen: + return [], f"no Blatt named {name}" + return chosen, None + return [sheets[0]], None + + +def parse_args(argv: list[str] | None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--karten", type=Path, required=True, help="directory of DTK25 GeoTIFFs") + parser.add_argument("--uebersicht", type=Path, required=True, help="Übersicht JSON to write") + parser.add_argument( + "--tiles", + type=Path, + help="write {DIR}/kacheln/{z}/{col}/{row}.png; pass public (one Blatt unless --all)", + ) + parser.add_argument( + "--only", + help="filename of the single Blatt to tile, e.g. DTK25_464000_5576000_col.tif", + ) + parser.add_argument("--all", action="store_true", help="tile every Blatt") + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + sheets, errors = load_blaetter(args.karten) + if errors: + for error in errors: + print(error, file=sys.stderr) + return 1 + write_uebersicht(args.uebersicht, sheets) + print(f"{len(sheets)} Blätter") + print(f"wrote {args.uebersicht}") + if args.tiles is None: + return 0 + + chosen, error = select_sheets(sheets, args.only, args.all) + if error: + print(error, file=sys.stderr) + return 1 + if not args.all and args.only is None: + print(f"tiling one Blatt ({chosen[0].path.name})") + + if gdal_available(): + print("tiling with gdalwarp and gdal_translate") + written = tile_with_gdal(chosen, args.tiles) + else: + print("gdalwarp/gdal_translate not on PATH; tiling with Pillow") + written = tile_with_pillow(chosen, args.tiles) + if written is None: + return 1 + print(f"{written} PNG tiles written to {args.tiles / 'kacheln'}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/adapters/cache-storage.ts b/src/adapters/cache-storage.ts new file mode 100644 index 0000000..2f9d7f5 --- /dev/null +++ b/src/adapters/cache-storage.ts @@ -0,0 +1,46 @@ +import type { CachePort } from "../karte/types"; + +export const kachelCacheName = "kacheln"; + +export function kachelUrl(id: string): string { + return `/kacheln/${id}.png`; +} + +function idFromPathname(pathname: string): string | null { + const prefix = "/kacheln/"; + const suffix = ".png"; + if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix)) return null; + const id = pathname.slice(prefix.length, -suffix.length); + return id.length > 0 ? id : null; +} + +export function createCacheStorage(): CachePort { + return { + async ids() { + const cache = await caches.open(kachelCacheName); + const requests = await cache.keys(); + const ids: string[] = []; + for (const request of requests) { + const id = idFromPathname(new URL(request.url).pathname); + if (id) ids.push(id); + } + return ids; + }, + + async put(id, bytes) { + const cache = await caches.open(kachelCacheName); + const body = new Uint8Array(bytes); + await cache.put( + kachelUrl(id), + new Response(body, { + headers: { "Content-Type": "image/png" }, + }), + ); + }, + + async delete(ids) { + const cache = await caches.open(kachelCacheName); + await Promise.all(ids.map((id) => cache.delete(kachelUrl(id)))); + }, + }; +} diff --git a/src/adapters/kachel-fetch.ts b/src/adapters/kachel-fetch.ts new file mode 100644 index 0000000..ce9267d --- /dev/null +++ b/src/adapters/kachel-fetch.ts @@ -0,0 +1,12 @@ +import type { KachelPort } from "../karte/types"; +import { kachelUrl } from "./cache-storage"; + +export function createKachelFetch(): KachelPort { + return { + async fetch(id, signal) { + const response = await fetch(kachelUrl(id), { signal, cache: "no-store" }); + if (!response.ok) throw new Error(`Kachel ${id} konnte nicht geladen werden.`); + return new Uint8Array(await response.arrayBuffer()); + }, + }; +} diff --git a/src/adapters/marks-db.ts b/src/adapters/marks-db.ts new file mode 100644 index 0000000..5426393 --- /dev/null +++ b/src/adapters/marks-db.ts @@ -0,0 +1,57 @@ +import type { MarksPort } from "../karte/types"; + +const DB_NAME = "karte"; +const STORE = "marks"; +const KEY = "heruntergeladen"; + +function openDatabase(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 1); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE); + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error ?? new Error("IndexedDB")); + }); +} + +function stringsFrom(value: unknown): string[] { + if (!Array.isArray(value)) return []; + return value.filter((item): item is string => typeof item === "string"); +} + +function settle(tx: IDBTransaction, value: () => unknown): Promise { + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(value()); + tx.onerror = () => reject(tx.error ?? new Error("IndexedDB")); + tx.onabort = () => reject(tx.error ?? new Error("IndexedDB")); + }); +} + +export function createMarksDb(): MarksPort { + return { + async read() { + const db = await openDatabase(); + try { + const tx = db.transaction(STORE, "readonly"); + const request = tx.objectStore(STORE).get(KEY); + const value = await settle(tx, () => request.result); + return { heruntergeladen: stringsFrom(value) }; + } finally { + db.close(); + } + }, + + async write(state) { + const db = await openDatabase(); + try { + const tx = db.transaction(STORE, "readwrite"); + tx.objectStore(STORE).put(state.heruntergeladen, KEY); + await settle(tx, () => undefined); + } finally { + db.close(); + } + }, + }; +} diff --git a/src/adapters/standort.ts b/src/adapters/standort.ts new file mode 100644 index 0000000..bcd068f --- /dev/null +++ b/src/adapters/standort.ts @@ -0,0 +1,33 @@ +import proj4 from "proj4"; +import type { Point } from "../karte/types"; + +const ETRS25832 = + "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"; + +proj4.defs("EPSG:25832", ETRS25832); + +export function toEtrs(lon: number, lat: number): Point { + const projected = proj4("EPSG:4326", "EPSG:25832", [lon, lat]); + return { easting: projected[0], northing: projected[1] }; +} + +export function watchStandort( + onStandort: (standort: Point | "denied") => void, +): () => void { + if (!navigator.geolocation) { + onStandort("denied"); + return () => {}; + } + + const id = navigator.geolocation.watchPosition( + (position) => { + onStandort(toEtrs(position.coords.longitude, position.coords.latitude)); + }, + (error) => { + if (error.code === error.PERMISSION_DENIED) onStandort("denied"); + }, + { enableHighAccuracy: true }, + ); + + return () => navigator.geolocation.clearWatch(id); +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..eada733 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,24 @@ +import type { KarteConfig, Viewport } from "./karte/types"; + +/** + * Opening scale 1:25,000 as ground metres per screen pixel. + * OpenLayers and the OGC use a 0.28 mm pixel: 25000 * 0.00028 = 7. + * Zoom-in stops at one TIFF pixel, 1.25 m on the ground. + * The phone cannot change this or the 36 km span; both ship with the app. + */ +export const shippedConfig: KarteConfig = { + maxSpanMeters: 36_000, + minResolutionMeters: 1.25, + openResolutionMeters: 7, +}; + +/** Largest ground metres per pixel that keeps both view sides within the shipped span. */ +export function maxResolutionMeters( + viewport: Viewport, + config: KarteConfig = shippedConfig, +): number { + return Math.min( + config.maxSpanMeters / viewport.widthPx, + config.maxSpanMeters / viewport.heightPx, + ); +} diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..64251fb --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/src/karte/fakes.ts b/src/karte/fakes.ts new file mode 100644 index 0000000..37629cf --- /dev/null +++ b/src/karte/fakes.ts @@ -0,0 +1,66 @@ +import type { CachePort, KachelPort, MarksPort } from "./types"; + +export function createMemoryCache(initial: string[] = []): CachePort { + const ids = new Set(initial); + return { + async ids() { + return [...ids]; + }, + async put(id) { + ids.add(id); + }, + async delete(drop) { + for (const id of drop) ids.delete(id); + }, + }; +} + +export function createMemoryMarks(initial: string[] = []): MarksPort { + let heruntergeladen = [...initial]; + return { + async read() { + return { heruntergeladen: [...heruntergeladen] }; + }, + async write(state) { + heruntergeladen = [...state.heruntergeladen]; + }, + }; +} + +type Job = { + resolve: (bytes: Uint8Array) => void; + reject: (error: unknown) => void; + settled: boolean; +}; + +/** Completes the oldest in-flight Kachel fetch when `tick` is called. */ +export function createFetchClock(): KachelPort & { tick: () => Promise } { + const queue: Job[] = []; + return { + fetch(_id, signal) { + return new Promise((resolve, reject) => { + const job: Job = { resolve, reject, settled: false }; + const abort = () => { + if (job.settled) return; + job.settled = true; + const index = queue.indexOf(job); + if (index >= 0) queue.splice(index, 1); + reject(new DOMException("The operation was aborted.", "AbortError")); + }; + if (signal.aborted) { + abort(); + return; + } + signal.addEventListener("abort", abort, { once: true }); + queue.push(job); + }); + }, + async tick() { + const job = queue.shift(); + if (!job || job.settled) return; + job.settled = true; + job.resolve(new Uint8Array([7])); + for (let step = 0; step < 20; step += 1) await Promise.resolve(); + }, + }; +} diff --git a/src/karte/koordinate.ts b/src/karte/koordinate.ts new file mode 100644 index 0000000..83beee1 --- /dev/null +++ b/src/karte/koordinate.ts @@ -0,0 +1,41 @@ +import { toPoint } from "mgrs"; +import proj4 from "proj4"; +import type { Point } from "./types"; + +proj4.defs( + "EPSG:25832", + "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs", +); + +const DIGITS = "Rechtswert und Hochwert müssen genau fünf Ziffern haben."; +const GITTER_NOTICE = "Dieses Gitter ist keine gültige Koordinate."; +const BLATT = "Kein Blatt deckt diese Koordinate."; + +/** Zone, latitude band, and 100 km square. I and O are not MGRS letters. */ +const GITTER_PATTERN = /^(\d{1,2})([C-HJ-NP-X])([A-HJ-NP-Z]{2})$/; + +export function parseKoordinate( + input: { gitter: string; rechtswert: string; hochwert: string }, + blattCovers: (point: Point) => boolean, +): { ok: true; point: Point } | { ok: false; notice: string } { + if (!/^\d{5}$/.test(input.rechtswert) || !/^\d{5}$/.test(input.hochwert)) { + return { ok: false, notice: DIGITS }; + } + + const compact = input.gitter.trim().toUpperCase().replace(/\s+/g, ""); + if (!GITTER_PATTERN.test(compact)) { + return { ok: false, notice: GITTER_NOTICE }; + } + + let geographic: number[]; + try { + geographic = toPoint(`${compact}${input.rechtswert}${input.hochwert}`); + } catch { + return { ok: false, notice: GITTER_NOTICE }; + } + + const projected = proj4("EPSG:4326", "EPSG:25832", geographic); + const point = { easting: projected[0], northing: projected[1] }; + if (!blattCovers(point)) return { ok: false, notice: BLATT }; + return { ok: true, point }; +} diff --git a/src/karte/session.test.ts b/src/karte/session.test.ts new file mode 100644 index 0000000..ca0fbd6 --- /dev/null +++ b/src/karte/session.test.ts @@ -0,0 +1,623 @@ +import { shippedConfig } from "../config"; +import { createFetchClock, createMemoryCache, createMemoryMarks } from "./fakes"; +import { openKarte } from "./session"; +import type { KarteSession, KarteSnapshot, Point, Uebersicht, Viewport } from "./types"; + +const sheet: Uebersicht = { + tileSizePx: 10, + resolutions: [10], + blaetter: [{ minE: 0, minN: 0, maxE: 300, maxN: 100 }], + gebiete: [ + { name: "Hessen", covers: "all" }, + { name: "Usingen", covers: [{ minE: 0, minN: 0, maxE: 200, maxN: 100 }] }, + { name: "Wehrheim", covers: [{ minE: 100, minN: 0, maxE: 300, maxN: 100 }] }, + ], +}; + +const TILE_A = "0/0/0"; +const TILE_B = "0/1/0"; +const TILE_C = "0/2/0"; + +const wideSquare: Uebersicht = { + tileSizePx: 256, + resolutions: [10], + blaetter: [{ minE: 400_000, minN: 5_500_000, maxE: 500_000, maxN: 5_600_000 }], + gebiete: [{ name: "Hessen", covers: "all" }], +}; + +const withHole: Uebersicht = { + tileSizePx: 256, + resolutions: [10], + blaetter: [ + { minE: 464_000, minN: 5_576_000, maxE: 470_000, maxN: 5_588_000 }, + { minE: 480_000, minN: 5_576_000, maxE: 490_000, maxN: 5_588_000 }, + ], + gebiete: [{ name: "Hessen", covers: "all" }], +}; + +const phone: Viewport = { widthPx: 400, heightPx: 800 }; +const oneTile: Viewport = { widthPx: 10, heightPx: 10 }; +const threeTiles: Viewport = { widthPx: 30, heightPx: 10 }; + +async function openSheet(options?: { + viewport?: Viewport; + online?: boolean; + standort?: Point | "denied"; + cache?: ReturnType; + marks?: ReturnType; + clock?: ReturnType; + uebersicht?: Uebersicht; + maxSpanMeters?: number; +}) { + const cache = options?.cache ?? createMemoryCache(); + const marks = options?.marks ?? createMemoryMarks(); + const clock = options?.clock ?? createFetchClock(); + const karte = await openKarte({ + uebersicht: options?.uebersicht ?? sheet, + cache, + marks, + kacheln: clock, + config: { ...shippedConfig, maxSpanMeters: options?.maxSpanMeters ?? shippedConfig.maxSpanMeters }, + viewport: options?.viewport ?? phone, + standort: options?.standort, + online: options?.online ?? false, + }); + return { karte, cache, marks, clock }; +} + +function appearance(karte: KarteSession, id: string) { + const kachel = karte.snapshot().kacheln.find((item) => item.id === id); + if (!kachel) throw new Error(`Kachel ${id} is not in the view`); + return kachel.appearance; +} + +function hessen(snapshot: KarteSnapshot) { + const gebiet = snapshot.gebiete.find((item) => item.name === "Hessen"); + if (!gebiet) throw new Error("Hessen missing"); + return gebiet; +} + +const sample = { gitter: "32U MA", rechtswert: "67030", hochwert: "78109" }; + +describe("opening the Karte", () => { + test("opens north-up at 1:25,000 on the middle of the Übersicht", async () => { + const { karte } = await openSheet(); + const snapshot = karte.snapshot(); + expect(snapshot.northUp).toBe(true); + expect(snapshot.resolution).toBe(7); + expect(snapshot.center).toEqual({ easting: 150, northing: 50 }); + expect(snapshot.koordinate.gitter).toBe("32U MA"); + expect(snapshot.ziel).toBeNull(); + expect(snapshot.standort).toBeNull(); + }); + + test("opens on the middle of the Kacheln already in the Cache when no Standort is known", async () => { + const { karte } = await openSheet({ cache: createMemoryCache([TILE_A]) }); + expect(karte.snapshot().center).toEqual({ easting: 50, northing: 50 }); + expect(karte.snapshot().resolution).toBe(7); + }); + + test("opens on a Standort the device already has", async () => { + const { karte } = await openSheet({ + standort: { easting: 1000, northing: 2000 }, + cache: createMemoryCache([TILE_A]), + }); + expect(karte.snapshot().center).toEqual({ easting: 1000, northing: 2000 }); + expect(karte.snapshot().standort).toEqual({ easting: 1000, northing: 2000 }); + expect(karte.snapshot().resolution).toBe(7); + }); + + test("a denied Standort leaves the opening center on the Cache, or else the Übersicht", async () => { + const withCache = await openSheet({ + standort: "denied", + cache: createMemoryCache([TILE_A]), + }); + expect(withCache.karte.snapshot().center).toEqual({ easting: 50, northing: 50 }); + expect(withCache.karte.snapshot().standort).toBeNull(); + + const empty = await openSheet({ standort: "denied" }); + expect(empty.karte.snapshot().center).toEqual({ easting: 150, northing: 50 }); + }); + + test("pinch-zoom stops at 1.25 m per pixel and at the shipped 36 km span", async () => { + const { karte } = await openSheet(); + karte.zoomTo(0.1); + expect(karte.snapshot().resolution).toBe(1.25); + + karte.setViewport(phone); + karte.zoomTo(100_000); + expect(karte.snapshot().resolution).toBe(45); + expect(karte.snapshot().resolution * phone.heightPx).toBe(36_000); + + karte.setViewport({ widthPx: 1024, heightPx: 768 }); + karte.zoomTo(100_000); + const tablet = karte.snapshot().resolution; + expect(tablet * 1024).toBe(36_000); + expect(tablet * 768).toBeLessThanOrEqual(36_000); + }); + + test("the 36 km span is shipped data, not something the session lets the person edit", async () => { + const { karte } = await openSheet({ maxSpanMeters: 18_000, viewport: phone }); + karte.zoomTo(100_000); + expect(karte.snapshot().resolution * phone.heightPx).toBe(18_000); + }); + + test("panning moves the view and a Kachel already in the Cache is drawn", async () => { + const { karte } = await openSheet({ + viewport: oneTile, + cache: createMemoryCache([TILE_A]), + }); + karte.zoomTo(10); + karte.panTo({ easting: 50, northing: 50 }); + expect(karte.snapshot().center).toEqual({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).toBe("drawn"); + }); + + test("a known Kachel missing from the Cache stays blank", async () => { + const { karte } = await openSheet({ viewport: oneTile }); + karte.zoomTo(10); + karte.panTo({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).toBe("blank-not-on-device"); + expect(karte.snapshot().notice).toBe("Nicht auf dem Gerät."); + }); +}); + +describe("fetching a missing Kachel", () => { + async function viewTile(easting: number) { + const opened = await openSheet({ viewport: oneTile }); + opened.karte.zoomTo(10); + opened.karte.panTo({ easting, northing: 50 }); + return opened; + } + + test("while open and online, a missing known Kachel is blank and then drawn", async () => { + const { karte, clock } = await viewTile(250); + karte.setOnline(true); + expect(appearance(karte, TILE_C)).toBe("blank-downloading"); + expect(karte.snapshot().notice).toBeNull(); + await clock.tick(); + expect(appearance(karte, TILE_C)).toBe("drawn"); + }); + + test("the Kachel stays in the Cache when the view leaves before the fetch ends", async () => { + const { karte, clock } = await viewTile(250); + karte.setOnline(true); + karte.panTo({ easting: 50, northing: 50 }); + await clock.tick(); + karte.panTo({ easting: 250, northing: 50 }); + expect(appearance(karte, TILE_C)).toBe("drawn"); + }); + + test("offline, a missing known Kachel stays blank and the notice says it is not on the device", async () => { + const { karte, clock } = await viewTile(50); + expect(appearance(karte, TILE_A)).toBe("blank-not-on-device"); + expect(karte.snapshot().notice).toBe("Nicht auf dem Gerät."); + await clock.tick(); + expect(appearance(karte, TILE_A)).toBe("blank-not-on-device"); + }); + + test("opening online later fetches the Kachel the view still needs", async () => { + const { karte, clock } = await viewTile(50); + karte.setAppOpen(false); + karte.setOnline(true); + await clock.tick(); + expect(appearance(karte, TILE_A)).toBe("blank-not-on-device"); + karte.setAppOpen(true); + expect(appearance(karte, TILE_A)).toBe("blank-downloading"); + await clock.tick(); + expect(appearance(karte, TILE_A)).toBe("drawn"); + }); + + test("a place the Übersicht does not know stays blank and is never downloaded", async () => { + const { karte, clock } = await openSheet({ viewport: oneTile }); + karte.zoomTo(10); + karte.panTo({ easting: 5_000, northing: 50 }); + karte.setOnline(true); + const before = karte.snapshot().kacheln.map((kachel) => kachel.appearance); + expect(before.every((item) => item === "blank-unknown")).toBe(true); + expect(karte.snapshot().notice).toBeNull(); + await clock.tick(); + await clock.tick(); + expect(karte.snapshot().kacheln.every((kachel) => kachel.appearance === "blank-unknown")).toBe(true); + }); + + test("on-demand fetch takes only the current view at the current scale", async () => { + const opened = await openSheet({ + viewport: oneTile, + uebersicht: { + tileSizePx: 10, + resolutions: [10, 20], + blaetter: [{ minE: 0, minN: 0, maxE: 100, maxN: 100 }], + gebiete: [{ name: "Hessen", covers: "all" }], + }, + }); + opened.karte.zoomTo(10); + opened.karte.panTo({ easting: 50, northing: 50 }); + opened.karte.setOnline(true); + await opened.clock.tick(); + expect(appearance(opened.karte, "0/0/0")).toBe("drawn"); + opened.karte.zoomTo(15); + expect(opened.karte.snapshot().kacheln.some((kachel) => kachel.id === "0/0/0")).toBe(true); + expect(opened.karte.snapshot().kacheln.some((kachel) => kachel.id === "1/0/0")).toBe(false); + opened.karte.zoomTo(20); + expect(appearance(opened.karte, "1/0/0")).toBe("blank-downloading"); + }); + + test("leaving the app pauses the fetch, and opening online continues it", async () => { + const { karte, clock } = await viewTile(250); + karte.setOnline(true); + karte.startGebiet("Hessen"); + karte.setAppOpen(false); + await clock.tick(); + expect(appearance(karte, TILE_C)).toBe("blank-not-on-device"); + karte.setAppOpen(true); + await clock.tick(); + expect(appearance(karte, TILE_C)).toBe("drawn"); + }); + + test("a finished Kachel is still drawn after the app is left and opened again", async () => { + const cache = createMemoryCache(); + const marks = createMemoryMarks(); + const clock = createFetchClock(); + const first = await openSheet({ viewport: oneTile, cache, marks, clock }); + first.karte.zoomTo(10); + first.karte.panTo({ easting: 250, northing: 50 }); + first.karte.setOnline(true); + await clock.tick(); + first.karte.setAppOpen(false); + + const second = await openSheet({ viewport: oneTile, cache, marks, clock, online: false }); + second.karte.zoomTo(10); + second.karte.panTo({ easting: 250, northing: 50 }); + expect(appearance(second.karte, TILE_C)).toBe("drawn"); + expect(second.karte.snapshot().ziel).toBeNull(); + }); +}); + +describe("naming a Ziel", () => { + test("accepts the sample Koordinate, keeps the typed text, and centers at 1:25,000", async () => { + const { karte } = await openSheet({ uebersicht: wideSquare, online: false }); + karte.enterKoordinate({ gitter: "32u ma", rechtswert: "67030", hochwert: "78109" }); + const snapshot = karte.snapshot(); + expect(snapshot.koordinate).toEqual({ gitter: "32u ma", rechtswert: "67030", hochwert: "78109" }); + expect(snapshot.resolution).toBe(7); + expect(snapshot.ziel).not.toBeNull(); + expect(Math.abs(snapshot.ziel!.easting - 467_030)).toBeLessThan(1); + expect(Math.abs(snapshot.ziel!.northing - 5_578_109)).toBeLessThan(1); + expect(snapshot.notice).toBe("Nicht auf dem Gerät."); + }); + + test("treats 32UMA like 32U MA", async () => { + const { karte } = await openSheet({ uebersicht: wideSquare }); + karte.enterKoordinate({ gitter: "32UMA", rechtswert: "67030", hochwert: "78109" }); + expect(Math.abs(karte.snapshot().ziel!.easting - 467_030)).toBeLessThan(1); + expect(karte.snapshot().koordinate.gitter).toBe("32UMA"); + }); + + test("accepts a leading zero as five digits, and works with the device offline", async () => { + const { karte } = await openSheet({ uebersicht: wideSquare, online: false }); + karte.enterKoordinate({ gitter: "32U MA", rechtswert: "06703", hochwert: "78109" }); + expect(karte.snapshot().ziel).not.toBeNull(); + expect(Math.abs(karte.snapshot().ziel!.easting - 406_703)).toBeLessThan(1); + }); + + test("rejects a Rechtswert or Hochwert that is not exactly five digits", async () => { + const { karte } = await openSheet({ uebersicht: wideSquare }); + karte.enterKoordinate(sample); + const kept = karte.snapshot(); + karte.panTo({ easting: 420_000, northing: 5_520_000 }); + karte.enterKoordinate({ gitter: "32U MA", rechtswert: "6703", hochwert: "78109" }); + expect(karte.snapshot().notice).toBe("Rechtswert und Hochwert müssen genau fünf Ziffern haben."); + expect(karte.snapshot().ziel).toEqual(kept.ziel); + expect(karte.snapshot().center).toEqual({ easting: 420_000, northing: 5_520_000 }); + expect(karte.snapshot().koordinate).toEqual(kept.koordinate); + + karte.enterKoordinate({ gitter: "32U MA", rechtswert: "670300", hochwert: "78109" }); + expect(karte.snapshot().notice).toBe("Rechtswert und Hochwert müssen genau fünf Ziffern haben."); + expect(karte.snapshot().ziel).toEqual(kept.ziel); + }); + + test("rejects an impossible Gitter", async () => { + const { karte } = await openSheet({ uebersicht: wideSquare }); + karte.enterKoordinate(sample); + const kept = karte.snapshot(); + karte.enterKoordinate({ gitter: "32U II", rechtswert: "67030", hochwert: "78109" }); + expect(karte.snapshot().notice).toBe("Dieses Gitter ist keine gültige Koordinate."); + expect(karte.snapshot().ziel).toEqual(kept.ziel); + expect(karte.snapshot().center).toEqual(kept.center); + }); + + test("rejects a Koordinate no Blatt covers, including a hole between Blätter", async () => { + const { karte } = await openSheet({ uebersicht: withHole }); + karte.enterKoordinate(sample); + const kept = karte.snapshot(); + expect(kept.ziel).not.toBeNull(); + + karte.panTo({ easting: 465_000, northing: 5_580_000 }); + karte.enterKoordinate({ gitter: "32U MA", rechtswert: "75000", hochwert: "78109" }); + expect(karte.snapshot().notice).toBe("Kein Blatt deckt diese Koordinate."); + expect(karte.snapshot().ziel).toEqual(kept.ziel); + expect(karte.snapshot().center).toEqual({ easting: 465_000, northing: 5_580_000 }); + expect(karte.snapshot().resolution).toBe(kept.resolution); + }); + + test("a second Koordinate replaces the Ziel, and there is only one", async () => { + const { karte } = await openSheet({ uebersicht: withHole }); + karte.enterKoordinate(sample); + karte.enterKoordinate({ gitter: "32U MA", rechtswert: "81000", hochwert: "78109" }); + const snapshot = karte.snapshot(); + expect(Math.abs(snapshot.ziel!.easting - 481_000)).toBeLessThan(1); + expect(snapshot.resolution).toBe(7); + expect(snapshot.koordinate).toEqual({ + gitter: "32U MA", + rechtswert: "81000", + hochwert: "78109", + }); + }); + + test("leaving the page drops the Ziel and keeps the Cache marks", async () => { + const cache = createMemoryCache(); + const marks = createMemoryMarks(); + const first = await openSheet({ uebersicht: wideSquare, cache, marks }); + first.karte.enterKoordinate(sample); + first.karte.startGebiet("Hessen"); + expect(first.karte.snapshot().ziel).not.toBeNull(); + + const second = await openSheet({ uebersicht: wideSquare, cache, marks }); + expect(second.karte.snapshot().ziel).toBeNull(); + expect(hessen(second.karte.snapshot()).heruntergeladen).toBe(true); + }); +}); + +describe("Standort and distance", () => { + test("a late first Standort centers the view once", async () => { + const { karte } = await openSheet(); + expect(karte.snapshot().center).toEqual({ easting: 150, northing: 50 }); + karte.reportStandort({ easting: 20, northing: 30 }); + expect(karte.snapshot().center).toEqual({ easting: 20, northing: 30 }); + expect(karte.snapshot().standort).toEqual({ easting: 20, northing: 30 }); + expect(karte.snapshot().resolution).toBe(7); + + karte.reportStandort({ easting: 80, northing: 90 }); + expect(karte.snapshot().standort).toEqual({ easting: 80, northing: 90 }); + expect(karte.snapshot().center).toEqual({ easting: 20, northing: 30 }); + }); + + test("a pinch before the first Standort still lets that fix center the view", async () => { + const { karte } = await openSheet(); + karte.zoomTo(10); + karte.reportStandort({ easting: 20, northing: 30 }); + expect(karte.snapshot().center).toEqual({ easting: 20, northing: 30 }); + expect(karte.snapshot().standort).toEqual({ easting: 20, northing: 30 }); + expect(karte.snapshot().resolution).toBe(7); + }); + + test("a late first Standort after a pan or a Ziel only moves the mark", async () => { + const panned = await openSheet(); + panned.karte.panTo({ easting: 10, northing: 10 }); + panned.karte.reportStandort({ easting: 80, northing: 90 }); + expect(panned.karte.snapshot().center).toEqual({ easting: 10, northing: 10 }); + expect(panned.karte.snapshot().standort).toEqual({ easting: 80, northing: 90 }); + + const named = await openSheet({ uebersicht: wideSquare }); + named.karte.enterKoordinate(sample); + const center = named.karte.snapshot().center; + named.karte.reportStandort({ easting: center.easting + 500, northing: center.northing }); + expect(named.karte.snapshot().center).toEqual(center); + expect(named.karte.snapshot().standort?.easting).toBe(center.easting + 500); + }); + + test("a denied Standort leaves the opening center, and a later fix can still center", async () => { + const { karte } = await openSheet(); + karte.reportStandort("denied"); + expect(karte.snapshot().center).toEqual({ easting: 150, northing: 50 }); + expect(karte.snapshot().standort).toBeNull(); + karte.reportStandort({ easting: 12, northing: 18 }); + expect(karte.snapshot().center).toEqual({ easting: 12, northing: 18 }); + }); + + test("no Standort is shown or updated while the app is closed", async () => { + const { karte } = await openSheet({ standort: { easting: 5, northing: 5 } }); + karte.setAppOpen(false); + karte.reportStandort({ easting: 90, northing: 90 }); + expect(karte.snapshot().standort).toEqual({ easting: 5, northing: 5 }); + expect(karte.snapshot().center).toEqual({ easting: 5, northing: 5 }); + }); + + test("centers on the Standort and on the Ziel at 1:25,000", async () => { + const { karte } = await openSheet({ uebersicht: wideSquare }); + const before = karte.snapshot(); + karte.centerOnStandort(); + karte.centerOnZiel(); + expect(karte.snapshot().center).toEqual(before.center); + expect(karte.snapshot().resolution).toBe(before.resolution); + + karte.enterKoordinate(sample); + karte.reportStandort({ + easting: karte.snapshot().ziel!.easting + 100, + northing: karte.snapshot().ziel!.northing, + }); + karte.zoomTo(1.25); + karte.panTo({ easting: 410_000, northing: 5_510_000 }); + karte.centerOnZiel(); + expect(karte.snapshot().resolution).toBe(7); + expect(karte.snapshot().center).toEqual(karte.snapshot().ziel); + + karte.zoomTo(1.25); + karte.centerOnStandort(); + expect(karte.snapshot().resolution).toBe(7); + expect(karte.snapshot().center).toEqual(karte.snapshot().standort); + }); + + test("shows metres below 1000 m and kilometres from 1000 m, and hides a missing end", async () => { + const { karte } = await openSheet({ uebersicht: wideSquare, online: false }); + expect(karte.snapshot().distance).toBeNull(); + karte.enterKoordinate(sample); + expect(karte.snapshot().distance).toBeNull(); + + const ziel = karte.snapshot().ziel!; + karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 500 }); + expect(karte.snapshot().distance).toEqual({ metres: 500, text: "500 m" }); + + karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 999 }); + expect(karte.snapshot().distance?.text).toBe("999 m"); + + karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 1000 }); + expect(karte.snapshot().distance?.text).toBe("1 km"); + + karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 1500 }); + expect(karte.snapshot().distance?.text).toBe("1,5 km"); + + karte.reportStandort({ easting: ziel.easting + 3000, northing: ziel.northing + 4000 }); + expect(karte.snapshot().distance).toEqual({ metres: 5000, text: "5 km" }); + expect(karte.snapshot().center).toEqual(ziel); + }); +}); + +describe("downloading and removing Hessen", () => { + async function view(easting: number, viewport: Viewport = oneTile) { + const opened = await openSheet({ viewport }); + opened.karte.zoomTo(10); + opened.karte.panTo({ easting, northing: 50 }); + opened.karte.setOnline(true); + return opened; + } + + test("starting Hessen marks it heruntergeladen and fetches the current view first", async () => { + const { karte, clock } = await view(250); + karte.startGebiet("Hessen"); + const started = hessen(karte.snapshot()); + expect(started.heruntergeladen).toBe(true); + expect(started.arrived).toBe(0); + expect(started.total).toBe(3); + + await clock.tick(); + expect(appearance(karte, TILE_C)).toBe("drawn"); + expect(hessen(karte.snapshot()).arrived).toBe(1); + karte.panTo({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).not.toBe("drawn"); + + await clock.tick(); + await clock.tick(); + karte.panTo({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).toBe("drawn"); + expect(hessen(karte.snapshot()).arrived).toBe(3); + }); + + test("Kacheln already in the Cache stay usable while Hessen is still downloading", async () => { + const { karte } = await openSheet({ + viewport: threeTiles, + cache: createMemoryCache([TILE_A]), + online: true, + }); + karte.zoomTo(10); + karte.panTo({ easting: 150, northing: 50 }); + karte.startGebiet("Hessen"); + expect(appearance(karte, TILE_A)).toBe("drawn"); + expect(appearance(karte, TILE_B)).toBe("blank-downloading"); + expect(hessen(karte.snapshot()).arrived).toBe(1); + }); + + test("a dropped connection keeps finished Kacheln and pauses the rest", async () => { + const { karte, clock } = await view(250); + karte.startGebiet("Hessen"); + await clock.tick(); + karte.setOnline(false); + expect(appearance(karte, TILE_C)).toBe("drawn"); + karte.panTo({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).toBe("blank-not-on-device"); + await clock.tick(); + expect(appearance(karte, TILE_A)).toBe("blank-not-on-device"); + karte.setOnline(true); + expect(appearance(karte, TILE_A)).toBe("blank-downloading"); + }); + + test("while Hessen is not heruntergeladen, looking around fetches only the current view", async () => { + const { karte, clock } = await view(250); + await clock.tick(); + await clock.tick(); + await clock.tick(); + karte.panTo({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).toBe("blank-downloading"); + expect(hessen(karte.snapshot()).heruntergeladen).toBe(false); + }); + + test("removing Hessen stops the rest and keeps a Kachel that still lies in Usingen", async () => { + const { karte, clock } = await view(250); + await clock.tick(); + karte.startGebiet("Usingen"); + await clock.tick(); + await clock.tick(); + karte.startGebiet("Hessen"); + await clock.tick(); + karte.setOnline(false); + karte.removeGebiet("Hessen"); + + expect(hessen(karte.snapshot()).heruntergeladen).toBe(false); + expect(karte.snapshot().gebiete.find((gebiet) => gebiet.name === "Usingen")!.heruntergeladen).toBe( + true, + ); + expect(appearance(karte, TILE_C)).toBe("blank-not-on-device"); + karte.panTo({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).toBe("drawn"); + karte.panTo({ easting: 150, northing: 50 }); + expect(appearance(karte, TILE_B)).toBe("drawn"); + + karte.setOnline(true); + await clock.tick(); + await clock.tick(); + await clock.tick(); + karte.panTo({ easting: 250, northing: 50 }); + expect(appearance(karte, TILE_C)).not.toBe("drawn"); + }); + + test("removing Wehrheim keeps the overlap and drops the Kachel only Wehrheim held", async () => { + const { karte, clock } = await openSheet({ viewport: threeTiles, online: true }); + karte.zoomTo(10); + karte.panTo({ easting: 150, northing: 50 }); + karte.startGebiet("Usingen"); + karte.startGebiet("Wehrheim"); + await clock.tick(); + await clock.tick(); + await clock.tick(); + karte.setOnline(false); + karte.removeGebiet("Wehrheim"); + expect(appearance(karte, TILE_A)).toBe("drawn"); + expect(appearance(karte, TILE_B)).toBe("drawn"); + expect(appearance(karte, TILE_C)).toBe("blank-not-on-device"); + expect(karte.snapshot().gebiete.find((gebiet) => gebiet.name === "Wehrheim")!.heruntergeladen).toBe( + false, + ); + }); + + test("deleting the whole Cache clears marks and does not start Hessen again", async () => { + const { karte, clock } = await view(250); + karte.startGebiet("Hessen"); + await clock.tick(); + await clock.tick(); + await clock.tick(); + karte.deleteCache(); + expect(hessen(karte.snapshot()).heruntergeladen).toBe(false); + expect(hessen(karte.snapshot()).arrived).toBe(0); + expect(appearance(karte, TILE_C)).toBe("blank-downloading"); + await clock.tick(); + expect(appearance(karte, TILE_C)).toBe("drawn"); + expect(hessen(karte.snapshot()).heruntergeladen).toBe(false); + karte.panTo({ easting: 50, northing: 50 }); + expect(appearance(karte, TILE_A)).not.toBe("drawn"); + }); + + test("Hessen can be started again after it is removed", async () => { + const { karte, clock } = await view(50); + karte.startGebiet("Hessen"); + await clock.tick(); + karte.setOnline(false); + karte.removeGebiet("Hessen"); + expect(appearance(karte, TILE_A)).toBe("blank-not-on-device"); + karte.setOnline(true); + karte.startGebiet("Hessen"); + expect(hessen(karte.snapshot()).heruntergeladen).toBe(true); + await clock.tick(); + expect(appearance(karte, TILE_A)).toBe("drawn"); + }); +}); diff --git a/src/karte/session.ts b/src/karte/session.ts new file mode 100644 index 0000000..34e7711 --- /dev/null +++ b/src/karte/session.ts @@ -0,0 +1,494 @@ +import { maxResolutionMeters } from "../config"; +import { parseKoordinate } from "./koordinate"; +import type { + GebietStatus, + KachelView, + KarteOptions, + KarteSession, + KarteSnapshot, + Point, + Rect, + Uebersicht, + Viewport, +} from "./types"; + +const NOT_ON_DEVICE = "Nicht auf dem Gerät."; + +type CatalogEntry = { extent: Rect; gebiete: string[] }; + +function intersects(a: Rect, b: Rect): boolean { + return a.minE < b.maxE && a.maxE > b.minE && a.minN < b.maxN && a.maxN > b.minN; +} + +function coversPoint(blatt: Rect, point: Point): boolean { + return ( + point.easting >= blatt.minE && + point.easting < blatt.maxE && + point.northing >= blatt.minN && + point.northing < blatt.maxN + ); +} + +function middleOf(rects: Rect[]): Point { + let minE = Infinity; + let minN = Infinity; + let maxE = -Infinity; + let maxN = -Infinity; + for (const rect of rects) { + minE = Math.min(minE, rect.minE); + minN = Math.min(minN, rect.minN); + maxE = Math.max(maxE, rect.maxE); + maxN = Math.max(maxN, rect.maxN); + } + return { easting: (minE + maxE) / 2, northing: (minN + maxN) / 2 }; +} + +function buildCatalog(uebersicht: Uebersicht): Map { + const catalog = new Map(); + const { resolutions, tileSizePx, blaetter, gebiete } = uebersicht; + for (let zi = 0; zi < resolutions.length; zi += 1) { + const ground = resolutions[zi] * tileSizePx; + for (const blatt of blaetter) { + const col0 = Math.floor(blatt.minE / ground); + const col1 = Math.ceil(blatt.maxE / ground) - 1; + const row0 = Math.floor(blatt.minN / ground); + const row1 = Math.ceil(blatt.maxN / ground) - 1; + for (let col = col0; col <= col1; col += 1) { + for (let row = row0; row <= row1; row += 1) { + const id = `${zi}/${col}/${row}`; + if (catalog.has(id)) continue; + const extent: Rect = { + minE: col * ground, + minN: row * ground, + maxE: (col + 1) * ground, + maxN: (row + 1) * ground, + }; + if (!intersects(extent, blatt)) continue; + const names = gebiete + .filter((gebiet) => + gebiet.covers === "all" + ? true + : gebiet.covers.some((rect) => intersects(extent, rect)), + ) + .map((gebiet) => gebiet.name); + catalog.set(id, { extent, gebiete: names }); + } + } + } + } + return catalog; +} + +/** Finest prepared scale that is still at least as detailed as the view. */ +function scaleIndex(resolutions: number[], viewResolution: number): number { + for (let index = resolutions.length - 1; index >= 0; index -= 1) { + if (resolutions[index] <= viewResolution + 1e-9) return index; + } + return 0; +} + +function viewRect(center: Point, resolution: number, viewport: Viewport): Rect { + const halfW = (viewport.widthPx * resolution) / 2; + const halfH = (viewport.heightPx * resolution) / 2; + return { + minE: center.easting - halfW, + maxE: center.easting + halfW, + minN: center.northing - halfH, + maxN: center.northing + halfH, + }; +} + +function cellsInView( + zi: number, + rect: Rect, + resolutions: number[], + tileSizePx: number, +): { id: string }[] { + const ground = resolutions[zi] * tileSizePx; + const col0 = Math.floor(rect.minE / ground); + const col1 = Math.ceil(rect.maxE / ground) - 1; + const row0 = Math.floor(rect.minN / ground); + const row1 = Math.ceil(rect.maxN / ground) - 1; + const cells: { id: string }[] = []; + for (let col = col0; col <= col1; col += 1) { + for (let row = row0; row <= row1; row += 1) { + cells.push({ id: `${zi}/${col}/${row}` }); + } + } + return cells; +} + +function formatDistance(metres: number): string { + if (metres < 1000) return `${Math.round(metres)} m`; + const kilometres = Math.round((metres / 1000) * 10) / 10; + if (Number.isInteger(kilometres)) return `${kilometres} km`; + return `${kilometres.toFixed(1).replace(".", ",")} km`; +} + +function isAbort(error: unknown): boolean { + return error instanceof DOMException && error.name === "AbortError"; +} + +function isPoint(value: Point | "denied" | undefined): value is Point { + return typeof value === "object" && value !== null; +} + +export async function openKarte(options: KarteOptions): Promise { + const { uebersicht, cache, marks, kacheln, config } = options; + const catalog = buildCatalog(uebersicht); + const loaded = await cache.ids(); + const stored = new Set(loaded); + const cached = new Set(loaded.filter((id) => catalog.has(id))); + const markState = await marks.read(); + const heruntergeladen = new Set( + markState.heruntergeladen.filter((name) => + uebersicht.gebiete.some((gebiet) => gebiet.name === name), + ), + ); + + const totals = new Map(uebersicht.gebiete.map((gebiet) => [gebiet.name, 0])); + for (const entry of catalog.values()) { + for (const name of entry.gebiete) { + totals.set(name, (totals.get(name) ?? 0) + 1); + } + } + + let viewport = { ...options.viewport }; + let online = options.online ?? false; + let appOpen = options.appOpen ?? true; + let standort: Point | null = isPoint(options.standort) ? { ...options.standort } : null; + let seenStandort = standort !== null; + let personMovedView = false; + let personEnteredZiel = false; + let ziel: Point | null = null; + let koordinateNotice: string | null = null; + let koordinate = { gitter: "32U MA", rechtswert: "", hochwert: "" }; + let queue: string[] = []; + const inFlight = new Map(); + const failed = new Set(); + const listeners = new Set<() => void>(); + + function arrivedCounts(): Map { + const counts = new Map(uebersicht.gebiete.map((gebiet) => [gebiet.name, 0])); + for (const id of cached) { + const entry = catalog.get(id); + if (!entry) continue; + for (const name of entry.gebiete) { + counts.set(name, (counts.get(name) ?? 0) + 1); + } + } + return counts; + } + + function clamp(resolution: number): number { + const maxResolution = maxResolutionMeters(viewport, config); + return Math.min(Math.max(resolution, config.minResolutionMeters), maxResolution); + } + + function middleOfCached(): Point | null { + let minE = Infinity; + let minN = Infinity; + let maxE = -Infinity; + let maxN = -Infinity; + let any = false; + for (const id of cached) { + const entry = catalog.get(id); + if (!entry) continue; + any = true; + minE = Math.min(minE, entry.extent.minE); + minN = Math.min(minN, entry.extent.minN); + maxE = Math.max(maxE, entry.extent.maxE); + maxN = Math.max(maxN, entry.extent.maxN); + } + if (!any) return null; + return { easting: (minE + maxE) / 2, northing: (minN + maxN) / 2 }; + } + + let center: Point = standort ? { ...standort } : (middleOfCached() ?? middleOf(uebersicht.blaetter)); + let resolution = clamp(config.openResolutionMeters); + + function publish(): void { + for (const listener of listeners) listener(); + } + + function persistMarks(): void { + void marks.write({ heruntergeladen: [...heruntergeladen] }); + } + + function kachelViews(): KachelView[] { + const rect = viewRect(center, resolution, viewport); + const scale = scaleIndex(uebersicht.resolutions, resolution); + const views: KachelView[] = []; + for (const cell of cellsInView(scale, rect, uebersicht.resolutions, uebersicht.tileSizePx)) { + if (!catalog.has(cell.id)) { + views.push({ id: cell.id, appearance: "blank-unknown" }); + continue; + } + if (cached.has(cell.id)) { + views.push({ id: cell.id, appearance: "drawn" }); + continue; + } + const willFetch = appOpen && online && !failed.has(cell.id); + views.push({ + id: cell.id, + appearance: willFetch ? "blank-downloading" : "blank-not-on-device", + }); + } + views.sort((a, b) => a.id.localeCompare(b.id, "en")); + return views; + } + + function rebuild(): void { + const rect = viewRect(center, resolution, viewport); + const scale = scaleIndex(uebersicht.resolutions, resolution); + const viewNow = cellsInView(scale, rect, uebersicht.resolutions, uebersicht.tileSizePx) + .map((cell) => cell.id) + .filter((id) => catalog.has(id)); + for (const id of failed) { + if (!viewNow.includes(id)) failed.delete(id); + } + const viewAny = new Set(); + for (let zi = 0; zi < uebersicht.resolutions.length; zi += 1) { + for (const cell of cellsInView(zi, rect, uebersicht.resolutions, uebersicht.tileSizePx)) { + if (catalog.has(cell.id)) viewAny.add(cell.id); + } + } + const gebietIds: string[] = []; + for (const [id, entry] of catalog) { + if (cached.has(id)) continue; + if (entry.gebiete.some((name) => heruntergeladen.has(name))) gebietIds.push(id); + } + const ordered: string[] = []; + const seen = new Set(); + const push = (id: string) => { + if (seen.has(id) || cached.has(id)) return; + seen.add(id); + ordered.push(id); + }; + for (const id of viewNow) push(id); + for (const id of gebietIds) { + if (viewAny.has(id)) push(id); + } + for (const id of gebietIds) push(id); + queue = ordered; + pump(); + } + + function pump(): void { + if (!appOpen || !online) return; + if (inFlight.size >= 1) return; + const next = queue.find((id) => !cached.has(id) && !failed.has(id) && !inFlight.has(id)); + if (!next) return; + void runOne(next); + } + + async function runOne(id: string): Promise { + const controller = new AbortController(); + inFlight.set(id, controller); + publish(); + try { + const bytes = await kacheln.fetch(id, controller.signal); + if (controller.signal.aborted) return; + cached.add(id); + stored.add(id); + await cache.put(id, bytes); + } catch (error) { + if (!isAbort(error)) failed.add(id); + } finally { + if (inFlight.get(id) === controller) inFlight.delete(id); + rebuild(); + publish(); + } + } + + function abortInFlight(shouldAbort: (id: string) => boolean): void { + for (const [id, controller] of inFlight) { + if (!shouldAbort(id)) continue; + inFlight.delete(id); + controller.abort(); + } + } + + function snapshot(): KarteSnapshot { + const arrived = arrivedCounts(); + const gebiete: GebietStatus[] = uebersicht.gebiete.map((gebiet) => ({ + name: gebiet.name, + heruntergeladen: heruntergeladen.has(gebiet.name), + arrived: arrived.get(gebiet.name) ?? 0, + total: totals.get(gebiet.name) ?? 0, + })); + const distance = + ziel && standort + ? { + metres: Math.hypot(ziel.easting - standort.easting, ziel.northing - standort.northing), + text: "", + } + : null; + if (distance) distance.text = formatDistance(distance.metres); + const views = kachelViews(); + const missingWhileOffline = + !online && views.some((kachel) => kachel.appearance === "blank-not-on-device"); + const notice = koordinateNotice ?? (missingWhileOffline ? NOT_ON_DEVICE : null); + return { + center: { ...center }, + resolution, + northUp: true, + ziel: ziel ? { ...ziel } : null, + standort: standort ? { ...standort } : null, + distance, + notice, + koordinate: { ...koordinate }, + gebiete, + kacheln: views, + }; + } + + function knownGebiet(name: string): boolean { + return uebersicht.gebiete.some((gebiet) => gebiet.name === name); + } + + const session: KarteSession = { + snapshot, + subscribe(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + setViewport(next) { + viewport = { ...next }; + resolution = clamp(resolution); + rebuild(); + publish(); + }, + panTo(next) { + personMovedView = true; + center = { ...next }; + rebuild(); + publish(); + }, + zoomTo(next) { + resolution = clamp(next); + rebuild(); + publish(); + }, + reportStandort(next) { + if (!appOpen) return; + if (next === "denied") { + standort = null; + publish(); + return; + } + standort = { ...next }; + if (!seenStandort && !personMovedView && !personEnteredZiel) { + center = { ...next }; + resolution = clamp(config.openResolutionMeters); + rebuild(); + } + seenStandort = true; + publish(); + }, + setAppOpen(open) { + appOpen = open; + if (!open) abortInFlight(() => true); + else { + failed.clear(); + rebuild(); + } + publish(); + }, + setOnline(next) { + online = next; + if (!next) abortInFlight(() => true); + else { + failed.clear(); + rebuild(); + } + publish(); + }, + enterKoordinate(input) { + const parsed = parseKoordinate(input, (point) => + uebersicht.blaetter.some((blatt) => coversPoint(blatt, point)), + ); + if (!parsed.ok) { + koordinateNotice = parsed.notice; + publish(); + return; + } + koordinateNotice = null; + koordinate = { + gitter: input.gitter, + rechtswert: input.rechtswert, + hochwert: input.hochwert, + }; + ziel = parsed.point; + personEnteredZiel = true; + center = { ...parsed.point }; + resolution = clamp(config.openResolutionMeters); + rebuild(); + publish(); + }, + centerOnStandort() { + if (!standort) return; + personMovedView = true; + center = { ...standort }; + resolution = clamp(config.openResolutionMeters); + rebuild(); + publish(); + }, + centerOnZiel() { + if (!ziel) return; + personMovedView = true; + center = { ...ziel }; + resolution = clamp(config.openResolutionMeters); + rebuild(); + publish(); + }, + startGebiet(name) { + if (!knownGebiet(name)) return; + heruntergeladen.add(name); + persistMarks(); + rebuild(); + publish(); + }, + removeGebiet(name) { + if (!knownGebiet(name)) return; + heruntergeladen.delete(name); + const removeIds: string[] = []; + for (const id of cached) { + const entry = catalog.get(id); + if (!entry?.gebiete.includes(name)) continue; + const kept = entry.gebiete.some( + (gebiet) => gebiet !== name && heruntergeladen.has(gebiet), + ); + if (!kept) removeIds.push(id); + } + for (const id of removeIds) { + cached.delete(id); + stored.delete(id); + } + abortInFlight((id) => { + const entry = catalog.get(id); + if (!entry?.gebiete.includes(name)) return false; + return !entry.gebiete.some((gebiet) => gebiet !== name && heruntergeladen.has(gebiet)); + }); + if (removeIds.length > 0) void cache.delete(removeIds); + persistMarks(); + rebuild(); + publish(); + }, + deleteCache() { + abortInFlight(() => true); + const ids = [...stored]; + cached.clear(); + stored.clear(); + heruntergeladen.clear(); + failed.clear(); + void cache.delete(ids); + persistMarks(); + rebuild(); + publish(); + }, + }; + + if (appOpen && online) rebuild(); + return session; +} diff --git a/src/karte/types.ts b/src/karte/types.ts new file mode 100644 index 0000000..e978435 --- /dev/null +++ b/src/karte/types.ts @@ -0,0 +1,107 @@ +export type Point = { easting: number; northing: number }; + +export type Rect = { minE: number; minN: number; maxE: number; maxN: number }; + +export type Viewport = { widthPx: number; heightPx: number }; + +/** A named set of Kacheln. `"all"` means every Kachel in the Übersicht. */ +export type GebietCoverage = { name: string; covers: "all" | Rect[] }; + +/** + * Catalog shipped with the app. No pixels. + * `resolutions` are metres per pixel, finest first, each coarser step + * covering four times the ground of the finer one. + */ +export type Uebersicht = { + tileSizePx: number; + resolutions: number[]; + blaetter: Rect[]; + gebiete: GebietCoverage[]; +}; + +export type KarteConfig = { + maxSpanMeters: number; + minResolutionMeters: number; + openResolutionMeters: number; +}; + +export type KachelAppearance = + | "drawn" + | "blank-downloading" + | "blank-not-on-device" + | "blank-unknown"; + +export type KachelView = { id: string; appearance: KachelAppearance }; + +export type GebietStatus = { + name: string; + heruntergeladen: boolean; + arrived: number; + total: number; +}; + +export type KoordinateText = { + gitter: string; + rechtswert: string; + hochwert: string; +}; + +/** What the screen shows. Distance text is German (`500 m`, `1,5 km`). */ +export type KarteSnapshot = { + center: Point; + resolution: number; + northUp: true; + ziel: Point | null; + standort: Point | null; + distance: { metres: number; text: string } | null; + notice: string | null; + koordinate: KoordinateText; + gebiete: GebietStatus[]; + kacheln: KachelView[]; +}; + +export interface CachePort { + ids(): Promise; + put(id: string, bytes: Uint8Array): Promise; + delete(ids: string[]): Promise; +} + +export interface MarksPort { + read(): Promise<{ heruntergeladen: string[] }>; + write(state: { heruntergeladen: string[] }): Promise; +} + +/** Fetches Kachel bytes. The session decides when, and stores them in the Cache. */ +export interface KachelPort { + fetch(id: string, signal: AbortSignal): Promise; +} + +export type KarteOptions = { + uebersicht: Uebersicht; + cache: CachePort; + marks: MarksPort; + kacheln: KachelPort; + config: KarteConfig; + viewport: Viewport; + /** A fix the device already has when the Karte opens. */ + standort?: Point | "denied"; + online?: boolean; + appOpen?: boolean; +}; + +export type KarteSession = { + snapshot(): KarteSnapshot; + subscribe(listener: () => void): () => void; + setViewport(viewport: Viewport): void; + panTo(center: Point): void; + zoomTo(resolution: number): void; + reportStandort(standort: Point | "denied"): void; + setAppOpen(open: boolean): void; + setOnline(online: boolean): void; + enterKoordinate(input: KoordinateText): void; + centerOnStandort(): void; + centerOnZiel(): void; + startGebiet(name: string): void; + removeGebiet(name: string): void; + deleteCache(): void; +}; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..8db7b30 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,99 @@ +import "ol/ol.css"; +import "./style.css"; +import { registerSW } from "virtual:pwa-register"; +import { createCacheStorage } from "./adapters/cache-storage"; +import { createKachelFetch } from "./adapters/kachel-fetch"; +import { createMarksDb } from "./adapters/marks-db"; +import { toEtrs, watchStandort } from "./adapters/standort"; +import { shippedConfig } from "./config"; +import { openKarte } from "./karte/session"; +import type { Point, Uebersicht } from "./karte/types"; +import { mountScreen } from "./ui/screen"; + +void main(); + +async function main(): Promise { + const root = document.querySelector("#app"); + if (!(root instanceof HTMLElement)) return; + + if (navigator.storage?.persist) void navigator.storage.persist(); + registerSW({ immediate: true }); + + try { + const [uebersicht, standort] = await Promise.all([ + loadUebersicht(), + readOpeningStandort(), + ]); + const session = await openKarte({ + uebersicht, + cache: createCacheStorage(), + marks: createMarksDb(), + kacheln: createKachelFetch(), + config: shippedConfig, + viewport: { + widthPx: window.innerWidth, + heightPx: window.innerHeight, + }, + ...(standort !== undefined ? { standort } : {}), + online: navigator.onLine, + appOpen: document.visibilityState === "visible", + }); + + mountScreen(root, session, uebersicht); + bindDevice(session); + } catch { + root.replaceChildren(); + const message = document.createElement("p"); + message.className = "boot-error"; + message.textContent = "Die Karte konnte nicht geöffnet werden."; + root.append(message); + } +} + +async function loadUebersicht(): Promise { + const response = await fetch("/uebersicht.json"); + if (!response.ok) throw new Error("Übersicht fehlt"); + return (await response.json()) as Uebersicht; +} + +function readOpeningStandort(): Promise { + if (!navigator.geolocation) return Promise.resolve("denied"); + return new Promise((resolve) => { + navigator.geolocation.getCurrentPosition( + (position) => { + resolve(toEtrs(position.coords.longitude, position.coords.latitude)); + }, + (error) => { + if (error.code === error.TIMEOUT) resolve(undefined); + else resolve("denied"); + }, + { maximumAge: 30_000, timeout: 2_000 }, + ); + }); +} + +function bindDevice(session: Awaited>): void { + let stopWatch: (() => void) | null = null; + + const watch = (open: boolean) => { + stopWatch?.(); + stopWatch = null; + if (!open) return; + stopWatch = watchStandort((standort) => session.reportStandort(standort)); + }; + + document.addEventListener("visibilitychange", () => { + const open = document.visibilityState === "visible"; + if (!open) { + watch(false); + session.setAppOpen(false); + return; + } + session.setAppOpen(true); + watch(true); + }); + + window.addEventListener("online", () => session.setOnline(true)); + window.addEventListener("offline", () => session.setOnline(false)); + watch(document.visibilityState === "visible"); +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..a1307d2 --- /dev/null +++ b/src/style.css @@ -0,0 +1,240 @@ +:root { + color: #1c1915; + background: #1e3328; + font-family: system-ui, sans-serif; + font-size: 16px; + line-height: 1.35; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + height: 100%; + overflow: hidden; + background: #1e3328; +} + +button, +input { + font: inherit; + font-size: 16px; +} + +#app { + height: 100dvh; + max-height: 100dvh; + display: flex; + flex-direction: column; + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + background: #1e3328; +} + +.map { + flex: 1 1 auto; + min-height: 40dvh; + position: relative; + background: #e7e1d4; +} + +.map .ol-viewport { + background: #e7e1d4; +} + +.panel { + flex: 0 1 auto; + max-height: 52dvh; + overflow: auto; + display: flex; + flex-direction: column; + gap: 10px; + background: #e7e1d4; + color: #1c1915; + border-bottom: 4px solid #1e3328; + padding: 8px max(16px, env(safe-area-inset-right)) 10px max(16px, env(safe-area-inset-left)); +} + +.panel-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.panel-head h1 { + flex: 1 1 auto; +} + +.head-actions { + display: flex; + flex: 0 0 auto; + gap: 8px; +} + +.fold-toggle, +.cache-toggle { + flex: 0 0 auto; +} + +.cache-toggle[aria-expanded="true"] { + background: #1e3328; + color: #e7e1d4; +} + +.fold { + display: flex; + flex-direction: column; + gap: 8px; +} + +h1 { + margin: 0; + font-size: 20px; + font-weight: 700; + letter-spacing: 0.06em; + color: #1e3328; +} + +form, +.actions, +.gebiet { + display: flex; + flex-direction: column; + gap: 8px; +} + +.fields { + display: grid; + grid-template-columns: 1fr; + gap: 8px; +} + +label { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 16px; + font-weight: 600; +} + +input { + width: 100%; + min-height: 44px; + padding: 8px 10px; + border: 1px solid #1e3328; + border-radius: 2px; + background: #f4f0e6; + color: #1c1915; +} + +.fields input { + width: 33%; + min-width: 6.5rem; +} + +button { + min-height: 44px; + padding: 10px 14px; + border: 1px solid #1e3328; + border-radius: 2px; + background: #1e3328; + color: #e7e1d4; + font-weight: 600; +} + +button.secondary { + background: #e7e1d4; + color: #1e3328; +} + +.actions { + flex-direction: row; +} + +.actions button { + flex: 1 1 0; +} + +.gebiet p, +.distance, +.notice, +.boot-error { + margin: 0; + font-size: 16px; +} + +.distance { + font-size: 18px; + font-weight: 700; +} + +.notice { + padding: 10px 12px; + background: #1e3328; + color: #e7e1d4; +} + +.boot-error { + margin: 24px 16px; + color: #e7e1d4; +} + +[hidden] { + display: none !important; +} + +dialog { + border: none; + max-width: min(22rem, calc(100vw - 32px)); + padding: 20px 16px max(16px, env(safe-area-inset-bottom)); + background: #e7e1d4; + color: #1c1915; +} + +dialog::backdrop { + background: rgba(28, 25, 21, 0.5); +} + +.ask { + margin: 0 0 8px; + font-size: 20px; + font-weight: 700; +} + +.explain { + margin: 0 0 16px; + font-size: 16px; +} + +.dialog-actions { + display: flex; + gap: 8px; +} + +.dialog-actions button { + flex: 1 1 0; +} + +button:focus-visible, +input:focus-visible { + outline: 2px solid #1d4e89; + outline-offset: 2px; +} + +@media (min-width: 520px) { + .fields { + grid-template-columns: repeat(3, max-content); + justify-content: start; + column-gap: 16px; + } + + .fields input { + width: 8.5rem; + } +} diff --git a/src/ui/map.ts b/src/ui/map.ts new file mode 100644 index 0000000..f87b2f6 --- /dev/null +++ b/src/ui/map.ts @@ -0,0 +1,340 @@ +import Map from "ol/Map"; +import View from "ol/View"; +import Feature from "ol/Feature"; +import TileLayer from "ol/layer/Tile"; +import VectorLayer from "ol/layer/Vector"; +import OlPoint from "ol/geom/Point"; +import { get as getProjection } from "ol/proj"; +import { register } from "ol/proj/proj4"; +import XYZ from "ol/source/XYZ"; +import VectorSource from "ol/source/Vector"; +import TileGrid from "ol/tilegrid/TileGrid"; +import ImageTile from "ol/ImageTile"; +import type Tile from "ol/Tile"; +import CircleStyle from "ol/style/Circle"; +import Fill from "ol/style/Fill"; +import Stroke from "ol/style/Stroke"; +import Style from "ol/style/Style"; +import { defaults as defaultInteractions } from "ol/interaction/defaults"; +import proj4 from "proj4"; +import { maxResolutionMeters, shippedConfig } from "../config"; +import { kachelCacheName, kachelUrl } from "../adapters/cache-storage"; +import type { KarteSession, KarteSnapshot, Point, Uebersicht, Viewport } from "../karte/types"; + +const ETRS25832 = + "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"; + +const BLANK = + "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; + +const PAPER = "#e7e1d4"; +const ZIEL = "#b42318"; +const STANDORT = "#1d4e89"; + +proj4.defs("EPSG:25832", ETRS25832); +register(proj4); + +export type FieldMap = { + apply(snapshot: KarteSnapshot): void; + refreshTiles(): void; +}; + +class KachelSource extends XYZ { + private generation = 0; + + reload(): void { + this.generation += 1; + this.setKey(`kacheln-${this.generation}`); + this.refresh(); + } +} + +function expandedExtent(uebersicht: Uebersicht): [number, number, number, number] { + let minE = Infinity; + let minN = Infinity; + let maxE = -Infinity; + let maxN = -Infinity; + for (const blatt of uebersicht.blaetter) { + minE = Math.min(minE, blatt.minE); + minN = Math.min(minN, blatt.minN); + maxE = Math.max(maxE, blatt.maxE); + maxN = Math.max(maxN, blatt.maxN); + } + const coarsest = uebersicht.resolutions[uebersicht.resolutions.length - 1] ?? 1; + const pad = coarsest * uebersicht.tileSizePx; + if (!Number.isFinite(minE)) return [0, 0, 1_000_000, 10_000_000]; + return [minE - pad, minN - pad, maxE + pad, maxN + pad]; +} + +function markStyle(color: string): Style { + return new Style({ + image: new CircleStyle({ + radius: 9, + fill: new Fill({ color }), + stroke: new Stroke({ color: "#ffffff", width: 3 }), + }), + }); +} + +function loadFromCache(tile: Tile, src: string): void { + if (!(tile instanceof ImageTile)) return; + const image = tile.getImage(); + if (!(image instanceof HTMLImageElement)) return; + + const showBlank = () => { + image.src = BLANK; + }; + + void caches + .open(kachelCacheName) + .then(async (cache) => { + const hit = await cache.match(src); + if (!hit) { + showBlank(); + return; + } + const blob = await hit.blob(); + const objectUrl = URL.createObjectURL(blob); + const release = () => URL.revokeObjectURL(objectUrl); + image.addEventListener("load", release, { once: true }); + image.addEventListener( + "error", + () => { + release(); + showBlank(); + }, + { once: true }, + ); + image.src = objectUrl; + }) + .catch(showBlank); +} + +function place(source: VectorSource, feature: Feature, point: Point | null): void { + if (!point) { + if (source.hasFeature(feature)) source.removeFeature(feature); + return; + } + feature.getGeometry()?.setCoordinates([point.easting, point.northing]); + if (!source.hasFeature(feature)) source.addFeature(feature); +} + +export function createFieldMap( + target: HTMLElement, + uebersicht: Uebersicht, + session: KarteSession, +): FieldMap { + target.style.background = PAPER; + + const projection = getProjection("EPSG:25832"); + if (!projection) throw new Error("EPSG:25832 ist nicht registriert."); + const extent = expandedExtent(uebersicht); + projection.setExtent(extent); + + const tileSize = uebersicht.tileSizePx; + const resolutions = [...uebersicht.resolutions].reverse(); + const tileGrid = new TileGrid({ + // Multiple of every tile ground (320 m × 2ⁿ), so coarser Kacheln sit on the sheet grid. + origin: [0, 10_240_000], + resolutions, + tileSize, + extent, + }); + + const source = new KachelSource({ + projection, + tileGrid, + // Prefer the sharper prepared Kachel, so a zoom-in shows TIFF pixels + // instead of an enlarged coarser picture. + zDirection: -1, + wrapX: false, + transition: 0, + tileUrlFunction(tileCoord) { + const finestIndex = uebersicht.resolutions.length - 1 - tileCoord[0]; + if (finestIndex < 0 || finestIndex >= uebersicht.resolutions.length) return undefined; + const resolution = tileGrid.getResolution(tileCoord[0]); + const tileExtent = tileGrid.getTileCoordExtent(tileCoord); + const ground = resolution * tileSize; + const col = Math.floor(tileExtent[0] / ground + 1e-9); + const row = Math.floor(tileExtent[1] / ground + 1e-9); + return kachelUrl(`${finestIndex}/${col}/${row}`); + }, + tileLoadFunction: loadFromCache, + }); + + const markers = new VectorSource(); + const ziel = new Feature({ geometry: new OlPoint([0, 0]) }); + const standort = new Feature({ geometry: new OlPoint([0, 0]) }); + ziel.setStyle(markStyle(ZIEL)); + standort.setStyle(markStyle(STANDORT)); + + const opening = session.snapshot(); + const openingViewport = viewportOf(target); + let view = makeView( + projection, + [opening.center.easting, opening.center.northing], + opening.resolution, + maxResolutionMeters(openingViewport, shippedConfig), + ); + + const map = new Map({ + target, + layers: [ + new TileLayer({ source }), + new VectorLayer({ + source: markers, + updateWhileAnimating: true, + updateWhileInteracting: true, + }), + ], + view, + controls: [], + interactions: defaultInteractions({ + altShiftDragRotate: false, + pinchRotate: false, + doubleClickZoom: false, + keyboard: false, + shiftDragZoom: false, + }), + }); + + let applying = false; + let gesture = false; + let reportedWidth = 0; + let reportedHeight = 0; + + const pushView = () => { + const current = map.getView(); + const center = current.getCenter(); + const resolution = current.getResolution(); + if (!center || resolution === undefined) return; + session.panTo({ easting: center[0], northing: center[1] }); + session.zoomTo(resolution); + }; + + const rememberGesture = () => { + if (gesture) return; + gesture = true; + pushView(); + }; + + map.on("pointerdrag", rememberGesture); + map.getViewport()?.addEventListener("wheel", rememberGesture, { passive: true }); + map.on("moveend", () => { + if (applying) return; + const current = map.getView(); + if (!gesture || current.getInteracting() || current.getAnimating()) return; + gesture = false; + pushView(); + }); + + const limitResolution = (viewport: Viewport) => { + const maxResolution = maxResolutionMeters(viewport, shippedConfig); + const current = map.getView(); + if (Math.abs(current.getMaxResolution() - maxResolution) < 1e-9) return; + const center = current.getCenter(); + const resolution = current.getResolution(); + if (!center || resolution === undefined) return; + applying = true; + view = makeView( + projection, + [center[0], center[1]], + Math.min(Math.max(resolution, shippedConfig.minResolutionMeters), maxResolution), + maxResolution, + ); + map.setView(view); + requestAnimationFrame(() => { + applying = false; + }); + }; + + const reportSize = () => { + const size = map.getSize(); + if (!size) return; + const widthPx = Math.round(size[0]); + const heightPx = Math.round(size[1]); + if (widthPx < 1 || heightPx < 1) return; + if (widthPx === reportedWidth && heightPx === reportedHeight) return; + reportedWidth = widthPx; + reportedHeight = heightPx; + const viewport = { widthPx, heightPx }; + const current = map.getView(); + if (!current.getInteracting() && !current.getAnimating()) limitResolution(viewport); + session.setViewport(viewport); + }; + + map.on("change:size", reportSize); + reportSize(); + + return { + apply(snapshot) { + place(markers, ziel, snapshot.ziel); + place(markers, standort, snapshot.standort); + + const current = map.getView(); + if (current.getInteracting() || current.getAnimating()) return; + const size = map.getSize(); + if (size && size[0] >= 1 && size[1] >= 1) { + limitResolution({ + widthPx: Math.round(size[0]), + heightPx: Math.round(size[1]), + }); + } + const settled = map.getView(); + const center = settled.getCenter(); + const resolution = settled.getResolution(); + const nextCenter: [number, number] = [snapshot.center.easting, snapshot.center.northing]; + const sameCenter = + center !== undefined && + Math.abs(center[0] - nextCenter[0]) < 0.01 && + Math.abs(center[1] - nextCenter[1]) < 0.01; + const sameResolution = + resolution !== undefined && Math.abs(resolution - snapshot.resolution) < 1e-6; + if (sameCenter && sameResolution) return; + + applying = true; + settled.setCenter(nextCenter); + settled.setResolution(snapshot.resolution); + settled.setRotation(0); + requestAnimationFrame(() => { + applying = false; + }); + }, + refreshTiles() { + source.reload(); + }, + }; +} + +function viewportOf(target: HTMLElement): Viewport { + const widthPx = Math.round(target.clientWidth) || window.innerWidth; + const heightPx = Math.round(target.clientHeight) || window.innerHeight; + return { + widthPx: Math.max(widthPx, 1), + heightPx: Math.max(heightPx, 1), + }; +} + +function makeView( + projection: NonNullable>, + center: [number, number], + resolution: number, + maxResolution: number, +): View { + const minResolution = shippedConfig.minResolutionMeters; + const farthest = Math.max(maxResolution, minResolution); + return new View({ + projection, + center, + resolution: Math.min(Math.max(resolution, minResolution), farthest), + rotation: 0, + enableRotation: false, + // A two-step list keeps the stop exactly at one TIFF pixel. + // Passing minResolution alone lets OpenLayers snap that stop to a + // power of two, which stays coarser than the sheet. + resolutions: [farthest, minResolution], + constrainResolution: false, + smoothResolutionConstraint: false, + multiWorld: false, + }); +} diff --git a/src/ui/screen.ts b/src/ui/screen.ts new file mode 100644 index 0000000..3eec664 --- /dev/null +++ b/src/ui/screen.ts @@ -0,0 +1,244 @@ +import type { GebietStatus, KarteSession, KarteSnapshot, Uebersicht } from "../karte/types"; +import { createFieldMap } from "./map"; + +type ConfirmKind = "hessen" | "cache"; +type GebietMode = "download" | "remove" | "none"; + +export function mountScreen( + root: HTMLElement, + session: KarteSession, + uebersicht: Uebersicht, +): void { + root.replaceChildren(); + + const mapElement = document.createElement("div"); + mapElement.className = "map"; + + const panel = document.createElement("section"); + panel.className = "panel"; + panel.innerHTML = ` +
+

Karte

+
+ + +
+
+
+
+
+ + + +
+ +
+
+ + +
+
+ + + + `; + + const dialog = document.createElement("dialog"); + dialog.innerHTML = ` +

+

+
+ + +
+ `; + + root.append(panel, mapElement, dialog); + + const fold = panel.querySelector("#koordinaten"); + const foldToggle = panel.querySelector(".fold-toggle"); + const cacheFold = panel.querySelector("#cache"); + const cacheToggle = panel.querySelector(".cache-toggle"); + const form = panel.querySelector("form"); + const gitter = panel.querySelector('input[name="gitter"]'); + const rechtswert = panel.querySelector('input[name="rechtswert"]'); + const hochwert = panel.querySelector('input[name="hochwert"]'); + const distance = panel.querySelector(".distance"); + const standortButton = panel.querySelector(".actions button:nth-child(1)"); + const zielButton = panel.querySelector(".actions button:nth-child(2)"); + const gebietBlock = panel.querySelector(".gebiet"); + const gebietText = panel.querySelector(".gebiet p"); + const gebietButton = panel.querySelector(".gebiet button"); + const wipeButton = panel.querySelector(".wipe"); + const notice = panel.querySelector(".notice"); + const question = dialog.querySelector(".ask"); + const explain = dialog.querySelector(".explain"); + const confirmButton = dialog.querySelector(".confirm"); + const cancelButton = dialog.querySelector(".cancel"); + + if ( + !fold || + !foldToggle || + !cacheFold || + !cacheToggle || + !form || + !gitter || + !rechtswert || + !hochwert || + !distance || + !standortButton || + !zielButton || + !gebietBlock || + !gebietText || + !gebietButton || + !wipeButton || + !notice || + !question || + !explain || + !confirmButton || + !cancelButton + ) { + return; + } + + const opening = session.snapshot(); + gitter.value = opening.koordinate.gitter; + rechtswert.value = opening.koordinate.rechtswert; + hochwert.value = opening.koordinate.hochwert; + + const fieldMap = createFieldMap(mapElement, uebersicht, session); + let gebietMode: GebietMode = "none"; + let pending: ConfirmKind | null = null; + let kachelKey = kachelnOf(session.snapshot()); + + form.addEventListener("submit", (event) => { + event.preventDefault(); + session.enterKoordinate({ + gitter: gitter.value, + rechtswert: rechtswert.value, + hochwert: hochwert.value, + }); + }); + + foldToggle.addEventListener("click", () => { + const open = fold.hasAttribute("hidden"); + fold.toggleAttribute("hidden", !open); + foldToggle.setAttribute("aria-expanded", open ? "true" : "false"); + foldToggle.textContent = open ? "Einklappen" : "Koordinaten"; + }); + + cacheToggle.addEventListener("click", () => { + const open = cacheFold.hasAttribute("hidden"); + cacheFold.toggleAttribute("hidden", !open); + cacheToggle.setAttribute("aria-expanded", open ? "true" : "false"); + }); + + standortButton.addEventListener("click", () => session.centerOnStandort()); + zielButton.addEventListener("click", () => session.centerOnZiel()); + gebietButton.addEventListener("click", () => { + if (gebietMode === "download") session.startGebiet("Hessen"); + if (gebietMode === "remove") ask("hessen"); + }); + wipeButton.addEventListener("click", () => ask("cache")); + + confirmButton.addEventListener("click", () => { + const kind = pending; + pending = null; + dialog.close(); + if (kind === "hessen") session.removeGebiet("Hessen"); + if (kind === "cache") session.deleteCache(); + }); + + const dismiss = () => { + pending = null; + if (dialog.open) dialog.close(); + }; + cancelButton.addEventListener("click", dismiss); + dialog.addEventListener("cancel", () => { + pending = null; + }); + + const render = () => { + const snapshot = session.snapshot(); + fieldMap.apply(snapshot); + const nextKey = kachelnOf(snapshot); + if (nextKey !== kachelKey) { + kachelKey = nextKey; + fieldMap.refreshTiles(); + } + renderPanel(snapshot); + }; + + const renderPanel = (snapshot: KarteSnapshot) => { + if (snapshot.distance) { + distance.hidden = false; + distance.textContent = `Entfernung ${snapshot.distance.text}`; + } else { + distance.hidden = true; + distance.textContent = ""; + } + + if (snapshot.notice) { + notice.hidden = false; + notice.textContent = snapshot.notice; + } else { + notice.hidden = true; + notice.textContent = ""; + } + + renderHessen(snapshot.gebiete.find((gebiet) => gebiet.name === "Hessen")); + }; + + const renderHessen = (hessen: GebietStatus | undefined) => { + if (!hessen) { + gebietBlock.hidden = true; + gebietMode = "none"; + return; + } + gebietBlock.hidden = false; + if (!hessen.heruntergeladen) { + gebietText.hidden = true; + gebietText.textContent = ""; + gebietButton.textContent = "Hessen herunterladen"; + gebietButton.classList.remove("secondary"); + gebietMode = "download"; + return; + } + gebietMode = "remove"; + gebietButton.textContent = "Hessen entfernen"; + gebietButton.classList.add("secondary"); + gebietText.hidden = false; + gebietText.textContent = + hessen.arrived === hessen.total + ? "Hessen heruntergeladen" + : `Hessen: ${hessen.arrived} von ${hessen.total} Kacheln`; + }; + + const ask = (kind: ConfirmKind) => { + if (dialog.open) return; + pending = kind; + if (kind === "hessen") { + question.textContent = "Hessen entfernen?"; + explain.textContent = "Die Kacheln von Hessen werden gelöscht."; + confirmButton.textContent = "Entfernen"; + } else { + question.textContent = "Den ganzen Cache löschen?"; + explain.textContent = "Alle Kacheln werden gelöscht."; + confirmButton.textContent = "Löschen"; + } + dialog.showModal(); + }; + + session.subscribe(render); + render(); +} + +function kachelnOf(snapshot: KarteSnapshot): string { + return snapshot.kacheln.map((kachel) => `${kachel.id}:${kachel.appearance}`).join("\n"); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..98ad208 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noEmit": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "isolatedModules": true, + "types": ["vitest/globals"] + }, + "include": ["src"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..6ffca6f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,71 @@ +import fs from "node:fs"; +import path from "node:path"; +import { defineConfig } from "vitest/config"; +import { VitePWA } from "vite-plugin-pwa"; + +/** Drop the previous app build. Kacheln, icons, and the Übersicht stay. */ +function replaceGeneratedApp() { + return { + name: "replace-generated-app", + apply: "build" as const, + buildStart() { + const outDir = path.resolve("public"); + fs.rmSync(path.join(outDir, "assets"), { recursive: true, force: true }); + for (const name of ["index.html", "sw.js", "manifest.webmanifest"]) { + fs.rmSync(path.join(outDir, name), { force: true }); + } + if (!fs.existsSync(outDir)) return; + for (const name of fs.readdirSync(outDir)) { + if (name.startsWith("workbox-") && name.endsWith(".js")) { + fs.rmSync(path.join(outDir, name)); + } + } + }, + }; +} + +export default defineConfig({ + plugins: [ + replaceGeneratedApp(), + VitePWA({ + registerType: "autoUpdate", + includeAssets: ["icon-192.png", "icon-512.png", "uebersicht.json"], + manifest: { + name: "Karte", + short_name: "Karte", + description: "DTK25-Karte für das Gelände", + lang: "de", + start_url: "/", + scope: "/", + display: "standalone", + background_color: "#e7e1d4", + theme_color: "#1e3328", + icons: [ + { src: "icon-192.png", sizes: "192x192", type: "image/png" }, + { + src: "icon-512.png", + sizes: "512x512", + type: "image/png", + purpose: "any maskable", + }, + ], + }, + workbox: { + // Top-level and assets/ only. A recursive glob would walk every Kachel. + globPatterns: ["*.{html,js,svg,woff2}", "assets/**/*.{js,css,woff2}"], + globIgnores: ["**/node_modules/**/*", "kacheln/**"], + navigateFallback: "index.html", + }, + }), + ], + build: { + outDir: "public", + emptyOutDir: false, + copyPublicDir: false, + }, + test: { + environment: "node", + include: ["src/**/*.test.ts"], + globals: true, + }, +});