diff --git a/.gitignore b/.gitignore
index 0c66e71..ee95f35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
-frontend/node_modules/
-frontend/dist/
-__pycache__/
+dist/
+target/
+node_modules/
+
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..5389d56
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1740 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ashpd"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a"
+dependencies = [
+ "async-fs",
+ "async-net",
+ "enumflags2",
+ "futures-channel",
+ "futures-util",
+ "rand",
+ "serde",
+ "serde_repr",
+ "url",
+ "zbus",
+]
+
+[[package]]
+name = "async-broadcast"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8"
+dependencies = [
+ "async-lock",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-net"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
+dependencies = [
+ "async-io",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "async-trait"
+version = "0.1.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
+
+[[package]]
+name = "cc"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "endi"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
+
+[[package]]
+name = "enumflags2"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+
+[[package]]
+name = "h2"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 0.2.12",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "headers"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
+dependencies = [
+ "base64",
+ "bytes",
+ "headers-core",
+ "http 0.2.12",
+ "httpdate",
+ "mime",
+ "sha1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http 0.2.12",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "hermit-abi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "http"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+dependencies = [
+ "bytes",
+ "http 0.2.12",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "0.14.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http 0.2.12",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "libc"
+version = "0.2.156"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5f43f184355eefb8d17fc948dbecf6c13be3c141f20d834ae842193a448c72a"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+dependencies = [
+ "hermit-abi 0.3.9",
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "multer"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2"
+dependencies = [
+ "bytes",
+ "encoding_rs",
+ "futures-util",
+ "http 0.2.12",
+ "httparse",
+ "log",
+ "memchr",
+ "mime",
+ "spin",
+ "version_check",
+]
+
+[[package]]
+name = "next"
+version = "0.1.0"
+dependencies = [
+ "ashpd",
+ "bytes",
+ "futures-util",
+ "num-derive",
+ "num-traits",
+ "tokio",
+ "warp",
+ "zbus",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.36.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "piper"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "polling"
+version = "3.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi 0.4.0",
+ "pin-project-lite",
+ "rustix",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.208"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.208"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "syn"
+version = "2.0.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.39.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
+dependencies = [
+ "futures-util",
+ "log",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+
+[[package]]
+name = "toml_edit"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tungstenite"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http 1.1.0",
+ "httparse",
+ "log",
+ "rand",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "uds_windows"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
+dependencies = [
+ "memoffset",
+ "tempfile",
+ "winapi",
+]
+
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "url"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "warp"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "headers",
+ "http 0.2.12",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "multer",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-tungstenite",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "xdg-home"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "zbus"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "hex",
+ "nix",
+ "ordered-stream",
+ "rand",
+ "serde",
+ "serde_repr",
+ "sha1",
+ "static_assertions",
+ "tokio",
+ "tracing",
+ "uds_windows",
+ "windows-sys 0.52.0",
+ "xdg-home",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "zvariant",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zvariant"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "static_assertions",
+ "url",
+ "zvariant_derive",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9eb95b4
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "next"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+tokio = { version = "1", features = ["full"] }
+warp = "0.3"
+ashpd = "0.9"
+futures-util = { version = "0.3", default-features = false, features = ["sink"] }
+bytes = "1.7"
+num-traits = "0.2"
+num-derive = "0.4"
+zbus = { version = "4", default-features = false, features = ["tokio"] }
diff --git a/InputController.py b/InputController.py
deleted file mode 100644
index f471549..0000000
--- a/InputController.py
+++ /dev/null
@@ -1,80 +0,0 @@
-from pynput.mouse import Controller as MouseController
-from pynput.keyboard import Controller as KeyboardController
-
-import constants as c
-from MessageParser import MessageParser
-
-class InputController():
- def __init__(self):
- self.mouse_controller = MouseController()
- self.keyboard_controller = KeyboardController()
- self.parser = MessageParser()
-
- # Keyboard key press
- self.parser.add_handler("k", {
- "key": "str"
- })
- # Relative mouse movement
- self.parser.add_handler("r", {
- "x": "float",
- "y": "float"
- })
- # Mouse relative scroll
- self.parser.add_handler("s", {
- "x": "float",
- "y": "float"
- })
- # Mouse button down
- self.parser.add_handler("d", {
- "button": "int"
- })
- # Mouse button up
- self.parser.add_handler("u", {
- "button": "int"
- })
- # Mouse button click
- self.parser.add_handler("c", {
- "button": "int"
- })
- def button_code_to_object(self, button_code: int):
- # HACK
- obj = None
- try:
- obj = c.button_code_lookup[button_code]
- except IndexError:
- return c.button_code_lookup[0]
- return obj
- def deserialize_key(self, key: str):
- obj = None
- try:
- obj = c.keyboard_lookup[key]
- except KeyError:
- if len(key) != 1:
- return None
- return key
- return obj
- def process_message(self, message: str) -> bool:
- code, args = self.parser.parse(message)
-
- if code == None:
- print("error while parsing message:", args)
- return False
- elif code == "r":
- self.mouse_controller.move(args["x"], args["y"])
- elif code == "d":
- self.mouse_controller.press(self.button_code_to_object(args["button"]))
- elif code == "u":
- self.mouse_controller.release(self.button_code_to_object(args["button"]))
- elif code == "c":
- self.mouse_controller.click(self.button_code_to_object(args["button"]))
- elif code == "s":
- self.mouse_controller.scroll(args["x"], args["y"])
- elif code == "k":
- key = self.deserialize_key(args["key"])
- if key:
- self.keyboard_controller.tap(key)
- else:
- print("got invalid code from parser (is this a bug with the MessageParser?)")
- return False
-
- return True
diff --git a/LICENSE b/LICENSE
index 117a364..af08f12 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021 hippoz
+Copyright (c) 2024 hippoz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
diff --git a/MessageParser.py b/MessageParser.py
deleted file mode 100644
index 5ef79ec..0000000
--- a/MessageParser.py
+++ /dev/null
@@ -1,40 +0,0 @@
-class MessageParser():
- def __init__(self):
- self.handlers = {}
- def add_handler(self, code: str, args={}):
- self.handlers[code] = {
- "args": args
- }
- def parse(self, message: str):
- if len(message) < 1:
- return None, "Message is empty"
- message_code = message[0]
- if message_code not in self.handlers:
- return None, "Message code is not handled"
- message_arguments = message[1:].split(";")
- handler = self.handlers[message_code]
- decoded_arguments = {}
- if len(handler["args"]) != len(message_arguments):
- return None, "Got message with invalid argument count"
- for i, argument_name in enumerate(handler["args"]):
- argument_type = handler["args"][argument_name]
- if argument_type == "int":
- try:
- decoded_arguments[argument_name] = int(message_arguments[i])
- except ValueError:
- return None, "Error parsing int argument for message (is it really an int?)"
- elif argument_type == "float":
- try:
- decoded_arguments[argument_name] = float(message_arguments[i])
- except ValueError:
- return None, "Error parsing float argument for message (is it really a float?)"
- elif argument_type == "str":
- decoded_arguments[argument_name] = message_arguments[i]
- elif argument_type == "char":
- if len(message_arguments[i]) != 1:
- return None, "Error parsing char argument due to invalid size"
- decoded_arguments[argument_name] = message_arguments[i]
- else:
- raise ValueError("parse(): Message handler references an invalid argument type")
-
- return message_code, decoded_arguments
diff --git a/capybara.py b/capybara.py
deleted file mode 100755
index 78ec090..0000000
--- a/capybara.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import sys
-from asyncio import wait_for, TimeoutError
-from base64 import b64encode
-from sanic import Sanic
-from sanic.response import file, redirect, empty
-
-from InputController import InputController
-from MessageParser import MessageParser
-
-
-app = Sanic(
- "capybara",
- env_prefix="CAPYBARA_"
-)
-input_controller = InputController()
-control_message_parser = MessageParser()
-
-# auth packet
-control_message_parser.add_handler("0", {
- "auth_string": "str"
-})
-
-app.static("app", "frontend/", resource_type="dir")
-
-@app.get("/")
-async def home(req):
- return redirect("/app/app.html")
-
-@app.get("/app")
-async def app_route(req):
- return redirect("/app/app.html")
-
-@app.websocket("/gateway")
-async def gateway(req, ws):
- # Before the client is able to send any data, we must await an authorization packet
- try:
- auth_payload = await wait_for(ws.recv(), timeout=5)
- except TimeoutError:
- await ws.close(code=4001, reason="Invalid auth packet")
- return
-
- code, args = control_message_parser.parse(auth_payload)
- if (
- not code or
- code != "0" or
- args["auth_string"] != app.ctx.EXPECTED_AUTH_STRING
- ): # 0 is the code for the initial auth packet
- await ws.close(code=4001, reason="Invalid auth packet")
- return
-
- await ws.send("1") # send a single `1` to let the client know the server is accepting input packets
-
- while True:
- input_controller.process_message(await ws.recv())
-
-
-def main():
- if not app.config.get("AUTH_PASSWORD"):
- print(
- "capybara: FATAL ERROR: Capybara is expecting the `CAPYBARA_AUTH_PASSWORD` environment variable to be set (to a password of your choice).\nYour users will use this password to authenticate to your server.\nEXITING...",
- file=sys.stderr
- )
- exit(1)
- return
-
- app.ctx.EXPECTED_AUTH_STRING = "%auth%" + str(b64encode(app.config.AUTH_PASSWORD.encode("utf-8")), "utf-8")
- app.run(host='0.0.0.0', port=4003, access_log=False)
-
-if __name__ == "__main__":
- main()
diff --git a/constants.py b/constants.py
deleted file mode 100644
index debbfc3..0000000
--- a/constants.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from pynput.keyboard import Key
-from pynput.mouse import Button
-
-button_code_lookup = [
- Button.left,
- Button.right
-]
-
-keyboard_lookup = {
- "{space}": Key.space,
- "{ent}": Key.enter,
- "{backspace}": Key.backspace
-}
diff --git a/frontend/App.js b/frontend/App.js
deleted file mode 100644
index f0a6ffb..0000000
--- a/frontend/App.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import Banner from "./Banner.js";
-import connection, { ConnectionEvent } from "./connection.js";
-import KeyboardController from "./Keyboard.js";
-import { setItem } from "./storage.js";
-import LoginPrompt from "./LoginPrompt.js";
-import TouchpadController from "./Touchpad.js";
-
-class App {
- constructor() {
- this.connection = connection;
- this.touchpad = new TouchpadController(this.connection);
- this.keyboard = new KeyboardController(this.connection);
- this.bannerComponent = new Banner();
- this.loginPromptComponent = new LoginPrompt(this.connection);
- this.connectionHandlers = [];
- }
-
- mountOn(mount, keepMountChildren=false) {
- this.mountElement = mount;
-
- if (!keepMountChildren) {
- while (mount.firstChild) {
- mount.removeChild(mount.lastChild);
- }
- }
-
- this.connectionHandlers.push([ConnectionEvent.Closed, this.connection.subscribe(ConnectionEvent.Closed, (code) => {
- this.unmountApp();
- if (code === 4001) { // 4001 - code for bad auth
- this.transitionTo("login");
- } else {
- this.transitionTo("reconnectingBanner");
- }
- })]);
-
- this.connectionHandlers.push([ConnectionEvent.Ready, this.connection.subscribe(ConnectionEvent.Ready, () => {
- this.transitionTo("app");
- })]);
- }
-
- unmount() {
- if (this.mountElement) {
- this.unmountApp();
- this.unmountBannerComponent();
- this.unmountLoginComponent();
- }
-
- this.connectionHandlers.forEach(([ event, handler ]) => {
- this.connection.unsubscribe(event, handler);
- });
- }
-
- transitionTo(type) {
- switch (type) {
- case "login":
- this.unmountBannerComponent();
- this.unmountApp();
- this.mountLoginComponent();
- break;
- case "app":
- this.unmountBannerComponent();
- this.unmountLoginComponent();
- this.mountApp();
- break;
- case "reconnectingBanner":
- this.unmountApp();
- this.unmountLoginComponent();
- this.mountBannerComponent("Connecting...", "Looks like you've lost connection. We're trying to reconnect you.");
- break;
- default:
- throw new Error(`transitionTo type ${type} is invalid`);
- }
- }
-
- mountBannerComponent(title, text) {
- this.bannerComponent.mountOn(this.mountElement);
- this.bannerComponent.updateTitle(title);
- this.bannerComponent.updateText(text);
- }
-
- unmountBannerComponent() {
- this.bannerComponent.unmount();
- }
-
- mountLoginComponent() {
- this.loginPromptComponent.mountOn(this.mountElement);
- this.loginPromptComponent.onPasswordSubmitted = (p) => {
- setItem("auth:token", p);
- this.connection.connect();
- };
- }
-
- unmountLoginComponent() {
- if (this.loginPromptComponent)
- this.loginPromptComponent.unmount();
- }
-
- mountApp() {
- this.touchpad.mountOn(this.mountElement);
- this.keyboard.mountOn(this.mountElement);
- }
-
- unmountApp() {
- this.touchpad.unmount();
- this.keyboard.unmount();
- }
-}
-
-export default App;
diff --git a/frontend/Banner.js b/frontend/Banner.js
deleted file mode 100644
index 9443f6a..0000000
--- a/frontend/Banner.js
+++ /dev/null
@@ -1,42 +0,0 @@
-class Banner {
- constructor() {
- this.element = null;
-
- this.title = "";
- this.text = "";
- }
-
- updateText(newText) {
- this.text = newText;
- this.element.querySelector("#banner-text").innerText = this.text;
- }
-
- updateTitle(newTitle) {
- this.title = newTitle;
- this.element.querySelector("#banner-title").innerText = this.title;
- }
-
- mountOn(target) {
- if (this.element)
- return; // Already mounted
-
- this.element = document.createRange().createContextualFragment(`
-
- `).children[0];
-
- target.appendChild(this.element);
- }
-
- unmount() {
- if (!this.element)
- return; // Already unmounted
-
- this.element.parentElement.removeChild(this.element);
- this.element = null;
- }
-}
-
-export default Banner;
diff --git a/frontend/Keyboard.js b/frontend/Keyboard.js
deleted file mode 100644
index 313a16d..0000000
--- a/frontend/Keyboard.js
+++ /dev/null
@@ -1,126 +0,0 @@
-class KeyboardController {
- constructor(connection) {
- this.connection = connection;
- this.container = null;
-
- this.layouts = {
- default: [
- ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
- ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
- ["a", "s", "d", "f", "g", "h", "j", "k", "l"],
- ["🄰", "z", "x", "c", "v", "b", "n", "m", ".", "⌦"],
- ["🔢", " ", "↵"]
- ],
- uppercase: [
- ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"],
- ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
- ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
- ["🅰", "Z", "X", "C", "V", "B", "N", "M", ",", "⌦"],
- ["🔢", " ", "↵"]
- ],
- symbols: [
- ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"],
- ["[", "]", "{", "}", ";", ":", "'", "\"", ",", "<"],
- [".", ">", "-", "_", "=", "+", "/", "?", "\\"],
- ["🄰", "|", "`", "~", "⌦"],
- ["🔤", " ", "↵"]
- ]
- }
- }
-
- _sendKeyPress(l) {
- this.connection.sendMessage("k", [l]);
- }
-
- makeKeyboardForLayout(layout) {
- queueMicrotask(() => {
- while (this.container.firstChild) {
- this.container.removeChild(this.container.lastChild);
- }
-
- const rowsParent = document.createElement("div");
- rowsParent.classList.add("Keyboard-rows");
-
- const rowsFragment = document.createDocumentFragment();
-
- for (let row = 0; row < layout.length; row++) {
- const rowElement = document.createElement("div");
- rowElement.classList.add("Keyboard-row");
- for (let col = 0; col < layout[row].length; col++) {
- const key = layout[row][col];
-
- const colElement = document.createElement("button");
- colElement.classList.add("Keyboard-button");
- colElement.innerText = key;
- colElement.addEventListener("click", () => {
- this.handleKeypress(key);
- });
-
- rowElement.appendChild(colElement);
- }
- rowsFragment.appendChild(rowElement);
- }
-
- rowsParent.appendChild(rowsFragment);
- this.container.appendChild(rowsParent);
- });
- }
-
- handleKeypress(key) {
- switch (key) {
- case "🄰": {
- this.makeKeyboardForLayout(this.layouts.uppercase);
- break;
- }
- case "🅰": {
- this.makeKeyboardForLayout(this.layouts.default);
- break;
- }
- case "🔢": {
- this.makeKeyboardForLayout(this.layouts.symbols);
- break;
- }
- case "🔤": {
- this.makeKeyboardForLayout(this.layouts.default);
- break;
- }
- case " ": {
- this._sendKeyPress("{space}");
- break;
- }
- case "⌦": {
- this._sendKeyPress("{backspace}");
- break;
- }
- case "↵": {
- this._sendKeyPress("{ent}");
- break;
- }
- default: {
- this._sendKeyPress(key);
- break;
- }
- }
- }
-
- mountOn(element) {
- if (this.container)
- return; // Already mounted;
-
- this.container = document.createElement("div");
- this.container.classList.add("Keyboard");
- element.appendChild(this.container);
-
- this.makeKeyboardForLayout(this.layouts.default);
- }
-
- unmount() {
- if (!this.container)
- return; // Not mounted - don't do anything
-
- this.container.parentElement.removeChild(this.container);
- this.container = null;
- }
-}
-
-export default KeyboardController;
\ No newline at end of file
diff --git a/frontend/LoginPrompt.js b/frontend/LoginPrompt.js
deleted file mode 100644
index 4f1ef78..0000000
--- a/frontend/LoginPrompt.js
+++ /dev/null
@@ -1,35 +0,0 @@
-class LoginPrompt {
- constructor() {
- this.element = null;
- }
-
- mountOn(target) {
- if (this.element)
- return; // Already mounted
-
- this.element = document.createRange().createContextualFragment(`
-
-
Login
-
-
-
- `).children[0];
-
- this.element.querySelector("#continue-button").addEventListener("click", () => {
- if (this.onPasswordSubmitted)
- this.onPasswordSubmitted(this.element.querySelector("#code-input").value);
- });
-
- target.appendChild(this.element);
- }
-
- unmount() {
- if (!this.element)
- return; // Already unmounted
-
- this.element.parentElement.removeChild(this.element);
- this.element = null;
- }
-}
-
-export default LoginPrompt;
diff --git a/frontend/Touchpad.js b/frontend/Touchpad.js
deleted file mode 100644
index d26dfeb..0000000
--- a/frontend/Touchpad.js
+++ /dev/null
@@ -1,217 +0,0 @@
-const HOLDING_THRESHOLD_MS = 300;
-const SCROLL_X_DAMPENING = 0.02;
-const SCROLL_Y_DAMPENING = 0.09;
-const MOVE_X_MULTIPLIER = 1.64;
-const MOVE_Y_MULTIPLIER = 1.64;
-
-class TouchpadController {
- constructor(connection) {
- this.currentMoveX = 0;
- this.currentMoveY = 0;
- this.lastMoveX = 0;
- this.lastMoveY = 0;
- this.shouldResetLastMove = false;
- this.isInHoldingMode = false;
- this.ongoingTouches = {};
- this.holdModeTimeout = null;
- this.touchpadDiv = null;
-
- this.connection = connection;
- }
-
- getButtonCode(button) {
- let buttonCode = 0;
- if (button === "right") buttonCode = 1;
- return buttonCode;
- }
-
- // takes a gesture name and a touch list
- // adds the gesture name to the touch and returns false if that touch already had that gesture
- _gesture(gestureName, touchList) {
- for (let i = 0; i < touchList.length; i++) {
- const touch = this.ongoingTouches[touchList[i].identifier];
- if (touch.gestured.includes(gestureName)) return false;
- touch.gestured.push(gestureName);
- }
- return true;
- }
-
- _sendRelativeMouseMovement(dx, dy) {
- if (dx === 0 && dy === 0)
- return false;
- this.connection.sendMessage("r", [dx, dy]);
- return true;
- }
-
- _sendRelativeMouseScroll(dx, dy) {
- if (dx === 0 && dy === 0)
- return false;
- this.connection.sendMessage("s", [dx, dy]);
- return true;
- }
-
- _sendMouseButtonDown(button="left") {
- this.connection.sendMessage("d", [this.getButtonCode(button)]);
- }
-
- _sendMouseButtonUp(button="left") {
- this.connection.sendMessage("u", [this.getButtonCode(button)]);
- }
-
- _sendSingleClick(button="left") {
- this.connection.sendMessage("c", [this.getButtonCode(button)]);
- }
-
- mountOn(element) {
- if (this.touchpadDiv)
- return; // Already mounted
-
- this.touchpadDiv = document.createElement("div");
- this.touchpadDiv.classList.add("Touchpad");
-
- this.touchpadDiv.addEventListener("touchmove", this.onTouchMove.bind(this));
- this.touchpadDiv.addEventListener("touchend", this.onTouchEnd.bind(this));
- this.touchpadDiv.addEventListener("touchstart", this.onTouchStart.bind(this));
-
- element.appendChild(this.touchpadDiv);
- }
-
- unmount() {
- if (!this.touchpadDiv)
- return; // Not mounted - don't do anything
-
- this.touchpadDiv.removeEventListener("touchmove", this.onTouchMove.bind(this));
- this.touchpadDiv.removeEventListener("touchend", this.onTouchEnd.bind(this));
- this.touchpadDiv.removeEventListener("touchstart", this.onTouchStart.bind(this));
- }
-
- onTouchMove(event) {
- const touches = event.changedTouches;
-
- const targetTouch = touches[0];
-
- this.currentMoveX = targetTouch.pageX;
- this.currentMoveY = targetTouch.pageY;
- // When ending a touch and starting a new one in another part of the touchpad,
- // the cursor "rubber bands" to that position.
- // To solve this, the "last move" parameters are reset when a touch is ended
- // (see onTouchEnd())
- if (this.shouldResetLastMove) {
- this.shouldResetLastMove = false;
- this.lastMoveX = this.currentMoveX;
- this.lastMoveY = this.currentMoveY;
- }
- const deltaX = this.currentMoveX - this.lastMoveX;
- const deltaY = this.currentMoveY - this.lastMoveY;
- this.lastMoveX = this.currentMoveX;
- this.lastMoveY = this.currentMoveY;
- // if two touches moved at the same time, assume scrolling intent
-
- // if _sendRelativeMouseScroll or _sendRelativeMouseMovement return true, it means that the delta values are non-zero and a packet has been sent to the server
- // in that case, the touches will be marked as moved
- let shouldMarkTouchesAsMoved = false;
- if (touches.length === 2) {
- if (
- this._sendRelativeMouseScroll(deltaX * SCROLL_X_DAMPENING, deltaY * SCROLL_Y_DAMPENING)
- ) {
- shouldMarkTouchesAsMoved = true;
- }
- } else if (touches.length === 3 && this._gesture("GESTURE_RIGHT_CLICK", touches)) {
- shouldMarkTouchesAsMoved = true;
- this._sendSingleClick("right");
- } else {
- if (
- this._sendRelativeMouseMovement(deltaX * MOVE_X_MULTIPLIER, deltaY * MOVE_Y_MULTIPLIER)
- ) {
- shouldMarkTouchesAsMoved = true;
- }
- }
- if (shouldMarkTouchesAsMoved) {
- for (let i = 0; i < touches.length; i++) {
- const touch = this.ongoingTouches[touches[i].identifier];
- touch.hasMoved = true;
-
- if (touch.indicatorElement) {
- touch.indicatorElement.style.top = `${targetTouch.pageY}px`;
- touch.indicatorElement.style.left = `${targetTouch.pageX}px`;
- }
- }
- }
- }
-
- onTouchEnd(event) {
- const changedTouches = event.changedTouches;
- this.shouldResetLastMove = true;
-
- if (changedTouches.length === 1) {
- // This is a single tap - left click
- if (!this.ongoingTouches[changedTouches[0].identifier].hasMoved) {
- this._sendSingleClick("left");
-
- // We were in "holding mode" and now that touch event has ended,
- // thus we have to stop holding the left click button
- } else if (this.isInHoldingMode) {
- this._sendMouseButtonUp("left");
- this.isInHoldingMode = false;
- }
- }
-
- // remove all ended touches
- for (let i = 0; i < changedTouches.length; i++) {
- const touch = changedTouches[i];
- const knownTouch = this.ongoingTouches[changedTouches[0].identifier];
-
- if (knownTouch && knownTouch.indicatorElement) {
- this.touchpadDiv.removeChild(knownTouch.indicatorElement);
- knownTouch.indicatorElement = null;
- delete knownTouch.indicatorElement;
- }
-
- this.ongoingTouches[touch.identifier] = null;
- delete this.ongoingTouches[touch.identifier];
- }
- }
-
- onTouchStart(event) {
- const changedTouches = event.changedTouches;
-
- // Clear the hold mode time out if another touch begins
- if (this.holdModeTimeout)
- clearTimeout(this.holdModeTimeout);
-
- // If the touch is still unmoved and held for a certain amount of time,
- // we will enter "holding mode" which keeps the mouse button held while dragging
- // allowing you to, for example, select text or drag a scrollbar
- if (changedTouches.length === 1) {
- const targetTouch = changedTouches[0];
- this.holdModeTimeout = setTimeout(() => {
- if (this.ongoingTouches[targetTouch.identifier] && !this.ongoingTouches[targetTouch.identifier].hasMoved) {
- this.isInHoldingMode = true;
- this._sendMouseButtonDown("left");
- }
- }, HOLDING_THRESHOLD_MS);
- }
-
- for (let i = 0; i < changedTouches.length; i++) {
- const touch = changedTouches[i];
- let indicatorElement = this.ongoingTouches[touch.identifier] ? this.ongoingTouches[touch.identifier].indicatorElement : null;
- if (!indicatorElement) {
- indicatorElement = document.createElement("div");
- indicatorElement.classList.add("Touchpad-touch-indicator");
- indicatorElement.style.top = `${touch.clientY}px`;
- indicatorElement.style.left = `${touch.clientX}px`;
- this.touchpadDiv.appendChild(indicatorElement);
- }
- this.ongoingTouches[touch.identifier] = {
- identifier: touch.identifier,
- clientX: touch.clientX,
- clientY: touch.clientY,
- hasMoved: false,
- gestured: [],
- indicatorElement
- };
- }
- }
-}
-
-export default TouchpadController;
\ No newline at end of file
diff --git a/frontend/app.html b/frontend/app.html
deleted file mode 100644
index 3eaae33..0000000
--- a/frontend/app.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
- Capybara
-
-
-
-
-
-
-
Loading...
-
-
-
\ No newline at end of file
diff --git a/frontend/connection.js b/frontend/connection.js
deleted file mode 100644
index 68cd394..0000000
--- a/frontend/connection.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import logger from "./logger.js";
-import { getItem } from "./storage.js";
-
-export const ConnectionEvent = {
- Open: 0,
- Closed: 1,
- Ready: 2,
-};
-
-export default {
- ws: null,
- log: logger(["Connection"], ["log"]).log,
- messageLog: logger(["Connection", "Message"], ["log"]).log,
- url: getItem("server:gatewayBase"),
- isReady: false,
- reconnectTimeout: 0,
- handlers: new Map(),
-
- formatAuthString(password) {
- return `%auth%${btoa(password)}`;
- },
-
- connect(password=getItem("auth:token")) {
- this.ws = new WebSocket(this.url);
- this.ws.onerror = (e) => this.log("Error", e);
- this.ws.onopen = () => {
- this.log("Open");
- this.dispatch(ConnectionEvent.Open, 1);
- this.reconnectTimeout = 0;
- this.log("Sending authentication packet");
- this.ws.send(`0${this.formatAuthString(password)}`); // send auth packet
- };
- this.ws.onmessage = ({ data }) => {
- if (data === "1") {
- this.isReady = true;
- this.dispatch(ConnectionEvent.Ready, 1);
- this.log("Handshake complete");
- }
- };
- this.ws.onclose = ({ code }) => {
- this.dispatch(ConnectionEvent.Closed, code);
-
- if (code === 4001) {// code for bad auth
- this.log("Closed due to bad auth - skipping reconnect");
- return;
- }
- this.isReady = false;
- this.reconnectTimeout += 400;
- if (this.reconnectTimeout >= 30000)
- this.reconnectTimeout = 30000;
- this.log(`Closed - will reconnect in ${this.reconnectTimeout}ms`);
- setTimeout(() => this.connect(password), this.reconnectTimeout);
- }
- },
-
- sendMessage(code, params=[]) {
- let message = code;
- for (let i = 0; i < params.length; i++) {
- const param = params[i];
- if (i == params.length - 1)
- message += param;
- else
- message += param + ";";
- }
-
- this.ws.send(message);
- },
-
- disconnect() {
- this.ws.close();
- },
-
- dispatch(event, data) {
- const eventHandlers = this.handlers.get(event);
- if (!eventHandlers)
- return;
-
- eventHandlers.forEach(e => e(data));
- },
-
- subscribe(event, handler) {
- if (!this.handlers.get(event)) {
- this.handlers.set(event, new Set());
- }
-
- this.handlers.get(event).add(handler);
- return handler; // can later be used for unsubscribe()
- },
-
- unsubscribe(event, handler) {
- const eventHandlers = this.handlers.get(event);
- if (!eventHandlers)
- return;
-
- eventHandlers.delete(handler);
-
- if (eventHandlers.size < 1) {
- this.handlers.delete(event);
- }
- },
-};
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..101078a
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ Vite + Svelte + TS
+
+
+
+
+
+
+
diff --git a/frontend/logger.js b/frontend/logger.js
deleted file mode 100644
index 43682dd..0000000
--- a/frontend/logger.js
+++ /dev/null
@@ -1,53 +0,0 @@
-const loggerOfType = (components, type='log') => (...args) => {
- let str = '%c';
- const style = 'color: #5e81ac; font-weight: bold;';
- for (const i in components) {
- const v = components[i];
- if (components[i+1] === undefined) {
- str += `[${v}]`;
- } else {
- str += `[${v}] `;
- }
- }
- switch (type) {
- case 'log': {
- console.log(str, style, ...args);
- break;
- }
-
- case 'error': {
- console.error(str, style, ...args);
- break;
- }
-
- case 'warn': {
- console.warn(str, style, ...args);
- break;
- }
-
- case 'genmsg': {
- return str;
- }
-
- default: {
- return str;
- }
- }
-};
-
-function logger(components, types=['warn', 'error', 'log']) {
- const loggerObj = {};
-
- for (const type of types) {
- loggerObj[type] = loggerOfType(components, type);
- }
-
- return loggerObj;
-}
-
-
-export function domLog(message) {
- document.body.appendChild(document.createTextNode(message));
-}
-
-export default logger;
\ No newline at end of file
diff --git a/frontend/main.css b/frontend/main.css
deleted file mode 100644
index 00b5ea4..0000000
--- a/frontend/main.css
+++ /dev/null
@@ -1,167 +0,0 @@
-:root {
- --body-bg-color: #2e2e2e;
- --body-bg-color-accent: hsl(0, 0%, 25%);
- --accent-bg-color: #d4d3d3;
- --accent-color: #949494;
- --grayed-text-color: #949494;
- --button-accent-color: #3d3d3d;
- --selected-bg-color: #2e2e2e;
- --hover-bg-color: #4d4d4d;
- --card-border-radius: 1em;
- --button-border-radius: 0.5em;
- --main-font-weight: 500;
-
- --fonts-regular: "Noto Sans", "Liberation Sans", sans-serif;
-}
-
-* {
- box-sizing: border-box;
-}
-
-html, body {
- font-weight: var(--main-font-weight);
- font-family: var(--fonts-regular);
- background-color: var(--body-bg-color);
- color: var(--accent-bg-color);
- overflow: hidden;
- margin: 0;
- padding: 0;
- height: 100%;
- width: 100%;
-}
-
-body {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: flex-end;
- background: var(--body-bg-color);
- background-image: radial-gradient(var(--body-bg-color-accent) 1.4px, transparent 0);
- background-size: 35px 35px;
-}
-
-/* util */
-
-.full-width {
- width: 100%;
-}
-
-/* Card */
-
-.Keyboard,
-.Card {
- margin: 6px;
- padding: 8px;
- background: var(--accent-bg-color);
- color: #000000;
- border-radius: var(--card-border-radius);
-}
-
-.Keyboard,
-.Card-ui-bottom {
- padding: 14px;
- width: 100%;
- max-width: 610px;
- box-shadow: 0 0 28px 3px rgba(0, 0, 0, 0.40);
- margin: 0;
- border-radius: var(--card-border-radius) var(--card-border-radius) 0 0;
- padding-top: 16px;
-}
-
-.Card-centered-text {
- text-align: center;
-}
-
-/* button */
-
-.button-default {
- display: block;
- padding: 12px;
- margin: 4px;
- border: none;
- background-color: var(--accent-bg-color);
- border-radius: var(--button-border-radius);
- color: var(--button-accent-color);
- min-width: 50px;
- text-align: center;
-}
-
-.button-default:hover:not(.button-selected) {
- color: var(--accent-bg-color);
- background-color: var(--hover-bg-color);
-}
-
-.button-selected {
- color: var(--accent-bg-color);
- background-color: var(--selected-bg-color);
-}
-
-/* input */
-
-.input {
- display: block;
- box-sizing: border-box;
- border: none;
- outline: none;
- border-radius: var(--button-border-radius);
- margin: 4px;
- padding: 12px;
-}
-
-/* LoginPrompt */
-
-.LoginPrompt {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
-}
-
-/* Touchpad */
-
-.Touchpad {
- width: 100%;
- height: 100%;
-}
-
-.Touchpad-touch-indicator {
- pointer-events: none;
- position: absolute;
- width: 28px;
- height: 28px;
- border-radius: 50%;
- background-color: var(--accent-bg-color);
-}
-
-/* Keyboard */
-
-.keyboard-rows {
- border-radius: 12px;
- display: flex;
- flex-direction: column;
-}
-
-.Keyboard-row {
- display: flex;
- flex-direction: row;
- flex-grow: 1;
-}
-
-.Keyboard-button {
- font-family: var(--fonts-regular);
- font-weight: var(--main-font-weight);
- padding: 8px;
- background-color: var(--accent-bg-color);
- color: #000000;
- flex-grow: 1;
- min-height: 50px;
- width: 20px;
- border: none;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.Keyboard-button:active {
- background-color: var(--accent-color);
-}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..b7bf21f
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,1756 @@
+{
+ "name": "capybara-frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "capybara-frontend",
+ "version": "0.0.0",
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^3.1.1",
+ "@tsconfig/svelte": "^5.0.4",
+ "svelte": "^4.2.18",
+ "svelte-check": "^3.8.5",
+ "tslib": "^2.6.3",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.1"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
+ "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz",
+ "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
+ "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz",
+ "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz",
+ "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz",
+ "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz",
+ "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz",
+ "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz",
+ "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz",
+ "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz",
+ "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz",
+ "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz",
+ "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz",
+ "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz",
+ "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz",
+ "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.1.tgz",
+ "integrity": "sha512-rimpFEAboBBHIlzISibg94iP09k/KYdHgVhJlcsTfn7KMBhc70jFX/GRWkRdFCc2fdnk+4+Bdfej23cMDnJS6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
+ "debug": "^4.3.4",
+ "deepmerge": "^4.3.1",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.10",
+ "svelte-hmr": "^0.16.0",
+ "vitefu": "^0.2.5"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20"
+ },
+ "peerDependencies": {
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
+ "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@tsconfig/svelte": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.4.tgz",
+ "integrity": "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/pug": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
+ "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.12.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
+ "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
+ "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/code-red": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
+ "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.10.0",
+ "estree-walker": "^3.0.3",
+ "periscopic": "^3.1.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/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+ "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-indent": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
+ "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/es6-promise": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+ "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=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/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "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-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "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/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==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-reference": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
+ "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/locate-character": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.11",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
+ "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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/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",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "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/periscopic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
+ "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^3.0.0",
+ "is-reference": "^3.0.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.41",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
+ "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.1",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz",
+ "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.20.0",
+ "@rollup/rollup-android-arm64": "4.20.0",
+ "@rollup/rollup-darwin-arm64": "4.20.0",
+ "@rollup/rollup-darwin-x64": "4.20.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.20.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.20.0",
+ "@rollup/rollup-linux-arm64-musl": "4.20.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.20.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.20.0",
+ "@rollup/rollup-linux-x64-gnu": "4.20.0",
+ "@rollup/rollup-linux-x64-musl": "4.20.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.20.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.20.0",
+ "@rollup/rollup-win32-x64-msvc": "4.20.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/sander": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
+ "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es6-promise": "^3.1.2",
+ "graceful-fs": "^4.1.3",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.2"
+ }
+ },
+ "node_modules/sorcery": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.1.tgz",
+ "integrity": "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.14",
+ "buffer-crc32": "^1.0.0",
+ "minimist": "^1.2.0",
+ "sander": "^0.5.0"
+ },
+ "bin": {
+ "sorcery": "bin/sorcery"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/svelte": {
+ "version": "4.2.18",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz",
+ "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.9.0",
+ "aria-query": "^5.3.0",
+ "axobject-query": "^4.0.0",
+ "code-red": "^1.0.3",
+ "css-tree": "^2.3.1",
+ "estree-walker": "^3.0.3",
+ "is-reference": "^3.0.1",
+ "locate-character": "^3.0.0",
+ "magic-string": "^0.30.4",
+ "periscopic": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/svelte-check": {
+ "version": "3.8.5",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.5.tgz",
+ "integrity": "sha512-3OGGgr9+bJ/+1nbPgsvulkLC48xBsqsgtc8Wam281H4G9F5v3mYGa2bHRsPuwHC5brKl4AxJH95QF73kmfihGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "chokidar": "^3.4.1",
+ "picocolors": "^1.0.0",
+ "sade": "^1.7.4",
+ "svelte-preprocess": "^5.1.3",
+ "typescript": "^5.0.3"
+ },
+ "bin": {
+ "svelte-check": "bin/svelte-check"
+ },
+ "peerDependencies": {
+ "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
+ }
+ },
+ "node_modules/svelte-hmr": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
+ "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^12.20 || ^14.13.1 || >= 16"
+ },
+ "peerDependencies": {
+ "svelte": "^3.19.0 || ^4.0.0"
+ }
+ },
+ "node_modules/svelte-preprocess": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz",
+ "integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/pug": "^2.0.6",
+ "detect-indent": "^6.1.0",
+ "magic-string": "^0.30.5",
+ "sorcery": "^0.11.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.10.2",
+ "coffeescript": "^2.5.1",
+ "less": "^3.11.3 || ^4.0.0",
+ "postcss": "^7 || ^8",
+ "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
+ "pug": "^3.0.0",
+ "sass": "^1.26.8",
+ "stylus": "^0.55.0",
+ "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
+ "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
+ "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "coffeescript": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "postcss-load-config": {
+ "optional": true
+ },
+ "pug": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
+ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz",
+ "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.41",
+ "rollup": "^4.13.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
+ "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..b2acee6
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "capybara-frontend",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^3.1.1",
+ "@tsconfig/svelte": "^5.0.4",
+ "svelte": "^4.2.18",
+ "svelte-check": "^3.8.5",
+ "tslib": "^2.6.3",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.1"
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app.css b/frontend/src/app.css
new file mode 100644
index 0000000..bdd6c75
--- /dev/null
+++ b/frontend/src/app.css
@@ -0,0 +1,137 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ --space-unit: 1em;
+ --space-xxs: calc(0.25 * var(--space-unit));
+ --space-xs: calc(0.5 * var(--space-unit));
+ --space-xsplus: calc(0.65 * var(--space-unit));
+ --space-sm: calc(0.75 * var(--space-unit));
+ --space-smplus: calc(0.8 * var(--space-unit));
+ --space-norm: var(--space-unit);
+ --space-normplus: calc(var(--space-unit) + var(--space-sm));
+ --space-md: calc(1.25 * var(--space-unit));
+ --space-lg: calc(2 * var(--space-unit));
+ --space-xl: calc(3.25 * var(--space-unit));
+ --space-xxl: calc(5.25 * var(--space-unit));
+
+ --radius-unit: 0.5em;
+ --radius-xxs: calc(0.25 * var(--radius-unit));
+ --radius-xs: calc(0.5 * var(--radius-unit));
+ --radius-xsplus: calc(0.65 * var(--radius-unit));
+ --radius-sm: calc(0.75 * var(--radius-unit));
+ --radius-norm: var(--radius-unit);
+ --radius-md: calc(1.25 * var(--radius-unit));
+ --radius-mdplus: calc(1.5 * var(--radius-unit));
+ --radius-lg: calc(2 * var(--radius-unit));
+ --radius-xl: calc(3.25 * var(--radius-unit));
+ --radius-xxl: calc(5.25 * var(--sradius-unit));
+
+ --h1: 1.802rem;
+ --h2: 1.602rem;
+ --h3: 1.424rem;
+ --h4: 1.266rem;
+ --h5: 1.125rem;
+ --h6: 0.889rem;
+ --p: 1rem;
+
+ --viewportWidth: 100vw;
+ --viewportHeight: 100vh;
+
+
+ color-scheme: dark;
+
+ --background-color-0: hsl(0, 0%, 6%);
+ --background-color-1: hsl(0, 0%, 10%);
+ --background-color-2: hsl(0, 0%, 13%);
+ --background-color-3: hsl(0, 0%, 15%);
+ --foreground-color-1: hsl(210, 100%, 100%);
+ --foreground-color-2: hsl(63, 10%, 82%);
+ --foreground-color-3: hsl(63, 2%, 60%);
+ --foreground-color-4: hsl(63, 2%, 49%);
+
+ --purple-1: hsl(266, 63%, 64%);
+ --blue-1: hsl(200, 78%, 50%);
+ --green-1: hsl(140, 78%, 50%);
+ --yellow-1: hsl(50, 78%, 50%);
+ --red-1: hsl(2, 78%, 65%);
+
+ --purple-2: hsl(266, 62%, 58%);
+ --purple-2-highlight: hsla(266, 62%, 58%, 0.1);
+ --blue-2: hsl(200, 78%, 45%);
+ --green-2: hsl(140, 78%, 40%);
+ --yellow-2: hsl(50, 78%, 60%);
+ --red-2: hsl(2, 78%, 60%);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ overflow: hidden;
+ font-size: 100%;
+ line-height: 26px;
+ width: var(--viewportWidth);
+ height: var(--viewportHeight);
+ background-color: var(--background-color-0);
+ color: var(--foreground-color-1);
+
+ display: flex;
+ flex-direction: column;
+ padding: var(--space-sm);
+ gap: var(--space-sm);
+}
+
+
+/* buttons */
+.button {
+ color: var(--foreground-color-1);
+ background: none;
+ text-align: center;
+ border: none;
+ padding: 0.85em;
+ padding-top: 0.65em;
+ padding-bottom: 0.65em;
+ border-radius: 9999px;
+ font: inherit;
+ user-select: none;
+ font-weight: 600;
+}
+
+.button:hover {
+ background-color: var(--background-color-2);
+}
+
+.button-accent {
+ color: var(--colored-element-text-color);
+ background-color: var(--purple-1);
+}
+
+.button-accent:hover {
+ background-color: var(--purple-2);
+}
+
+.button-accent:disabled {
+ background-color: var(--purple-1);
+}
+
+.button-danger {
+ color: var(--red-2);
+}
+
+.button-danger:hover {
+ color: var(--red-1);
+}
diff --git a/frontend/src/components/App.svelte b/frontend/src/components/App.svelte
new file mode 100644
index 0000000..5a804a8
--- /dev/null
+++ b/frontend/src/components/App.svelte
@@ -0,0 +1,33 @@
+
+
+
+{#if !$gatewayReady}
+ Connecting...
+{/if}
+
+
+{#if showKeyboard}
+
+{/if}
+
diff --git a/frontend/src/components/ChipBar.d.ts b/frontend/src/components/ChipBar.d.ts
new file mode 100644
index 0000000..4527d09
--- /dev/null
+++ b/frontend/src/components/ChipBar.d.ts
@@ -0,0 +1,8 @@
+declare interface ChipOption {
+ id: string;
+ hidden?: boolean;
+ selected?: boolean;
+ icon?: string;
+ text?: string;
+ handle?: (event: Event) => void;
+};
diff --git a/frontend/src/components/ChipBar.svelte b/frontend/src/components/ChipBar.svelte
new file mode 100644
index 0000000..966dc3f
--- /dev/null
+++ b/frontend/src/components/ChipBar.svelte
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+ {#each options as option (option.id)}
+ {#if !option.hidden}
+
+ {/if}
+ {/each}
+
diff --git a/frontend/src/components/Keyboard.svelte b/frontend/src/components/Keyboard.svelte
new file mode 100644
index 0000000..f3d924a
--- /dev/null
+++ b/frontend/src/components/Keyboard.svelte
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+ {#each currentLayout as row}
+
+ {#each row as key}
+
+ {/each}
+
+ {/each}
+
diff --git a/frontend/src/components/Toast.svelte b/frontend/src/components/Toast.svelte
new file mode 100644
index 0000000..c6444ed
--- /dev/null
+++ b/frontend/src/components/Toast.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
diff --git a/frontend/src/components/TouchArea.svelte b/frontend/src/components/TouchArea.svelte
new file mode 100644
index 0000000..ec0fcc6
--- /dev/null
+++ b/frontend/src/components/TouchArea.svelte
@@ -0,0 +1,153 @@
+
+
+
+
+
diff --git a/frontend/src/gateway.ts b/frontend/src/gateway.ts
new file mode 100644
index 0000000..faa94e4
--- /dev/null
+++ b/frontend/src/gateway.ts
@@ -0,0 +1,196 @@
+import { get, writable } from "svelte/store";
+
+export enum FrameType {
+ None = 0,
+
+ // Client to server
+ Authenticate = 1,
+ // Server to client
+ Ready,
+
+ // Client to server
+ PointerMotion = 10,
+ PointerAxis,
+ PointerAxisFinal,
+ PointerButtonPressed,
+ PointerButtonReleased,
+ PointerButtonTapped,
+ KeyboardButtonPressed,
+ KeyboardButtonReleased,
+ KeyboardButtonTapped
+};
+
+let ws: null | WebSocket = null;
+const sizeToDataView = new Map();
+let reconnectTimeout: number | null = null;
+const RECONNECT_TIME_MS = 800;
+
+export let gatewayReady = writable(false);
+
+
+function isConnected(): boolean {
+ return ws !== null && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING;
+}
+
+function isReady() {
+ return isConnected() && get(gatewayReady);
+}
+
+function wantsConnection(): boolean {
+ return document.visibilityState === "visible";
+}
+
+function clearReconnect() {
+ if (reconnectTimeout !== null) {
+ clearTimeout(reconnectTimeout);
+ reconnectTimeout = null;
+ }
+}
+
+function getDataView(size: number): DataView {
+ const existing = sizeToDataView.get(size);
+ if (existing) return existing;
+
+ const buffer = new ArrayBuffer(size);
+ const view = new DataView(buffer);
+ sizeToDataView.set(size, view);
+
+ return view;
+}
+
+function createFrame(type: FrameType, arg1: number, arg2: number): ArrayBuffer {
+ let view;
+
+ switch (type) {
+ case FrameType.Authenticate: {
+ view = getDataView(1);
+ view.setUint8(0, FrameType.Authenticate);
+ break;
+ }
+ case FrameType.PointerMotion: {
+ view = getDataView(9);
+ view.setUint8(0, FrameType.PointerMotion);
+ view.setFloat32(1, arg1);
+ view.setFloat32(5, arg2);
+ break;
+ }
+ case FrameType.PointerAxis: {
+ view = getDataView(9);
+ view.setUint8(0, FrameType.PointerAxis);
+ view.setFloat32(1, arg1);
+ view.setFloat32(5, arg2);
+ break;
+ }
+ case FrameType.PointerAxisFinal: {
+ view = getDataView(9);
+ view.setUint8(0, FrameType.PointerAxisFinal);
+ view.setFloat32(1, arg1);
+ view.setFloat32(5, arg2);
+ break;
+ }
+ case FrameType.PointerButtonPressed: {
+ view = getDataView(5);
+ view.setUint8(0, FrameType.PointerButtonPressed);
+ view.setInt32(1, arg1);
+ break;
+ }
+ case FrameType.PointerButtonReleased: {
+ view = getDataView(5);
+ view.setUint8(0, FrameType.PointerButtonReleased);
+ view.setInt32(1, arg1);
+ break;
+ }
+ case FrameType.PointerButtonTapped: {
+ view = getDataView(5);
+ view.setUint8(0, FrameType.PointerButtonTapped);
+ view.setInt32(1, arg1);
+ break;
+ }
+ case FrameType.KeyboardButtonPressed: {
+ view = getDataView(5);
+ view.setUint8(0, FrameType.KeyboardButtonPressed);
+ view.setInt32(1, arg1);
+ break;
+ }
+ case FrameType.KeyboardButtonReleased: {
+ view = getDataView(5);
+ view.setUint8(0, FrameType.KeyboardButtonReleased);
+ view.setInt32(1, arg1);
+ break;
+ }
+ case FrameType.KeyboardButtonTapped: {
+ view = getDataView(5);
+ view.setUint8(0, FrameType.KeyboardButtonTapped);
+ view.setInt32(1, arg1);
+ break;
+ }
+ default: {
+ throw new Error(`bad FrameType: ${type}`);
+ }
+ }
+
+ return view.buffer;
+}
+
+export function sendFrame(type: FrameType, arg1: number = 0, arg2: number = 0) {
+ if (!isReady() || !ws) {
+ return;
+ }
+
+ ws.send(createFrame(type, arg1, arg2));
+}
+
+export function connect() {
+ clearReconnect();
+ ws = null;
+ gatewayReady.set(false);
+
+ if (!wantsConnection()) return;
+
+ ws = new WebSocket(`ws://${location.host}/gateway`);
+ ws.binaryType = "arraybuffer";
+ ws.addEventListener("open", () => {
+ clearReconnect();
+
+ if (!ws) return;
+
+ ws.send(createFrame(FrameType.Authenticate, 0, 0));
+ });
+ ws.addEventListener("message", (event: MessageEvent) => {
+ if (event.data && typeof event.data === "object" && event.data instanceof ArrayBuffer && event.data.byteLength) {
+ const view = new DataView(event.data);
+ const frameType = view.getUint8(0) as FrameType;
+ switch (frameType) {
+ case FrameType.Ready: {
+ gatewayReady.set(true);
+ break;
+ }
+ default: {
+ console.error(`got bad FrameType from server: ${frameType}`);
+ }
+ }
+ }
+ });
+ ws.addEventListener("close", () => {
+ clearReconnect();
+ ws = null;
+ gatewayReady.set(false);
+
+ if (!wantsConnection()) return;
+
+ reconnectTimeout = setTimeout(() => {
+ reconnectTimeout = null;
+ connect();
+ }, RECONNECT_TIME_MS);
+ });
+}
+
+export function initVisibilityHandlers() {
+ document.addEventListener("visibilitychange", () => {
+ if (!isConnected() && wantsConnection()) {
+ queueMicrotask(connect);
+ } else if (ws && isConnected() && !wantsConnection()) {
+ ws.close();
+ }
+ });
+}
diff --git a/frontend/src/keyboard.ts b/frontend/src/keyboard.ts
new file mode 100644
index 0000000..38f1642
--- /dev/null
+++ b/frontend/src/keyboard.ts
@@ -0,0 +1,237 @@
+
+// XKB Keysyms. Taken from https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h
+
+const KEY_BackSpace = 0xff08; /* U+0008 BACKSPACE */
+const KEY_Return = 0xff0d; /* U+000D CARRIAGE RETURN */
+
+// Exported because they need special handling to change the keyboard layout.
+export const KEY_Num_Lock = 0xff7f;
+export const KEY_Caps_Lock = 0xffe5; /* Caps lock */
+
+const KEY_space = 0x0020; /* U+0020 SPACE */
+const KEY_exclam = 0x0021; /* U+0021 EXCLAMATION MARK */
+const KEY_quotedbl = 0x0022; /* U+0022 QUOTATION MARK */
+const KEY_numbersign = 0x0023; /* U+0023 NUMBER SIGN */
+const KEY_dollar = 0x0024; /* U+0024 DOLLAR SIGN */
+const KEY_percent = 0x0025; /* U+0025 PERCENT SIGN */
+const KEY_ampersand = 0x0026; /* U+0026 AMPERSAND */
+const KEY_apostrophe = 0x0027; /* U+0027 APOSTROPHE */
+const KEY_parenleft = 0x0028; /* U+0028 LEFT PARENTHESIS */
+const KEY_parenright = 0x0029; /* U+0029 RIGHT PARENTHESIS */
+const KEY_plus = 0x002b; /* U+002B PLUS SIGN */
+const KEY_comma = 0x002c; /* U+002C COMMA */
+const KEY_minus = 0x002d; /* U+002D HYPHEN-MINUS */
+const KEY_period = 0x002e; /* U+002E FULL STOP */
+const KEY_slash = 0x002f; /* U+002F SOLIDUS */
+const KEY_0 = 0x0030; /* U+0030 DIGIT ZERO */
+const KEY_1 = 0x0031; /* U+0031 DIGIT ONE */
+const KEY_2 = 0x0032; /* U+0032 DIGIT TWO */
+const KEY_3 = 0x0033; /* U+0033 DIGIT THREE */
+const KEY_4 = 0x0034; /* U+0034 DIGIT FOUR */
+const KEY_5 = 0x0035; /* U+0035 DIGIT FIVE */
+const KEY_6 = 0x0036; /* U+0036 DIGIT SIX */
+const KEY_7 = 0x0037; /* U+0037 DIGIT SEVEN */
+const KEY_8 = 0x0038; /* U+0038 DIGIT EIGHT */
+const KEY_9 = 0x0039; /* U+0039 DIGIT NINE */
+const KEY_colon = 0x003a; /* U+003A COLON */
+const KEY_semicolon = 0x003b; /* U+003B SEMICOLON */
+const KEY_less = 0x003c; /* U+003C LESS-THAN SIGN */
+const KEY_equal = 0x003d; /* U+003D EQUALS SIGN */
+const KEY_greater = 0x003e; /* U+003E GREATER-THAN SIGN */
+const KEY_question = 0x003f; /* U+003F QUESTION MARK */
+const KEY_at = 0x0040; /* U+0040 COMMERCIAL AT */
+const KEY_A = 0x0041; /* U+0041 LATIN CAPITAL LETTER A */
+const KEY_B = 0x0042; /* U+0042 LATIN CAPITAL LETTER B */
+const KEY_C = 0x0043; /* U+0043 LATIN CAPITAL LETTER C */
+const KEY_D = 0x0044; /* U+0044 LATIN CAPITAL LETTER D */
+const KEY_E = 0x0045; /* U+0045 LATIN CAPITAL LETTER E */
+const KEY_F = 0x0046; /* U+0046 LATIN CAPITAL LETTER F */
+const KEY_G = 0x0047; /* U+0047 LATIN CAPITAL LETTER G */
+const KEY_H = 0x0048; /* U+0048 LATIN CAPITAL LETTER H */
+const KEY_I = 0x0049; /* U+0049 LATIN CAPITAL LETTER I */
+const KEY_J = 0x004a; /* U+004A LATIN CAPITAL LETTER J */
+const KEY_K = 0x004b; /* U+004B LATIN CAPITAL LETTER K */
+const KEY_L = 0x004c; /* U+004C LATIN CAPITAL LETTER L */
+const KEY_M = 0x004d; /* U+004D LATIN CAPITAL LETTER M */
+const KEY_N = 0x004e; /* U+004E LATIN CAPITAL LETTER N */
+const KEY_O = 0x004f; /* U+004F LATIN CAPITAL LETTER O */
+const KEY_P = 0x0050; /* U+0050 LATIN CAPITAL LETTER P */
+const KEY_Q = 0x0051; /* U+0051 LATIN CAPITAL LETTER Q */
+const KEY_R = 0x0052; /* U+0052 LATIN CAPITAL LETTER R */
+const KEY_S = 0x0053; /* U+0053 LATIN CAPITAL LETTER S */
+const KEY_T = 0x0054; /* U+0054 LATIN CAPITAL LETTER T */
+const KEY_U = 0x0055; /* U+0055 LATIN CAPITAL LETTER U */
+const KEY_V = 0x0056; /* U+0056 LATIN CAPITAL LETTER V */
+const KEY_W = 0x0057; /* U+0057 LATIN CAPITAL LETTER W */
+const KEY_X = 0x0058; /* U+0058 LATIN CAPITAL LETTER X */
+const KEY_Y = 0x0059; /* U+0059 LATIN CAPITAL LETTER Y */
+const KEY_Z = 0x005a; /* U+005A LATIN CAPITAL LETTER Z */
+const KEY_bracketleft = 0x005b; /* U+005B LEFT SQUARE BRACKET */
+const KEY_backslash = 0x005c; /* U+005C REVERSE SOLIDUS */
+const KEY_bracketright = 0x005d; /* U+005D RIGHT SQUARE BRACKET */
+const KEY_asciicircum = 0x005e; /* U+005E CIRCUMFLEX ACCENT */
+const KEY_underscore = 0x005f; /* U+005F LOW LINE */
+const KEY_grave = 0x0060; /* U+0060 GRAVE ACCENT */
+const KEY_quoteleft = 0x0060; /* deprecated */
+const KEY_a = 0x0061; /* U+0061 LATIN SMALL LETTER A */
+const KEY_b = 0x0062; /* U+0062 LATIN SMALL LETTER B */
+const KEY_c = 0x0063; /* U+0063 LATIN SMALL LETTER C */
+const KEY_d = 0x0064; /* U+0064 LATIN SMALL LETTER D */
+const KEY_e = 0x0065; /* U+0065 LATIN SMALL LETTER E */
+const KEY_f = 0x0066; /* U+0066 LATIN SMALL LETTER F */
+const KEY_g = 0x0067; /* U+0067 LATIN SMALL LETTER G */
+const KEY_h = 0x0068; /* U+0068 LATIN SMALL LETTER H */
+const KEY_i = 0x0069; /* U+0069 LATIN SMALL LETTER I */
+const KEY_j = 0x006a; /* U+006A LATIN SMALL LETTER J */
+const KEY_k = 0x006b; /* U+006B LATIN SMALL LETTER K */
+const KEY_l = 0x006c; /* U+006C LATIN SMALL LETTER L */
+const KEY_m = 0x006d; /* U+006D LATIN SMALL LETTER M */
+const KEY_n = 0x006e; /* U+006E LATIN SMALL LETTER N */
+const KEY_o = 0x006f; /* U+006F LATIN SMALL LETTER O */
+const KEY_p = 0x0070; /* U+0070 LATIN SMALL LETTER P */
+const KEY_q = 0x0071; /* U+0071 LATIN SMALL LETTER Q */
+const KEY_r = 0x0072; /* U+0072 LATIN SMALL LETTER R */
+const KEY_s = 0x0073; /* U+0073 LATIN SMALL LETTER S */
+const KEY_t = 0x0074; /* U+0074 LATIN SMALL LETTER T */
+const KEY_u = 0x0075; /* U+0075 LATIN SMALL LETTER U */
+const KEY_v = 0x0076; /* U+0076 LATIN SMALL LETTER V */
+const KEY_w = 0x0077; /* U+0077 LATIN SMALL LETTER W */
+const KEY_x = 0x0078; /* U+0078 LATIN SMALL LETTER X */
+const KEY_y = 0x0079; /* U+0079 LATIN SMALL LETTER Y */
+const KEY_z = 0x007a; /* U+007A LATIN SMALL LETTER Z */
+const KEY_braceleft = 0x007b; /* U+007B LEFT CURLY BRACKET */
+const KEY_bar = 0x007c; /* U+007C VERTICAL LINE */
+const KEY_braceright = 0x007d; /* U+007D RIGHT CURLY BRACKET */
+const KEY_asciitilde = 0x007e; /* U+007E TILDE */
+const KEY_multiply = 0x00d7; /* U+00D7 MULTIPLICATION SIGN */
+
+
+
+
+export const keyboardLayouts = {
+ "default": [
+ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
+ ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
+ ["a", "s", "d", "f", "g", "h", "j", "k", "l"],
+ ["🄰", "z", "x", "c", "v", "b", "n", "m", ".", "⌦"],
+ ["🔢", " ", "↵"]
+ ],
+ "uppercase": [
+ ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"],
+ ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
+ ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
+ ["🅰", "Z", "X", "C", "V", "B", "N", "M", ",", "⌦"],
+ ["🔢", " ", "↵"]
+ ],
+ "symbols": [
+ ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"],
+ ["[", "]", "{", "}", ";", ":", "'", "\"", ",", "<"],
+ [".", ">", "-", "_", "=", "+", "/", "?", "\\"],
+ ["🄰", "|", "`", "~", "⌦"],
+ ["🔤", " ", "↵"]
+ ],
+};
+
+export const charToLinuxEventCode = new Map(Object.entries({
+ "1": KEY_1,
+ "2": KEY_2,
+ "3": KEY_3,
+ "4": KEY_4,
+ "5": KEY_5,
+ "6": KEY_6,
+ "7": KEY_7,
+ "8": KEY_8,
+ "9": KEY_9,
+ "0": KEY_0,
+ "q": KEY_q,
+ "w": KEY_w,
+ "e": KEY_e,
+ "r": KEY_r,
+ "t": KEY_t,
+ "y": KEY_y,
+ "u": KEY_u,
+ "i": KEY_i,
+ "o": KEY_o,
+ "p": KEY_p,
+ "a": KEY_a,
+ "s": KEY_s,
+ "d": KEY_d,
+ "f": KEY_f,
+ "g": KEY_g,
+ "h": KEY_h,
+ "j": KEY_j,
+ "k": KEY_k,
+ "l": KEY_l,
+ "🄰": KEY_Caps_Lock,
+ "z": KEY_z,
+ "x": KEY_x,
+ "c": KEY_c,
+ "v": KEY_v,
+ "b": KEY_b,
+ "n": KEY_n,
+ "m": KEY_m,
+ ".": KEY_period,
+ "⌦": KEY_BackSpace,
+ "🔢": KEY_Num_Lock,
+ "🔤": KEY_Num_Lock,
+ " ": KEY_space,
+ "↵": KEY_Return,
+ "!": KEY_exclam,
+ "@": KEY_at,
+ "#": KEY_numbersign,
+ "$": KEY_dollar,
+ "%": KEY_percent,
+ "^": KEY_asciicircum,
+ "&": KEY_ampersand,
+ "*": KEY_multiply,
+ "(": KEY_parenleft,
+ ")": KEY_parenright,
+ "Q": KEY_Q,
+ "W": KEY_W,
+ "E": KEY_E,
+ "R": KEY_R,
+ "T": KEY_T,
+ "Y": KEY_Y,
+ "U": KEY_U,
+ "I": KEY_I,
+ "O": KEY_O,
+ "P": KEY_P,
+ "A": KEY_A,
+ "S": KEY_S,
+ "D": KEY_D,
+ "F": KEY_F,
+ "G": KEY_G,
+ "H": KEY_H,
+ "J": KEY_J,
+ "K": KEY_K,
+ "L": KEY_L,
+ "🅰": KEY_Caps_Lock,
+ "Z": KEY_Z,
+ "X": KEY_X,
+ "C": KEY_C,
+ "V": KEY_V,
+ "B": KEY_B,
+ "N": KEY_N,
+ "M": KEY_M,
+ ",": KEY_comma,
+ "[": KEY_bracketright,
+ "]": KEY_bracketleft,
+ "{": KEY_braceright,
+ "}": KEY_braceleft,
+ ";": KEY_semicolon,
+ ":": KEY_colon,
+ "'": KEY_apostrophe,
+ "\"": KEY_quotedbl,
+ "<": KEY_less,
+ ">": KEY_greater,
+ "-": KEY_minus,
+ "_": KEY_underscore,
+ "=": KEY_equal,
+ "+": KEY_plus,
+ "/": KEY_slash,
+ "?": KEY_question,
+ "\\": KEY_backslash,
+ "|": KEY_bar,
+ "`": KEY_grave,
+ "~": KEY_asciitilde,
+}));
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
new file mode 100644
index 0000000..fce39b5
--- /dev/null
+++ b/frontend/src/main.ts
@@ -0,0 +1,16 @@
+import './app.css';
+import App from './components/App.svelte';
+import { connect, initVisibilityHandlers } from './gateway';
+import { initViewportExtents } from './responsive';
+
+
+initViewportExtents();
+connect();
+initVisibilityHandlers();
+
+const app = new App({
+ target: document.body
+});
+
+
+export default app;
diff --git a/frontend/src/responsive.ts b/frontend/src/responsive.ts
new file mode 100644
index 0000000..dc790eb
--- /dev/null
+++ b/frontend/src/responsive.ts
@@ -0,0 +1,15 @@
+export const initViewportExtents = () => {
+ const root = document.querySelector(':root');
+
+ if (CSS.supports("(width: 1dvw)") && root) {
+ root.style.setProperty("--viewportWidth", "100dvw");
+ root.style.setProperty("--viewportHeight", "100dvh");
+ } else if (root) {
+ const updateUnits = () => {
+ root.style.setProperty("--viewportWidth", `${window.innerWidth}px`);
+ root.style.setProperty("--viewportHeight", `${window.innerHeight}px`);
+ };
+ window.addEventListener("resize", updateUnits);
+ updateUnits();
+ }
+};
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..4078e74
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/frontend/storage.js b/frontend/storage.js
deleted file mode 100644
index 9bb86cf..0000000
--- a/frontend/storage.js
+++ /dev/null
@@ -1,47 +0,0 @@
-const defaults = {
- "server:gatewayBase": `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/gateway`,
- "auth:token": "",
-};
-const store = new Map(Object.entries(defaults));
-const persistentProvider = localStorage;
-let didCacheProvider = false;
-
-export function setItem(key, value) {
- store.set(key, value);
- if (persistentProvider) {
- persistentProvider.setItem(key, typeof value === "string" ? value : JSON.stringify(value));
- }
-}
-
-export function getItem(key) {
- if (!didCacheProvider) {
- init();
- }
- return store.get(key);
-}
-
-export function removeItem(key) {
- store.delete(key);
- if (persistentProvider) {
- persistentProvider.removeItem(key);
- }
-}
-
-export function init() {
- if (!persistentProvider)
- return;
-
- store.forEach((defaultValue, key) => {
- const override = persistentProvider.getItem(key);
- if (override !== null) {
- try {
- store.set(key, typeof defaultValue === "string" ? override : JSON.parse(override));
- } catch (o_O) {
- console.warn("[Storage]", `init(): An exception was thrown while parsing the value of key "${key}" from persistentProvider. The key "${key}" will be removed from persistentProvider.`, o_O);
- persistentProvider.removeItem(key);
- }
- }
- });
-
- didCacheProvider = true;
-}
diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js
new file mode 100644
index 0000000..61eb947
--- /dev/null
+++ b/frontend/svelte.config.js
@@ -0,0 +1,7 @@
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
+
+export default {
+ // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..5b2c1a8
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "extends": "@tsconfig/svelte/tsconfig.json",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "resolveJsonModule": true,
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable checkJs if you'd like to use dynamic types in JS.
+ * Note that setting allowJs false does not prevent the use
+ * of JS in `.svelte` files.
+ */
+ "allowJs": true,
+ "checkJs": true,
+ "isolatedModules": true,
+ "moduleDetection": "force"
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.js",
+ "src/**/*.svelte"
+ ],
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
new file mode 100644
index 0000000..396d96d
--- /dev/null
+++ b/frontend/tsconfig.node.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "noEmit": true
+ },
+ "include": [
+ "vite.config.ts"
+ ]
+}
\ No newline at end of file
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..5f1c8cb
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import { svelte } from '@sveltejs/vite-plugin-svelte'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [svelte()],
+})
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 4e2a732..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-pynput
-sanic
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..f10133b
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,274 @@
+use std::{process::ExitCode, sync::Arc};
+use ashpd::{
+ desktop::{
+ remote_desktop::{DeviceType, KeyState, RemoteDesktop},
+ PersistMode, Session,
+ },
+ WindowIdentifier,
+};
+use bytes::{Buf, BufMut, BytesMut};
+use warp::{
+ filters::ws::Message, ws::WebSocket, Filter
+};
+use futures_util::{SinkExt, StreamExt};
+use num_traits::FromPrimitive;
+use num_derive::FromPrimitive;
+use zbus::{proxy, zvariant::{DeserializeDict, OwnedValue, SerializeDict, Type, Value}, Connection, Result};
+
+
+#[repr(u8)]
+#[derive(FromPrimitive, Debug)]
+enum FrameType {
+ None = 0,
+
+ // Client to server
+ Authenticate = 1,
+ // Server to client
+ Ready,
+
+ // Client to server
+ PointerMotion = 10,
+ PointerAxis,
+ PointerAxisFinal,
+ PointerButtonPressed,
+ PointerButtonReleased,
+ PointerButtonTapped,
+ KeyboardButtonPressed,
+ KeyboardButtonReleased,
+ KeyboardButtonTapped
+}
+
+struct RemoteDesktopSession<'a> {
+ proxy: RemoteDesktop<'a>,
+ session: Session<'a, RemoteDesktop<'a>>,
+}
+
+
+async fn create_remote_desktop_session<'a>() -> ashpd::Result>> {
+ let proxy = RemoteDesktop::new().await?;
+ let session = proxy.create_session().await?;
+ proxy
+ .select_devices(
+ &session,
+ DeviceType::Keyboard | DeviceType::Pointer,
+ None,
+ PersistMode::DoNot,
+ )
+ .await?;
+
+ let response = proxy
+ .start(&session, &WindowIdentifier::default())
+ .await?
+ .response()?;
+ println!("{:#?}", response.devices());
+
+ Ok(Arc::new(RemoteDesktopSession { proxy, session }))
+}
+
+async fn handle_gateway_connection(ws: WebSocket, session: Arc>) {
+ let (mut user_ws_tx, mut user_ws_rx) = ws.split();
+ let mut authenticated = false;
+
+ while let Some(result) = user_ws_rx.next().await {
+ let msg = match result {
+ Ok(msg) => msg,
+ Err(_e) => {
+ break;
+ }
+ };
+ if !msg.is_binary() {
+ continue;
+ }
+
+ let mut bytes = msg.as_bytes();
+ if bytes.remaining() < 1 {
+ continue;
+ }
+ let frame_type = FromPrimitive::from_u8(bytes.get_u8());
+ println!("frame: {:?}", frame_type);
+ match frame_type {
+ Some(FrameType::Authenticate) => {
+ if authenticated {
+ continue;
+ }
+
+ // Here we'd check a password or something
+ authenticated = true;
+ let mut out_bytes = BytesMut::with_capacity(1);
+ out_bytes.put_u8(FrameType::Ready as u8);
+ if let Err(_) = user_ws_tx.send(Message::binary(out_bytes)).await {
+ return;
+ }
+ },
+ Some(FrameType::PointerMotion) => {
+ if !authenticated {
+ continue;
+ }
+
+ // PointerMotion payload:
+ // f32 dx, f32 dy
+ if bytes.remaining() < 8 {
+ continue;
+ }
+
+ let dx = bytes.get_f32();
+ let dy = bytes.get_f32();
+
+ let _ = session.proxy.notify_pointer_motion(&session.session, dx.into(), dy.into()).await;
+ },
+ Some(FrameType::PointerAxis) => {
+ if !authenticated {
+ continue;
+ }
+
+ // PointerAxis payload:
+ // f32 dx, f32 dy
+ if bytes.remaining() < 8 {
+ continue;
+ }
+
+ let dx = bytes.get_f32();
+ let dy = bytes.get_f32();
+
+ let _ = session.proxy.notify_pointer_axis(&session.session, dx.into(), dy.into(), false).await;
+ },
+ Some(FrameType::PointerAxisFinal) => {
+ if !authenticated {
+ continue;
+ }
+
+ // PointerAxisFinal payload:
+ // f32 dx, f32 dy
+ if bytes.remaining() < 8 {
+ continue;
+ }
+
+ let dx = bytes.get_f32();
+ let dy = bytes.get_f32();
+
+ let _ = session.proxy.notify_pointer_axis(&session.session, dx.into(), dy.into(), true).await;
+ },
+ Some(FrameType::PointerButtonPressed) => {
+ if !authenticated {
+ continue;
+ }
+
+ // PointerButtonPressed payload:
+ // i32 button
+ if bytes.remaining() < 4 {
+ continue;
+ }
+
+ let button = bytes.get_i32();
+
+ let _ = session.proxy.notify_pointer_button(&session.session, button, KeyState::Pressed).await;
+ },
+ Some(FrameType::PointerButtonReleased) => {
+ if !authenticated {
+ continue;
+ }
+
+ // PointerButtonReleased payload:
+ // i32 button
+ if bytes.remaining() < 4 {
+ continue;
+ }
+
+ let button = bytes.get_i32();
+
+ let _ = session.proxy.notify_pointer_button(&session.session, button, KeyState::Released).await;
+ },
+ Some(FrameType::PointerButtonTapped) => {
+ if !authenticated {
+ continue;
+ }
+
+ // PointerButtonTapped payload:
+ // i32 button
+ if bytes.remaining() < 4 {
+ continue;
+ }
+
+ let button = bytes.get_i32();
+
+ let _ = session.proxy.notify_pointer_button(&session.session, button, KeyState::Pressed).await;
+ let _ = session.proxy.notify_pointer_button(&session.session, button, KeyState::Released).await;
+ },
+ Some(FrameType::KeyboardButtonPressed) => {
+ if !authenticated {
+ continue;
+ }
+
+ // KeyboardButtonPressed payload:
+ // i32 code
+ if bytes.remaining() < 4 {
+ continue;
+ }
+
+ let code = bytes.get_i32();
+
+ let _ = session.proxy.notify_keyboard_keysym(&session.session, code, KeyState::Pressed).await;
+ },
+ Some(FrameType::KeyboardButtonReleased) => {
+ if !authenticated {
+ continue;
+ }
+
+ // KeyboardButtonReleased payload:
+ // i32 code
+ if bytes.remaining() < 4 {
+ continue;
+ }
+
+ let code = bytes.get_i32();
+
+ let _ = session.proxy.notify_keyboard_keysym(&session.session, code, KeyState::Released).await;
+ },
+ Some(FrameType::KeyboardButtonTapped) => {
+ if !authenticated {
+ continue;
+ }
+
+ // KeyboardButtonTapped payload:
+ // i32 code
+ if bytes.remaining() < 4 {
+ continue;
+ }
+
+ let code = bytes.get_i32();
+
+ let _ = session.proxy.notify_keyboard_keysym(&session.session, code, KeyState::Pressed).await;
+ let _ = session.proxy.notify_keyboard_keysym(&session.session, code, KeyState::Released).await;
+ },
+
+ // Invalid frame types
+ None | Some(FrameType::None) | Some(FrameType::Ready) => {}
+ }
+ }
+}
+
+#[tokio::main]
+async fn main() -> ExitCode {
+ let session = if let Ok(x) = create_remote_desktop_session().await { x } else { return ExitCode::from(1); };
+ let session = warp::any().map(move || session.clone());
+
+ let chat = warp::path("gateway")
+ .and(warp::ws())
+ .and(session)
+ .map(|ws: warp::ws::Ws, session| {
+ ws.on_upgrade(move |socket| handle_gateway_connection(socket, session))
+ });
+
+ let assets = warp::path("assets")
+ .and(warp::fs::dir("frontend/dist/assets"));
+
+ let root = warp::path::end()
+ .and(warp::fs::file("frontend/dist/index.html"))
+ .or(assets);
+
+ warp::serve(chat.or(root))
+ .run(([0, 0, 0, 0], 3030))
+ .await;
+
+ return ExitCode::from(0);
+}