From 6733901d9984f5a69b58b0ae500b061567b18c71 Mon Sep 17 00:00:00 2001 From: Git Manager GUI Date: Mon, 14 Sep 2026 20:56:41 +0200 Subject: [PATCH] Upload via GUI (10 Dateien) --- .gitignore | 4 + README.md | 157 ++ package-lock.json | 5001 +++++++++++++++++++++++++++++++++++ package.json | 67 + renderer/app.js | 2228 ++++++++++++++++ renderer/index.html | 109 + renderer/styles.css | 452 ++++ web-client/index.html | 135 + web-client/offline-store.js | 91 + web-client/pair-client.js | 435 +++ 10 files changed, 8679 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 renderer/app.js create mode 100644 renderer/index.html create mode 100644 renderer/styles.css create mode 100644 web-client/index.html create mode 100644 web-client/offline-store.js create mode 100644 web-client/pair-client.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e399c9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.fellakte.lock +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..59538ca --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +# Fellakte — Anleitung + +Die digitale Patientenmappe für die Tierheilpraxis Iris Hack. Ersetzt die +Papier-Karteikarte: ein Ordner pro Tier mit allem drin — Stammdaten, Anamnese, +Notizen, Körperkarte, Termine, Dokumente, Unterschriften. + +Alle Daten liegen **nur bei dir** (auf den Praxis-PCs bzw. dem gemeinsamen Ordner) — +keine Cloud, keine Internetverbindung nötig für den täglichen Betrieb. + +--- + +## 1. Erste Einrichtung (einmalig, auf dem ersten PC) + +1. Die Installationsdatei ausführen (`Fellakte Setup X.X.X.exe` unter Windows, + die `.dmg` auf dem Mac). +2. **Windows:** Dem Installationsassistenten folgen (Fertigstellen reicht, Standard- + Einstellungen lassen). **Mac:** Die `.dmg` öffnen, Fellakte in den + Programme-Ordner ziehen. Da die App nicht käuflich signiert ist, zeigt macOS + beim allerersten Start eine Warnung — dann mit **Rechtsklick auf Fellakte → + Öffnen → nochmal Öffnen bestätigen**. Danach startet sie ganz normal per + Doppelklick. +3. Beim ersten Start fragt Fellakte nach einem **Datenordner**. Hier legt sie + alles ab (Kartei + Fotos/Dokumente). Wichtig für später: diesen Ordner an + einem Ort wählen, den auch der zweite PC erreichen kann — z. B. ein + gemeinsames Netzlaufwerk, ein NAS, oder ein synchronisierter Ordner + (OneDrive, Dropbox). Nutzt du nur einen PC, reicht ein ganz normaler Ordner. + +## 2. Zweiten PC (oder Mac) einrichten + +Genau wie Schritt 1 — nur bei der Frage nach dem Datenordner **denselben Ordner** +auswählen wie beim ersten PC (also z. B. dasselbe Netzlaufwerk). Danach sehen +beide PCs dieselbe Kartei. + +**Faustregel:** Nicht an zwei Geräten gleichzeitig an derselben Akte arbeiten. +Fellakte warnt, wenn ein anderes Gerät den Ordner kürzlich benutzt hat — das ist +aber nur ein Hinweis, keine echte Sperre. + +## 3. Einen Besitzer & ein Tier anlegen + +- Links auf **"+ Neuer Besitzer"** → Name, Telefon, E-Mail, Adresse eintragen +- Beim Besitzer auf das **+**-Symbol klicken → neues Tier anlegen (Name, Tierart, + Rasse, Geburtsdatum, Gewicht, Chip-Nummer) +- Ein Besitzer kann mehrere Tiere haben — einfach mehrfach auf **+** klicken + +Alle Werte lassen sich später jederzeit anklicken und ändern (speichert +automatisch). + +## 4. Die Mappe eines Tieres + +Nach Klick auf ein Tier links öffnen sich oben die Reiter: + +| Reiter | Wofür | +|---|---| +| **Stammdaten** | Grunddaten des Tiers + Besitzerdaten, Status (aktiv/verstorben) | +| **Anamnese** | Der ausführliche Fragebogen (Herkunft, Haltung, Ernährung, Verhalten, Vorerkrankungen …) | +| **Notizen** | Kurze, schnelle Einträge — ein Textfeld, Enter drücken zum Speichern | +| **Behandlung** | Klickbare Körperkarte zum Markieren von Befunden + Untersuchungsbogen (Puls, Temperatur, …) | +| **Verlauf** | Größere Einträge mit Titel (z. B. "Kontrolltermin") | +| **Labor** | Laborbefunde mit einzelnen Werten | +| **Bilder** | Fotos hochladen | +| **Dokumente** | Sonstige Dateien hochladen + hier entstehen auch alle erzeugten PDFs | +| **Datenschutz** | DSGVO-Einwilligung mit Checkliste + Unterschrift | +| **Behandlungsvertrag** | Vertragstext + Unterschrift | + +### Befunde auf der Körperkarte markieren + +Im Reiter **Behandlung** auf die Körper-Silhouette tippen/klicken → Notiz +eintragen, Einstufung wählen (unauffällig/beobachten/auffällig) → Speichern. +Vorhandene Markierungen antippen, um sie zu bearbeiten oder zu löschen. + +### Unterschreiben lassen (Datenschutz / Behandlungsvertrag) + +Im jeweiligen Reiter auf **"Auf dem Tablet unterschreiben lassen"** — das Gerät +(Tablet, Touchscreen-PC) dem Kunden reichen, unterschreiben lassen, speichern. +Es wird automatisch ein PDF erzeugt (landet im Reiter Dokumente) und — falls +E-Mail-Versand eingerichtet ist (siehe Einstellungen) — direkt an den Kunden +verschickt. + +## 5. Vorlagen (Ernährungsplan, Rezept) + +Im Reiter **Dokumente** unter "Vorlagen erstellen" → gewünschte Vorlage +auswählen, ausfüllen, "PDF erstellen". Landet ebenfalls im Reiter Dokumente, +mit eurem Briefkopf. + +## 6. Kalender + +Oben auf **"Kalender"** wechseln. **"+ Neuer Termin"** → Terminart antippen +(füllt Titel + übliche Dauer automatisch aus), Datum/Uhrzeit/Ort eintragen. +Termine mit Uhrzeit lösen automatisch eine Erinnerung aus (Standard: 30 Minuten +vorher, einstellbar unter Einstellungen). + +## 7. Verzeichnis + +Oben auf **"Verzeichnis"** — durchsuchbare Liste von Präparaten/Mitteln +(z. B. Bachblüten). Eigene Einträge über "+ Neuer Eintrag" ergänzen. + +## 8. Datensicherung + +Läuft **automatisch** einmal täglich im Hintergrund. Unter dem Zahnrad-Symbol +oben rechts → Reiter **Datensicherung** siehst du, wann zuletzt gesichert wurde, +kannst manuell sichern, und im Notfall eine ältere Sicherung wiederherstellen +(dabei wird der aktuelle Stand vorher automatisch selbst gesichert, nichts geht +verloren). + +## 9. Tablet oder Handy verbinden + +Die Kartei lässt sich auch von einem Tablet, Handy oder zusätzlichen Mac aus +nutzen — läuft komplett über euer eigenes WLAN, keine Cloud, keine Kosten. + +**Einmalig koppeln:** +1. Am PC: Zahnrad-Symbol → Reiter **Tablet-Zugang** → **"Neues Gerät koppeln"** + → zeigt einen QR-Code + 6-stelligen Code (10 Minuten gültig) +2. Am Tablet/Handy (im **selben WLAN**): QR-Code mit der Kamera scannen, oder + die angezeigte Adresse im Browser öffnen und den Code eintippen +3. Im Browser-Menü **"Zum Home-Bildschirm hinzufügen"** — danach wie eine + normale App mit eigenem Symbol startbar + +**Im Alltag:** +- Vor der Fahrt zum Kunden: das betreffende Tier noch im WLAN kurz öffnen + (merkt es sich für offline) +- Beim Kunden: Notizen und Befunde funktionieren auch **ganz ohne Empfang** — + oben rechts steht dann "Offline — X wartend" +- Zurück im WLAN: gleicht sich automatisch ab, kein Knopf zu drücken +- PDFs erstellen, Unterschrift und E-Mail-Versand brauchen dagegen Verbindung + zu einem der Praxis-PCs (der muss dafür an sein und die App offen haben) + +**Zugriff entziehen:** Zahnrad → Tablet-Zugang → beim jeweiligen Gerät +"Zugriff entziehen". + +## 10. Ein Tier ist verstorben + +Im Reiter Stammdaten → "Als verstorben markieren". Bleibt in der Kartei +erhalten (mit kleinem Symbol markiert), lässt sich bei Bedarf rückgängig machen. + +## 11. Einen Besitzer löschen + +Neben dem Besitzernamen links auf das Mülleimer-Symbol. **Löscht unwiderruflich +auch alle seine Tiere** (inkl. Notizen, Dokumente, Fotos, Unterschriften) — vorher +wird noch mal nachgefragt. + +## 12. Lexware + +Oben rechts der Knopf "Lexware" öffnet direkt Lexware (Pfad einmalig unter +Zahnrad → Einstellungen hinterlegen). + +## 13. Bei Problemen + +| Was passiert | Was tun | +|---|---| +| "Datenordner wurde zuletzt woanders benutzt" | Kurz nachfragen, ob der andere PC gerade wirklich läuft, bevor beide gleichzeitig speichern | +| Tablet zeigt "Code falsch/abgelaufen" | Am PC einen neuen Code erzeugen (10 Min. gültig) | +| Tablet zeigt "Offline" obwohl WLAN da ist | Prüfen, ob der Praxis-PC an ist und Fellakte dort geöffnet ist | +| E-Mail-Versand klappt nicht | Zahnrad → Einstellungen → E-Mail-Zugangsdaten prüfen, "Verbindung testen" | +| Etwas ist aus Versehen falsch eingetragen worden | Wert einfach anklicken und korrigieren (speichert automatisch) | +| Größeres Durcheinander | Zahnrad → Datensicherung → passende ältere Sicherung wiederherstellen | + +Bei allem, was hier nicht auftaucht: einfach schildern, was passiert ist. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bd8a986 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5001 @@ +{ + "name": "fellakte", + "version": "0.7.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fellakte", + "version": "0.7.0", + "dependencies": { + "express": "^5.2.1", + "nodemailer": "^10.0.9", + "qrcode": "^1.5.4", + "sql.js": "^1.11.0" + }, + "devDependencies": { + "electron": "^32.2.0", + "electron-builder": "^26.15.3" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/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/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/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/@electron/fuses/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/@electron/fuses/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/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/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/@electron/notarize/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/@electron/notarize/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/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/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/@electron/osx-sign/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/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/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/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/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/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/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/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/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", + "optional": true, + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/windows-sign/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", + "optional": true, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/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/@malept/flatpak-bundler/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/@malept/flatpak-bundler/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/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/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/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra/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/app-builder-lib/node_modules/fs-extra/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/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "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-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "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/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/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/builder-util/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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "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/cross-spawn/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/cross-spawn/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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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", + "optional": true, + "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", + "optional": true, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/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/dir-compare/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/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/dmg-builder/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/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "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": { + "version": "32.3.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-32.3.3.tgz", + "integrity": "sha512-7FT8tDg+MueAw8dBn5LJqDvlM4cZkKJhXfgB3w7P5gvSoUQVAY6LIQcXJxgL+vw2rIRY/b9ak7ZBFbCMF2Bk4w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/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/electron-builder/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/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/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/electron-publish/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/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-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==", + "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/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "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-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "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/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.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "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/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/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/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "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-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "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==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "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/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/node-abi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemailer": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.9.tgz", + "integrity": "sha512-BF0qcyplCwp+jMk6HCjFykBz/YhhZSsxrARhOldLwFWH+8kGjQsd2WIMIZhqEuyXRoGFi0ONbDeWDMDoL8MhLw==", + "license": "MIT-0", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "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/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "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/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/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/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/sql.js": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.2.tgz", + "integrity": "sha512-3ZGPovObMFrdw79zrUHbfdE/DLIsy8jdNdssmMSQuRAymedU6q84asPt0kgiqrdMYlPegDItiIMfmIXzZnYFcw==", + "license": "MIT" + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/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/temp-file/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/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "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/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/undici": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.1.tgz", + "integrity": "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/unzipper/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/unzipper/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/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a080bd3 --- /dev/null +++ b/package.json @@ -0,0 +1,67 @@ +{ + "name": "fellakte", + "version": "0.7.0", + "description": "Digitale Patientenmappe für die Tierheilpraxis Iris Hack", + "author": "Tierheilpraxis Iris Hack", + "main": "main/main.js", + "private": true, + "scripts": { + "start": "electron .", + "check": "node --check main/main.js && node --check main/db.js && node --check main/lock.js && node --check main/ipc.js && node --check main/preload.js && node --check main/backup.js && node --check main/reminders.js && node --check main/webserver.js && node --check main/documents.js", + "dist": "electron-builder --win", + "dist:mac": "electron-builder --mac" + }, + "dependencies": { + "express": "^5.2.1", + "nodemailer": "^10.0.9", + "qrcode": "^1.5.4", + "sql.js": "^1.11.0" + }, + "devDependencies": { + "electron": "^32.2.0", + "electron-builder": "^26.15.3" + }, + "build": { + "appId": "de.tierheilpraxis-hack.fellakte", + "productName": "Fellakte", + "copyright": "Tierheilpraxis Iris Hack", + "publish": null, + "directories": { + "output": "dist" + }, + "files": [ + "main/**/*", + "renderer/**/*", + "web-client/**/*", + "assets/**/*", + "package.json", + "!**/.fellakte.lock" + ], + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ], + "icon": "assets/icon.ico" + }, + "mac": { + "target": [ + { "target": "dmg", "arch": ["universal"] } + ], + "category": "public.app-category.medical", + "icon": "assets/logo.png" + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "Fellakte" + } + } +} diff --git a/renderer/app.js b/renderer/app.js new file mode 100644 index 0000000..cef33c5 --- /dev/null +++ b/renderer/app.js @@ -0,0 +1,2228 @@ +"use strict"; + +const TABS = [ + { id: "stammdaten", label: "Stammdaten", icon: "i-id", color: "var(--tab-1)" }, + { id: "anamnese", label: "Anamnese", icon: "i-clip", color: "var(--tab-2)" }, + { id: "notizen", label: "Notizen", icon: "i-pencil", color: "var(--tab-3)" }, + { id: "behandlung", label: "Behandlung", icon: "i-paw", color: "var(--tab-4)" }, + { id: "verlauf", label: "Verlauf", icon: "i-clock", color: "var(--tab-5)" }, + { id: "labor", label: "Labor", icon: "i-tube", color: "var(--tab-6)" }, + { id: "bilder", label: "Bilder", icon: "i-image", color: "var(--tab-7)" }, + { id: "dokumente", label: "Dokumente", icon: "i-file", color: "var(--tab-8)" }, + { id: "datenschutz", label: "Datenschutz", icon: "i-shield", color: "var(--tab-9)" }, + { id: "vertrag", label: "Behandlungsvertrag", icon: "i-contract", color: "var(--tab-10)" }, +]; + +const SPECIES_ICON = { dog: "i-dog", cat: "i-cat", rabbit: "i-rabbit", tortoise: "i-turtle", other: "i-paw" }; +const SPECIES_COLOR = { dog: "var(--tab-1)", cat: "var(--tab-6)", rabbit: "var(--tab-3)", tortoise: "var(--tab-4)", other: "var(--tab-9)" }; +const SPECIES_LABEL_DEFAULT = { dog: "Hund", cat: "Katze", rabbit: "Kaninchen", tortoise: "Griechische Landschildkröte", other: "Tier" }; +// Real skeleton illustrations from the practice's own Patientenakte template (used with +// permission — see conversation). Natural pixel size, used to keep the body-map stage's +// aspect ratio matched so object-fit:contain never letterboxes (which would throw off +// the percentage-based pin coordinates). +const SILHOUETTE_IMAGE = { dog: "skeleton-dog.png", cat: "skeleton-cat.png", rabbit: "skeleton-rabbit.png" }; +const SILHOUETTE_ASPECT = { dog: 691 / 508, cat: 731 / 443, rabbit: 592 / 525 }; +const SEV = { + ok: { label: "unauffällig", color: "var(--ok)" }, + warn: { label: "beobachten", color: "var(--warn)" }, + critical: { label: "auffällig", color: "var(--critical)" }, +}; + +let directory = []; +let filterText = ""; +let noteMatchIds = new Set(); // animal ids whose Notizen match the current search +let searchToken = 0; +let currentAnimalId = null; +let bundle = null; +let activeTab = "stammdaten"; +let bmPopup = null; +let showSigPad = false; +let showContractSigPad = false; +let anamneseExpanded = false; +let emailStatus = {}; // { einwilligung: {kind,msg}, vertrag: {kind,msg} } + +let viewMode = "patients"; // "patients" | "calendar" +const today = new Date(); +let calYear = today.getFullYear(); +let calMonth = today.getMonth(); // 0-indexed +let calAppointments = []; // appointments for the currently visible grid range + +const APPOINTMENT_TYPES = [ + { label: "Erstanamnese", duration: 120 }, + { label: "Folgeuntersuchung", duration: 45 }, + { label: "Telefonische Beratung", duration: 15 }, + { label: "Kurzcheck", duration: 15 }, + { label: "Ernährungsberatung", duration: 45 }, +]; +const WEEKDAY_LABELS = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; +const MONTH_LABELS = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]; + +// Mirrors the practice's real "Fragebogen Erstanamnese" paper form — everything that +// isn't already captured in Stammdaten/Besitzer. Stored as one JSON object per animal. +const ANAMNESE_SCHEMA = [ + { + title: "Herkunft des Tieres", + fields: [ + { key: "h_farbe", label: "Farbe / Merkmale", type: "text" }, + { key: "h_erkennung", label: "Erkennung", type: "check", options: ["gechipt", "tätowiert", "Brandzeichen"] }, + { key: "h_gewichtstrend", label: "Gewichtstrend", type: "check", options: ["Zunahme", "Abnahme"] }, + { key: "h_zuchttier", label: "Zuchttier", type: "radio", options: ["Nein", "Ja", "trächtig", "säugend"] }, + { key: "h_letzte_geburt", label: "Letzte Geburt", type: "text" }, + { key: "h_anzahl_geburten", label: "Anzahl Geburten bisher", type: "text" }, + { key: "h_lebensmittel_tier", label: "Sonstiges", type: "check", options: ["Lebensmittel lieferndes Tier"] }, + { + key: "h_herkunft", + label: "Herkunft", + type: "check", + options: ["Züchter", "Tierheim Inland", "Zoohandlung", "Tierschutz Ausland", "von Privat / Vorbesitzer", "Plattform im Internet"], + }, + { key: "h_herkunft_sonstiges", label: "Herkunft, Sonstiges", type: "text" }, + { key: "h_besonderheiten", label: "Besonderheiten", type: "textarea", wide: true }, + ], + }, + { + title: "Lebenssituation des Tieres", + fields: [ + { + key: "l_haltung", + label: "Haltungsart", + type: "check", + wide: true, + options: [ + "Haustier", "Familientier", "Hoftier", "Arbeitstier", "Sporttier", "Außenhaltung", + "Zucht/Ausstellungstier", "Rudel/Gruppe", "Innen/Außen gemischt", "Balkon", "Innenhaltung", + ], + }, + { key: "l_haltungssituation", label: "Genaue Beschreibung der Haltungssituation", type: "textarea", wide: true }, + { key: "l_bei_ihnen_seit", label: "Bei Ihnen seit", type: "text" }, + { key: "l_alter_bei_uebernahme", label: "Alter bei Übernahme", type: "text" }, + { key: "l_vorgeschichte", label: "Was wissen Sie über die Zeit davor?", type: "textarea", wide: true }, + { key: "l_eingewoehnung", label: "Wie hat sich das Tier eingewöhnt?", type: "textarea", wide: true }, + { key: "l_familie_mensch", label: "Menschliche Familienmitglieder", type: "text" }, + { key: "l_familie_tier", label: "Tierische Familienmitglieder", type: "text" }, + { key: "l_bezugsperson", label: "Bezugsperson", type: "text" }, + { key: "l_beschaeftigung", label: "Tägliche Beschäftigung", type: "textarea", wide: true }, + { key: "l_training", label: "Training / Sport / „Beruf“", type: "text" }, + { key: "l_alltag", label: "Alltag, Tagesablauf", type: "textarea", wide: true }, + { key: "l_besonderheiten", label: "Besonderheiten", type: "text" }, + { key: "l_alleine", label: "Wie oft alleine zuhause?", type: "radio", options: ["Nie", "Selten", "Regelmäßig"] }, + { key: "l_alleine_stunden", label: "Stunden pro Tag", type: "text" }, + ], + }, + { + title: "Impfstatus & Parasitenkontrolle", + fields: [ + { key: "i_letzte_impfung", label: "Letzte Impfung am", type: "text" }, + { key: "i_regelmaessig", label: "Regelmäßig geimpft?", type: "radio", options: ["Ja", "Nein"] }, + { key: "i_impfungen", label: "Impfungen", type: "textarea", wide: true }, + { key: "i_kotuntersuchung", label: "Letzte Kotuntersuchung", type: "text" }, + { key: "i_wurmkur", label: "Letzte Wurmkur", type: "text" }, + { key: "i_floh_zecken", label: "Letzte Floh-/Zeckenprophylaxe, Mittel", type: "text" }, + { key: "i_ausland", label: "Kürzlich im Ausland / Urlaub?", type: "radio", options: ["Nein", "Ja"] }, + { key: "i_ausland_wo", label: "Wo genau", type: "text" }, + ], + }, + { + title: "Vorstellungsgrund", + fields: [ + { + key: "v_kategorie", + label: "Kategorie", + type: "check", + wide: true, + options: [ + "Gesundheitsberatung", "Ernährungsumstellung", "Allergien/Unverträglichkeiten", "Verhaltensauffälligkeiten", + "chronische Erkrankungen", "Gewichtsprobleme", "Haut-/Fellprobleme", "Magen-Darm-Beschwerden", + ], + }, + { key: "v_sonstiges", label: "Sonstiges", type: "text" }, + { key: "v_grund", label: "Grund der Vorstellung", type: "textarea", wide: true }, + { key: "v_akut", label: "Akute Auffälligkeiten", type: "text" }, + { key: "v_seit_wann", label: "Wie lange besteht das Problem schon?", type: "text" }, + { key: "v_haeufigkeit", label: "Wie häufig tritt es auf?", type: "radio", options: ["täglich", "wöchentlich", "sporadisch"] }, + { key: "v_ausloeser", label: "Auslöser oder Veränderung im Umfeld?", type: "textarea", wide: true }, + ], + }, + { + title: "Medizinische Vorgeschichte", + fields: [ + { key: "m_chronisch", label: "Chronische/wiederkehrende Krankheiten?", type: "radio", options: ["nein", "ja"] }, + { key: "m_chronisch_welche", label: "Welche?", type: "text" }, + { key: "m_operationen", label: "Frühere Operationen/Eingriffe?", type: "radio", options: ["nein", "ja"] }, + { key: "m_operationen_welche", label: "Welche?", type: "text" }, + { key: "m_vererbt", label: "Erkrankungen bei Vorfahren bekannt?", type: "radio", options: ["nein", "ja"] }, + { key: "m_vererbt_welche", label: "Welche?", type: "text" }, + { key: "m_diagnosen", label: "Aktuelle Diagnosen vom Tierarzt", type: "text", wide: true }, + { key: "m_behandlungen", label: "Bisherige Behandlungen/Therapien", type: "text", wide: true }, + { key: "m_medikamente", label: "Regelmäßige Medikamente/Präparate/NEM?", type: "radio", options: ["nein", "ja"] }, + { key: "m_medikamente_welche", label: "Welche?", type: "text" }, + ], + }, + { + title: "Allergien und Unverträglichkeiten", + fields: [ + { key: "a_bekannt", label: "Bekannte Allergien/Unverträglichkeiten?", type: "radio", options: ["nein", "ja"] }, + { key: "a_welche", label: "Welche?", type: "text" }, + { key: "a_symptome", label: "Beschwerden äußern sich als", type: "check", options: ["Juckreiz", "Durchfall", "Erbrechen", "Rötungen/Ausschläge"] }, + { key: "a_symptome_andere", label: "Andere Beschwerden", type: "text" }, + ], + }, + { + title: "Ernährung", + fields: [ + { key: "e_art", label: "Fütterungsart aktuell", type: "check", options: ["Trockenfutter", "Nassfutter", "BARF", "Selbstgekocht", "Mischfütterung"] }, + { key: "e_sonstiges", label: "Sonstiges", type: "text" }, + { key: "e_marken", label: "Marken/Produkte", type: "text", wide: true }, + { key: "e_nem", label: "Nahrungsergänzungsfuttermittel", type: "text", wide: true }, + { key: "e_mahlzeiten", label: "Mahlzeiten pro Tag", type: "radio", options: ["1", "2", "3", "mehr"] }, + { key: "e_menge", label: "Futtermenge pro Tag (g)", type: "text" }, + { key: "e_leckerlis", label: "Leckerlis/Kauartikel", type: "radio", options: ["regelmäßig", "selten", "gar nicht"] }, + { key: "e_appetit", label: "Fressverhalten", type: "radio", options: ["sehr guter Appetit", "normal", "wählerisch", "frisst schlecht"] }, + { key: "e_reaktionen", label: "Reaktionen auf bestimmtes Futter?", type: "textarea", wide: true }, + { key: "e_trinken", label: "Trinkverhalten", type: "text" }, + { key: "e_verhalten", label: "Verhaltensweisen", type: "check", options: ["Kotfressen", "Grasfressen"] }, + ], + }, + { + title: "Verdauung", + fields: [ + { key: "d_kotabsatz", label: "Kotabsatz", type: "radio", options: ["regelmäßig", "unregelmäßig", "häufig", "selten"] }, + { key: "d_konsistenz", label: "Konsistenz", type: "radio", options: ["normal", "weich", "Durchfall", "wechselnd"] }, + { key: "d_geruch", label: "Geruch", type: "radio", options: ["unauffällig", "sehr stark", "faulig", "blähend"] }, + { key: "d_blaehungen", label: "Blähungen?", type: "radio", options: ["Nein", "Ja"] }, + { key: "d_blaehungen_wie_oft", label: "Wie oft?", type: "text" }, + { key: "d_erbrechen", label: "Erbricht gelegentlich?", type: "radio", options: ["Nein", "Ja"] }, + { key: "d_erbrechen_wann", label: "Wann/wie oft?", type: "text" }, + ], + }, + { + title: "Charakter & Verhalten", + fields: [ + { key: "c_charakter", label: "Charakterbeschreibung (neugierig, zahm, aggressiv, ängstlich, dominant …)", type: "textarea", wide: true }, + { key: "c_anfassen", label: "Lässt sich anfassen? Überall? Von wem?", type: "textarea", wide: true }, + { key: "c_vorlieben", label: "Vorlieben", type: "textarea", wide: true }, + { key: "c_kaelte_waerme", label: "Kälte oder Wärme?", type: "radio", options: ["Kälte", "Wärme"] }, + { key: "c_naehe_abstand", label: "Nähe oder Abstand?", type: "radio", options: ["Nähe", "Abstand"] }, + { key: "c_ruhe_action", label: "Ruhe oder Action?", type: "radio", options: ["Ruhe", "Action"] }, + { key: "c_liegeplatz", label: "Fester oder weicher Liegeplatz?", type: "radio", options: ["Fester", "Weicher"] }, + { key: "c_vertraeglich", label: "Verträglich mit", type: "check", options: ["Artgenossen", "anderen Tieren", "Menschen", "Kindern"] }, + { key: "c_kontaktverhalten", label: "Verhalten bei verträglichen/unverträglichen Kontakten", type: "textarea", wide: true }, + { key: "c_aengste", label: "Hat Ängste?", type: "radio", options: ["nein", "ja"] }, + { key: "c_aengste_vor", label: "Vor was? Seit wann? Wie äußert es sich?", type: "text", wide: true }, + { key: "c_schmerzen", label: "Äußert Schmerzen?", type: "radio", options: ["nein", "ja"] }, + { key: "c_schmerzen_wie", label: "Wann, wo und wie?", type: "text", wide: true }, + { key: "c_trauma", label: "Trauma-/Verlust-/Misshandlungssituationen erlebt?", type: "textarea", wide: true }, + { key: "c_spiel", label: "Spielverhalten, Lieblingsspielzeug", type: "textarea", wide: true }, + { key: "c_veraendert", label: "Verhalten verändert? Wie, wann?", type: "textarea", wide: true }, + ], + }, + { + title: "Schlaf", + fields: [ + { key: "s_verhalten", label: "Schlafverhalten", type: "radio", options: ["ungestört", "häufig unterbrochen", "ruhelos"] }, + { key: "s_platz", label: "Liebster Liege-/Schlafplatz", type: "text", wide: true }, + { key: "s_stunden", label: "Stunden Schlaf pro Tag", type: "text" }, + { key: "s_anmerkungen", label: "Weitere Anmerkungen", type: "textarea", wide: true }, + ], + }, +]; + +// The vitals/exam-findings block from the real paper Patientenakte (dog, cat, rabbit, +// guinea pig cards all share this exact layout) — captured as one dated entry per visit. +const EXAM_SCHEMA = [ + { key: "puls", label: "Puls" }, + { key: "atmung", label: "Atmung" }, + { key: "temperatur", label: "Temperatur" }, + { key: "herztoene", label: "Herztöne" }, + { key: "gewicht", label: "Gewicht" }, + { key: "schleimhaeute", label: "Schleimhäute" }, + { key: "maul_zaehne", label: "Maul / Zähne" }, + { key: "augen_ohren", label: "Augen / Ohren" }, + { key: "hydration", label: "Hydration" }, + { key: "lymphknoten", label: "Lymphknoten" }, + { key: "haut_fell", label: "Haut / Fell" }, + { key: "gangbild", label: "Gangbild" }, + { key: "verhalten", label: "Verhalten / Stimmung" }, +]; + +// The practice's real DSGVO-Einwilligung, condensed to checkable items. +const CONSENT_ITEMS = [ + { key: "verarbeitung_erforderlich", label: "Datenverarbeitung zur Behandlung (Anamnese, Befunderhebung, Diagnose, Therapie, Nachsorge) ist erforderlich und wird zur Kenntnis genommen." }, + { key: "dokumentation_lexware", label: "Speicherung zur Dokumentation; Buchhaltungsdaten dürfen über Lexware verarbeitet und an Steuerberater/in weitergegeben werden." }, + { key: "vorab_informiert", label: "Vor der Datenerhebung wurden die erforderlichen Informationen mitgeteilt." }, + { key: "freiwillig", label: "Die Einwilligung erfolgt freiwillig." }, + { key: "kuenftige_vertraege", label: "Daten dürfen auch für künftige Behandlungsverträge genutzt werden." }, + { key: "labore", label: "Daten dürfen bei Bedarf an Untersuchungslabore übermittelt werden." }, + { key: "wissenschaftlich", label: "Anonymisierte Nutzung zu wissenschaftlicher Dokumentation und fachlicher Supervision." }, + { key: "rechnung_email", label: "Rechnungen dürfen per E-Mail zugestellt werden." }, + { key: "praxisgemeinschaft", label: "Tierdaten dürfen mit anderen Tierheilpraktiker*innen in einer Praxisgemeinschaft besprochen werden (keine Personendaten)." }, + { key: "praxisnachfolge", label: "Daten dürfen bei einer Praxisübergabe an eine Nachfolge weiter genutzt werden." }, + { key: "widerruf_kenntnis", label: "Die Widerrufsbelehrung wurde zur Kenntnis genommen." }, + { key: "datenschutz_aufklaerung", label: "Aufklärung über die geltenden Datenschutzrichtlinien und Rechtsgrundlagen erhalten." }, +]; + +// The practice's real Behandlungsvertrag (AGB), §1–§15 — read/confirm, then signed at §16. +const CONTRACT_TEXT = [ + { + title: "§ 1 Vertragsgegenstand", + html: `

Der/die Tierhalter/in nimmt eine tierheilpraktikertypische naturheilkundliche Behandlung für sein/ihr Tier in Anspruch. Der Behandlungsvertrag ist ein Dienstvertrag gemäß § 611 Abs. 1 und § 612 Abs. 1 BGB und regelt die Geschäftsbeziehungen zwischen der Mobilen Tierheilpraxis Iris Hack als Tierheilpraktikerin und dem/der Tierhalter/in, soweit zwischen den Vertragsparteien Abweichendes nicht schriftlich vereinbart wurde.

+

Dieser Behandlungsvertrag kommt zustande, wenn der/die Tierhalter/in das Angebot der Tierheilpraktikerin in Form von Beratung, Diagnosestellung, Untersuchung und Therapie annimmt. Eine über die Behandlung des Tieres hinausgehende Heilung wird nicht geschuldet.

+

Der Behandlungsvertrag gilt als rechtsverbindlich geschlossen, wenn Tierhalter/in und Tierheilpraktikerin einen ersten Termin vereinbaren – auch bei einer mündlichen Vereinbarung bzw. Zustimmung. Die Tierheilpraktikerin ist berechtigt, einen Behandlungsvertrag ohne Angabe von Gründen abzulehnen; der Honoraranspruch für bis dahin entstandene Leistungen bleibt erhalten.

`, + }, + { + title: "§ 2 Versprechen auf Heilung", + html: `

Es wird ausdrücklich darauf hingewiesen, dass keine Garantie auf Heilung oder Linderung gegeben wird. Ein Versprechen auf Heilung gemäß Heilmittelwerbegesetz (HWG) wird nicht gegeben – dies ist weder möglich noch gesetzlich zulässig.

`, + }, + { + title: "§ 3 Aufklärung / Hinweise", + html: `

Der/die Tierhalter/in wird darauf hingewiesen, dass es sich bei den angewandten Verfahren um schulmedizinisch nicht anerkannte Verfahren handelt und die Behandlung eine ärztliche Therapie nicht ersetzt. Sofern ärztlicher Rat erforderlich ist, veranlasst die Tierheilpraktikerin unverzüglich eine Weiterleitung an einen Tierarzt oder spricht eine entsprechende Empfehlung aus. Für die Erteilung einer Auskunft an Dritte ist die schriftliche Einwilligung des/der Tierhalters/in erforderlich.

`, + }, + { + title: "§ 4 Aufklärungspflicht / Aufklärungsumfang", + html: `

Die Tierheilpraktikerin ist verpflichtet, dem/der Tierhalter/in in verständlicher Weise zu Beginn der Behandlung und, soweit erforderlich, in deren Verlauf sämtliche wesentlichen Umstände zu erläutern. Mit der Unterschrift bestätigt der/die Tierhalter/in, dass folgende Punkte umfassend besprochen wurden: aktueller Gesundheitszustand des Tieres, Art der Erkrankung, Behandlungsmethode und deren voraussichtliche Dauer, zur Verfügung stehende Behandlungsalternativen, Belastungen und Risiken der Therapie, Honorar.

`, + }, + { + title: "§ 5 Behandlung", + html: `

Alle Behandlungen erfolgen auf Wunsch des/der Tierhalters/in unter der Maßgabe, das Tier ganzheitlich zu behandeln. Die Behandlungszeiten richten sich nach Vorgaben der Tierheilpraktikerin, können im Einzelfall aber länger dauern als zunächst angenommen.

+

Der/die Tierhalter/in verpflichtet sich, alle Fragen zum Tier und dessen Gesundheit wahrheitsgetreu zu beantworten. Die Tierheilpraktikerin ist berechtigt, die Behandlung abzubrechen, wenn das erforderliche Vertrauensverhältnis nicht mehr gegeben scheint.

`, + }, + { + title: "§ 6 Haftung des Behandlers", + html: `

Eine Haftung für leicht fahrlässige Pflichtverletzungen wird ausgeschlossen, ebenso jegliche Haftung für Schäden am Tier durch Behandlungsempfehlungen oder Therapien, soweit nach BGB zulässig. Dies gilt nicht für vorsätzliche und grob fahrlässige Pflichtverletzungen sowie für Verletzungen von Leben, Körper oder Gesundheit oder von Kardinalpflichten; in diesem Fall ist der Schadensersatz auf die bei Vertragsschluss vorhersehbaren, vertragstypischen Schäden begrenzt.

`, + }, + { + title: "§ 7 Honorar, Kostenerstattung", + html: `

Nach Dienstvertragsrecht (§ 612 BGB) gilt eine Abrechnung im üblichen Rahmen als vereinbart. Geschuldet wird die Leistung, nicht der Erfolg – kein Erfolgshonorar. Das Honorar berechnet sich nach Leistungs- und/oder Zeitaufwand und ist unmittelbar zur Zahlung fällig (bar, EC-Cash oder PayPal); eine Quittung wird ausgestellt, eine detaillierte Rechnung folgt innerhalb einer Woche.

+

Die Gebühren orientieren sich an den Richtlinien des Verbands Deutscher Tierheilpraktiker e.V.; die aktuelle Preisliste liegt vor und wurde zur Kenntnis genommen. Bei Zahlungsverzug wird eine Mahngebühr von 5,00 € erhoben; danach kann der Vorgang einem Inkassobüro übergeben werden. Die Abrechnung erfolgt ohne Mehrwertsteuer gemäß Kleinunternehmerregelung § 19 UStG.

`, + }, + { + title: "§ 8 Terminvereinbarung", + html: `

Termine gelten als vertraglich vereinbart, wenn sie schriftlich, mündlich oder persönlich bestätigt wurden. Bei Hausbesuchen kann es zu unvorhersehbaren Verzögerungen kommen. Termine müssen mindestens 24 Stunden im Voraus telefonisch abgesagt werden.

`, + }, + { + title: "§ 9 Ausfallhonorar", + html: `

Bei kurzfristigem Rücktritt oder Versäumen eines Termins wird ein Ausfallhonorar in Höhe von 100 % des reservierten Zeitfensters fällig, sofern nicht mindestens 24 Stunden vorher abgesagt wurde oder ein unverschuldetes Hindernis (höhere Gewalt) nachgewiesen wird. Bei kürzerfristiger Absage wird pauschal ein Ausfallhonorar von 50,00 € berechnet.

`, + }, + { + title: "§ 10 Beratung / Nachbetreuung über Telefon oder Videocall", + html: `

Telefon- oder Videoberatung ersetzt die reguläre Behandlung nicht und wird zusätzlich zu persönlichen Terminen angeboten. Über Telefon oder Videocall können keine Diagnosen gestellt werden.

`, + }, + { + title: "§ 11 Laborkosten / Kosten für Medikamente", + html: `

Tierheilpraktiker*innen dürfen keine verschreibungs- oder apothekenpflichtigen Arzneimittel verschreiben oder abgeben; die Direktverabreichung durch die Tierheilpraktikerin ist zulässig. Empfehlungen von Produkten können gegen Provision erfolgen. Vom/von der Tierhalter/in mitgebrachte Arzneimittel dürfen nicht angewendet werden. Kosten für Laboruntersuchungen durch ein Fremdlabor werden zusätzlich in Rechnung gestellt.

`, + }, + { + title: "§ 12 Datenschutz / Einverständniserklärung Datenerhebung", + html: `

Die Tierheilpraktikerin erhebt und verarbeitet personenbezogene Daten ausschließlich im Rahmen der vertraglichen Vereinbarungen und behandelt Patientendaten streng vertraulich. Die separate datenschutzrechtliche Einwilligung gemäß Art. 7 DSGVO wurde erhalten, verstanden und unterzeichnet. Handakten werden 10 Jahre nach der letzten Behandlung bzw. 5 Jahre nach dem Tod des Patienten vernichtet.

`, + }, + { + title: "§ 13 Salvatorische Klausel", + html: `

Sollten einzelne Bestimmungen unwirksam sein oder werden, bleibt die Wirksamkeit des Vertrages im Übrigen unberührt; die Bestimmung wird durch eine dem Vertragszweck möglichst nahekommende Regelung ersetzt.

`, + }, + { + title: "§ 14 Erfüllungsort und Gerichtsstand", + html: `

Erfüllungsort: Leonberg, Baden-Württemberg. Gerichtsstand für beide Parteien: Amtsgericht Stuttgart.

`, + }, + { + title: "§ 15 Widerrufsrecht / Widerrufsbelehrung", + html: `

Der Vertrag kann binnen vierzehn Tagen ohne Angabe von Gründen widerrufen werden. Zur Fristwahrung genügt die rechtzeitige Absendung der Widerrufserklärung per Post oder E-Mail an die Tierheilpraktikerin.

`, + }, +]; + +function esc(s) { + return String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); +} +function fmtDate(d) { + return d ? new Date(d).toLocaleDateString("de-DE") : "—"; +} + +// ---------------- boot ---------------- + +async function boot() { + document.body.dataset.view = viewMode; + directory = await window.api.listDirectory(); + renderDirectory(); + + document.getElementById("new-owner-btn").addEventListener("click", openNewOwnerModal); + document.getElementById("settings-btn").addEventListener("click", openSmtpSettingsModal); + document.getElementById("lexware-btn").addEventListener("click", openLexware); + document.querySelectorAll(".view-switch-btn").forEach((btn) => btn.addEventListener("click", () => switchView(btn.dataset.view))); + // Narrow-screen master/detail: goes back from an open Mappe to the Besitzer/Tier-Liste. + document.getElementById("mobile-back-btn").addEventListener("click", () => { + currentAnimalId = null; + document.body.classList.remove("has-animal"); + document.getElementById("folder-view").hidden = true; + document.getElementById("empty-state").hidden = false; + renderDirectory(); + }); + document.getElementById("search-input").addEventListener("input", (e) => { + filterText = e.target.value.trim().toLowerCase(); + renderDirectory(); + const token = ++searchToken; + if (filterText.length < 2) { + noteMatchIds = new Set(); + return; + } + window.api.searchNotes(filterText).then((ids) => { + if (token !== searchToken) return; // a newer keystroke already superseded this search + noteMatchIds = new Set(ids); + renderDirectory(); + }); + }); + document.getElementById("modal-overlay").addEventListener("click", (e) => { + if (e.target.id === "modal-overlay") closeModal(); + }); +} + +// ---------------- calendar ---------------- + +function switchView(mode) { + viewMode = mode; + document.body.dataset.view = mode; + document.querySelectorAll(".view-switch-btn").forEach((b) => b.classList.toggle("active", b.dataset.view === mode)); + document.getElementById("calendar-view").hidden = mode !== "calendar"; + document.getElementById("remedies-view").hidden = mode !== "remedies"; + document.getElementById("empty-state").hidden = mode !== "patients" || !!currentAnimalId; + document.getElementById("folder-view").hidden = mode !== "patients" || !currentAnimalId; + if (mode === "calendar") renderCalendar(); + if (mode === "remedies") renderRemedies(); +} + +function pad2(n) { + return String(n).padStart(2, "0"); +} +function isoDate(y, m, d) { + return `${y}-${pad2(m + 1)}-${pad2(d)}`; +} +function isoToday() { + const t = new Date(); + return isoDate(t.getFullYear(), t.getMonth(), t.getDate()); +} +function formatIsoDateLong(iso) { + const [y, m, d] = iso.split("-").map(Number); + return `${d}. ${MONTH_LABELS[m - 1]} ${y}`; +} +function backupInfoText(info) { + if (!info.count) return "Noch keine Sicherung vorhanden."; + // backup folder names are "YYYY-MM-DD" or "YYYY-MM-DD_HHMMSS" for same-day manual backups + return `${info.count} Sicherung${info.count === 1 ? "" : "en"} vorhanden — letzte: ${formatIsoDateLong(info.last.slice(0, 10))}`; +} +function formatIsoDateShort(iso) { + const [y, m, d] = iso.split("-").map(Number); + return `${pad2(d)}.${pad2(m)}.`; +} + +async function renderCalendar() { + const container = document.getElementById("calendar-view"); + // Monday-start 6-week (42 day) grid covering the visible month plus padding days. + const firstOfMonth = new Date(calYear, calMonth, 1); + const firstWeekday = (firstOfMonth.getDay() + 6) % 7; // 0=Mon + const gridStart = new Date(calYear, calMonth, 1 - firstWeekday); + const days = Array.from({ length: 42 }, (_, i) => { + const d = new Date(gridStart); + d.setDate(gridStart.getDate() + i); + return d; + }); + const fromIso = isoDate(days[0].getFullYear(), days[0].getMonth(), days[0].getDate()); + const toIso = isoDate(days[41].getFullYear(), days[41].getMonth(), days[41].getDate()); + calAppointments = await window.api.listAppointments(fromIso, toIso); + + const byDate = {}; + calAppointments.forEach((a) => (byDate[a.date] ??= []).push(a)); + const todayIso = isoToday(); + + const dayCells = days + .map((d) => { + const iso = isoDate(d.getFullYear(), d.getMonth(), d.getDate()); + const inMonth = d.getMonth() === calMonth; + const items = (byDate[iso] || []).sort((a, b) => (a.start_time || "").localeCompare(b.start_time || "")); + const shown = items.slice(0, 3); + const moreCount = items.length - shown.length; + return `
+
${d.getDate()}
+ ${shown.map((a) => `
${a.start_time ? esc(a.start_time) + " " : ""}${esc(a.title)}
`).join("")} + ${moreCount > 0 ? `
+${moreCount} mehr
` : ""} +
`; + }) + .join(""); + + const upcoming = calAppointments + .filter((a) => a.date >= todayIso) + .sort((a, b) => (a.date + (a.start_time || "")).localeCompare(b.date + (b.start_time || ""))) + .slice(0, 8); + + container.innerHTML = ` +
+

${MONTH_LABELS[calMonth]} ${calYear}

+
+ + + +
+
+ +
+
+ ${WEEKDAY_LABELS.map((w) => `
${w}
`).join("")} + ${dayCells} +
+
+

Nächste Termine

+ ${ + upcoming.length + ? upcoming + .map( + (a) => `
+
${formatIsoDateShort(a.date)}${a.start_time ? " · " + esc(a.start_time) : ""}
+
${esc(a.title)}
${a.animalName ? `
${esc(a.animalName)}${a.ownerName ? " · " + esc(a.ownerName) : ""}
` : ""}${a.location ? `
${esc(a.location)}
` : ""}
+
` + ) + .join("") + : '

Keine anstehenden Termine.

' + } +
`; + + document.getElementById("cal-prev").addEventListener("click", () => { + calMonth--; if (calMonth < 0) { calMonth = 11; calYear--; } + renderCalendar(); + }); + document.getElementById("cal-next").addEventListener("click", () => { + calMonth++; if (calMonth > 11) { calMonth = 0; calYear++; } + renderCalendar(); + }); + document.getElementById("cal-today-btn").addEventListener("click", () => { + calYear = today.getFullYear(); calMonth = today.getMonth(); + renderCalendar(); + }); + document.getElementById("cal-new-appt").addEventListener("click", () => openAppointmentModal({ defaultDate: todayIso })); + + container.querySelectorAll(".cal-day").forEach((cell) => + cell.addEventListener("click", (e) => { + if (e.target.closest(".cal-appt")) return; + openAppointmentModal({ defaultDate: cell.dataset.date }); + }) + ); + container.querySelectorAll("[data-appt-id]").forEach((el) => + el.addEventListener("click", (e) => { + e.stopPropagation(); + const appt = calAppointments.find((a) => a.id === el.dataset.apptId); + if (appt) openAppointmentModal({ existing: appt }); + }) + ); +} + +function openAppointmentModal({ existing, defaultDate }) { + const overlay = document.getElementById("modal-overlay"); + const card = document.getElementById("modal-card"); + card.className = "modal-card wide"; + + const animalOptions = directory + .flatMap((o) => o.animals.map((a) => ({ id: a.id, label: `${a.name} (${o.name})` }))) + .sort((a, b) => a.label.localeCompare(b.label, "de")); + + card.innerHTML = `

${existing ? "Termin bearbeiten" : "Neuer Termin"}

+
+
+ ${APPOINTMENT_TYPES.map((t) => ``).join("")} +
+
+ + + + + + +
+ + +
`; + overlay.hidden = false; + document.getElementById("appt-title").focus(); + + card.querySelectorAll("#appt-quicktypes button").forEach((b) => + b.addEventListener("click", () => { + document.getElementById("appt-title").value = b.dataset.title; + document.getElementById("appt-duration").value = b.dataset.duration; + card.querySelectorAll("#appt-quicktypes button").forEach((x) => x.classList.remove("active")); + b.classList.add("active"); + }) + ); + document.getElementById("modal-cancel").addEventListener("click", closeModal); + const openAnimalLink = document.getElementById("appt-open-animal"); + if (openAnimalLink) + openAnimalLink.addEventListener("click", async (e) => { + e.preventDefault(); + closeModal(); + switchView("patients"); + await selectAnimal(existing.animal_id); + }); + const deleteBtn = document.getElementById("appt-delete"); + if (deleteBtn) + deleteBtn.addEventListener("click", () => { + openConfirmModal({ + title: "Termin löschen?", + message: `"${existing.title}" am ${formatIsoDateLong(existing.date)} wirklich löschen?`, + confirmLabel: "Löschen", + onConfirm: async () => { + await window.api.deleteAppointment(existing.id); + renderCalendar(); + }, + }); + }); + document.getElementById("appt-form").addEventListener("submit", async (e) => { + e.preventDefault(); + const data = { + animalId: document.getElementById("appt-animal").value || null, + title: document.getElementById("appt-title").value.trim(), + date: document.getElementById("appt-date").value, + startTime: document.getElementById("appt-time").value || null, + durationMinutes: Number(document.getElementById("appt-duration").value) || null, + location: document.getElementById("appt-location").value.trim(), + notes: document.getElementById("appt-notes").value.trim(), + }; + if (!data.title || !data.date) return; + if (existing) await window.api.updateAppointment(existing.id, data); + else await window.api.addAppointment(data); + closeModal(); + renderCalendar(); + }); +} + +// ---------------- Verzeichnis (Medikamente/Präparate) ---------------- + +let remedyFilter = ""; + +async function renderRemedies() { + const container = document.getElementById("remedies-view"); + const items = await window.api.listRemedies(remedyFilter); + const byCategory = {}; + items.forEach((r) => (byCategory[r.category || "Sonstiges"] ??= []).push(r)); + const categories = Object.keys(byCategory).sort((a, b) => a.localeCompare(b, "de")); + + container.innerHTML = ` +
+

Verzeichnis

+
+ + +
+ ${ + categories.length + ? categories + .map( + (cat) => `

${esc(cat)}

` + ) + .join("") + : `

${remedyFilter ? "Keine Treffer." : "Noch keine Einträge — leg gleich den ersten an."}

` + }`; + + document.getElementById("remedy-search").addEventListener("input", (e) => { + remedyFilter = e.target.value.trim(); + renderRemedies(); + }); + document.getElementById("remedy-new").addEventListener("click", () => openRemedyModal({})); + container.querySelectorAll("[data-remedy-id]").forEach((el) => + el.addEventListener("click", async () => { + const existing = items.find((r) => r.id === el.dataset.remedyId); + if (existing) openRemedyModal({ existing }); + }) + ); +} + +function openRemedyModal({ existing }) { + const overlay = document.getElementById("modal-overlay"); + const card = document.getElementById("modal-card"); + card.className = "modal-card wide"; + card.innerHTML = `

${existing ? "Eintrag bearbeiten" : "Neuer Eintrag"}

+
+
+ + +
+ + + +
`; + overlay.hidden = false; + document.getElementById("rm-name").focus(); + + document.getElementById("modal-cancel").addEventListener("click", closeModal); + const deleteBtn = document.getElementById("rm-delete"); + if (deleteBtn) + deleteBtn.addEventListener("click", () => { + openConfirmModal({ + title: "Eintrag löschen?", + message: `"${existing.name}" wirklich löschen?`, + confirmLabel: "Löschen", + onConfirm: async () => { + await window.api.deleteRemedy(existing.id); + renderRemedies(); + }, + }); + }); + document.getElementById("remedy-form").addEventListener("submit", async (e) => { + e.preventDefault(); + const data = { + name: document.getElementById("rm-name").value.trim(), + category: document.getElementById("rm-category").value.trim(), + keywords: document.getElementById("rm-keywords").value.trim(), + notes: document.getElementById("rm-notes").value.trim(), + }; + if (!data.name) return; + if (existing) await window.api.updateRemedy(existing.id, data); + else await window.api.addRemedy(data); + closeModal(); + renderRemedies(); + }); +} + +// ---------------- modal ---------------- + +function fieldInputHtml(f) { + if (f.type === "select") { + return ` + `; + } + return ` + `; +} +function fieldHtml(f) { + return ``; +} +// Renders a flat field list, grouping consecutive fields that share the same +// `group` value into one row (e.g. Straße + Hausnummer side by side). +function fieldsHtml(fields) { + const out = []; + let i = 0; + while (i < fields.length) { + const f = fields[i]; + if (f.group) { + const groupFields = []; + while (i < fields.length && fields[i].group === f.group) { + groupFields.push(fields[i]); + i++; + } + out.push(``); + } else { + out.push(fieldHtml(f)); + i++; + } + } + return out.join(""); +} + +function openModal(title, fields, submitLabel, onSubmit) { + const overlay = document.getElementById("modal-overlay"); + const card = document.getElementById("modal-card"); + card.innerHTML = `

${esc(title)}

`; + overlay.hidden = false; + document.getElementById("modal-cancel").addEventListener("click", closeModal); + document.getElementById("modal-form").addEventListener("submit", async (e) => { + e.preventDefault(); + const data = {}; + fields.forEach((f) => (data[f.key] = document.getElementById("mf-" + f.key).value.trim())); + if (fields.some((f) => f.required && !data[f.key])) return; + await onSubmit(data); + closeModal(); + }); + const first = card.querySelector("input,select"); + if (first) first.focus(); +} +function closeModal() { + document.getElementById("modal-overlay").hidden = true; + document.getElementById("modal-card").className = "modal-card"; +} + +function openConfirmModal({ title, message, confirmLabel, danger = true, onConfirm }) { + const overlay = document.getElementById("modal-overlay"); + const card = document.getElementById("modal-card"); + card.innerHTML = `

${esc(title)}

${esc(message)}

+ `; + overlay.hidden = false; + document.getElementById("modal-cancel").addEventListener("click", closeModal); + document.getElementById("modal-confirm").addEventListener("click", async () => { + closeModal(); + await onConfirm(); + }); +} + +function backupRowLabel(name) { + // "YYYY-MM-DD" (daily) or "YYYY-MM-DD_HHMMSS" (manual/safety-net same-day backup) + const datePart = name.slice(0, 10); + const timePart = name.slice(11); + const label = formatIsoDateLong(datePart); + if (!timePart) return label; + const t = `${timePart.slice(0, 2)}:${timePart.slice(2, 4)}:${timePart.slice(4, 6)}`; + return `${label}, ${t} Uhr`; +} + +async function openSmtpSettingsModal() { + const current = (await window.api.getSmtpSettings()) || {}; + const lexwareCurrent = (await window.api.getLexwarePath()) || ""; + const reminderLead = await window.api.getReminderLead(); + const backupInfo = await window.api.getBackupInfo(); + const backups = await window.api.listBackups(); + const webDevices = await window.api.listWebDevices(); + const overlay = document.getElementById("modal-overlay"); + const card = document.getElementById("modal-card"); + card.className = "modal-card xwide"; + card.innerHTML = `

Einstellungen

+
+ + + +
+
+
+

E-Mail-Versand

+

Für den automatischen Versand der unterschriebenen PDFs. Diese Zugangsdaten liegen unverschlüsselt im gemeinsamen Datenordner.

+
+ + + + + + +
+ +
+ +

Lexware & Erinnerungen

+
+ + +
+
+ +
+

Sicherung

+

Täglich beim ersten Start wird automatisch eine Sicherung des Datenordners (Datenbank + Fotos/Dokumente) in backups/ angelegt, die letzten 30 werden aufgehoben.

+

${backupInfoText(backupInfo)}

+ + +

Wiederherstellung

+

Stellt den Datenordner auf den Stand einer früheren Sicherung zurück. Der aktuelle Stand wird vorher automatisch selbst gesichert, die App startet danach neu.

+
${renderRestoreList(backups)}
+
+ +
+

Tablet / Mac im WLAN verbinden

+

Läuft komplett lokal über euer eigenes WLAN — keine Cloud, keine Registrierung, keine laufenden Kosten. Phase 1: die Kartei lässt sich am gekoppelten Gerät ansehen, Bearbeiten folgt später. Dieser PC muss dafür an und die App geöffnet sein.

+ +
+ +

Gekoppelte Geräte

+
${renderWebDevicesList(webDevices)}
+
+ + +
`; + overlay.hidden = false; + + document.querySelectorAll("#settings-tabstrip .tab").forEach((tabBtn) => + tabBtn.addEventListener("click", () => { + document.querySelectorAll("#settings-tabstrip .tab").forEach((b) => b.classList.toggle("active", b === tabBtn)); + document.querySelectorAll(".settings-panel").forEach((p) => p.classList.toggle("active", p.dataset.spanel === tabBtn.dataset.stab)); + }) + ); + + document.getElementById("backup-now").addEventListener("click", async (e) => { + const btn = e.currentTarget; + btn.disabled = true; + btn.textContent = "Sichere …"; + const result = await window.api.backupNow(); + const info = await window.api.getBackupInfo(); + document.getElementById("backup-info").textContent = result.ok ? backupInfoText(info) : `Sicherung fehlgeschlagen: ${result.error}`; + if (result.ok) { + const list = await window.api.listBackups(); + document.getElementById("restore-list").innerHTML = renderRestoreList(list); + wireRestoreButtons(); + } + btn.disabled = false; + btn.textContent = "Jetzt sichern"; + }); + document.getElementById("backup-open").addEventListener("click", () => window.api.openBackupFolder()); + wireRestoreButtons(); + + document.getElementById("pair-start").addEventListener("click", async (e) => { + const btn = e.currentTarget; + btn.disabled = true; + const info = await window.api.startWebPairing(); + btn.disabled = false; + renderPairingPanel(info); + }); + wireWebDeviceButtons(); + + const readForm = () => ({ + host: document.getElementById("smtp-host").value.trim(), + port: document.getElementById("smtp-port").value.trim() || "587", + secure: document.getElementById("smtp-secure").checked, + user: document.getElementById("smtp-user").value.trim(), + pass: document.getElementById("smtp-pass").value, + fromName: document.getElementById("smtp-fromname").value.trim(), + fromEmail: document.getElementById("smtp-fromemail").value.trim(), + }); + const showStatus = (kind, msg) => { + document.getElementById("smtp-status").innerHTML = ``; + }; + + document.getElementById("modal-cancel").addEventListener("click", closeModal); + document.getElementById("smtp-test").addEventListener("click", async () => { + showStatus("ok", "Verbindung wird geprüft …"); + const result = await window.api.testSmtpSettings(readForm()); + if (result.ok) showStatus("ok", "Verbindung erfolgreich – die Zugangsdaten funktionieren."); + else showStatus("error", "Verbindung fehlgeschlagen: " + (result.error || "unbekannter Fehler")); + }); + document.getElementById("smtp-form").addEventListener("submit", async (e) => { + e.preventDefault(); + await window.api.setSmtpSettings(readForm()); + await window.api.setLexwarePath(document.getElementById("lexware-path").value.trim()); + const lead = Number(document.getElementById("reminder-lead").value.trim()); + await window.api.setReminderLead(Number.isFinite(lead) && lead > 0 ? lead : 30); + closeModal(); + }); +} + +function renderRestoreList(backups) { + if (!backups.length) return '

Noch keine Sicherungen vorhanden.

'; + return `
${backups + .map( + (name) => `
+
${backupRowLabel(name)}
+ +
` + ) + .join("")}
`; +} + +function wireRestoreButtons() { + document.querySelectorAll("[data-restore]").forEach((btn) => + btn.addEventListener("click", () => { + const name = btn.dataset.restore; + openConfirmModal({ + title: "Sicherung wiederherstellen?", + message: `Der Datenordner wird auf den Stand vom ${backupRowLabel(name)} zurückgesetzt. Alles, was seitdem eingetragen wurde, geht dabei verloren (der aktuelle Stand wird vorher aber noch selbst gesichert). Die App startet danach automatisch neu.`, + confirmLabel: "Zurücksetzen & neu starten", + onConfirm: async () => { + const result = await window.api.restoreBackup(name); + if (!result.ok) { + openConfirmModal({ + title: "Wiederherstellung fehlgeschlagen", + message: String(result.error || "Unbekannter Fehler."), + confirmLabel: "OK", + danger: false, + onConfirm: () => {}, + }); + } + // on success the app quits and relaunches itself — nothing more to do here + }, + }); + }) + ); +} + +function renderPairingPanel(info) { + const panel = document.getElementById("pair-panel"); + if (!panel) return; + const expiresIn = Math.max(0, Math.round((info.expiresAt - Date.now()) / 60000)); + panel.innerHTML = `
+ QR-Code +
+
Mit Kamera/QR-Scanner am Tablet scannen, oder von Hand eingeben:
+
${esc(info.code)}
+
Adresse: ${esc(info.url)}
+
Gültig noch ca. ${expiresIn} Min. — beide Geräte müssen im selben WLAN sein.
+
+
`; +} + +function renderWebDevicesList(devices) { + if (!devices.length) return '

Noch kein Gerät gekoppelt.

'; + return `
${devices + .map( + (d) => `
+
${esc(d.name)}gekoppelt ${fmtDate(d.pairedAt)} · zuletzt ${fmtDate(d.lastSeen)}
+ +
` + ) + .join("")}
`; +} + +function wireWebDeviceButtons() { + document.querySelectorAll("[data-revoke-device]").forEach((btn) => + btn.addEventListener("click", () => { + const id = btn.dataset.revokeDevice; + openConfirmModal({ + title: "Zugriff entziehen?", + message: "Das Gerät kann sich danach nicht mehr mit dieser Kartei verbinden, bis es neu gekoppelt wird.", + confirmLabel: "Entziehen", + onConfirm: async () => { + await window.api.revokeWebDevice(id); + const list = await window.api.listWebDevices(); + document.getElementById("web-devices-list").innerHTML = renderWebDevicesList(list); + wireWebDeviceButtons(); + }, + }); + }) + ); +} + +async function openLexware() { + const path = await window.api.getLexwarePath(); + if (!path) { + await openSmtpSettingsModal(); + return; + } + const result = await window.api.openLexware(); + if (!result.ok) { + openConfirmModal({ + title: "Lexware konnte nicht geöffnet werden", + message: `Fehler: ${result.error}. Pfad/Adresse in den Einstellungen prüfen?`, + confirmLabel: "Einstellungen öffnen", + danger: false, + onConfirm: openSmtpSettingsModal, + }); + } +} + +function openNewOwnerModal() { + openModal( + "Neuer Besitzer", + [ + { key: "name", label: "Name", required: true }, + { key: "phone", label: "Telefon" }, + { key: "email", label: "E-Mail" }, + { key: "street", label: "Straße", group: "addr-street" }, + { key: "house_no", label: "Hausnummer", group: "addr-street" }, + { key: "zip", label: "PLZ", group: "addr-city" }, + { key: "city", label: "Ort", group: "addr-city" }, + ], + "Anlegen", + async (data) => { + data.address = [[data.street, data.house_no].filter(Boolean).join(" "), [data.zip, data.city].filter(Boolean).join(" ")] + .filter(Boolean) + .join(", "); + await window.api.createOwner(data); + directory = await window.api.listDirectory(); + renderDirectory(); + } + ); +} + +function openNewAnimalModal(ownerId) { + openModal( + "Neues Tier", + [ + { key: "name", label: "Name", required: true }, + { + key: "species", + label: "Tierart", + type: "select", + options: [ + { value: "dog", label: "Hund" }, + { value: "cat", label: "Katze" }, + { value: "rabbit", label: "Kaninchen" }, + { value: "tortoise", label: "Landschildkröte" }, + { value: "other", label: "Andere" }, + ], + }, + { key: "breed", label: "Rasse (z. B. Australian Shepherd)" }, + { key: "sex", label: "Geschlecht (z. B. Rüde, kastriert)" }, + { key: "birth_date", label: "Geburtsdatum (TT.MM.JJJJ)" }, + { key: "weight", label: "Gewicht (z. B. 12,5 kg)" }, + { key: "chip_number", label: "Chip-Nummer" }, + ], + "Mappe anlegen", + async (data) => { + data.species_label = SPECIES_LABEL_DEFAULT[data.species] || "Tier"; + const id = await window.api.createAnimal(ownerId, data); + directory = await window.api.listDirectory(); + renderDirectory(); + await selectAnimal(id); + } + ); +} + +// ---------------- directory ---------------- + +function renderDirectory() { + const list = document.getElementById("directory-list"); + const q = filterText; + const groups = directory + .map((owner) => { + const animals = owner.animals.filter( + (a) => !q || owner.name.toLowerCase().includes(q) || a.name.toLowerCase().includes(q) || noteMatchIds.has(a.id) + ); + if (q && !owner.name.toLowerCase().includes(q) && animals.length === 0) return ""; + const rows = animals + .map((a) => { + const noteHit = q && noteMatchIds.has(a.id) && !a.name.toLowerCase().includes(q) && !owner.name.toLowerCase().includes(q); + return ` + `; + }) + .join(""); + return `
+
${esc(owner.name)} + + +
+ ${rows || '
Noch kein Tier
'} +
`; + }) + .join(""); + list.innerHTML = groups || '
Noch keine Besitzer angelegt.
'; + + list.querySelectorAll(".animal-row").forEach((btn) => btn.addEventListener("click", () => selectAnimal(btn.dataset.id))); + list.querySelectorAll(".add-animal-btn:not(.danger)").forEach((btn) => btn.addEventListener("click", () => openNewAnimalModal(btn.dataset.owner))); + list.querySelectorAll("[data-delete-owner]").forEach((btn) => + btn.addEventListener("click", () => { + const name = btn.dataset.ownerName; + const count = Number(btn.dataset.animalCount); + const ownerId = btn.dataset.deleteOwner; + const animalNote = count > 0 ? ` und ${count === 1 ? "sein/ihr Tier" : "alle " + count + " Tiere"} (inkl. Notizen, Dokumente, Fotos, Unterschriften)` : ""; + openConfirmModal({ + title: "Besitzer löschen?", + message: `"${name}"${animalNote} unwiderruflich löschen? Das kann nicht rückgängig gemacht werden.`, + confirmLabel: "Endgültig löschen", + onConfirm: async () => { + const owner = directory.find((o) => o.id === ownerId); + if (currentAnimalId && owner && owner.animals.some((a) => a.id === currentAnimalId)) { + currentAnimalId = null; + document.body.classList.remove("has-animal"); + document.getElementById("folder-view").hidden = true; + document.getElementById("empty-state").hidden = false; + } + await window.api.deleteOwner(ownerId); + directory = await window.api.listDirectory(); + renderDirectory(); + }, + }); + }) + ); +} + +// ---------------- folder ---------------- + +async function refreshBundle() { + bundle = await window.api.getAnimalBundle(currentAnimalId); +} + +async function selectAnimal(id) { + if (viewMode !== "patients") switchView("patients"); + currentAnimalId = id; + activeTab = "stammdaten"; + bmPopup = null; + showSigPad = false; + showContractSigPad = false; + anamneseExpanded = false; + emailStatus = {}; + await refreshBundle(); + renderDirectory(); + renderFolder(); +} + +function renderFolder() { + document.body.classList.add("has-animal"); + document.getElementById("empty-state").hidden = true; + const view = document.getElementById("folder-view"); + view.hidden = false; + + const a = bundle.animal, + owner = bundle.owner; + document.getElementById("crumb").innerHTML = `${esc(owner.name)}${esc(a.name)}`; + document.getElementById("avatar-lg").style.background = SPECIES_COLOR[a.species] || SPECIES_COLOR.other; + document.getElementById("avatar-lg").innerHTML = ``; + document.getElementById("animal-name").textContent = a.name; + const deceasedChip = a.deceased_at ? `Verstorben am ${esc(a.deceased_at)}` : ""; + document.getElementById("animal-chips").innerHTML = + deceasedChip + + [a.species_label, a.breed, a.sex, a.birth_date ? "geb. " + a.birth_date : "", a.weight] + .filter(Boolean) + .map((c) => `${esc(c)}`) + .join(""); + document.getElementById("owner-card").innerHTML = ` +
Besitzer
+
${esc(owner.name)}
+ ${owner.phone ? `
${esc(owner.phone)}
` : ""} + ${owner.email ? `
${esc(owner.email)}
` : ""}`; + + renderTabstrip(); + renderPanels(); +} + +function renderTabstrip() { + document.getElementById("tabstrip").innerHTML = TABS.map( + (t) => `` + ).join(""); + document.querySelectorAll(".tab").forEach((btn) => + btn.addEventListener("click", () => { + activeTab = btn.dataset.tab; + bmPopup = null; + renderPanels(); + document.querySelectorAll(".tab").forEach((b) => b.classList.toggle("active", b.dataset.tab === activeTab)); + }) + ); +} + +// ---------------- panels ---------------- + +function renderPanels() { + const a = bundle.animal; + const map = { + stammdaten: panelStammdaten, + anamnese: panelAnamnese, + notizen: panelNotizen, + behandlung: panelBehandlung, + verlauf: panelVerlauf, + labor: panelLabor, + bilder: panelBilder, + dokumente: panelDokumente, + datenschutz: panelDatenschutz, + vertrag: panelVertrag, + }; + document.getElementById("panels").innerHTML = TABS.map( + (t) => `
${map[t.id](a)}
` + ).join(""); + document.querySelectorAll(".tab").forEach((b) => b.classList.toggle("active", b.dataset.tab === activeTab)); + + wireStammdaten(); + wireAnamnese(); + wireNotizen(); + wireVerlauf(); + wireLabor(); + wireBilder(); + wireDokumente(); + wireDatenschutz(); + wireVertrag(); + wireBehandlung(); +} + +function editableField(key, label, value, entity = "animal") { + return `
${esc(label)}
${esc(value) || "—"}
`; +} + +function panelStammdaten(a) { + return `

Grunddaten

+ ${editableField("name", "Name", a.name)} + ${editableField("species_label", "Tierart", a.species_label)} + ${editableField("breed", "Rasse", a.breed)} + ${editableField("sex", "Geschlecht", a.sex)} + ${editableField("birth_date", "Geburtsdatum", a.birth_date)} + ${editableField("weight", "Gewicht", a.weight)} + ${editableField("chip_number", "Chip-Nummer", a.chip_number)} +

Auf einen Wert klicken, um ihn zu ändern — speichert automatisch.

+

Besitzer

+ ${editableField("name", "Name", bundle.owner.name, "owner")} + ${editableField("phone", "Telefon", bundle.owner.phone, "owner")} + ${editableField("email", "E-Mail", bundle.owner.email, "owner")} + ${editableField("address", "Adresse", bundle.owner.address, "owner")} +

Auf einen Wert klicken, um ihn zu ändern — speichert automatisch. Gilt für alle Tiere dieses Besitzers.

+

Status

+ + ${a.deceased_at ? `
${editableField("deceased_at", "Verstorben am", a.deceased_at)}
` : ""} +
`; +} +function wireStammdaten() { + document.querySelectorAll("#panel-stammdaten .field[data-field] .val").forEach((valEl) => { + valEl.addEventListener("click", () => { + const fieldEl = valEl.closest(".field"); + const key = fieldEl.dataset.field; + const entity = fieldEl.dataset.entity || "animal"; + const current = valEl.textContent === "—" ? "" : valEl.textContent; + const input = document.createElement("input"); + input.className = "edit-val"; + input.value = current; + valEl.replaceWith(input); + input.focus(); + input.select(); + let committed = false; + const commit = async () => { + if (committed) return; + committed = true; + if (entity === "owner") { + await window.api.updateOwnerField(bundle.owner.id, key, input.value.trim()); + directory = await window.api.listDirectory(); + renderDirectory(); + } else { + await window.api.updateAnimalField(currentAnimalId, key, input.value.trim()); + } + await refreshBundle(); + renderPanels(); + }; + input.addEventListener("blur", commit); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + e.preventDefault(); + input.blur(); + } + if (e.key === "Escape") { + committed = true; + renderPanels(); + } + }); + }); + }); + + const deceasedToggle = document.getElementById("deceased-toggle"); + if (deceasedToggle) + deceasedToggle.addEventListener("click", async () => { + const isDeceased = !!bundle.animal.deceased_at; + const value = isDeceased ? "" : new Date().toLocaleDateString("de-DE"); + await window.api.updateAnimalField(currentAnimalId, "deceased_at", value); + await refreshBundle(); + directory = await window.api.listDirectory(); + renderDirectory(); + renderFolder(); + }); +} + +function getAnamneseData() { + try { + return JSON.parse(bundle.animal.anamnese_data || "{}"); + } catch { + return {}; + } +} + +function afFieldHtml(f, data) { + const val = data[f.key]; + const wideCls = f.wide ? " af-wide" : ""; + if (f.type === "text") + return `
`; + if (f.type === "textarea") + return `
`; + if (f.type === "radio") + return `
${f.options + .map((o) => ``) + .join("")}
`; + // check + const arr = Array.isArray(val) ? val : []; + return `
${f.options + .map((o) => ``) + .join("")}
`; +} + +function panelAnamnese(a) { + const data = getAnamneseData(); + const sections = ANAMNESE_SCHEMA.map( + (s) => `

${esc(s.title)}

${s.fields.map((f) => afFieldHtml(f, data)).join("")}
` + ).join(""); + return `
+

Kurzfassung

+ +

Speichert automatisch, sobald du das Feld verlässt.

+
+
+

Vollständiger Erstanamnese-Fragebogen (Herkunft, Haltung, Impfstatus, Ernährung, Verhalten …)

+ +
+ ${anamneseExpanded ? `
${sections}
` : ""}`; +} +function wireAnamnese() { + const box = document.getElementById("anamnese-box"); + if (box) + box.addEventListener("blur", async () => { + await window.api.updateAnimalField(currentAnimalId, "anamnese", box.value); + await refreshBundle(); + }); + + const expandBtn = document.getElementById("anamnese-expand-btn"); + if (expandBtn) + expandBtn.addEventListener("click", () => { + anamneseExpanded = !anamneseExpanded; + renderPanels(); + }); + + const grid = document.querySelector("#panel-anamnese .af-section-grid"); + if (!grid) return; + + const saveField = async (key, value) => { + const data = getAnamneseData(); + data[key] = value; + bundle.animal.anamnese_data = JSON.stringify(data); + await window.api.updateAnimalField(currentAnimalId, "anamnese_data", JSON.stringify(data)); + }; + + grid.querySelectorAll('input[data-type="text"], textarea[data-type="textarea"]').forEach((el) => { + el.addEventListener("blur", () => saveField(el.dataset.key, el.value.trim())); + }); + grid.querySelectorAll('input[data-type="radio"]').forEach((el) => { + el.addEventListener("change", () => saveField(el.dataset.key, el.value)); + }); + grid.querySelectorAll('input[data-type="check"]').forEach((el) => { + el.addEventListener("change", () => { + const data = getAnamneseData(); + const arr = new Set(Array.isArray(data[el.dataset.key]) ? data[el.dataset.key] : []); + if (el.checked) arr.add(el.value); + else arr.delete(el.value); + saveField(el.dataset.key, Array.from(arr)); + }); + }); +} + +function panelNotizen(a) { + return `

Notizen

+
+
+ ${bundle.notes.map((n) => `
${n.date}
${esc(n.text)}
`).join("") || '

Noch keine Notizen.

'} +
`; +} +function wireNotizen() { + const btn = document.getElementById("note-add"); + if (!btn) return; + const input = document.getElementById("note-input"); + const add = async () => { + const val = input.value.trim(); + if (!val) return; + await window.api.addNote(currentAnimalId, val); + await refreshBundle(); + renderPanels(); + }; + btn.addEventListener("click", add); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") add(); + }); +} + +function panelVerlauf(a) { + return `

Verlauf

+
+ + + +
+
${bundle.history.map((v) => `
${v.date}

${esc(v.title)}

${esc(v.text)}

`).join("") || '

Noch keine Einträge.

'}
+
`; +} +function wireVerlauf() { + const btn = document.getElementById("hist-add"); + if (!btn) return; + btn.addEventListener("click", async () => { + const title = document.getElementById("hist-title").value.trim(); + const text = document.getElementById("hist-text").value.trim(); + if (!title) return; + await window.api.addHistoryEntry(currentAnimalId, title, text); + await refreshBundle(); + renderPanels(); + }); +} + +function panelLabor(a) { + const cards = bundle.labResults + .map( + (l) => `
+

${esc(l.name)} · ${l.date}

+
+ + ${l.values.map((r) => ``).join("")} +
ParameterWertReferenz
${esc(r.parameter)}${esc(r.value)}${esc(r.reference)}
+
+ + + + +
+
` + ) + .join(""); + return `${cards}
`; +} +function wireLabor() { + const newBtn = document.getElementById("lab-new"); + if (!newBtn) return; + newBtn.addEventListener("click", () => { + openModal("Neuer Laborbefund", [{ key: "name", label: "Bezeichnung (z. B. Kleines Blutbild)", required: true }], "Anlegen", async (data) => { + await window.api.addLabResult(currentAnimalId, data.name); + await refreshBundle(); + renderPanels(); + }); + }); + document.querySelectorAll(".lab-del").forEach((btn) => + btn.addEventListener("click", async () => { + await window.api.deleteLabResult(btn.dataset.id); + await refreshBundle(); + renderPanels(); + }) + ); + document.querySelectorAll(".lv-add").forEach((btn) => + btn.addEventListener("click", async () => { + const labId = btn.dataset.lab; + const param = document.querySelector(`.lv-param[data-lab="${labId}"]`).value.trim(); + const value = document.querySelector(`.lv-value[data-lab="${labId}"]`).value.trim(); + const ref = document.querySelector(`.lv-ref[data-lab="${labId}"]`).value.trim(); + if (!param) return; + await window.api.addLabValue(labId, param, value, ref); + await refreshBundle(); + renderPanels(); + }) + ); +} + +function panelBilder(a) { + return `

Bilder

+ ${bundle.photos.map((p) => `
${esc(p.filename)}
${esc(p.filename)}
`).join("")} +
Foto hinzufügen
+
`; +} +function wireBilder() { + const btn = document.getElementById("add-photo"); + if (!btn) return; + btn.addEventListener("click", async () => { + const added = await window.api.addDocument(currentAnimalId, "photo"); + if (added.length) { + await refreshBundle(); + renderPanels(); + } + }); +} + +function panelDokumente(a) { + return `

Vorlagen erstellen

+

Erzeugt ein PDF mit eurem Briefkopf, direkt für ${esc(a.name)} — landet automatisch unten in den Dokumenten.

+
+ + +
+
+

Dokumente

+
PDF, Scan oder Foto ablegen — oder Datei auswählen
+
${bundle.documents.map(docRow).join("") || '

Noch keine Dokumente.

'}
+
`; +} +function docRow(d) { + const kb = d.size_bytes ? Math.max(1, Math.round(d.size_bytes / 1024)) + " KB" : ""; + return `
+
${esc(d.filename)}
${fmtDate(d.uploaded_at)} · ${kb}
+
`; +} +function wireDokumente() { + const btn = document.getElementById("doc-upload"); + if (!btn) return; + btn.addEventListener("click", async () => { + const added = await window.api.addDocument(currentAnimalId, "document"); + if (added.length) { + await refreshBundle(); + renderPanels(); + } + }); + document.querySelectorAll(".doc-open").forEach((b) => b.addEventListener("click", () => window.api.openDocument(b.dataset.id))); + + const ernaehrungsBtn = document.getElementById("tpl-ernaehrungsplan"); + if (ernaehrungsBtn) ernaehrungsBtn.addEventListener("click", openErnaehrungsplanModal); + const rezeptBtn = document.getElementById("tpl-rezept"); + if (rezeptBtn) rezeptBtn.addEventListener("click", openRezeptModal); +} + +const WOCHENTAGE = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; +const TAGESZEITEN = ["morgens", "mittags", "abends"]; + +function ernaehrungsRowHtml(i) { + return `
+
+ + +
+
+
${WOCHENTAGE.map((t) => ``).join("")}
+
${TAGESZEITEN.map((t) => ``).join("")}
+
+
+
`; +} + +function wireErnaehrungsRow(row) { + const removeBtn = row.querySelector(".fp-row-remove"); + removeBtn.addEventListener("click", () => { + const list = document.getElementById("fp-rows"); + if (list.children.length <= 1) return; // always keep at least one row + row.remove(); + }); +} + +function openErnaehrungsplanModal() { + const overlay = document.getElementById("modal-overlay"); + const card = document.getElementById("modal-card"); + card.className = "modal-card xwide"; + card.innerHTML = `

Ernährungsplan für ${esc(bundle.animal.name)}

+
${ernaehrungsRowHtml(0)}
+ + +
+ `; + overlay.hidden = false; + wireErnaehrungsRow(document.querySelector("#fp-rows .fp-row")); + + document.getElementById("modal-cancel").addEventListener("click", closeModal); + let rowCounter = 1; + document.getElementById("fp-add-row").addEventListener("click", () => { + const list = document.getElementById("fp-rows"); + list.insertAdjacentHTML("beforeend", ernaehrungsRowHtml(rowCounter++)); + wireErnaehrungsRow(list.lastElementChild); + list.lastElementChild.scrollIntoView({ block: "nearest" }); + }); + document.getElementById("fp-create").addEventListener("click", async () => { + const rows = Array.from(document.querySelectorAll("#fp-rows .fp-row")) + .map((row) => ({ + futtermittel: row.querySelector(".fp-futtermittel").value.trim(), + tage: Array.from(row.querySelectorAll(".fp-tag:checked")).map((c) => c.value), + zeiten: Array.from(row.querySelectorAll(".fp-zeit:checked")).map((c) => c.value), + menge: row.querySelector(".fp-menge").value.trim(), + })) + .filter((r) => r.futtermittel); + if (!rows.length) { + document.getElementById("fp-status").innerHTML = ''; + return; + } + const notes = document.getElementById("fp-notes").value.trim(); + const bodyHtml = ` + + ${rows + .map( + (r) => + `` + ) + .join("")}
FuttermittelTagWannMenge
${esc(r.futtermittel)}${esc(r.tage.join(", ") || "—")}${esc(r.zeiten.join(", ") || "—")}${esc(r.menge || "—")}
+ ${notes ? `

Anmerkungen

${esc(notes)}

` : ""}`; + document.getElementById("fp-status").innerHTML = ''; + await window.api.generateTemplatePdf(currentAnimalId, "Ernaehrungsplan", { + title: "Ernährungsplan", + ownerBlockHtml: buildOwnerBlockHtml(bundle.owner, bundle.animal), + bodyHtml, + }); + closeModal(); + await refreshBundle(); + renderPanels(); + }); +} + +function openRezeptModal() { + const overlay = document.getElementById("modal-overlay"); + const card = document.getElementById("modal-card"); + card.className = "modal-card wide"; + const todayStr = new Date().toLocaleDateString("de-DE"); + card.innerHTML = `

Rezept für ${esc(bundle.animal.name)}

+ + +
+ `; + overlay.hidden = false; + document.getElementById("rz-text").focus(); + + document.getElementById("modal-cancel").addEventListener("click", closeModal); + document.getElementById("rz-create").addEventListener("click", async () => { + const text = document.getElementById("rz-text").value.trim(); + if (!text) { + document.getElementById("rz-status").innerHTML = ''; + return; + } + const dateStr = document.getElementById("rz-date").value.trim(); + document.getElementById("rz-status").innerHTML = ''; + const bodyHtml = `

Datum: ${esc(dateStr)}

${esc(text)}

+

Empfehlung im Rahmen der Tierheilpraxis – keine tierärztliche Verschreibung.

`; + await window.api.generateTemplatePdf(currentAnimalId, "Rezept", { + title: "Rezept", + ownerBlockHtml: buildOwnerBlockHtml(bundle.owner, bundle.animal), + bodyHtml, + }); + closeModal(); + await refreshBundle(); + renderPanels(); + }); +} + +function getConsentItems() { + try { + return JSON.parse((bundle.consent && bundle.consent.items_json) || "{}"); + } catch { + return {}; + } +} + +function panelDatenschutz(a) { + const c = bundle.consent || {}; + const items = getConsentItems(); + const checklist = `

Datenschutzrechtliche Einwilligung

`; + const granted = !!c.granted; + // showSigPad must win over an existing signature — otherwise "Neu unterschreiben" + // re-renders straight back into the "already signed" branch and looks like a no-op. + const sigSection = showSigPad + ? `
+
` + : c.signature_url + ? `
Unterschrift
+
` + : `
`; + + return `${checklist}

Datenschutz & Einverständnis

+ + + ${sigSection} + ${emailSection("einwilligung", c)} +
`; +} + +// Shared by Datenschutz + Behandlungsvertrag: shows send status under the signature, +// plus a resend button once something has been signed. +function emailSection(kind, record) { + const status = emailStatus[kind]; + const statusHtml = status ? `
${esc(status.msg)}
` : ""; + if (!record || !record.signature_path) return statusHtml; + const lastSent = record.email_sent_at ? `

Zuletzt per E-Mail gesendet am ${fmtDate(record.email_sent_at)}.

` : ""; + return `${lastSent}
${statusHtml}`; +} + +function buildOwnerBlockHtml(owner, animal) { + const esc2 = esc; + return ` +
Tierhalter/in${esc2(owner.name)}
+
Adresse${esc2(owner.address) || "—"}
+
E-Mail${esc2(owner.email) || "—"}
+
Tier${esc2(animal.name)} (${esc2(animal.species_label)})
`; +} + +function summarizeEmailResult(result) { + if (result.ok) return { kind: "ok", msg: `PDF erstellt und per E-Mail an ${bundle.owner.email} gesendet.` }; + if (result.reason === "not-configured") return { kind: "error", msg: "PDF wurde erstellt. E-Mail-Versand ist noch nicht eingerichtet (Zahnrad oben rechts)." }; + if (result.reason === "no-owner-email") return { kind: "error", msg: "PDF wurde erstellt. Für diesen Besitzer ist keine E-Mail-Adresse hinterlegt." }; + return { kind: "error", msg: "PDF wurde erstellt, E-Mail-Versand ist fehlgeschlagen: " + (result.reason || "unbekannter Fehler") }; +} + +async function generateAndMaybeSendPdf(kind) { + const a = bundle.animal, + owner = bundle.owner; + const record = kind === "vertrag" ? bundle.contract : bundle.consent; + const title = kind === "vertrag" ? "Behandlungsvertrag" : "Datenschutzrechtliche Einwilligung"; + const ownerBlockHtml = buildOwnerBlockHtml(owner, a); + const bodyHtml = + kind === "vertrag" + ? CONTRACT_TEXT.map((s) => `

${esc(s.title)}

${s.html}`).join("") + : ``; + const signedInfoHtml = `

Unterschrieben von: ${esc(owner.name)}
Datum: ${fmtDate(record.signed_at)}
Methode: ${esc(record.method || "")}

`; + + const result = await window.api.generatePdf(currentAnimalId, kind, { title, ownerBlockHtml, bodyHtml, signedInfoHtml }); + await refreshBundle(); + + const smtp = await window.api.getSmtpSettings(); + if (!smtp) return { ok: false, reason: "not-configured" }; + if (!bundle.owner.email) return { ok: false, reason: "no-owner-email" }; + + const sendResult = await window.api.sendDocumentEmail(currentAnimalId, kind, { absPath: result.absPath, filename: result.filename }); + await refreshBundle(); + return sendResult.ok ? { ok: true } : { ok: false, reason: sendResult.error }; +} + +function wireDatenschutz() { + document.querySelectorAll("#panel-datenschutz .consent-item input").forEach((cb) => { + cb.addEventListener("change", async () => { + const items = getConsentItems(); + items[cb.dataset.key] = cb.checked; + const c = bundle.consent || {}; + await window.api.setConsent(currentAnimalId, { + granted: !!c.granted, + method: c.method || null, + signaturePath: c.signature_path || null, + marketingOk: !!c.marketing_ok, + itemsJson: JSON.stringify(items), + }); + await refreshBundle(); + renderPanels(); + }); + }); + const toggle = document.getElementById("consent-toggle"); + if (toggle) + toggle.addEventListener("click", async () => { + const c = bundle.consent || {}; + await window.api.setConsent(currentAnimalId, { + granted: !c.granted, + method: !c.granted ? "Manuell im Programm markiert" : null, + signaturePath: c.signature_path || null, + marketingOk: !!c.marketing_ok, + }); + await refreshBundle(); + renderPanels(); + }); + const marketing = document.getElementById("marketing-toggle"); + if (marketing) + marketing.addEventListener("click", async () => { + const c = bundle.consent || {}; + await window.api.setConsent(currentAnimalId, { + granted: !!c.granted, + method: c.method || null, + signaturePath: c.signature_path || null, + marketingOk: !c.marketing_ok, + }); + await refreshBundle(); + renderPanels(); + }); + const sigStart = document.getElementById("sig-start"); + if (sigStart) sigStart.addEventListener("click", () => { showSigPad = true; renderPanels(); }); + const sigRedo = document.getElementById("sig-redo"); + if (sigRedo) sigRedo.addEventListener("click", () => { showSigPad = true; renderPanels(); }); + + const emailBtn = document.querySelector('#panel-datenschutz [data-email-kind="einwilligung"]'); + if (emailBtn) + emailBtn.addEventListener("click", async () => { + emailStatus.einwilligung = { kind: "pending", msg: "PDF wird erstellt und gesendet …" }; + renderPanels(); + const result = await generateAndMaybeSendPdf("einwilligung"); + emailStatus.einwilligung = summarizeEmailResult(result); + renderPanels(); + }); + + const pad = document.getElementById("sig-pad"); + if (pad) { + const ctx = pad.getContext("2d"); + ctx.lineWidth = 2.4; + ctx.lineCap = "round"; + ctx.strokeStyle = "#3a332c"; + let drawing = false, + last = null; + const posFor = (e) => { + const r = pad.getBoundingClientRect(); + return { x: ((e.clientX - r.left) / r.width) * pad.width, y: ((e.clientY - r.top) / r.height) * pad.height }; + }; + pad.addEventListener("pointerdown", (e) => { + pad.setPointerCapture(e.pointerId); + drawing = true; + last = posFor(e); + }); + pad.addEventListener("pointermove", (e) => { + if (!drawing) return; + const p = posFor(e); + ctx.beginPath(); + ctx.moveTo(last.x, last.y); + ctx.lineTo(p.x, p.y); + ctx.stroke(); + last = p; + }); + pad.addEventListener("pointerup", () => (drawing = false)); + pad.addEventListener("pointercancel", () => (drawing = false)); + + document.getElementById("sig-clear").addEventListener("click", () => ctx.clearRect(0, 0, pad.width, pad.height)); + document.getElementById("sig-save").addEventListener("click", async () => { + const dataUrl = pad.toDataURL("image/png"); + bundle.consent = await window.api.saveSignature(currentAnimalId, dataUrl); + showSigPad = false; + emailStatus.einwilligung = { kind: "pending", msg: "PDF wird erstellt und gesendet …" }; + renderPanels(); + const result = await generateAndMaybeSendPdf("einwilligung"); + emailStatus.einwilligung = summarizeEmailResult(result); + renderPanels(); + }); + } +} + +function panelVertrag(a) { + const c = bundle.contract || {}; + const granted = !!c.granted; + const sections = CONTRACT_TEXT.map((s) => `

${esc(s.title)}

${s.html}`).join(""); + const sigSection = showContractSigPad + ? `
+
` + : c.signature_url + ? `
Unterschrift
+
` + : `
+ ${c.briefing_confirmed ? "" : '

Erst die Bestätigung oben ankreuzen, dann kann unterschrieben werden.

'}`; + + return `
+

Behandlungsvertrag mit Allgemeinen Geschäftsbedingungen

+
${sections}
+ +
+

Unterschrift

+ + ${sigSection} + ${emailSection("vertrag", c)} +
`; +} + +function wireVertrag() { + const confirmBox = document.getElementById("vertrag-confirm"); + if (confirmBox) + confirmBox.addEventListener("change", async () => { + const c = bundle.contract || {}; + await window.api.setContract(currentAnimalId, { + granted: !!c.granted, + method: c.method || null, + signaturePath: c.signature_path || null, + briefingConfirmed: confirmBox.checked, + }); + await refreshBundle(); + renderPanels(); + }); + + const sigStart = document.getElementById("vertrag-sig-start"); + if (sigStart) sigStart.addEventListener("click", () => { showContractSigPad = true; renderPanels(); }); + const sigRedo = document.getElementById("vertrag-sig-redo"); + if (sigRedo) sigRedo.addEventListener("click", () => { showContractSigPad = true; renderPanels(); }); + + const emailBtn = document.querySelector('#panel-vertrag [data-email-kind="vertrag"]'); + if (emailBtn) + emailBtn.addEventListener("click", async () => { + emailStatus.vertrag = { kind: "pending", msg: "PDF wird erstellt und gesendet …" }; + renderPanels(); + const result = await generateAndMaybeSendPdf("vertrag"); + emailStatus.vertrag = summarizeEmailResult(result); + renderPanels(); + }); + + const pad = document.getElementById("vertrag-sig-pad"); + if (pad) { + const ctx = pad.getContext("2d"); + ctx.lineWidth = 2.4; + ctx.lineCap = "round"; + ctx.strokeStyle = "#3a332c"; + let drawing = false, + last = null; + const posFor = (e) => { + const r = pad.getBoundingClientRect(); + return { x: ((e.clientX - r.left) / r.width) * pad.width, y: ((e.clientY - r.top) / r.height) * pad.height }; + }; + pad.addEventListener("pointerdown", (e) => { + pad.setPointerCapture(e.pointerId); + drawing = true; + last = posFor(e); + }); + pad.addEventListener("pointermove", (e) => { + if (!drawing) return; + const p = posFor(e); + ctx.beginPath(); + ctx.moveTo(last.x, last.y); + ctx.lineTo(p.x, p.y); + ctx.stroke(); + last = p; + }); + pad.addEventListener("pointerup", () => (drawing = false)); + pad.addEventListener("pointercancel", () => (drawing = false)); + + document.getElementById("vertrag-sig-clear").addEventListener("click", () => ctx.clearRect(0, 0, pad.width, pad.height)); + document.getElementById("vertrag-sig-save").addEventListener("click", async () => { + const dataUrl = pad.toDataURL("image/png"); + bundle.contract = await window.api.saveContractSignature(currentAnimalId, dataUrl); + showContractSigPad = false; + emailStatus.vertrag = { kind: "pending", msg: "PDF wird erstellt und gesendet …" }; + renderPanels(); + const result = await generateAndMaybeSendPdf("vertrag"); + emailStatus.vertrag = summarizeEmailResult(result); + renderPanels(); + }); + } +} +function wireDatenschutz() { + document.querySelectorAll("#panel-datenschutz .consent-item input").forEach((cb) => { + cb.addEventListener("change", async () => { + const items = getConsentItems(); + items[cb.dataset.key] = cb.checked; + const c = bundle.consent || {}; + await window.api.setConsent(currentAnimalId, { + granted: !!c.granted, + method: c.method || null, + signaturePath: c.signature_path || null, + marketingOk: !!c.marketing_ok, + itemsJson: JSON.stringify(items), + }); + await refreshBundle(); + renderPanels(); + }); + }); + const toggle = document.getElementById("consent-toggle"); + if (toggle) + toggle.addEventListener("click", async () => { + const c = bundle.consent || {}; + await window.api.setConsent(currentAnimalId, { + granted: !c.granted, + method: !c.granted ? "Manuell im Programm markiert" : null, + signaturePath: c.signature_path || null, + marketingOk: !!c.marketing_ok, + }); + await refreshBundle(); + renderPanels(); + }); + const marketing = document.getElementById("marketing-toggle"); + if (marketing) + marketing.addEventListener("click", async () => { + const c = bundle.consent || {}; + await window.api.setConsent(currentAnimalId, { + granted: !!c.granted, + method: c.method || null, + signaturePath: c.signature_path || null, + marketingOk: !c.marketing_ok, + }); + await refreshBundle(); + renderPanels(); + }); + const sigStart = document.getElementById("sig-start"); + if (sigStart) sigStart.addEventListener("click", () => { showSigPad = true; renderPanels(); }); + const sigRedo = document.getElementById("sig-redo"); + if (sigRedo) sigRedo.addEventListener("click", () => { showSigPad = true; renderPanels(); }); + + const pad = document.getElementById("sig-pad"); + if (pad) { + const ctx = pad.getContext("2d"); + ctx.lineWidth = 2.4; + ctx.lineCap = "round"; + ctx.strokeStyle = "#3a332c"; + let drawing = false, + last = null; + const posFor = (e) => { + const r = pad.getBoundingClientRect(); + return { x: ((e.clientX - r.left) / r.width) * pad.width, y: ((e.clientY - r.top) / r.height) * pad.height }; + }; + pad.addEventListener("pointerdown", (e) => { + pad.setPointerCapture(e.pointerId); + drawing = true; + last = posFor(e); + }); + pad.addEventListener("pointermove", (e) => { + if (!drawing) return; + const p = posFor(e); + ctx.beginPath(); + ctx.moveTo(last.x, last.y); + ctx.lineTo(p.x, p.y); + ctx.stroke(); + last = p; + }); + pad.addEventListener("pointerup", () => (drawing = false)); + pad.addEventListener("pointercancel", () => (drawing = false)); + + document.getElementById("sig-clear").addEventListener("click", () => ctx.clearRect(0, 0, pad.width, pad.height)); + document.getElementById("sig-save").addEventListener("click", async () => { + const dataUrl = pad.toDataURL("image/png"); + bundle.consent = await window.api.saveSignature(currentAnimalId, dataUrl); + showSigPad = false; + await refreshBundle(); + renderPanels(); + }); + } +} + +function examFieldValue(key) { + const el = document.querySelector(`#exam-form [data-exam-key="${key}"]`); + return el ? el.value.trim() : ""; +} + +function panelExamForm() { + return `
+

Untersuchung dokumentieren

+
+ ${EXAM_SCHEMA.map((f) => `
`).join("")} +
+
+
+ +
`; +} + +function panelExamHistory() { + if (!bundle.exams.length) return ""; + return `

Untersuchungsverlauf

+ ${bundle.exams + .map((ex) => { + const rows = EXAM_SCHEMA.filter((f) => ex[f.key]).map((f) => `${esc(f.label)}: ${esc(ex[f.key])}`).join(""); + return `
${ex.date}
+ ${rows ? `
${rows}
` : ""} + ${ex.auffaelligkeiten ? `

Auffälligkeiten: ${esc(ex.auffaelligkeiten)}

` : ""} + ${ex.empfehlung ? `

Empfehlung: ${esc(ex.empfehlung)}

` : ""} +
`; + }) + .join("")} +
`; +} + +function panelBehandlung(a) { + const findings = bundle.findings + .map( + (p) => `
+
${SEV[p.severity].label} — ${esc(p.text)}
+
` + ) + .join("") || `

Noch keine Markierungen. Auf die Silhouette klicken, um einen Befund einzutragen.

`; + + return `${panelExamForm()}${panelExamHistory()}
+

Körper-Silhouette

+

Auf die Silhouette klicken oder tippen, um an der berührten Stelle einen Befund zu markieren.

+
+
+ ${silhouette(a.species)} + ${bundle.findings.map((p, i) => `
${i + 1}
`).join("")} + ${bmPopup ? bmPopupHtml(bmPopup) : ""} +
+

Markierte Befunde

${findings}
+
+
`; +} +function bmPopupHtml(p) { + const left = Math.min(Math.max(p.x, 10), 60); + const top = Math.min(Math.max(p.y, 8), 58); + return `
+ +
+ + + +
+
+ ${p.editId ? '' : ""} + + +
+
`; +} +function silhouette(species) { + if (SILHOUETTE_IMAGE[species]) { + return ``; + } + if (species === "tortoise") + return ` + + + + + + + + + + + + + + + `; + return ` + + `; +} +function wireBehandlung() { + const examSave = document.getElementById("exam-save"); + if (examSave) + examSave.addEventListener("click", async () => { + const fields = {}; + EXAM_SCHEMA.forEach((f) => (fields[f.key] = examFieldValue(f.key))); + fields.auffaelligkeiten = examFieldValue("auffaelligkeiten"); + fields.empfehlung = examFieldValue("empfehlung"); + const hasAny = Object.values(fields).some((v) => v); + if (!hasAny) return; + await window.api.addExam(currentAnimalId, fields); + await refreshBundle(); + renderPanels(); + }); + + const stage = document.getElementById("bm-stage"); + if (!stage) return; + stage.addEventListener("click", (e) => { + if (e.target.closest(".bm-pin") || e.target.closest(".bm-pop")) return; + const r = stage.getBoundingClientRect(); + const x = ((e.clientX - r.left) / r.width) * 100; + const y = ((e.clientY - r.top) / r.height) * 100; + bmPopup = { x, y, sev: "warn", text: "" }; + renderPanels(); + }); + stage.querySelectorAll(".bm-pin").forEach((pin) => + pin.addEventListener("click", (e) => { + e.stopPropagation(); + const finding = bundle.findings.find((f) => f.id === pin.dataset.id); + bmPopup = { x: finding.x, y: finding.y, sev: finding.severity, text: finding.text, editId: finding.id }; + renderPanels(); + }) + ); + const popText = document.getElementById("pop-text"); + if (popText) { + document.querySelectorAll(".sev-btn").forEach((b) => + b.addEventListener("click", () => { + bmPopup.sev = b.dataset.sev; + renderPanels(); + }) + ); + document.getElementById("pop-cancel").addEventListener("click", () => { + bmPopup = null; + renderPanels(); + }); + const popDelete = document.getElementById("pop-delete"); + if (popDelete) + popDelete.addEventListener("click", async () => { + await window.api.deleteFinding(bmPopup.editId); + bmPopup = null; + await refreshBundle(); + renderPanels(); + }); + document.getElementById("pop-save").addEventListener("click", async () => { + const text = popText.value.trim() || "Ohne Beschreibung"; + if (bmPopup.editId) { + await window.api.updateFinding(bmPopup.editId, { severity: bmPopup.sev, text }); + } else { + await window.api.addFinding(currentAnimalId, { x: bmPopup.x, y: bmPopup.y, severity: bmPopup.sev, text }); + } + bmPopup = null; + await refreshBundle(); + renderPanels(); + }); + } + document.querySelectorAll("#panel-behandlung .finding .rm").forEach((btn) => + btn.addEventListener("click", async () => { + await window.api.deleteFinding(btn.dataset.id); + await refreshBundle(); + renderPanels(); + }) + ); +} + +boot(); diff --git a/renderer/index.html b/renderer/index.html new file mode 100644 index 0000000..f93c8df --- /dev/null +++ b/renderer/index.html @@ -0,0 +1,109 @@ + + + + + +Fellakte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
Fellakte
+
Tierheilpraxis Iris Hack
+
+
+
+ + + +
+ +
+
+ + +
+ +
+ + +
+
+ +

Noch kein Tier ausgewählt.
Links einen Besitzer anlegen und ein Tier hinzufügen, um seine Mappe zu öffnen.

+
+ + + + + +
+
+
+ + + + + + diff --git a/renderer/styles.css b/renderer/styles.css new file mode 100644 index 0000000..4502e3d --- /dev/null +++ b/renderer/styles.css @@ -0,0 +1,452 @@ +:root{ + --bg:#f7f0e8; + --surface:#ffffff; + --surface-alt:#efe1d2; + --surface-raised:#fffaf5; + --border:#e2d0bd; + --ink:#3a332c; + --ink-soft:#7a6c60; + --ink-faint:#a89c8f; + --accent:#b48f8e; + --accent-ink:#7c5c5a; + --accent-tint:#e9dad7; + --gold:#b8975a; + --ok:#6f8f5c; --ok-tint:#e3ead9; + --warn:#c08a3e; --warn-tint:#f3e4cd; + --critical:#b5533f;--critical-tint:#f3ded8; + --tab-1:#b48f8e; --tab-2:#c98a86; --tab-3:#c4a05a; + --tab-4:#7f9a6e; --tab-5:#5f97a0; --tab-6:#ba6b4f; + --tab-7:#9b7fa0; --tab-8:#ad7a5c; --tab-9:#6f7f8c; --tab-10:#5f7a9a; + --shadow:0 1px 2px rgba(58,41,30,.06),0 6px 18px rgba(58,41,30,.07); + --radius-s:7px; --radius-m:12px; --radius-l:20px; + --font-display:'Dancing Script',cursive; + --font-head:'Montserrat',system-ui,sans-serif; + --font-body:'Source Sans Pro',system-ui,sans-serif; +} +@media (prefers-color-scheme:dark){ + :root:not([data-theme="light"]){ + --bg:#211b17; --surface:#2a2320; --surface-alt:#2e2621; + --surface-raised:#332a24; --border:#463b32; + --ink:#f1e6da; --ink-soft:#c8b6a6; --ink-faint:#8f8074; + --accent:#dfb0ac; --accent-ink:#f1d7d3; --accent-tint:#453431; + --gold:#d8b978; + --ok:#96b481; --ok-tint:#33402c; + --warn:#dcac63; --warn-tint:#453824; + --critical:#d98871; --critical-tint:#452f2a; + --tab-1:#d3a6a4; --tab-2:#dba09b; --tab-3:#d4b170; + --tab-4:#9bb787; --tab-5:#7bb2ba; --tab-6:#d1866a; + --tab-7:#b598ba; --tab-8:#c2926f; --tab-9:#8b9aa6; --tab-10:#8fa8c4; + --shadow:0 1px 2px rgba(0,0,0,.4),0 10px 24px rgba(0,0,0,.35); + } +} +:root[data-theme="dark"]{ + --bg:#211b17; --surface:#2a2320; --surface-alt:#2e2621; + --surface-raised:#332a24; --border:#463b32; + --ink:#f1e6da; --ink-soft:#c8b6a6; --ink-faint:#8f8074; + --accent:#dfb0ac; --accent-ink:#f1d7d3; --accent-tint:#453431; + --gold:#d8b978; + --ok:#96b481; --ok-tint:#33402c; + --warn:#dcac63; --warn-tint:#453824; + --critical:#d98871; --critical-tint:#452f2a; + --tab-1:#d3a6a4; --tab-2:#dba09b; --tab-3:#d4b170; + --tab-4:#9bb787; --tab-5:#7bb2ba; --tab-6:#d1866a; + --tab-7:#b598ba; --tab-8:#c2926f; --tab-9:#8b9aa6; --tab-10:#8fa8c4; + --shadow:0 1px 2px rgba(0,0,0,.4),0 10px 24px rgba(0,0,0,.35); +} + +*{box-sizing:border-box;} +/* An element's own `display` rule (author CSS) always wins over the browser's + default `[hidden]{display:none}` (user-agent CSS), regardless of selector + specificity — origin beats specificity in the cascade. Several elements here + toggle visibility via the `hidden` attribute *and* declare their own `display` + (.modal-overlay, .empty-state), so without this !important safety net they'd + stay visually active — and, for a `position:fixed` overlay, silently swallow + every click on the page — even while `hidden` reads true. */ +[hidden]{ display:none !important; } +@media (prefers-reduced-motion:reduce){ *{animation:none!important;transition:none!important;} } +html,body{height:100%;} +body{ + margin:0; background:var(--bg); color:var(--ink); + font-family:var(--font-body); font-size:15px; line-height:1.55; + -webkit-font-smoothing:antialiased; +} +h1,h2,h3{font-family:var(--font-head); text-wrap:balance; margin:0;} +button, input, textarea, select{font-family:inherit; color:inherit;} +:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:4px;} +::selection{background:var(--accent-tint); color:var(--ink);} + +.app{ display:grid; grid-template-rows:auto 1fr; height:100vh; } + +.topbar{ + display:flex; align-items:center; gap:16px; padding:12px 20px; + background:var(--surface); border-bottom:1px solid var(--border); +} +.brandmark{ display:flex; align-items:center; gap:10px; flex-shrink:0; } +.brand-logo{ height:40px; width:auto; display:block; } +.brand-word{ font-family:var(--font-display); font-weight:700; font-size:22px; color:var(--accent-ink); line-height:1; } +.brand-sub{ font-family:var(--font-head); font-size:9px; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-faint); } +.view-switch{ display:flex; gap:2px; background:var(--surface-alt); border-radius:99px; padding:3px; flex-shrink:0; } +.view-switch-btn{ display:flex; align-items:center; gap:6px; border:0; background:transparent; padding:7px 14px; border-radius:99px; font-family:var(--font-head); font-size:12.5px; font-weight:600; color:var(--ink-soft); cursor:pointer; } +.view-switch-btn svg{ width:14px; height:14px; } +.view-switch-btn.active{ background:var(--surface); color:var(--accent-ink); box-shadow:var(--shadow); } +.crumb{ font-family:var(--font-head); font-size:13px; color:var(--ink-soft); display:flex; align-items:center; gap:6px; } +.crumb b{ color:var(--ink); font-weight:600; } +.topbar-spacer{ flex:1; } + +.shell{ display:grid; grid-template-columns:272px 1fr; min-height:0; } + +.directory{ + background:var(--surface-alt); border-right:1px solid var(--border); + padding:16px 14px; overflow-y:auto; display:flex; flex-direction:column; +} +.search{ + display:flex; align-items:center; gap:8px; background:var(--surface); + border:1px solid var(--border); border-radius:var(--radius-m); padding:9px 12px; + margin-bottom:10px; color:var(--ink-faint); +} +.search input{ border:0; background:transparent; outline:0; width:100%; font-size:13.5px; color:var(--ink); } +.btn.block{ width:100%; justify-content:center; margin-bottom:16px; } +.owner-group{ margin-bottom:14px; } +.owner-name-row{ display:flex; align-items:center; gap:6px; padding:0 8px 6px; } +.owner-name{ + font-family:var(--font-head); font-size:11px; font-weight:700; letter-spacing:.06em; + color:var(--ink-faint); text-transform:uppercase; flex:1; +} +.add-animal-btn{ background:none; border:0; color:var(--ink-faint); cursor:pointer; padding:2px 4px; border-radius:5px; flex-shrink:0; } +.add-animal-btn:hover{ color:var(--accent-ink); background:var(--surface); } +.add-animal-btn.danger:hover{ color:var(--critical); background:var(--critical-tint); } +.add-animal-btn svg{ width:13px; height:13px; display:block; } +.animal-row{ + display:flex; align-items:center; gap:10px; width:100%; text-align:left; + padding:9px 8px; border-radius:var(--radius-m); border:1px solid transparent; + background:transparent; cursor:pointer; margin-bottom:2px; +} +.animal-row:hover{ background:var(--surface); } +.animal-row.active{ background:var(--surface); border-color:var(--border); box-shadow:var(--shadow); } +.animal-avatar{ + width:34px; height:34px; border-radius:10px; display:flex; align-items:center; justify-content:center; + flex-shrink:0; color:#fff; +} +.animal-avatar svg{ width:18px; height:18px; } +.animal-meta{ min-width:0; } +.animal-meta .nm{ font-weight:600; font-size:13.5px; display:flex; align-items:center; gap:4px; } +.animal-meta .sp{ font-size:11.5px; color:var(--ink-soft); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.animal-row.deceased .animal-avatar{ opacity:.55; } +.animal-row.deceased .nm{ color:var(--ink-soft); } +.rip-mark{ width:12px; height:12px; color:var(--ink-faint); flex-shrink:0; } +.empty-owners{ font-size:12.5px; color:var(--ink-faint); padding:8px; } + +.main{ overflow-y:auto; padding:22px 28px 60px; } +.empty-state{ + height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center; + gap:14px; color:var(--ink-faint); text-align:center; +} +.empty-state svg{ color:var(--ink-faint); } +.empty-state p{ max-width:280px; font-size:14px; line-height:1.6; } + +.folder-head{ display:flex; flex-wrap:wrap; gap:18px; align-items:flex-start; justify-content:space-between; margin-bottom:18px; } +.folder-title{ display:flex; align-items:center; gap:14px; } +.folder-title .avatar-lg{ width:56px; height:56px; border-radius:16px; display:flex; align-items:center; justify-content:center; color:#fff; flex-shrink:0; } +.folder-title .avatar-lg svg{ width:28px; height:28px; } +.folder-title h1{ font-size:26px; font-weight:700; } +.chips{ display:flex; flex-wrap:wrap; gap:7px; margin-top:7px; } +.chip{ font-size:11.5px; font-family:var(--font-head); font-weight:600; color:var(--ink-soft); background:var(--surface); border:1px solid var(--border); padding:4px 10px; border-radius:99px; display:inline-flex; align-items:center; gap:4px; } +.chip svg{ width:11px; height:11px; } +.chip.deceased-chip{ color:var(--ink-faint); background:var(--surface-alt); } +.owner-card{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-m); padding:12px 14px; min-width:220px; box-shadow:var(--shadow); } +.owner-card .lbl{ font-family:var(--font-head); font-size:10px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-faint); margin-bottom:4px; } +.owner-card .nm{ font-weight:700; font-size:14.5px; margin-bottom:5px; } +.owner-card .line{ display:flex; gap:7px; align-items:center; font-size:12.5px; color:var(--ink-soft); margin-top:3px; } +.owner-card .line svg{ width:13px; height:13px; flex-shrink:0; opacity:.7; } + +.tabstrip{ display:flex; flex-wrap:wrap; gap:3px; border-bottom:1px solid var(--border); margin-bottom:20px; } +.tab{ + display:flex; align-items:center; gap:7px; padding:10px 13px; border:0; background:transparent; + font-family:var(--font-head); font-size:12.5px; font-weight:600; color:var(--ink-soft); + border-bottom:2.5px solid transparent; cursor:pointer; white-space:nowrap; margin-bottom:-1px; +} +.tab svg{ width:15px; height:15px; } +.tab:hover{ color:var(--ink); } +.tab.active{ color:var(--ink); border-bottom-color:var(--tab-color); } + +.panel{ display:none; } +.panel.active{ display:block; } + +.card{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-m); padding:16px 18px; box-shadow:var(--shadow); } +.card + .card{ margin-top:14px; } +.card h3{ font-size:13px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--ink-soft); margin-bottom:12px; } +.grid-2{ display:grid; grid-template-columns:1fr 1fr; gap:14px 24px; } +.field .lbl{ font-size:11px; color:var(--ink-faint); margin-bottom:3px; font-family:var(--font-head); font-weight:600; letter-spacing:.03em; } +.field .val{ font-size:14.5px; font-weight:600; font-variant-numeric:tabular-nums; padding:4px 6px; margin:-4px -6px; border-radius:6px; cursor:text; } +.field .val:hover{ background:var(--surface-alt); } +.field input.edit-val{ + width:100%; font-size:14.5px; font-weight:600; font-family:inherit; border:1px solid var(--accent); border-radius:6px; + padding:4px 6px; outline:0; background:var(--surface); +} +.prose{ font-size:14.5px; line-height:1.7; color:var(--ink); white-space:pre-wrap; } +.anamnese-box{ + width:100%; min-height:160px; border:1px solid var(--border); border-radius:var(--radius-s); padding:12px 14px; + font-family:var(--font-body); font-size:14.5px; line-height:1.7; background:var(--surface); outline:0; resize:vertical; +} +.anamnese-box:focus{ border-color:var(--accent); } +.hint{ font-size:11.5px; color:var(--ink-faint); margin-top:8px; } + +.anamnese-toggle{ display:flex; align-items:center; justify-content:space-between; } +.anamnese-toggle p{ margin:0; font-size:12.5px; color:var(--ink-faint); } +.af-section-grid{ display:flex; flex-direction:column; gap:16px; margin-top:14px; } +.af-section h4{ + font-family:var(--font-head); font-size:11.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; + color:var(--accent-ink); background:var(--accent-tint); display:inline-block; padding:4px 10px; border-radius:99px; margin-bottom:12px; +} +.af-grid{ display:grid; grid-template-columns:1fr 1fr; gap:12px 20px; } +.af-field{ display:flex; flex-direction:column; gap:4px; } +.af-field.af-wide{ grid-column:1 / -1; } +.af-field label{ font-size:11px; font-family:var(--font-head); font-weight:600; color:var(--ink-soft); } +.af-field input[type=text]{ border:1px solid var(--border); border-radius:var(--radius-s); padding:7px 9px; font-size:13px; background:var(--surface); outline:0; } +.af-field input[type=text]:focus{ border-color:var(--accent); } +.af-field textarea{ border:1px solid var(--border); border-radius:var(--radius-s); padding:8px 9px; font-size:13px; background:var(--surface); outline:0; min-height:56px; resize:vertical; font-family:inherit; } +.af-field textarea:focus{ border-color:var(--accent); } +.af-options{ display:flex; flex-wrap:wrap; gap:6px 14px; padding-top:2px; } +.af-opt{ display:flex; align-items:center; gap:5px; font-size:12.5px; color:var(--ink); cursor:pointer; } +.af-opt input{ accent-color:var(--accent); } +.af-pairs{ display:flex; flex-direction:column; gap:6px; } +.af-pair{ display:flex; align-items:center; gap:14px; font-size:12.5px; } +.af-pair .af-opt{ min-width:110px; } + +.consent-list{ display:flex; flex-direction:column; gap:0; margin-top:4px; } +.consent-item{ display:flex; gap:10px; padding:9px 0; border-top:1px solid var(--border); align-items:flex-start; } +.consent-item:first-child{ border-top:0; } +.consent-item input{ margin-top:3px; accent-color:var(--accent); flex-shrink:0; } +.consent-item span{ font-size:13px; line-height:1.5; } + +.btn{ + font-family:var(--font-head); font-size:12.5px; font-weight:600; padding:8px 14px; + border-radius:var(--radius-s); border:1px solid var(--border); background:var(--surface); + cursor:pointer; display:inline-flex; align-items:center; gap:6px; color:var(--ink); +} +.btn:hover{ border-color:var(--accent); color:var(--accent-ink); } +.btn.primary{ background:var(--accent); border-color:var(--accent); color:#fff; } +.btn.primary:hover{ filter:brightness(1.05); color:#fff; } +.btn.danger{ background:var(--critical); border-color:var(--critical); color:#fff; } +.btn.danger:hover{ filter:brightness(1.05); color:#fff; } +.btn.ghost{ background:transparent; border-color:transparent; } +.btn svg{ width:14px; height:14px; } + +.note-composer{ display:flex; gap:8px; margin-bottom:14px; } +.note-composer input{ flex:1; border:1px solid var(--border); border-radius:var(--radius-s); padding:9px 12px; background:var(--surface); font-size:13.5px; outline:0; } +.note{ display:flex; gap:12px; padding:11px 0; border-top:1px solid var(--border); } +.note:first-child{ border-top:0; } +.note .date{ font-family:var(--font-head); font-size:11px; color:var(--ink-faint); width:78px; flex-shrink:0; padding-top:1px; font-variant-numeric:tabular-nums; } + +.timeline{ position:relative; padding-left:22px; } +.timeline::before{ content:""; position:absolute; left:5px; top:4px; bottom:4px; width:2px; background:var(--border); } +.tl-item{ position:relative; padding-bottom:18px; } +.tl-item:last-child{ padding-bottom:0; } +.tl-item::before{ content:""; position:absolute; left:-22px; top:3px; width:11px; height:11px; border-radius:50%; background:var(--tab-5); border:2px solid var(--surface); box-shadow:0 0 0 1px var(--border); } +.tl-item .date{ font-family:var(--font-head); font-size:11px; font-weight:700; color:var(--ink-faint); letter-spacing:.03em; } +.tl-item h4{ font-size:14px; margin:2px 0 3px; } +.tl-item p{ margin:0; font-size:13.5px; color:var(--ink-soft); } +.composer-2{ display:grid; grid-template-columns:1fr 2fr auto; gap:8px; margin-bottom:16px; } +.composer-2 input{ border:1px solid var(--border); border-radius:var(--radius-s); padding:9px 12px; background:var(--surface); font-size:13.5px; outline:0; } + +.lab-table{ width:100%; border-collapse:collapse; font-size:13.5px; } +.lab-table th{ text-align:left; font-family:var(--font-head); font-size:10.5px; text-transform:uppercase; letter-spacing:.05em; color:var(--ink-faint); padding:6px 8px; border-bottom:1px solid var(--border); } +.lab-table td{ padding:8px 8px; border-bottom:1px solid var(--border); font-variant-numeric:tabular-nums; } +.lab-add-row{ display:grid; grid-template-columns:1fr 1fr 1fr auto; gap:8px; margin-top:10px; } +.lab-add-row input{ border:1px solid var(--border); border-radius:var(--radius-s); padding:7px 9px; background:var(--surface); font-size:12.5px; outline:0; } +.card-head-row{ display:flex; align-items:center; justify-content:space-between; } +.card-head-row .btn{ padding:5px 9px; font-size:11px; } + +.photo-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:10px; } +.photo-tile{ aspect-ratio:1; border-radius:var(--radius-s); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; color:var(--ink-faint); overflow:hidden; background:var(--surface-alt); position:relative; } +.photo-tile img{ width:100%; height:100%; object-fit:cover; display:block; } +.photo-tile .fn{ position:absolute; bottom:0; left:0; right:0; font-size:9.5px; background:rgba(0,0,0,.45); color:#fff; padding:3px 5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.photo-tile svg{ width:26px; height:26px; } +.photo-tile.add{ background:var(--surface-alt); border-style:dashed; cursor:pointer; flex-direction:column; gap:6px; font-size:11px; font-family:var(--font-head); font-weight:600; } +.photo-tile.add:hover{ border-color:var(--accent); color:var(--accent-ink); } + +.doc-row{ display:flex; align-items:center; gap:12px; padding:10px 4px; border-top:1px solid var(--border); } +.doc-row:first-child{ border-top:0; } +.doc-icon{ width:34px; height:34px; border-radius:8px; background:var(--surface-alt); display:flex; align-items:center; justify-content:center; flex-shrink:0; color:var(--ink-soft); } +.doc-icon svg{ width:16px; height:16px; } +.doc-name{ font-weight:600; font-size:13.5px; } +.doc-meta{ font-size:11.5px; color:var(--ink-faint); } +.doc-row .btn{ margin-left:auto; } +.dropzone{ border:1.5px dashed var(--border); border-radius:var(--radius-m); padding:22px; text-align:center; color:var(--ink-faint); font-size:13px; margin-bottom:14px; background:var(--surface-alt); cursor:pointer; } +.dropzone:hover{ border-color:var(--accent); color:var(--accent-ink); } +.dropzone b{ color:var(--accent-ink); font-weight:600; } + +.consent-row{ display:flex; align-items:center; justify-content:space-between; gap:14px; padding:12px 0; border-top:1px solid var(--border); } +.consent-row:first-child{ border-top:0; } +.consent-row .t{ font-weight:600; font-size:13.5px; } +.consent-row .d{ font-size:12px; color:var(--ink-faint); margin-top:2px; } +.badge-ok{ display:flex; align-items:center; gap:5px; font-family:var(--font-head); font-size:11.5px; font-weight:700; color:var(--ok); background:var(--ok-tint); padding:5px 10px; border-radius:99px; white-space:nowrap; border:0; cursor:pointer; } +.badge-off{ display:flex; align-items:center; gap:5px; font-family:var(--font-head); font-size:11.5px; font-weight:700; color:var(--ink-faint); background:var(--surface-alt); padding:5px 10px; border-radius:99px; white-space:nowrap; border:0; cursor:pointer; } +.badge-ok svg, .badge-off svg{ width:12px; height:12px; } +.sig-box{ border:1px solid var(--border); border-radius:var(--radius-s); padding:6px; margin-top:10px; background:var(--surface-alt); } +.sig-box img{ width:100%; display:block; max-height:90px; object-fit:contain; } +.sig-pad-wrap{ margin-top:10px; } +.sig-pad{ width:100%; height:160px; border:1px solid var(--border); border-radius:var(--radius-s); background:#fff; touch-action:none; cursor:crosshair; } +.sig-actions{ display:flex; gap:8px; margin-top:8px; } + +.bm-layout{ display:grid; grid-template-columns:1fr 260px; gap:18px; align-items:start; } +.bm-hint{ font-size:12.5px; color:var(--ink-faint); margin-bottom:10px; } +.bm-stage{ position:relative; background:var(--surface-alt); border:1px solid var(--border); border-radius:var(--radius-m); aspect-ratio:16/10.5; cursor:crosshair; overflow:hidden; } +.bm-stage svg.silhouette{ position:absolute; inset:0; width:100%; height:100%; } +.bm-stage img.silhouette{ position:absolute; inset:0; width:100%; height:100%; object-fit:contain; pointer-events:none; } +.bm-pin{ position:absolute; width:20px; height:20px; margin:-10px 0 0 -10px; border-radius:50% 50% 50% 0; transform:rotate(-45deg); border:2px solid var(--surface); box-shadow:0 1px 3px rgba(0,0,0,.25); cursor:pointer; } +.bm-pin span{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; transform:rotate(45deg); font-family:var(--font-head); font-size:9px; font-weight:800; color:#fff; } +.bm-pop{ position:absolute; width:248px; background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-m); box-shadow:var(--shadow); padding:11px; z-index:10; } +.bm-pop textarea{ width:100%; border:1px solid var(--border); border-radius:var(--radius-s); padding:7px 9px; font-size:12.5px; resize:none; height:52px; outline:0; background:var(--surface); margin-bottom:8px; } +.sev-row{ display:flex; gap:5px; margin-bottom:9px; } +.sev-btn{ flex:1; font-size:10.5px; font-family:var(--font-head); font-weight:700; padding:6px 4px; border-radius:6px; border:1.5px solid var(--border); background:var(--surface); cursor:pointer; color:var(--ink-soft); } +.sev-btn.sel-ok{ border-color:var(--ok); background:var(--ok-tint); color:var(--ok); } +.sev-btn.sel-warn{ border-color:var(--warn); background:var(--warn-tint); color:var(--warn); } +.sev-btn.sel-critical{ border-color:var(--critical); background:var(--critical-tint); color:var(--critical); } + +.quicktype-row{ display:flex; flex-wrap:wrap; gap:7px; } +.quicktype-chip{ font-family:var(--font-head); font-size:12px; font-weight:600; padding:7px 14px; border-radius:99px; border:1.5px solid var(--border); background:var(--surface); color:var(--ink-soft); cursor:pointer; white-space:nowrap; transition:border-color .12s, color .12s, background .12s; } +.quicktype-chip:hover{ border-color:var(--accent); color:var(--accent-ink); } +.quicktype-chip.active{ border-color:var(--accent); background:var(--accent-tint); color:var(--accent-ink); } + +/* Toggle-chip checkbox (Ernährungsplan Tage/Tageszeiten) — same visual language as + .quicktype-chip but backed by a real (visually hidden) checkbox input. */ +.chip-check{ position:relative; display:inline-flex; } +.chip-check input{ position:absolute; inset:0; opacity:0; margin:0; cursor:pointer; } +.chip-check span{ font-family:var(--font-head); font-size:11.5px; font-weight:600; padding:6px 11px; border-radius:99px; border:1.5px solid var(--border); background:var(--surface); color:var(--ink-soft); white-space:nowrap; pointer-events:none; transition:border-color .12s, color .12s, background .12s; } +.chip-check:has(input:checked) span{ border-color:var(--accent); background:var(--accent-tint); color:var(--accent-ink); } + +.fp-row{ border:1px solid var(--border); border-radius:var(--radius-m); padding:13px 14px; margin-bottom:10px; background:var(--surface-alt); } +.fp-row-head{ display:flex; gap:8px; margin-bottom:12px; } +.fp-row-head input{ flex:1; border:1px solid var(--border); border-radius:var(--radius-s); padding:8px 10px; font-size:13px; background:var(--surface); outline:0; } +.fp-row-head input:focus{ border-color:var(--accent); } +.fp-row-remove{ flex-shrink:0; width:34px; height:34px; display:flex; align-items:center; justify-content:center; border:1px solid var(--border); border-radius:var(--radius-s); background:var(--surface); color:var(--ink-soft); cursor:pointer; } +.fp-row-remove svg{ width:15px; height:15px; } +.fp-row-remove:hover{ border-color:var(--critical); color:var(--critical); } +.fp-row-grid{ display:flex; flex-wrap:wrap; gap:16px 22px; align-items:flex-start; } +.fp-field{ display:flex; flex-direction:column; gap:6px; } +.fp-field .lbl{ font-size:10.5px; font-family:var(--font-head); font-weight:600; color:var(--ink-faint); letter-spacing:.03em; } +.fp-field .chips{ display:flex; flex-wrap:wrap; gap:5px; } +.fp-field .fp-menge{ width:120px; border:1px solid var(--border); border-radius:var(--radius-s); padding:7px 9px; font-size:12.5px; background:var(--surface); outline:0; } +.fp-field .fp-menge:focus{ border-color:var(--accent); } +.bm-pop-actions{ display:flex; flex-wrap:wrap; gap:6px; justify-content:flex-end; } +.bm-pop-actions .btn.danger{ margin-right:auto; } +.bm-pop-actions .btn{ padding:6px 11px; font-size:11.5px; white-space:nowrap; } +.finding-list h3{ margin-bottom:10px; } +.finding{ display:flex; gap:9px; padding:9px 0; border-top:1px solid var(--border); align-items:flex-start; } +.finding:first-child{ border-top:0; } +.finding .sw{ width:10px; height:10px; border-radius:50%; margin-top:4px; flex-shrink:0; } +.finding .tx{ font-size:12.5px; line-height:1.45; } +.finding .rm{ margin-left:auto; background:none; border:0; color:var(--ink-faint); cursor:pointer; font-size:14px; line-height:1; padding:2px; flex-shrink:0; } +.finding .rm:hover{ color:var(--critical); } + +.modal-overlay{ position:fixed; inset:0; background:rgba(33,27,23,.42); display:flex; align-items:center; justify-content:center; z-index:100; } +.modal-card{ width:380px; max-width:calc(100vw - 40px); max-height:calc(100vh - 40px); overflow-y:auto; background:var(--surface); border-radius:var(--radius-l); border:1px solid var(--border); box-shadow:var(--shadow); padding:22px; } +.modal-card h2{ font-size:17px; margin-bottom:16px; } +.modal-field{ margin-bottom:12px; } +.modal-field .lbl{ font-size:11px; font-family:var(--font-head); font-weight:600; color:var(--ink-faint); letter-spacing:.03em; margin-bottom:4px; display:block; } +.modal-field input, .modal-field select{ width:100%; border:1px solid var(--border); border-radius:var(--radius-s); padding:9px 11px; font-size:13.5px; outline:0; background:var(--surface); } +.modal-field input:focus, .modal-field select:focus{ border-color:var(--accent); } +.modal-actions{ display:flex; justify-content:flex-end; gap:8px; margin-top:18px; } +.modal-card.wide{ width:460px; } +.modal-card.xwide{ width:640px; } +.settings-tabstrip{ display:flex; gap:3px; border-bottom:1px solid var(--border); margin-bottom:18px; } +.settings-panel{ display:none; } +.settings-panel.active{ display:block; } +.backup-row{ display:flex; align-items:center; justify-content:space-between; gap:10px; padding:9px 0; border-bottom:1px solid var(--border); } +.backup-row:last-child{ border-bottom:0; } +.backup-row .d{ font-size:13px; } +.backup-row .d small{ display:block; font-size:11px; color:var(--ink-faint); margin-top:1px; } +.modal-field .modal-hint{ font-size:11px; color:var(--ink-faint); margin-top:6px; } +.modal-field label.check-row{ display:flex; align-items:center; gap:8px; font-size:12.5px; color:var(--ink); cursor:pointer; } +.modal-field label.check-row input{ width:auto; accent-color:var(--accent); } +.modal-status{ font-size:12px; border-radius:var(--radius-s); padding:8px 10px; margin-top:10px; } +.modal-status.ok{ background:var(--ok-tint); color:var(--ok); } +.modal-status.error{ background:var(--critical-tint); color:var(--critical); } + +.icon-btn{ padding:8px; } +.icon-btn svg{ width:17px; height:17px; } + +.contract-text{ max-height:320px; overflow-y:auto; border:1px solid var(--border); border-radius:var(--radius-s); padding:14px 16px; background:var(--surface-alt); font-size:12.5px; line-height:1.65; } +.contract-text h4{ font-family:var(--font-head); font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.03em; color:var(--accent-ink); margin:14px 0 4px; } +.contract-text h4:first-child{ margin-top:0; } +.contract-text p{ margin:0 0 8px; } +.confirm-row{ display:flex; align-items:flex-start; gap:10px; margin-top:14px; padding-top:14px; border-top:1px solid var(--border); } +.confirm-row input{ margin-top:3px; accent-color:var(--accent); flex-shrink:0; } +.confirm-row span{ font-size:12.5px; line-height:1.5; } + +.email-status{ font-size:12px; border-radius:var(--radius-s); padding:8px 11px; margin-top:10px; } +.email-status.ok{ background:var(--ok-tint); color:var(--ok); } +.email-status.error{ background:var(--critical-tint); color:var(--critical); } +.email-status.pending{ background:var(--surface-alt); color:var(--ink-faint); } + +.cal-head{ display:flex; align-items:center; gap:14px; margin-bottom:18px; flex-wrap:wrap; } +.cal-head h1{ font-size:24px; font-weight:700; min-width:220px; } +.cal-nav{ display:flex; align-items:center; gap:4px; } +.cal-nav-btn{ width:32px; height:32px; display:flex; align-items:center; justify-content:center; border-radius:var(--radius-s); border:1px solid var(--border); background:var(--surface); cursor:pointer; color:var(--ink-soft); } +.cal-nav-btn:hover{ border-color:var(--accent); color:var(--accent-ink); } +.cal-nav-btn svg{ width:15px; height:15px; } +.cal-head-spacer{ flex:1; } + +.cal-grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:8px; } +.cal-dow{ font-family:var(--font-head); font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--ink-faint); text-align:center; padding-bottom:4px; } +.cal-day{ + background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-m); + min-height:104px; padding:8px; cursor:pointer; display:flex; flex-direction:column; gap:4px; +} +.cal-day:hover{ border-color:var(--accent); } +.cal-day.other-month{ background:var(--surface-alt); opacity:.55; } +.cal-day.today{ border-color:var(--accent); box-shadow:0 0 0 1px var(--accent) inset; } +.cal-day-num{ font-family:var(--font-head); font-size:12px; font-weight:700; color:var(--ink-soft); } +.cal-day.today .cal-day-num{ color:var(--accent-ink); } +.cal-appt{ + font-size:10.5px; font-family:var(--font-head); font-weight:600; padding:3px 6px; border-radius:6px; + background:var(--accent-tint); color:var(--accent-ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; + cursor:pointer; +} +.cal-appt:hover{ filter:brightness(.97); } +.cal-appt-more{ font-size:10px; color:var(--ink-faint); font-family:var(--font-head); font-weight:600; padding:0 6px; } + +.agenda-card{ margin-top:20px; } +.agenda-item{ display:flex; gap:12px; align-items:flex-start; padding:10px 0; border-top:1px solid var(--border); cursor:pointer; } +.agenda-item:first-child{ border-top:0; } +.agenda-item:hover .agenda-title{ color:var(--accent-ink); } +.agenda-date{ font-family:var(--font-head); font-size:11px; color:var(--ink-faint); width:100px; flex-shrink:0; line-height:1.4; font-variant-numeric:tabular-nums; } +.agenda-title{ font-weight:600; font-size:13.5px; } +.agenda-sub{ font-size:12px; color:var(--ink-soft); margin-top:1px; } + +@media (max-width:880px){ + .shell{ grid-template-columns:1fr; } + .bm-layout{ grid-template-columns:1fr; } + + /* Master/detail on narrow screens: show either the Besitzer/Tier-Liste or the + geöffnete Mappe, never a half-hidden mix of both — body[data-view] + .has-animal + are kept up to date in app.js (switchView/renderFolder/back button). */ + body[data-view="patients"]:not(.has-animal) .main{ display:none; } + body[data-view="patients"].has-animal .directory{ display:none; } + body[data-view="calendar"] .directory, + body[data-view="remedies"] .directory{ display:none; } + + #mobile-back-btn{ display:none; } + body[data-view="patients"].has-animal #mobile-back-btn{ display:flex; } + + .topbar{ flex-wrap:wrap; row-gap:8px; padding:10px 14px; } + .brand-sub{ display:none; } + .view-switch-btn{ padding:6px 9px; font-size:11px; } + .crumb{ font-size:12px; } + .crumb span{ display:none; } /* keep just the animal name (the ) on narrow screens */ + + .tabstrip{ flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch; } + .tab{ flex-shrink:0; } + + .grid-2{ grid-template-columns:1fr; } + .af-grid{ grid-template-columns:1fr; } + .composer-2{ grid-template-columns:1fr; } + .lab-add-row{ grid-template-columns:1fr; } + .folder-head{ flex-direction:column; } + .owner-card{ min-width:0; width:100%; } + + .settings-tabstrip{ flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch; } + + body{ overflow-x:hidden; } +} diff --git a/web-client/index.html b/web-client/index.html new file mode 100644 index 0000000..3d37f81 --- /dev/null +++ b/web-client/index.html @@ -0,0 +1,135 @@ + + + + + + +Fellakte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
Fellakte
+
Tierheilpraxis Iris Hack
+
+
+
+ + + +
+ +
+
+ Verbunden + + + +
+ +
+ + +
+
+ +

Noch kein Tier ausgewählt.
Links einen Besitzer bzw. ein Tier antippen, um seine Mappe zu öffnen.

+
+ + + + + +
+
+
+ + + + + + + + + + + + + diff --git a/web-client/offline-store.js b/web-client/offline-store.js new file mode 100644 index 0000000..f52bddd --- /dev/null +++ b/web-client/offline-store.js @@ -0,0 +1,91 @@ +// offline-store.js — the tablet's local data store (IndexedDB), so the app keeps +// working with zero connectivity once the directory/an animal has been opened while +// still on the practice WiFi. Two jobs: (1) cache what's been read, so it can be +// re-displayed offline; (2) queue what's been written while offline, so it can be +// replayed against the practice PC once reachable again. +"use strict"; + +(function () { + const DB_NAME = "fellakte-offline"; + const DB_VERSION = 1; + let dbPromise = null; + + function openDb() { + if (dbPromise) return dbPromise; + dbPromise = new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, DB_VERSION); + req.onupgradeneeded = () => { + const db = req.result; + if (!db.objectStoreNames.contains("bundles")) db.createObjectStore("bundles", { keyPath: "id" }); + if (!db.objectStoreNames.contains("meta")) db.createObjectStore("meta", { keyPath: "key" }); + if (!db.objectStoreNames.contains("queue")) db.createObjectStore("queue", { keyPath: "id", autoIncrement: true }); + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + return dbPromise; + } + + // Runs a write (put/add/delete) against `name` and resolves once the transaction + // commits. The return value of `fn` (an IDBRequest) is not needed by any caller. + async function runWrite(name, fn) { + const db = await openDb(); + return new Promise((resolve, reject) => { + const t = db.transaction(name, "readwrite"); + fn(t.objectStore(name)); + t.oncomplete = () => resolve(); + t.onerror = () => reject(t.error); + }); + } + // Runs a single read request against `name` and resolves with its result + // (undefined on error, so callers can treat a miss the same as "not cached"). + async function runRead(name, fn) { + const db = await openDb(); + return new Promise((resolve) => { + const t = db.transaction(name, "readonly"); + const req = fn(t.objectStore(name)); + req.onsuccess = () => resolve(req.result); + req.onerror = () => resolve(undefined); + }); + } + + window.offlineStore = { + async saveDirectory(list) { + await runWrite("meta", (s) => s.put({ key: "directory", value: list, at: Date.now() })); + }, + async getDirectory() { + const row = await runRead("meta", (s) => s.get("directory")); + return row ? row.value : null; + }, + + async saveBundle(bundle) { + if (!bundle || !bundle.animal) return; + await runWrite("bundles", (s) => s.put({ id: bundle.animal.id, bundle, at: Date.now() })); + }, + async getBundle(id) { + const row = await runRead("bundles", (s) => s.get(id)); + return row ? row.bundle : null; + }, + // Scans cached bundles for one containing a finding with this id — used for + // update/delete offline, where the caller only has the finding id, not the + // animal it belongs to (same shape as the desktop IPC calls). + async findAnimalIdForFinding(findingId) { + const rows = (await runRead("bundles", (s) => s.getAll())) || []; + const hit = rows.find((r) => r.bundle.findings.some((f) => f.id === findingId)); + return hit ? hit.id : null; + }, + + async enqueue(op) { + await runWrite("queue", (s) => s.add({ ...op, createdAt: Date.now() })); + }, + async listQueue() { + return (await runRead("queue", (s) => s.getAll())) || []; + }, + async removeFromQueue(id) { + await runWrite("queue", (s) => s.delete(id)); + }, + async queueCount() { + return (await this.listQueue()).length; + }, + }; +})(); diff --git a/web-client/pair-client.js b/web-client/pair-client.js new file mode 100644 index 0000000..03d2cf2 --- /dev/null +++ b/web-client/pair-client.js @@ -0,0 +1,435 @@ +// pair-client.js — loaded before app.js. Defines window.api for the web client +// (talking to main/webserver.js over the local network instead of Electron IPC), +// handles the one-time pairing screen, and only injects app.js once a valid +// session is confirmed — app.js calls window.api immediately on load, so it must +// never run before window.api (and a working session) exist. +// +// Phase 3: offline. Reads fall back to the local cache (offline-store.js) when the +// practice PC isn't reachable; writes to Notizen/Befunde queue locally and apply +// optimistically to the cache, then replay automatically once reachable again. This +// only works for a tab that was already open (and the animal already viewed) while +// still on the practice WiFi — a from-scratch cold load with zero network needs a +// Service Worker, which needs HTTPS, which this plain-WLAN setup deliberately skips. +"use strict"; + +(function () { + const TOKEN_KEY = "fellakte_token"; + let token = localStorage.getItem(TOKEN_KEY); + let isOnline = true; + let syncing = false; + + const overlay = document.getElementById("pairing-overlay"); + const form = document.getElementById("pair-form"); + const codeInput = document.getElementById("pair-code-input"); + const status = document.getElementById("pair-status"); + const toast = document.getElementById("readonly-toast"); + + function showPairing(prefillCode) { + overlay.hidden = false; + if (prefillCode) codeInput.value = prefillCode; + codeInput.focus(); + } + function hidePairing() { + overlay.hidden = true; + } + + function authHeaders() { + return token ? { Authorization: "Bearer " + token } : {}; + } + + let toastTimer = null; + function showToast(text) { + if (!toast) return; + toast.textContent = text; + toast.hidden = false; + clearTimeout(toastTimer); + toastTimer = setTimeout(() => (toast.hidden = true), 3200); + } + function readOnlyNotice() { + showToast("Bearbeiten kommt in einer späteren Phase — aktuell nur zum Ansehen."); + } + function offlineUnavailableNotice() { + showToast("Ohne Verbindung aktuell nicht möglich."); + } + + function showWarningBanner(info) { + const banner = document.getElementById("warning-banner"); + const text = document.getElementById("warning-banner-text"); + if (!banner || !text) return; + const when = new Date(info.updatedAt).toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" }); + text.textContent = `Achtung: "${info.host}" wurde zuletzt um ${when} Uhr ebenfalls verwendet — kurz absprechen, bevor beide gleichzeitig speichern.`; + banner.hidden = false; + } + + async function renderSyncStatus() { + const chip = document.getElementById("sync-status"); + if (!chip) return; + const pending = await window.offlineStore.queueCount(); + if (isOnline && pending === 0) { + chip.textContent = "Verbunden"; + chip.style.background = "var(--ok-tint)"; + chip.style.color = "var(--ok)"; + } else if (isOnline && pending > 0) { + chip.textContent = `Synchronisiere … (${pending})`; + chip.style.background = "var(--warn-tint)"; + chip.style.color = "var(--warn)"; + } else if (pending > 0) { + chip.textContent = `Offline — ${pending} wartend`; + chip.style.background = "var(--warn-tint)"; + chip.style.color = "var(--warn)"; + } else { + chip.textContent = "Offline"; + chip.style.background = "var(--surface-alt)"; + chip.style.color = "var(--ink-faint)"; + } + } + function setOnline(v) { + const changed = v !== isOnline; + isOnline = v; + renderSyncStatus(); + if (changed && v) trySync(); + } + + // ---- reads: network first, cache fallback ---- + async function apiGet(path, cache) { + try { + const res = await fetch(path, { headers: authHeaders() }); + if (res.status === 401) { + token = null; + localStorage.removeItem(TOKEN_KEY); + showPairing(); + throw new Error("unauthorized"); + } + if (!res.ok) throw new Error("request-failed:" + res.status); + const data = await res.json(); + setOnline(true); + if (cache && cache.kind === "directory") window.offlineStore.saveDirectory(data); + if (cache && cache.kind === "bundle") window.offlineStore.saveBundle(data); + return data; + } catch (err) { + if (err.message === "unauthorized") throw err; + setOnline(false); + if (cache && cache.kind === "directory") { + const cached = await window.offlineStore.getDirectory(); + if (cached) return cached; + } + if (cache && cache.kind === "bundle") { + const cached = await window.offlineStore.getBundle(cache.key); + if (cached) return cached; + } + throw err; + } + } + + // ---- writes: network first; on failure, queue + apply optimistically to the + // cache when `offlineSpec` says this kind of write supports that (Notizen, + // Befunde). Surfaces the server's "someone else is using this data folder right + // now" advisory as a dismissible banner (same idea as the desktop app's startup + // dialog) — advisory only, matching how the two practice PCs already behave. + async function apiSend(method, path, body, offlineSpec) { + try { + const res = await fetch(path, { + method, + headers: { ...authHeaders(), "Content-Type": "application/json" }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + if (res.status === 401) { + token = null; + localStorage.removeItem(TOKEN_KEY); + showPairing(); + throw new Error("unauthorized"); + } + if (!res.ok) throw new Error("request-failed:" + res.status); + const data = await res.json().catch(() => ({})); + setOnline(true); + if (data && data.warning) showWarningBanner(data.warning); + return data; + } catch (err) { + if (err.message === "unauthorized") throw err; + setOnline(false); + if (!offlineSpec) { + offlineUnavailableNotice(); + throw err; + } + await window.offlineStore.enqueue({ method, path, body }); + await applyOptimistic(offlineSpec, body); + renderSyncStatus(); + return { ok: true, offline: true }; + } + } + + async function applyOptimistic(spec, body) { + const nowIso = new Date().toISOString(); + const localId = () => "lokal-" + Date.now() + "-" + Math.random().toString(36).slice(2, 7); + + if (spec.kind === "note") { + const bundle = await window.offlineStore.getBundle(body.animalId); + if (!bundle) return; + bundle.notes.unshift({ + id: localId(), + animal_id: body.animalId, + date: new Date().toLocaleDateString("de-DE"), + text: body.text, + created_at: nowIso, + }); + await window.offlineStore.saveBundle(bundle); + } else if (spec.kind === "finding-add") { + const bundle = await window.offlineStore.getBundle(body.animalId); + if (!bundle) return; + bundle.findings.push({ + id: localId(), + animal_id: body.animalId, + x: body.x, + y: body.y, + severity: body.severity, + text: body.text, + created_at: nowIso, + }); + await window.offlineStore.saveBundle(bundle); + } else if (spec.kind === "finding-update") { + const animalId = await window.offlineStore.findAnimalIdForFinding(spec.findingId); + if (!animalId) return; + const bundle = await window.offlineStore.getBundle(animalId); + const f = bundle.findings.find((x) => x.id === spec.findingId); + if (f) { + f.severity = body.severity; + f.text = body.text; + await window.offlineStore.saveBundle(bundle); + } + } else if (spec.kind === "finding-delete") { + const animalId = await window.offlineStore.findAnimalIdForFinding(spec.findingId); + if (!animalId) return; + const bundle = await window.offlineStore.getBundle(animalId); + bundle.findings = bundle.findings.filter((x) => x.id !== spec.findingId); + await window.offlineStore.saveBundle(bundle); + } + } + + // Replays the queue in order against the real server. Stops (leaves the rest + // queued) at the first failure — still offline, or the practice PC went away + // mid-sync. On a full, non-empty drain, reloads so the whole page re-reads fresh, + // server-confirmed data instead of the optimistic local copies. + async function trySync() { + if (syncing) return; + syncing = true; + try { + const queue = await window.offlineStore.listQueue(); + if (!queue.length) { + setOnline(await pingServer()); + return; + } + let drained = 0; + for (const op of queue) { + try { + const res = await fetch(op.path, { + method: op.method, + headers: { ...authHeaders(), "Content-Type": "application/json" }, + body: op.body !== undefined ? JSON.stringify(op.body) : undefined, + }); + if (res.status === 401) { + token = null; + localStorage.removeItem(TOKEN_KEY); + showPairing(); + return; + } + if (!res.ok) throw new Error("sync-failed:" + res.status); + await window.offlineStore.removeFromQueue(op.id); + drained++; + } catch { + setOnline(false); + return; // still offline (or PC unreachable) — rest stays queued for next try + } + } + setOnline(true); + if (drained > 0) location.reload(); + } finally { + syncing = false; + } + } + + async function pingServer() { + try { + await fetch("/api/directory", { headers: authHeaders() }); + return true; // any HTTP response at all means the practice PC is reachable + } catch { + return false; + } + } + + // Resolves with `fallback` after showing the read-only notice — safe for callers + // that just re-render afterward (await refreshBundle(); renderPanels();). + function stub(fallback) { + return async () => { + readOnlyNotice(); + return fallback; + }; + } + // Rejects instead — used only where the caller immediately depends on the result + // (e.g. `const id = await createAnimal(...); await selectAnimal(id)`), so a + // silently-wrong id can't cascade into a broken render. + function rejectingStub() { + return async () => { + readOnlyNotice(); + throw new Error("read-only"); + }; + } + + window.api = { + listDirectory: () => apiGet("/api/directory", { kind: "directory" }), + getAnimalBundle: (id) => apiGet("/api/animal/" + encodeURIComponent(id), { kind: "bundle", key: id }), + listAppointments: (fromDate, toDate) => + apiGet(`/api/appointments?from=${encodeURIComponent(fromDate || "")}&to=${encodeURIComponent(toDate || "")}`), + listRemedies: (query) => apiGet("/api/remedies?q=" + encodeURIComponent(query || "")), + searchNotes: (query) => apiGet("/api/search-notes?q=" + encodeURIComponent(query || "")), + openDocument: async (documentId) => { + const { url } = await apiGet("/api/document-url/" + encodeURIComponent(documentId)); + window.open(url, "_blank"); + return true; + }, + documentAbsPath: stub(null), + dataFolder: stub(null), + + createOwner: rejectingStub(), + createAnimal: rejectingStub(), + updateAnimalField: stub(true), + updateOwnerField: stub(true), + deleteAnimal: stub(true), + deleteOwner: stub(true), + + addNote: (animalId, text) => apiSend("POST", "/api/note", { animalId, text }, { kind: "note" }), + addHistoryEntry: stub(true), + + addLabResult: stub(null), + addLabValue: stub(true), + deleteLabResult: stub(true), + + addFinding: (animalId, finding) => apiSend("POST", "/api/finding", { animalId, ...finding }, { kind: "finding-add" }), + updateFinding: (id, patch) => + apiSend("PATCH", "/api/finding/" + encodeURIComponent(id), patch, { kind: "finding-update", findingId: id }), + deleteFinding: (id) => + apiSend("DELETE", "/api/finding/" + encodeURIComponent(id), undefined, { kind: "finding-delete", findingId: id }), + + addExam: stub(null), + + // Phase 4: needs a live connection to the practice PC (PDF/E-Mail are server-side + // operations — see the file header) — not queued offline like Notizen/Befunde. + setConsent: (animalId, patch) => apiSend("POST", "/api/consent", { animalId, ...patch }), + saveSignature: (animalId, dataUrl) => apiSend("POST", "/api/consent/signature", { animalId, dataUrl }), + setContract: (animalId, patch) => apiSend("POST", "/api/contract", { animalId, ...patch }), + saveContractSignature: (animalId, dataUrl) => apiSend("POST", "/api/contract/signature", { animalId, dataUrl }), + + generatePdf: (animalId, kind, content) => apiSend("POST", "/api/document/pdf", { animalId, kind, ...content }), + generateTemplatePdf: (animalId, filenamePrefix, content) => + apiSend("POST", "/api/document/template-pdf", { animalId, filenamePrefix, ...content }), + sendDocumentEmail: (animalId, kind, file) => apiSend("POST", "/api/document/email", { animalId, kind, ...file }), + + addRemedy: stub(null), + updateRemedy: stub(true), + deleteRemedy: stub(true), + + // Termine: not yet queued for offline (Phase 3 scope is Notizen/Befunde) — a + // clear "not available offline" notice instead of queueing silently. + addAppointment: (data) => apiSend("POST", "/api/appointment", data), + updateAppointment: (id, data) => apiSend("PATCH", "/api/appointment/" + encodeURIComponent(id), data), + deleteAppointment: (id) => apiSend("DELETE", "/api/appointment/" + encodeURIComponent(id)), + + getLexwarePath: stub(null), + setLexwarePath: stub(true), + openLexware: stub({ ok: false, error: "not-available" }), + + getSmtpSettings: stub(null), + setSmtpSettings: stub(true), + testSmtpSettings: stub({ ok: false, error: "not-available" }), + + getReminderLead: stub(30), + setReminderLead: stub(true), + + backupNow: stub({ ok: false, error: "not-available" }), + getBackupInfo: stub({ count: 0, last: null }), + listBackups: stub([]), + openBackupFolder: stub(true), + restoreBackup: stub({ ok: false, error: "not-available" }), + + addDocument: stub([]), + }; + + const warningClose = document.getElementById("warning-banner-close"); + if (warningClose) warningClose.addEventListener("click", () => (document.getElementById("warning-banner").hidden = true)); + + function loadApp() { + const s = document.createElement("script"); + s.src = "app.js"; + document.body.appendChild(s); + } + + // Distinguishes "token is genuinely invalid" (401 → must re-pair) from "can't + // reach the practice PC right now" (network error → proceed in offline mode with + // whatever's cached, a valid token just can't be re-confirmed this moment). + async function checkSession() { + try { + const res = await fetch("/api/directory", { headers: authHeaders() }); + if (res.status === 401) return "invalid"; + return "online"; + } catch { + return "offline"; + } + } + + form.addEventListener("submit", async (e) => { + e.preventDefault(); + const code = codeInput.value.trim(); + if (!code) return; + status.textContent = "Verbinde …"; + try { + const res = await fetch("/api/pair", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ code, deviceName: guessDeviceName() }), + }); + if (!res.ok) { + status.textContent = res.status === 410 || res.status === 429 + ? "Code abgelaufen. Am PC einen neuen Code erzeugen." + : "Code falsch — bitte nochmal prüfen."; + return; + } + const data = await res.json(); + token = data.token; + localStorage.setItem(TOKEN_KEY, token); + location.reload(); + } catch { + status.textContent = "Keine Verbindung zum Praxis-PC — gleiches WLAN?"; + } + }); + + function guessDeviceName() { + const ua = navigator.userAgent || ""; + if (/iPad/.test(ua)) return "iPad"; + if (/iPhone/.test(ua)) return "iPhone"; + if (/Android/.test(ua)) return "Android-Gerät"; + if (/Macintosh/.test(ua)) return "Mac"; + return "Gerät"; + } + + (async function init() { + const params = new URLSearchParams(location.search); + const prefill = params.get("pair"); + + if (!token) { + showPairing(prefill); + return; + } + const state = await checkSession(); + if (state === "invalid") { + showPairing(prefill); + return; + } + hidePairing(); + if (prefill) history.replaceState(null, "", location.pathname); + isOnline = state === "online"; + loadApp(); + renderSyncStatus(); + setInterval(trySync, 15000); + window.addEventListener("online", trySync); + if (state === "online") trySync(); // drain any queue left over from last time + })(); +})();