diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6ab9e94..82c75df 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,12 +39,32 @@ jobs: uses: baptiste0928/cargo-install@v3.3.0 with: crate: leptosfmt - - name: Install plotters-rs deps - run: sudo apt install libfontconfig1-dev + - name: Install tauri + uses: baptiste0928/cargo-install@v3.3.0 + with: + crate: tauri-cli + - name: Install trunk + uses: baptiste0928/cargo-install@v3.3.0 + with: + crate: trunk + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf \ + libfontconfig1-dev - name: Run build run: cargo leptos build --release + - name: Build Tauri app + run: | + cd tauri + cargo tauri build - name: Run tests - run: cargo test --verbose + run: cargo test - name: Run clippy run: | cargo clippy diff --git a/Cargo.lock b/Cargo.lock index 636a0b6..4c33a84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -65,6 +80,63 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +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" @@ -76,6 +148,60 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[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 2.0.103", +] + +[[package]] +name = "async-signal" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +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.88" @@ -84,7 +210,30 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", ] [[package]] @@ -113,7 +262,7 @@ dependencies = [ "manyhow", "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -129,7 +278,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn", + "syn 2.0.103", ] [[package]] @@ -146,7 +295,7 @@ checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", "axum-macros", - "base64", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -225,7 +374,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -243,6 +392,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -279,6 +434,58 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.1", +] + +[[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 = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.18.1" @@ -302,12 +509,76 @@ name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] [[package]] name = "camino" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "cargo_toml" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" +dependencies = [ + "serde", + "toml", +] [[package]] name = "cc" @@ -318,6 +589,33 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -381,6 +679,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -400,7 +708,7 @@ dependencies = [ "pathdiff", "serde", "toml", - "winnow", + "winnow 0.7.11", ] [[package]] @@ -461,6 +769,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "convert_case" version = "0.6.0" @@ -500,6 +814,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -513,8 +837,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] @@ -526,7 +863,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", "libc", ] @@ -536,8 +884,8 @@ version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" dependencies = [ - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "foreign-types 0.5.0", "libc", ] @@ -575,6 +923,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -601,27 +958,64 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.20.11" +name = "cssparser" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ - "darling_core", - "darling_macro", + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", ] [[package]] -name = "darling_core" -version = "0.20.11" +name = "cssparser-macros" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ - "fnv", - "ident_case", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.103", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.103", ] [[package]] @@ -632,7 +1026,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -664,7 +1058,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -696,7 +1090,20 @@ checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.103", ] [[package]] @@ -732,6 +1139,22 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -740,7 +1163,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -749,7 +1172,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.8", +] + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] @@ -758,12 +1204,42 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + [[package]] name = "drain_filter_polyfill" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dwrote" version = "0.11.3" @@ -776,6 +1252,12 @@ dependencies = [ "wio", ] +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "either" version = "1.15.0" @@ -795,6 +1277,26 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "embed-resource" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0963f530273dc3022ab2bdc3fcd6d488e850256f2284a82b7413cb9481ee85dd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -804,6 +1306,33 @@ 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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -816,6 +1345,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1731451909bde27714eacba19c2566362a7f35224f52b153d3f42cf60f72472" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.12" @@ -873,6 +1412,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + [[package]] name = "flate2" version = "1.1.2" @@ -920,8 +1469,8 @@ checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" dependencies = [ "bitflags 2.9.1", "byteorder", - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "core-text", "dirs", "dwrote", @@ -964,7 +1513,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -999,6 +1548,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.31" @@ -1059,6 +1618,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -1067,7 +1639,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -1100,6 +1672,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "game-manager" version = "0.1.0" @@ -1132,7 +1713,6 @@ dependencies = [ "cfg-if", "chrono", "codee", - "getrandom 0.3.3", "leptos", "leptos-use", "log", @@ -1145,6 +1725,105 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1155,6 +1834,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -1199,54 +1889,202 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "gloo-net" -version = "0.6.0" +name = "gio" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" dependencies = [ "futures-channel", "futures-core", - "futures-sink", - "gloo-utils", - "http", - "js-sys", - "pin-project", - "serde", - "serde_json", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", "thiserror 1.0.69", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", ] [[package]] -name = "gloo-timers" -version = "0.3.0" +name = "gio-sys" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ + "bitflags 2.9.1", "futures-channel", "futures-core", - "js-sys", - "wasm-bindgen", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", ] [[package]] -name = "gloo-utils" -version = "0.2.0" +name = "glib-macros" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] -name = "guardian" -version = "1.3.0" +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "guardian" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" @@ -1262,13 +2100,19 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1295,6 +2139,12 @@ dependencies = [ "hashbrown 0.15.4", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1349,6 +2199,18 @@ dependencies = [ "utf8-width", ] +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + [[package]] name = "http" version = "1.3.1" @@ -1452,7 +2314,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.0", ] [[package]] @@ -1477,7 +2339,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -1521,6 +2383,16 @@ dependencies = [ "cc", ] +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -1648,6 +2520,17 @@ dependencies = [ "png", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -1656,6 +2539,16 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.4", + "serde", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", ] [[package]] @@ -1689,6 +2582,25 @@ dependencies = [ "serde", ] +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1704,6 +2616,51 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jpeg-decoder" version = "0.3.1" @@ -1720,6 +2677,51 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.9.0", + "selectors", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1736,7 +2738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ceaf7d86820125c57dcd380edac4b972debf480ee4c7eea6dd7cea212615978" dependencies = [ "any_spawner", - "base64", + "base64 0.22.1", "cfg-if", "either_of", "futures", @@ -1856,13 +2858,13 @@ checksum = "597f84532609518092960ac241741963c90c216ee11f752e1b238b846f043640" dependencies = [ "anyhow", "camino", - "indexmap", + "indexmap 2.9.0", "parking_lot", "proc-macro2", "quote", "rstml", "serde", - "syn", + "syn 2.0.103", "walkdir", ] @@ -1900,7 +2902,7 @@ dependencies = [ "rstml", "rustc_version", "server_fn_macro", - "syn", + "syn 2.0.103", "uuid", ] @@ -1911,7 +2913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c03b2d5e0a9e7060bce4862c009ced3c2ad0afe45d005eaa4defa3872d2d2aac" dependencies = [ "futures", - "indexmap", + "indexmap 2.9.0", "leptos", "once_cell", "or_poisoned", @@ -1955,7 +2957,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -1965,7 +2967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5af59932aa8a640da4d3d20650cf07084433e25db0ee690203d893b81773db29" dependencies = [ "any_spawner", - "base64", + "base64 0.22.1", "codee", "futures", "hydration_context", @@ -1978,12 +2980,46 @@ dependencies = [ "tachys", ] +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.8.8" @@ -2062,6 +3098,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "manyhow" version = "0.11.4" @@ -2071,7 +3113,7 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -2085,6 +3127,31 @@ dependencies = [ "quote", ] +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "matchers" version = "0.1.0" @@ -2094,6 +3161,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "matchit" version = "0.8.4" @@ -2116,6 +3189,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[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" @@ -2216,6 +3298,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "muda" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + [[package]] name = "multer" version = "3.1.0" @@ -2260,11 +3363,66 @@ dependencies = [ ] [[package]] -name = "next_tuple" -version = "0.1.0" +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "next_tuple" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2338,6 +3496,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -2353,7 +3533,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "getrandom 0.2.16", "http", @@ -2367,6 +3547,220 @@ dependencies = [ "url", ] +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "libc", + "objc2 0.6.1", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +dependencies = [ + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "libc", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + [[package]] name = "object" version = "0.36.7" @@ -2392,6 +3786,18 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "openssl" version = "0.10.73" @@ -2415,7 +3821,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -2448,12 +3854,47 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" +[[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 = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "parking" version = "2.2.1" @@ -2530,76 +3971,234 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pin-project" -version = "1.1.10" +name = "phf" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "pin-project-internal", + "phf_shared 0.8.0", ] [[package]] -name = "pin-project-internal" -version = "1.1.10" +name = "phf" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ - "proc-macro2", - "quote", - "syn", + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "phf" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "phf_codegen" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] [[package]] -name = "pkcs1" -version = "0.7.5" +name = "phf_codegen" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "der", - "pkcs8", - "spki", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "phf_generator" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" dependencies = [ - "der", - "spki", + "phf_shared 0.8.0", + "rand 0.7.3", ] [[package]] -name = "pkg-config" -version = "0.3.32" +name = "phf_generator" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] [[package]] -name = "plotters" -version = "0.3.7" +name = "phf_generator" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "chrono", - "font-kit", - "image", - "lazy_static", - "num-traits", - "pathfinder_geometry", + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[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 = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" +dependencies = [ + "base64 0.22.1", + "indexmap 2.9.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", "plotters-backend", "plotters-bitmap", "plotters-svg", @@ -2659,6 +4258,21 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -2683,6 +4297,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "prettyplease" version = "0.2.34" @@ -2690,7 +4310,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.103", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.27", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] @@ -2712,9 +4384,15 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro-utils" version = "0.10.0" @@ -2743,11 +4421,20 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", "version_check", "yansi", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.8" @@ -2831,7 +4518,7 @@ dependencies = [ "proc-macro-utils", "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -2840,6 +4527,20 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.5" @@ -2861,6 +4562,16 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2881,6 +4592,15 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -2899,6 +4619,30 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "reactive_graph" version = "0.2.2" @@ -2948,7 +4692,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -2971,6 +4715,26 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "regex" version = "1.11.1" @@ -3021,10 +4785,11 @@ version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", + "futures-util", "h2", "http", "http-body", @@ -3049,14 +4814,16 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 1.0.0", ] [[package]] @@ -3125,7 +4892,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.103", "syn_derive", "thiserror 2.0.12", ] @@ -3230,26 +4997,65 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.1" +name = "schemars" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ - "bitflags 2.9.1", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", ] [[package]] -name = "security-framework-sys" +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.103", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" @@ -3258,11 +5064,32 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "send_wrapper" @@ -3282,6 +5109,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-untagged" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -3301,7 +5139,18 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] @@ -3337,6 +5186,17 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -3358,6 +5218,59 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "schemars 0.9.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "server_fn" version = "0.8.2" @@ -3365,7 +5278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b0f92b9d3a62c73f238ac21f7a09f15bad335a9d1651514d9da80d2eaf8d4c" dependencies = [ "axum", - "base64", + "base64 0.22.1", "bytes", "const-str", "const_format", @@ -3410,7 +5323,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.103", "xxhash-rust", ] @@ -3421,7 +5334,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ab934f581482a66da82f2b57b15390ad67c9ab85bd9a6c54bb65060fb1380" dependencies = [ "server_fn_macro", - "syn", + "syn 2.0.103", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", ] [[package]] @@ -3498,6 +5421,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.10" @@ -3532,6 +5467,54 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "spin" version = "0.9.8" @@ -3570,7 +5553,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "chrono", "crc", @@ -3583,11 +5566,12 @@ dependencies = [ "futures-util", "hashbrown 0.15.4", "hashlink", - "indexmap", + "indexmap 2.9.0", "log", "memchr", "once_cell", "percent-encoding", + "rustls", "serde", "serde_json", "sha2", @@ -3598,6 +5582,7 @@ dependencies = [ "tokio-stream", "tracing", "url", + "webpki-roots 0.26.11", ] [[package]] @@ -3610,7 +5595,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.103", ] [[package]] @@ -3621,7 +5606,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -3633,7 +5618,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.103", "tokio", "url", ] @@ -3645,7 +5630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags 2.9.1", "byteorder", "bytes", @@ -3689,7 +5674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags 2.9.1", "byteorder", "chrono", @@ -3742,137 +5727,565 @@ dependencies = [ "serde_urlencoded", "sqlx-core", "thiserror 2.0.12", - "time", - "tracing", + "time", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tachys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a9a5d6436e532fd27b49bcca005a038bf510fc369687de830121a74811ccf4" +dependencies = [ + "any_spawner", + "async-trait", + "const_str_slice_concat", + "drain_filter_polyfill", + "either_of", + "erased", + "futures", + "html-escape", + "indexmap 2.9.0", + "itertools", + "js-sys", + "linear-map", + "next_tuple", + "oco_ref", + "once_cell", + "or_poisoned", + "parking_lot", + "paste", + "reactive_graph", + "reactive_stores", + "rustc-hash", + "rustc_version", + "send_wrapper", + "slotmap", + "throw_error", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "tao" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "sqlx", + "tauri 2.6.2", + "tauri-build", + "tauri-plugin-opener", + "tauri-plugin-sql", + "tokio", +] + +[[package]] +name = "tauri" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" +dependencies = [ + "anyhow", + "bytes", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.3", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.12", + "tokio", + "tray-icon", "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", ] [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "tauri-build" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml", + "walkdir", +] [[package]] -name = "stringprep" -version = "0.1.5" +name = "tauri-codegen" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.103", + "tauri-utils", + "thiserror 2.0.12", + "time", + "url", + "uuid", + "walkdir", ] [[package]] -name = "strsim" -version = "0.11.1" +name = "tauri-macros" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.103", + "tauri-codegen", + "tauri-utils", +] [[package]] -name = "subtle" -version = "2.6.1" +name = "tauri-plugin" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml", + "walkdir", +] [[package]] -name = "syn" -version = "2.0.103" +name = "tauri-plugin-opener" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri 2.6.2", + "tauri-plugin", + "thiserror 2.0.12", + "url", + "windows", + "zbus", ] [[package]] -name = "syn_derive" -version = "0.2.0" +name = "tauri-plugin-sql" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" +checksum = "df059378695202fef1e274b8e7916fc3dffc44716ae4baf8c0226089b2f390ae" dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", + "futures-core", + "indexmap 2.9.0", + "log", + "serde", + "serde_json", + "sqlx", + "tauri 2.6.2", + "tauri-plugin", + "thiserror 2.0.12", + "time", + "tokio", ] [[package]] -name = "sync_wrapper" -version = "1.0.2" +name = "tauri-runtime" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" dependencies = [ - "futures-core", + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.1", + "objc2-ui-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.12", + "url", + "windows", ] [[package]] -name = "synstructure" -version = "0.13.2" +name = "tauri-runtime-wry" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" dependencies = [ - "proc-macro2", - "quote", - "syn", + "gtk", + "http", + "jni", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", ] [[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +name = "tauri-ui" +version = "0.1.0" dependencies = [ - "bitflags 2.9.1", - "core-foundation", - "system-configuration-sys", + "anyhow", + "chrono", + "console_error_panic_hook", + "console_log", + "game-ui", + "getrandom 0.3.3", + "gloo-timers", + "js-sys", + "leptos", + "leptos-use", + "log", + "minesweeper-lib", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] -name = "system-configuration-sys" -version = "0.6.0" +name = "tauri-utils" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" dependencies = [ - "core-foundation-sys", - "libc", + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.12", + "toml", + "url", + "urlpattern", + "uuid", + "walkdir", ] [[package]] -name = "tachys" -version = "0.2.3" +name = "tauri-winres" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a9a5d6436e532fd27b49bcca005a038bf510fc369687de830121a74811ccf4" +checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" dependencies = [ - "any_spawner", - "async-trait", - "const_str_slice_concat", - "drain_filter_polyfill", - "either_of", - "erased", - "futures", - "html-escape", - "indexmap", - "itertools", - "js-sys", - "linear-map", - "next_tuple", - "oco_ref", - "once_cell", - "or_poisoned", - "parking_lot", - "paste", - "reactive_graph", - "reactive_stores", - "rustc-hash", - "rustc_version", - "send_wrapper", - "slotmap", - "throw_error", - "wasm-bindgen", - "web-sys", + "embed-resource", + "indexmap 2.9.0", + "toml", ] [[package]] @@ -3888,6 +6301,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3914,7 +6338,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -3925,7 +6349,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -4030,7 +6454,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -4098,7 +6522,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -4110,19 +6534,48 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", - "winnow", + "toml_write", + "winnow 0.7.11", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.2" @@ -4221,7 +6674,7 @@ checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b" dependencies = [ "async-trait", "axum-core", - "base64", + "base64 0.22.1", "futures", "http", "parking_lot", @@ -4280,7 +6733,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -4322,6 +6775,28 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tray-icon" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -4368,15 +6843,53 @@ checksum = "60d8d828da2a3d759d3519cdf29a5bac49c77d039ad36d0782edadbf9cd5415b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[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 = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + [[package]] name = "unic-langid" version = "0.9.6" @@ -4395,6 +6908,26 @@ dependencies = [ "tinystr", ] +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.8.1" @@ -4464,6 +6997,18 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + [[package]] name = "utf-8" version = "0.7.6" @@ -4490,6 +7035,7 @@ checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", "js-sys", + "serde", "wasm-bindgen", ] @@ -4505,12 +7051,38 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -4530,6 +7102,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -4573,7 +7151,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.103", "wasm-bindgen-shared", ] @@ -4608,7 +7186,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4681,6 +7259,59 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + [[package]] name = "webpki-roots" version = "1.0.0" @@ -4690,6 +7321,42 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.12", + "windows", + "windows-core", +] + [[package]] name = "weezl" version = "0.1.10" @@ -4737,6 +7404,43 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -4750,6 +7454,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -4758,7 +7473,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -4769,7 +7484,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -4778,6 +7493,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.5.2" @@ -4807,6 +7532,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4843,6 +7577,21 @@ dependencies = [ "windows-targets 0.53.2", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4890,6 +7639,30 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4908,6 +7681,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4926,6 +7705,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4956,6 +7741,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4974,6 +7765,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4992,6 +7789,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5010,6 +7813,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5028,6 +7837,15 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.11" @@ -5037,6 +7855,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + [[package]] name = "wio" version = "0.2.2" @@ -5061,6 +7889,71 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "wry" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" +dependencies = [ + "base64 0.22.1", + "block2 0.6.1", + "cookie", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.12", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + [[package]] name = "xxhash-rust" version = "0.8.15" @@ -5104,10 +7997,70 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", "synstructure", ] +[[package]] +name = "zbus" +version = "5.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow 0.7.11", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.103", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.11", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.8.26" @@ -5125,7 +8078,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", ] [[package]] @@ -5145,7 +8098,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", "synstructure", ] @@ -5185,5 +8138,46 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.103", +] + +[[package]] +name = "zvariant" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d30786f75e393ee63a21de4f9074d4c038d52c5b1bb4471f955db249f9dffb1" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.11", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75fda702cd42d735ccd48117b1630432219c0e9616bf6cb0f8350844ee4d9580" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.103", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.103", + "winnow 0.7.11", ] diff --git a/Cargo.toml b/Cargo.toml index 9c6c117..612fcfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ members = [ "web", # integrations + "tauri", + "tauri/src-tauri", # libraries "minesweeper-lib", diff --git a/game-ui/Cargo.toml b/game-ui/Cargo.toml index eedb96a..6f71535 100644 --- a/game-ui/Cargo.toml +++ b/game-ui/Cargo.toml @@ -17,8 +17,7 @@ leptos-use = { version = "0.16.0", features = ["storage", "use_document", "use_e log = "0.4" serde = "1.0" serde_json = "1.0" -wasm-bindgen = "=0.2.100" +wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["WebSocket", "Performance"] } -getrandom = { version = "0.3", features = ["wasm_js"] } plotters = "0.3.7" plotters-canvas = "0.3.0" diff --git a/web/src/app/background.rs b/game-ui/src/background.rs similarity index 93% rename from web/src/app/background.rs rename to game-ui/src/background.rs index 8cfd12d..537a240 100644 --- a/web/src/app/background.rs +++ b/game-ui/src/background.rs @@ -1,4 +1,4 @@ -use game_ui::icons::Mine; +use crate::icons::Mine; use leptos::prelude::*; use serde::{Deserialize, Serialize}; @@ -41,100 +41,100 @@ fn FloatingMinesBackground() -> impl IntoView { // Large mines - slow movement
// Medium mines - medium movement
// Small mines - fast movement
// Additional depth mines - mixed speeds
@@ -193,4 +193,4 @@ pub fn BackgroundToggle(set_background_variant: WriteSignal) } -} +} \ No newline at end of file diff --git a/game-ui/src/dark_mode.rs b/game-ui/src/dark_mode.rs new file mode 100644 index 0000000..2d8b138 --- /dev/null +++ b/game-ui/src/dark_mode.rs @@ -0,0 +1,39 @@ +use leptos::prelude::*; +use leptos_use::{use_color_mode, ColorMode, UseColorModeReturn}; + +#[component] +pub fn DarkModeToggle() -> impl IntoView { + let UseColorModeReturn { mode, set_mode, .. } = use_color_mode(); + view! { + + } +} \ No newline at end of file diff --git a/game-ui/src/icons.rs b/game-ui/src/icons.rs index d89724e..87647a1 100644 --- a/game-ui/src/icons.rs +++ b/game-ui/src/icons.rs @@ -32,6 +32,16 @@ macro_rules! widget_icon_holder { }; } +#[macro_export] +macro_rules! widget_icon_standalone { + ($bg:literal) => { + concat!("inline-block align-text-top h-6 w-6 p-0.5 ", $bg) + }; + ($bg:literal, true) => { + concat!("inline-block align-text-top h-6 w-6 p-0.5 relative ", $bg) + }; +} + #[component] pub fn IconTooltip(children: Children) -> impl IntoView { view! { @@ -435,3 +445,33 @@ pub fn StopWatch() -> impl IntoView { } } + +#[component] +pub fn Circle() -> impl IntoView { + view! { + + + + } +} + +#[component] +pub fn PlayArrow() -> impl IntoView { + view! { + + + + + + } +} diff --git a/game-ui/src/lib.rs b/game-ui/src/lib.rs index 8e59e86..150a67c 100644 --- a/game-ui/src/lib.rs +++ b/game-ui/src/lib.rs @@ -1,4 +1,6 @@ +pub mod background; pub mod components; +pub mod dark_mode; pub mod icons; pub mod info; @@ -10,6 +12,8 @@ mod replay; mod stats; mod widgets; +pub use background::{AnimatedBackground, BackgroundToggle, BackgroundVariant}; +pub use dark_mode::DarkModeToggle; pub use game::{ActiveGame, InactiveGame, ReplayGame}; pub use minesweeper::{GameInfo, GameInfoWithLog, GameSettings}; pub use mode::{GameMode, PresetButtons, SettingsInputs}; @@ -20,8 +24,8 @@ pub use stats::{ TimelineGameModeStats, TimelineStats, TimelineStatsGraphs, }; pub use widgets::{ - game_time_from_start_end, ActiveMines, ActiveTimer, CopyGameLink, GameWidgets, InactiveMines, - InactiveTimer, + game_time_from_start_end, ActiveMines, ActiveTimer, CopyGameLink, GameState, GameStateWidget, + GameWidgets, InactiveGameStateWidget, InactiveMines, InactiveTimer, }; use leptos::prelude::*; diff --git a/game-ui/src/minesweeper.rs b/game-ui/src/minesweeper.rs index 1ad0bb4..8d6165e 100644 --- a/game-ui/src/minesweeper.rs +++ b/game-ui/src/minesweeper.rs @@ -4,9 +4,9 @@ use serde::{Deserialize, Serialize}; use minesweeper_lib::{ board::{Board, CompactBoard}, - cell::PlayerCell, + cell::{HiddenCell, PlayerCell}, client::ClientPlayer, - game::{Play, PlayOutcome, CompactPlayOutcome}, + game::{CompactPlayOutcome, Play, PlayOutcome}, }; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -27,6 +27,28 @@ pub struct GameInfo { } impl GameInfo { + pub fn new_singleplayer(game_id: String, rows: usize, cols: usize, num_mines: usize) -> Self { + Self { + game_id, + has_owner: false, + is_owner: false, + rows, + cols, + num_mines, + max_players: 1, + is_started: true, + is_completed: false, + start_time: Some(Utc::now()), + end_time: None, + final_board: CompactBoard::from_board(&Board::new( + rows, + cols, + PlayerCell::Hidden(HiddenCell::Empty), + )), + players: vec![None], + } + } + /// Convert the compact board to full Board for UI use pub fn board(&self) -> Board { self.final_board.to_board() diff --git a/game-ui/src/stats.rs b/game-ui/src/stats.rs index 7043337..7bb80fb 100644 --- a/game-ui/src/stats.rs +++ b/game-ui/src/stats.rs @@ -372,15 +372,17 @@ pub fn TimelineStatsGraphs(timeline_stats: Signal>) -> imp ); view! { -
- +
+
+ +
+
- } } diff --git a/game-ui/src/widgets.rs b/game-ui/src/widgets.rs index 43cacce..6f27293 100644 --- a/game-ui/src/widgets.rs +++ b/game-ui/src/widgets.rs @@ -1,4 +1,5 @@ use chrono::DateTime; +use leptos::either::EitherOf4; use leptos::prelude::*; use leptos_use::{ use_clipboard, use_interval_fn_with_options, use_timeout_fn, utils::Pausable, @@ -6,15 +7,17 @@ use leptos_use::{ }; use crate::{ - icons::{Copy, IconTooltip, Mine, StopWatch}, - widget_icon_holder, + icons::{Circle, Copy, IconTooltip, Mine, PlayArrow, Star, StopWatch}, + widget_icon_holder, widget_icon_standalone, }; #[component] pub fn GameWidgets(children: Children) -> impl IntoView { view! {
-
{children()}
+
+ {children()} +
} } @@ -152,9 +155,9 @@ pub fn CopyGameLink(game_url: String) -> impl IntoView { let copy_class = move || { let show_tooltip = show_tooltip.get(); if show_tooltip { - "show-tooltip cursor-default" + "whitespace-nowrap show-tooltip cursor-default" } else { - "cursor-pointer" + "whitespace-nowrap cursor-pointer" } }; view! { @@ -176,3 +179,148 @@ pub fn CopyGameLink(game_url: String) -> impl IntoView {
} } + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum GameState { + NotStarted, + Active, + Victory, + Dead, +} + +#[component] +pub fn GameStateWidget( + victory: ReadSignal, + dead: ReadSignal, + sync_time: ReadSignal>, + on_click: F, +) -> impl IntoView +where + F: Fn() + 'static, +{ + let game_state = move || { + let is_victory = victory.get(); + let is_dead = dead.get(); + let has_started = sync_time.get().is_some(); + + if is_victory { + GameState::Victory + } else if is_dead { + GameState::Dead + } else if has_started { + GameState::Active + } else { + GameState::NotStarted + } + }; + + view! { +
+ +
+ } +} + +#[component] +pub fn InactiveGameStateWidget(game_state: GameState, on_click: F) -> impl IntoView +where + F: Fn() + 'static, +{ + view! { +
+ +
+ } +} diff --git a/minesweeper-lib/src/client.rs b/minesweeper-lib/src/client.rs index 905f8d0..aef7ec0 100644 --- a/minesweeper-lib/src/client.rs +++ b/minesweeper-lib/src/client.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; pub struct MinesweeperClient { pub player: Option, pub players: Vec>, - pub game_over: bool, + pub victory: bool, pub board: Board, } @@ -18,7 +18,7 @@ impl MinesweeperClient { MinesweeperClient { player: None, players, - game_over: false, + victory: false, board, } } @@ -76,7 +76,7 @@ impl MinesweeperClient { self.board[point] = player_cell; updated.push((point, player_cell)); }); - self.game_over = true; + self.victory = true; } PlayOutcome::Failure(cell) => { let point = cell.0; diff --git a/minesweeper-lib/src/game.rs b/minesweeper-lib/src/game.rs index b280c8c..87006ea 100644 --- a/minesweeper-lib/src/game.rs +++ b/minesweeper-lib/src/game.rs @@ -139,7 +139,7 @@ impl Board<(Cell, CellState)> { } } -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct Play { #[serde(rename = "p", alias = "player")] pub player: usize, @@ -697,6 +697,186 @@ fn bool_to_u8(b: bool) -> u8 { } } +pub fn compress_game_log(log: &[(Play, PlayOutcome)]) -> Vec { + let mut compressed = Vec::new(); + + for (play, outcome) in log { + // Compress Play + compressed.push(play.player as u8); + compressed.push(play.action.to_compact_byte()); + compressed.push(play.point.row as u8); + compressed.push(play.point.col as u8); + + // Compress PlayOutcome + match outcome { + PlayOutcome::Success(cells) => { + compressed.push(0); // Success marker + compressed.push(cells.len() as u8); + for (point, revealed) in cells { + compressed.push(point.row as u8); + compressed.push(point.col as u8); + compressed.push(revealed.player as u8); + compressed.push(match revealed.contents { + Cell::Empty(n) => n, + Cell::Mine => 9, + }); + } + } + PlayOutcome::Failure((point, revealed)) => { + compressed.push(1); // Failure marker + compressed.push(point.row as u8); + compressed.push(point.col as u8); + compressed.push(revealed.player as u8); + compressed.push(match revealed.contents { + Cell::Empty(n) => n, + Cell::Mine => 9, + }); + } + PlayOutcome::Victory(cells) => { + compressed.push(2); // Victory marker + compressed.push(cells.len() as u8); + for (point, revealed) in cells { + compressed.push(point.row as u8); + compressed.push(point.col as u8); + compressed.push(revealed.player as u8); + compressed.push(match revealed.contents { + Cell::Empty(n) => n, + Cell::Mine => 9, + }); + } + } + PlayOutcome::Flag((point, player_cell)) => { + compressed.push(3); // Flag marker + compressed.push(point.row as u8); + compressed.push(point.col as u8); + compressed.push(player_cell.to_compact_byte()); + } + } + } + + compressed +} + +pub fn decompress_game_log(compressed: &[u8]) -> Vec<(Play, PlayOutcome)> { + let mut log = Vec::new(); + let mut i = 0; + + while i < compressed.len() { + if i + 4 > compressed.len() { + break; + } + + // Decompress Play + let player = compressed[i] as usize; + let action = Action::from_compact_byte(compressed[i + 1]); + let point = BoardPoint { + row: compressed[i + 2] as usize, + col: compressed[i + 3] as usize, + }; + let play = Play { player, action, point }; + i += 4; + + if i >= compressed.len() { + break; + } + + // Decompress PlayOutcome + let outcome_type = compressed[i]; + i += 1; + + let outcome = match outcome_type { + 0 => { // Success + if i >= compressed.len() { + break; + } + let cell_count = compressed[i] as usize; + i += 1; + let mut cells = Vec::new(); + + for _ in 0..cell_count { + if i + 4 > compressed.len() { + break; + } + let point = BoardPoint { + row: compressed[i] as usize, + col: compressed[i + 1] as usize, + }; + let player = compressed[i + 2] as usize; + let contents = if compressed[i + 3] == 9 { + Cell::Mine + } else { + Cell::Empty(compressed[i + 3]) + }; + cells.push((point, RevealedCell { player, contents })); + i += 4; + } + PlayOutcome::Success(cells) + } + 1 => { // Failure + if i + 4 > compressed.len() { + break; + } + let point = BoardPoint { + row: compressed[i] as usize, + col: compressed[i + 1] as usize, + }; + let player = compressed[i + 2] as usize; + let contents = if compressed[i + 3] == 9 { + Cell::Mine + } else { + Cell::Empty(compressed[i + 3]) + }; + i += 4; + PlayOutcome::Failure((point, RevealedCell { player, contents })) + } + 2 => { // Victory + if i >= compressed.len() { + break; + } + let cell_count = compressed[i] as usize; + i += 1; + let mut cells = Vec::new(); + + for _ in 0..cell_count { + if i + 4 > compressed.len() { + break; + } + let point = BoardPoint { + row: compressed[i] as usize, + col: compressed[i + 1] as usize, + }; + let player = compressed[i + 2] as usize; + let contents = if compressed[i + 3] == 9 { + Cell::Mine + } else { + Cell::Empty(compressed[i + 3]) + }; + cells.push((point, RevealedCell { player, contents })); + i += 4; + } + PlayOutcome::Victory(cells) + } + 3 => { // Flag + if i + 3 > compressed.len() { + break; + } + let point = BoardPoint { + row: compressed[i] as usize, + col: compressed[i + 1] as usize, + }; + let player_cell = PlayerCell::from_compact_byte(compressed[i + 2]); + i += 3; + PlayOutcome::Flag((point, player_cell)) + } + _ => break, // Invalid outcome type + }; + + log.push((play, outcome)); + } + + log +} + #[derive(Clone, Debug, Default)] pub struct Player { played: bool, @@ -726,7 +906,26 @@ impl Action { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +impl CompactSerialize for Action { + fn to_compact_byte(&self) -> u8 { + match self { + Action::Flag => 0, + Action::Reveal => 1, + Action::RevealAdjacent => 2, + } + } + + fn from_compact_byte(byte: u8) -> Self { + match byte { + 0 => Action::Flag, + 1 => Action::Reveal, + 2 => Action::RevealAdjacent, + _ => Action::Reveal, // Default fallback + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum PlayOutcome { #[serde(rename = "s", alias = "Success")] Success(Vec<(BoardPoint, RevealedCell)>), @@ -1615,4 +1814,256 @@ mod test { assert_ne!(game.board[POINT_2_3].0, Cell::Mine); assert_eq!(game.board[POINT_0_0].0, Cell::Mine); } + + #[test] + fn test_game_log_compression_round_trip() { + use crate::cell::{Cell, RevealedCell}; + + // Create a sample game log with various play outcomes + let test_log = vec![ + ( + Play { + player: 0, + action: Action::Reveal, + point: BoardPoint { row: 1, col: 1 }, + }, + PlayOutcome::Success(vec![ + ( + BoardPoint { row: 1, col: 1 }, + RevealedCell { + player: 0, + contents: Cell::Empty(2), + }, + ), + ( + BoardPoint { row: 1, col: 2 }, + RevealedCell { + player: 0, + contents: Cell::Empty(1), + }, + ), + ]), + ), + ( + Play { + player: 0, + action: Action::Flag, + point: BoardPoint { row: 2, col: 2 }, + }, + PlayOutcome::Flag(( + BoardPoint { row: 2, col: 2 }, + PlayerCell::Hidden(HiddenCell::Flag), + )), + ), + ( + Play { + player: 0, + action: Action::Reveal, + point: BoardPoint { row: 3, col: 3 }, + }, + PlayOutcome::Failure(( + BoardPoint { row: 3, col: 3 }, + RevealedCell { + player: 0, + contents: Cell::Mine, + }, + )), + ), + ( + Play { + player: 0, + action: Action::RevealAdjacent, + point: BoardPoint { row: 0, col: 0 }, + }, + PlayOutcome::Victory(vec![ + ( + BoardPoint { row: 0, col: 0 }, + RevealedCell { + player: 0, + contents: Cell::Empty(0), + }, + ), + ]), + ), + ]; + + // Test compression and decompression + let compressed = compress_game_log(&test_log); + let decompressed = decompress_game_log(&compressed); + + assert_eq!(test_log.len(), decompressed.len()); + + for (original, restored) in test_log.iter().zip(decompressed.iter()) { + assert_eq!(original.0.player, restored.0.player); + assert_eq!(original.0.action, restored.0.action); + assert_eq!(original.0.point, restored.0.point); + assert_eq!(original.1, restored.1); + } + } + + #[test] + fn test_game_log_compression_empty_log() { + let empty_log: Vec<(Play, PlayOutcome)> = Vec::new(); + let compressed = compress_game_log(&empty_log); + let decompressed = decompress_game_log(&compressed); + + assert_eq!(empty_log, decompressed); + } + + #[test] + fn test_game_log_compression_single_play() { + let single_play = vec![ + ( + Play { + player: 5, + action: Action::Flag, + point: BoardPoint { row: 10, col: 15 }, + }, + PlayOutcome::Flag(( + BoardPoint { row: 10, col: 15 }, + PlayerCell::Hidden(HiddenCell::Flag), + )), + ), + ]; + + let compressed = compress_game_log(&single_play); + let decompressed = decompress_game_log(&compressed); + + assert_eq!(single_play, decompressed); + } + + #[test] + fn test_game_log_compression_all_actions() { + use crate::cell::{Cell, RevealedCell}; + + let test_log = vec![ + ( + Play { + player: 0, + action: Action::Flag, + point: BoardPoint { row: 0, col: 0 }, + }, + PlayOutcome::Flag(( + BoardPoint { row: 0, col: 0 }, + PlayerCell::Hidden(HiddenCell::Flag), + )), + ), + ( + Play { + player: 1, + action: Action::Reveal, + point: BoardPoint { row: 1, col: 1 }, + }, + PlayOutcome::Success(vec![ + ( + BoardPoint { row: 1, col: 1 }, + RevealedCell { + player: 1, + contents: Cell::Empty(5), + }, + ), + ]), + ), + ( + Play { + player: 2, + action: Action::RevealAdjacent, + point: BoardPoint { row: 2, col: 2 }, + }, + PlayOutcome::Failure(( + BoardPoint { row: 2, col: 2 }, + RevealedCell { + player: 2, + contents: Cell::Mine, + }, + )), + ), + ]; + + let compressed = compress_game_log(&test_log); + let decompressed = decompress_game_log(&compressed); + + assert_eq!(test_log, decompressed); + } + + #[test] + fn test_game_log_compression_large_numbers() { + use crate::cell::{Cell, RevealedCell}; + + let test_log = vec![ + ( + Play { + player: 255, + action: Action::Reveal, + point: BoardPoint { row: 255, col: 255 }, + }, + PlayOutcome::Success(vec![ + ( + BoardPoint { row: 255, col: 255 }, + RevealedCell { + player: 255, + contents: Cell::Empty(8), + }, + ), + ]), + ), + ]; + + let compressed = compress_game_log(&test_log); + let decompressed = decompress_game_log(&compressed); + + // Note: Player numbers > 255 will be truncated to u8 + assert_eq!(decompressed[0].0.player, 255); + assert_eq!(decompressed[0].0.point.row, 255); + assert_eq!(decompressed[0].0.point.col, 255); + + match &decompressed[0].1 { + PlayOutcome::Success(cells) => { + assert_eq!(cells[0].1.player, 255); + assert_eq!(cells[0].1.contents, Cell::Empty(8)); + } + _ => panic!("Expected Success outcome"), + } + } + + #[test] + fn test_game_log_compression_efficiency() { + use crate::cell::{Cell, RevealedCell}; + + // Create a longer game log to test compression efficiency + let mut test_log = Vec::new(); + for i in 0..100 { + test_log.push(( + Play { + player: i % 4, + action: Action::Reveal, + point: BoardPoint { row: i / 10, col: i % 10 }, + }, + PlayOutcome::Success(vec![ + ( + BoardPoint { row: i / 10, col: i % 10 }, + RevealedCell { + player: i % 4, + contents: Cell::Empty((i % 9) as u8), + }, + ), + ]), + )); + } + + let compressed = compress_game_log(&test_log); + let decompressed = decompress_game_log(&compressed); + + assert_eq!(test_log.len(), decompressed.len()); + + // Check that compression is reasonably efficient + // Each play should take roughly 8 bytes (4 for play + 4 for outcome) + // Plus some overhead, so let's say max 12 bytes per play + assert!(compressed.len() < test_log.len() * 12); + + // Verify correctness + for (original, restored) in test_log.iter().zip(decompressed.iter()) { + assert_eq!(original, restored); + } + } } diff --git a/tauri/.gitignore b/tauri/.gitignore new file mode 100644 index 0000000..48c3ca4 --- /dev/null +++ b/tauri/.gitignore @@ -0,0 +1,3 @@ +/dist/ +/target/ +/Cargo.lock diff --git a/tauri/.taurignore b/tauri/.taurignore new file mode 100644 index 0000000..1ebdc6d --- /dev/null +++ b/tauri/.taurignore @@ -0,0 +1,3 @@ +/src +/public +/Cargo.toml \ No newline at end of file diff --git a/tauri/.vscode/extensions.json b/tauri/.vscode/extensions.json new file mode 100644 index 0000000..24d7cc6 --- /dev/null +++ b/tauri/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] +} diff --git a/tauri/.vscode/settings.json b/tauri/.vscode/settings.json new file mode 100644 index 0000000..e6d9d21 --- /dev/null +++ b/tauri/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "emmet.includeLanguages": { + "rust": "html" + } +} diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml new file mode 100644 index 0000000..02925b8 --- /dev/null +++ b/tauri/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tauri-ui" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +leptos = { version = "0.8.0", features = ["csr", "nightly"] } +leptos-use = { version = "0.16.0" } +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +serde = { version = "1", features = ["derive"] } +serde_json = "1.0" +serde-wasm-bindgen = "0.6" +console_error_panic_hook = "0.1.7" +minesweeper-lib = { path = "../minesweeper-lib" } +game-ui = { path = "../game-ui" } +anyhow = "1.0" +getrandom = { version = "0.3", features = ["wasm_js"] } +chrono = { version = "0.4", features = ["serde"] } +log = "0.4" +console_log = "1.0" +gloo-timers = { version = "0.3", features = ["futures"] } diff --git a/tauri/README.md b/tauri/README.md new file mode 100644 index 0000000..77812aa --- /dev/null +++ b/tauri/README.md @@ -0,0 +1,7 @@ +# Tauri + Leptos + +This template should help get you started developing with Tauri and Leptos. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). diff --git a/tauri/Trunk.toml b/tauri/Trunk.toml new file mode 100644 index 0000000..c9a88b7 --- /dev/null +++ b/tauri/Trunk.toml @@ -0,0 +1,9 @@ +[build] +target = "./index.html" + +[watch] +ignore = ["./src-tauri"] + +[serve] +port = 1420 +open = false diff --git a/tauri/index.html b/tauri/index.html new file mode 100644 index 0000000..ad6f157 --- /dev/null +++ b/tauri/index.html @@ -0,0 +1,24 @@ + + + + + Tauri + Leptos App + + + + + + + diff --git a/tauri/public/flag.svg b/tauri/public/flag.svg new file mode 100644 index 0000000..cc2552b --- /dev/null +++ b/tauri/public/flag.svg @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/tauri/src-tauri/.gitignore b/tauri/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/tauri/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/tauri/src-tauri/Cargo.toml b/tauri/src-tauri/Cargo.toml new file mode 100644 index 0000000..ff485aa --- /dev/null +++ b/tauri/src-tauri/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tauri" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "tauri_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tauri-plugin-sql = { version = "2", features = ["sqlite"] } +tokio = { version = "1", features = ["full"] } +sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite", "chrono"] } diff --git a/tauri/src-tauri/build.rs b/tauri/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/tauri/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/tauri/src-tauri/capabilities/default.json b/tauri/src-tauri/capabilities/default.json new file mode 100644 index 0000000..da52e70 --- /dev/null +++ b/tauri/src-tauri/capabilities/default.json @@ -0,0 +1,13 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "opener:default", + "sql:default" + ] +} \ No newline at end of file diff --git a/tauri/src-tauri/icons/128x128.png b/tauri/src-tauri/icons/128x128.png new file mode 100644 index 0000000..6d6864f Binary files /dev/null and b/tauri/src-tauri/icons/128x128.png differ diff --git a/tauri/src-tauri/icons/128x128@2x.png b/tauri/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e0e213b Binary files /dev/null and b/tauri/src-tauri/icons/128x128@2x.png differ diff --git a/tauri/src-tauri/icons/32x32.png b/tauri/src-tauri/icons/32x32.png new file mode 100644 index 0000000..a60da76 Binary files /dev/null and b/tauri/src-tauri/icons/32x32.png differ diff --git a/tauri/src-tauri/icons/64x64.png b/tauri/src-tauri/icons/64x64.png new file mode 100644 index 0000000..5f50627 Binary files /dev/null and b/tauri/src-tauri/icons/64x64.png differ diff --git a/tauri/src-tauri/icons/Square107x107Logo.png b/tauri/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..7e3dc9d Binary files /dev/null and b/tauri/src-tauri/icons/Square107x107Logo.png differ diff --git a/tauri/src-tauri/icons/Square142x142Logo.png b/tauri/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..7363bac Binary files /dev/null and b/tauri/src-tauri/icons/Square142x142Logo.png differ diff --git a/tauri/src-tauri/icons/Square150x150Logo.png b/tauri/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..f1d859c Binary files /dev/null and b/tauri/src-tauri/icons/Square150x150Logo.png differ diff --git a/tauri/src-tauri/icons/Square284x284Logo.png b/tauri/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..fa8f4d6 Binary files /dev/null and b/tauri/src-tauri/icons/Square284x284Logo.png differ diff --git a/tauri/src-tauri/icons/Square30x30Logo.png b/tauri/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..ffbe0d3 Binary files /dev/null and b/tauri/src-tauri/icons/Square30x30Logo.png differ diff --git a/tauri/src-tauri/icons/Square310x310Logo.png b/tauri/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..dd3d999 Binary files /dev/null and b/tauri/src-tauri/icons/Square310x310Logo.png differ diff --git a/tauri/src-tauri/icons/Square44x44Logo.png b/tauri/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..b506bea Binary files /dev/null and b/tauri/src-tauri/icons/Square44x44Logo.png differ diff --git a/tauri/src-tauri/icons/Square71x71Logo.png b/tauri/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..819b376 Binary files /dev/null and b/tauri/src-tauri/icons/Square71x71Logo.png differ diff --git a/tauri/src-tauri/icons/Square89x89Logo.png b/tauri/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..0ff496d Binary files /dev/null and b/tauri/src-tauri/icons/Square89x89Logo.png differ diff --git a/tauri/src-tauri/icons/StoreLogo.png b/tauri/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..62db086 Binary files /dev/null and b/tauri/src-tauri/icons/StoreLogo.png differ diff --git a/tauri/src-tauri/icons/icon.icns b/tauri/src-tauri/icons/icon.icns new file mode 100644 index 0000000..1312a42 Binary files /dev/null and b/tauri/src-tauri/icons/icon.icns differ diff --git a/tauri/src-tauri/icons/icon.ico b/tauri/src-tauri/icons/icon.ico new file mode 100644 index 0000000..04542a7 Binary files /dev/null and b/tauri/src-tauri/icons/icon.ico differ diff --git a/tauri/src-tauri/icons/icon.png b/tauri/src-tauri/icons/icon.png new file mode 100644 index 0000000..f5e5f3e Binary files /dev/null and b/tauri/src-tauri/icons/icon.png differ diff --git a/tauri/src-tauri/src/lib.rs b/tauri/src-tauri/src/lib.rs new file mode 100644 index 0000000..d32117c --- /dev/null +++ b/tauri/src-tauri/src/lib.rs @@ -0,0 +1,372 @@ +use serde::{Deserialize, Serialize}; +use sqlx::{sqlite::SqliteConnectOptions, Row, SqlitePool}; +use std::fs; +use std::str::FromStr; +use tauri::{command, AppHandle, Manager, State}; +use tokio::sync::Mutex; + +#[derive(Debug, Serialize, Deserialize)] +pub struct SavedGame { + pub game_id: String, + pub rows: u32, + pub cols: u32, + pub num_mines: u32, + pub is_completed: bool, + pub victory: bool, + pub start_time: Option, + pub end_time: Option, + pub final_board: Option, + pub game_log: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GameStats { + pub total_games: u32, + pub wins: u32, + pub losses: u32, + pub win_rate: f64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GameModeStats { + pub played: u32, + pub victories: u32, + pub best_time: Option, + pub average_time: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AggregateStats { + pub beginner: GameModeStats, + pub intermediate: GameModeStats, + pub expert: GameModeStats, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TimelineGameData { + pub victory: bool, + pub seconds: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TimelineStats { + pub beginner: Vec, + pub intermediate: Vec, + pub expert: Vec, +} + +struct DatabaseState { + pool: Mutex, +} + +#[command] +async fn save_game(state: State<'_, DatabaseState>, game: SavedGame) -> Result<(), String> { + let pool = state.pool.lock().await; + + sqlx::query( + "INSERT OR REPLACE INTO games (game_id, rows, cols, num_mines, is_completed, victory, start_time, end_time, final_board, game_log) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ) + .bind(&game.game_id) + .bind(game.rows as i64) + .bind(game.cols as i64) + .bind(game.num_mines as i64) + .bind(game.is_completed as i32) + .bind(game.victory as i32) + .bind(game.start_time.unwrap_or_default()) + .bind(game.end_time.unwrap_or_default()) + .bind(game.final_board.unwrap_or_default()) + .bind(&game.game_log) + .execute(&*pool) + .await + .map_err(|e| format!("Failed to save game: {e}"))?; + + Ok(()) +} + +async fn get_game_mode_stats( + pool: &SqlitePool, + rows: u32, + cols: u32, + num_mines: u32, +) -> Result { + let row = sqlx::query( + "SELECT + COUNT(*) as played, + SUM(CASE WHEN victory = 1 THEN 1 ELSE 0 END) as victories, + MIN(CASE WHEN victory = 1 AND start_time IS NOT NULL AND end_time IS NOT NULL + THEN (julianday(end_time) - julianday(start_time)) * 86400 + ELSE NULL END) as best_time, + AVG(CASE WHEN victory = 1 AND start_time IS NOT NULL AND end_time IS NOT NULL + THEN (julianday(end_time) - julianday(start_time)) * 86400 + ELSE NULL END) as average_time + FROM games + WHERE is_completed = 1 + AND rows = ? AND cols = ? AND num_mines = ? + AND start_time IS NOT NULL AND end_time IS NOT NULL", + ) + .bind(rows as i64) + .bind(cols as i64) + .bind(num_mines as i64) + .fetch_one(pool) + .await + .map_err(|e| format!("Failed to get game mode stats: {e}"))?; + + let played: i64 = row.try_get("played").unwrap_or(0); + let victories: i64 = row.try_get("victories").unwrap_or(0); + let best_time: Option = row.try_get("best_time").ok(); + let average_time: Option = row.try_get("average_time").ok(); + + Ok(GameModeStats { + played: played as u32, + victories: victories as u32, + best_time: best_time.map(|t| t.floor() as u32), + average_time, + }) +} + +#[command] +async fn get_aggregate_stats(state: State<'_, DatabaseState>) -> Result { + let pool = state.pool.lock().await; + + let beginner = get_game_mode_stats(&pool, 9, 9, 10).await?; + let intermediate = get_game_mode_stats(&pool, 16, 16, 40).await?; + let expert = get_game_mode_stats(&pool, 16, 30, 99).await?; + + Ok(AggregateStats { + beginner, + intermediate, + expert, + }) +} + +async fn get_game_mode_timeline( + pool: &SqlitePool, + rows: u32, + cols: u32, + num_mines: u32, +) -> Result, String> { + let timeline_rows = sqlx::query( + "SELECT + victory, + (julianday(end_time) - julianday(start_time)) * 86400 as seconds + FROM games + WHERE is_completed = 1 + AND rows = ? AND cols = ? AND num_mines = ? + AND start_time IS NOT NULL AND end_time IS NOT NULL + ORDER BY start_time ASC + LIMIT 1000", + ) + .bind(rows as i64) + .bind(cols as i64) + .bind(num_mines as i64) + .fetch_all(pool) + .await + .map_err(|e| format!("Failed to get timeline stats: {e}"))?; + + let mut timeline_data = Vec::new(); + for row in timeline_rows { + let victory: i32 = row.try_get("victory").unwrap_or(0); + let seconds: f64 = row.try_get("seconds").unwrap_or(0.0); + + timeline_data.push(TimelineGameData { + victory: victory != 0, + seconds: seconds.floor() as u32, + }); + } + + Ok(timeline_data) +} + +#[command] +async fn get_timeline_stats(state: State<'_, DatabaseState>) -> Result { + let pool = state.pool.lock().await; + + let beginner = get_game_mode_timeline(&pool, 9, 9, 10).await?; + let intermediate = get_game_mode_timeline(&pool, 16, 16, 40).await?; + let expert = get_game_mode_timeline(&pool, 16, 30, 99).await?; + + Ok(TimelineStats { + beginner, + intermediate, + expert, + }) +} + +#[command] +async fn get_game_stats(state: State<'_, DatabaseState>) -> Result { + let pool = state.pool.lock().await; + + let row = sqlx::query( + "SELECT + COUNT(*) as total_games, + SUM(CASE WHEN victory = 1 THEN 1 ELSE 0 END) as wins, + SUM(CASE WHEN victory = 0 AND is_completed = 1 THEN 1 ELSE 0 END) as losses + FROM games WHERE is_completed = 1", + ) + .fetch_one(&*pool) + .await + .map_err(|e| format!("Failed to get stats: {e}"))?; + + let total_games: i64 = row.try_get("total_games").unwrap_or(0); + let wins: i64 = row.try_get("wins").unwrap_or(0); + let losses: i64 = row.try_get("losses").unwrap_or(0); + let win_rate = if total_games > 0 { + wins as f64 / total_games as f64 + } else { + 0.0 + }; + + Ok(GameStats { + total_games: total_games as u32, + wins: wins as u32, + losses: losses as u32, + win_rate, + }) +} + +#[command] +async fn get_saved_games(state: State<'_, DatabaseState>) -> Result, String> { + let pool = state.pool.lock().await; + + let rows = sqlx::query( + "SELECT game_id, rows, cols, num_mines, is_completed, victory, start_time, end_time, final_board, game_log + FROM games ORDER BY start_time DESC LIMIT 100" + ) + .fetch_all(&*pool) + .await + .map_err(|e| format!("Failed to get saved games: {e}"))?; + + let mut games = Vec::new(); + for row in rows { + let game_log: Option> = row.try_get("game_log").unwrap_or_default(); + + games.push(SavedGame { + game_id: row.try_get("game_id").unwrap_or_default(), + rows: row.try_get::("rows").unwrap_or(0) as u32, + cols: row.try_get::("cols").unwrap_or(0) as u32, + num_mines: row.try_get::("num_mines").unwrap_or(0) as u32, + is_completed: row.try_get::("is_completed").unwrap_or(0) != 0, + victory: row.try_get::("victory").unwrap_or(0) != 0, + start_time: row.try_get("start_time").ok(), + end_time: row.try_get("end_time").ok(), + final_board: row.try_get("final_board").ok(), + game_log, + }); + } + + println!("Returning {} games", games.len()); + for (i, game) in games.iter().enumerate() { + println!( + "Game {}: {} ({}x{}, {} mines)", + i, game.game_id, game.rows, game.cols, game.num_mines + ); + } + + Ok(games) +} + +#[command] +async fn load_game_replay( + state: State<'_, DatabaseState>, + game_id: String, +) -> Result>, String> { + let pool = state.pool.lock().await; + + let row = sqlx::query("SELECT game_log FROM games WHERE game_id = ? AND game_log IS NOT NULL") + .bind(&game_id) + .fetch_optional(&*pool) + .await + .map_err(|e| format!("Failed to get game replay: {e}"))?; + + if let Some(row) = row { + let game_log: Option> = row.try_get("game_log").ok(); + Ok(game_log) + } else { + Ok(None) + } +} + +async fn establish_connection(app_handle: AppHandle) -> SqlitePool { + // Get the app data directory + let app_data_dir = app_handle + .path() + .app_data_dir() + .expect("Failed to get app data directory"); + + // Create the directory if it doesn't exist + if let Err(e) = fs::create_dir_all(&app_data_dir) { + eprintln!("Failed to create app data directory: {e}"); + } + + // Create the full path to the database file + let db_file_path = app_data_dir.join("minesweeper.db"); + + println!("Connecting to database at: {}", db_file_path.display()); + + // Use SqliteConnectOptions to enable creating the database file if it doesn't exist + let options = SqliteConnectOptions::from_str(&format!("sqlite:{}", db_file_path.display())) + .expect("Invalid database URL") + .create_if_missing(true); + + let pool = SqlitePool::connect_with(options) + .await + .expect("Failed to connect to database"); + + // Create the games table if it doesn't exist + sqlx::query( + "CREATE TABLE IF NOT EXISTS games ( + game_id TEXT PRIMARY KEY, + rows INTEGER NOT NULL, + cols INTEGER NOT NULL, + num_mines INTEGER NOT NULL, + is_completed INTEGER NOT NULL DEFAULT 0, + victory INTEGER NOT NULL DEFAULT 0, + start_time TEXT, + end_time TEXT, + final_board TEXT + )", + ) + .execute(&pool) + .await + .expect("Failed to create games table"); + + // Add game_log column if it doesn't exist (migration) + sqlx::query("ALTER TABLE games ADD COLUMN game_log BLOB") + .execute(&pool) + .await + .ok(); // Ignore error if column already exists + + println!("Database initialized successfully!"); + + pool +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_sql::Builder::new().build()) + .plugin(tauri_plugin_opener::init()) + .setup(|app| { + let app_handle = app.handle().clone(); + + // Block on database initialization to ensure it's ready before the app starts + tauri::async_runtime::block_on(async move { + let pool = establish_connection(app_handle.clone()).await; + app_handle.manage(DatabaseState { + pool: Mutex::new(pool), + }); + }); + + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + save_game, + get_game_stats, + get_saved_games, + load_game_replay, + get_aggregate_stats, + get_timeline_stats, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/tauri/src-tauri/src/main.rs b/tauri/src-tauri/src/main.rs new file mode 100644 index 0000000..21cf973 --- /dev/null +++ b/tauri/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + tauri_lib::run() +} diff --git a/tauri/src-tauri/tauri.conf.json b/tauri/src-tauri/tauri.conf.json new file mode 100644 index 0000000..8860a05 --- /dev/null +++ b/tauri/src-tauri/tauri.conf.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Minesweeper", + "version": "0.1.0", + "identifier": "com.hansbaker.minesweeper", + "build": { + "beforeDevCommand": "trunk serve", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "trunk build", + "frontendDist": "../dist" + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "title": "Minesweeper", + "width": 1200, + "height": 900, + "resizable": true, + "minimizable": true, + "maximizable": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/tauri/src/app.rs b/tauri/src/app.rs new file mode 100644 index 0000000..1f97fd5 --- /dev/null +++ b/tauri/src/app.rs @@ -0,0 +1,161 @@ +use crate::components::{ + GameControls, GameStatsModal, SavedGamesList, TauriActiveGame, TauriInactiveGame, + TauriReplayGame, +}; +use chrono::Utc; +use game_ui::{ + button_class, logo, AnimatedBackground, BackgroundToggle, BackgroundVariant, DarkModeToggle, + GameInfo, GameMode, GameSettings, +}; +use leptos::{either::*, prelude::*, server::codee::string::JsonSerdeWasmCodec}; +use leptos_use::storage::use_local_storage; +use minesweeper_lib::game::CompletedMinesweeper; +use std::sync::Arc; + +#[derive(Clone)] +pub struct GameData { + pub game_info: GameInfo, + pub show_replay: bool, + pub completed_game: Option>, +} + +impl GameData { + pub fn new(game_info: GameInfo) -> Self { + Self { + game_info, + show_replay: false, + completed_game: None, + } + } + + pub fn with_completed( + mut game_info: GameInfo, + completed_game: Arc, + ) -> Self { + // Update game_info to mark as completed + game_info.is_completed = true; + game_info.end_time = Some(Utc::now()); + + Self { + game_info, + show_replay: false, + completed_game: Some(completed_game), + } + } +} + +#[component] +pub fn App() -> impl IntoView { + let init_game_settings: GameSettings = GameMode::ClassicBeginner.into(); + let (game_signal, set_game_signal) = signal(GameData::new(GameInfo::new_singleplayer( + String::new(), + init_game_settings.rows as usize, + init_game_settings.cols as usize, + init_game_settings.num_mines as usize, + ))); + + let (show_saved_games, set_show_saved_games) = signal(false); + let (show_stats, set_show_stats) = signal(false); + + let (background_variant, set_background_variant, _) = + use_local_storage::("background-variant"); + + view! { +
+ +
+
+
+
+ + +
+
+

+ {logo()} +

+
+
+ + +
+
+ + + // Game Board +
+
+ {move || { + let game_data = game_signal.get(); + if game_data.show_replay && game_data.completed_game.is_some() { + EitherOf3::A( + view! { }, + ) + } else if game_data.game_info.is_completed { + EitherOf3::B( + view! { }, + ) + } else { + EitherOf3::C( + view! { + + }, + ) + } + }} +
+
+ + // Saved Games Modal + +
+
+ +
+
+
+ + // Stats Modal + +
+
+ +
+
+
+
+
+
+ } +} diff --git a/tauri/src/components.rs b/tauri/src/components.rs new file mode 100644 index 0000000..fc8112e --- /dev/null +++ b/tauri/src/components.rs @@ -0,0 +1,774 @@ +use crate::game::{FrontendGame, GameModeStats, SavedGame}; +use crate::GameData; +use chrono::{DateTime, Utc}; +use game_ui::{ + button_class, game_time_from_start_end, + icons::{Mine, Trophy}, + parse_timeline_stats, player_icon_holder, ActiveGame, ActiveMines, ActiveTimer, GameInfo, + GameMode, GameSettings, GameState, GameStateWidget, GameWidgets, InactiveGame, + InactiveGameStateWidget, InactiveMines, InactiveTimer, PlayerGameModeStats, PlayerStats, + PlayerStatsRow, PlayerStatsTable, ReplayControls, ReplayGame, TimelineStats, + TimelineStatsGraphs, +}; +use leptos::either::Either; +use leptos::prelude::*; +use leptos::task::spawn_local; +use minesweeper_lib::{ + analysis::AnalyzedCell, + board::CompactBoard, + cell::{HiddenCell, PlayerCell}, + client::ClientPlayer, + game::Action as PlayAction, + replay::ReplayAnalysisCell, +}; +use std::sync::Arc; + +#[component] +pub fn GameControls(set_game_signal: WriteSignal) -> impl IntoView { + let (show_custom, set_show_custom) = signal(false); + let (custom_rows, set_custom_rows) = signal(9i64); + let (custom_cols, set_custom_cols) = signal(9i64); + let (custom_mines, set_custom_mines) = signal(10i64); + let (custom_errors, set_custom_errors) = signal::>(Vec::new()); + + let new_game = move |game_settings: GameSettings| { + let game_info = GameInfo::new_singleplayer( + String::new(), + game_settings.rows as usize, + game_settings.cols as usize, + game_settings.num_mines as usize, + ); + set_game_signal(GameData::new(game_info)); + }; + + let new_custom_game = move || { + let rows = custom_rows.get(); + let cols = custom_cols.get(); + let mines = custom_mines.get(); + + let mut errors = Vec::new(); + if rows <= 0 || rows > 100 { + errors.push("Invalid rows. Must be between 1 and 100".to_string()); + } + if cols <= 0 || cols > 100 { + errors.push("Invalid columns. Must be between 1 and 100".to_string()); + } + if mines <= 0 || mines >= rows * cols { + errors.push("Invalid mines. Must be less than total tiles".to_string()); + } + + if errors.is_empty() { + let game_info = GameInfo::new_singleplayer( + String::new(), + rows as usize, + cols as usize, + mines as usize, + ); + set_game_signal(GameData::new(game_info)); + set_show_custom(false); + } else { + set_custom_errors(errors); + } + }; + + view! { +
+ // Game Controls +
+ + + + +
+ + // Custom game inputs + +
+
+
+ + ().unwrap_or(9), + ); + set_custom_errors(Vec::new()); + } + /> +
+
+ + ().unwrap_or(9), + ); + set_custom_errors(Vec::new()); + } + /> +
+
+ + ().unwrap_or(10), + ); + set_custom_errors(Vec::new()); + } + /> +
+
+
} + > +
+ {move || { + custom_errors + .get() + .into_iter() + .map(|err| view! {
{err}
}) + .collect_view() + }} +
+
+ +
+ + + } +} + +#[component] +pub fn TauriActiveGame( + game_info: GameInfo, + set_game_signal: WriteSignal, +) -> impl IntoView { + let (error, set_error) = signal::>(None); + + let game = FrontendGame::new(&game_info, set_error); + let flag_count = game.flag_count; + let sync_time = game.sync_time; + let completed = game.completed; + let victory = game.victory; + let dead = game.dead; + let cells = (*game.cells).clone(); + let num_mines = game_info.num_mines; + + let game_info = StoredValue::new(game_info); + let game = StoredValue::new(game); + + // Watch for game completion + Effect::new(move |prev_completed: Option| { + let is_completed = completed.get(); + if is_completed && prev_completed != Some(true) { + // Game just completed, extract the CompletedMinesweeper + game.with_value(|g| { + if let Some(completed_minesweeper) = g.extract_completed_game() { + let mut new_game_info = game_info.get_value().clone(); + new_game_info.is_completed = true; + new_game_info.is_started = true; + new_game_info.start_time = g.start_time.get(); + new_game_info.end_time = Some(Utc::now()); + new_game_info.final_board = + CompactBoard::from_board(&completed_minesweeper.player_board_final(0)); + new_game_info.players = vec![Some(ClientPlayer { + player_id: 0, + username: String::new(), + dead: dead.get_untracked(), + victory_click: victory.get_untracked(), + top_score: false, + score: 0, + })]; + let completed_game = Arc::new(completed_minesweeper); + let updated_data = + GameData::with_completed(new_game_info.clone(), completed_game.clone()); + set_game_signal(updated_data); + + // Extract signal data before spawn_local to avoid accessing dropped signals + let is_completed = g.completed.get_untracked(); + let victory = g.victory.get_untracked(); + let start_time = g.start_time.get_untracked(); + + // Automatically save the completed game + spawn_local(async move { + if let Err(e) = FrontendGame::save_game_with_completed( + &new_game_info, + is_completed, + victory, + start_time, + Some(completed_game), + ) + .await + { + log::error!("Failed to auto-save game: {e}"); + } else { + log::info!("Game automatically saved"); + } + }); + } + }); + } + is_completed + }); + + let handle_action = move |pa: PlayAction, row: usize, col: usize| { + game.with_value(|game| { + let res = match pa { + PlayAction::Reveal => game.try_reveal(row, col), + PlayAction::Flag => game.try_flag(row, col), + PlayAction::RevealAdjacent => game.try_reveal_adjacent(row, col), + }; + res.unwrap_or_else(|e| (game.err_signal)(Some(format!("{e:?}")))); + }) + }; + + let remake_game = move || { + let game_info = game_info.get_value(); + set_game_signal.set(GameData::new(GameInfo::new_singleplayer( + String::new(), + game_info.rows, + game_info.cols, + game_info.num_mines, + ))); + }; + + view! { + + + + + + +
{error}
+ } +} + +#[component] +pub fn TauriInactiveGame( + game_data: GameData, + set_game_signal: WriteSignal, +) -> impl IntoView { + let game_info = &game_data.game_info; + let game_time = game_time_from_start_end(game_info.start_time, game_info.end_time); + let game_state = game_info + .players + .first() + .flatten() + .map(|p| { + if p.victory_click { + GameState::Victory + } else if p.dead { + GameState::Dead + } else { + GameState::NotStarted + } + }) + .unwrap_or(GameState::NotStarted); + + // Get the final board from completed game or fall back to game_info board + let board = game_info.final_board.to_board(); + + let game_data = StoredValue::new(game_data); + + // Count mines in the final board + let num_mines = board + .rows_iter() + .flatten() + .filter(|&c| matches!(c, PlayerCell::Hidden(HiddenCell::Mine))) + .count(); + + let remake_game = move || { + let game_data = game_data.get_value(); + set_game_signal.set(GameData::new(GameInfo::new_singleplayer( + String::new(), + game_data.game_info.rows, + game_data.game_info.cols, + game_data.game_info.num_mines, + ))); + }; + + let open_replay = move |_| { + set_game_signal.update(|gi| { + gi.show_replay = true; + }); + }; + + view! { + + + + + + +
+ + +
+ } +} + +#[component] +pub fn TauriReplayGame( + game_data: GameData, + set_game_signal: WriteSignal, +) -> impl IntoView { + let game_info = game_data.game_info; + let completed_game = game_data + .completed_game + .as_ref() + .expect("Completed game should exist when showing replay"); + + let game_time = game_time_from_start_end(game_info.start_time, game_info.end_time); + let (_flag_count, set_flag_count) = signal(0); + + let board = game_info.board(); + + // Create cell signals for replay + let (cell_read_signals, cell_write_signals) = board + .rows_iter() + .map(|col| { + col.iter() + .copied() + .map(|pc| signal(ReplayAnalysisCell(pc, None::))) + .collect::<(Vec<_>, Vec<_>)>() + }) + .collect::<(Vec>, Vec>)>(); + + // Clone for ReplayGame before converting to Arc + let cell_read_signals_clone = cell_read_signals.clone(); + + // Create replay with analysis for single player + let replay = completed_game + .replay(Some(0)) // Single player is always player 0 + .expect("Replay should be available for completed game") + .with_analysis(); + + let close_replay = move |_| { + set_game_signal.update(|gi| { + gi.show_replay = false; + }); + }; + + view! { + + + + + + + + } +} + +fn convert_game_mode_stats(stats: &GameModeStats) -> PlayerGameModeStats { + PlayerGameModeStats { + played: stats.played as usize, + victories: stats.victories as usize, + best_time: stats.best_time.map(|t| t as usize).unwrap_or(0), + average_time: stats.average_time.unwrap_or(0.0), + } +} + +fn convert_timeline_data(timeline_data: &[crate::game::TimelineGameData]) -> Vec<(bool, i64)> { + let result: Vec<(bool, i64)> = timeline_data + .iter() + .map(|data| (data.victory, data.seconds as i64)) + .collect(); + + log::info!("Timeline data converted: {result:?}"); + result +} + +#[component] +pub fn GameStatsModal(set_show_stats: WriteSignal) -> impl IntoView { + let player_stats = LocalResource::new(move || async move { + let aggregate_stats = FrontendGame::get_aggregate_stats().await; + aggregate_stats.ok().map(|stats| PlayerStats { + beginner: convert_game_mode_stats(&stats.beginner), + intermediate: convert_game_mode_stats(&stats.intermediate), + expert: convert_game_mode_stats(&stats.expert), + }) + }); + let timeline_stats = LocalResource::new(move || async move { + let tl_stats = FrontendGame::get_timeline_stats().await; + tl_stats.ok().map(|stats| TimelineStats { + beginner: parse_timeline_stats(&convert_timeline_data(&stats.beginner)), + intermediate: parse_timeline_stats(&convert_timeline_data(&stats.intermediate)), + expert: parse_timeline_stats(&convert_timeline_data(&stats.expert)), + }) + }); + + view! { +
+
+

+ "Game Statistics" +

+ +
+ + +
Loading stats...
+
+ } + }> +
+ {move || { + if let Some(stats) = player_stats.get().flatten() { + Either::Left( + view! { +
+ + + + + +

+ "Performance Over Time" +

+
+ +
+
+ }, + ) + } else { + Either::Right( + view! { +
+

+ "No game data available. Play some games to see your statistics!" +

+
+ }, + ) + } + }} +
+ + + } +} + +#[component] +pub fn SavedGamesList( + set_game_signal: WriteSignal, + set_show_saved_games: WriteSignal, +) -> impl IntoView { + let saved_games = + LocalResource::new(move || async move { FrontendGame::get_saved_games().await.ok() }); + + let load_replay = Callback::new(move |game: SavedGame| { + set_show_saved_games(false); // Close the modal after loading + spawn_local(async move { + match FrontendGame::reconstruct_completed_game(&game) { + Ok(Some(completed_game)) => { + let mut game_info = GameInfo::new_singleplayer( + game.game_id.clone(), + game.rows as usize, + game.cols as usize, + game.num_mines as usize, + ); + game_info.is_completed = game.is_completed; + game_info.start_time = game.start_time.as_ref().and_then(|s| s.parse().ok()); + game_info.end_time = game.end_time.as_ref().and_then(|s| s.parse().ok()); + if let Some(board) = game.final_board { + if let Ok(board) = serde_json::from_str(&board) { + game_info.final_board = board; + } + } + + let mut game_data = + GameData::with_completed(game_info, Arc::new(completed_game)); + game_data.show_replay = true; // Automatically show replay when loading from saved games + set_game_signal(game_data); + } + Ok(None) => { + // Could use a toast or notification here + log::error!("No replay data available for this game"); + } + Err(e) => { + // Could use a toast or notification here + log::error!("Failed to load replay: {e}"); + } + } + }); + }); + + view! { +
+
+

"Saved Games"

+ +
+ + +
Loading saved games...
+
+ } + }> + {move || { + saved_games + .get() + .flatten() + .map(|games| { + view! { +
+ {games + .iter() + .map(move |game| { + view! { } + }) + .collect_view()} +
+ +
+

+ "No saved games found. Complete some games to see them here!" +

+
+
+ } + }) + }} + + + } +} + +#[component] +fn SavedGameRow(game: SavedGame, load_replay: Callback) -> impl IntoView { + let has_replay = game.game_log.is_some() && game.final_board.is_some(); + let game_duration = match (&game.start_time, &game.end_time) { + (Some(start), Some(end)) => { + match (start.parse::>(), end.parse::>()) { + (Ok(start_dt), Ok(end_dt)) => { + let duration = end_dt - start_dt; + Some(duration.num_seconds()) + } + _ => None, + } + } + _ => None, + }; + let formatted_start_time = game.start_time.as_ref().and_then(|t| { + t.parse::>() + .ok() + .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string()) + }); + let game_mode_display = match (game.rows, game.cols, game.num_mines) { + (9, 9, 10) => "Beginner".to_string(), + (16, 16, 40) => "Intermediate".to_string(), + (16, 30, 99) => "Expert".to_string(), + _ => format!("Custom({}x{})", game.rows, game.cols), + }; + let result_icon = if game.victory { + // Calculate game duration in seconds + + // Format start time for display + + // Determine game mode + + // Victory/defeat icon + view! { + + + + } + .into_any() + } else { + view! { + + + + } + .into_any() + }; + + let game_stored = StoredValue::new(game); + + view! { +
+
+
+

+ {game_mode_display} +

+ {result_icon} +
+ {game_duration.map(|d| format!("{d}s")).unwrap_or_else(|| "?".to_string())} +
+
+
+ + + + + + "No Replay" + + +
+
+
+ {formatted_start_time + .map(|t| format!("Played: {t}")) + .unwrap_or_else(|| "Date: Unknown".to_string())} +
+
+ } +} diff --git a/tauri/src/game.rs b/tauri/src/game.rs new file mode 100644 index 0000000..5bf15a3 --- /dev/null +++ b/tauri/src/game.rs @@ -0,0 +1,425 @@ +use anyhow::Result; +use chrono::{DateTime, Utc}; +use leptos::prelude::*; +use serde::{Deserialize, Serialize}; +use std::sync::{Arc, RwLock}; +use wasm_bindgen::prelude::*; + +use minesweeper_lib::{ + board::{Board, BoardPoint, CompactBoard}, + cell::{HiddenCell, PlayerCell}, + client::MinesweeperClient, + game::{ + Action as PlayAction, CompletedMinesweeper, Minesweeper, MinesweeperBuilder, + MinesweeperOpts, Play, + }, +}; + +use game_ui::GameInfo; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SavedGame { + pub game_id: String, + pub rows: u32, + pub cols: u32, + pub num_mines: u32, + pub is_completed: bool, + pub victory: bool, + pub start_time: Option, + pub end_time: Option, + pub final_board: Option, + pub game_log: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GameModeStats { + pub played: u32, + pub victories: u32, + pub best_time: Option, + pub average_time: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AggregateStats { + pub beginner: GameModeStats, + pub intermediate: GameModeStats, + pub expert: GameModeStats, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TimelineGameData { + pub victory: bool, + pub seconds: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TimelineStats { + pub beginner: Vec, + pub intermediate: Vec, + pub expert: Vec, +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])] + async fn invoke(cmd: &str, args: JsValue) -> JsValue; +} + +#[derive(Clone)] +pub struct FrontendGame { + pub err_signal: WriteSignal>, + pub completed: ReadSignal, + pub victory: ReadSignal, + pub dead: ReadSignal, + pub flag_count: ReadSignal, + pub sync_time: ReadSignal>, + pub cells: Arc>>>, + pub start_time: ReadSignal>>, + cell_signals: Arc>>>, + set_completed: WriteSignal, + set_victory: WriteSignal, + set_dead: WriteSignal, + set_flag_count: WriteSignal, + set_sync_time: WriteSignal>, + set_start_time: WriteSignal>>, + game: Arc>, + game_client: Arc>, +} + +impl FrontendGame { + pub fn new(game_info: &GameInfo, err_signal: WriteSignal>) -> Self { + let (read_signals, write_signals) = signals_from_board(&game_info.board()); + let (completed, set_completed) = signal(game_info.is_completed); + let (victory, set_victory) = signal(false); + let (dead, set_dead) = signal(false); + let (flag_count, set_flag_count) = signal(0); + let (sync_time, set_sync_time) = signal::>(None); + let (start_time, set_start_time) = signal(game_info.start_time); + let rows = game_info.rows; + let cols = game_info.cols; + let num_mines = game_info.num_mines; + FrontendGame { + cells: read_signals.into(), + cell_signals: write_signals.into(), + err_signal, + completed, + victory, + dead, + set_completed, + set_victory, + set_dead, + flag_count, + set_flag_count, + sync_time, + set_sync_time, + start_time, + set_start_time, + game: Arc::new(RwLock::new( + MinesweeperBuilder::new(MinesweeperOpts { + rows, + cols, + num_mines, + }) + .expect("Should be able to create game") + .with_log() + .with_superclick() + .init(), + )), + game_client: Arc::new(RwLock::new(MinesweeperClient::new(rows, cols))), + } + } + + pub fn try_reveal(&self, row: usize, col: usize) -> Result<()> { + let point = BoardPoint { row, col }; + let play = Play { + player: 0, + action: PlayAction::Reveal, + point, + }; + self.try_play(play) + } + + pub fn try_flag(&self, row: usize, col: usize) -> Result<()> { + let point = BoardPoint { row, col }; + let play = Play { + player: 0, + action: PlayAction::Flag, + point, + }; + self.try_play(play) + } + + pub fn try_reveal_adjacent(&self, row: usize, col: usize) -> Result<()> { + let point = BoardPoint { row, col }; + let play = Play { + player: 0, + action: PlayAction::RevealAdjacent, + point, + }; + let res = self.try_play(play); + match &res { + Ok(_) => self.err_signal.set(None), + Err(e) => self.err_signal.set(Some(format!("{e:?}"))), + }; + res + } + + fn try_play(&self, play: Play) -> Result<()> { + let is_started = self.sync_time.get_untracked().is_some(); + let game_client: &mut MinesweeperClient = &mut (*self.game_client).write().unwrap(); + let game: &mut Minesweeper = &mut (*self.game).write().unwrap(); + let po = game.play(play)?; + let plays = game_client.update(po); + game_client.add_or_update_player(0, game.player_score(0).ok(), game.player_dead(0).ok()); + if !is_started { + self.set_sync_time.set(Some(0)); + self.set_start_time.set(Some(Utc::now())); + } + plays.iter().for_each(|(point, cell)| { + log::debug!("Play outcome: {point:?} {cell:?}"); + self.update_cell(*point, *cell); + }); + let is_victory = game_client.victory; + let is_dead = matches!(game.player_dead(0), Ok(true)); + + if is_victory { + self.set_victory.set(true); + self.set_completed.set(true); + } else if is_dead { + self.set_dead.set(true); + self.set_completed.set(true); + } + Ok(()) + } + + pub fn update_cell(&self, point: BoardPoint, cell: PlayerCell) { + let curr_cell = self.cells[point.row][point.col].get_untracked(); + match (curr_cell, cell) { + (PlayerCell::Hidden(HiddenCell::Flag), PlayerCell::Hidden(HiddenCell::Empty)) => { + self.set_flag_count.update(|nm| *nm -= 1); + log::debug!("Removed flag") + } + (PlayerCell::Hidden(HiddenCell::Flag), PlayerCell::Revealed(_)) => { + self.set_flag_count.update(|nm| *nm -= 1); + log::debug!("Removed flag") + } + (PlayerCell::Hidden(HiddenCell::Empty), PlayerCell::Hidden(HiddenCell::Flag)) => { + self.set_flag_count.update(|nm| *nm += 1); + log::debug!("Added flag") + } + (PlayerCell::Hidden(HiddenCell::Empty), PlayerCell::Revealed(rc)) + if rc.contents.is_mine() => + { + self.set_flag_count.update(|nm| *nm += 1); + log::debug!("Mine revealed") + } + _ => {} + } + self.cell_signals[point.row][point.col].set(cell); + } + + pub fn extract_completed_game(&self) -> Option { + // Check if game is over before attempting to consume it + log::debug!("Checking if game is over before extracting completed game"); + { + let game = self.game.read().ok()?; + if !game.is_over() { + log::debug!("Game is not over, cannot extract completed game"); + return None; + } + } + + log::debug!("Game is over, extracting completed game"); + // Take ownership of the game by replacing it with a dummy game + // This is a bit of a hack, but necessary since complete() requires ownership + let dummy_game = MinesweeperBuilder::new(MinesweeperOpts { + rows: 1, + cols: 2, + num_mines: 1, + }) + .ok()? + .with_log() + .init(); + + log::debug!("Replacing game with dummy game to extract completed game"); + + let mut game_lock = self.game.write().ok()?; + log::debug!("Acquired write lock on game"); + let owned_game = std::mem::replace(&mut *game_lock, dummy_game); + drop(game_lock); + + Some(owned_game.complete()) + } + + pub async fn save_game_with_completed( + game_info: &GameInfo, + is_completed: bool, + victory: bool, + start_time: Option>, + completed_game: Option>, + ) -> Result<(), String> { + if !is_completed { + return Err("Cannot save game that is not completed".to_string()); + } + + let game_id = format!("game_{}", chrono::Utc::now().timestamp()); + let rows = game_info.rows as u32; + let cols = game_info.cols as u32; + let num_mines = game_info.num_mines as u32; + + // Serialize the final board and game log if we have the completed game + let (final_board, game_log) = if let Some(completed) = completed_game { + let board = completed.viewer_board_final(); + let compact_board = CompactBoard::from_board(&board); + let serialized_board = serde_json::to_string(&compact_board) + .map_err(|e| format!("Failed to serialize board: {e}"))?; + + let log = completed.get_log(); + let compressed_log = log.map(|log| minesweeper_lib::game::compress_game_log(&log)); + + (Some(serialized_board), compressed_log) + } else { + (None, None) + }; + + let saved_game = SavedGame { + game_id, + rows, + cols, + num_mines, + is_completed, + victory, + start_time: start_time.map(|t| t.to_rfc3339()), + end_time: Some(chrono::Utc::now().to_rfc3339()), + final_board, + game_log, + }; + + // Wrap the saved_game in an object with "game" key as expected by the Tauri command + let args = js_sys::Object::new(); + js_sys::Reflect::set( + &args, + &JsValue::from_str("game"), + &serde_wasm_bindgen::to_value(&saved_game) + .map_err(|e| format!("Failed to serialize game: {e}"))?, + ) + .map_err(|_| "Failed to create args object")?; + + let result = invoke("save_game", args.into()).await; + + // The result from Tauri is already unwrapped, so we just need to check if it's null (success) or a string (error) + if result.is_null() || result.is_undefined() { + Ok(()) + } else if let Ok(error_msg) = serde_wasm_bindgen::from_value::(result) { + Err(error_msg) + } else { + // If we can't parse as string, assume success + Ok(()) + } + } + + pub async fn get_saved_games() -> Result, String> { + let result = invoke("get_saved_games", JsValue::null()).await; + + // Log the raw result for debugging + log::info!("Raw result from get_saved_games: {result:?}"); + + match serde_wasm_bindgen::from_value::>(result) { + Ok(games) => { + log::info!("Successfully deserialized {} games", games.len()); + Ok(games) + } + Err(e) => { + log::error!("Failed to deserialize games: {e}"); + Err(format!("Failed to deserialize games: {e}")) + } + } + } + + pub fn reconstruct_completed_game( + saved_game: &SavedGame, + ) -> Result, String> { + // Check if we have both the final board and game log + let final_board_json = saved_game + .final_board + .as_ref() + .ok_or("No final board data")?; + let game_log_compressed = saved_game.game_log.as_ref().ok_or("No game log data")?; + + // Deserialize the final board + let compact_board: CompactBoard = serde_json::from_str(final_board_json) + .map_err(|e| format!("Failed to deserialize final board: {e}"))?; + let final_board = compact_board.to_board(); + + // Decompress the game log + let game_log = minesweeper_lib::game::decompress_game_log(game_log_compressed); + + // Create a mock player for single-player games + let player = minesweeper_lib::client::ClientPlayer { + player_id: 0, + username: String::new(), + dead: !saved_game.victory, + victory_click: saved_game.victory, + top_score: false, + score: 0, + }; + + // Reconstruct the CompletedMinesweeper + let completed_game = CompletedMinesweeper::from_log(final_board, game_log, vec![player]); + + Ok(Some(completed_game)) + } + + pub async fn get_aggregate_stats() -> Result { + let result = invoke("get_aggregate_stats", JsValue::null()).await; + + match serde_wasm_bindgen::from_value::(result) { + Ok(stats) => { + log::info!("Successfully fetched aggregate stats"); + Ok(stats) + } + Err(e) => { + log::error!("Failed to fetch aggregate stats: {e}"); + Err(format!("Failed to fetch aggregate stats: {e}")) + } + } + } + + pub async fn get_timeline_stats() -> Result { + let result = invoke("get_timeline_stats", JsValue::null()).await; + + match serde_wasm_bindgen::from_value::(result) { + Ok(stats) => { + log::info!("Successfully fetched timeline stats"); + Ok(stats) + } + Err(e) => { + log::error!("Failed to fetch timeline stats: {e}"); + Err(format!("Failed to fetch timeline stats: {e}")) + } + } + } +} + +#[allow(clippy::type_complexity)] +pub fn signals_from_board( + board: &Board, +) -> ( + Vec>>, + Vec>>, +) { + let mut read_signals = Vec::with_capacity(board.size()); + let mut write_signals = Vec::with_capacity(board.size()); + board.rows_iter().for_each(|cells| { + let mut read_row = Vec::new(); + let mut write_row = Vec::new(); + cells.iter().for_each(|cell| { + let (rs, ws) = signal(*cell); + read_row.push(rs); + write_row.push(ws); + }); + read_signals.push(read_row); + write_signals.push(write_row); + }); + (read_signals, write_signals) +} diff --git a/tauri/src/main.rs b/tauri/src/main.rs new file mode 100644 index 0000000..13aafd1 --- /dev/null +++ b/tauri/src/main.rs @@ -0,0 +1,18 @@ +mod app; +mod components; +mod game; + +use app::*; +use leptos::prelude::*; + +fn main() { + #[cfg(debug_assertions)] + let log_level = log::Level::Debug; + #[cfg(not(debug_assertions))] + let log_level = log::Level::Warn; + _ = console_log::init_with_level(log_level); + console_error_panic_hook::set_once(); + mount_to_body(|| { + view! { } + }) +} diff --git a/tauri/styles.css b/tauri/styles.css new file mode 100644 index 0000000..6a77447 --- /dev/null +++ b/tauri/styles.css @@ -0,0 +1,2267 @@ +/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */ +@layer properties; +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --color-red-100: oklch(93.6% 0.032 17.717); + --color-red-400: oklch(70.4% 0.191 22.216); + --color-red-500: oklch(63.7% 0.237 25.331); + --color-red-600: oklch(57.7% 0.245 27.325); + --color-red-700: oklch(50.5% 0.213 27.518); + --color-red-900: oklch(39.6% 0.141 25.723); + --color-orange-200: oklch(90.1% 0.076 70.697); + --color-yellow-200: oklch(94.5% 0.129 101.54); + --color-yellow-400: oklch(85.2% 0.199 91.936); + --color-lime-200: oklch(93.8% 0.127 124.321); + --color-green-400: oklch(79.2% 0.209 151.711); + --color-green-500: oklch(72.3% 0.219 149.579); + --color-green-600: oklch(62.7% 0.194 149.214); + --color-green-700: oklch(52.7% 0.154 150.069); + --color-green-800: oklch(44.8% 0.119 151.328); + --color-emerald-200: oklch(90.5% 0.093 164.15); + --color-teal-200: oklch(91% 0.096 180.426); + --color-teal-600: oklch(60% 0.118 184.704); + --color-cyan-200: oklch(91.7% 0.08 205.041); + --color-sky-200: oklch(90.1% 0.058 230.902); + --color-sky-400: oklch(74.6% 0.16 232.661); + --color-sky-500: oklch(68.5% 0.169 237.323); + --color-sky-600: oklch(58.8% 0.158 241.966); + --color-sky-700: oklch(50% 0.134 242.749); + --color-sky-800: oklch(44.3% 0.11 240.79); + --color-sky-900: oklch(39.1% 0.09 240.876); + --color-blue-200: oklch(88.2% 0.059 254.128); + --color-blue-400: oklch(70.7% 0.165 254.624); + --color-blue-500: oklch(62.3% 0.214 259.815); + --color-blue-600: oklch(54.6% 0.245 262.881); + --color-blue-700: oklch(48.8% 0.243 264.376); + --color-blue-950: oklch(28.2% 0.091 267.935); + --color-indigo-200: oklch(87% 0.065 274.039); + --color-indigo-700: oklch(45.7% 0.24 277.023); + --color-indigo-800: oklch(39.8% 0.195 277.366); + --color-purple-200: oklch(90.2% 0.063 306.703); + --color-purple-500: oklch(62.7% 0.265 303.9); + --color-purple-600: oklch(55.8% 0.288 302.321); + --color-purple-700: oklch(49.6% 0.265 301.924); + --color-purple-800: oklch(43.8% 0.218 303.724); + --color-fuchsia-200: oklch(90.3% 0.076 319.62); + --color-rose-200: oklch(89.2% 0.058 10.001); + --color-rose-900: oklch(41% 0.159 10.272); + --color-slate-100: oklch(96.8% 0.007 247.896); + --color-slate-200: oklch(92.9% 0.013 255.508); + --color-slate-400: oklch(70.4% 0.04 256.788); + --color-slate-600: oklch(44.6% 0.043 257.281); + --color-slate-700: oklch(37.2% 0.044 257.287); + --color-slate-800: oklch(27.9% 0.041 260.031); + --color-gray-100: oklch(96.7% 0.003 264.542); + --color-gray-200: oklch(92.8% 0.006 264.531); + --color-gray-300: oklch(87.2% 0.01 258.338); + --color-gray-400: oklch(70.7% 0.022 261.325); + --color-gray-500: oklch(55.1% 0.027 264.364); + --color-gray-600: oklch(44.6% 0.03 256.802); + --color-gray-700: oklch(37.3% 0.034 259.733); + --color-gray-800: oklch(27.8% 0.033 256.848); + --color-gray-900: oklch(21% 0.034 264.665); + --color-gray-950: oklch(13% 0.028 261.692); + --color-zinc-200: oklch(92% 0.004 286.32); + --color-zinc-800: oklch(27.4% 0.006 286.033); + --color-zinc-900: oklch(21% 0.006 285.885); + --color-neutral-50: oklch(98.5% 0 0); + --color-neutral-200: oklch(92.2% 0 0); + --color-neutral-300: oklch(87% 0 0); + --color-neutral-400: oklch(70.8% 0 0); + --color-neutral-500: oklch(55.6% 0 0); + --color-neutral-600: oklch(43.9% 0 0); + --color-neutral-700: oklch(37.1% 0 0); + --color-neutral-800: oklch(26.9% 0 0); + --color-neutral-900: oklch(20.5% 0 0); + --color-neutral-950: oklch(14.5% 0 0); + --color-black: #000; + --color-white: #fff; + --spacing: 0.25rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-4xl: 56rem; + --container-6xl: 72rem; + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-4xl: 2.25rem; + --text-4xl--line-height: calc(2.5 / 2.25); + --font-weight-light: 300; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --tracking-wide: 0.025em; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + --default-font-family: var(--font-sans); + --default-mono-font-family: var(--font-mono); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + } + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { + ::placeholder { + color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, currentcolor 50%, transparent); + } + } + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden="until-found"])) { + display: none !important; + } +} +@layer utilities { + .pointer-events-none { + pointer-events: none; + } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + .absolute { + position: absolute; + } + .fixed { + position: fixed; + } + .relative { + position: relative; + } + .static { + position: static; + } + .inset-0 { + inset: calc(var(--spacing) * 0); + } + .top-0 { + top: calc(var(--spacing) * 0); + } + .top-2 { + top: calc(var(--spacing) * 2); + } + .right-0 { + right: calc(var(--spacing) * 0); + } + .right-2 { + right: calc(var(--spacing) * 2); + } + .bottom-0 { + bottom: calc(var(--spacing) * 0); + } + .bottom-2 { + bottom: calc(var(--spacing) * 2); + } + .left-0 { + left: calc(var(--spacing) * 0); + } + .left-2 { + left: calc(var(--spacing) * 2); + } + .z-50 { + z-index: 50; + } + .col-span-2 { + grid-column: span 2 / span 2; + } + .col-span-full { + grid-column: 1 / -1; + } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } + .mx-1 { + margin-inline: calc(var(--spacing) * 1); + } + .mx-4 { + margin-inline: calc(var(--spacing) * 4); + } + .mx-auto { + margin-inline: auto; + } + .my-2 { + margin-block: calc(var(--spacing) * 2); + } + .my-3 { + margin-block: calc(var(--spacing) * 3); + } + .my-4 { + margin-block: calc(var(--spacing) * 4); + } + .my-8 { + margin-block: calc(var(--spacing) * 8); + } + .ms-3 { + margin-inline-start: calc(var(--spacing) * 3); + } + .-mt-5 { + margin-top: calc(var(--spacing) * -5); + } + .mr-4 { + margin-right: calc(var(--spacing) * 4); + } + .mb-1 { + margin-bottom: calc(var(--spacing) * 1); + } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } + .mb-3 { + margin-bottom: calc(var(--spacing) * 3); + } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .mb-6 { + margin-bottom: calc(var(--spacing) * 6); + } + .mb-8 { + margin-bottom: calc(var(--spacing) * 8); + } + .ml-4 { + margin-left: calc(var(--spacing) * 4); + } + .block { + display: block; + } + .contents { + display: contents; + } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .inline { + display: inline; + } + .inline-block { + display: inline-block; + } + .inline-flex { + display: inline-flex; + } + .table { + display: table; + } + .table-cell { + display: table-cell; + } + .table-row { + display: table-row; + } + .h-2 { + height: calc(var(--spacing) * 2); + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-5 { + height: calc(var(--spacing) * 5); + } + .h-6 { + height: calc(var(--spacing) * 6); + } + .h-7 { + height: calc(var(--spacing) * 7); + } + .h-8 { + height: calc(var(--spacing) * 8); + } + .h-9 { + height: calc(var(--spacing) * 9); + } + .h-10 { + height: calc(var(--spacing) * 10); + } + .h-11 { + height: calc(var(--spacing) * 11); + } + .h-12 { + height: calc(var(--spacing) * 12); + } + .h-13 { + height: calc(var(--spacing) * 13); + } + .h-14 { + height: calc(var(--spacing) * 14); + } + .h-15 { + height: calc(var(--spacing) * 15); + } + .h-16 { + height: calc(var(--spacing) * 16); + } + .h-auto { + height: auto; + } + .h-full { + height: 100%; + } + .max-h-10 { + max-height: calc(var(--spacing) * 10); + } + .max-h-10\/12 { + max-height: calc(10/12 * 100%); + } + .max-h-\[80vh\] { + max-height: 80vh; + } + .min-h-screen { + min-height: 100vh; + } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-6 { + width: calc(var(--spacing) * 6); + } + .w-7 { + width: calc(var(--spacing) * 7); + } + .w-8 { + width: calc(var(--spacing) * 8); + } + .w-8\/12 { + width: calc(8/12 * 100%); + } + .w-9 { + width: calc(var(--spacing) * 9); + } + .w-10 { + width: calc(var(--spacing) * 10); + } + .w-10\/12 { + width: calc(10/12 * 100%); + } + .w-11 { + width: calc(var(--spacing) * 11); + } + .w-12 { + width: calc(var(--spacing) * 12); + } + .w-13 { + width: calc(var(--spacing) * 13); + } + .w-14 { + width: calc(var(--spacing) * 14); + } + .w-15 { + width: calc(var(--spacing) * 15); + } + .w-16 { + width: calc(var(--spacing) * 16); + } + .w-80 { + width: calc(var(--spacing) * 80); + } + .w-auto { + width: auto; + } + .w-fit { + width: fit-content; + } + .w-full { + width: 100%; + } + .max-w-4xl { + max-width: var(--container-4xl); + } + .max-w-6xl { + max-width: var(--container-6xl); + } + .max-w-full { + max-width: 100%; + } + .max-w-md { + max-width: var(--container-md); + } + .max-w-sm { + max-width: var(--container-sm); + } + .max-w-xs { + max-width: var(--container-xs); + } + .min-w-\[2rem\] { + min-width: 2rem; + } + .flex-1 { + flex: 1; + } + .flex-none { + flex: none; + } + .flex-shrink { + flex-shrink: 1; + } + .flex-shrink-0 { + flex-shrink: 0; + } + .flex-grow { + flex-grow: 1; + } + .grow { + flex-grow: 1; + } + .table-auto { + table-layout: auto; + } + .border-collapse { + border-collapse: collapse; + } + .border-separate { + border-collapse: separate; + } + .border-spacing-2 { + --tw-border-spacing-x: calc(var(--spacing) * 2); + --tw-border-spacing-y: calc(var(--spacing) * 2); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + } + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); + } + .cursor-default { + cursor: default; + } + .cursor-pointer { + cursor: pointer; + } + .resize { + resize: both; + } + .appearance-none { + appearance: none; + } + .grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .grid-cols-\[2fr_3fr\] { + grid-template-columns: 2fr 3fr; + } + .flex-col { + flex-direction: column; + } + .flex-wrap { + flex-wrap: wrap; + } + .items-center { + align-items: center; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .justify-end { + justify-content: flex-end; + } + .gap-2 { + gap: calc(var(--spacing) * 2); + } + .gap-4 { + gap: calc(var(--spacing) * 4); + } + .gap-6 { + gap: calc(var(--spacing) * 6); + } + .space-y-1 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-2 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-4 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-x-2 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))); + } + } + .space-x-4 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); + } + } + .overflow-auto { + overflow: auto; + } + .overflow-hidden { + overflow: hidden; + } + .overflow-x-auto { + overflow-x: auto; + } + .overflow-y-hidden { + overflow-y: hidden; + } + .rounded { + border-radius: 0.25rem; + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-lg { + border-radius: var(--radius-lg); + } + .rounded-md { + border-radius: var(--radius-md); + } + .rounded-sm { + border-radius: var(--radius-sm); + } + .rounded-l-md { + border-top-left-radius: var(--radius-md); + border-bottom-left-radius: var(--radius-md); + } + .rounded-r-md { + border-top-right-radius: var(--radius-md); + border-bottom-right-radius: var(--radius-md); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-2 { + border-style: var(--tw-border-style); + border-width: 2px; + } + .border-4 { + border-style: var(--tw-border-style); + border-width: 4px; + } + .border-24 { + border-style: var(--tw-border-style); + border-width: 24px; + } + .border-t { + border-top-style: var(--tw-border-style); + border-top-width: 1px; + } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } + .border-solid { + --tw-border-style: solid; + border-style: solid; + } + .border-black { + border-color: var(--color-black); + } + .border-blue-950 { + border-color: var(--color-blue-950); + } + .border-gray-200 { + border-color: var(--color-gray-200); + } + .border-gray-300 { + border-color: var(--color-gray-300); + } + .border-gray-800 { + border-color: var(--color-gray-800); + } + .border-neutral-500 { + border-color: var(--color-neutral-500); + } + .border-red-400 { + border-color: var(--color-red-400); + } + .border-slate-100 { + border-color: var(--color-slate-100); + } + .border-slate-400 { + border-color: var(--color-slate-400); + } + .bg-black { + background-color: var(--color-black); + } + .bg-black\/50 { + background-color: color-mix(in srgb, #000 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-black) 50%, transparent); + } + } + .bg-blue-200 { + background-color: var(--color-blue-200); + } + .bg-blue-400 { + background-color: var(--color-blue-400); + } + .bg-blue-500 { + background-color: var(--color-blue-500); + } + .bg-blue-600 { + background-color: var(--color-blue-600); + } + .bg-cyan-200 { + background-color: var(--color-cyan-200); + } + .bg-emerald-200 { + background-color: var(--color-emerald-200); + } + .bg-fuchsia-200 { + background-color: var(--color-fuchsia-200); + } + .bg-gray-100 { + background-color: var(--color-gray-100); + } + .bg-gray-200 { + background-color: var(--color-gray-200); + } + .bg-gray-300 { + background-color: var(--color-gray-300); + } + .bg-gray-600 { + background-color: var(--color-gray-600); + } + .bg-gray-900 { + background-color: var(--color-gray-900); + } + .bg-green-400 { + background-color: var(--color-green-400); + } + .bg-green-400\/40 { + background-color: color-mix(in srgb, oklch(79.2% 0.209 151.711) 40%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-green-400) 40%, transparent); + } + } + .bg-green-500 { + background-color: var(--color-green-500); + } + .bg-green-600 { + background-color: var(--color-green-600); + } + .bg-green-700 { + background-color: var(--color-green-700); + } + .bg-green-800 { + background-color: var(--color-green-800); + } + .bg-indigo-200 { + background-color: var(--color-indigo-200); + } + .bg-indigo-700 { + background-color: var(--color-indigo-700); + } + .bg-lime-200 { + background-color: var(--color-lime-200); + } + .bg-neutral-200 { + background-color: var(--color-neutral-200); + } + .bg-neutral-200\/50 { + background-color: color-mix(in srgb, oklch(92.2% 0 0) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-neutral-200) 50%, transparent); + } + } + .bg-neutral-200\/80 { + background-color: color-mix(in srgb, oklch(92.2% 0 0) 80%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-neutral-200) 80%, transparent); + } + } + .bg-neutral-500 { + background-color: var(--color-neutral-500); + } + .bg-neutral-500\/50 { + background-color: color-mix(in srgb, oklch(55.6% 0 0) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-neutral-500) 50%, transparent); + } + } + .bg-neutral-600 { + background-color: var(--color-neutral-600); + } + .bg-neutral-700 { + background-color: var(--color-neutral-700); + } + .bg-neutral-800 { + background-color: var(--color-neutral-800); + } + .bg-orange-200 { + background-color: var(--color-orange-200); + } + .bg-purple-200 { + background-color: var(--color-purple-200); + } + .bg-purple-500 { + background-color: var(--color-purple-500); + } + .bg-purple-700 { + background-color: var(--color-purple-700); + } + .bg-red-100 { + background-color: var(--color-red-100); + } + .bg-red-400 { + background-color: var(--color-red-400); + } + .bg-red-400\/40 { + background-color: color-mix(in srgb, oklch(70.4% 0.191 22.216) 40%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-red-400) 40%, transparent); + } + } + .bg-red-500 { + background-color: var(--color-red-500); + } + .bg-red-600 { + background-color: var(--color-red-600); + } + .bg-rose-200 { + background-color: var(--color-rose-200); + } + .bg-sky-200 { + background-color: var(--color-sky-200); + } + .bg-sky-600 { + background-color: var(--color-sky-600); + } + .bg-sky-700 { + background-color: var(--color-sky-700); + } + .bg-slate-100 { + background-color: var(--color-slate-100); + } + .bg-slate-200 { + background-color: var(--color-slate-200); + } + .bg-teal-200 { + background-color: var(--color-teal-200); + } + .bg-transparent { + background-color: transparent; + } + .bg-white { + background-color: var(--color-white); + } + .bg-yellow-200 { + background-color: var(--color-yellow-200); + } + .bg-yellow-400 { + background-color: var(--color-yellow-400); + } + .bg-yellow-400\/40 { + background-color: color-mix(in srgb, oklch(85.2% 0.199 91.936) 40%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-yellow-400) 40%, transparent); + } + } + .bg-zinc-800 { + background-color: var(--color-zinc-800); + } + .fill-black { + fill: var(--color-black); + } + .fill-blue-600 { + fill: var(--color-blue-600); + } + .object-cover { + object-fit: cover; + } + .p-0 { + padding: calc(var(--spacing) * 0); + } + .p-0\.5 { + padding: calc(var(--spacing) * 0.5); + } + .p-1 { + padding: calc(var(--spacing) * 1); + } + .p-2 { + padding: calc(var(--spacing) * 2); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .p-8 { + padding: calc(var(--spacing) * 8); + } + .px-1 { + padding-inline: calc(var(--spacing) * 1); + } + .px-2 { + padding-inline: calc(var(--spacing) * 2); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .px-4 { + padding-inline: calc(var(--spacing) * 4); + } + .py-0 { + padding-block: calc(var(--spacing) * 0); + } + .py-0\.5 { + padding-block: calc(var(--spacing) * 0.5); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .py-3 { + padding-block: calc(var(--spacing) * 3); + } + .py-8 { + padding-block: calc(var(--spacing) * 8); + } + .py-12 { + padding-block: calc(var(--spacing) * 12); + } + .pt-8 { + padding-top: calc(var(--spacing) * 8); + } + .pb-8 { + padding-bottom: calc(var(--spacing) * 8); + } + .text-center { + text-align: center; + } + .text-left { + text-align: left; + } + .align-text-top { + vertical-align: text-top; + } + .align-top { + vertical-align: top; + } + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + .text-4xl { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .leading-none { + --tw-leading: 1; + line-height: 1; + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } + .font-light { + --tw-font-weight: var(--font-weight-light); + font-weight: var(--font-weight-light); + } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .tracking-wide { + --tw-tracking: var(--tracking-wide); + letter-spacing: var(--tracking-wide); + } + .whitespace-nowrap { + white-space: nowrap; + } + .text-black { + color: var(--color-black); + } + .text-blue-600 { + color: var(--color-blue-600); + } + .text-blue-950 { + color: var(--color-blue-950); + } + .text-gray-500 { + color: var(--color-gray-500); + } + .text-gray-600 { + color: var(--color-gray-600); + } + .text-gray-700 { + color: var(--color-gray-700); + } + .text-gray-800 { + color: var(--color-gray-800); + } + .text-gray-900 { + color: var(--color-gray-900); + } + .text-gray-950 { + color: var(--color-gray-950); + } + .text-green-600 { + color: var(--color-green-600); + } + .text-neutral-50 { + color: var(--color-neutral-50); + } + .text-neutral-600 { + color: var(--color-neutral-600); + } + .text-neutral-800 { + color: var(--color-neutral-800); + } + .text-neutral-950 { + color: var(--color-neutral-950); + } + .text-red-500 { + color: var(--color-red-500); + } + .text-red-600 { + color: var(--color-red-600); + } + .text-red-700 { + color: var(--color-red-700); + } + .text-rose-900 { + color: var(--color-rose-900); + } + .text-sky-700 { + color: var(--color-sky-700); + } + .text-slate-600 { + color: var(--color-slate-600); + } + .text-teal-600 { + color: var(--color-teal-600); + } + .text-white { + color: var(--color-white); + } + .text-zinc-200 { + color: var(--color-zinc-200); + } + .underline { + text-decoration-line: underline; + } + .accent-cyan-200 { + accent-color: var(--color-cyan-200); + } + .opacity-20 { + opacity: 20%; + } + .opacity-22 { + opacity: 22%; + } + .opacity-24 { + opacity: 24%; + } + .opacity-25 { + opacity: 25%; + } + .opacity-26 { + opacity: 26%; + } + .opacity-27 { + opacity: 27%; + } + .opacity-28 { + opacity: 28%; + } + .opacity-29 { + opacity: 29%; + } + .opacity-30 { + opacity: 30%; + } + .opacity-32 { + opacity: 32%; + } + .opacity-35 { + opacity: 35%; + } + .opacity-40 { + opacity: 40%; + } + .shadow-lg { + --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-md { + --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-xl { + --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } + .filter { + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .transition { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-all { + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .duration-500 { + --tw-duration: 500ms; + transition-duration: 500ms; + } + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } + .select-none { + -webkit-user-select: none; + user-select: none; + } + .peer-checked\:bg-gray-400 { + &:is(:where(.peer):checked ~ *) { + background-color: var(--color-gray-400); + } + } + .peer-disabled\:cursor-not-allowed { + &:is(:where(.peer):disabled ~ *) { + cursor: not-allowed; + } + } + .peer-disabled\:opacity-70 { + &:is(:where(.peer):disabled ~ *) { + opacity: 70%; + } + } + .after\:absolute { + &::after { + content: var(--tw-content); + position: absolute; + } + } + .after\:start-\[2px\] { + &::after { + content: var(--tw-content); + inset-inline-start: 2px; + } + } + .after\:top-0\.5 { + &::after { + content: var(--tw-content); + top: calc(var(--spacing) * 0.5); + } + } + .after\:h-5 { + &::after { + content: var(--tw-content); + height: calc(var(--spacing) * 5); + } + } + .after\:w-5 { + &::after { + content: var(--tw-content); + width: calc(var(--spacing) * 5); + } + } + .after\:rounded-full { + &::after { + content: var(--tw-content); + border-radius: calc(infinity * 1px); + } + } + .after\:border { + &::after { + content: var(--tw-content); + border-style: var(--tw-border-style); + border-width: 1px; + } + } + .after\:border-gray-300 { + &::after { + content: var(--tw-content); + border-color: var(--color-gray-300); + } + } + .after\:bg-cyan-200 { + &::after { + content: var(--tw-content); + background-color: var(--color-cyan-200); + } + } + .after\:transition-all { + &::after { + content: var(--tw-content); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + } + .after\:content-\[\'\'\] { + &::after { + content: var(--tw-content); + --tw-content: ''; + content: var(--tw-content); + } + } + .peer-checked\:after\:translate-x-full { + &:is(:where(.peer):checked ~ *) { + &::after { + content: var(--tw-content); + --tw-translate-x: 100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + } + } + .peer-checked\:after\:border-gray-600 { + &:is(:where(.peer):checked ~ *) { + &::after { + content: var(--tw-content); + border-color: var(--color-gray-600); + } + } + } + .hover\:bg-blue-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-600); + } + } + } + .hover\:bg-blue-700\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(48.8% 0.243 264.376) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-blue-700) 90%, transparent); + } + } + } + } + .hover\:bg-gray-200 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-200); + } + } + } + .hover\:bg-green-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-600); + } + } + } + .hover\:bg-green-800\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(44.8% 0.119 151.328) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-green-800) 90%, transparent); + } + } + } + } + .hover\:bg-indigo-800\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(39.8% 0.195 277.366) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-indigo-800) 90%, transparent); + } + } + } + } + .hover\:bg-neutral-300 { + &:hover { + @media (hover: hover) { + background-color: var(--color-neutral-300); + } + } + } + .hover\:bg-neutral-400\/50 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(70.8% 0 0) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-neutral-400) 50%, transparent); + } + } + } + } + .hover\:bg-neutral-600\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(43.9% 0 0) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-neutral-600) 90%, transparent); + } + } + } + } + .hover\:bg-neutral-800\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(26.9% 0 0) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-neutral-800) 90%, transparent); + } + } + } + } + .hover\:bg-purple-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-purple-600); + } + } + } + .hover\:bg-purple-800\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(43.8% 0.218 303.724) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-purple-800) 90%, transparent); + } + } + } + } + .hover\:bg-red-500\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-red-500) 90%, transparent); + } + } + } + } + .hover\:bg-red-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-600); + } + } + } + .hover\:bg-red-700\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(50.5% 0.213 27.518) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-red-700) 90%, transparent); + } + } + } + } + .hover\:bg-sky-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-sky-600); + } + } + } + .hover\:bg-sky-800\/30 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(44.3% 0.11 240.79) 30%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-sky-800) 30%, transparent); + } + } + } + } + .hover\:bg-sky-900\/90 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, oklch(39.1% 0.09 240.876) 90%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-sky-900) 90%, transparent); + } + } + } + } + .hover\:bg-zinc-900 { + &:hover { + @media (hover: hover) { + background-color: var(--color-zinc-900); + } + } + } + .hover\:text-sky-900 { + &:hover { + @media (hover: hover) { + color: var(--color-sky-900); + } + } + } + .hover\:text-white { + &:hover { + @media (hover: hover) { + color: var(--color-white); + } + } + } + .focus\:ring-2 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-blue-500 { + &:focus { + --tw-ring-color: var(--color-blue-500); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } + .disabled\:pointer-events-none { + &:disabled { + pointer-events: none; + } + } + .disabled\:cursor-default { + &:disabled { + cursor: default; + } + } + .disabled\:cursor-not-allowed { + &:disabled { + cursor: not-allowed; + } + } + .disabled\:bg-gray-300 { + &:disabled { + background-color: var(--color-gray-300); + } + } + .disabled\:opacity-50 { + &:disabled { + opacity: 50%; + } + } + .sm\:right-8 { + @media (width >= 40rem) { + right: calc(var(--spacing) * 8); + } + } + .sm\:col-start-2 { + @media (width >= 40rem) { + grid-column-start: 2; + } + } + .sm\:grid-cols-3 { + @media (width >= 40rem) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + } + .md\:grid-cols-3 { + @media (width >= 48rem) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + } + .md\:grid-cols-4 { + @media (width >= 48rem) { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + } + .md\:justify-end { + @media (width >= 48rem) { + justify-content: flex-end; + } + } + .md\:justify-start { + @media (width >= 48rem) { + justify-content: flex-start; + } + } + .xl\:col-start-2 { + @media (width >= 80rem) { + grid-column-start: 2; + } + } + .xl\:grid-cols-4 { + @media (width >= 80rem) { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + } + .rtl\:peer-checked\:after\:-translate-x-full { + &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) { + &:is(:where(.peer):checked ~ *) { + &::after { + content: var(--tw-content); + --tw-translate-x: -100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + } + } + } + .dark\:border-gray-600 { + &:where(.dark, .dark *) { + border-color: var(--color-gray-600); + } + } + .dark\:border-gray-700 { + &:where(.dark, .dark *) { + border-color: var(--color-gray-700); + } + } + .dark\:border-red-700 { + &:where(.dark, .dark *) { + border-color: var(--color-red-700); + } + } + .dark\:border-slate-600 { + &:where(.dark, .dark *) { + border-color: var(--color-slate-600); + } + } + .dark\:border-slate-700 { + &:where(.dark, .dark *) { + border-color: var(--color-slate-700); + } + } + .dark\:bg-blue-600 { + &:where(.dark, .dark *) { + background-color: var(--color-blue-600); + } + } + .dark\:bg-gray-700 { + &:where(.dark, .dark *) { + background-color: var(--color-gray-700); + } + } + .dark\:bg-gray-800 { + &:where(.dark, .dark *) { + background-color: var(--color-gray-800); + } + } + .dark\:bg-gray-900 { + &:where(.dark, .dark *) { + background-color: var(--color-gray-900); + } + } + .dark\:bg-gray-950\/50 { + &:where(.dark, .dark *) { + background-color: color-mix(in srgb, oklch(13% 0.028 261.692) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-gray-950) 50%, transparent); + } + } + } + .dark\:bg-green-600 { + &:where(.dark, .dark *) { + background-color: var(--color-green-600); + } + } + .dark\:bg-neutral-800\/80 { + &:where(.dark, .dark *) { + background-color: color-mix(in srgb, oklch(26.9% 0 0) 80%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-neutral-800) 80%, transparent); + } + } + } + .dark\:bg-neutral-900 { + &:where(.dark, .dark *) { + background-color: var(--color-neutral-900); + } + } + .dark\:bg-purple-600 { + &:where(.dark, .dark *) { + background-color: var(--color-purple-600); + } + } + .dark\:bg-red-600 { + &:where(.dark, .dark *) { + background-color: var(--color-red-600); + } + } + .dark\:bg-red-900\/20 { + &:where(.dark, .dark *) { + background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 20%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-red-900) 20%, transparent); + } + } + } + .dark\:bg-slate-800 { + &:where(.dark, .dark *) { + background-color: var(--color-slate-800); + } + } + .dark\:fill-white { + &:where(.dark, .dark *) { + fill: var(--color-white); + } + } + .dark\:text-blue-400 { + &:where(.dark, .dark *) { + color: var(--color-blue-400); + } + } + .dark\:text-gray-100 { + &:where(.dark, .dark *) { + color: var(--color-gray-100); + } + } + .dark\:text-gray-200 { + &:where(.dark, .dark *) { + color: var(--color-gray-200); + } + } + .dark\:text-gray-300 { + &:where(.dark, .dark *) { + color: var(--color-gray-300); + } + } + .dark\:text-gray-400 { + &:where(.dark, .dark *) { + color: var(--color-gray-400); + } + } + .dark\:text-green-400 { + &:where(.dark, .dark *) { + color: var(--color-green-400); + } + } + .dark\:text-neutral-50 { + &:where(.dark, .dark *) { + color: var(--color-neutral-50); + } + } + .dark\:text-red-400 { + &:where(.dark, .dark *) { + color: var(--color-red-400); + } + } + .dark\:text-sky-500 { + &:where(.dark, .dark *) { + color: var(--color-sky-500); + } + } + .dark\:text-slate-400 { + &:where(.dark, .dark *) { + color: var(--color-slate-400); + } + } + .dark\:text-white { + &:where(.dark, .dark *) { + color: var(--color-white); + } + } + .dark\:opacity-40 { + &:where(.dark, .dark *) { + opacity: 40%; + } + } + .peer-checked\:dark\:bg-gray-500 { + &:is(:where(.peer):checked ~ *) { + &:where(.dark, .dark *) { + background-color: var(--color-gray-500); + } + } + } + .dark\:hover\:bg-blue-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-700); + } + } + } + } + .dark\:hover\:bg-gray-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-700); + } + } + } + } + .dark\:hover\:bg-green-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-700); + } + } + } + } + .dark\:hover\:bg-purple-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-purple-700); + } + } + } + } + .dark\:hover\:bg-red-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-700); + } + } + } + } + .dark\:hover\:bg-sky-700 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + background-color: var(--color-sky-700); + } + } + } + } + .dark\:hover\:text-sky-400 { + &:where(.dark, .dark *) { + &:hover { + @media (hover: hover) { + color: var(--color-sky-400); + } + } + } + } + .\[\&\>\*\:nth-child\(1\)\]\:justify-self-start { + &>*:nth-child(1) { + justify-self: flex-start; + } + } + .\[\&\>\*\:nth-child\(2\)\]\:justify-self-center { + &>*:nth-child(2) { + justify-self: center; + } + } + .\[\&\>\*\:nth-child\(3\)\]\:justify-self-end { + &>*:nth-child(3) { + justify-self: flex-end; + } + } +} +.border-24 { + border-width: 24px; +} +.border-groove { + border-style: groove; +} +.tooltip { + visibility: hidden; + position: absolute; +} +.has-tooltip:hover .tooltip { + visibility: visible; + z-index: 50; +} +.show-tooltip .tooltip { + visibility: visible; + z-index: 50; +} +@keyframes float-slow { + 0%, 100% { + transform: translateY(0px) translateX(0px); + } + 25% { + transform: translateY(-10px) translateX(5px); + } + 50% { + transform: translateY(-5px) translateX(-10px); + } + 75% { + transform: translateY(-15px) translateX(8px); + } +} +@keyframes float-medium { + 0%, 100% { + transform: translateY(0px) translateX(0px); + } + 33% { + transform: translateY(-8px) translateX(-6px); + } + 66% { + transform: translateY(-12px) translateX(4px); + } +} +@keyframes float-fast { + 0%, 100% { + transform: translateY(0px) translateX(0px); + } + 50% { + transform: translateY(-6px) translateX(-8px); + } +} +.animate-float-slow { + animation: float-slow 8s ease-in-out infinite; +} +.animate-float-medium { + animation: float-medium 6s ease-in-out infinite; +} +.animate-float-fast { + animation: float-fast 4s ease-in-out infinite; +} +.bg-grid-pattern { + background-image: linear-gradient(rgba(0, 0, 0, 0.30) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 0, 0, 0.30) 1px, transparent 1px); + background-size: 28px 28px; +} +.dark { + .bg-grid-pattern { + background-image: linear-gradient(rgba(255, 255, 255, 0.30) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.30) 1px, transparent 1px); + background-size: 28px 28px; + } +} +.bg-gradient-radial { + background: radial-gradient(circle at center, var(--tw-gradient-stops)); +} +@keyframes gradient-shift-1 { + 0%, 100% { + background: radial-gradient(ellipse at top left, rgba(59, 130, 246, 0.25) 0%, rgba(147, 197, 253, 0.15) 50%, transparent 70%); + } + 50% { + background: radial-gradient(ellipse at top left, rgba(37, 99, 235, 0.30) 0%, rgba(125, 211, 252, 0.18) 50%, transparent 70%); + } +} +@keyframes gradient-shift-2 { + 0%, 100% { + background: radial-gradient(ellipse at bottom right, rgba(34, 197, 94, 0.20) 0%, rgba(74, 222, 128, 0.12) 50%, transparent 70%); + } + 50% { + background: radial-gradient(ellipse at bottom right, rgba(22, 163, 74, 0.25) 0%, rgba(52, 211, 153, 0.15) 50%, transparent 70%); + } +} +@keyframes gradient-shift-3 { + 0%, 100% { + background: radial-gradient(circle at center, rgba(168, 85, 247, 0.15) 0%, rgba(196, 181, 253, 0.08) 40%, transparent 60%); + } + 50% { + background: radial-gradient(circle at center, rgba(147, 51, 234, 0.18) 0%, rgba(167, 139, 250, 0.12) 40%, transparent 60%); + } +} +@keyframes gradient-shift-dark-1 { + 0%, 100% { + background: radial-gradient(ellipse at top left, rgba(59, 130, 246, 0.12) 0%, rgba(147, 51, 234, 0.07) 50%, transparent 70%); + } + 50% { + background: radial-gradient(ellipse at top left, rgba(37, 99, 235, 0.15) 0%, rgba(124, 58, 237, 0.09) 50%, transparent 70%); + } +} +@keyframes gradient-shift-dark-2 { + 0%, 100% { + background: radial-gradient(ellipse at bottom right, rgba(6, 182, 212, 0.10) 0%, rgba(34, 197, 94, 0.06) 50%, transparent 70%); + } + 50% { + background: radial-gradient(ellipse at bottom right, rgba(8, 145, 178, 0.12) 0%, rgba(22, 163, 74, 0.07) 50%, transparent 70%); + } +} +@keyframes gradient-shift-dark-3 { + 0%, 100% { + background: radial-gradient(circle at center, rgba(168, 85, 247, 0.07) 0%, rgba(6, 182, 212, 0.04) 40%, transparent 60%); + } + 50% { + background: radial-gradient(circle at center, rgba(147, 51, 234, 0.09) 0%, rgba(8, 145, 178, 0.05) 40%, transparent 60%); + } +} +.gradient-layer-1 { + animation: gradient-shift-1 12s ease-in-out infinite; +} +.gradient-layer-2 { + animation: gradient-shift-2 15s ease-in-out infinite reverse; +} +.gradient-layer-3 { + animation: gradient-shift-3 18s ease-in-out infinite; +} +.dark .gradient-layer-1 { + animation: gradient-shift-dark-1 12s ease-in-out infinite; +} +.dark .gradient-layer-2 { + animation: gradient-shift-dark-2 15s ease-in-out infinite reverse; +} +.dark .gradient-layer-3 { + animation: gradient-shift-dark-3 18s ease-in-out infinite; +} +.floating-mine { + filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.2)); + transition: filter 0.3s ease; +} +.dark .floating-mine { + filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.41)) drop-shadow(0 0 8px rgba(255, 255, 255, 0.20)); +} +@property --tw-border-spacing-x { + syntax: ""; + inherits: false; + initial-value: 0; +} +@property --tw-border-spacing-y { + syntax: ""; + inherits: false; + initial-value: 0; +} +@property --tw-rotate-x { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; +} +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-space-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-leading { + syntax: "*"; + inherits: false; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-tracking { + syntax: "*"; + inherits: false; +} +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { + syntax: "*"; + inherits: false; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-blur { + syntax: "*"; + inherits: false; +} +@property --tw-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-invert { + syntax: "*"; + inherits: false; +} +@property --tw-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-sepia { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-drop-shadow-size { + syntax: "*"; + inherits: false; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} +@property --tw-ease { + syntax: "*"; + inherits: false; +} +@property --tw-content { + syntax: "*"; + initial-value: ""; + inherits: false; +} +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; + --tw-space-y-reverse: 0; + --tw-space-x-reverse: 0; + --tw-border-style: solid; + --tw-leading: initial; + --tw-font-weight: initial; + --tw-tracking: initial; + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + --tw-outline-style: solid; + --tw-blur: initial; + --tw-brightness: initial; + --tw-contrast: initial; + --tw-grayscale: initial; + --tw-hue-rotate: initial; + --tw-invert: initial; + --tw-opacity: initial; + --tw-saturate: initial; + --tw-sepia: initial; + --tw-drop-shadow: initial; + --tw-drop-shadow-color: initial; + --tw-drop-shadow-alpha: 100%; + --tw-drop-shadow-size: initial; + --tw-duration: initial; + --tw-ease: initial; + --tw-content: ""; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-translate-z: 0; + } + } +} diff --git a/web/src/app.rs b/web/src/app.rs index f7aee8e..b591ab0 100644 --- a/web/src/app.rs +++ b/web/src/app.rs @@ -1,5 +1,4 @@ mod auth; -mod background; mod error_template; mod footer; mod header; @@ -17,9 +16,9 @@ use leptos_use::storage::{use_local_storage_with_options, UseStorageOptions}; use wasm_bindgen::JsValue; use auth::{get_frontend_user, Login, Logout}; -use background::{AnimatedBackground, BackgroundVariant}; use error_template::{AppError, ErrorTemplate}; use footer::Footer; +use game_ui::background::{AnimatedBackground, BackgroundVariant}; use header::Header; use home::HomeView; use info::ControlsInfo; diff --git a/web/src/app/header.rs b/web/src/app/header.rs index 0e9922b..88a12bc 100644 --- a/web/src/app/header.rs +++ b/web/src/app/header.rs @@ -1,12 +1,10 @@ use leptos::either::*; use leptos::prelude::*; use leptos_router::components::*; -use leptos_use::{use_color_mode, ColorMode, UseColorModeReturn}; use web_auth::FrontendUser; -use crate::app::background::{BackgroundToggle, BackgroundVariant}; -use game_ui::logo; +use game_ui::{logo, DarkModeToggle, BackgroundToggle, BackgroundVariant}; #[component] pub fn Header( @@ -66,39 +64,3 @@ pub fn Header( } } -#[component] -pub fn DarkModeToggle() -> impl IntoView { - let UseColorModeReturn { mode, set_mode, .. } = use_color_mode(); - view! { - - } -} diff --git a/web/src/app/minesweeper/client.rs b/web/src/app/minesweeper/client.rs index c8cc194..1c1a9f6 100644 --- a/web/src/app/minesweeper/client.rs +++ b/web/src/app/minesweeper/client.rs @@ -175,7 +175,7 @@ impl FrontendGame { log::debug!("Play outcome: {point:?} {cell:?}"); self.update_cell(*point, *cell); }); - if game.game_over { + if game.victory { self.set_completed.set(true); } Ok(()) diff --git a/web/src/app/minesweeper/entry.rs b/web/src/app/minesweeper/entry.rs index b2b6407..c446808 100644 --- a/web/src/app/minesweeper/entry.rs +++ b/web/src/app/minesweeper/entry.rs @@ -255,27 +255,25 @@ pub fn JoinOrCreateGame() -> impl IntoView { } #[component] -pub fn ReCreateGame(game_settings: GameSettings) -> impl IntoView { +pub fn ReCreateGameButton(game_settings: GameSettings) -> impl IntoView { let new_game = ServerAction::::new(); view! { -
- - - - - - - -
+ + + + + + + } } diff --git a/web/src/app/minesweeper/game.rs b/web/src/app/minesweeper/game.rs index 0c8a5a5..543bcac 100644 --- a/web/src/app/minesweeper/game.rs +++ b/web/src/app/minesweeper/game.rs @@ -13,7 +13,8 @@ use minesweeper_lib::{ }; use super::{ - client::FrontendGame, entry::ReCreateGame, players::PlayerButtons, replay::OpenReplayButton, + client::FrontendGame, entry::ReCreateGameButton, players::PlayerButtons, + replay::OpenReplayButton, }; use game_ui::*; @@ -402,8 +403,10 @@ fn WebInactiveGame(game_info: GameInfo) -> impl IntoView { - - +
+ + +
} } @@ -415,7 +418,6 @@ fn WebReplayGame(replay_data: GameInfoWithLog) -> impl IntoView { let game_time = game_time_from_start_end(game_info.start_time, game_info.end_time); let (top_score, _) = signal(None); let (flag_count, set_flag_count) = signal(0); - let (replay_started, set_replay_started) = signal(false); let board = game_info.board(); let players = game_info.players; @@ -440,13 +442,10 @@ fn WebReplayGame(replay_data: GameInfoWithLog) -> impl IntoView { let completed_minesweeper = CompletedMinesweeper::from_log(board, full_log, players.into_iter().flatten().collect()); - let replay_data_stored = StoredValue::new(( - Arc::new(completed_minesweeper), - player_num, - Arc::new(cell_read_signals.clone()), - Arc::new(cell_write_signals), - Arc::new(player_write_signals), - )); + let replay = completed_minesweeper + .replay(player_num.map(|p| p.into())) + .expect("We are guaranteed log is not None") + .with_analysis(); view! { @@ -457,53 +456,14 @@ fn WebReplayGame(replay_data: GameInfoWithLog) -> impl IntoView { - - - "Start Replay" - - } - } - > - {move || { - replay_data_stored - .with_value(| - ( - completed_minesweeper, - player_num, - cell_read_signals, - cell_write_signals, - player_write_signals, - )| - { - let replay = completed_minesweeper - .replay(player_num.map(|p| p.into())) - .expect("We are guaranteed log is not None") - .with_analysis(); - view! { - - } - }) - }} - + + } } diff --git a/web/src/app/minesweeper/replay.rs b/web/src/app/minesweeper/replay.rs index 03226d4..7b03dfb 100644 --- a/web/src/app/minesweeper/replay.rs +++ b/web/src/app/minesweeper/replay.rs @@ -6,16 +6,14 @@ use game_ui::button_class; #[component] pub fn OpenReplayButton() -> impl IntoView { view! { - + + "Open Replay" + } }