AVM Analyzer initial commit.
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..ca8f068
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+**/target
+**/.vscode
+**/.git
+**/dist
+**/Dockerfile*
+**/*.sh
+!tools/avm_analyzer/build_avm.sh
diff --git a/tools/avm_analyzer/Cargo.lock b/tools/avm_analyzer/Cargo.lock
new file mode 100644
index 0000000..2147d78
--- /dev/null
+++ b/tools/avm_analyzer/Cargo.lock
@@ -0,0 +1,4625 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ab_glyph"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225"
+dependencies = [
+ "ab_glyph_rasterizer",
+ "owned_ttf_parser",
+]
+
+[[package]]
+name = "ab_glyph_rasterizer"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
+
+[[package]]
+name = "accesskit"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb10ed32c63247e4e39a8f42e8e30fb9442fbf7878c8e4a9849e7e381619bea"
+dependencies = [
+ "enumn",
+ "serde",
+]
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-activity"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39b801912a977c3fd52d80511fe1c0c8480c6f957f21ae2ce1b92ffe970cf4b9"
+dependencies = [
+ "android-properties",
+ "bitflags 2.4.2",
+ "cc",
+ "cesu8",
+ "jni",
+ "jni-sys",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "num_enum",
+ "thiserror",
+]
+
+[[package]]
+name = "android-properties"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
+
+[[package]]
+name = "anstream"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+
+[[package]]
+name = "arboard"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08"
+dependencies = [
+ "clipboard-win",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "parking_lot",
+ "thiserror",
+ "winapi",
+ "x11rb 0.12.0",
+]
+
+[[package]]
+name = "as-raw-xcb-connection"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
+
+[[package]]
+name = "async-channel"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 2.5.3",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 4.0.3",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-fs"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd1f344136bad34df1f83a47f3fd7f2ab85d75cb8a940af4ccf6d482a84ea01b"
+dependencies = [
+ "async-lock 3.3.0",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb41eb19024a91746eba0773aa5e16036045bbf45733766661099e182ea6a744"
+dependencies = [
+ "async-lock 3.3.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
+dependencies = [
+ "event-listener 4.0.3",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15c1cd5d253ecac3d3cf15e390fd96bd92a13b1d14497d81abf077304794fb04"
+dependencies = [
+ "async-channel 2.1.1",
+ "async-io",
+ "async-lock 3.3.0",
+ "async-signal",
+ "blocking",
+ "cfg-if",
+ "event-listener 4.0.3",
+ "futures-lite",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
+dependencies = [
+ "async-io",
+ "async-lock 2.8.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
+
+[[package]]
+name = "async-trait"
+version = "0.1.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "atomic_enum"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6227a8d6fdb862bcb100c4314d0d9579e5cd73fa6df31a2e6f6e1acd3c5f1207"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "avm-analyzer-app"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "avm-analyzer-common",
+ "avm-stats",
+ "base64",
+ "bincode",
+ "console_error_panic_hook",
+ "convert_case",
+ "eframe",
+ "egui",
+ "egui_dock",
+ "egui_extras",
+ "egui_plot",
+ "ehttp",
+ "ezsockets",
+ "futures",
+ "getrandom",
+ "image",
+ "itertools 0.10.5",
+ "js-sys",
+ "log",
+ "mime",
+ "mime_guess",
+ "once_cell",
+ "ordered-float",
+ "poll-promise",
+ "prost",
+ "rand",
+ "re_memory",
+ "rfd",
+ "ron",
+ "serde",
+ "serde_json",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "web-time 1.0.0",
+ "weezl",
+ "y4m",
+ "zip",
+]
+
+[[package]]
+name = "avm-analyzer-common"
+version = "0.1.0"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "avm-analyzer-server"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-fs",
+ "async-process",
+ "avm-analyzer-common",
+ "avm-stats",
+ "axum",
+ "clap",
+ "futures-lite",
+ "image",
+ "prost",
+ "prost-types",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tower",
+ "tower-http",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "avm-stats"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "emath 0.25.0",
+ "itertools 0.10.5",
+ "log",
+ "num",
+ "num-derive",
+ "num-traits",
+ "once_cell",
+ "ordered-float",
+ "prost",
+ "prost-build",
+ "prost-types",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "axum"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "bytes",
+ "futures-util",
+ "http 1.0.0",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "multer",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 1.0.0",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit_field"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7"
+dependencies = [
+ "objc-sys",
+]
+
+[[package]]
+name = "block2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68"
+dependencies = [
+ "block-sys",
+ "objc2",
+]
+
+[[package]]
+name = "blocking"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
+dependencies = [
+ "async-channel 2.1.1",
+ "async-lock 3.3.0",
+ "async-task",
+ "fastrand",
+ "futures-io",
+ "futures-lite",
+ "piper",
+ "tracing",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
+dependencies = [
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "calloop"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298"
+dependencies = [
+ "bitflags 2.4.2",
+ "log",
+ "polling",
+ "rustix",
+ "slab",
+ "thiserror",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
+dependencies = [
+ "calloop",
+ "rustix",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-expr"
+version = "0.15.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
+[[package]]
+name = "cgl"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "clap"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
+name = "clipboard-win"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
+dependencies = [
+ "error-code",
+ "str-buf",
+ "winapi",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cursor-icon"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "directories-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+
+[[package]]
+name = "duplicate"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+]
+
+[[package]]
+name = "ecolor"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57539aabcdbb733b6806ef421b66dec158dc1582107ad6d51913db3600303354"
+dependencies = [
+ "bytemuck",
+ "serde",
+]
+
+[[package]]
+name = "eframe"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79c00143a1d564cf27570234c9a199cbe75dc3d43a135510fb2b93406a87ee8e"
+dependencies = [
+ "bytemuck",
+ "cocoa",
+ "directories-next",
+ "egui",
+ "egui-winit",
+ "egui_glow",
+ "glow",
+ "glutin",
+ "glutin-winit",
+ "image",
+ "js-sys",
+ "log",
+ "objc",
+ "parking_lot",
+ "percent-encoding",
+ "raw-window-handle",
+ "ron",
+ "serde",
+ "static_assertions",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "winapi",
+ "winit",
+]
+
+[[package]]
+name = "egui"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0bf640ed7f3bf3d14ebf00d73bacc09c886443ee84ca6494bde37953012c9e3"
+dependencies = [
+ "accesskit",
+ "ahash",
+ "epaint",
+ "log",
+ "nohash-hasher",
+ "ron",
+ "serde",
+]
+
+[[package]]
+name = "egui-winit"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d95d9762056c541bd2724de02910d8bccf3af8e37689dc114b21730e64f80a0"
+dependencies = [
+ "arboard",
+ "egui",
+ "log",
+ "raw-window-handle",
+ "serde",
+ "smithay-clipboard",
+ "web-time 0.2.4",
+ "webbrowser",
+ "winit",
+]
+
+[[package]]
+name = "egui_dock"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "727ea33d9940fb3eea4ba59c78e47db5d0c25bdcb3f3169be68d0ee7088f5e61"
+dependencies = [
+ "duplicate",
+ "egui",
+ "paste",
+]
+
+[[package]]
+name = "egui_extras"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "753c36d3e2f7a32425af5290af2e52efb3471ea3a263b87f003b5433351b0fd7"
+dependencies = [
+ "egui",
+ "enum-map",
+ "image",
+ "log",
+ "mime_guess2",
+ "serde",
+]
+
+[[package]]
+name = "egui_glow"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb2ef815e80d117339c7d6b813f7678d23522d699ccd3243e267ef06166009b9"
+dependencies = [
+ "bytemuck",
+ "egui",
+ "glow",
+ "log",
+ "memoffset",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "egui_plot"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a159fffebf052f79d1fd26d48e68906a21fec2fce808f7c0a982ec14ed506be"
+dependencies = [
+ "egui",
+]
+
+[[package]]
+name = "ehttp"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80b69a6f9168b96c0ae04763bec27a8b06b34343c334dd2703a4ec21f0f5e110"
+dependencies = [
+ "js-sys",
+ "ureq",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "emath"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a045c6c0b44b35e98513fc1e9d183ab42881ac27caccb9fa345465601f56cce4"
+
+[[package]]
+name = "emath"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ee58355767587db7ba3738930d93cad3052cd834c2b48b9ef6ef26fe4823b7e"
+dependencies = [
+ "bytemuck",
+ "serde",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enfync"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ce8c1fbec15a38aced3838c4d7ea90a1403bd50364b5cfe8008e679df0c8dde"
+dependencies = [
+ "async-trait",
+ "futures",
+ "tokio",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasmtimer",
+]
+
+[[package]]
+name = "enum-map"
+version = "2.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
+dependencies = [
+ "enum-map-derive",
+ "serde",
+]
+
+[[package]]
+name = "enum-map-derive"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "enumn"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "termcolor",
+]
+
+[[package]]
+name = "epaint"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e638cb066bff0903bbb6143116cfd134a42279c7d68f19c0352a94f15a402de7"
+dependencies = [
+ "ab_glyph",
+ "ahash",
+ "bytemuck",
+ "ecolor",
+ "emath 0.25.0",
+ "log",
+ "nohash-hasher",
+ "parking_lot",
+ "serde",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "error-code"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
+dependencies = [
+ "libc",
+ "str-buf",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.3",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "exr"
+version = "1.71.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8"
+dependencies = [
+ "bit_field",
+ "flume",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "rayon-core",
+ "smallvec",
+ "zune-inflate",
+]
+
+[[package]]
+name = "ezsockets"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ab2935707532c19357939ce9649a0ca45eb02c2295bf37990f4601cb3a8afd4"
+dependencies = [
+ "async-channel 1.9.0",
+ "async-trait",
+ "atomic_enum",
+ "base64",
+ "cfg-if",
+ "enfync",
+ "fragile",
+ "futures",
+ "futures-util",
+ "getrandom",
+ "http 0.2.11",
+ "tokio",
+ "tokio-tungstenite-wasm",
+ "tracing",
+ "tungstenite",
+ "url",
+ "wasm-bindgen-futures",
+ "wasmtimer",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "flume"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
+dependencies = [
+ "spin",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fragile"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+ "num_cpus",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[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.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
+dependencies = [
+ "libc",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gif"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "gio-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+ "winapi",
+]
+
+[[package]]
+name = "gl_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
+dependencies = [
+ "khronos_api",
+ "log",
+ "xml-rs",
+]
+
+[[package]]
+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 = "glow"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
+dependencies = [
+ "js-sys",
+ "slotmap",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "glutin"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "005459a22af86adc706522d78d360101118e2638ec21df3852fcc626e0dbb212"
+dependencies = [
+ "bitflags 2.4.2",
+ "cfg_aliases",
+ "cgl",
+ "core-foundation",
+ "dispatch",
+ "glutin_egl_sys",
+ "glutin_glx_sys",
+ "glutin_wgl_sys",
+ "icrate",
+ "libloading",
+ "objc2",
+ "once_cell",
+ "raw-window-handle",
+ "wayland-sys",
+ "windows-sys 0.48.0",
+ "x11-dl",
+]
+
+[[package]]
+name = "glutin-winit"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735"
+dependencies = [
+ "cfg_aliases",
+ "glutin",
+ "raw-window-handle",
+ "winit",
+]
+
+[[package]]
+name = "glutin_egl_sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd"
+dependencies = [
+ "gl_generator",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "glutin_glx_sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f"
+dependencies = [
+ "gl_generator",
+ "x11-dl",
+]
+
+[[package]]
+name = "glutin_wgl_sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"
+dependencies = [
+ "gl_generator",
+]
+
+[[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-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722"
+dependencies = [
+ "atk-sys",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 1.0.0",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "http"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+dependencies = [
+ "bytes",
+ "http 1.0.0",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http 1.0.0",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-range-header"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe"
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http 1.0.0",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http 1.0.0",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "icrate"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319"
+dependencies = [
+ "block2",
+ "dispatch",
+ "objc2",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "image"
+version = "0.24.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "exr",
+ "gif",
+ "jpeg-decoder",
+ "num-traits",
+ "png",
+ "qoi",
+ "tiff",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
+dependencies = [
+ "hermit-abi",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[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",
+ "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 = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lebe"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "libloading"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.2",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "libredox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
+dependencies = [
+ "bitflags 2.4.2",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "litrs"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "log-once"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d8a05e3879b317b1b6dbf353e5bba7062bedcc59815267bb23eaa0c576cebf0"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memmap2"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memory-stats"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34f79cf9964c5c9545493acda1263f1912f8d2c56c8a2ffee2606cb960acaacc"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "mime_guess2"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "multer"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219"
+dependencies = [
+ "bytes",
+ "encoding_rs",
+ "futures-util",
+ "http 1.0.0",
+ "httparse",
+ "log",
+ "memchr",
+ "mime",
+ "spin",
+ "version_check",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[package]]
+name = "ndk"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
+dependencies = [
+ "bitflags 2.4.2",
+ "jni-sys",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle",
+ "thiserror",
+]
+
+[[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.5.0+25.2.9519653"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
+[[package]]
+name = "ntapi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459"
+
+[[package]]
+name = "objc2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d"
+dependencies = [
+ "objc-sys",
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666"
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "orbclient"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166"
+dependencies = [
+ "libredox 0.0.2",
+]
+
+[[package]]
+name = "ordered-float"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "owned_ttf_parser"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7"
+dependencies = [
+ "ttf-parser",
+]
+
+[[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.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "petgraph"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
+
+[[package]]
+name = "png"
+version = "0.17.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "poll-promise"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6a58fecbf9da8965bcdb20ce4fd29788d1acee68ddbb64f0ba1b81bccdb7df"
+dependencies = [
+ "document-features",
+ "static_assertions",
+]
+
+[[package]]
+name = "polling"
+version = "3.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "545c980a3880efd47b2e262f6a4bb6daad6555cf3367aa9c4e52895f69537a41"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "prettyplease"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
+dependencies = [
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[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-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]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prost"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270"
+dependencies = [
+ "bytes",
+ "heck",
+ "itertools 0.10.5",
+ "lazy_static",
+ "log",
+ "multimap",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 1.0.109",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
+dependencies = [
+ "anyhow",
+ "itertools 0.10.5",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "puffin"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02330f795caafc2007510f742624c10aa813b8c3097c77ff344b1b86eb6be846"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "qoi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
+
+[[package]]
+name = "rayon"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "re_format"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f7253a068deca4b2f9f75ec8f2f1a31de7330dbf42f109423b5a258a939623"
+
+[[package]]
+name = "re_log"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45ff89b2c17420a6395510f2c2821a86d3164ed8d86d95334b44b707e0e36b36"
+dependencies = [
+ "env_logger",
+ "js-sys",
+ "log",
+ "log-once",
+ "parking_lot",
+ "tracing",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "re_memory"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ca1c9e3b9ca11d8249670984f11b3e206654bea437f68645c201ffcc37cf3e2"
+dependencies = [
+ "ahash",
+ "backtrace",
+ "emath 0.24.1",
+ "itertools 0.12.0",
+ "memory-stats",
+ "nohash-hasher",
+ "once_cell",
+ "parking_lot",
+ "re_format",
+ "re_log",
+ "re_tracing",
+ "smallvec",
+ "sysinfo",
+ "wasm-bindgen",
+ "web-time 0.2.4",
+]
+
+[[package]]
+name = "re_tracing"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad438a971b668f3f554df33a843576636ea1ef97e7039f18dd89973c7f97850"
+dependencies = [
+ "puffin",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox 0.0.1",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.4",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rfd"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c9e7b57df6e8472152674607f6cc68aa14a748a3157a857a94f516e11aeacc2"
+dependencies = [
+ "block",
+ "dispatch",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "js-sys",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "raw-window-handle",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
+dependencies = [
+ "cc",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ron"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
+dependencies = [
+ "base64",
+ "bitflags 2.4.2",
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustix"
+version = "0.38.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+dependencies = [
+ "bitflags 2.4.2",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.21.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki",
+ "sct",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "slotmap"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f"
+dependencies = [
+ "bitflags 2.4.2",
+ "calloop",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix",
+ "thiserror",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smithay-clipboard"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb62b280ce5a5cba847669933a0948d00904cf83845c944eae96a4738cea1a6"
+dependencies = [
+ "libc",
+ "smithay-client-toolkit",
+ "wayland-backend",
+]
+
+[[package]]
+name = "smol_str"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "str-buf"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[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.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "sysinfo"
+version = "0.30.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2"
+dependencies = [
+ "cfg-if",
+ "core-foundation-sys",
+ "libc",
+ "ntapi",
+ "once_cell",
+ "windows",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331"
+dependencies = [
+ "cfg-expr",
+ "heck",
+ "pkg-config",
+ "toml",
+ "version-compare",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
+
+[[package]]
+name = "tempfile"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall 0.4.1",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tiff"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
+dependencies = [
+ "futures-util",
+ "log",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-tungstenite-wasm"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ec8c7cf09b20184f946f114e3d8c0deca34368912c90100812861c14bb63b66"
+dependencies = [
+ "futures-channel",
+ "futures-util",
+ "http 0.2.11",
+ "httparse",
+ "js-sys",
+ "thiserror",
+ "tokio",
+ "tokio-tungstenite",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit 0.21.0",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0da193277a4e2c33e59e09b5861580c33dd0a637c3883d0fa74ba40c0374af2e"
+dependencies = [
+ "bitflags 2.4.2",
+ "bytes",
+ "futures-util",
+ "http 1.0.0",
+ "http-body",
+ "http-body-util",
+ "http-range-header",
+ "httpdate",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "ttf-parser"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
+
+[[package]]
+name = "tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http 0.2.11",
+ "httparse",
+ "log",
+ "rand",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "ureq"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97"
+dependencies = [
+ "base64",
+ "flate2",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-webpki",
+ "url",
+ "webpki-roots",
+]
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "version-compare"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
+
+[[package]]
+name = "wasmtimer"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf"
+dependencies = [
+ "futures",
+ "js-sys",
+ "parking_lot",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "nix",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3"
+dependencies = [
+ "bitflags 2.4.2",
+ "nix",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-csd-frame"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
+dependencies = [
+ "bitflags 2.4.2",
+ "cursor-icon",
+ "wayland-backend",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b"
+dependencies = [
+ "nix",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c"
+dependencies = [
+ "bitflags 2.4.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
+dependencies = [
+ "bitflags 2.4.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6"
+dependencies = [
+ "bitflags 2.4.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af"
+dependencies = [
+ "dlib",
+ "log",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee269d72cc29bf77a2c4bc689cc750fb39f5cbd493d2205bbb3f5c7779cf7b0"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webbrowser"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71"
+dependencies = [
+ "core-foundation",
+ "home",
+ "jni",
+ "log",
+ "ndk-context",
+ "objc",
+ "raw-window-handle",
+ "url",
+ "web-sys",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
+
+[[package]]
+name = "weezl"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-wsapoll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "winit"
+version = "0.29.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c824f11941eeae66ec71111cc2674373c772f482b58939bb4066b642aa2ffcf"
+dependencies = [
+ "ahash",
+ "android-activity",
+ "atomic-waker",
+ "bitflags 2.4.2",
+ "bytemuck",
+ "calloop",
+ "cfg_aliases",
+ "core-foundation",
+ "core-graphics",
+ "cursor-icon",
+ "icrate",
+ "js-sys",
+ "libc",
+ "log",
+ "memmap2",
+ "ndk",
+ "ndk-sys",
+ "objc2",
+ "once_cell",
+ "orbclient",
+ "percent-encoding",
+ "raw-window-handle",
+ "redox_syscall 0.3.5",
+ "rustix",
+ "smithay-client-toolkit",
+ "smol_str",
+ "unicode-segmentation",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-plasma",
+ "web-sys",
+ "web-time 0.2.4",
+ "windows-sys 0.48.0",
+ "x11-dl",
+ "x11rb 0.13.0",
+ "xkbcommon-dl",
+]
+
+[[package]]
+name = "winnow"
+version = "0.5.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
+dependencies = [
+ "memchr",
+]
+
+[[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 = "x11rb"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
+dependencies = [
+ "gethostname 0.3.0",
+ "nix",
+ "winapi",
+ "winapi-wsapoll",
+ "x11rb-protocol 0.12.0",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a"
+dependencies = [
+ "as-raw-xcb-connection",
+ "gethostname 0.4.3",
+ "libc",
+ "libloading",
+ "once_cell",
+ "rustix",
+ "x11rb-protocol 0.13.0",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc"
+dependencies = [
+ "nix",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
+
+[[package]]
+name = "xcursor"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
+
+[[package]]
+name = "xkbcommon-dl"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699"
+dependencies = [
+ "bitflags 2.4.2",
+ "dlib",
+ "log",
+ "once_cell",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+
+[[package]]
+name = "y4m"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "zip"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
+dependencies = [
+ "byteorder",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+]
+
+[[package]]
+name = "zune-inflate"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
+dependencies = [
+ "simd-adler32",
+]
diff --git a/tools/avm_analyzer/Cargo.toml b/tools/avm_analyzer/Cargo.toml
new file mode 100644
index 0000000..86bac2e
--- /dev/null
+++ b/tools/avm_analyzer/Cargo.toml
@@ -0,0 +1,13 @@
+[workspace]
+members = ["avm_analyzer_app", "avm_analyzer_common", "avm_analyzer_server", "avm_stats"]
+resolver = "2"
+
+[profile.dev]
+opt-level = 1
+
+[profile.release]
+opt-level = 2 # fast and small wasm
+
+# Optimize all dependencies even in debug builds:
+[profile.dev.package."*"]
+opt-level = 2
diff --git a/tools/avm_analyzer/Dockerfile.builder b/tools/avm_analyzer/Dockerfile.builder
new file mode 100644
index 0000000..2418172
--- /dev/null
+++ b/tools/avm_analyzer/Dockerfile.builder
@@ -0,0 +1,47 @@
+# TODO(comc): cargo chef caching doesn't seem to be working properly.
+FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
+RUN apt-get update
+RUN apt-get install -y make pkg-config git unzip cmake protobuf-compiler libssl-dev --no-install-recommends
+RUN cargo install trunk
+RUN rustup target add wasm32-unknown-unknown
+RUN mkdir /extract_proto
+COPY tools/extract_proto/avm_frame.proto /extract_proto/avm_frame.proto
+WORKDIR /app
+
+FROM chef as planner_frontend
+COPY tools/avm_analyzer .
+RUN cargo chef prepare --recipe-path recipe.json --bin avm_analyzer_app
+
+FROM chef AS cacher_frontend
+COPY --from=planner_frontend /app/recipe.json recipe.json
+RUN cargo chef cook --release --target wasm32-unknown-unknown --recipe-path recipe.json --bin avm-analyzer-app
+
+FROM chef as planner_backend
+COPY tools/avm_analyzer /app
+RUN cargo chef prepare --recipe-path recipe.json --bin avm_analyzer_server
+
+FROM chef AS cacher_backend
+COPY --from=planner_backend /app/recipe.json recipe.json
+RUN cargo chef cook --release --recipe-path recipe.json --bin avm-analyzer-server
+
+FROM chef AS builder_frontend
+COPY --from=cacher_frontend /app .
+COPY tools/avm_analyzer .
+RUN trunk build --release avm_analyzer_app/index.html
+
+FROM chef AS builder_backend
+COPY --from=cacher_backend /app .
+COPY tools/avm_analyzer .
+RUN cargo build --release --bin avm-analyzer-server
+
+FROM avm_analyzer_runtime as avm_builder
+COPY . /avm
+RUN mkdir /avm_build
+RUN /scripts/build_avm.sh --avm_build_dir /avm_build --avm_source_dir /avm
+RUN rm -r /avm
+
+FROM avm_analyzer_runtime as runtime
+WORKDIR /app
+COPY --from=builder_frontend /app/avm_analyzer_app/dist dist
+COPY --from=builder_backend /app/target/release/avm-analyzer-server avm-analyzer-server
+COPY --from=avm_builder /avm_build /avm_build
diff --git a/tools/avm_analyzer/Dockerfile.runtime b/tools/avm_analyzer/Dockerfile.runtime
new file mode 100644
index 0000000..af2011e
--- /dev/null
+++ b/tools/avm_analyzer/Dockerfile.runtime
@@ -0,0 +1,7 @@
+# Basic runtime image with required dependencies to build libavm.
+FROM ubuntu:22.04
+RUN apt-get update
+RUN apt-get install -y cmake git perl g++ yasm make protobuf-compiler libprotobuf-dev python3 --no-install-recommends
+WORKDIR /scripts
+COPY tools/avm_analyzer/build_avm.sh /scripts/build_avm.sh
+RUN chmod +x /scripts/build_avm.sh
diff --git a/tools/avm_analyzer/README.md b/tools/avm_analyzer/README.md
new file mode 100644
index 0000000..f53300e
--- /dev/null
+++ b/tools/avm_analyzer/README.md
@@ -0,0 +1,39 @@
+# AVM Analyzer
+
+## Building
+
+### With Docker
+
+```
+./launch_server_docker.sh --streams_dir <STREAMS_PATH> [--port <PORT>]
+```
+
+### Local Build
+1. Install dependencies
+```
+# libavm (if not already installed)
+apt install cmake yasm perl
+# Protobuf compiler
+apt install protobuf-compiler libprotobuf-dev
+# Rust toolchain (see rustup.rs)
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+# Trunk
+cargo install --locked trunk
+# WebAssembly build target
+rustup target add wasm32-unknown-unknown
+```
+
+2. Build AVM
+```
+export AOM_ROOT=/path/to/git/root
+export AOM_BUILD_DIR=/path/to/avm/build
+./build_avm.sh --avm_source_dir ${AOM_ROOT} --avm_build_dir ${AOM_BUILD_DIR}
+```
+
+3. Build and launch AVM Analyzer
+```
+./launch_server_local.sh --streams_dir <STREAMS_PATH> --avm_build_dir ${AOM_BUILD_DIR} [--port <PORT>]
+```
+
+## Troubleshooting
+Please contact comc@google.com.
\ No newline at end of file
diff --git a/tools/avm_analyzer/avm_analyzer_app/.cargo/config.toml b/tools/avm_analyzer/avm_analyzer_app/.cargo/config.toml
new file mode 100644
index 0000000..e7becff
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/.cargo/config.toml
@@ -0,0 +1,2 @@
+[target.wasm32-unknown-unknown]
+rustflags = ["--cfg=web_sys_unstable_apis"]
\ No newline at end of file
diff --git a/tools/avm_analyzer/avm_analyzer_app/Cargo.toml b/tools/avm_analyzer/avm_analyzer_app/Cargo.toml
new file mode 100644
index 0000000..95d6354
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/Cargo.toml
@@ -0,0 +1,73 @@
+[package]
+name = "avm-analyzer-app"
+version = "0.1.0"
+authors = ["Conor McCullough <comc@google.com>"]
+edition = "2021"
+rust-version = "1.75"
+
+
+[dependencies]
+avm-analyzer-common = { path = "../avm_analyzer_common" }
+avm-stats = { path = "../avm_stats" }
+anyhow = "1.0"
+egui = "0.25"
+egui_extras = { version = "0.25", features = ["image"] }
+eframe = { version = "0.25", default-features = false, features = [
+    "default_fonts",
+    "glow",
+    "persistence",
+    "__screenshot",
+] }
+ehttp = "0.2.0"
+egui_plot = "0.25"
+egui_dock = "0.10"
+image = { version = "0.24", features = ["jpeg", "png"] }
+itertools = "0.10"
+prost = "0.11"
+serde = { version = "1", features = ["derive"] }
+serde_json = "1.0"
+ron = "0.8"
+poll-promise = "0.3.0"
+y4m = "0.8.0"
+web-sys = { version = "0.3.64", features = [
+    "Blob",
+    "FileReader",
+    "Document",
+    "Element",
+    "History",
+    "Performance",
+    "HtmlElement",
+    "UrlSearchParams",
+    "Location",
+    "Window",
+    "Url",
+    "UrlSearchParams",
+] }
+js-sys = { version = "0.3" }
+futures = { version = "0.3.10", features = ["thread-pool"] }
+rfd = { version = "0.12", features = ["file-handle-inner"] }
+log = "0.4"
+once_cell = "1.19.0"
+zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
+web-time = "1.0.0"
+async-trait = "0.1.52"
+ezsockets = { version = "0.6", default-features = false, features = [
+    "wasm_client",
+] }
+url = "2.2.2"
+wasm-bindgen = "0.2"
+mime = { version = "0.3" }
+mime_guess = { version = "2.0" }
+rand = { version = "0.8.5" }
+getrandom = { version = "0.2.10", features = ["js"] }
+ordered-float = "4.2.0"
+re_memory = "0.12.1"
+bincode = "1.3"
+base64 = "0.21.7"
+weezl = "0.1.8"
+convert_case = "0.6.0"
+
+# web:
+[target.'cfg(target_family = "wasm")'.dependencies]
+console_error_panic_hook = "0.1.6"
+wasm-bindgen-futures = "0.4"
diff --git a/tools/avm_analyzer/avm_analyzer_app/Trunk.toml b/tools/avm_analyzer/avm_analyzer_app/Trunk.toml
new file mode 100644
index 0000000..bd6c484
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/Trunk.toml
@@ -0,0 +1,2 @@
+[build]
+filehash = false
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/favicon.ico b/tools/avm_analyzer/avm_analyzer_app/assets/favicon.ico
new file mode 100644
index 0000000..ccf2ba4
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/favicon.ico
Binary files differ
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/icon-192x192.png b/tools/avm_analyzer/avm_analyzer_app/assets/icon-192x192.png
new file mode 100644
index 0000000..a6fef93
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/icon-192x192.png
Binary files differ
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/icon-256x256.png b/tools/avm_analyzer/avm_analyzer_app/assets/icon-256x256.png
new file mode 100644
index 0000000..cb66e93
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/icon-256x256.png
Binary files differ
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/icon-384x384.png b/tools/avm_analyzer/avm_analyzer_app/assets/icon-384x384.png
new file mode 100644
index 0000000..4dd0a66
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/icon-384x384.png
Binary files differ
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/icon-512x512.png b/tools/avm_analyzer/avm_analyzer_app/assets/icon-512x512.png
new file mode 100644
index 0000000..a9dbcd9
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/icon-512x512.png
Binary files differ
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.ivf b/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.ivf
new file mode 100644
index 0000000..23a58d8
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.ivf
Binary files differ
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.yuv b/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.yuv
new file mode 100644
index 0000000..657a7e9
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.yuv
@@ -0,0 +1 @@
+žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžœœœœœœ›œœ››ššššš™™˜˜™™™™™™™™™™™™™ššš›œœœœœœœœžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸžžžžžœœœœ›™™™™˜˜————•–––––•“‘‘‘‘‘‘‘“”•————˜™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœ›››šš™™™™šš™™ššš™˜˜˜˜˜™™™™™™˜˜˜˜˜˜™™šššš›œœœ›œžžžžžžžžžžžžžžžžžžžžžžžŸŸŸŸžžžžœœœœ›šš™™™™™—–••••”“““““’‘‘‘‘““–˜————™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžž›™š™š™™™™™šš™™™™™™˜˜˜˜˜˜˜˜˜™™˜˜––——˜™šššššš›œœœœœœœžžžžžžžžžžžžžžžžžžžŸŸŸŸžžžžœœœœœ›™™™™™™™™—•“’‘‘’ŽŽŽ‘’“•—————™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœ™™™™™˜˜˜˜˜™™™™˜————————––––——–––––—˜™™š™™™››››››››œœžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸžžœ›šššš˜˜˜—˜˜˜–””“ŽŽŒ‹‰Š‹ŒŒŽŽŽŽ‘’”•——˜˜™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœ™™™™™™˜˜˜˜™™˜˜˜–––—––––•”””–—–•••–—˜™™›š™š›šš›››››œœœžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸžžœ›™š™™™˜—–•––––”‘ˆ}ƒ‰‹‰‰‹‹ŒŽŽ‘’“”–—˜˜™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœ™˜™™™˜˜˜˜˜˜˜————–••”””“““““““”•––––——™šš™™™™™›œœœœœœœœžžžžžžžžžžžžžžžžžžžžžžžžœ›™™™™˜˜—•“’‘Œƒysorz€„ˆ‰ˆ†‡‡ˆŠ‹‹ŒŽ‘’””–——™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœ™™™˜˜˜˜˜˜˜——––––•”“““’’‘‘‘‘’’““”••••–—ššš™™˜˜šœœœœœœœœžžžžžžžžžžžžžžœ›š˜˜˜™˜—•’’…s`VSV_l|ƒ…†………„………††‰‹Ž‘‘’“•––˜™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœ™™™™˜˜˜˜˜˜˜—–””””“’‘‘‘’’“’““”–—™™˜™™™›œœœœœœœžžžžžœžžžžžžžžœœš™˜˜˜˜—•“Žƒp[KA>DKXoƒ…„„ƒ‚‚‚ƒ…‡ŠŒ‘’•–—˜™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœ™™™˜˜˜˜˜˜˜——•”“““’‘ŽŽŽŽŽ‘‘’“””•—˜˜˜™™›œœœžžžžžžžžžžžžž›šš™˜˜˜—•’‰uZIA??AEIVs†…ƒ€€~€€ƒƒƒ…‰‹Ž‘”•—˜™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœ™™™™˜˜——–——–”““’‘‘ŽŽŽ‘‘‘’“•——˜™™šœœœœœžžžžžžžžžžžžžžžžœ™™˜˜˜˜˜“Œ{aJ<:=BGLPWdz„…„‚~~}~€‚ƒ…†‰‹ŒŽ’•—˜™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœš™™™˜———––•”“’‘ŒŒŒŒŒ‹ŒŒŒŒŒŒŒŽ‘’’“•——˜™™™›œœœœœœœœžžœœ›››››š™˜˜–•—–„dK><?HS\cjnt{„„ƒ‚}{||}~€ƒ…†‡‰‹Ž“–—˜™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœ›™™™˜——––”“““’‹‰‰‰‰‰ˆˆˆˆ‰ˆˆ‰‰Š‹Ž‘’’“““•–—˜™›šššš™˜™š›œœœœœœœœœ›››ššš™˜™™™™˜———•“’[@8=CKYfmrtuy€……„‚€}{{{|}~€‚…†ˆ‰Œ‘“–˜˜™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœ›™™™˜——–•””““‘Ž‹ˆ‡‡††………………†††‡‡ˆŠ‹Œ‘’’”—˜˜——˜———˜˜šššš™™š™™™™™™™™——––––•”’’‘ŠuS?;>JT\fmrtuvy…†‚€}|zzzzz{~€‚…†‡ŠŒ‘”–˜˜˜žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœ›™™™˜—––•““’’ŽŒ‹‡††…„„„„„ƒƒ„„…………†ˆŠ‹‹ŒŽ“””•••”••–—˜˜˜˜˜˜˜———————–••”“““’‘ˆjH;;<IW^binpqsuw~……~zyyxxxxx|~„†ˆŠŽ‘”—˜˜˜žžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžžžžžžžžžžžžžœœœœ›™™™—––••”“’‘ŽŒ‹‰‡……„‚‚‚€€€‚„…††ˆ‰ŠŒŒŽ’‘‘‘‘’’’“”––––––––••••””“““’’ŽŒŠdD9<=ES]`binopqtw|ƒ…}zyyxwxvvy|}€ƒ†‰‹’”—˜˜™žžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžžžžžžžžžžžžœœœœ›™™™—––••““’ŽŒŠ††…ƒ€€€~~~~}~~‚‚„…†ˆ‰ˆŠ‘‘“’““’’’“““““’’’‘ŽŽŒ‡~]=7<AIS\cdhmrrrsvz|„‚}zyxvvvuux{}€„†ˆ‹“•—˜˜˜žžžžžžžžžžžžžžžžžžžžžžžžžŸŸžžžžžžžŸžžœœ›š™™˜—–•”•“’’ŽŒ‹‰†…‚~~~}|{{{z{{|~€‚ƒ„……‡ˆ‹‹ŒŒŒŒŽŽŽŽŒ‹Œ‰{]<5:BMWZailptvuttuuz„„~yxvuutuvy|~„†ˆ‹’•—˜˜™žžžžžžžžžžžžžžžžžžžžžžžžžžŸŸžžžœ˜’‰‚Š˜žžœœ›š™˜—––•”“’’‘‹‰‡…„~~}|||zzyyxyyyz{|~€‚ƒ……††‡‰‰ŠŠŠŠ‹‹‹‹‹ŽŽŽŽŽ‹‹ŠŠˆ†…wV;49?JW_chmnprrqpppqv…†~wutsssuvx}„†ˆ‹’•—˜˜™žžžžžžžžžžžžžžžžŸŸŸŸŸžŸœœ››š–•“Ž‡{`]tŽ›žœœš™˜—–•”““’’‘Œ‹‰†„ƒ€~}{{{zyyxvuuuvvwyz{|~€‚„…………‡‡‡‡‡‰ŠŠ‰ˆŠŠ‹‹‹Š‹ŠŠ‹‹ŠŠ‰‡†…„…‚tU607>ER]aglnnooqrrrrrv€ˆ‰urrrrstvx|‚…‡ˆ‹Ž“–—˜˜šžžžžžžžžžžžžžžžžžžŸŸžžžœ™—•“‘ŽŠ…kSVgŠœœœš™˜—––”““’’‘Ž‹‰‡…„‚}zzyyxwvusssttttuxzz{}€‚ƒƒ……………………†ˆˆ‡ˆ‰‰‰ˆ‡ˆ‡‡ˆˆ‡‡‡‡…ƒ€}qT8/5=HR^ddhlnnnnopqqrrt‡ˆtqrqqrtux|€‚…‡ˆŒ‘“–—˜™›žžžžžžžžžžžžžžžžžžžŸŸžš—”‘ŽŒŠŠŠˆ„€t^QM^—žœ›š˜—–•””“’‘Ž‹ˆ‡…ƒ~|yxxxwutsrrrrrpqrtwwwyz|€‚‚€ƒ„…††††‡‡†††††…††……ƒvaK604=GRZ`abgkmmmmllllllnyƒ†spqrrrtvx|€‚…‡ŠŽ’”–—˜™›žžžžžžžžžžžžžžžžžŸžœ—“‘Œ‹‰ˆˆ‡„ykULGSu‘œš˜—–•””“’‘Œ‹‰†„ƒ€~{yxxxutsqppqpoonorttuwxx{|}||~~~€€‚‚‚ƒƒƒƒ‚‚‚€€}u^E1-3;EQWY[`cfjllklkkjjjjmx‚…~tnqsssuvx|†ˆ‹’”—˜™šœžžžžžžžžžžžžžžžœœœžžž›–’Ž‹ŠŠ‰‰ˆ†„{saTLDOpœœš——––”“’‘ŽŒŠˆ‡„}zyxxwtsqnnnnmmmmmoqsstttwxxxxz{{{{|}~~~~~~~~}zrY>2148>HRUW]ehlopqrsuwyzzxx}†Š†}wuuttuwy|€‚…ˆ‹Ž‘“•—˜˜šœžžžžžžžžžžžžžžžœœŸžžš—’Ž‹Š‰‰Šˆ…ƒ|zuldSHFN_x˜——–•”““’‹‰‡†ƒ€}yyxvutrollllklkklmnooppqstutuwxxxxyzz{{|||||||{{|{|~wjYD4.28>BIRUY`giloqsuwwwwvvvw|†‹‰zwuuuuwy|€‚…‰Œ‘“–—˜˜™œžžžžžžžžžžžžžžœžŸž›–‹ˆ‰ŠŠ‹‰…ƒ}{wpk[OJFENmŽ———–•”’‘Š‡‡†ƒ€}|yxvtsrqolkkkkkjkklmmlmnnqqrrrsuuuvvxwwwwxxxwvwwvvuwzykQ:4437@DHNVY\befghknppnonmmlnu‚ˆ„{utttuwxy}„‡Š’“–˜™™™œžžžžžžžžžžŸ™”‡†‡‰Š‹Š†„{xpk`UOJC?Ja‚Ž”–•’ŽŒˆ†…ƒ€~{ywuttsrqnkjjjkiijkkkkkklmnnnooqsssstttttsssssrsstsqokbQ<.256;DHLPV[^`cdcfiklmlmmmnnnr€‡vqqstuwyz~„‡Š’“•˜™ššžžžžžžžžžžžœ˜’‰…‚†ˆŠ‹Š‡„{xqkbZUPHA@D[p~†Š‡†‹ŒŠˆ†…‚|zwtsssssrnkjjihhhjkkkkklmlllllmnonqrqqqpprpoooopqrojaSC4.149?CILNSY\^`abcegjllmnnoqror‡upoqrsux{~„‰‹Ž’’•˜™›šžžžžžžžžžŸŸœ—‡ƒ€ƒ‡Š‹Š†ƒ|yrld^ZTLE?<=GXhponv‚†„‚‚}{xuqqstuutolkhfeefhijjjjjkkkkklklkkpqpppommkjjlkjjicWI82026:AGKPRVZ]_abcdfikmnnnnprttps‡€uqopqrsx{~‚…‰‹’’•˜™››žžžžžžžžžŸž›—…€‚…ˆŠˆ„€|xuoia]XQJC>:8=HOONWu€|{zzwsokjlqtvuolmjihhilnpppqrrssssrrokknpqqnhcbaaab`^\ZP?6/-/7>EKOSUX[]`cefikmprrqqqqsvyxtw€‡‚wsrrrruz|~…ˆ‹Ž’’•˜š››žžžžŸžžžžš˜…€€ƒ…††ƒ~|yuqlda]VOJC=98:=<=CWkkegiiiheb`biruspnllmpuz~€‚‚€€~~~zrpprtsmb\WUUUUTQOK?30/.3?IMPTXZ]^acfilmoqtvuttttwz}{wy€‡ƒxtsstuwz}~…‰‹‘’•—™››žžžžžŸŸžž›˜„€ƒ…††…‚|ytqnhea[UPJB<978668=JNLPRTUVWVTWamvvtqqsw|„‡‡ˆˆˆˆ‡‡‡†‡‡‡‡†„|vvxyxqd[TNKONMIB=5//02?MQRVY[`bcefgkmnqsuwuuuuuvz}{x{‚…€vrrstvxz}~‚…‰‹‘‘•—™››žžžžŸŸžžž›˜}‚……†‡†‚€~zusqlhd_[VOG@;864357:=@ADGJLLJIKWmxyz{~€ƒ…‡‡†‡‡‡‡‡‡‡ˆ‰Š‹‹‹‰‚}|}~|wlc[OHLKG@:82-.2<HQV\_ccddefghlmprtttqoooptx{zx{‚ƒ{rnopstux|‚…ˆ‹Ž’“•˜š››œžžžžžžžžžžœ™€|€‚„…††‡„€}zxurlifc]YSJD>:75424589:<CFFFFGJWoz|€„„…†††‡†…†‡‡‡‡ˆˆ‰ŠŒŽ‹†‚€~vshYOJID<98546>GNU[bedcbceghjnopqppnmlklnsxzzx|ƒwnklnqqsx{~„ˆŠŒ’“•—š››žžžžžžžžžžžžžžžžžžŸžšŽ~ƒ„……††„‚~|wtqmjfc`[YRKC>;75223467;ADDDDHN[nz€„†††ˆ‡†……„…†‡‡††‡‰ŠŒˆ„ƒ€€{ztcWOLD;9879>GMTY`dfb`adhiilpqponlkkkllosxz{y}„~rkikmoprvz~€„‡‰‹Ž‘“•—™››œžžžžžžžžžžžžžžžžžžžžžš‚ƒ„„……†„ƒ|wurmjfc`\YWRJC>952111468<@HKMR\eq|‚‡ˆ‰‡‡†…„ƒ„„†‡‡†……‡ŠŽ‘‘Ž‹†„‚€€€|xoc]YM?=::@GNSZ`fgfb`ckomkkllkjiihijkloty{{z†}oihjknqruy|‚†‰Š“•˜™š™›žžžžžžžžžžžžžžžžžžžžžš…€ƒ„„„„„„‚|xusnlhd`]YXUPGA;840/1025;BPY`hnsy…Š‹‹‰‡‡…ƒ„…‡‡„ƒƒ…ŠŠˆ‡„ƒƒ‚ƒ|upgWIFEEIOSU_lojfddippmigeccfghijklmouz}|z†}pkkklnqrtx{‚†ˆŠŒ’•˜™š›œžžžžžžžžžžžžžžžžžžžžžœ™’ˆ‚‚„„‚‚…„‚€}zvsplhd^ZXXUTNHA:62////4AS`kt{~€‚‡ŠŒŒ‹ŠŠ‡…‚€‚‚„„‚€€…ŠŒ‹ŠŠˆ‡‡‰‰‡…‚€zn^VTSSTWY[fttnhfgptlhffddgijjkmmmnquyzyx€‡~soollmoqswz~„†‰Œ’•—˜™œžžžžžžžžžžžžžžžžžžžžŸœ™”Š„…‡ˆˆˆ…ƒ€}{yvrnjfa^[ZYVSOH@:73322<Rblv„…†ˆŒŽŽ‹Š‡††„ƒ€€~ƒ‡ŠŒ‹Š‰‰ŠŒŒŒŠ‡…uib_^^`acdpwtrmjmuqfacgimopponnlkmrtvutu†~vqpmlmopruy}€ƒ†ˆ‹Ž‘”—˜™›œžžžžžžžžžžžžžžžžžžžŸœ˜”ˆ‰‹Ž‡ƒ}{zyvspnljfa]YWUPHB?=><>Pbnw€…‡†‡‰ŒŽ‹ŠŠˆ‡‰‹Šˆ„‚}{}€ƒ†‡‰Š‹ŒŠ‰‰Š‹Ž‰ƒ{qllllkjknvvssooutjfcglptwtqnkjiikostssuƒ…~xsolllnprux{ƒ†‰‹Ž‘“–—™šœžžžžžžžžžžžžžžžžžžžžž›™•ŽŠ‰ŠŠƒ|~€€~{{yxutsrqmha\YXWSJGILNVamv|„ˆˆˆ‡ˆ‹ŒŒŠˆ‡†ˆ‹ŽŒŠ†ƒ~|}~€‚‚ƒ…ˆ‰‰ˆŠŒŽˆwtuxxtorvzurtorwtpkipw{|vmhgdcgmnrstuv|‡ˆysolllnortw{~‚…‡ŠŒ’•˜™™šžžžžžžžžžžžžžžžžžžœœ™—Š†„‚~z{|~€~|{zyvtsrppmg`[YZ\TNOZgmpv{€…ˆˆ‰ˆˆ‰ŠŠ‰‡…††‰‘‘Ž‹ˆ„ƒ‚~„‡ˆ‡‰ŒŽ‘Ž‹‚}z~€|y{|}ysoryvpoms„ƒ{kdcccgntuxxyx{‚‡‡‚xqlkllnopsvz„…‡‹“——˜˜ŸŸžžžžŸžžŸŸžžžžžœœœ›˜’ˆƒ€€~zzz}~~}}|{zwsqonmmiea]Z]`ZWdry}„‡ˆˆ‰ˆ‰‰ˆ††„ƒ…ˆŠ‘“”“Š‰ˆ…‚€~}‚„…‡‰Œ‘‘ŽŒ‡€€ƒ‚ƒ‚ƒ‚€}rnx~wsqu€ˆˆriijkox~~~~~†Šˆ‚vnklopqpqrtx{‚‚„‰Œ’•––—ŸŸžžžŸŸŸŸŸŸžžžžžœœœ›™’ˆ~{yyz{||||{zxurnkijiigaZ[dhfkw€„„††‡‡‰‰‡††„ƒ‚€€‚…ˆ‹”——“‘‘’„|{|}}‚„‰‹Ž‘‘Š†…‡ˆ†‡‡ˆ†ysy}}yxzŠ‹ƒvmorux†‰‰ˆ„}}€Š‹€rlnsutspqqsvy{ƒ†ŠŒ’”•—ŸŸŸžžŸŸŸŸžžžžžžžœœœ›š“‰ƒ€€|yyyz{{||||zzwrnklmnlg`^gpw{€…ˆ‡ˆˆˆˆ‡†„ƒ‚€~~€…ˆŒ“˜˜•’“–—˜”‰ƒ€|yz{|~€…†ŠŽŒŠŠŠŠ‰Š‹‹ˆ€{„€~zz‚ŒŽˆ{tvxxy~…‹Ž‹ƒ~|yzƒ…zsuz~yusppqsuxz}‚…ˆ‹‘“”—ŸŸŸŸŸŸŸŸŸžžžžžžžœœœ›š”Š„ƒ€|zxyyyz{|}€€~zvrppponlnt~„‡‰Š‰ˆ‡‡†„€€~}{{}‚†ˆ•—––˜›œ›™‰ƒzyzyz{€…ŠŒŒ‹Š‹ŠŠŠ‹‹‹ˆ„Šˆ„~…‹‚|xxzz{‡‹‹‡yz{|†‹‰‚€†‹Š‚xsrpopstvxz}‚…‰‹Ž‘“–ŸŸŸŸŸŸŸŸŸžžžžžžžœœœ››—„ƒƒ}{yxxyz|~€ƒ…‡‰Šˆƒ}xuvxyzy}‚ˆŠ‹ŠŠŠ†…‚€~~~}zwvxz|}~ƒ‡‘——™ Ÿœ•Ž†}zyywx|~‚…Š‹‹‹Š‹‹Œ‹‹‹‹‰‡‰‰‡„ˆŠzwwx{~†Š„zx{~‚†‰‹Š‹‘”Ž‚zwsonnpruvy{}„‡‰Œ‘•ŸŸŸŸŸŸŸŸŸžžžžžžžžœœœœš˜†ƒ‚‚}zyyz~„…‡‰‹ˆƒ€‚…ˆŒ‹‹‹‰ˆ„‚~|{zyvtssuwxz|~‚—šž£¤¤ ˜‘ˆ‚~zxwuwz~ƒ‡‰ˆˆŠŠŒŒ‹‹‹Šˆ‡‡†…„†ŒŠ€{zxw{‹’ˆuv†…ƒ…ˆ‹ŒŒ‘”•Ž…ƒ|rnlmnosuxy{~…‡ŠŒŽ’ŸŸŸŸŸŸŸŸŸžŸžžžžžžžœœ›š˜’‰‚‚€}z{{|€ƒ„„†ŠŽ’‘ŽŠˆˆ‰Š‹‹‹‰‡„}{yyxvsppprtsvxy™¡¥¦¤¡ž›”Š„zwvvwz|…‡‡†‡‰‹‹ŠŠŠ‰‰ˆˆ††‰Šˆ‚~}}~~ƒŠ†yqtˆ‰‰‰Š‹Œ‹ŒŽ‘“•‘‰€rllllmnptvwz{~‚„‡‰ŒŸŸŸŸŸŸŸŸŸžŸžžžžžžžœ››™˜”‹ƒƒ„‚€€€€€‚‡Œ‘ŒŒŒŽŠˆ‡†„€}zwuttsronnoqorvu~“›ž¢£¥¤¡Ÿœ˜…~zwwuvz|~€ƒ„„ƒ…††ˆˆŠŠŠŠ‰‰‡‡Š‡‚€€„†ŠŽŒ‚wpu‚‹‰‡ˆˆˆŠ‹Ž’“–“†vllljklmnqstwy{~„‡ŠŽŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžœ›™™—•†‚………††‡‰‰‡…„ƒ‚ƒ‚‚ƒ‡‘’‘‘ŽŽŽŒ‰†…„|wtsrrqonmnnnnpsr—› £¤¥¥¢Ÿ™†ywusvz|}~€‚€ƒ…‡‰ŠŠ‰‰ˆ‡††„‚‚…†‡Š‹‹wsxƒˆˆ†„„„‡‰‘’”—{nlikkjklmoqrsuxz}…ˆŒŸŸŸŸŸŸŸŸŸŸŸžžžžžžœœœš™™˜—“‹……………‡‰‹‹‹‹‹ŠŠŠŠ†„ƒ‡ŽŽŒ‹‡„‚‚€|yusqoonnmkklllnqn€˜œ¡£¤¥¥¢ ™‡€{xtruyz{}~~}~~~€„†‰ˆˆ‰‰‡…ƒ„„„…‡‡ˆ‡ƒ|stzƒ‰‡…ƒ‚„‡‹’“••‡uqnlmmlkklnoprsvwy}‚…‰žžžžžžžžžžžžžžžžžœœœœš™™˜˜”ˆ‡‡††‡‡…ƒ„†ˆ‹ŒŒ‹‹‡†ˆŠŽ‹„~{xurrpnllkhhhhjnol˜¢¤¤¥¦£¡Ÿ™‡|wtqrwxz{||||{{}~€…†‡ˆ‰‡……………††…„~vsu{„†††„ƒ†ŒŽ”––”Œƒztrpnmljlmnpstuwz~‚†žžžžžžžžžžžžžžžžžœœ™˜˜˜—”Š†„…†ˆ‹‹ˆ„„„„‡ŠŠ‰‡‡ˆŠŒŒŒ‹ŽŒ‰ƒ€~}|zyvsqonmlligfedlpl†™Ÿ¤¥¥¦¦¤£ ›‘‰ƒ|wtonsvxyz{{{zyxxxy„†‡†††‡††††…‚yvvy~€‚…‡‡…‚„Œ‘–˜—”“’Ž…~wsqpmkkmnprttux{ƒžžžžžžžžžžžžžžžœœœ™———–“Š†ƒ}z|„Š‹ˆ…‚„‡ˆˆˆ‰ŠŒ‹‹‹Š‹‹Š‡ƒ€~}{yxusqnmnmlieca`ippŠš ¦¦§§¦¥¥¢œ–ƒytqkipstvxyyyvvtssty„†††††††††„}zxwx{~‚‡ˆƒ~~…Ž‘ŒŒ‘–—”‘‘‰„~xuspllmnortttwz}€žžžžžžžžžžžœšš™˜——––’Œ‰‹Š„€|{y}‚†ˆ†ƒƒ…‡ˆˆ‰‰‹Š‹Š‰ˆˆ†ƒ~|{xusqnmmmmlid`]`ksxŽž£¥§§¦¦¥¥¤ ™‘„xplebjnoqtvvvtsrqrrv{‚………†††††„‚~|zwuw|ƒ„xmpx~‡‹Œ““‘ŽŽ‹‰ƒ}zwuronooqrstvy{}œœœœœœœœœ››š˜™™˜—–––’ƒ~ƒƒ…‡„~z}‚‰ˆ…„†‡ˆ‰ŠŠŠ‰‰ˆ†…~}|zxvtqnkjijkjhea^dq€‡• ¥¦§¨§§¦¦¥¡œ•ˆzpja[dgglrtsrroonoorwz|…††††††„ƒ|xvx}rcgpy€Š‰ŠŽŽŽŒŒŠ†}zxwurqpopstuwxyžžœœœœœœœœœ›š™˜˜———–––”…~€~~…‡‡„}€„‡††‡‡‰‰ŠŠ‡††„}{yxvutrojggghghihdbnŽ•› ¤§¨§§©¨§¦£—tl^W```eosrpomllmmprvx|‚…††††……„ƒ€{xx{~zjdlt{‚‹Š‡ˆ‹Œ‹‹‹ŒŒŠˆ„}|{zwvuttsssuuwžŸžœ›œœœœœœ››™™˜—–—————•†~€}{z{~…‡‡†‚††‡‡ˆ‰‰ˆ‡†ƒ€{zxwtrqpmhfddfggghhdg|Œ•œ £¤§¨¨¨©©©§£ž˜‘…vkVT\\[`hmnnlkjijlnoruy~ƒ††††…„„ƒ€|xx{zrggmuzˆŠ‡††‰ŒŒ‹ŠŠŠ‹Œ‹‰†~~}|{{{yyxwttuvžžœ››š››››œœœ›š™˜—•”••••”†{|xwxy{|~ƒ‡‹Š…ƒ…††‡‡‰ˆ…ƒ~{yvsrqmkifa^^]`ceffghiwŽ™Ÿ¤¦§¨ªªªª©©¨§¥¡œ•‹|gOTZXZ_fjjjhhhhijlmoqv{€„…††……„„‚}zyywpiiov{€„††‡‡Š‹Œ‹‹Š‹‹‹Š‰‡ƒ€}|zywvvyzywvwœš™™š››››œœœ›™™˜–•”’’“’’‡yvtsvxyxy…‡ŠŠˆ‡‡‡††……|wttpnnljid]YWWY_bbdeinw‡•£¨ªªª««««©©¨§¥£ž™ƒiMPUUZaghgggfeghkkmnpsx}„†………„„ƒ€~|zxusruy|„…‡ˆŠŒŒŒ‹‹‹ŒŒ‹Šˆ‡„€}zxtstxz{zyxœ›œš˜˜™š››œœœœœ›šš™—•“’‘ŒŒ„sgehnuvtv{‚…‰Š‹‹Š‡†…ƒ}xwsppmjijihbZTRPSZ^aeek}ˆ‘› ¥©«ªª«««ªª¨¨¨¦¤ œ“ˆpNMRU\dfeddcbbeghklmnquzƒ………„„„ƒ‚~}~}~~€„‡ˆ‰‹ŒŒŽŽŽŒ‹Š‰‡†ƒ€}|{ywvvvwwxœ›™™˜˜˜™š››ššš™–’Œˆ‡†yeTUW\hptvyƒ‡ŠŒŒŠ‡…€|wrpomkifefghaXRNMOUZaggtˆ‘š ¤¦©«ª«¬¬«ªª©©¨§¥¢ž–ŒpNNTY^cdca`_^_aeghijlmsw|€ƒ…„‚‚ƒƒ„„„††††††ˆ‰‹ŒŽŒ‹Š‰††…‚€€€}ywvuvw›š˜˜™™™™šš›››››ššššš™•‰ƒ€yiZOONQ[fotw{€…ŠŒŒ‰ƒ}xtnjigdb``aceaYQMMOT[cko‚‘™ ¤¦©«««¬¬¬¬«ªªª©¨¦¤¡›’rPTZ__`aa_\ZY\_bcdfghkquy~‚€€ƒƒ…†ˆŠŠŠ‹ŠŠŒ’’“”““’’‘ŽŒ‹‰ˆ‡†…ƒ€€€~~~}{zš™™™š™™™ššššššššššššš—‹…|lYPKLLNU^clu{€…Š‹ŒŒ‹ˆ‚{uqjfcb^^^^``a_ZSOOQU[eo{Žœ¡¤¦¨««¬«¬­®­¬¬«ª«©§¥£Ÿ™„ddhid_a`]XTTWZ]_abdgjotx{€€€€‚„…‡ŠŒŽ‘’”““““’’ŽŒ‹Šˆ†…„}~~€€}˜˜˜™™™šššššššš™™™™™™™—’‹ƒ|o^MCEJMS[aku~‚†Š‹‹Š‰†‚|uphca_\\]\]]^]ZVSSTV[ft…˜Ÿ¢¦©ª¬¬­¬­®®­­­¬«ªª©¦¥¡š}yxrf_`]XROPRUX[^aehlpswz}€„†‰‹‘‘‘‘‘‘‘‘“””””“’‘‘‘ŽŒŠˆ……ƒzxwx{€˜˜˜˜™™™™šššš™™™™™™™˜•Šƒ|yk\OBAFGLTcox…ˆ‹Œ‹‰ˆ…}uqjca_\[[[ZZZZZXUUVW\hzŸ£¥¨ª¬­­¬­­®®­­­¬¬ª«ª¨¦¢—Žˆ‚ug^ZTNJJKNQTWZ^fjmpsvy|ƒ„†‰‰‹‘‘’’’‘‘‘’”–—˜˜—–––•”“‘‡…„€|ywtuw{~˜˜˜™™™™™šš™™š™™˜˜˜—•Ž†‚~wm]PHBC@BCNdu„ŠŒŒŒŠ†‚~wrkfca^[YXWWWWYYYYZ[_l‚˜£¥¨ªª®¯®¬¬­­­­­®¬¬««ª©¦£›š’…wgYOICBCEFGIOU\fkmpsux{}||ƒ………‡†ˆ‘‘‘‘‘’’“•–˜š››››š™˜——•“‘ˆ†…ƒ|ywttz}™™™™š™™™š™™˜———–•”“Žƒ}{sg[PHB@ADOV^l|ƒ‡ŠŒŽŽ‹†„zuoieb^[YVTSTTUXZ[\^an…š£¦©«¬¬­®¬­­­­­­­¬«¬«ª©§¤™’…}paNEBDKOLJHFJPXdkmpstuxyyy|€„…………ˆ‹‘‘’‘’““•—˜šœœ›š™™˜–•“Šˆ†ƒ€~{yx{}™ššš™˜˜™š™—•””“’‘‹ƒ{sj^VRJDDHOVbnsy€…‰Š‹ŽŽŽ‹‡ƒ{wsngb]YWSOMOOQTVY\^bo‡›¤¨ª«««¬­­­­­­­­­¬««««©§¢”ƒ|rjbRCDMXaffed[QQU_hlpststuvwz~ƒ„„„‡Š‘’““•–——™›žŸžœ››™˜˜•“‹‰‡…„‚€~~}™š™™™—˜˜™˜–“Ž‹†vl]VTRLIP^jpu|‚††‡ˆˆ‰‰ˆ‰‡„ƒ{wsoha[UQMIFGJLPVY\^ewŸ¥¨ª«««¬®®®®¯®¯­­¬«¬««ª¥—ph_[YPMTYZ[_dhmmbVRYeknpqrrstuy}‚ƒƒ…‰ŒŽ‘‘’““•——˜™›žžžžžžœœš™˜•’Œ‹‰‰ˆ…‚™ššš™˜˜˜˜—“Ž‹„wnf[WTTU\fw~€‚†ˆ‹Œ‰‡‡†‡…„ƒ‚€}zuroi`WOKJFEGIJLNXafo–¡¦¨©ª«¬¬®®®®¯¯¯­®¬«««ª¨Ÿ†lc_[VUVVVRPLNU[bjkaWU`hklllortwy|~€€€€‚†ŠŽ‘’“”–——™››œžžžœ›š˜•“‘ŒŒŠˆ‡†„„š›šš™™˜——•ŽŽŽ…{tof`ZXX[er|„ˆˆŠŒŒŽ‹ˆ††…~}zxuroh_UMGMY`_^ZSOUbw†œ¢¥¦¨ªª«¬®®®®®¯¯­®¬ªªªª¦˜v`][XWVURKGF@EKQV]gj`W]fghgfinrvxz{|}||€„ˆŒ‘’“•––—™››››œœœ››š˜—–”“’’ŽŒ‹‡†………™ššš™™˜—–’ŽŒŒŒ‡~vpkd^[]clu~…‰ŒŽŠ†…„~~~}|zywtrpi]RQ[gpngb^[XW]sŽ—ž£¤¥§¨ª««®®®®®¯¯®­¬«ªªª¦”q\WVVXXSMFDB?CGJOS]ll[Xbfebadhnsvvwxyyz~ƒ‡‹Ž‘‘“””•—˜™™™™™ššš™™˜——˜˜˜˜—–“‘ŽŠ‡„‚‚™ššš™™™˜–’ŒŒˆ‚|uqjebbity~ƒ‰Ž‰…ƒ}|zxwutsrpogXVepnfYPLLNSXZf†—Ÿ££¥¦¦¨ª«¬®¯¯¯°°¯­¬««ª¨£Œm[RRWYVPIDDBDEBHOSZip_P\db^\`fjnstsvxzz{€†ŠŽ‘“““”—˜˜˜˜—˜˜˜˜™˜™™›š™™™–”Œ‰†‚}||˜™šš™˜˜˜—•‘Œˆ„~vpmklosx}ƒˆŒ’‘Š…}{yutrpoononcYgum[J@<=?CKT\dvŽž¢£¥¥¦¨ªª«­¯°°±°¯­¬ª©§¦¡‹qaSSYYWSJ?CBBCFMRX\ep`IS]ZYY]bdgkortwyz{~†ŠŽ‘’“”””•—˜——————˜˜˜™œœ˜––’Š…€zxwuuu˜™š™˜˜™™˜—”‘Ž‹‡vqnnpsx~„ˆŽ‘‘‘‘‘Œˆ‚}{zzxurpjhhijjja`moaQD979<>FOYai~™¢¤¥¦¦¦¨ª¬­°±±±°¯««©¨¥¥¡‘wgVT_]YTF:IOGCKTRV]fq`DKUUVX]`acglnqtwz|~…Š’““””––˜˜————–——˜˜™™šš˜”‹ˆzusrrrr™šš™™™™™™—–”“‹ƒztqpruw|‚ˆŒ‘‘‘‘‘ŽŠ†€{yxwtqnjd`bdedc^enfUH<568=>AKSZcp £¤¥¥¥§©¬®°±°±¯®««¨§¥¤ ˜ˆqXUbbYTJM‚›iBBHKMViq[BISTUY\_`aejmoswz}€…Š’““”–——˜™˜˜˜———˜˜™˜˜˜™—“ŒŠˆ|vsqqqr™™™™™™˜™™—”’‰‚{wutv|ƒ‡Š‘‘’‘‘‘Œ‰„~zwurqnke_]\`ba[[kk]MA88:=?<@JPYbh€š££¤£¤¥§ª¬¯¯¯¯®¬«ª¦¥¥£Ÿ›‘xTTgh[UNNr\ABFJN[mjTCHQUY[\^_`diloswz}€‡ŠŽ’””–˜˜˜™š™™˜˜˜™™™™˜˜˜˜–’Œˆ‚€}{xuuuu˜™˜˜˜˜˜˜˜•’‹†|zxx|‚†Š‘“““’‘ŽŒˆƒ}xtpookga]ZZ[_[MUlfVJA96<B<>MXUZehp¢¤¤££¤§ª¬­­®­­¬ª¨¥¤¤£¡ž“uMOfk]TOIIGBDEINUdl^J@GOTXZZ\^^chlosuy}€…ŠŽ‘“”—˜™šššššš™˜™š™˜™™™˜–“Œ‡‚‚€~zyxx—˜———––––”‘‰…}|{}†‹Œ’’“””“’‘ŒŒˆ‚}wrnmligb]ZXXZWISidUKB<Iia?;GOS]hig† ¢¢¢£§ª«­¬­­¬«©§¥£¤££Ÿ“pGI[k`VRLIGFGJMQZfeTDBHMRVVXZ\]agknrvy}€…Š’“•—™›››œœœœ›š››œš™š™—”‘ŒŒ‰ˆ†…ƒ€}zx“•–––––”“”“‹‡†„‚„‡‹‘•–———”’Ž‹Œ‡‚}wrnljhgb]ZWWWTGQfcUKB<`“t?8@FP^kja{—Ÿ¡¢£¥ª««««ª©©¨¥¤£¤¢£ ’kC>HbdYURONOOOPV^cZICGLPTTSVY[\`glnrwz}€…‰‘“–˜š›œœœœ›œœ›™˜–•“Ž‹‰‡…‚}zwt’“•–––”“••”‹‹‰…†ˆ‹Ž’“—˜˜—˜–“‘‹Š†|vrnkhfe_[XUTUREMbcWMD@FLB8<ELS`kfYn•Ÿ¡ ¡¡¢¦¨¦¥¦¥¥¥¤£¢¢¡Ÿ ž‘iC<>N]\WUTTUTSSZb\NBDLPSUUVY[\_dhmosx|‚…‰Œ“—˜šœœœœœœ™˜—•’’‘‹‰†ƒ~zxtq’“””•••–••“Ž‹‡†‰ŒŽ“•——˜˜—•“‘Œ‰ƒzupljgc_\YVVUUQEDYf^PEA@<8:AJOXdg]Sh”¡ œœžŸŸžœœŸ  ŸŸŸžœ™‹jG=>FNVVSRRRQPQUVLDGT^fiigefedgiknrvy}†ˆŠ‘“–˜šœœžœœ›™˜—•“”“’‘‘ŒŠˆƒ}yurqˆ‹Ž‘’”•”•”••”‘Ž‹‰‘•——˜˜˜—•“‘Œˆ‚~ztoihe_\YWVVUUSJCNfhYOIC@>>FNS_hbVRg’ œ™˜š››š™—––™ž››š˜’„gQFGZ[SPQRSTTVYZXYeu~€|xurnhefilotx}‚†‹Ž‘”–˜šœžœ™™—–•••”“’’Œ‰ˆ†€zvtsƒ…ˆŠ’’’’“”–•“ŒŽ‘’•——˜˜˜˜—•’‘‹‡~yrmieb]ZVWVVWWVRI@Qge[RJGJIMSW`f]TTeŠ›™––—˜˜–•”’’•˜›™™™™™—†rdekz€{zz{z{{}„ˆ‹‘•–•“‹ƒ{vqigjnprwz€…‹‘’‘‘“•˜šœžžžœ™˜˜——–––”“’Š‰‡„|yu€ƒ…ˆŠŒ‘’“”””“‘Ž‘’•——˜˜˜˜˜–“’‹†~ysnifb]YUUXYYYWVQC<Oa`ZSTVVWZ]ada^\gˆ–––—˜—–“’‘‘•–—˜——™™˜–‘†{€‚‡ˆˆ‰Œ’”•–—˜šœ›–ƒ|vqpsttty}„Š”–•“““•˜šœžœœ›™˜˜————–••“ŒŠˆ…~zx~€ƒ…‡ŠŒŽ‘“““””’ŽŽ’•˜˜˜˜˜˜˜—”‘Ž‹…}zwqkhd`\ZZ\[[ZXVRLEFOSRSSTVZ`hs{xppuŠ•—•–•–”’‘‘‘“––•––—™š™—•Š‰ˆ‰Œ‘”—š›žŸžžŸ¡¢¢ —‰ˆ…‚€~‚ˆŽ’–˜š˜——–—˜šœžœœœ›™˜˜˜˜˜˜—––•’‘Ž‹ˆ„}ywy{~€‚„‡‰‹‘’““‘Ž’•˜˜——˜˜™—•’ŽŠ…€~{xsmiebaaceecb`][\WTSSSSSUboz„Š‹„{{—™—–––•“‘’“”––•••–˜š›™˜–”‘‘”˜›ž¡¥©ªª¨¦§¨¨©¨¦¤£Ÿ›––“’’‘Ž’—˜š›œ››š™˜™›œžœœ›™˜˜™™˜˜—–—–•“‰…ƒ~yusvy|~‚„†‰‹ŒŽ‘ŽŽ’•˜˜˜˜˜˜™™–“Ž‰…€|yvqjgeedflpqrsx}€‚ƒƒƒƒƒ„‹“–”’ŒˆˆŽ”—™˜–––•”““•–—––•••–šœš™™˜–”•—šŸ£§«¯±¯­«¬¬¬¬­«¨¦¤£ žž›˜–—˜˜šœžžœ››œžœœ›™˜™™™˜˜—–––––“‘Œ‰‡„~ynqsvxz|‚…‡‰ŠŒŽ’•˜˜˜™™™™™—“Š…€~|ytmhfefhlpu|„Œ‘—˜™š™˜———–—˜™˜”‘‘’—™™—•””””””•––––•””–˜œœœž›˜˜—™Ÿ¢¦ª¯°¯®­­­­­­«ª§¦¥¤¢¡Ÿž››œœœžŸžžŸŸŸŸžžžžœ›šš™˜˜˜™˜˜—––––•“‘Ž‹ˆ„hlnpsuxz}€ƒ†ˆŠŒŽŽŽ‘”—™š››š™˜—•“‰„‚zupkffhklp{‡”›¡¡¡¡Ÿœœœš™š™—“‘’’•™š™–“’’”•••–––––•““”˜šž Ÿœšš› £¦«­®¯¯®®®®­¬««ª¨§¥¥¤¢¡žžŸŸŸ   Ÿ  ¡ ŸŸŸžš™™™™™˜™™˜—–––•“’‘Ž‹ˆ…dfhkmprtw{}ƒ†‡‰Œ“–™šœœœš™˜—”ŽŠ‡ƒ€}xsnlkmpqu‹”œ¡¥¥¥¥£¡   Ÿž›š˜–’“”˜››˜–“‘“•–––——––••““•˜š ££¡ŸžžŸ¢¤¦ª­¯°°°°°°¯®¬«ªª©§§¦¥£¡¡¡ ¡¢¢¢¢£££¢¢¡ ¡¢¡¡ žŸš™™™™™™™™˜–––•”’‘‘‘Œ‡ƒ`acegilnptvy}€ƒ†‡‰ŒŽ‘•˜™œœššš™—•’Œ†€{upnoruzŒ”œ¢¦©ªª©©¨¨¨¨§¤Ÿš˜–•—›œœš–’Ž‘”–—––———•”““–™›Ÿ¢¤¤£¢¢¢££¥§«®±±°°°°°°°¯­ªª©©¨§¦¥¤££¤££¤££¤¤¤¤££¤¤¤££¢¢ ž›™™ššš˜˜˜—•–––”’’’’‘‰‚|]_acdfhkmoruy}€‚„‡‰Œ‘“–˜šœ››œ›—•“‘‹†ƒ~xtrtvz‚Ž–œ£¦©ª«­®®®­­¬«§£Ÿ™˜˜›žŸ–‰Œ’”–•“”“‘ŽŽ’™ ££¤¤¤££¤¤¥¨«®°±°°°°°°¯¯­¬«ªª©¨§§¦¥£¤¥¥¥¥¦§§¦§§¦§§¦¥¥¤£¢Ÿš›››š˜˜˜—–—–––”““’’‘Œ‚|\^`abceghiloty|~„ˆŠ“–—˜šœœœœœ›™˜–‘Œ‡ƒ~~‰•› ¦ªª«¬­¯¯¯®¬«©§¤ ›š›Ÿ ¡¡ž”‰„…ˆŠ‹‹ŠŠˆ‡…ƒƒ…Œ•ž¢¤¤¤¤¥¥¤¤¥¦¨©¬¯°°¯¯¯°±°¯®¬««ª©©§¦¥¥¥¥¦¦§¨¨©©¨©©©©©¨§¦¦¥£ œœ›œš˜˜˜˜————˜——–”“‘Ž…€Z[^__``bcdgjotwy{~‚…ˆ‹‘”–˜œžžžžžœš–’ŽŽ•œœž£§ª¬¬­­®®®¯­«©¦¢ ŸŸ¡¢£¡œ‚€ƒ‚€€~‚‰’›¡££¥¤¥¥¤¥¦¦¨¨©¬®¯¯°°°°°¯­¬««©¨¨¦¦¥¦¦§§§¨©©ªª©©©©©©©¨§¦¥£¡Ÿœ›››™˜˜˜˜˜˜˜™˜™˜—–“’ˆXY[\\\]^_abfjortuwz}€„‰Ž’•˜›žžžŸ ¡¢ ž›˜—–———˜˜œ¡£¤§©ª¬¬­®®®®®­«©¥¢¡ Ÿ ¢¢£¢Ÿ˜‰}~€‚ƒ‚‚‚ƒ‚„„„†‹Ž–œ ¤¥¥¥¥¦¦§©©©ª«­¯°°°°°¯­¬«ª©©¨§§¦§¨©©©ª««ªªª©©ª¨¨¨¨§¦¥£¡Ÿœ›››š™™™™™™™™™™™—––•””WYYYYZZ[\]_afkmnortw{}ƒŒ’•—›ŸŸŸ¡¢¢¢¢¡ ŸžŸŸ  ¡¢¤§ªªªª¬¬­®®®®®­«¨¥£¢¡ ¢£¢¢£ž~…ˆŠ‹‹‹ŽŒ‰ˆ‡Ž›¡£¤¥¥¦¦¦¨©©ª©©ª¬­®®®®­¬ªª©¨¨¨¨¨¨©ª«««¬¬¬«ªªªªª©ª©¨§¦¥£¡Ÿœ›››š™™™™™™™™™™˜˜˜˜˜™šYZYYYYZZ[\]^bgjkmoqtwz‹“•˜šŸ  ¢£¢£¤¤¤¡¢£££¢£¥¦¨ªªª«¬¬­®®®®®¬«©¥¤£¢££££¡ ˜‚wx~†ŠŒŽŽŽ‘’Œ„~~}z…˜Ÿ¢¤¥¥¥¦¦§¨©©©¨§¨ªª«««««¨§¨¨§¨©©ªª«¬¬­­¬¬¬««««««ªª©¨§¦¥£¢ œ››šš™™™™™ššš™š™ššš›œ›XZYYZYZZZ[[[]behjlorvy~ˆ‘”—™œŸ ¢¤¤£¤¥¥¦¤¤¤¤¤¤¥¦¨ª©ª«««¬­®®®­­¬ª§¥¤£££¢£¢¡žxrsw{z‹Ž}gju|‰™ ¢¤¤¤¥¥¦¦¦§¨¨¨¨§§¨©©ªª©¨¦¥¦¥¦§¨©««¬­­­­­¬¬¬¬¬¬«ªªª©¨§¦¤££¡žžœšš™™™™ššš›œœœœ››š›šXYYYXXXYZZZZ[]_beikmqv{…’–™œž¡¢¤¤£¥¦¥¦¥¦¦¦¦¥¦§ª«ªª«ªª­­­®­¬¬«©¦¥¤££££££ œ‘uurfao…ŽŽŽ‰pgvˆ“ž¢£¤¦¤¥¥¥¥¥¥¦§¨¨¨¨¨¨¨©©©§¦¥¢¢£¤§©ª««¬­­¬­­¬¬¬¬¬­¬«ªª©©§¦¥£¤£ Ÿžœšššš™™››š››œ›š™˜™™XYYXUUVWYZZZ[\]`cefimty‚‰•˜›Ÿ¡¤¥¤¥¦¥¥¦¦¦¦¦§§¨ªª©ª«««­­®­¬««©©¦¥££¢££££¡Ÿž”‡„wnt„‹Ž‡txŽšŸ¢¤¥¦§¦¥¥¥¥¥¤¦§¨¨¨¨©©©©¨¨¦¦¥£££¥§©ª««¬¬¬¬¬¬¬¬¬¬¬¬¬«ªª©©§¦¥¤££ Ÿžœš™šš™™™™™˜˜˜˜˜˜———XYYWTRTUVWWYYZ\]_begmt{ƒ‹’–˜›Ÿ¢¤¥¦¥¦¦¦¦§¦§©©¨©¨¨©ª««¬­­®¬«ªª¨§¦¥£¢¢££¤¤£¢¡Ÿ›—“†‚‡‹‹ŒŒˆ€‹œ ¢¢¤§¨¨¦¦¥¦§§§©©¨©©©©¨¨¨¨§¦¥¥¥¤£¥§©ª«¬¬­­¬¬­¬¬¬¬¬¬««ªª©©§¦¥¤¤¢ Ÿžœ›š™™™™˜˜—–••••––•””YYYYURSTSRSUWY[\^bgls|ƒ‹‘•˜™›Ÿ¢£¥¦¦¦§¨¨¨¨©©©©©¨¨ª«««¬¬­«ªª©§¦¦¦¥£¢££¤¥¥¥¦¥¤¤¢Ÿš“ŠŒ‹‰ˆ‡Š•Ÿ£¤¥¥¨©©¨¨§¦§©©©ªªªª©©©©©§¦¦¥¥¥¥¤¦¨ª««¬¬®®®®­®¯®­¬¬«ª«ª©©¨§¤¢¢¡Ÿœ›™˜˜——••”•’’‘‘‘’“’“Z[\[XUUUUTTUWY[]`els{†‘”—˜™šž¡¢¤¥§¨¨©¨§¨©¨©ª©¨¨©©ª««««ª©¨¦¥¦¦¦¥¤££¤¤¥¦§§§§§§¦¤“ŽŒ‡†‡‘œ£¤§¨¨©ª©ª©¨¨¨¨©©ªªªª©©©¨¨¦¦¥¥¥¥¤¤¦¨ª«¬­®¯°¯¯¯¯°¯¯®¬««ª©¨¨¨¤¡Ÿž›™—–•”“‘ŽŽŒ’\]]]YVXXVWWWWZZ^bgow~ˆ”•˜˜š™œžŸ¢¤¥§¨¨©¨§¨©©©ªªª©©©ªªªªª©¨§¥¥¦¦¦¦¥¤£¤¥¥¦§§¨¨©©§¥¢›”Œ‡‡Œš¢¦¨ªªªªª©©©¨¨¨©©©©©©©©©©¨¦¦¥¥¥¥¥¦¦¨©««®®¯¯¯¯¯°¯¯°¯®¬ª©¨¤¢¡¡žœ›™—•”’ŽŒ‹‹‹‹‹Œ‹Š‹‹Œ‘]^][VSTTTUTTSVY]ciry€‹“–˜˜™™˜šš›ž¤¥¦¨©ª©©©ªªª««««©©©©©©©¨¨§§§¨¨¦¦¥¤¤¥¥¥¤¥§©¨¨¨§¥¤ž˜‘ˆƒˆ“ ¤¦©ª«ªªª©©©©©©©¨©©©©¨¨¨¦¦¦¥¤¤¥¥¥¦§¨ª¬¬­®®¯¯¯¯¯¯¯¯­ª¨¦¤¢žœš˜—••”“ŽŒŠˆ‡ˆˆˆ‰‰‰‰‰ˆŠŒŽ‘’^_][VSSSSSSRRTZ_hpv|„‘–˜˜™™™™š›šœ¢¤§©¨ªª©ª«««««¬«©©©¨©¨¨¨¨¨¨¨¨¨§§¦¥¤¤¥¥¥¦¨©¨¨¨§¦¤Ÿ˜‘„ˆ˜ ¢¤¦¨ªª««ªª©©©©©©©¨¨¨§§§¦¥¦¥¥¥¥¦¦¦¥¨©«¬®¯¯¯°°°°®¬¬ª¦¤¡Ÿœ˜˜˜•‘ŽŒ‹ˆ†„‚‚‚…†‡‰‰ˆ‰‰‰‹ŒŽ‘`a`]ZXWVTTTTTW\`fov‹”—˜™™™™šš››¢¤¦¨¨ª«ªª«««««««©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¦¥¤¥¥¦¦¦¨¨¨§¦¦¦¥Ÿ˜’„ˆ˜œŸ¡¤¦¨©ªª©©©©¨¨¨§§§§¦¥¥¥¥¦¥¦¦¦§§¦¥¦¨««­®¯¯¯°°®¬«ª§£ ™–’Š‰ˆ‡‡†„‚€~€‚‡‰Š‹‹‹‹ŠŠŒŽcbba^\ZYXVVUVX\`enx„‘—˜™™š›™™š›œž¢¤¦¨©ª«««««««««ª©©©¨©¨¨§¦¦§§§©¨¨§¦¥¦§¦§¦¦§§¥¥¥¥¤Ÿ™“ˆ~†–™›ž ¢¤¥§¦§§¨©©©©§¦¦¦¦¥¥¦¦§§§§§§¨©§§¦§ª«­®¯¯¯¯¯¬«©§¥£Ÿœ—”Žˆ…ƒ…††††„€‚†ŠŒŒŒŒŒ‹‹‹ŽŒhhhfdb`^\[ZXX\aflu~‹“—™ššš›™™™›ž¡£¥¨©ª««««¬¬¬««ª©¨©©©¨¨§¦¦¦¦¦¨©©¨§¦¦§§¦¦¦¦¥¤¤¤¤¢—‘„yŽ–šžŸ ££¥¥¦¦§¨§§§§¦¥¥¥¦¦¨§¨©©©©©©¨¨©ª««¬®¯­®®®¬«©§¦¥¤¡ž™–“Š‰Š‹Œ‹‹Š†……ˆ‰ŠŒŒŒŒŽŒmmnljhfdca^]_ekr{ƒ‰•˜™šššš™™™›ž¡¤¥¨©ª««««¬¬¬¬««©¨¨©©©¨¨§§§§§¨©©¨§§§¦¦¦¦¥¤££¤£¢ž™“Š~v{†”™ ¢£¤¥¦¦§§¦¦¥¥¥¥¥¥¥¦§§©©©©©©©¨¨©¬¬¬­®®¬¬¬¬ª©¦¤   žš˜–”’’’“””””’Ž‘ŽŽqqrqpnmljgedipw}ƒŠ‘•˜™™šššš™™™›œž¡¤¥¨©©««««¬¬¬¬¬¬«ª©©©©¨¨¨¨¨¨¨©©©©¨¨¨¦¦¥¥¤£££ žœ˜‘Œ†€{}‚ˆŠ•š ¤¥¥¤¥¦¦¦¥¥¥¥¥¥¥¦§©©©ªªª©ªª«¬­®®­®®­¬«ª©§¤¢Ÿžžœš™—————˜™™—–””“’ŽŒŒŒŒŒvvuutttsqnmpw{ˆŒ‘•˜™™™™™™™˜˜™›œž¡£¥¨©©ª«««¬¬¬¬­­¬¬ª©¨¨§¦¦¦§§§¨©©§§§§¦¥¤£¢¢ ›˜”Ž‰ˆ†‚‚………†……ˆ‘™Ÿ¡¡¢£¤¥¥¥¥¥¥¥¥¦¨©©©©©©©ª««­®®¯®¯¯­¬«©§¤£¡ ŸŸžžœ›š™˜——˜™š™—––•“’‘‘‘Ž‹‹‹‹‹zzyyyxxxwvx{€…Š‘“•——˜˜—˜˜˜˜˜˜™šœ £¥¨¨©«¬¬¬­®­®¯°¯­¬ª©¨¨§¦¦¦¦¦§¨§§¦¦¥££¢ Ÿœ˜•“‹ˆˆ‰ˆ‡‡‡†††‚||€†“˜¢¤¥¥¤¤¥¦¦¦§©©ªª©©ªª«¬­¬­¯®®®­¬ª©¦¤£¡¡ Ÿžœ›š™˜˜˜˜˜˜—–•••”“’’‘‘ŽŽŒ‹‹‹ŠŠ}}{z{{z{}‚„ˆŠŒ’•••••––——˜™™™™šœ £¥¨¨ª¬¬­®¯¯¯°±²²±¯­«ª©¨§§¨¨¨¨¨§¦¥¦¤£¢ Ÿœ—’Ž‹‹ŒŽŒŒ‹‰‰‰‡„}ysru~‹—Ÿ££££¤¥¦¦¦¨©©©©ªª¬­¬¬­­­®®­®¯®¬«¨¦£¡  Ÿžœš™š™˜———˜——””••“’’‘’‘Œ‹‹‹ŠŠ~||}}‚„‡ˆˆ‰Š’’““’“–——˜˜˜™™›ž £¥§©ª¬­¯°°°±±²²²²°®­¬«ªª©©©©©§¦¦¥¥¥£¡Ÿš•‘‘“–—–”““’‘‘‘“““Š‡ƒ~{‰’šŸ ¡¢£¥¥¦¦¨©©©©«¬¯®®®¯®®®®¯°¯¯­«©¦¤¢ Ÿžœš™˜™™˜—————–•”“””’’‘’ŽŒ‹ŠŠŠŠ‚}|€‚ƒ…††ˆˆŠ‘‘‘‘’“–—˜—˜˜™›ž £¥¨©«¬®°°±±±²³³²²±°¯®¯®®¬ª©©©¨§¦¦¥¥£ œ™˜˜™š››š˜—–—™››˜•‘“˜šŸ¡¢¤¥¦¦§©©ª©©«­®¯°¯¯­¬®­­®­­¬ª¨¥£ žš™˜˜————––––—•”““““’‘‘ŽŒŒŒŒŠŠŠŠŠ€…‡~€€…†‰ŠŒŽ’””–—˜™š›œž £¥¨ª¬®¯°±±±°²³³´³²±±°°°¯­«©©©¨§¦¦¥¤£ ŸŸžž   ¡¡¡  ›ššœžžŸ ¡¢¡Ÿœš—˜™›žŸ £¥¥¦§¨©©©©ª«¬­®°¯­¬«¬««««ªª©¥£¡ž›™˜———––••••–––•“’‘‘’‘ŒŒŒŒŒŒŒ‹ŠŠŠŠŠ‚ŠŽ‰„‚€~||{}„†ˆ‰‰Š‹‹‘’“”–—˜˜™› £¥§ª¬®°°°±±±²³³³²²²²²±°°¯¬ªª©©§¦¦¦¥£¢£¢¢££¤¤¤££¢¡ŸœžŸ  ¡¢££££ ›œžŸ ¢¤¦§§§©©ªªªª«­­®®­«««©ª©©¨§¥¤¡žœ™˜˜˜———–————–––––“‘Ž‹Š‹ŒŒ‹ŒŒ‹ŠŠŠŠ‹…Œ“‹‡‚{zzz}€‚„††…‡Š‘‘’“”––—™›ž £¥§ª¬­¯°°±±°±²²±²²±±³²±°°­«ª©©¨¨¨¨§¥¥¥¦¥¥¥¥¥¤£¤¤£¢¡Ÿ ¢¤¥¤¤¤¥¤£¡žžŸ ¡¡£¤¥¥§©©ªª©ª«««¬¬««ªªª©©©¨§¦¤ Ÿ›™™™˜˜———–———˜——–”“‘ŽŒ‹‹ŠŠ‰ŠŠŒŒŒŒ‹‹‹‹‹‹‹ŠŠ†Ž”•’†‚|yyyy|„……†‡Œ‘’“”•–—™› £¥§ª«­¯°°±±±±²²²±±²²²±±°¯­¬ª©©¨¨¨©©©¨¨¨§¦¦¦¦¥¤¤¥¤¤£¤¤¥¦¥¥¥¥¦¦¦£¢¡  ££¤¥¦¥§©ªªª©ªª««««©ª©©¨¨¨¦¦¥£¢ ž›š™™˜————–——˜˜—–“’ŽŠˆˆ‡‰ˆ‰Š‹‹ŒŒ‹ŒŒŒ‹‹‹‹‹‹Š‘˜™˜–†{zxy{~ƒ„„†‡‹Ž‘’““”•˜› £¥¦ª¬®¯°±²²²²²²²²²²²²²±°¯­¬«ª©¨©©ªª©©©©¨¨¨§¦¥¥¥¦¦¦¥¥¦§§¦§¦¦§¨§¥¤££¢¤¤¥¦§¨©ªªªªªªªª««ªªª¨§§¦¦¤¢¡  Ÿžœ››™˜——————––—–•’‘Ž‹‰‡‡ˆ‰Š‰Š‹ŒŒŒŒ‹‹‹‹‹‹‹‹‹Š’˜™™˜–ƒ|z|~‚ƒƒ„…†ŠŒŽ‘‘‘’’”–™ ¢¤¦©¬­¯°°±²²²²²²²²²²²±±°¯®®­«ªªªªªª©©©©¨¨¨¨¨§§¦¨©©©©¨©¨§§§§¨©©§¤£££¤¦§©¨©«««ªªª©©ªªª¨¨§¦¥¤¡ žœœœœœœ›šš™˜—————–––•“‘ŽŠ‡ˆˆŠŠ‹ŒŒŒŒŒ‹‹‹‹Š‹‹‹‹‹’˜™™™˜‘‡‚€~€ƒ„„‚‚‚ƒ…‰ŒŽ‘‘’“•™ ¢¤¥¨«¬¯°°°±²²²²²²²²±±±±°°¯¯­¬¬«ªª©ªª©©¨¨§¨¨¨¨¨§©ªª©ª©ª©§¦¦¦¦¦¦¦£££¤¥§©ª¨©«««««ª§§¤¢ ŸžžŸŸœ››š™™™™™™™™™———–––––•“‘Ž‹Š‰†ˆ‰Š‹ŒŒŒŒ‹‹‹‹‹‹‹‹Š‹‹‹‹Œ“—˜šš™–‡††Œ‰…‚„ˆŒŽ‘’’•˜œŸ¢¤¥¨ª«­°°°°±±±±±±±±±±±±°°¯¯®¬«««ªª©©©©¨¨¨¨¨¨¨§¦¨©©©©©ª©©¥¤¥¥¤¤¥££¤¤¥§©¨¨©ª«««¬¬©¦Ÿš˜˜—˜˜™™™˜™˜™™˜™™™˜˜———•–••–•’Ž‹‰ˆ‡†‡‰ŠŠŠ‹‹ŒŒ‹ŠŠ‰Š‹‹‹‹‹‹‹‹‹‘•—™™™—””šš•‰†ˆŒ‘‘‘“–—›ž¡£¥§©ª­¯°°°°°°°°°°°°°°°°¯®­¬«ªªªªª©©ªª©©©¨¨¨¨§¨©©¨¨©©ª©¨¥££¤£££¢££¥§¨¨¨¨©ªªª«¬¬«§ š˜—˜˜˜——————˜˜˜˜˜˜˜—–—–•••”“‘Œ‹Š‰ˆˆˆ‰ŠŠ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠ‹’–šš™˜––—˜™–‰…„‡Œ’‘‘‘‘’“–—š £¥¦©«­®¯°°°¯¯¯¯¯¯¯¯¯¯¯®®®¬««ª©©ª©©©ªª©ª©©©©¨©©©¨¨©©©©©¦¤£¤¤¤£¢£££¦¨©¨¨¨©ªªª«¬¬ª¥Ÿš˜—˜˜———––—————————–––––•”“’ŒŒŒ‹‹ŠŠŠ‹Š‹‹‹Š‰‰ŠŠŠŠŠŠŠŠŠŠŠŠ‹Œ’”—˜š˜˜˜—˜—–‘Š„‚„‹’””’’‘‘‘’’”–—˜œŸ¢¥¦©«®®®­¬­®­­®®®®®®­­«««ª©©©©©©©©©©ªªªªªªªª¨¨¨¨¨¨¨¨¨§¥¥¥§§¨¥¤¤¥§¨©©¨¨¨©ªªª«¬¬ª¥Ÿš˜˜˜˜———–––––––––––––––••“‘‹‹ŒŒŒŒ‹‹‹‹‹‹‹‹‰‡‡‰ˆˆŠŠŠŠŠŠ‹‹‹Œ•—˜˜š™™˜———”ˆ„ƒ‰’——–•””“’’’“•–˜›ž¢¤§©«­¬¬«««¬««««««¬¬«ªª©©§¥¥¦¦§¨©©©ªªªªªªªªª©¦¦¦¦§¦¦¦¦¥¦§¨©©¨§§¨©©ª©§¨¨ªªªª«¬¬¨¢žš˜˜˜˜—–—•””””””––––––—•”“’‘‘ŒŒŒŒŒ‹‹‹‹Šˆ‡†ˆ‡‡ˆ‰ŠŠŠ‹‹‹‹Œ–˜˜š›™™˜˜——•‘ŒŠ‹“˜™š›š˜––”“““”••—œ ¤¦©ª«ªªª©©ªª©ªª©ªª©¨§§§¦¤¢¢¢£££¤¥¦¦§¨©©ªªªª©§§§¦§§¨¨§§¨ª««ªª©©ªªª©¨§§¨ªªª«ªª«§¡œ™˜˜™˜–”•“““““““•––––•–”“’’’‘‘ŒŒŒŒ‹‰ˆˆˆ†……††‡‡ˆ‰‰Š‹‹‹ŒŒ‘˜™šš›š™™™˜˜—”‘“˜›œš———”““””•–›Ÿ£¦§©©©©§§§¨¨¨§§§§§¦¥¤¤££¢ ŸŸ ŸŸžŸŸ ¢¤§©©ªªª©¦¦¦¦§§©ª«¬­­­­­¬««¬¬¬«©¨¨¨©©ªª©©§¦¡œ˜˜™™˜–”““““““““”•––––••“’’’‘‘’“ŠŠ‹Œ‹Š‰‰‡†………†††ˆˆ‰‰‰‹‹ŒŒŽ”™š››››š™šššš™˜™›œœž›š˜˜—•””••—˜šŸ£¥§¦§§§¦¦¦¦¦¦¤¤¤¤¥¤£¤¢¢¢¢¡Ÿžž £¥¨©¨¨¨§¤¤¤¦§¨ª¬­¬®®­®®­«¬¬­¯¬ª©©¨¨§©©©¨¦¤ œ˜™—˜˜—”“’’“•••••”•–——–•“’‘‘’’““’Ž‰ˆ‰ŠŠ‰ˆˆ‡…ƒ‚„†††‡‡‡‡‰ŒŽŽŽŽ”˜š›œœ››œœœœœžžžŸŸžžœ›šš———––˜™œ¡¤¤¦¥¥¥¥¦¦¥¥¥¤££££¤£££¢¢£££¢ ŸŸžžžžŸ¡¢£¥¦¨§¦¤¡¡¢¥¨ª«­­¬­­®®­­¬¬­®°­ªªª©¦¦¨§§¥£¡Ÿœ˜˜——––•“’“•–——––•”––—––••”““’““‘‹ˆ†††‡†‡‡…ƒ€„†††‡ˆ‡ˆŒŽŽ•˜™›žžŸŸ¡¡¡ ¡¢££¡Ÿžžœœš™™™™šœžŸ¡££¤¥¥¥¥¥¥¥¤¥¤£££££££¢¢£¤£££££¡    ¡¡¡¢£¤¥¦¨¦¥£¢¢¦ª¬­®­¬¬¬«««««ª­¯¯«ª«¬ª¥¤¤£¢¡Ÿœ™˜˜—–•–•““•–—˜—––•”•—–––———–”’‹…ƒƒƒ„††‡…‚€‚…†ˆˆˆŠŠ‹Ž‘ŽŽ–™šž ¢£££¤¤¤¤¥¦¦¦¥£¡ Ÿžœ››œœž ¡¡¢¤¤¤¥¥¥¥¥¥¥¥¥¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤£££¢¢¢¢¢¢¢¢£¤¥¥§§§¥¤¤¥ª¬®¬¬ªª©¨¨¨¨§¨ª®¬¨§ª¬©¥£¡žœ›šš˜˜———––––————˜—––”“”–––•”““’‘ŽŒ‰†‚ƒ„…††…‚€€€„††‡‰‹‹ŒŽŽ—™ž £¦¦¦¦¦¦¥¥¥¥¦¦¥¦¥£¢¡   Ÿž ¡¢¢¢¤¤¤¤¥¥¥¥¥¥¥¥¤¤£¤¤¤¤¤¤¤¤¤¤¤¤¤¤£¢¢¢¢¢¢¢¢¢£¥¥¦¨§¦¥¤¤¦«­¬ª§¦¥¥¥¤¤£¦©¬«¨£¤¨§¥¢ žš˜˜——————˜˜—˜˜——˜˜—––––•••”“’Ž‹‰‡…„…†‡‡………ƒ€‚„††‡ŠŒŒŽŽ™œŸ¡£¥§¦¦¦¦¥¤¥¤¥¥¥¥¥¥¥¤¤££¢¡   ¡¡¢¢¢££¤¥¥¤¤¤¤££¢¢££¤¤¤¤¤¤¤¤¤¤¤¤¤£££££££¢¡¡¢¤¥¥§¦¦¤£££§ª«©¥¤£¢£¢ ž¡£¥§§›Ÿ¡¢ Ÿžœ˜”“•——˜˜™™™™™™˜˜˜˜—––––•”’‘ŽŽŒ‰ˆ‡‡‡‰ŠŠ‰‡†…„„………‡‡‰‹‘‘‘™œŸ¢¤¥¦¦¦¦¦¥¥¥¥¥¦¦¦¥¥¥¥¥¥¥¤£¢¢¡¡¡¡¡¢£££¤¤¥¤¤¤££¢¡£££¤¤¤¤¤¤¤¤¤¤¤¥¤¤¤¤¤¤¤£¢ŸŸ£¥¥¥¥¥£¡ ŸŸ¤¦¦¤¡Ÿ ¡¡žœœŸ¡ž˜˜œœš•“–—™™™™™™šš™™˜—–•–––“’‘ŽŒ‹‹‹‹‹‹‹Šˆ†††‡‡‡‰Š‹‘‘Ž˜šž ¢¤¥¥¥¥¥¥¥¥¦¦¦¦¦¦¦¦¦¦¦¦¦¥¥¤¢¢¢¢¢££¤¤¤¤¥¥¥¥¥¤£¢£¤¤¤¤¤¤¥¥¥¥¥¥¤¤¤¤¤¤¤¤£¢ žŸ¢¥¦¦¥£¡ŸžžŸ £¡Ÿžž¡£Ÿ›š™™šœš˜—˜›š™˜–‹“—™™™šš›ššš™˜—–••••”“‘ŽŒŒŒŠŠ‰‰‰‰‰ŠŠ‹Ž‘‘’‘‘‘‘ŽŽŽ—™Ÿ¡¢¤¤¤¤¤¥¥¦§§§¦¦¦¦§§¨¨©¨§§§¦¥¥¤¤¥¥¦¥¦¦¦¦¥¥¤¤¤£¤¤¤¤¤¤¥¥¥¥¥¥¥¥¤¤¥¤¤¥¤£¢ Ÿ £¥¦¦¥£¢Ÿžœš›œ  ŸžžŸ£ ›™˜••—˜˜—–™™™–‘‹‰“–˜™š›››š™˜˜—–“““’’‘‘ŽŒŽŽŒ‹ŠŠŠŠ‹Œ‘’’’’’’‘–˜œž ¢¢£¤¤¥¥¦§§§§§§¨¨¨©©©©©©©©©©¨¨¨¨©¨¨¦§¦¥¥££££££¤¤¤¤¤¥¥¦¦¦¦¦¦¥¥¥¥¥¥¤¢¡Ÿ¡£¤¥§§¦¤£¡žœ™™šžžžœ™—’’•˜˜–˜™—“Žˆˆ–—˜˜™™™™˜———–”““““‘ŒŒŒŒŒŒŒŒŒŽ‘’““’“’’‘‘Ž•˜šž¡¡££¤¥¥§§§§¦¦§©¨©ª©©©©©©©©©©©©¨¨¨¨¦¦¦¥¤£¢¢£££¤¤¤¥¦¦§§§§§§¦¦¦¦¥¥¥¤¢¡ ¢¤¥¥§¨¦¥¤¢žœ™™››œœœœ›™—‘Œ“–˜——˜•‘‹†Š”™™˜˜˜˜—˜—–––•”“““’ŽŽŒŒŒŒŒŒŒŒŒŽ‘‘‘’’““’’’’‘‘‘’‘“–šž¡££¥¥¥§§§§¦¦¨©©©©©©©©©©©©©©©©©¨©¨¦¦¥¤£¢£££££¤¤¥¥¦§¨¨¨¨¨¨¦¦¦¦¥¥¥¤£££¤¥¥¥¦§¦¦¥¤¡žœœœœœš˜•‘Œ’•–––•“ŒŒ‘—˜˜——–––—–••”““’’’‘ŽŽŒŒŒŒŽŽŽ‘‘’“““““““”““’‘‘‘’“’‘‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€‚ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚€€‚€€‚‚ƒ‚ƒƒƒƒƒƒ‚‚‚‚‚‚‚‚€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ‚~~~~€€ƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒ‚‚‚~||||}}€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚~~zzz{{|~€‚‚‚ƒ‚‚ƒ‚€€~~}}~~~~€€€€‚‚ƒ‚‚‚‚‚‚‚ƒ‚‚‚‚‚‚‚‚ƒƒ€~~~zxyyz|}~€‚‚‚‚‚‚‚€~~}||}}}}~~€€€€€‚‚‚‚‚‚‚‚ƒ‚‚‚‚ƒƒ‚}|zyyyz|~€€‚‚‚ƒ‚‚‚‚‚‚~}|{{{{||}€€€€€€€‚‚‚‚‚‚ƒƒ‚~|zyzz{}‚‚‚‚ƒ‚‚‚‚ƒ‚}||{z{{||}~~~~}~}~€€€€€€€€€€‚‚‚‚ƒ‚‚€}}zyyz|~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒ‚‚‚‚‚‚|{|zz{{{{|}}}}|}}}~~~~~~€€€‚‚‚‚‚‚‚‚‚€}|zyzz{}~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚}||{{{zz{||||{|{{|||}}}}}}}}}~€€‚‚‚‚‚‚‚‚€}|zzzz{||~€€€€€‚‚‚‚‚‚‚ƒ‚€~}|{zxyxz||{{||{{|{{|||||}}|}}~€€‚‚‚‚ƒ‚‚}|zzzz{}}€€€€€‚‚‚‚‚‚‚‚€~}|{zzwwyz{{zzz{zzz{{||||||||}}€‚ƒƒ„ƒ„ƒ|zzzyz|}€‚‚‚‚‚€€€€‚‚‚‚‚‚}|{{zyxxyzzzzzzzzz{{z{{{{||||~~~~~~€‚ƒ„„ƒ„ƒ‚}zzzyz|~€‚‚‚‚‚‚€€€€€€€€‚‚‚‚~}}|{yyzzzzzzzz{{{zz{|{zz{|~~€€~~~‚ƒ„…„ƒƒ|zzzzz|€‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€~~|{zyz{zzzzzz{{{{{{||{{{|~€€€€~}}~‚‚„…„ƒƒ‚|zzzzy|~‚‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€~€€€€}{zzzzzzzzzz{{{{{||{{{{|}€€€€~~ƒ„„…„„„ƒ}{zzzz{~€‚‚‚‚‚‚‚‚‚€€€€€€€~}~€€~|zyzzzzzzzz{{{{{|{{{|{}€€€€€~~€‚ƒƒ„„„„„ƒ|zzzzyz~€‚‚‚‚‚‚‚€€€€€~~~~€€€€€~~}zyzzzzz{zz{{{{{||||}}~€€€€€ƒƒ‚ƒ„„ƒ‚‚|yxyyxy~‚€€€€€€~}{yzzzzz|{zz{|||||}}}}~~€€€€€‚ƒƒ‚ƒ„„ƒ‚‚{xxyyxy}~€€€€€€€€€€€~}}|zyzzzz|{zz{|||{|~}}}}~€€€€€~‚ƒƒƒƒ‚‚‚ƒ‚~zxxyyxz|}~€‚‚‚‚‚‚‚‚‚‚‚‚€€€€€}}|{zzzzz|{{{{{||z{}||||}€~~€‚‚€}zyyyyx{{}~€€‚‚‚‚‚‚‚‚‚‚~~~‚€€€€}||{zzzzz{{|}}||{yyz{{{||}}~~~~~€€~}{zyxxx{{|~€‚~~~€‚€€~||{zzzzzz{|~~}{yzz{||||}}}~~~~€€~}}{zxxxyz||~‚‚€~~~€€~}|{{{{yzzz{~}|zzzz||||}}}}~~~~~~}€~~|{yxxxyyz|~~€€€€€€‚€~}||{z{z{zz|}|{zzzzzz{|}}}}}~~}}}}}€€€~|{zyxxxxyz|}}€‚‚€€~}|{{zz{zz|~~~~}{zzzzyyz{|}|}}}}|}||}~~~}|{zyxwxxxx{|{‚‚‚‚‚‚‚‚‚‚ƒ‚‚‚‚‚€}}|{{zz{{{}€€€€~|zzzyxxzz{||||}}||||}}}}~~}||zzzyxyyxxz{{‚‚‚‚‚‚‚‚‚‚ƒƒ€~~~~}|||{{{|}}~€€€|zz{yxy{{{{||{|||||||}}}|}|{{{z{yyyyxxzz{‚‚‚‚‚‚~}}||||||||}~~€€}{|||{||||||||||||}||}||{{{z{{||zzzzyyyzz‚‚‚~}|{{||}}|}~€~|||||||||||||||||}{z|{zzzzz{{{{{{zzyyyzz€€€€€€€}|{{{}~~~}~€€€€€€€~|||||||}}|{{{||{{|{z{{{yyz{||||{{{zyzzzz€€€€€€€‚€‚€~}{z{{}~~~~€€€€€€€~}|}}|}~}|}|||||{z|zz{z{zy{{|||||{{{zzz{z‚‚‚‚€€€€‚€€€€€€€€}}{|||~~~€€€€~|||}}}}~~}}|||{zz{yyyyyz{{z|||}}||||{{z{‚€€€€€€€€€€~~€€~||}~~~€€€€€€€€€€~}~}}~~~~~~~|{{zzzzyyyyyz{{z{{}}~}||||{{{€€€€€~~~~~}}}~€~}|}~€‚‚‚‚‚‚€€€€€~€~~~}||||{zzyyyzzz|{||}~~~}}|||||€€€€€€~~~~~|{||~~}}}~€€‚‚‚‚‚‚‚‚€€€€€€€€€€~}}|||zzyxxzzz{{|||}~~}}|||{|€€€€€€€~~~~~|{{|}}}~ƒƒ‚‚‚‚‚‚‚‚€~}~€€‚ƒƒƒ€}}}||{xwwxzzz{{{{||}~~}|{z{{€€€€~~~~~~~~~|{||}}}~ƒ„„„ƒ‚‚‚ƒƒƒ‚‚€€~}~€€‚‚ƒ~~}||{xwwyzzz{|{|||}}}}|{zz{€‚€‚€~~}}~~~~~~~|{{{|||ƒ„††„‚€ƒƒ‚‚ƒƒƒ‚€€~~}}}~€‚~}||{zxwwyzzz{{z{||}||||{zzz€‚~~~}}~~~~~~}{zzz{{ƒ„……ƒ‚ƒƒ‚‚ƒ„ƒƒ€~}}|~€€‚~}}|{zxwwyzzz{{z{||||{|z{zzz€€€~~~~~~~~~~|{zz{{|‚ƒ„ƒƒƒ‚‚ƒƒ„„„„‚€€€€~~~~€€€€€€€~}|zyyxyyzzz{|{|zz{{z{zzzzy~€€€~~~~~~|{zz{{|‚‚‚‚‚‚‚ƒƒ„„„ƒ‚€~~~~€€€€~{zyyyyyzzz{|{|{{{{{{zzzzy~~€€€€€€€~~~~~|{z{{|}~€‚‚‚‚ƒ„„„„ƒ‚€}|}}}~€€€€€}|{yyyyyzzz|{{|{{{{zzzzzzy}}~€€€€€€€€~~~}~|{z{|}~~~‚ƒƒƒƒ„„ƒ‚~}||}~€€€€€~}|{zyyzyzz{{{{{{{{{{{{{{z{}~~~~~€€€~~~~}|{zzz|}~~€€‚‚‚‚ƒƒ‚€}||||~€~~€€}}}|||{zz{{{z{{{{{{z{{{{z{{|}}|}~€~~}|{{{||~€€€€‚‚‚€~||||~~€~~~~~~~~|||{{|{{|||{{{{z{{{{{zz||||}~~~~}}}}}}}~‚€€€€€‚ƒ‚~}||{{~~~~~~~~~}|||||||}|||||{{||||{zz{{{||}~~€€€€~~~€‚ƒƒ‚‚‚‚‚‚‚‚‚‚~}|zzz|}~€~~~~~~~~}}}}||{{{{|||||||zzzzz{|}||}~€€€€€€€€€‚‚ƒƒƒƒ„ƒ‚‚‚‚‚‚‚€|{zzz{|}€~~~~~}}|{{{|}|}}}}zzzzz{||{{|~€€€€‚ƒƒ„„„„ƒƒƒƒƒ‚‚€~}|{{{|}}~€~~~~~~~~~}}}|||||}~~~~zzzzz{|{z{|€€€€‚‚‚‚‚ƒƒƒƒƒƒƒƒƒ‚‚‚‚‚ƒ€~}||{|}~~~~}}}~~~~~~}|||||||}}~~~zzzzzzz{||}~€€€€‚ƒƒ‚ƒ„ƒƒƒƒƒ„ƒ‚‚‚‚‚‚‚€~}|||}~~~~~}|}~~~~~~}}|||||||||||zzzzzzz{}}~~€€€ƒ„ƒƒ„„„„ƒ„„ƒ‚ƒƒ„ƒ‚‚‚~|}~~~~~|||}~~~}}}}}~}}}|{{{{{{{|zzzzzyy{|}~€€€€‚ƒƒƒƒƒƒƒƒƒ„ƒ‚ƒ„„ƒ‚‚‚‚€~~~~}}|}~~~}|}}}}}||{{{{{{{|zzzzzzz{|~€€€‚ƒƒ‚‚‚‚‚‚ƒ„ƒƒƒƒƒƒ‚‚‚‚ƒ€~~~}|~~€€~}|{|}}}||{{{{{{{{zzzzzzz|}~€€€€‚‚‚ƒƒ‚‚‚ƒƒƒƒƒ„ƒƒ„„„„ƒƒ‚ƒƒ€€~~~~~~€~}}{{{||{{{|{{{z{{{zzzzzzz|~€€€€‚‚‚‚ƒƒ‚‚‚ƒƒƒƒƒƒƒƒ„ƒ„„„ƒƒ„ƒ‚‚€€€€~~~~~€€€~~}{{{{{zzz{{{zzzz{{zzz{{{{|€€€€€€‚‚‚ƒƒ‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„„ƒ‚„‚‚‚€€€€€€€€~~~~~~~}}|{{{{{{z{{{zz{{zz{||||{{}€€€€€€ƒƒ„ƒ‚ƒƒƒ‚ƒƒƒƒƒ„„„„„‚‚‚‚‚‚‚€€€€€€€€€~~~}}}}|{|}||{|{zzzzzz}}|||{{|}~€€‚‚ƒ‚‚‚ƒ‚‚‚ƒƒ„„„„„ƒ‚~~~€€€€€€€€~}}~}|||}|{{{{z{{{z{}}}}|}|{|}}}}~}‚‚ƒ‚‚‚ƒ‚‚‚ƒ„„„„ƒ‚‚‚‚‚€~~~€€€€€€€€€€~}}~~}||}}|{{{|||{z{}}}}}||||}}~~~~‚ƒ„ƒƒƒƒƒ‚ƒƒ‚‚‚‚‚‚‚‚‚‚ƒ‚‚~~€€€€€€€€€€€€€€€€~~}}}}|||}|||||||{{z}~~}||||}|}~~~ƒƒ‚‚ƒƒ‚‚‚‚‚‚‚€‚‚‚€€~~~~€€€€€€€€€€~~~~}|{{{{|{{{{|{zzzz~~|||}}}}~~~€€‚ƒƒ‚‚‚‚‚‚‚‚‚€€€€€~~€‚‚‚€€€€~~}|}}}|}~}zz{zzzzz{{{yyyyz~~~}|}~~}~}~~€€‚‚‚‚‚€€€€€€~~~€€€}}}|||}||}||z{z{zzzzzzzzyyz~}~~~~~~~~€€‚€€€€~€€€€~~~€€}||}|}|{|}}|z{zz{{zzzzzzzzz€~~~~~~€~~~~~}}}}}}~~~~~~€€€€€}|}~}}}|||}|z{zzzzzzzzzz{{z€€€€€€€~~~~~}}}}€€}}}~~~}}}}}}}}}}}}}}}}}~€€€€~~~~~~}}~}}}|||}|z{zzzzzzzzzzzzz€€€€€~~~~}}|||~~~}|~~~~}}}|||}||||||||||}~~€~}}}}~}}|||}}||||{zzzzz{{{{{z{zz{~€€€€€€€~~~~~}}~~~}|}~~~}}}}}}|||||||||||}|}~~~~~}}||}~}}}}}}|||||{{{z{{{{{{{{zz{€€€€€€€~~}~~~~}}}}}}||||}}}}}}}}}|||}||}}}}||}}|~~}}}}}}}||||||||||{{{|||{{|~€€€€€€€~~~~}}~~~}}}}}}||||}}}}}}}}}||||{{{{{{||}}}~~}}}}}}||{{{{|||||{{{||{{{{{~~~~}~~~~~~~~~~~~~~~~}~~~~~€€~~~~~~}}~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€€€€€~~~~}~~~~~~~~~~~~~~~}~~~~~~~€€€€€€€€€€€~~~~~~}~~~~~}~~~~~~~~~~~€€€€‚‚‚‚€}}}}|}}}}}}}}}}}}}}}|~~~~~~~€€€€€€€€€€~~~~}}}}}}~~~~~~~~~~~~~~€€€€€ƒƒƒ‚‚‚‚€€~}}}}}}}}}}}}}}}}}}}}}~~~~~~~~€‚‚‚€€~~~~~~~~~~~~~~~~~~~‚ƒ„„ƒƒƒƒ‚€~~~~~~~~~~}}}}}~}}~~~~~~~~~~€€‚ƒ‚‚‚ƒ‚€€€€€€~~~~~~~~~~~~~~€‚‚‚ƒƒ„…„„„ƒƒ‚€€€€€€€€€~~~~}}}~~~~€€€‚‚ƒƒ„„‚€€~~€€€~€€€‚‚‚‚‚‚„……„„ƒ‚ƒ‚€€€€€€€€€€€€€~~~~}}}~~~€€ƒƒ„„ƒƒ‚‚‚‚ƒ‚‚‚€~€€‚€€€€~€€€€‚‚‚‚‚‚„……„„ƒ‚‚€~~~~~}}~~~~€€‚„…………„„ƒƒƒƒƒ‚ƒ‚€€€€€‚€€€€€€‚€‚ƒ‚‚ƒ„……†…„„‚€€~~€€~~~€€‚ƒ…………„„„ƒƒƒƒƒƒ‚‚‚ƒ‚‚‚€€ƒƒ‚ƒƒƒƒƒƒ„……†……ƒ‚€€~€ƒƒƒ‚‚€€‚ƒ„………„„…„„„„„„ƒ„„ƒƒ„„„„ƒ„…„ƒ‚‚€‚ƒƒƒƒƒ„………ƒƒ„………„ƒ‚€€~€€€„†‡††…„‚€€‚‚„……………„…„„„…„…„……„ƒ„„„„„……„ƒ‚‚€€ƒƒƒƒƒ…†……ƒƒ„†………ƒ€~€€€€€€„†‡‡‡††‡…„‚€€‚ƒ„……………„„„„„„„„„……„„„„„„………ƒƒ‚‚ƒƒƒƒ…………ƒƒ„…„„„‚€€€€€€€€€‚„‡ˆ‡‡‡‡‡††„‚€€€€‚ƒƒ„……………„„„„„………„……„„………………ƒ„„‚‚ƒƒƒƒ„„„„…………ƒƒ„…„„„ƒ€€€~~~~~€ƒ…‡ˆ‰ˆ‡‡†††„ƒƒ‚‚ƒ…†……††…„„„„…………„………………„„„ƒƒƒƒ…†………„„„„„„„„ƒƒƒ……„„ƒ€~~~~~€ƒ…‡‰‰ˆˆ‡……………„‚€ƒ„………………„„„„…………………………„‚‚‚‚‚‚‚……………„„„„„„„„ƒ‚ƒ……„…ƒ€~~~~~~€‚‡ˆˆˆ‰‰‡………………ƒ‚‚‚‚„……………„„„„„…………„„…„……ƒ‚‚‚‚‚‚‚……………„„„„„„„…ƒ‚„…„„…„~~~~~~ˆŠ‰‰‰‰‡††††……ƒƒ‚€‚‚„„…„„„„……„„…………„„„ƒ„„ƒ‚‚‚ƒ………†……„„„„„„„„„…†………„~€€€€€€€€€€‚†‰Š‰‰‰ˆ†††………„‚‚ƒ„„„„„„………„„„„„„„„„„ƒƒ‚‚ƒ…„………„„„„„„„„„…†††……„ƒ‚€€€€€€€€€€€…‰‰‰‰‰ˆ†††………„‚‚‚ƒƒ„„„„„„……„„„„„„„„„„„„„„‚‚‚‚‚‚‚ƒ„„„……„„„„„„„„………††…†…„‚€€†ˆ‰‰‰‰ˆ‡†……………ƒ‚ƒƒ„……………………………„„……„„……„„„„ƒ‚‚ƒ‚ƒ„…„„…„„„„„„„„„…†††††…„„ƒ€…ˆ‰‰Š‰ˆˆ‡……………ƒ‚‚ƒ„………………………………„……„„„…„„„„ƒ‚‚ƒƒƒƒ„…„„„„„„……………„………††…………ƒ‚€€~~†‰‰Š‰‰‰ˆ†………„ƒƒ‚‚ƒƒ„…………„„„…„„„„„„„„„„……„ƒƒƒƒƒƒƒƒ„„ƒƒ„„…………………„„…†……………„ƒ‚€€~~†‰Š‰ŠŠ‰‡……„„„ƒƒ‚‚ƒƒƒƒ„„„…„„…„ƒ‚ƒ„„„„„„……„„ƒƒƒ„…„„„„ƒƒƒ„…………………„„…††††††…ƒ‚€€€€€€~~~€…ˆŠ‰ˆ‰ˆ…„„„„ƒƒ‚‚‚ƒƒƒ„„…„„…„€‚„„„„„…………„…„„………………„„„…………………„„„…†††ˆ‡†…ƒƒƒ€€€€€~~€ƒ†ˆ‡†††…„ƒƒƒ‚‚‚€‚ƒ„„………„„…ƒ€€‚„„„„„……†††…„…………†…………………………………„„…†‡‡ˆˆˆ†ƒƒ„~~~~€ƒ‡†„„„„ƒ‚‚‚‚‚‚€€‚ƒ„………………„„„€€ƒ„…„„„††††…„…††††…†…„…„……„„…„„…„…†‡‡ˆˆ‡‡„„…~€€‚„„‚ƒƒƒ‚‚‚‚‚‚‚‚„………………„ƒƒ‚€‚ƒ„……„†‡‡†……………………†…„„„………„…„„„…„…‡‡ˆˆˆˆ††…~~~~€€ƒƒ‚‚ƒƒ‚‚‚ƒƒƒƒ„………………„ƒ‚€€‚„………†‡‡‡†……„„……………………††…………………„…††‡ˆˆˆ††…}}~~~~€€€ƒ…ƒƒƒƒ‚ƒƒƒƒƒ„………†………„‚€€ƒƒ„……………………„„„……†………†††††‡††…………………††ˆˆ†††|~~~~~€€€€€€€ƒ……ƒƒƒ‚ƒƒƒ„†„…„…„„…„‚€€‚ƒƒ„„„…„„„„„„„………………††††††‡†…„……„…………††††…}~}~~~€€€€€€ƒ††„„ƒƒƒƒ„…„„……„„…‚€€€€ƒ„…„…„„„„„„„……………†††††††††…„„…„…„…………………~~}~~~€€€‚„………ƒ‚‚„ƒƒ„…„„„…ƒƒƒ‚€€€€€€ƒ„„ƒ„„ƒ„„„„……††……†††††††††…„„„„„„……†……†…~~~€€€€€ƒ„…„ƒ‚‚ƒ„„…„„„„„„„„ƒ‚‚‚€€€‚ƒƒƒ„ƒƒƒ„„„„……………††‡‡††……†…ƒƒƒƒƒƒ„„……………~€~~€€ƒ‚‚‚ƒ„„‚ƒ‚‚„„……„„ƒƒƒ„„‚‚€€€€‚ƒ„…„ƒƒƒƒ„………………††‡ˆ‡†………„ƒ‚‚‚ƒƒƒƒ„„………€~~€€€€ƒƒƒ‚‚ƒƒƒƒ‚„………ƒ‚ƒ‚‚€€€€€‚‚ƒ„„„„„„„„…†††††††‡‡†…„…†„ƒƒƒƒƒƒƒƒƒƒ„…†€~~‚ƒƒƒ‚€€ƒƒ„„ƒƒ„„„‚‚€~~~€€‚ƒƒ„……„„„„„„…††………†‡‡‡‡…„………„„„ƒ‚ƒƒ„„………~€€~ƒ„„„ƒƒ‚‚ƒ‚ƒ„„„„ƒƒ„ƒ‚€‚‚€~~€€€€ƒƒ„……………„ƒ„„……………†‡‡‡‡†„„……………„‚‚…†………~€~‚„……ƒ‚‚ƒƒ„„„…………„ƒ‚‚‚€ƒƒ€~~~€€€€€‚ƒƒ………………„‚‚ƒ„……††…††‡‡‡†…„††††…„ƒ‚‚‚††…††€€~‚ƒƒ„‚€ƒƒƒ„„„……………ƒ‚„„‚€€€€€‚ƒ………………„‚‚ƒ……††…†‡††††…„……………„ƒ‚‚ƒ…††††€€~€‚ƒƒ‚€‚ƒƒ„ƒ„………†…ƒ€€‚ƒƒ‚€€€€€‚ƒ„„………„„‚‚€‚„……†…†‡††††…„……………„„„„„…………†€€€€€€€€‚ƒƒƒƒ„……††…ƒ‚€€€€~€‚€‚ƒ„„„„„„„ƒƒ‚‚ƒ…………†††‡†…„ƒ„…††…„„…„…„„„„…‚€€€€€€€€€€‚‚‚„„……†‡†…„‚€€€€€‚€€€‚‚‚‚‚‚‚ƒƒ„„„„„ƒƒ‚‚ƒ„„…†‡‡†‡†…„ƒ……………„„„„„„„„„…ƒ‚€€€€€€€€€€‚‚‚ƒ………†††……ƒ‚‚€€€€€€€‚„„„ƒ‚‚ƒ‚ƒƒ„„„„„„„‚‚ƒƒ„„…‡††‡†…„„†……†…„……„…„„„„„„ƒ‚€€€€€€€€‚ƒƒ„……††††……„ƒ‚€€€€ƒ……†…„ƒƒƒƒ„„„„„„„„ƒƒƒƒƒ„……†††††„…†……………„„„„„ƒ„„……„ƒ‚‚‚‚€€€€€€‚ƒƒƒ…„…††…„ƒ„ƒ‚‚‚‚‚‚‚„„………„„ƒƒƒ„„ƒ„„ƒ‚‚‚‚‚‚‚‚ƒƒ„…………„………„………„ƒƒƒƒƒƒƒ………„ƒƒƒƒ‚€€€€‚ƒ„„„†…„ƒ‚‚€‚„………†……„„„„ƒƒ‚‚€€‚‚ƒƒƒƒ„„„„„„„……„ƒƒƒ„ƒƒƒ……………ƒ„„ƒ€€€€‚‚ƒ………ƒ€€€€€€€€€€‚„……‡‡‡‡††…„‚ƒ‚€€€€€ƒƒƒƒƒƒ„„„………ƒƒƒƒ„„ƒƒ……………„„„ƒ‚€€€€‚ƒƒ‚~~€€€€‚‚†‰‰‹‹Š‰‰ˆ…ƒ‚‚‚€€€€€€€€€‚‚‚‚‚‚ƒ…†‡…„ƒƒ„„ƒƒ„†…………„„ƒƒ‚€€€€€€€€‚‚€~~~~}~~~~€€‚ƒ†‹ŒŒŒ‹Šˆ…ƒ‚‚‚‚‚‚‚‚‚‚€€€€‚‚ƒ††…ƒƒƒƒƒƒƒ‚†††††………„ƒ‚€€€€€~€€~~~}}}~~~~€€‚ƒ†‹ŒŒŒŒŠˆ…„‚‚ƒƒ‚ƒƒƒ‚‚ƒ‚€€€€€€€‚ƒƒ„…„ƒƒƒƒƒƒƒƒ††††††………„‚€€€€€~}~}}~~~~}}}}~~~~€€‚ƒ‡‹ŒŒŒ‹‰‡…ƒ‚‚‚‚‚‚‚ƒƒƒ€€€‚‚‚ƒ…ƒƒƒƒƒƒƒƒƒ††………††……ƒƒ€€€€}}}}}}~~~~~~~~~~~€ƒ†‰‹ŒŒŠˆ†…„‚‚€€€‚‚‚‚‚‚‚‚‚€‚‚‚ƒ„ƒƒƒƒƒƒƒƒƒ†…………†††…„ƒ€€€€€€~}~~}}}~~~~~}~~~€‚„‡ŠŒ‰…ƒƒ€€€ƒ‚‚‚ƒ‚€€€‚ƒ„ƒƒƒƒƒƒƒƒƒƒƒ„……………††……„‚€€€€€~~}}}}~€~~~~~~€€€‚„‡Š‰‡„€€€€€‚‚ƒ‚€€€€€‚ƒƒƒƒƒƒƒƒ„ƒƒƒ„……………†††…ƒ‚€€€€€~}}}}}~€~~}}}}€‚…ˆ‡…ƒ€€€€€‚‚€€€€€‚ƒƒƒƒ„„……„ƒ„„„†………††……„ƒ€€€€€€}}}}}~~~~|}}}~~„‡‰ˆ†„€€€‚‚€€€€€€‚‚ƒ‚ƒƒƒƒ„…†……„„„…„……………†…ƒƒƒ€€€€}}}}}}}}~~~}|}|}~~ƒ†ˆ‰ˆ‡…‚‚‚ƒƒ‚‚€€€€€€‚‚‚‚ƒƒƒƒƒ„………„„„„…„…………††…ƒ‚‚€€€~€}}}}}}}}~~~}|}}}~~€ƒ‡‰ˆˆ‡…„„„ƒƒ„ƒ‚‚€€€€€€€€€€ƒƒƒƒƒƒƒ„„………„„……………………†………ƒ€€€~€}~~~~~~}}~~}}{}~€‚†‡‡‡††………„ƒƒƒ‚‚€€€€€€€€€€€€€‚„ƒƒƒƒƒƒƒ„………„………………„„„………„‚‚€€~€~~~~~~~~}||||}~~€€‚ƒ„……„„„…„ƒ‚€€€€€€€€€€€€‚ƒƒ‚‚ƒƒƒƒƒƒ„…………………„ƒƒ„„„…„‚€€~€~~~~~~~~}|}}}}~~~ƒƒƒƒƒƒƒ„ƒƒ€€€€€€€€€€€€‚ƒƒ‚‚‚ƒƒƒƒ„……………………„ƒƒ„„„„ƒ€€‚€~~~~~}}~}}~~~}}~€‚‚€€€€€€€€€€€€€‚ƒƒƒƒƒƒƒƒƒƒƒƒ„……†††…„ƒ‚ƒƒƒƒ„ƒ‚‚€€€€~~~~~~~~~~~~€€€€€€€€€€€€€‚‚‚‚‚‚ƒƒƒ„„„„„ƒ„„…………………ƒ‚ƒƒƒƒƒ‚‚ƒ‚€€€~~~€€€€€~~€€€€€‚ƒ‚‚ƒ‚‚ƒƒƒ„……„ƒƒ„……………………ƒ‚ƒƒƒƒƒ‚‚‚ƒ‚€€€~€€€€€€~~€€€€ƒƒƒƒ‚ƒƒƒƒ„…„„„„„„„„…†………‚€‚‚ƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€€€€€€€€€€~€~€€€€‚‚‚‚ƒƒƒƒ„„…„„……………………„„„‚€‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒƒƒƒ„„………………………„„„„„€€€€€€€€€€€€€€€€€€€€€€€€€€‚‚‚‚‚‚ƒƒƒ„„…………………„„„„„„„€‚€€€€€€€€€€€€€€€€€€€€€€€‚‚‚‚‚‚ƒƒƒƒ„„…………………………„………‚‚€€€€€€€€€€€€€€‚‚‚‚‚‚‚ƒƒƒƒƒƒ„…„……„„………………„‚€€€€‚€€€€€€€€€€€‚‚‚‚‚‚‚‚ƒ‚‚‚‚‚ƒƒƒƒƒƒ„„………„ƒƒƒƒ„„„„‚€€€€€€€€€€€€€€€€€€€€€€€‚€€€€€€€€€€€‚ƒ‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„„„ƒƒƒƒƒƒƒžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœœ››››œœ›ššš™˜˜˜™™™šš™šš™™™™™›ššœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸžžžœœœš™™˜————––••”“’’““““““–˜˜™™™™™™ššœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœš››ššš™šššššš™™™™˜˜˜˜˜˜˜˜™™˜˜˜˜—˜™šš›œžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸžžžœ›šš™™——–––••””““’’’‘’’’’’“•—˜˜™˜˜™ššœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœšš™™™™™™™š™™™™™™™˜˜˜˜˜˜˜˜————˜——˜˜™™š›œžœœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸŸžžœœ››šššš™™—•”“““’’‘‘‘‘‘’’““•–˜˜˜˜˜™šœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœš™™™™™™™™™˜˜˜˜˜˜˜——————––––––—————˜™š›œœœœœœœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸŸŸžœ›™˜™˜˜˜™™—•“’ŽŽŽŽŽ‘‘‘‘’”–———˜™š›žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžž›š™™™™˜˜˜˜™˜˜˜˜˜––————–•••••••––––—˜™›œœœœœœœœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžžžž›š™™˜———˜—–””’Ž‰„„ˆŠ‹‹‹‹ŒŒŒ’“”———˜™š›žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžž›™™™™™˜˜˜˜™˜———––•••••””””““““”•••–—˜šœ››œœœœœžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžžžžœšš™™˜—–––•”Žˆ{wy~ƒˆŠ‰ˆ‰ŠŠ‹‹ŒŒŒ‘’“•–——™™›žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœš™™™™˜˜˜˜˜—–•””““”“““““““““““““““•––˜š›šššœœœžžžžžžžžžžžžžžžžžžžŸŸŸžžžžœššššš™—•••‰vjb`ajx‚†ˆˆ‡…†‡ˆ‰‰ŠŠŒ‘‘’“–——˜™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœš™™™™˜˜˜˜——•””““’’’’’’‘‘‘‘‘‘’’’’““”–—˜™š™šš›œœœžžžžžžžžžžžžžžžžžžžŸŸžžœ›™™™™™˜–’Šz`IBDKWi}„…†…„‚‚ƒ„††…†‹’”–˜˜™›žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœš™™™™˜˜˜˜——•”“’’’’‘‘‘‘‘‘‘’’“”•–—˜˜™™™š›œœžžžžžžžžžžžžžžžžžžžžžŸŸžœœ›š™˜™˜™™—zdMB?@CGPjƒ†…ƒ‚€ƒƒƒ…ˆŠŒŒŒŽ‘“•—˜™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœš™™™˜˜˜————•“’’‘‘‘ŽŽŽ‘‘’“•–—˜˜˜™ššœœœžžžžžžžžžžžžžžžžžžžžš™™˜˜———–’‚gJ=<?BFKNXr††„€€€€‚„…‡‰‹‹“•—˜™šžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœš™™˜˜˜—––—•”“’’‘ŽŒŒŒŒŒŒŽŽ‘’“••–——™™šœœœœ›œœœžžžžžžžžžžžž›™™˜———–ˆoT@;@HS^dinw†…ƒ€~€ƒ…†ˆ‰‹’•—˜™™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœœš™™˜˜——––•”“’‘ŽŒ‹ŠŠ‹ŠŠŠ‰ˆ‰‰‰‰‰Š‹‹Œ‘’’’“•––—™šššš™™™™›œœœœœœœœœ››››››š›››››š™™™˜˜—•”’]A:=CO^ipuvy†‡…ƒ~~~~€„†‡ˆ‹’•—˜™™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœœš™™˜——––•”“’’‘Šˆ‡‡ˆ‡‡‡†††††‡†‡ˆˆ‰‹ŒŽ‘’’”–——˜—————˜™šš™šššš™˜˜˜˜™™˜™˜˜˜˜————–”“‘{V?:@KU`kquwwx€†ˆ…‚~}|}}|}€„†‡ˆ‹’•—˜™™žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžœœœœš™™˜——––”“’’’Ž‹‡††………………„………†…††††ˆŠ‹ŒŽ‘’”•••––•–—˜˜˜˜˜˜™™—————————–––••””“’‰mM=:?NZ_fmpqssw†ˆ…€|zzzzzyz}€ƒ†‡‰‹Ž‘“–—˜™™žžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžžžžžžžžžžžžžžœœœš™™˜—––•”“’‘ŽŒŠ‡……„ƒ‚ƒƒ‚‚‚‚‚‚‚ƒƒƒ„…†ˆ‰Š‹‘““““““””•–––––––•”•–•••”””““’’’’‘ŠƒhD8<>HX`bhmnnnrv|ƒ†„€{xxyxxwx{}‚†‡ŠŒŽ‘”–˜˜™™žžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžžžžžžžžžžžžžœœœš™™˜—––•”“’‘Š‰†…„ƒ€€€€€€‚„…†‡ˆ‰ŠŒŽ‘‘‘‘‘‘‘’’’’’’““““““’’’’’’‘‘‘Œ‰^@8<AIV_bdioqrquz~‚…„€zxxxwvvvz|‚…ˆŠŒ’”–˜™™™žžžžžžžžžžžžžžžžžžžžžžžžŸŸŸžžžžžžžžžœœ›š™™˜—––•”“’ŒŠˆ…ƒ‚€~~~}||||}}}~€‚ƒ„……†ˆŠŒŒŒŽŽŽŽŽŽŽŽŽ‘‘ŽŒˆ|]<5;DOV^eilquxwvwy|‚†…€zxxwuuvwz}€ƒ†ˆŠŒ’“–˜˜™™žžžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸžžžœ››žžžœœ›š™˜—–––”““’Ž‹‰‡…€~~}||{{zzzyzzz{}€‚‚„…††‡ˆˆ‹‹‹‹‹‹ŒŒŒŽŽŽŽŽŽŽŽŽŒŒŒŒŠxZ=5:AMZ^cjnrtuussrsw†‡yvuutttvy}€‚…‡‰ŒŽ‘“–˜™™šžžŸŸŸŸžžžžžžžžžžžžžžžžžŸ ŸŸŸžœš—‘ˆxo”œ›™˜——–•”“’’‘‹‰‡…„€~}|||zzzyxwwwxwwy{|}~€‚„……††‰‰‰‰‰Š‹ŠŠŠ‹‹‹‹‹ŒŒ‹‹Šˆ‡‡‚sR828>GU_ejmorsssrrrrv€ˆ‰‚vssrrstvx|‚…†ŠŒŽ’“–—˜™›ŸŸŸŸŸŸžžžžžžžžžžžžžžžžžžžœ›™—’‘’Š…vZWožžœ›™˜˜—–•”“’’‘ŽŒŠˆ†…ƒ€~{zzzyyxvutsttttuxzz{}~€‚‚ƒ……………††‡ˆˆ‰‰‰‰‰‰‰‰Š‹‹‹‹‹ŠŠˆ‡…„„lO806?GQ_dglnooqrrsstsvˆŠ‚uqqqqstvx|€‚…‡ŠŽ“”–—˜›œŸŸŸŸŸŸŸžžžžžžžžžžžžžžžœ˜•’‹ŒŽ‹ˆ…}fSTfˆšžœš˜˜––•”“’‘ŽŒ‰‡†„|yyyxxwtsrrrsrqqrtwwwyz|€‚‚‚‚‚ƒƒ„…†‡‡‡†††‡‡‡ˆˆˆˆˆˆ‡†„‚}whJ425<GS^ddfklmnoonnnnmpz„‡uprqrrtvx|‚…‡‹Ž‘“•——™›ŸŸŸŸŸŸŸŸžžžžžžžžžžžžžžš—“‘ŒŠŠŠ‰‡ƒ}p[NK[}–žœš™˜—–•”“’‘ŽŒ‰‡…ƒ€~|yyxxwusrrrqqqonpqtttwwx{|}}}€€€‚ƒ„…„„………„…………………„ƒ~pW?1/4=HSW\]adhklmllkjjiimwƒ…uoprsstvx|‚†ˆ’”•—˜šœžŸŸŸŸ  ŸŸžžžžžžžžžžžžžž—’ŒŠˆˆˆˆ†‚~vgUKFRu’žžš˜˜—–””“’‘Ž‹‰†„‚}{yyxwusqpppponmlmorsssstwxwwxz}}}~~~€€€€€€€€€€€~{nU=0049BMUVY^ekoqqqrtuyzywv|…Š†}vuvuvuvx|‚†ˆŒ’”–—˜š›žŸŸŸŸŸŸŸŸžžžžžžžžžžžžžž›—“‹Š‰‰‰ˆˆ…ƒ}zo_RHDQm‡—™˜˜—–•”“’‘ŽŒŠˆ†„~{yywvtrommmmllkklmnooooqstssuwyyyz{||}~~~~}}}~~~~~~}xmT9/058<DLTVZcjmprtuwxxwxxwx}†Œ‹ƒ|zyxxwwx}€‚†‰’•—˜™™›žŸŸŸŸŸ ŸŸžžžžžžžžžžžžžž›˜’ŽŠŠ‰ŠŠ‰‡…{ysm^NIGJWu‘™˜—––•“‘Ž‹‰‡†„€~|zyxwvtqnlkkkkkjklllllmmoqqqrrsuvvwwxxyyyyyzyyz{{{{{~taM>304;@EKRXZ^cefgjmoqpponnlnv‚Š†~xwwxxxxy}€ƒ†ŠŽ’•—˜™š›žŸŸŸžŸŸŸŸŸžŸŸŸŸŸŸŸŸŸŸŸŸ›˜’Œ‰‰ŠŠŠŠˆ…€{ysmdVOJDBNkˆ”–˜–”‘‘ŒŠˆ†…ƒ}{yxwwvuqnlkkjjijkkklllllnnnoooqsssttuvvvvvvvutuvvvttvsbC2246;DIOTY\]`cccehjmnmmmmonmr€‡vqrtuvwxz}€ƒ‡Š“”–˜š›œŸŸŸžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ›–Žˆ†‡ˆ‰‹Šˆ…‚}ztme[TOH@@Hb~‰“ŠŽ‹ˆ†…„~|yvuvvvusolkjihhijkkklklllmllmmnooqrrssrrsrqpqqrssrokbUA2047:@GNTX\]]_abbdgilllmnoqqnr‡uopqrsuxy}€ƒ‡‹Ž‘’“–˜š›œŸŸŸžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸž›”‹†„„‡Š‹‹‰…‚}ztoh`YUME@?CSds~|ˆˆ†…ƒ|zxtstvxwtpmkigfefhjjjjjjklklllklkkoqqqpooonlklmnookdZI:1148>CHMRW\^^^_acfgjlmnnnoqttpsˆupoqrruxy}€ƒ‡Š‘’”–˜š›œžžžžžžŸžžžžžžžžŸŸŸŸžŸžš’ˆ„‚†ˆ‹Š‡„}zuqkb]YQIC>:;BQ\][bzƒ}{zwtroptvxvqnmkiihjloqpppqrrsrrrrolknpqrokgfeddedbbb\L@3.06;@HKPSW[^aaacfjnprrqqqrsvxxtwˆƒwsrsstuxy}€ƒ‡ŠŽ‘’”—˜š›œŸŸžŸŸžŸŸŸŸŸŸŸŸžŸŸŸŸžžš“‡ƒ€„†‰‰…‚€~zvsme`\VNHC<98<ACCHdutpqrsqolhefkrvvqmlllosz~€‚€€€€€~~zsoqrstqg]XVVWVURPQI91-,0:CJNQUXZ^acefilnqtvutttuwz}{vy‡ƒxttttvvwz~€‚‡Š‘’“–˜š›œŸŸŸŸŸŸŸŸŸŸŸŸŸŸžŸŸŸŸžžœ™“ˆ€‚……†…ƒ€~zwsoiea[UOIB;98999:DUVTVY[\]_]Y[cltwtppru{ƒ†‡ˆˆˆˆ‡††‡‡‡‡‡‡ƒ|vvwxyvi[TOMPONJHE<2.,-6EMORUY\_bdfhjmoqsvvvvuuvvz~{xz‚†€vrrtuvvwy}€‚‡Š‘’“–˜š›œŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžœš“†€‚ƒ…†‡‡„{wsqlhe`[VOH@;877668=BEFHKLNQQNNVguyz{|‚…‡††‡†‡‡‡‡‡ˆ‰Š‹Œ‹‰‚}|}}~{pb[PGLMIB><80-/3BPSVZ]_adeghjlnpstutsqopruy|zw{‚„{qmnqsuvwy}€‚‡ŠŽ‘“”—™››œŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžš’„}‚…†‡ˆ‡…‚~{wtrnjfb^ZULD>:8654469;=?DHHHHGINew{‚ƒ„††‡†…††‡‡‡ˆ‡‡‰ŠŒŽ‹†€€€€ypk]QJIF>:;8227AJSX^cccbdfghjnoqrqqonlklnsxyzx|ƒ‚xojknqsuwz~€ƒ‡ŠŽ“•—˜š›œŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžŸŸš‘ƒ}ƒ…‡‡‡†…‚|xtrokgca]YRJC>;85212369<@EEDDDHPdv}‚„…†††‡†„„†‡‰‰ˆ‡†ˆ‹ŽŒˆ‚‚‚‚}zyl\OJG=::669CKQW]cdcaadhiiloqqomlkklllotxz{y}„slikmoqtw{~„‡ŠŒ’”–˜š›œŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžŸŸ›‘„~€„…‡‡‡†…‚{wupmidb^[XSJC?:62001347;ACEFIOYfv€…‡‡‡‡‡…„ƒƒ…‡‰Š‡……‡‹Ž‘‹‡„„„ƒƒ~{ug[UQA<;9>DLQW^dgfc`aiomklnnlkhhhiklmouz{{{†~oihjlnqsvz}€„‡ŠŒ‘”—˜š›œŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžŸŸš’…€„…†††……ƒ|xtolieb^[YVQIB<841////16:CMSYajq{ƒ‰ŠŠ‰ˆ‡†„ƒ†ˆˆ…ƒƒ…ŠŽ‘Šˆ†………„‚~|vnh]KDEDGLST[iokgecgoqnjggeeghiikmmnpv{}}z‡~pljklnqsvx|‚†‰‹Ž‘“–˜™›œœŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžŸŸš”ˆ€ƒ…………„„€|zusokhf`\YWVTOGA;73/--.05>O[emtwz€ˆŽ‹‹Šˆ…€€„…ƒ€„ŠŽ‹Š‰‡‡ˆ‡‡…ƒ€|teWRRRRUXXaquoiffmslhfeedfikmmooopruz{zyŠ€soommorstx{~…ˆŠŽ‘“–˜™›œœŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžŸŸš•‹ƒ„…„„†„‚~}{zvtqmif_ZXWWUSOH?940./.0<O`jt|ƒ„ˆ‘ŽŒŒŠ‡…„€€~‚‡‹ŽŽŽŒŠˆˆ‹ŒŒ‹‰…znc_]]^`aajvwsnjjrsgabfjloqqrqqpnpstvvuu‚‹urpmmorrtwy}€„‡‰’•—˜›ŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžŸš•†…‡‰‹Š†ƒ€~~{zxvsolifc`\XUUPH?:6456;M`ku~ƒ†…†‹Ž‘Ž‹ŠŠ‹Šˆ…}z~€ƒˆŠ‹ŒŒŠŠ‹Œ‹‡€umkjkihjktxstpmsxmfcfmrwxvronkjjmqsttru…‹€xspmlnrrsvy|„†‰Œ’”—˜›ŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžŸžš–‰‰‹Ž†€~€€}|{zwsssqnia\YWURICAACCM^ju~„†‡†ˆŒŽŽŒ‹Š‰ŠŒŽŽŽ‹‡‚~{}‚‚…†‡‰ŠŠŠ‹ŒŽŽ‹„ztvwyupqsyxrtpovvpkiow}ypigfehmoqsstv{‰‹‚ysollnrrruy{ƒ…‰‹Ž‘“–˜šœŸŸŸžžŸŸŸŸžžžžžžžžžžœ˜‘‹ˆˆˆ‚|zz~€€~|zywusrrpnhb\XW^WLKNW_fkt{ƒ‡‰‰ˆ‰‹ŒŒ‹Šˆ‡‡‰Œ‘‘Œˆ„€~~€‚…ˆ‰ŠŠŒŽŽ‘Ž†~z|~{{||vsquysqnq|ƒ…pgeedflsuwyyx{‰Šƒyplklnqpqtw{‚„†‰Œ’•—˜šœŸŸžžžŸŸŸžžžžžžžœœ™“‹…ƒ‚{yz{}~|{zywtqononlg_ZX^c\TWgrwz}€…‡‰‰‰Š‹Š‰ˆ†„…‡‰Œ’””‘ŽŒ‰‡…‚}{|~€„‡ˆˆ‰ŒŽˆ}‚‚ƒƒƒƒ€ypq}{srt}‡ˆƒujhkmmt|ƒ‚~†‹Šƒvnkmprqopruy|‚„†Š‘“•–˜šŸŸžžžŸŸŸžžžžžžžœœœš”Š„{zzz{|{{{zyxuqmkjkkjfa\Yagcakwƒ„…†‡‰ŠŠŠ‰‡…„‚ƒ‡ˆŠŽ’–˜–’’“‡|z|€ƒ„†ŠŒ‘’Ž‹…‚††„†ˆ‰‰„utz~{xy~ˆŒ‡znlquw}„‰‹Š†‚}}€ŠŒ€rmouxvsporuwz|‚…ˆ‹Ž‘“”—™ŸŸžžžžŸ Ÿžžžžžžœœœœ›”‰ƒ|zzzzz{{{{{{yvrnjijlmiaZ^horw…ˆ‡†‡‡ˆ‰‰ˆ†„‚€€‚…†Š–™—““–˜™˜Ž„€}{||~€ƒ‡Š‘‘ŒŠ‰Š‰‡ˆ‹‹Š„}|ƒ‚~{z€‹‰{ttvxy|ˆŒŽˆ|y{‚ŒŽ‡{tw{~yurpoqtwy{}€ƒ†ŠŒ’”—™ŸŸŸŸŸŸŸ¡¡Ÿžžžžžžœœž›•‹ƒ}zzyyyz{|}‚‚|vnlnonjffjt~ƒ†ˆ‰ˆ‡‡‡‡†…„ƒ€~}|~€ƒ‡Œ’˜–•–™›››”Œ†~zy{{}ƒ†‹ŽŒŒ‹‹‹Š‹‹ŒŠ„‡Š†€}Šˆ|vwyz{~ƒŠŒ‰ƒ}z{}‡ŒŠ„„‰‰‚xtronpruwy|ƒ…‰Œ‘”–™ŸŸŸŸŸŸŸ¡¡ŸŸžžœœžž›—Ž…‚‚}zyxwxz{}ƒ†ˆŠŒŠ…ztqrsuutv|ƒ‡Š‹Š‰‰ˆ‡…ƒ€}zxx{~~‚…Œ–—˜›ž žžš“‹…|yzyz{~†ŠŒŒ‹‹‹‹Œ‹‹‹ŒŠ‡‡‹‰†ƒŒxvxy{~ƒ‰ŒŠyz~€‚†ŠŒ‹Œ‘“Ž‚yvsnmnptuxy}„‡Š’”˜ŸŸŸŸŸŸŸ¡¡ŸŸžžžžœœœžž™‘‡ƒƒƒ|zxwwy{‚…†Š‘‘‡€{y|~~€„ˆ‹‹‹ŠŠ‰‡…‚~}}|ywuvwyzz}€ˆ”˜š ¤¥¡Ÿœ—Ž‡ƒ{yxxy|ƒ‡Š‹‹Š‹‹‹‹‹‹‹Šˆ‡‡‡……‰Ž‚wtwy{‰’„xtz„†…†ˆ‹ŒŒŽ‘•–Ž…‚{smlmoqtvxz‚…ˆ‹’–ŸŸŸŸŸŸŸ ¡ŸŸžžžžžŸžžœœ™”Š„‚‚€~zyyz}‚ƒ„†‰’’“’‰„ƒ„…ˆŠŒŒ‹Š‰ˆ†„}{zyyvrqqrtuwy{„’šž£¦§¤¡žš’‰…~zxvvx{~…ˆˆˆˆ‰Š‹‹‹Œ‹Šˆ‡‡‡‡‰Š†}z{{|Š‘‹trz…ŠŠŠ‹‹Œ‹ŒŽ‘”–‘‰tnlklmorsvy|‚…‡Š“ŸŸŸŸŸŸŸ  ŸŸŸŸŸŸžžžžœ›š™•…€€ƒƒ‚€~~€‚ˆŽ‘“““‘Œ‡†ˆ‹ŒŒŒ‰ˆ†„}yvvuuspoooqqswx~™Ÿ¤§¨¥¢Ÿœ–Œ„~ywwvw{~€ƒ…†…†‡ˆ‰Š‹‹‹‹Š‰ˆ‡‰ˆ„€€€‚„ˆŽ†{qr}‰Œ‰ˆ‰‰‹‹Œ’“–“†wmkjjklmortvx{~…ˆŠŒŸŸŸŸŸŸŸŸŸ ŸŸŸŸŸžžžžž›˜˜˜–…€‚„…†‰Š‹ˆ‡…ƒ‚‚‚‚‚„‰Ž‘“’‘ŽŒŒŽŽŒ‰‡†„‚€}zvsqqqponmnonquuzšŸ¥§¨§£ ˜Ž„}ywvux{}‚ƒ‚ƒ„…‡‰‰ŠŠ‹Š‰ˆ‡†„‚ƒ„…†ˆ‹†{tu€‡‹ˆ…„†‡Š’”—‘}oljiijjjmprstxz|€…ˆ‰ŒŸŸŸŸŸŸŸŸŸ ŸŸŸŸŸžžžžžš—˜˜—“‹„…†…†ˆ‹ŒŒŒ‹‹ŠŠŠ‰†„„‡Œ‘’’’‘ŽŠ†„ƒ‚{wtrqpoonmlmnmorp|”›¡¦§¨§£ ˜Ž…~ywtsvy{}~€ƒ…†ˆ‰ŠŠ‰‡†„ƒ„„…†‡ˆˆ†€usx‰ˆ†„‚‚‡‹‘‘“••ˆvpmlllllkjloqruvy}„†‰žžžžžžŸŸ ŸŸŸŸŸŸžžžžœš—˜˜˜•ˆ†‡‡ˆˆ†……‡ˆŠŒŒŒŒ‹ˆ††ˆ‹Ž’’‘ŽŒ‰…‚€€|zwtrqonmkihjllopk|•›¡¦§¨¨¤¡ž˜Ž…zvsptxz|}}~~}}~€‚ƒ†ˆ‰‰ˆ†……†††††…‚ztsy€„‡††…‚ƒ†Œ”––•Œ‚wrqonnlklmnorsvy|€ƒ†žžžžžžžŸŸŸŸŸŸŸŸžžžžžœ™————”‘Š†††ˆŠ‹‰†„„……ˆ‹‹‰ˆˆ‡‰‹Œ‹‡ƒ€~~|{zwuspnmmlhgghinqj}—¢¦§©¨¥¢Ÿ™…€|wroqvxz{|||{zz{{{„†‡‡††††‡‡††„|vvx~…‡ˆ†ƒ„ŒŒ‘–˜—–“‘„}vqnmlkkllnqrtvx}„žžžžžžŸŸŸŸŸžžŸžžžžœ™—–––”‘‹‡‚}{}ˆ‹‰‡„…‡ˆˆˆˆŠŒŒŒŽŒŠ‡ƒ€~|{zxvurpnnmljgdbclrk˜ž¤§§©¨¦¥¡›”ˆ€zvqklsvwxzzyywvvvvyƒ…†††††‡‡†…‚~zxxw{~†‰‡€„‘Œ‘–˜–“‘’‰…}uonnmmmmnqsstv{~žžžžžžžŸŸŸžžžžœœœœ™—–––”Œ‹Š„|zz„ˆˆˆ…„†‡ˆˆˆŠŒŒŒŒŒŒ‹‰‡ƒ€~{zyvtspnmmmmkga^`kqp‡› ¥¨©©¨¦¦£ž˜Žwqmefnqstxxwutttttux}‚ƒ…†††††…„‚€}zwuw{†spv~‡‘ŒŒ‘••’ŽŒˆ„yutssrpopqstux{~žžžžœœœœœœ›š˜——––”‡‚„…‡†ƒ{{„ŠŠ†…‡ˆˆ‰‹ŒŒŒŒ‹‹‰†‚€~|zyxtrpnlkkllkhb]_myž£¦§¨©¨§¦¥¡š”†yqkb^fknqtwuttqqqqruy}€„††††††…„‚€}ytw|xgfnxŠ‡‰Ž‘‘ŽŒŠ‡ƒ{xywurooprtvxy{žžžžžžœœœœœœœœ›š™˜—–—––•Š}€€~~…††‚}|†‡‡‡‡‰‰ŠŒŒŒŒ‹Š‡ƒ€}{zxxwsqmihiiiiihdagw‰‘˜ ¤¦§©¨©¨§¦¡œ—ŒtlaY`eflrtsrqomnnpruy|††††††…„„‚~yvx}}kbjsz‹Ž‹„‡ŒŽŽŒŒŒŒŒŒŠ†‚~~}zwtrrrstvwzžžžžžžœœœœœœ››››™™˜—––––––Œ|zz~‚†‡†…€~ƒ††‡‰Š‹‹‹‹‹‰‡ƒ€~|zxvtsspmjgfgghhhhfdq„‘™Ÿ¢£§©ª©ª©©§£ž˜‘…wm[U]__fnppomkkllnpruy}‚††‡†……„…„€zxyzrfenuz€‰Š‡„†Š‹‹ŠŠŠŠŒŒŒ‹‰†‚€~~{yxvuttvxžžžžžœœœ››››ššš›š™—–•••••”‹||yxyxy{„‡‹‰ƒ‚„…‡ˆˆŠŠŠ‰‰†ƒ~|xvusqonkgdbbccefgggglƒ”š ¤¦§©«««ª©©§¤¡œ•‹|jRRZ[\bjllljiiiiknpruz€„††††…„„„‚}yxvoffovz€„………‡Š‹‹ŠŠŠ‹ŒŒŠŠ‡„}|{xwxzzyxxxžžœœœœœœœœ››››š™—•”““”“”}zwwvvuux€‡Š‹Š‡‡††‡‡ˆ‰‡…‚€}yvsqomkjhc^[Y[`ccdegkp}‘œ¢¦©ªª««¬¬«©©§¥£ š‘ƒhMOUX\ciiiihgghijlnorw}„†††…………ƒ}zwsqqsx|€ƒ„…‡‰‹‹Œ‹‹‹ŒŒ‹Š‰†„€~}zyvtvz||{yyœœœ››œœœœœœœœœ›šš˜–”“’’~soqvzvqqy‚†Š‹‹‹Šˆ†…†…‚|zwsqnlkkiie\WTTV]`adehu€Œ— ¥¨ª«ª«¬¬¬¬ª©¨¦¤£Ÿ•ŠnLJQV]dgfffedegiiklnqty~‚…††……„„ƒ‚}|}}}}‚†‡ˆ‰‹ŒŒŽŽŽŒ‹‰ˆ‡‡„€}~}|yvtuvxyœœœ›››››œœœœœœœœ›ššš—•’‰ˆ…vaZ\cntrru{ˆ‹ŒŽŒˆ†…‚}zvusomjgghihdZSQPQW[_egm‚”¢§ª«««¬¬­­­«ª©¨§¥¢šŽqMIPW`fecbbaaadghiklmrw}€„†…„ƒƒ„„„„ƒ„„…††…†ˆŠ‹ŽŽŽŒŠ‰‰‡†…‚€~ytstvxœœ›šššš›œœœœœœœœ›ššš™—“Œ‡„„|hPHHJVeptvy}…ŠŒŽ‹ˆ„yupomjgebacefbYRNNNSZahkw•œ¢¥©«¬«¬¬¬­­­¬«ªª©¦£’sNNW\abba`][]_bdfghijouy}ƒ‚€ƒ„…†‡‰‹Š‹‹‹‹ŒŽ‘’“““““’Œ‹‰ˆ‡†…‚€~}|{œœššš™™š›™šššššššššš™˜“‹†{m^OJKKR\fmty…Š‹‹‰{wrmjhdb`__``baZRNNNRZblt…—¢¥§ª¬¬¬­­­­­­­¬««©§¤Ÿ˜‚\Zbfd`a`^[VVY]_accfimrw{~€€€€ƒ…†‰Š‹Ž‘‘’”””“’‘ŽŒ‹Š‰†…„‚€~~€‚ƒ‚~š™˜™š››››ššš™šš™š™šš™™–ˆubTKJKLPY^frz‚†Š‹‹ˆ‚{vpjec`^]]^^___[TQQQUYbp~‘ ¤§©¬®¯¯¯°°®­­­¬¬¬«¨¥¡œyrtqi_`_ZUQQTX[]`cfimrvy|€€€ƒ…‡Š‹ŒŽ‘’’‘‘’”••”““’’’ŽŒ‹ˆ……ƒ~yxvvx}‚‚™™››œœ›››ššššš™™™™™˜˜—”Ž‡vgUGBGJNW`ju~„ˆ‹ŒŒ‹‹‰†‚|vpic`_]\[[\\\]\XUSTVZct‡š¢£§©«­¯°¯¯¯¯­­­¬¬¬¬«©¦£•‰‚€xj`\WQLILPSVZ]cikmqtwz~€€„†‰Š‰‰Œ’’’‘‘‘’”–—˜˜˜—˜—–•”“‘Œ‡…„|yvttw|ƒ€š›œœœœ›››ššššš™˜˜˜˜˜—‘Š…~xq_TH@CDEJZmx‡‹ŒŒŠ‰…‚}vqjda_][ZYXXXZZZWVXY[fy‘¡¤¦©«­¯°°¯¯¯¯­­­­¬¬¬«©§£ž™’†zk]RKDABDGJMSZahlnrtwy|}~€‚…†ˆˆ‡ˆŒŽ‘’‘‘’“”–—˜›œœœœ›š˜˜—–“‘Œˆ†„ƒ|zxuu{~š››œ›››››šš™˜˜—–––••†}vhYOJCA@ADHZn|‚‡ŒŒŽŽŒ‰†„xsnhda^[ZXVUUVXYYY]^^i–£¦¨«¬­®¯¯¯¯¯¯¯°¯­­¬¬«©¨£œ—–„xgSFA?DIIGFGMWajlnqsvxz{{~„…††…‡‹‘’’’”•–—˜šžž›š™™˜––“ŽŠ‡†ƒ€}zxy|š››››™™™šš™–•“““’‘‘Ž†}xqaVLED@BDLY`hu‚…‰‹ŽŽŽ‹†„€{vrlgc^[XURPRUVY\^_aaj–¢¦©««¬®®¯¯°°°¯¯¯­­«¬«ª§¤—ŠƒztiXFDJU`hfb[OMS]ilmpsuvxxxz~ƒ„„„†ŠŒŽ’’““”–——™šžžžžžž››š˜˜•’‹‰‡…„ƒ~}~š››››™™™™™˜•’‘Š†~wl^URLFFMW]anvz~„†‰‹‹ŒŒŒˆ‡ƒ€|wsoha\WSOKIKOQTX\\^do…š¤¨ª««¬®¯¯°°°¯¯¯¯­­«¬«ª§Œynf`\RKRZ^`chlnh]QTailnpstuvvy}ƒ„ƒ„ˆ‹’’““•——˜š›œžžŸŸžžœ›™—•“Œ‹‰‰ˆ†„ƒ‚š›ššš™™˜™˜–’‘‘‹…€wndYVSOOR`pwx}‚…‡‰ˆˆ‰ˆˆ‡‡‡…„‚€|wsqjbYRNJDACBDHMW\`ftŒž¤§©««¬®®¯¯¯°°¯¯¯®¬¬«ª¨¤”yga[XWWXXUSNOV^hqn]SXeikmmnrtvy}€„ˆŒ‘’“”–——™›œœžžŸŸžž›š˜–“‘ŽŒŒŠ‰‡†‡†™™™™˜˜˜˜˜—“Ž‘‘Ž†}vne_ZXVV_iu„†‰‹Šˆ‡‡†…„ƒƒ‚€}{wsqkbYPHDEILLLJIQ]lyƒ•¡¥§¨ªª¬®®®®®¯¯¯¯®­««ªª¨ž…g]\YXYYXPJHCGMSXbmo^Ubgghhhlquyz}~~}}‚†ŠŽ’“”–—˜™››œœžžžœ™—•“’‘‘ŽŒŠ‡††††šš™˜˜˜˜˜—”ŒŽ‹…|tnha\ZZ^ep|ƒ‡‹Š‡††„€€~~{xurpkbWOJN]ghgd`YW\p‰’›£¥¦¦©ª«¬®®®®®¯¯¯®­ª«ªª©šy]WWWYZYSKEB@DGLPUbpjY\egebbfkqvvxyyyz}…ˆŒ““••˜˜™ššš›œœœœœ›™—————••“Š†„‚‚‚šš™˜˜˜˜—–“Œ‹Š…~wqlhc`^dmu|ƒ‰ŒŽ‹ˆ†…„‚~}}}{zywtsqk_TS_ouoe]XUVXYd„—£¤¥¦¦©«¬®®¯¯¯¯¯¯­¬««ªª¨•t[RQV[[WQGDCDDCIOT]ko[Sada^^chnsuuuxyyz~‚‡‹Ž‘’““”—˜™˜˜˜˜˜™™™™™™™šššš—”Œ‰†‚€~{z™™™˜˜˜˜˜—•’Œˆ„€yrnkklnry~ƒ‡Ž‘‘‹‡„ƒ‚€|{yxvvttsqpj[UestiWKDBCGLS]t’¢£¥¦¦¨ª«­®°±±°°¯®¬««©©¥saUPZ`\WPDEBCDGMRX]gpZKZ]YX[`ehlqstwyyz{†Š‘’“””–—˜˜———˜˜˜™™šœž›™™˜”ŽŠ…€|xwvut˜˜˜™™™˜™˜—”‘Šˆ…|tqooquy„‰‘‘‘‘‘‹…}zxusqoonnnoh[btrdRB;;<?BDKUfš¢£¥¥¦§¨ª¬®°±±°°¯­¬ª©§¦¤•yiZOak_XN=ADBCLTRX^hqYDRVVVZ^adhloqtwxyz}…ŠŽ‘““”–——˜˜——˜˜˜˜™šš›œ›˜•‘Œ‡zusrqrs˜™™™™™˜™™˜–““’‰xsppsuw}…‰‘‘’’‘‘‘Œˆƒ~zyzxuqnkgghhijd^jpfUJ@989=ABGN[p¡¤¥¦¥¦¨ª¬¯°±±°®®¬«©¨¦¤£‡r_OgqaWNHvŠaCDJMNYkpVBNUUWZ^_aeilnruxz|~„ŠŽ‘“”–—˜˜˜˜˜˜˜˜˜™›œš™™™—“Š‡{vsqpqr˜˜™™™™˜™™˜–“’Žˆzvtsx|‚†Š‘’’’’‘Ž‹†|xwwtqmjfaacdec^bnj[L@;;;<>>@EKUg‚›£¥¥¥¦§©«­°°°¯®¬««¨¦¦¤¢ž”_KiveYSQ€•eBCGJO]niPCMSVZ\^_`cgkosvy{}€…Š‘“–—˜˜™™˜˜™™™™š››š™˜™—“Œˆƒ|{xvvvv˜™˜˜˜˜˜˜˜–“Œ‡€}zyxz~ƒˆŠŒ’“““’’‘ŽŒ‰„~zvtqqnje`]]`bc_[isgVH>9;<=<COJIUdv’¢¤£¤¤¥§ª¬¯¯¯®­¬«ª§¥¥¤¢Ÿ˜‚VIdukZTPNLHCFKOXfj[HBKRWZ[[\]_dimqux{‚‡‹‘”–˜™š››š™ššš™š›šš™˜˜–“Œ‡‚‚€}{zyx˜˜˜————–•”’‰…‚|z|„ˆŒŽ“”•••”“’‹‡‚}ytpnnlhc^[Y[^_VPhrcSGA;GLC;I]NJYgi„Ÿ£¤££¤¦ª¬®­®®­¬«¨¥¥¤¤¤¡—}PGXpqaZVNHHFIMR]gaOBCKQUWXYZ[]cilptw{~‚ˆ‹’•–˜™›œœ››œœ›šš››š™˜˜•‘ŒŒ‰ˆ‡…ƒ€}{x”•–—––•”””’ŽŠ‡‡…‚€‚…‰Ž‘‘–——˜–”’‘Œ‹‡}xsomligc]ZXXZ[PJcn`PGAMŠ—W:=AGN]jdx˜ž¡¢¢¤¦©««¬¬¬««ª¦¥¤¤£¤£—yJBFcrobZSNMMNPW_cWGCGMRTUUXZ\_ekmptx|ƒˆ‹’•–˜›œœžœœ››š˜–”“ŽŽ‹‰†„‚~zxv’“”•––””•”“ŽŒ‹‰†…†ŠŒŽ‘“”—˜˜˜—•’’‘‹‡|ytqmlihd]YWVWXNH^k`QJDHfkG:<BJQbk`m’ ¢££¥©«©ªª©¨¨¦¤££¢¡¡¡—vH>=Lbqj\XUUUQR[`ZKAFLPUVVVY\_ehlmqvz~…ˆŠ’•—™›œœœ›š˜–“’‘ŽŽ‹‰†‚~{wtr‘’”•––––••“Œˆ‡ˆ‹‘“•˜˜˜˜—•“’‹†|xsomkhfb\XVUUUKAThdWNHFC;7;BJOVegXaŽ¡£¢¢¡¢¤¥¤£££££¢¡¡ Ÿžœ’tK==CMV^[ZXVUQRXVKBER^cfecdddehimptx|„‡‰‹’•˜™›œœœ›š˜•”“’‘Ž‹Š‡‚|xuroˆ‹‘’”•••••”’‘ŽŒŠ‹Ž‘‘“–—˜˜˜˜–”“‘‘Š„}wrmjhea^ZXVUVUM?IbkcUMJF>:?INR^g^S^Œ¤¢Ÿžžžœ›™™œžŸžž›™–ŠpQD?MQPOMNOPQUWVTUcs{~~{wsqkfdfinquyƒˆ‹Ž’•˜™›œž›š˜––••““’‘ŒŠ‰†ywvuƒ…‡ŠŒŽ‘‘’“”•“’Ž’“–——˜˜˜˜—•’‘‘‰ƒ~{vplhe_[YXWXWWWRGCSkpdZQJGDENRWdfXQ]‡ ž›šššš™˜—••—š›šš››š˜’†t_]`nwwonoqrsw|€…‰”““Ž‡~xtmhhmorty}‚‡‘‘’“”—™›žš™————–•”“’ŒŠ‰ˆ„~||z€‚…‡‰ŒŽ‘’“”•”“’ŽŽ’“—˜˜˜˜˜™˜–”’‘‰‚{vojgc]YVUVWYZYXQF?OjlcYTSRRVX\`_XU^€™š™˜˜˜—•””’’•—˜˜——˜™˜•†x{}…‹‹ŒŽ‘’‘’”•–˜™™œžš’‰yroqtttw{‡Œ‘”•”““•—™›œ›™——˜˜——–•“‘ŒŠˆ…€~{y}„†ˆ‹’““””’‘Ž‘”—™™™™™™˜—•’‘ˆƒ|wqlhd_\YWYZ[\ZXTMCANZ^dc[Z[]`gmnkjk€–˜————–”““’”—————–—™™˜•ˆˆˆˆ‹‹Ž‘”—™ ŸžœŸ¡¢Ÿš”Œ‡†€}}{{~ƒ‰Ž‘•———–––˜™›œœš˜—˜˜˜˜——–•“’‹ˆ…‚}zxwz|ƒ†ˆŠŒŽ’“’‘Ž‘”—™™™™™˜™—•“‘Œ‡ƒ|ytokhecb``aa_^YVTRMLMOUWTU^iuŒ‰|wz‡”˜–––––•”“”–—————––š›˜˜–“‘”—šŸ¢¦©ªª§§¨¨©§¦¤¡œ˜••‘Ž‹‹Œ“–˜™™š›š™˜™š››™™™˜˜˜˜——–•“‘Ž‰†„yuquwz|~ƒ„ˆŠŒ‘‘ŽŽ‘“—™™™™™™™™—”‘Œ‡‚|yvpkifeeeinmllmpsssssrqrt{„”—•Ž‡„ˆ–˜™—––––•”–——————––šš™š˜–”•—šŸ£¦«°²±¯­¬¬­­¬ª§¥£¡ŸŸš˜–•–—™œžžžœ›œœœœœœœš™˜˜˜˜˜—–––”‘Šˆ„~ylortwy{}€„†‰ŠŒŽŽ‘“—™™ššš™™˜—•‹‡~~zwtohecdejpsw}†Œ“•––••–————˜˜–’•™š™—–––––————————•–™›œž›˜——™œ £§¬°²±°¯®­­­¬ªª§¦¥££¡žœ›š›œžžŸ   ŸŸžžžžžžœœœ›š™˜˜™˜—––––”‘‹Š†hknpqsvx{ƒ„†‰‹ŽŽ’•˜™šœœ›™˜—–‘Œ‡ƒ€~{wqkfefhkow‰‘˜Ÿ¡¡¡ œœ›™˜˜˜˜•‘’‘’—šœ™—–––——˜———————••™›œž žœšš›ž¢¤¨­¯±±±°¯®¯­¬¬ª©§¦¥¥£¡ŸžžžŸ     ¡      Ÿžœ››ššš™˜˜˜—–––•”’‘Ž‹Šˆ…begiknqtx{}€‚†ˆŠ‹‘”—™›œš™˜–”Œ†„‚|xsnkkllns{‡‘—œ£¥¥¥¤¢   ›š™˜•’’•–š›œš—–•——˜˜˜———————–™™œ¡£¢¡ŸžŸ £¥©¬¯±²²²±°°¯®¬«ª©¨§§¦¤¢   ¡¢¢¢¢¢£££¢¢¢¡¡¡ Ÿžžžœššš™™™˜˜˜—–––“’‘‘‰…^`bdfgkoswx{‚„†‡‰‹“–˜›œ›š™˜–’Œˆ†‚~zvsppqrv{ƒŽ—Ÿ¥©ª««ª©§¦¦¤¡ž›™™–”–™œ™—”“—˜™˜™—————˜——˜š¡¤£¤¢¡¡¢¤¦ª®°±²³²²±°°¯­¬«ª©©¨§¥¤¤¤£££¤¤¤¤¥¥¥¤¤¤¤¤£££¢¡Ÿœš™ššš˜˜˜˜––––”’‘’‘’Š‚|[]_`cehkmorvz~€‚„…‡‹’–˜›œ›š™–“‘Ž‰„yusrsv|ˆ’™Ÿ¥©«­­®®®¬«ª¨¦£ œš˜™œžž›••——˜˜—–••”“““—¡¤¥¤¤££££¥¦©­°²²±°°°°°¯®¬««ª©©¨§¦¦¥¤¥¥¥¥¥¦§¨§¦¦§§§¦¥¥¤£ ž›š››š™™™˜˜———–”“““’‘Œƒ|Z\^_acdeghkosx{~„…Š’–™šœžžœ›š˜–•“Š†|xusu|†—£©«­¯¯¯°°®¬«©¦¤¢žœœ› ¡¡ “‰‰‘‹ˆ……‰“Ÿ£¦¦¤¤¤¤¤¤¥¦¨ª­°°°°°°°°¯¯­««ª©ª¨§¦¦¦¦¦¦¦¦§¨©©©©¨©©©©§§¦¤¡Ÿœ›››š™š™˜˜˜˜˜——–”“‘Ž…XZ\^___abcehlrvy|~‚†ˆŒ’–˜šžŸŸž›š˜”Œˆ…†…†ˆŽ–™›Ÿ¦ª«­¯¯°±±±¯¬ª§¤¡žœ¡¤¤£¡›ƒƒƒ…†……………„€~†Ž›¢¦¤¤¥¥¥¥¥¥¦¨©ª¬¯°°°°±±°¯®««ªª©¨¦¥¥¦§§§¨¨©©ª««««ª©©¨§§¦¤¢Ÿžœ›››š™š™˜˜˜˜˜™™˜–•“‘ˆXZ[[\\]]^_adinqsuxz~€ƒ‰Ž“–™œž  ŸŸ¡¡ŸŸ™•“’”–—˜žŸ ¤¨«¬­¯°°±±±°®«¨¤¡ŸŸŸ¡¤¥¤£ ˜‰€‚‚€€ƒˆŽ•›Ÿ¢¤¥¥¥¥¥¦¨©ª©ª­¯¯°°°°°¯­««ª©¨©§§¨¨©©ªªªª««««««ª©¨©§§¦¤¢Ÿžœœ››ššš™˜˜˜˜™™™˜——–•”“YYYYYYZ[\]^_dimopqtx{}Š‘–™›Ÿ  ¡£££¡ žœ›™™š›žŸ¢¥§©ª«¬­¯°°°±°°®«¨¥£¢¢£¤¥¥¥¤Ÿ•‡€‚…‡‡‡‡†‡ˆ‰ŠŠ‹‹ˆŠ— ¤¥¥¥¥¦§¨©©©©ª«¬®¯¯¯¯­¬ªª©¨©©©©©ª«««¬¬««««««««ªªª©§¦¤¢ žœœ››ššš™™˜˜˜™˜˜˜˜˜˜˜™™ZYXXXXYY[\\]_dikmnqtvy}…–˜›žŸ ¢£¢¢££¢£¢¡¡¡¢¢£¤¥§ªªª«¬­¯°°°°¯¯¬ª¨¥¤¤¤¤¥¦¥¤¢–…€„ˆŒŽŽŽ‘‰‡€{…—¡¤¥¥¥¥¦§¨©©©©¨©ªª««««ª©©©¨©©ªªª«¬­®®­­¬«««««¬«ª«©¨§¦¤£¡Ÿœ›š™™š™™˜˜˜™™™™™šš™››YYXXZZZZZ[[\\_dgjlnqux{‚Ž•˜šœ ¡¢¤£££¤¥¥¤£¤¤£££¥§¨©ªª««­¯°°°°¯­«©§¦¦¤¤¥¥¥¤£œ‡ts|‡‰Š‘‘•{ruy{„–Ÿ¢¤¥¥¤¤¤¥¦§©©¨§¨¨©©ª©©¨¦¦¦¦§¨ªªª«­®¯¯­¬¬¬¬¬¬¬¬«ªª©¨§¥¤£¢ žœššš™™šššš›œ›››œ›šš™YYWXYXYZYZZYZ\`cdgilpux‹“—™›Ÿ¡¢¤¤¤¥¥¥¦¥¥¥¥¥¦§¨ª«««¬¬¬­¯°°¯°¯­«©§¦¦¤¤¥¥¥¤¢˜‚nlpngo„Ž’”„b`sš¡£¤¥¥¥¤£££¥§¨¨§¨¨¨¨©©©¨¦¥£¢¤¦©©ª«¬­­­­­¬¬¬¬¬¬¬¬«ªª©¨§¥¤££¡ Ÿœœ››››œœœ›ššššš˜˜™™YYXWVUUXYZ[ZZ[]_`cdhlqw‰•˜› ¡£¤¤¥¥¥¥¦¦¦¥¦¨©©ª««««¬¬¬­®¯¯¯®­¬¬ª¨¦¥¥¥¥¥¥¤¢”ƒztk`br†|iuŠ—Ÿ£¤¦§¦¦¥¥¤¤¤¦§¨¨¨¨©©©©©©§¦¤£¢£¦©©«¬¬­¬¬¬¬¬¬¬¬¬¬¬¬«ªª©¨§¥¥££¢ žœœ››œššš™˜——˜˜˜——˜˜XZZWTRSUWXYZ\\\\]`cglry‰‘–™›Ÿ £¥¥¦¦¥¥¦¦§§§©ª««¬¬¬¬­­­­¬­®¬¬««ª¨§¥¥¤¤¤¥¦¥¤¢Ÿ™•’‹vu„‹yŒ› £¤¦©©©©¨¦¦§¨©©©©©©¨¨¨¨¨¨¦¦¥¤£¤§©ª«¬¬­¬­­¬¬¬¬¬¬¬¬«ªªª©¨§¦¥¤£¢Ÿžœ›š™™™˜˜—–••––——–––VY[WTQRSRQRTW[]]\afkr{ƒ‰•˜™›Ÿ¡£¥¥¥§§¦§¨¨©©¨©ª«¬¬¬¬®¯®¬¬¬««ª©©§¦¥¥¤¤¤¤¥¦§¦¥¤££¡œ”Š„‡ŠŒ‹Š†‰™¡£¤¦¨ªªªª©¨¨¨©©©©ªª©©©©¨§¦¥¥¥¥¥¥§©ª«¬¬­­®®­®®­­­¬««ªªª©¨¨¥¤¢¡Ÿž›™˜˜—–•••”“’‘’“”””””VZ[ZWTTSSSSSUXX[_cksz„‘“—˜™šž¡£¤¥¦¦¨¨¨©©©©¨¨©ª««¬¬¬¬¬¬¬¬ªª¨§¦¦¦¦¥¥¤¤¤¥§¨©©©¨¨¦¤ ˜Œ‡Š‹ˆ‡Š’ž¤¥§©ªªªª©©¨©©©ªªªªª©©©©¨¦¥¥¥¥¥¥¥¨©ª¬¬­®¯¯¯¯¯¯°°¯­¬«ªª©¨¨¦£Ÿœš™™—•”“‘ŽŽ‘‘’’“\^^]ZVWXXXXYZZZ\cir{ƒ‹‘”•—™™šœžŸ¢¤¤¦§¨¨¨©©©¨¨§¨©©©ª«¬¬«««ª©¨¦¦¦¦¦¦¦¦¥¥¥¦§©ªªªªªª©§¡”‹ŠŠ‡‡Žš£¦§©ª««ªª©¨§©©©©©©©©©©¨¨§¦¥¤¥¥¥¥¦¨ªª¬­®¯¯¯¯¯¯°¯°®¬¬ª©§¥¢¡ žœš™——–”’‘‹‹Œ‹‹‹‹‹Š‹Œ‘‘aba^ZVVWVWWXYY[^gov}…•——˜™˜™šœœž£¤¥§¨¨©©©©ªªªªªªªª«««ªª©©¨¨§¨¨¨¨¨§¦¥¥¥¦§¨©«ªª«««¨¥ž’ŒŠ‡‹—¡¥¨ª«««««ª©©¨©©©©©©©©¨§§¦¦¥¥¥¦¦¦§§©«¬­®®¯¯¯¯°¯¯¯¯¬ª©¦¤¡ž›š™˜——•’Ž‹‰ˆ‡‡ˆ‰‰ŠˆˆˆŠŒ‘^_^[WRRRSSRQRTX_emt{‡’—˜™™™™™šššœ¢¤¥¦¨©©ª««¬¬¬¬¬¬¬¬«««ª©¨¨¨¨¨¨¨¨¨¨§¦¦¥¦¦§¨©ªªª«««ª§¢–†„£¥§ª«««¬«««ªª©©©©¨¨©¨§¦¦¦¥¥¥¥¦¦¦¦¦¨«¬­¯®¯¯¯¯°¯­­¬©¦¤¡Ÿ›˜™—“‘ŽŽ‰…ƒ‚„†‡‰‰‰‡ˆ‰‹Œ‘]^^[XUUUTSSSUX]admv€•˜™šš™™ššœ›¡£¥§¨©ª«««¬¬¬¬¬¬¬¬ªªª©©¨¨¨¨©©©©©©¨§¦¦¦¦¨©©©©©ªª©¨§£™Žƒ‚Ÿ¡£¥©ªª¬¬«««ª©©¨¨§§§¦¥¥¥¥¦¥¥¦¦§¦¦¦©ª«­¯¯¯¯¯¯¯­«ª¨¥£Ÿœ™”‘Š‡……„ƒ€}}~‚‡‰Š‹‹ŠŠŠ‹ŒŽ‘bcb_\[[ZXVUUVY^afoy‡’–˜šššššš›œ £¤¦©ª«¬¬¬¬¬¬¬¬¬¬¬ªª©©¨¨©©©ªªªª©©©©¨¨¨§§¨¨¨¨§§§¦¦¥¤›„‚››Ÿ¢¥¦§©©©ª©©©¨§§¦¦¦¦¥¦¦§§¨§¨¨§¦¦¦¨ª«­®®¯°°¯¬«ª©¨¥¡ž˜“Žˆ…‚‚ƒ„…†„‚€~€ƒ‡‹ŒŒŒŒŒ‹‹‹ŒŽŽiihfddca^[YWX]cfnw•˜™››œš™™šœœ ¢¤¦©ª«¬¬¬¬¬¬¬¬¬¬¬«ª©©¨¨¨©©ªªªªªªªª©©©§¦§¦§§¥¥¥¥¥¤¢š‘„~—›ž ¡£¤¥¥¦§¨©¨¨¨§§¦¥¥¥¦§¨©©©©©ªª¨§§¨ª¬®¯¯¯®¯­«ª¨§¥¥£Ÿ›–’Œ‹‹‹Œ‹‹Š‰‡„†ˆ‹ŽŽŒŒŒŽŽŽŽŽoonmkkjhda^]`gns}„‹’—™š››œšš™šœ £¤§©ª«¬¬¬¬­­­­­¬¬¬ª©©¨¨¨©ªªªªªªªªª©©©§¦¦¦¥¥¤¤¤¤£¡ž—‹}y…•œŸ ¢£¤¥¥¦¦§§§¦¦¥¥¥¥¥¦§©ªªªªª««ª©¨©«¬¯¯®­­­«ª©¥£  Ÿžž™—•••••“’’‘ŽŽŽŽŽsssrrrqnkgffkrx~„Œ’—™šš›šššš™šœž £¤¨¨©ª«¬¬¬¬¬­­­­­¬«ª©©©¨©ªªªªªªªªª©©©§§§¦¤£££¢¢ ›–‡{xˆ”™Ÿ¢¤¥¤¥¥¦¦¦¥¤¤¤¥¥¥§§©ªªªªª¬¬¬««¬­®®®­««ªª¨¦¤¡žžž›˜˜˜˜˜™™™˜–“’‘‘‘‘‘‘ŽŽŽŒŒwwvvuuutronry~„Š“–™›œšš™™™™™šœž ¤¥¨¨©ª««¬¬¬­®®®®®­¬ªª©ª©ªªªªªªªªªªª©¨§¦¦¥¥¤¤¢Ÿœ˜“‰…€€ƒ„†ˆŒ‘•˜¢£¢£¥¥¥¥¤¤¥¥¥¥¦§©©©©©ª«¬¬¬¬­®¯­­­¬«©§¦¤£¢ ŸŸœš™™™™™›››š—•“’’‘‘‘‘‘‘ŽŒŒŒŒ‹{{zyyyyyxxy}„ˆ‹‘”•—™œœš™˜˜˜˜˜šœž ¤¥¨¨©ª««¬­®¯¯°°°°¯®¬«ªªªªªªªªªªªªªª©§¦¥¤¤££¡˜’Ž‰‡‡†„††………‚€ƒ‡•˜œ £¤¤¤¥¤¥¥¦¦§©ªªªªª¬¬­­­¯¯¯®®®¬ª©¦¥¤¢¡  Ÿœ›š™™™™™šš›š—”““’‘‘‘‘‘ŽŒ‹‹‹Š}}}{{{{|ƒ…‰ŠŒ’”•–—˜™™™˜˜™˜™šœž ¤¥¨©ª«¬¬­¯°°±²²²²±°®¬««««ªªªªªªªª©©§¦¥£¢ ™”Œˆ†‡‰Œ‹‰Š‰‡‡†‚}{xttzƒš¡£££££¤¥¦§§©ªªªª¬­­®®¯¯°¯¯®®­¬«§¦¥£¡  ž››™™™™™™™™™˜–“““’‘‘’‘‘ŽŽŒ‹‹‹‹Š€|{}…‡‡ˆ‰‰‹““””•–—˜˜˜˜˜™šœž ¤¥§©««¬®¯°±±²²²³²²±°®­¬««ª©©©©©©¨§§¦¥¤¢ œ˜“ŽŽ‘‘’’‘‘Ž‘‘ˆ‚}xwŠ”œ ¡¢¢£¤¥¦¨¨©ªª©«¬®¯¯°¯¯°¯¯¯¯¯®«¨¦¥£¢Ÿžž›™™˜˜˜˜˜˜˜˜——•“’“’’’‘‘ŽŽŒ‹ŠŠŠŠ€ƒƒ~{~…‡‡††‡‰Š‘‘‘‘’”–—˜˜˜™š›ž ¢¥§©«¬®¯°±±²²³³³³³³±±°¯¬«©ª©©©¨©¨§¦¦¥£ ›˜–•—šœœœš˜––—šœš”‘ŒŒŽ’–šž ¡£¤¥¥§¨©ªª©ª¬®¯¯¯¯¯¯¯®®¯¯®­«¨¦¤£¡Ÿœ›š˜—–––––––––•“’““’’‘ŽŽŒ‹‹‹‹Š€†ˆ‚€‚ƒ„‚€‚…‡‰ŠŒŽŽŽ’”•–—˜˜™›œž ¢¤§©«®¯¯±±°²²³³³³µ´´´³±¯®¬«ª©©©¨§¦¦¥¥£ ŸœžŸ ¡   žšššœžž ¡¢¢Ÿœš˜–˜šœžŸ¢£¥¦¦¨©©ªªªª¬®¯¯¯®­­­¬««¬¬«ª¦¤¢ ›™˜˜—–•••””•––•“’’‘’‘ŽŒŒŒŒŒŒŒŒŒ‹‹Š‚ŠŠ…„~|zy|€…‡‡ˆˆ‰Š‹’“”•––—˜šœžŸ¢¥§©¬®¯°±°°²³³´´´µµµµ³²²±°­¬«ª©¨§§¦§¥£¢¢¢¢££££££¢¢ ž  ¡¡££¤£¢Ÿ››œž ¢£¤¦§¨©©ªªªª«¬¬­®­­¬¬««©ªª¨§¥¢¡žœ™™˜˜——–––——––––•“‘ŒŠŠŒŒŒ‹‹ŠŠŠŠ‹Š„Œ“‹‡‚~zyyy}€‚ƒƒ„„†‰Ž‘’“”•–——™šž ¢¤§©¬­¯¯±±±²³µµµµµµµµ³³³²°¯®¬«©¨¨¨¨¨§¥¥¦¥¥¥¥¥¤££¤££¡¡¡£¤¤¤¤¥¥¦¥¢ žžŸ¡¢¤¤¥¦¨©©©ªª«««¬¬¬«««ªªªª©¨§¤¡ žœ››™™˜˜——————˜——–”“’Ž‹Œ‹ŠŠŠŠ‹ŒŒ‹ŠŠŠŠŠŠŠ‹‹†Ž”–’‡{yyxxz~ƒ„…‰‘’’“•–—˜šž ¢¤§ª¬­¯°²²²²³µµµµµµµµ³³³²±¯®¬«ª©¨¨©ª©¨¨¨§§§¦¦¥¤£¤¥¥¤£¤¥¦¥¥¥¦§§¦¤¢  ¡¢£¥¦¦§©©©ªªªª««¬«ªªªª©ªª¨§¦¤¢ŸŸœ›š™˜˜——˜—–—˜˜˜—•’‘‹Šˆˆ‰‰‰‰‹ŒŒŒ‹‹‹‹‹‹‹‹‹‰‘˜™˜–‘†~{ywwy|ƒ„†ˆŒ‘’““•–˜š ¢¤§ª¬­¯°±²²²³´³³³³³³³³³³²±¯®¬«ªª©ªªª©©©©¨§§§¦¦¥¥¥¦§¦¦¦§§¦¦¥¦¨©©§¤£¡¡£¥¦¦§©ªªªªªªª««««ª©¨©¨§§¥¢¡¡ Ÿžœ›š™˜—————––——•”‘ŒŠˆ‡‡ˆ‰‰Š‹ŒŒŒŒŒ‹ŠŠŠŠŠŠ’˜™™™–Œƒ~zyz|~€‚ƒ……†Š‘‘’“”•—™ £¤§ª¬­¯¯±²²²²²²²²±±±±³³²²±°®­«ªªªª©©©ª©¨¨¨¨¨¨¨§§¨¨©©©©¨©¨§¦¦¨©ª¨¤£¢£¥¦§¨ªª««««ªªªªªª©§¦¦¥¥£¢ œœ›š™˜˜————–•––”’Œ‰ˆ‰Š‹‹‹‹ŒŽŒŒŒ‹‹‹ŠŠ‹‹“—™™™˜’ˆ€{{„„ƒƒƒƒ„…‰Œ‘‘’’“–™ ¢¤§ª«¬¯¯°±²²²²²²²±±±±²²±°°¯¯­«ªªªª©©©©©©©©¨¨¨¨¨¨¨¨¨¨©ª©ª©¨¦¥¦¨©§£££¥¦§©©ª«¬«¬««ª¨¤¡ŸŸžŸŸžœ›ššš›ššš™™˜˜——––––•”’‹‰‡‡ˆŠ‹‹‹ŒŒŒ‹‹ŒŒŒ‹‹‹‹Š‹Œ“—˜™™˜–Ž…‚†’‰…‚„ˆ‹Ž‘‘‘’”˜Ÿ¢¤¦©««­¯°°±±±²²²²²²²±°°°°¯¯¯­«««ªªªªª©©©©©©¨¨¨¨¨¨¨¨¨©©ªª©§¥¤¤¥¥¤££¤¥§¨©©ª«««¬¬¬¬¨¡›˜—˜˜˜˜˜——˜˜™™™™™™™™˜˜—––••••”‘ŒŠ‰‡††ˆŠŠŠ‹‹ŒŒ‹Š‰Š‹‹‹‹‹ŠŠŠŠ‹Œ‘•˜™š™˜”‘•šš–‘Ž‰†ˆŒ‘‘’”—œŸ¡£¤§ª«­®¯°±±±±±²²±±±±°°°°¯¯­¬¬«ªª©©ªªªªªªªª©¨¨¨¨¨¨¨¨¨©ª©§¦¤£££¤£££¤¥§¨¨©ªªª«¬­­¬¨¡›—————˜—––———˜˜˜˜˜˜˜—–—–••”““‹Š‰ˆ‡ˆˆ‰ŠŠ‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŠ“—™š™˜––˜™™—ˆ„ƒ‡‹’‘ŽŽ’“•–—›ž £¤¦©«­¯°±±±±±±°±°°±±°¯¯¯¯®¬«ªªªª©©ªªª«««ªªª©©©©©¨©©©©©©§¦¤££££¢££¤§©¨¨©ªªª««¬­«§ ›˜˜˜—˜˜˜˜—–———————–————–•–”’ŒŒŒ‹ŠŠŠŠŠŠ‹‹‹ŠŠ‰ŠŠ‰ŠŠŠŠŠ‰‰ŠŠŠŒŽ“•—™š™˜————–‘Š‚~ˆ””‘’”–––—š £¤¦©«­¯¯®®®®¯¯®¯¯¯¯¯¯®­­­¬ªª©©©©©©ª««¬¬¬¬««ªªªªª©©©©©©¨¦¥¤¥¦¦¥¤¤¥§¨©©¨©ªªª«¬¬­«¦¡˜˜™˜˜————•••••••••–—–—–•”’‘‹ŒŒŒŒ‹‹‹‹‹‹‹‹‹Š‰‡‰‰ˆ‰ŠŠŠŠŠŠŠ‹‹•—˜™š™™˜˜˜—”Žˆƒ‡‘–——”••––•”•–—˜›Ÿ¢¤¦©«­­­««¬¬¬¬¬¬¬¬¬­­¬««««©¨§¨©©©©ª«¬­­­­­¬«««««ª©©¨¨§§¥¤¥§¨¨¨§§§¨©©¨¨ªªªª««¬¬ª¤Ÿ›˜˜™˜—––––””””””””–––––•”“‘ŽŒ‹‹‹ŠŒŒŒŒŒ‹‹ŠŠ‰ˆ‡ˆˆ‡ˆŠŠŠŠŠ‹‹‹Œ—˜˜šš™™˜˜˜˜•‹‰Š’™šš›™˜˜—•’‘“”—˜ £¦©«¬¬«ªªª««©ª©ª««¬ª©¨¨¨§¦¤¥¥¦§©©©ª«¬¬¬¬­­¬«««ªª©©©©¨¦¦¦§©©ªª©©©©ª¨§¨ªªªªªªª«©¢žš˜˜˜˜—•••”“““““““”–•––•”“’‘ŽŽ‹ŒŒ‹‹‹ŠŠ‰ˆ‡†…†††‡‰‰‰Š‹‹‹Œ’—™š›š™™˜˜˜——““˜œžœ™—•”‘‘“••–›Ÿ£¥¨ªªªª©©©©ª©©¨¨§§§¦¥¤¤¤£¢¡¡  ¡¡¡¡¢¥§¨©ª««ªªª¨©©¨©©ªª©ª«¬¬¬­¬«««¬¬ª¨¨©©ªªªªª¨§£žš˜˜˜˜—”““““““““““”––—–•”“’’‘‘’’ŒŠŠŠ‹Š‰ˆˆ†…„„…††‡‰‰ˆŠ‹ŒŒ”˜™š››™™˜™˜˜˜—–˜š›œ›š™˜—””••–––›Ÿ£¥§¨©¨©§§§§¨§§¥¤££££¤££££¢¡ Ÿžœœ›œŸ¢¥§ªªª©¨¥¥¥§¨¨ª«¬¬­®­®¯­¬¬¬­®¬«©©©©¨©ª©¨¦¥¢žš˜˜˜—–”““““”•••””•––––•”’’’’’“”“‹‰ˆˆˆ‡‡‡†…ƒƒ„……†‡‡‡‡ŠŒŽŽŽ•˜šœœ›šššššš››žžŸžžœœœœš˜—˜™˜˜™šœ £¥¦§¦¦¦¦¦¦¦¤££¢££££££££££¢¢¢¡ŸžžžžžŸ¢£¦¨¨©¨¦£¢£¤§ª¬­¯­­®®­®­­««­°­«ª«ª§§¨¨¨§¥£ œ˜——––•”“’“”–———–”•••””•”“““““’“‘Œ‡„„„…†††…ƒ€„†††‡‡‡ˆ‹”—™œžžŸ  ¡¢££¢ ž›ššš›œœž ¢¤¤¥¥¦¦¦¥¥¥¤¤££££¤¤¤¤¤¤£££¢¢¢¢¡¡¡¡¡¢¡££¤¥¦§¨¦¤££¥ª«¬­­¬¬¬¬¬¬«ªª«®°­ª¬¬ª¦¤¥¥¤¡ žœš———––––”“•–—˜˜—–””””“”•–——•“’Ž‹†ƒ‚ƒ„…………‚€…††‡‰‰Š‹Ž–˜šœžŸ¡¡¡¢£££¤¥¥¦¦¥¤¢ ŸŸŸžžžŸŸ ¡¢£¤¤¤¥¥¥¥¥¥¥¥¥¥¤¤¤¤¤¤¤¤¤¤¤¤££££¢¢¢¢£££££¤¥¥§¨§¦¤¤¥©¬­­¬ªªªª©¨¨§¨©­®¨¨ª««¦£¡ Ÿžœ›š˜——————–––———˜˜—•””””•••––•”‘Œ‹‡„ƒ……………„ƒ€€ƒ††‡‰Š‹ŒŽŽŽ—™œŸ¢¥¥¥¥¥¦¦¥¥¥¦¦¦¦¥¤£¢¢¡¡ Ÿ   ¡¢¢£¤¤¤¤¤¤¥¥¥¥¥¥¥¥¤¤¤¤¤¤¤¤¤¤¥¤¤£££¢¢¢¢¢£££¤¥¥§©§¦¦¥¤¥ª¬¬«§¦§§¦¥¥¤¤¨«¬¨¤¤©¨¦£¡žœ™˜˜———˜˜˜˜——˜—˜˜˜—–––•••–•”“‘ŽŒ‰‡†…†ˆ‰‰‡…„ƒƒ……‡ˆŠ‹ŒŽ˜š ¢¥§¦¦¦¦¦¥¥¥¥¥¥¥¥¥¦¥¤¤¤££¢¢¢¡¡¡¢£££¤¤¤¤¥¥¥¥£¢£££¤¤¤¤¥¤¥¥¥¥¤¤¤¤££££££££¢£¤¥¥¦¦¦¥¤££¥¨ª©¥¤¤¤££¡ ¡¤¥¨¨¡›Ÿ¢£¡Ÿžœ™–”•–——˜˜˜˜˜™™™˜˜˜—––—–•”“’‘ŽŽŒ‹‰ˆˆ‰Š‹‹‹ˆ…„„ƒ„……†ˆ‰‹‘‘‘Ž™œŸ¢£¥¦¦¦¦¦¦¥¥¥¥¥¥¥¥¥¥¥¥¥¥¤¤¤£¢¢¡¡¡¢¢£¤¤¤¤¤¤¤£¢¢¢¢££¤¤¥¥¤¥¥¥¤¥¤¤¤¤¤¤¤¤¤££¡ ¢¥¥¥¥¥¤¢  Ÿ£¦¦£¢¡  ¡ žžŸž ¢ ™˜œžœš—“–—˜˜™™™™šš™™˜—––——–““‘ŽŽŒŒ‹‹ŒŒŠˆ††……†‡‰Š‹‘‘‘‘Ž™›ž¡¢£¥¦¥¥¦¦¥¦¦¦¦¦¦¦¦¦§¦¦¦¦¦¥¥£¢¢¢¢£££¤¤¤¤¤¥¤¤¤£¢¢££¤¤¤¦¥¦¦¦¦¦¥¥¤¤¤¤¤¤¤¤¢Ÿž¡¥¦¦¦¥£ ŸžŸ £¢Ÿžž ¤¢›š™šœ™–—š››™—“Œ’•˜˜™šššššš™˜—–••••”’‘ŽŽŒŒŽ‹Š‰‰ˆˆŠŠ‹ŒŽ‘’’‘‘‘‘Œ—™Ÿ ¢¤¥¤¤¥¥¦§¦¦§§§§§¨©©ª©ª©©§§¥¤£¤¤¥¥¥¦¦¦¦¥¥¥¤¤££¤¤¤¤¤¦¦¦¦¦¦¦¦¦¥¥¤¤¤¤¤£¡ŸŸ¢¥¦¦¦¤¢ Ÿ›š›Ÿ¡ŸŸž¢¡œš˜–•˜™™˜–˜šš—“ŽŠ‹‘–˜™š›››š™™˜—•““““’‘‘ŽŽŒŒŒŽŽŽŒ‹‹‹‹‹ŽŽŽ‘’’’’’’’ŒŽ–™œžŸ¡¢¤¤¤¥¥¦¨§¨¨¨¨©©©ªªªª«¬¬ª©©¨§§§©¨¨§¦¦¦¥¤¤¤¤£¤¤¤¤¤¤¦¦¦¦¦¦¦¦¦¦¥¦¥¥¥¤£ŸŸ¢¤¦§§¦¥¤¢Ÿ›™˜›žžš—•’–˜™—˜™™•ŠˆŒ“—˜˜™™™™˜˜——–”““““’ŽŒŒŒŒŒŽ‘‘‘’“’’’’’‘ŽŽ‘‘”—šž ¡£¤¤¥¥¦§§¨¨¨©©©©©©©©©¨©©©©©©©©©©©§¦¦¥¤££££££¤¤¥¥¥¦¦¦§§¨¨¨§¦¦¦¦¥¥¤£¡¢¤¤¥§©§¦¥£Ÿ›™š›œš—““•™——˜–’‡‰’˜™˜˜˜—˜˜——––•”’’“’ŽŽŽŒŒŒŒŒŒŒ‘’’’“““““’’’’‘‘‘‘’’‘’–šž ££¤¤¥¦§§§§©©©ªªªª©¨©¨§¨©©©©©©©¨©§¥¥¥¤¢¢¢£££¤¥¥¦¦§§§¨©©©©§¦¦¦¥¥¥¤¤££¤¥¥¥§¨¦¦¥¢Ÿžœœœœœœœ™—’Œ‘”–––•“‘ŽŠ–™˜˜—–––—–•–•““’’“‘ŽŽŒŒŒŒŒŽŽŽŽ‘‘’”•”””“”“’’’‘‘‘’““’‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚€€€‚ƒƒƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚€€‚‚ƒ‚ƒƒƒƒƒƒƒƒ‚€~~~€ƒ‚‚ƒƒƒƒƒƒƒƒƒƒƒƒ‚€€€€€‚ƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€~~~~}~}€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€~~~~€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€~~~}}}|~€€‚‚ƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€}|{{|~€€‚‚‚‚‚‚‚‚‚ƒ€€~~~~~|}}~~~€€‚‚‚‚‚‚‚‚‚ƒ‚‚‚‚‚‚€|zyy|}~€‚‚‚‚‚‚‚‚‚‚‚‚€~}}}}}~}~~€€€€€‚‚‚‚‚‚‚‚‚‚‚‚}|ywyy{}€‚‚‚‚‚‚‚‚‚‚‚‚€~}}{{{||}}}}~€€€€‚‚‚‚‚‚‚‚‚‚}zxwyz|}~€‚‚‚‚‚‚‚‚‚‚ƒ‚~}}|{zz{|||||}~~~~~€€€€€€€€€€€€‚ƒ‚‚}zxwyz|~€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚}||{zzz{||{|}}}}}}~~~~~€€€€€€€€€€‚‚‚‚‚‚‚€}zyyzz{~~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚}}||zzyy{|||||{{{{||}}~}~~~€€‚‚‚‚‚‚‚€}|zz{z{||~€‚€€€‚‚‚‚‚‚‚}}|{zxxxz{{{||||{||||}}}}}}}}~~~€‚‚‚‚‚ƒƒ€~|{z{z{}}~€‚€€€€€€‚‚‚‚‚‚‚‚~||{{yxxyzzzz{{{{{{{{||||||||}~~~€‚ƒƒƒƒ‚~|zzzz{}~€€€€€€€€‚‚‚‚‚‚€~|{{{zyyyzzzzz{{zz{{{zzz{||||~~~~€‚‚„„ƒƒ‚|zzzz{}~€‚€€€€€€€‚‚‚‚€}|||{yzzzzzzzzzzzzz|{zzz{|}}~€€~}}ƒƒ„…„ƒ‚~|{zzz|}~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€‚€~~|{zzzzzzzzzz{{zz{||{{{{||}€€€~}}~ƒƒ„„„„ƒ‚|z{{{||~‚‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~€€€€€~{zzzzzzzzzzzzzz{||{{{{|}}€€€~}}~‚‚„…„„ƒ‚|zz||{{}‚‚‚€€€€€€~~~€€€€€€}{zzzzzz{zzzz{{|||||||}~€€€€~~~€‚ƒƒ„„……„ƒ{yyzzy|~€‚ƒ‚€€€€€€~~~~€€€€€€~|zyzzy{{z{{{{{|||||||~‚€‚‚ƒƒ„„„„ƒ‚{xwxyx{~€‚‚‚€€€€€~~€€€€~|{yyzzz|{zz{{{{||||}}}‚€€~€‚‚ƒƒ„„„ƒƒ‚~zxxxyx{}~€€€€€€€€~€€€€}}|{zyzzz{|{{{|{|||}}~~~€‚~~€‚ƒƒƒƒƒ‚‚ƒ‚~zxwxyxz|}~€‚‚‚‚‚‚‚‚‚€€‚€€€€~||zzzyzz{{{{{{||{{}}}}}~~€~~~€‚‚‚€}{yxyxy{||~€‚‚‚‚‚‚‚‚‚~~~€‚‚‚‚€€€€€€~|{yzzzzz{{|}~}||zz{{|}|}}}~~~€€€€~|{zyxwx{||~€‚~~~~€‚€~~}{{zzzzz{|~~||zzz{||}}}}}~~~~~~€€€~~}|zxxxx{|~~‚~~€€~}||{zzzzz{}~||zyzz||}}|}}}~~~~}}€}}{zyyxxz{}~~‚€€€€€‚‚€~~|||{{zzzzzz|~}|zyzyyz{{z{|}}~~}}}|}€€€€~|{zzyxwxy|}}‚‚‚€‚‚‚€~}|}}|{zzyzzz|~~|zzzyxyzzzz{|}}~}|||}~~~~~~|zzzyxwwxz{||‚‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚‚€~}||}||{zz{{|}€€}zzzywxyyxyz{||}|||||}}}}~}|{zzyyyxwxxz{|‚‚‚‚‚‚‚‚‚‚ƒƒ‚€€~}||||||{z{|}~€€~{zzywxzyyyz{||||||||}}}||||{zzzyyxxxxyz{‚‚‚‚‚‚€~}}||||||||}~~€€~|||{z{{{{||||||||}||}}||||{{{{{zzyyxxyyz‚‚‚‚€}}|{||||||}~~€€€€€||||||||||||||||||{{||{{z{{|{{{{{zyyxyyz‚‚‚‚‚€~}|{z|}~~~~~€€€€€€€€}|||||{||||||||{{|{z{{{zz{|||||||{zzzzzz€€~||{{|~€€€€€€€~|}}}|}}}||||}}{{|{zzz{zz{{||||||{{z{z{z‚‚‚‚€€€€‚‚‚‚‚‚€€€‚€~}||||~~~~‚€€€€€€~}}}}}}}}}}}||{zzzyzzzzy{zz{|||||||{{{zz‚‚‚‚€€€€‚€€€€€~}}~~~~~~€€€€€€€~~}}}~}}}~}}{{zzzyyyzzzzzz{{{|}}}||||{{{€€€€€€€€~}~~~~}}}~€€€€‚‚‚‚€€€€€~~~}|||{yyyyzzzz{{|}}}~}}}||||€€€€€€~~~}}|{||}~}}}}~€‚‚‚‚‚‚‚€€€€€€€~|}|{zyxxyzz{{z{|}~~~}}||{{|€€€€€€~}~~|{{z{{|}}}~~€‚‚‚‚‚‚‚‚‚‚€~}€€€‚‚‚~~}}|{yxwwyzz{z||}~~~}}{zzz|€€€~~~~~~~||{{{||}}}ƒƒƒƒ‚‚‚ƒƒƒƒ€€~}~€€€€€€‚€~}~||{xwwwyzz{{||||}}}}{zzz{‚‚‚€€€~}}}}}~~}||{{|||||~ƒ„…„ƒ‚‚‚‚‚ƒ‚€€~}}}~€€€€‚~}}|{{xwwwyz{{{{{|||}}|{zzzz€€~}}~~~~~}||{{{{{{}~ƒ……„ƒ‚ƒƒƒƒƒ‚€€}}}}€€‚‚‚~}|{zxwwxyzzzzzz{|||{|{{{zz€€€€~~~~~~~}|||{zzz{|~€ƒ„…ƒ„ƒ‚‚ƒ„„„„„‚€€€€~~€€€ƒ‚}}}{zxxyyyzzz{|{zzzzzz{z{z{~€€€€€€€€~~~~}|||z{z{z|}‚ƒƒ‚ƒ‚‚‚ƒ……„„ƒ€€~~€€€€€€‚~}}{yyyyyyzz{{{|{z{z{{zz{zz~~€€€€€€€~~~}|||zzzz{{|‚‚‚‚ƒƒ„„„ƒ~~~~~~€€€€~}|zyyyyzzz{{{{{z|{z{{{{{z|}~€€€€€€€€~~~}}|zyz{|}}€‚‚ƒ„ƒ„„„„‚~}|}~~~~~€€~~~}{zzzzzzz||{{z{{{{z{{{{z{|}~~~€€€€~~~}|{{z{||}~€€‚‚ƒƒƒ‚‚‚€~}||}~~~~~€~€~~~}}|||{{{{{{{{{zz{{{z{{{zzz{}}}}~~}|{{z{|}~€€‚‚‚‚€~}}}}}}~€~~~~~}}}||{{{{{|{{{{z{{{{zzz{||}}~~~}|||||}€‚‚‚‚€€~|}}|}}~~~~~~~~~}}}}||||}||||{{{||{{zzzz||}~}~€€€€€~}~}}~‚‚‚ƒ‚‚‚€‚‚‚€~}|{{|}~~~~~~~~~~~~}}}}||{||{{|||||zzyz{{|}||~€€€€€€‚‚‚‚‚‚‚‚‚‚‚€}}{zy{}}~~~~}}|||||{||}}}zzzyz{||z{|€€€€€ƒƒƒ‚‚ƒƒ‚‚‚‚‚~}{zzz}}~~~~~~~~~}}|||||}~~~~zzzzzz|{{{|~€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€~}{{{{|}~}}~~~~~~~~}}}||||}~~~}zzzzzzzz{|}€€€‚ƒƒ‚ƒƒ‚‚‚‚ƒ„„ƒ‚ƒƒƒ‚‚‚‚~}}|||}~€€€~~~}~}|~~~~~~~~~}|||||{{||}}|zzzyzzzz||}€€€‚‚ƒ„„„ƒƒƒƒƒ„„ƒƒƒƒƒƒƒ‚‚‚}|}}}€€€~}}}|~}~~}}}|}}}}||{{{{{{||zzyyzzz{||~€€€ƒƒƒ‚ƒƒ‚‚ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€~~~~€€€€~~}||~~€~}}|}}}}}|{{{{{{{{zzzzzzz{|}~€€€€‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚‚‚‚‚‚‚‚‚€~€€€~~~~~~~€~}|{|}}}|{{{{{z{{{zyyzzzz{}~€€€€‚‚‚‚ƒ‚‚ƒ‚‚‚ƒƒ‚ƒƒƒ„ƒƒƒƒƒ€€~~€€~}|{{{{|{{zzzzzzzz{yzyyzzz|~€€€€€‚‚‚‚‚‚ƒ‚‚‚‚‚‚‚‚‚‚ƒƒƒ‚ƒƒƒ‚€€€~~~~~~~€€€~}}|{{{{{zz{{{{zzzz{{{{{zzz|€€€€€€‚‚‚ƒƒƒ‚‚‚‚‚‚‚‚‚‚‚ƒƒ‚ƒƒ‚‚€€€€€€€€€€~~~~~~~~~~}}|{{{{{{zz{{{zzzz{|||||{z|€€€€€€‚‚ƒƒƒ„ƒ‚‚ƒƒƒƒƒƒƒ„„ƒƒ‚‚‚‚ƒ‚€€€€~€€€€~}}}}}|{|}||{{{zzzzzz||}}|{{|}~}€‚ƒ„ƒƒ‚‚‚‚‚‚‚‚ƒƒ„„„„ƒ‚€€€€~}}~€€€€€€€€}}}}}|{}|||{{{zzzzz{|}}}}||||}}~~~~‚ƒ„ƒƒ‚‚‚‚‚‚‚‚ƒƒƒƒƒƒƒ‚‚‚‚€€~~~~€€€€€€€€€€€€}}}}}|{||}|{{|{||{{{}}~}}||||}}~~~~‚ƒƒƒƒƒ‚‚‚‚‚‚‚ƒƒ‚‚‚‚ƒƒƒƒ‚€~~~~€€€€€€€€€€€€€€€~}}}}||||}||||||||{|}~}||{|||}~~~‚‚‚‚ƒ‚€€‚‚‚‚‚‚‚‚€~€€€€€~}~~|{|{{z{{{{{{zzy{~~}|||||}~~~~€‚‚‚€€€‚‚€~€‚‚‚€€€}}}}}}}}}}}{{zzzzz{{zzzyyy{~~|}~}~~~~}~~€€‚€€€€€€€€€€~€€€€~}}}}}}|||}|{{{z{{{zzzzyzz{~~~~~~~~~~€€€~~~~~€€€€~~€‚‚€€€~}}}}}|{|}}|{z{z{{{{{{yyyzz€€€~~~~~€€~~~}||}}}}}}}~~~~~~~~~€€€€€€€~~}}}}}}||}||{zzz{{{{{{zz{zz~€€€~~~~~}~~~~}}}}}}||}}}}}}}}||}}}}}~~€€€€€~~~~~~}}}}}}}|}}|zzz{{{{{{zzzzzz€€€€€€€~~~}|}}}~~~}|}}|||||||||||||||||||}}~~}}}~~}|||}||||||{zz{z{zz{zzzz{zz~€€€€€€~~}}}}}}~~}}|||}}}}}}}||||||||}|||}~~~~~}||}~~}}}}}}}||||{{{{{{{{{{z{{{zz€€€€€€~}}~~}}||}}}}||||}}}}}}}}|||||}}}}}}}||}~~~}}}}}|||||}}|||||{||||{{{|~€€€€€€~}}~~}}}}~}||||{|}}}}}}}|||||{{{|{{||}}}~~}}}}}}|{{{{{{|||||{{{{{z{{{~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€~~}~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€~~~~~~~~~~~~~~~~~~~~~~~~~~~€€€€€€€€€€€}}}}}}}}}}}}}}}}}}}}~~~~~~~~€€€€€€€~}}}}}}}}}}}}}}}}~~~~~~~~€‚ƒ‚‚€€€}}}}}}}}}}}}}}}}}}}}}}~~~~~~}}~€€€€€~~~~~~~~~}}}}}}}~~~~~~~ƒƒ„ƒ‚‚‚€€~~~~~~~~~~~~}}}}}}~}}~~~~~~~~}~€€€€‚‚‚‚‚‚€€€~~~~~~~~~~~~~~€€~~~€‚‚ƒ„„„„ƒƒƒ‚€€€€€€€€€€€€~~}}}~}}~~~~~~€‚ƒƒ„„‚‚‚€€~€€€€€€€€€€€€‚‚‚„………„ƒ‚‚‚€€€€€€€€€€€€€€~~~}~}}~~~~€€ƒƒ„„„„‚‚‚‚‚‚‚‚€€€€€€€€€‚‚‚ƒ…††……ƒ‚€€€~~}}}}~~~~~€€€ƒ„…………ƒ‚ƒƒƒƒƒƒƒ€€€€‚€€‚‚‚ƒ‚‚‚ƒ…†††…ƒ‚€~~~~~~~€€€ƒ„„………„„ƒƒ„„„„„ƒ‚‚‚ƒƒ‚‚‚‚‚€‚ƒƒƒƒƒƒƒƒƒ„…†……„‚€€€‚ƒ‚€€~~‚‚ƒ„………………„„„„„„„„„„„„„„„„ƒ„ƒ‚‚€€‚‚ƒƒƒƒ„……†ƒƒ„…………ƒ‚€€~€€ƒ……††„ƒ‚€~~€‚ƒ„…………………„……„„……„„…„…„„……„„ƒ‚‚€€ƒƒƒƒ„………†ƒƒ„…………ƒ€€€€€€€€€€€€ƒ…‡‡‡††…„„ƒ~€‚‚‚ƒ…………………………………………………………„„„„‚‚€€‚ƒƒƒ„…………ƒƒ„…„……ƒ‚€€€€€€€€€€€„†ˆˆˆ‡††……„‚€€‚‚„……………„…„„………………„……………„„„„„‚‚‚‚‚ƒ„„ƒƒ…………ƒ„„…„…„ƒ€€„†ˆ‰‰ˆ†††……ƒ‚€€‚‚ƒ……„…………………„……………„……………………„ƒ‚ƒ„„„……„„„ƒ„„„„ƒƒ„………ƒƒ€„‡‰‰ˆˆ†…………„„‚‚‚‚„„„…………………………………„„………ƒƒƒ‚‚‚‚ƒ…„„……„„„„„„„„ƒ‚„………„ƒ€~…ˆˆˆˆˆ‡………††…ƒ‚€‚ƒ„………„„„„…………………„………„ƒ‚‚‚‚‚‚‚„…………„„„„„„„„ƒƒ„…………„~€‚‡‰ˆˆˆˆ‡†……††…ƒ‚‚€€‚ƒ„…„„„„……„„„…………„………„ƒƒ‚‚‚‚‚ƒ„………„„„„„„„„„„…†††……„‚€~€€€€€€€€€€‚†‰‰‰‰ˆ‡†……………„‚‚‚‚‚„……„„……………………………„„„„„ƒ‚‚‚‚‚‚„„„„„„„„„„„„„„„……‡†……„ƒ€€€€€€€€€€…ˆ‰‰‰‰ˆ‡†…………„ƒ‚‚‚ƒ„„…………………………………………„„„„ƒ‚‚‚‚‚‚„„„„…„„„„„„„„„„…†‡†……„ƒƒ€€€…ˆ‰‰‰‰ˆ‡†…………„„ƒ‚‚ƒ„„……………„……………………„……„„……ƒ‚‚‚‚‚ƒ…„„„…„„„„…„„„„……††††…„„„ƒ€€‚…ˆ‰‰‰‰ˆ‡…„…„……„ƒ‚‚ƒ„……………„„…………………„„………„……ƒ‚‚‚‚‚ƒ„„„„„„„„……………„………†………„„„ƒ‚€…‰‰‰ˆˆ‡†„ƒƒƒ……„ƒ‚‚‚„………………„……„„„„…„„„……„……ƒƒ‚ƒƒƒƒ„ƒƒƒƒƒƒ„………………„„„†…………„„ƒ‚€€…ŠŠ‰ˆˆˆ†ƒƒƒ‚„„ƒƒ‚ƒƒƒƒ„„……„……„ƒ‚‚„„„……………„„ƒƒƒ„„„„„„„ƒ„„……………†……„„……††……ƒƒ‚‚€€€€€€€€…‰‰‰ˆˆ‡…‚‚‚‚ƒƒƒ‚€‚‚ƒ„ƒ„…………„„ƒ„„………………„„„„…„„………„„„„………………„„„„……†‡†…ƒ‚‚‚€€€€€€~~€ƒ†ˆ‡††……ƒƒƒ‚€€‚ƒƒ„„„„…………„‚€‚„„………†‡†††…………………………„„………………„„…„††…††…‚‚ƒ€~}~ƒ†…„„…„ƒ‚‚€€€€€€ƒ„………………………ƒ€ƒ„„……†‡†‡†††……………†…„„„…„„…„„„„„…††…†††„‚ƒ„~}}~„ƒ‚ƒƒ‚‚€€€€‚‚ƒ„……††………„„‚€€‚„………†‡‡†‡‡‡……………†…„„„„„„„„„„„„…††………††„ƒ„~~~~ƒƒ‚‚ƒ‚€ƒ„ƒ……†††……„ƒƒ‚€€€€ƒ…………‡‡††††…………†………………………„„„………††………†‡…„…~~~}~~€€ƒ„‚‚‚ƒƒ„…………†……„‚€€€ƒ…………………………………††………†††††…ƒ„ƒ………………††‡ˆ‡††~~~~~~€€€€€€ƒ„‚‚‚‚‚ƒ„…………………„‚€€€€ƒƒ„„„„„„„„„„„…††………†††††………ƒ………………†††‡††…~~~~~€€€€€€ƒ„ƒƒ‚‚‚‚ƒƒ„……„……„„ƒ€€€‚ƒ„„„……„„„„„„……††……†‡‡‡†………ƒ„„…„……………………†‚„„„ƒƒ‚‚ƒƒƒƒ„†……„„ƒƒ‚€€€ƒƒƒ„„„„„„„„………†………†††††………„„„„„…………†…†††~€ƒ„„„ƒ‚‚‚ƒ„„„„„…„„…„ƒ‚‚€€€‚‚‚ƒƒƒƒƒƒ„„……………„†‡…„…ƒ…………ƒ‚ƒƒƒƒ„………………~€€€€‚‚‚‚ƒƒ„ƒ‚‚‚ƒ„…………„…„„…„‚‚€€€€‚‚ƒƒƒƒƒƒƒƒ„…††………‡‡†……ƒ„………ƒ‚ƒƒƒƒ„„„…………~€€€‚ƒƒƒ‚‚‚ƒƒ‚ƒƒƒ„………ƒƒƒ‚‚‚‚€€€‚„„„…„„„„„„…††††…†‡†„…„ƒ………„ƒƒ‚‚ƒƒƒƒƒ„……€€€€~~€€ƒƒƒƒ‚‚ƒƒ„ƒ„………„‚‚€€€€€~€€€ƒ„…………„„„„„…††………†‡†…†„„…†………ƒƒ‚‚‚ƒ„…………€€€€~‚„„„ƒƒ‚‚ƒƒƒ„ƒ„„„„……„‚‚€~~~€€€ƒ……………„„‚‚ƒ„…†………††‡‡‡†……†…„„ƒƒ‚‚‚……†……~‚ƒ……„‚ƒ„………„……„„ƒƒƒ‚‚€€€€€~~~€€€€‚ƒ………………„ƒ‚‚ƒ„……†††‡†‡‡†……††……„„ƒƒƒƒ…†‡††€~‚ƒ„„„€‚‚ƒ„„„„„……„ƒ‚‚‚‚‚‚€~€€€€€‚‚ƒ„………„„ƒ„………††‡‡‡‡†…………………„ƒ‚‚ƒ†‡††‡€~‚ƒƒ‚€ƒƒƒ„ƒ„……†…„‚€€‚‚ƒ„„ƒ€€€‚‚ƒƒ„……„ƒ€ƒ„………†‡‡‡‡†…„„„„„„…ƒƒƒ„……†……€€€€€€ƒƒƒƒ„„…††…„‚€‚ƒƒƒƒƒ€€€‚‚‚ƒƒ……„‚€~€ƒ„…††††‡‡††…„„„……„„„„…………………‚€€€€€€€€€€‚‚‚„„……†††…‚€€‚‚‚‚ƒ‚€€€€€‚‚‚‚‚‚ƒ„……„‚€€€ƒ„„……†††‡‡†„„…………„……„……„…………„‚€€€€€€€€€€ƒ‚ƒ„……††††…ƒ‚‚‚‚‚‚‚€€€ƒƒƒƒ‚‚‚ƒƒƒ„„„„‚€ƒ„„„…††††††…„…††…„………„…„„„………„‚€€€€€€€€ƒ‚ƒ„…†††…†…„ƒ‚‚€€€€‚„………„ƒƒƒƒƒ……„„„ƒ‚‚ƒ‚‚ƒƒ„…††…………„…†……„……„„ƒƒ„„„………ƒ‚‚‚‚‚€€€€€‚‚ƒ‚……††………„„ƒƒ€‚‚ƒ‚ƒ„……„„ƒƒƒƒƒ„„„„ƒƒ‚‚‚‚‚ƒ‚ƒƒ…„„„ƒ„……………„„„ƒƒƒƒƒƒ„†††…ƒƒƒƒƒ‚‚‚„„……†…„ƒ‚‚‚‚‚‚ƒ„………„„„„„ƒƒ„ƒ‚‚‚‚ƒ‚‚‚‚ƒ…………„„„…ƒƒƒƒƒƒ„†…………„„ƒƒƒƒ‚ƒ„………‚€€€€€€€‚ƒ„…†††…………ƒƒƒ‚€€€€€€€€€‚‚‚‚‚„„„„„……„ƒƒƒƒƒƒ„…†…†……„„ƒƒ‚€€€€€‚‚ƒ„„ƒ€~~~~~~}}~~€‚„†‡‰ŠŠ‰‰ˆ†„‚‚‚‚€€€€€€€‚ƒƒƒ‚‚‚‚ƒ„…‡„ƒ‚‚ƒƒƒƒ„††††………ƒƒ‚€€€€€€€‚ƒ‚~~~~~~}|||}€‚„‰Š‹ŒŒ‹ŠŠ‡„‚‚‚‚‚‚‚‚‚‚€€€€€ƒ„…†„ƒ‚‚ƒƒƒƒƒ††††…††……„ƒ€€€€€€€‚~~~~~~~}|||}€‚…‰‹ŒŒ‹Š‡„‚ƒ‚‚‚‚ƒƒƒ‚‚‚‚€€€€€€ƒƒ……„ƒƒƒƒƒƒƒƒ†††††……………ƒ‚€€€€~}}}~~~~~~~~}|||~€‚…‰‹ŒŒ‹‰†„‚‚‚‚‚‚ƒƒ‚€€‚‚ƒ„ƒƒƒƒƒƒƒƒƒ†††…†††……„ƒ‚€€€~|{|}}}}~~~~~~~~~~~~€‚ƒ‡Š‹Œ‹ˆ†„‚€€€€‚‚‚‚ƒ‚‚‚‚ƒ„ƒƒƒƒƒƒƒƒƒ†††††††……„ƒ€€€~}||}}}}~~~~~~~~~~€‚…ˆ‹‹ˆ…ƒ‚€€€€‚ƒ‚ƒƒ€€€€€€€‚ƒƒ‚ƒƒƒƒƒƒ„„„„„†…†††††…„„ƒ€€~}}~}}}}~~€~~~~~€ƒ…‰‹Š†ƒ€€€€€‚‚‚‚€€€€€‚‚‚‚‚ƒƒƒ„„„ƒ„„†…††…††…„„‚€€}}}}}}}~~~~~~~~~~ƒ…ˆ‡…‚€€€€€€€€‚‚€€€€€‚‚‚‚ƒ‚ƒ„„…„ƒƒƒ„ƒ††††……………ƒ‚€€}}}}}}}~~~~~~~~~~~~‚„†ˆ‡†ƒ€€€€€€€€€€€€€€€‚ƒƒƒƒ„„………„„„„ƒƒƒ…†††…………ƒ‚€€€€~~}}}}}}~~~~~~~~~~~~€ƒ†ˆ‰ˆ†ƒ€€€€€€€€€€€‚ƒƒƒƒƒƒƒ…………………„„„……………………‚€€€~}}}}}~~~~~~~~~~}}~€„‡‰ˆˆ‡…ƒ„ƒ‚‚‚‚‚‚€€€€€€€€€€‚ƒƒƒƒƒƒƒ„………………„„…„„„……†……‚€€€€}}}}}~~~}}}}}||~~€‚…††‡†………„ƒƒƒ‚‚‚€€€€€€€€€€€€‚ƒƒƒƒƒƒƒƒ„………………„…ƒ‚ƒ…………‚€~€~}}}}}}~~|||||||~~€€‚ƒ„ƒ„………„ƒ‚€€€€€€€€€€€€‚‚ƒƒƒƒƒƒƒƒƒ„…………………ƒ‚‚„„…„‚‚€~€~}}}~~~~||||}}}~~~€‚ƒ‚ƒƒ„„ƒ‚€€€€€€€€€€€€€‚ƒƒƒ‚ƒƒƒƒ„ƒ„…………………ƒ‚‚ƒ„„„ƒ€~~}}~~~~~}}}}}~}~~}}}}}€€€€€€€€€€€€€€€€‚ƒƒƒƒƒƒƒƒ„„„„……………†ƒ‚‚‚‚ƒ„„ƒƒ‚€€€~~~~~~~~~~~~~~€€€€€€€€€€€€€€€‚‚‚ƒƒƒƒ„„„„ƒ„„…………………ƒ‚ƒ„„ƒƒ‚€€€€~~~€€€€~~€€€~€€€€€€€€€‚‚ƒƒƒƒ„………„ƒ………………………ƒƒƒƒƒƒ‚‚‚‚‚€€€€€€€€€€€€~~€€€€€‚ƒƒƒƒƒ„…………………„………………‚‚ƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€€€€€€€€€~~€€€€€€~~€€‚‚ƒƒƒ„„……………………………„„„‚‚‚‚‚‚‚‚€€€€€‚€€€€€€€€€€€€€€€€‚‚ƒƒƒƒ„………………………„„………€€€€€‚€€‚€€€€€€€€€€€€€€€€€€‚ƒ‚ƒƒƒ„„„………†…………………………€‚ƒ€€€€€€€€€€€€€€€€€€€€€€€€‚‚‚‚‚ƒƒƒ„„„„………………„……………„€€€€€€€€€€€€€€€‚ƒ‚‚‚‚ƒƒƒ„ƒƒ„…………„„„„………„„‚€€€€€€€€€€€€€€€€‚‚€€€€€€€€€ƒ‚‚‚‚‚‚‚ƒ‚‚‚ƒ„ƒƒƒƒƒ„„„……„ƒƒƒ„„„„ƒ‚€€€€€€€€€€€€€€€€€€€€‚ƒ‚€€€€€€€€€€€‚ƒ‚‚‚‚‚ƒƒƒ‚‚‚ƒƒƒƒƒƒƒƒƒƒƒ„ƒƒƒƒƒƒƒƒƒžžžžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžŸŸŸŸŸžžžœœœœœœœšššššššššššš™™™š›œœžžžžžžžžžžŸŸžŸŸŸŸŸžŸžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžžžžžžœœœœ›™™™˜——————–•••–––––––————˜™šššœžžžžžžžžžžžžžžžžžžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžžžžžžœœ››››™™™™™™™™™™™™™™˜˜™š››œœžžžžžžžžžžŸŸŸŸžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžžžžžœœœ›š™———–––––•”“““““““““”–————™ššš›žžžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžœœ››š™™™™™™™™™™™™™™˜˜˜™šš››œœœœœžžžžžžžžŸŸŸŸžžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžœœ›œ›™˜–•”•””””“’’’‘‘‘’’’’“–———˜˜™™šš›ŸŸžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžœœœœœœœœœœœ››š™™˜™˜˜˜˜˜˜˜˜——————˜˜™™ššššœœœžžžžžžžžŸŸŸŸžžžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸž›š™™™™™š˜—”’‘‘‘‘‘Ž‘’’’“•—˜—˜™™š™šŸŸžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžœ››››››››œ›š™™˜˜˜˜————––—––––—————˜™››œœœœœžžžžžžžžŸŸŸŸžžžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžžž›™˜™˜————–””’‰‡‰‹ŒŒŒŒŽ’“•—˜˜™™š™›ŸŸŸžžžžžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžžžŸžžœ››šššš™šš™™™˜˜˜———––••••••••–––––—˜™šœœœœžžžžŸŸŸŸŸŸŸžžžžžŸŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžœ›™™š—–––––“‰ƒ}z|ƒ‰Š‹Š‹‹‹‹ŒŒŽ‘’“•—˜™™™™›ŸŸŸŸŸŸŸžŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ¡ žžžžžžžžœ›››šš™™™™˜˜˜————––••”““”““““”•••–——˜™šš›œœœœœžžžŸŸŸŸŸŸŸžžžžžŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžž›ššš™˜—––‘ˆ~ukgju€…‰‹Š‰‰ŠŠŠ‹‹‹Œ‘“•—˜˜™™šŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ¡ žžžžžžžžœ›››šš™™™˜˜˜˜˜———–•“”“““““““““”“”•–—˜˜™™š››œœœžžžžžŸŸŸŸŸŸŸŸžžžžŸŸŸŸŸŸŸŸŸŸŸžžžžžžžžœ›™™™™˜—•‚mZPORYiz„‡Š‰ˆ†‡ˆ‡‡‡‡ˆ‹‘’“”—˜™š›ŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ¡¡žžžžžžžžœ›››šš™™˜˜˜˜——––””“’’’‘‘‘‘‘‘‘’’’’“”•–——˜˜™ššœœžžžžžžžŸŸžžžžžžžžŸŸŸŸŸŸŸŸŸŸŸžžžžž››šš˜˜™™˜”ƒlVGCBFKUlƒˆ‡‡†„‚‚„„„„„†‰ŒŽ’“”—˜š›ŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ¡¡žŸžžžžžžœœœ›š™˜˜˜˜˜—–––”““‘‘‘‘‘‘’“”•–——˜˜˜š›œžžžžžžžžžžžžžžŸŸŸŸŸŸŸŸŸŸŸŸžžžžœš™˜˜˜———”ŠrS@<>AEHKVqˆˆ…ƒ€€‚ƒ„†ˆŠŒ‘’”–˜š›ŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸžŸŸŸŸŸ¡¡ Ÿžžžžžžœ›››š™˜˜˜˜˜–”•””“’‘‘ŽŒŒŒŒŒŒ‘’’”•–———™š›œžžžžžžœœžžžžžžžžžžžžžžžœ›š™˜———˜y]E<=ELRX^cm{††„‚€€~€‚ƒ„†‰‹ŒŽ’”–—™›ŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ ¡¡Ÿžžžžžžœ›››™˜˜˜˜˜˜–”“’’’‘‘ŽŽŽŠˆˆˆ‰‰‰Š‹‹ŒŽ‘’’”•———˜™š›››››œœœœœœœœœœœžžžžœœœ›šš™˜—•”“‰kI;<AJYemrty€…‡†„€~€ƒ…ˆŠŒ’“•—™šŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ¡¡¡ŸŸžžžžžœœ›š™˜˜˜˜——–”’’’‘ŒŒŒŒŒŠˆ†…††††‡ˆˆ‰‹ŒŽ’“•–———˜™™˜˜˜™š™ššššššš››››››šššš™™™˜˜˜—–•”‘Ž^E;>HR\gpuwxy€‡ˆ†ƒ~~~~~~€€…‡‰Œ’“–—™›   ŸŸŸŸŸžŸ      ŸŸŸŸŸŸžžžŸ ¡¡ ŸŸŸŸŸ¡¢ žžžžŸžžœ›š™™˜˜˜˜––•“’’‘Š‰ˆ‰ˆ‡…„„„…„„……††ˆŠ‹ŒŽ‘’”•––––—–———˜˜˜˜˜˜˜˜™™˜˜˜™˜˜˜˜———–––•“’‘‹sQ?;<HW^dkpsuux€†ˆ…‚~}|}}|}€€„‡‰Œ‘’”–˜šœ¡¢¡ ŸŸŸžžŸ¡¡¡¡¡ ŸŸŸŸŸŸžžžŸ¡¢¢¡ŸŸŸŸ¡¢¢ Ÿžžžžžžœœš™˜˜˜˜—–•”’’‘ŽŠ‡††††…ƒ‚‚‚‚‚‚‚‚ƒ„…†ˆ‰‹ŒŽ‘’“”“““””””””•–––––————————–––•””“““’‘Œ†mG9<=CS^beknoprv}„‡…€|zzzzzz{~~ƒ†‰Œ‘’•—˜šœ¡¡¡ŸŸŸŸžžž¡¡¡¡¡ŸŸŸŸŸŸŸžžžŸ¡¢¢¡ŸŸŸŸ ¢¢¢ Ÿžžžžžœœš˜˜˜˜˜—–”’’‘‹‰†…„„„‚€€€€€‚„…†ˆ‰‹Ž‘’‘‘‘’’’’’’‘’’’’’’”””•••””“““’’’’‘ŠcC9<@FR]acgnpopsy}‚…„€{xxyxxwy|~}‚†‰Œ‘’•˜™šœ¡¡¡ŸžŸŸžžŸ¡¡¡¡¡ ŸŸŸŸŸŸžžžŸ¡¢¢¡žŸžžžŸŸ Ÿžžžžžžœœš˜˜˜˜˜—•“’‘ŽŒŠˆ†„ƒ‚‚€~}}||||}||~€‚„†ˆ‰‹‹ŽŽŽ‘’’’’’’’’‘‘‘‘‰b@5:BMS[chlotvuuvy}ƒ†…€zxxxwvwx{~~€ƒ‡‰ŒŽ‘“–˜˜›œ¡¡¡    ŸžŸŸŸŸŸŸŸŸŸžŸ  ŸŸŸŸ¡¡¡ Ÿžžž–’›žžžžžœ›š˜˜˜˜––•’‘‘Ž‹‰†„„‚€~||{zzyyzzz{}€‚„†‡ˆ‰Š‹ŒŒŒŒŽŽŽŽŽŒ‰wY>6;BNZ]binruvxvuuw{ƒŠˆ‚zxxxwwwx{~€…‡ŠŒ‘“–—™›œ¡¡¡¡¡¡¡ŸžŸŸŸŸŸŸŸŸŸž ¡¡ Ÿ  ŸŸŸžœš–|owˆ™žžžž››š˜˜˜—–”“’Œ‹‡…ƒ€~~|{zyxxwwwwxyz|~€€‚‚…†‡ˆ‰Š‹‹‹‹ŒŒ‹‹ŒŽŽŽŽŽŽŽŽŽŒ‰ˆ‡„rN74:@JW`ejnqttuuutssy‚‹‹„xuvuuwwxz}€ƒ†‰‹“”–—š›œ¡¡¡¡¡¡¡ŸŸŸŸŸŸŸŸŸŸŸž ¡¡ ŸŸžœ›™—’““Ž‰|dZh€–Ÿžœ››š˜˜˜—•”“’Ž‹ˆ†…‚~||{yxvutsttstuvwz|}~€‚‚„…†‡††ˆˆ‰‰‰Š‰ŠŠŠ‰ŠŠŠŠŠŠŠŠŠŠ‰‰ˆ„„oO517AGS_dhloqsttttrppw€‰‹„wqsssuvxz}€ƒ†‰‹Ž‘”•——šœ¡¡¡¡¡¡¡ŸŸŸŸŸŸŸŸŸŸŸŸ ¢¡ŸŸ›˜•’Ž‹ŽŽ‹ˆ€p^W^z•žžœœš˜˜˜—•”“’Œ‰‡†„~|{zzwttsrsrrrsstuwyz{{}~€ƒ„„„……††‡ˆ‰ˆˆˆ‡ˆ‡‡ˆˆˆˆˆ‡ˆ‡…ƒxkN526=IT_ecgkmoqrsrqqpot}…‰„wqsrrtuwz}€ƒ†‰‹Ž’”–—˜›œ¡¡¡¡¡¡¡ ŸŸŸŸŸŸŸŸŸŸŸ ¢¢Ÿš—“‘ŒŠ‰Š‰†‚{mYOSn›ž›™˜˜˜—•”“’ŽŒ‹ˆ†…ƒ~zyyyvsrrrrqpqqrrsuwxxyz{|}€‚ƒ„†††…†††………………………„‚€s[B2/6>ITX\_afjlnprqqqrqqs|…‡‚vqrrsuvwz}€ƒ†‰‹“•—˜™›œ¡¡¡¡¡¡¡ŸŸŸŸŸŸŸŸŸŸŸŸ ¢¡žœ—’ŒŠ‰ˆˆˆ…‚}teSILd†™žš™˜˜˜—•””’ŽŒŠ‡†„~}zyyxtqpppponoppqrsuuuuwxyz|~~~~€€‚‚‚‚‚‚‚‚|sY@103;DOVWZ`fkoppqsttuuuuxŠ‹…{uttuuvwz|€ƒ†‰Œ“•—˜™›œ¡¡¡¡¡¡¡ŸŸŸŸŸŸŸŸŸŸŸŸ ¢¡ž›—’‹Š‰‰ˆˆ‡…‚|ym\NFIb€•š™™˜˜˜—•”“’ŒŠ‡†ƒ€|zyxwspnmmmmlmnnnoqrsrstuvwxz{{{||}}~~~~{pU;0059>FOTV\fmqtvuvwwxwvvvw~‰„}yxwwxxz}€ƒ†‰Œ“–˜˜™šœž¡¡¡¡¡¡¡ŸŸŸŸŸŸŸŸŸŸŸŸŸ¡¡ž›—’ŽŠŠ‰ŠŠ‰†„{ysiYMIKTh†•—˜˜˜—”““‘ŽŒŠˆ‡„}{yyxurnkkkkkllllmmnopppqrrstuwxwxyzyz{{{{{{{{{{{{|||ufU@303:AEJSXZ_ehkmoqsssrrrrrsz†Œˆ€zxxxxxx{~„‡ŠŒ“–˜˜™šœžŸŸŸ ¡¡¡ŸŸŸŸŸŸŸŸŸŸŸŸ ¢¡žœ—‘Œ‰‰ŠŠ‹‹ˆ…|ztmdWOIDJa€‘–˜—–’‘‘Ž‹Š‰ˆ†‚€}zyxxwuspmkkjjjklllmmmnnnooopprsttuvvwwwwwwwwwwxwwuuvraA3356>EINSY\]`cdfhjmnoopopqqry…Š‚wrsuuwxy}‚…ˆŒ“–—˜™š›ŸŸŸŸ¡¡¡ ŸŸŸŸŸŸŸŸŸŸŸ ¡¡ž›•ˆ†‡‰Š‹‹‰†ƒ~zung\UNF@CZw…Œ‘ŠŽŒ‰‡‡‡…~zwvvvwvtqnljiiiklllmnnoonnnnnnnppqrsttsstsrrrrstsrokbUA0158:BIOTX]]]`bbcfhkmmmmnorrrw„‰uopqrtwy}€ƒ…‰Œ“•—˜˜™›ŸŸŸŸ¡¡¡ŸŸŸŸŸŸŸŸŸŸŸŸ ¡¡žš’Š…„…ˆŠ‹ŒŠ…ƒ~{upjaZTJC?AM^n{}y~ˆ‰‡††„‚|yussvxxvrnlkgfgjlkklmmnnmmmmmmmmlnqqqrqpppnmmnooojcYI:1158?CINSV\^^^_acfhjlnnnooqtusw„Šupoprsvy}€ƒ…‰Œ“•—˜˜™›žžžŸ¡¡¡ žžžžžžžžžžŸ ¡¡žš‡ƒ„ˆŠŒŠ‡„‚~zvqlc^WOHC=;?KX\[_wƒƒ€|xurnnrvxwsomljhikllmmnnonmnnnnnnlkmpqqpmjhghfggecb\M@2-06;@HLPTW[^`aacfjmorrqqqrtxzzw{…Š‚wsrrsuwy}€ƒ†ŠŽ“–—˜™šœžŸŸžŸ¡¡¡ŸžŸŸŸŸŸŸŸŸžŸŸ¡¢ž™†‚†ˆ‰‰†ƒ~{wsnf`[TNJD>:8=DEG`uuppssrolhdchpvwtomllnqstvxxxxxxwwwwwwwsnoqrsqkb][ZYXWURRI92-,0:BHLRVXZ^acegiloruvvttuvy}}y}…‰ƒxtsstvwy}€„†ŠŽ“•—˜™›žŸŸžŸ¡¡¡ŸžŸŸŸŸŸŸŸŸžŸŸ¡¢ž™†€‚…†††…„{wrpid_WSOJC<98::;CUYTVY[[\]]XX_iswvrqrty‚ƒ…††††††††††…†ƒ{tuuvxvn`XTPPROLIE<2/,-7DKNQVZ]`cdfikmortvwwvuvw{z~…‡wrstuvxy}€…‡‹Ž‘“•˜˜™›žžžžŸ¡¡¡ŸžžžžŸŸŸŸžŸŸ ¡¡ž™ƒ~ƒ†‡†‡‡…‚€|xtqmhb\XUOHA;87778=BDFHKLMOPMLRarxy{|~‚…†‡‡Š‹‹‹‹‹ŒŒŒŒŒŒ‹ƒ{z{{|ztg_ZMIMLEA=81./4COQUY\_bdeghjlnqstvutqqrty}~z††|rnortvwy}€…‡‹Ž‘“–˜™šœž  Ÿ ¡¡¡ Ÿ   Ÿ   ŸŸŸ   ž™‚|€„††‡‡‡†ƒ}yusokea^ZUME>;865468:=?DGGGGFGL_u{‚„„††‡†‡‰Š‹ŒŒŒ‹‹‹ŒŒˆ€€}sohYNJHB=;9426?JRW]bccbceggjmprsssqomlnrw{}}{†ƒwoklpruvy}€„‡ŠŒŽ“•˜™™œŸ¡¡¡¡¡¡¡¡¡¡¡¡  ¡      Ÿžš‚}€…‡ˆ‡†‡‡…}ywtqlieb^ZSMD?<8522368:?DEDDDHP_s}‚…†††‡‡‡‡‡ˆŠ‹Œ‹‰ˆ‰‹‘Ž‹†‚‚ƒ‚€|zvfXMJD:;878@HOV\beeb_chkjkorrqqonnllnrx|~~|€†tmlloqtuy|€‚†‰ŒŽ‘’•—™™œžŸ¡¡¡¡¡¡¡¡¡¡¡ Ÿ¡¢¡     Ÿžšƒ…‡ˆ‡‡‡‡†ƒ{ywrnkfc`\YTLE@;73002349>AGLOU`kx€†ˆˆˆ‡‡‡‡‡††‡ŠŒŠ‡†ˆ‹“’ŽŠ†„„ƒƒ‚~zqcXSL>=:=BIPT\eihfb`enpmlnonmljjlklosy}~~}‰olmmnptux{…ˆŠ’•—™™œžŸ¡¡¡¡¡¡¡¡¡¡¡ Ÿ¡¢¡     ŸŸš„€‚„‡‡†††††„}zvqmjgd_[YWRJC>840../159ES\djqx~„ŠŒŒŠ‰‰ˆ‡‡…„…‡Šˆ…„†Š’’Š‡†……„„~{rkdWHFGGJQUVbpqjfefmqnkhhffhjjjklmosz}~}|Š}nnonmpstvz}„‡ŠŒ’”—˜šœžŸ¡¡¡¡¡¡¡¡¡¡¡  ¡¢¡     ŸŸš’ˆ‚‚„††††‡††…}yuqmkhd^ZWVUPIC;62/../6BVdnwz}…‰‘ŽŒŒ‹Š‡„ƒ‚…‡‡ƒ„‰Ž‘‘‘‹‹‰ˆˆˆˆ‡…‚~yp`VTUSUXX]lwtnjgiqrjfeddehllmmnoqu{~}{{„Š{rrponpssvy|ƒ†‰Œ‘”—˜šœž¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡      žš”Šƒƒ†‡†††…†……|yvromhd^ZXXVTQKA:62313?Ugq{‚……†‹’’’‘‹ˆ…„ƒƒ„„ƒ‡‹ŽŽŽŒ‹ŠŠ‹‹‹Šˆ…€wkc_^^acchvyurmlqwqfadhkmoppppoosuy{zwy†Š|ttpmnqssux{~†ˆ‹Ž‘”—™›œž¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡    ¡ ž™•Œ††‰ŒŽˆƒ€…††‚~|yxtqmjfda]ZWVSJA=;<;@Qcq{ƒ‰‹‹‹Ž‘’’‘Œ‹‹‰ˆ†ƒ}~„ˆ‹ŒŒŒŽŽŠ†~smmmliklr}ytsopxwmhfjpuwvtqpnllptvvusyˆ‰~wtpmmqrrtwz}…‡Š“—™›ž¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡      Ÿš•ŽŠŠŒŒ†€}ƒ††ƒ€~{zwsqpookc]ZXVSKHIKMUbnx‚‰ŒŒŽŽ‹‹‹‹ŽŒˆ‚}}€€ƒ…†‡ˆ‰‹Œ‹ŒŽŽŠ‚xwyzztrty~wtsotyurllsz~}uljhfimortutt}‹‰wsommprrsvz}ƒ…‰ŒŽ’•˜™œžž¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡   Ÿžœ–Šˆ‰ˆ‚~~‚††ƒ€~{ywtrqopojc^[YZTOR\gkov~†ŠŒŒŒ‹Šˆˆ‰‹‘’’‘Œ‡„‚€€€‚„‡‰ŠŠ‹Ž‘ŽŒ„}ƒƒ~€€{ups|ytqnu€„ƒykeedemstvwxy}„Š‰‚xqnmmoqqsuy|€ƒ…‡Š”—™›ž¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ŸŸŸŸž™ˆ…„„€}}€‚‚}{yvsqonnnmid_[`aXXgsx}„‡‰‹ŒŒŒ‹Š‰‡†…†‰‹’–—•’Œ‹Š‡„€}}~€ƒ‡‰‰‰‹Œ’‘‡…‰‡…†‡‡†‚}ts}€xrqw…ŒŠ|niihksy|~€‚‚ƒˆŒ‰‚vnloqqqprux{~„…ˆ‹Ž‘•˜š››¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ŸŸŸŸž™‘‡ƒ‚‚€~|{~€~|zyxuqmjijkkje]_ggdmx€…‡ˆ‰‰‹‹ŒŒ‹‰‡†ƒ‚„†ˆ‰Œ–™™–““””•„~||~€‚„†‡ˆ‹’’ŽŠ‡Š‹ŠˆŠŒ‹‰‚zv€…€xuz‡“topsv}…‡ˆ†‡…„ŽŠ~sorvwurpquyz|ƒ…†‰Œ“—˜™™¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ŸŸŸŸžš‘‡ƒ‚}zz|}~}}|}}}{wtokjlnomechqw|†‰‰‰ŠŠŠŠŠˆ†…ƒ‚€€‚„†‰•™™—•–™››–Šƒ€}|~ƒ†‰‘’‘Œ‹ŒŒŒŒŒ‰~„‰†{{‡•‘‚vwzzz~„‹†{}…Œ„yux}~xtrprtyz{~‚…‡ŠŽ’•˜™š¢¢¢¡¡¡¡¢¢¢¡¡¡¡¡ ŸŸŸŸŸŸžžš’ˆ‚€‚|z{||||}€‚„„ƒ|vomoqrqqqy‚‡‰Š‹‹‹ŠŠ‰‡†ƒƒ‚~}~‚…‰—˜–—™œžš’‹…€~|}|~€…ˆŽŒŠ„†Š‰†ƒ‹‹€ywz||ƒŠŽŒ‡€||~‰Œ‰ƒ€ƒˆ‡wsqpqswzz|€‚…ˆŒ“–˜š¢¢¢¡¡¡¡¢¢¢¡¡¡¡¡ŸŸŸŸŸŸŸžž›•‹ƒ‚ƒ}z{|||~€„†ŠŒŽ‹…}xvxxz|}‚‡Š‹‹‹‹‹‹‰†ƒ€~{yy{~~€„Š”˜˜›ž¡¡žž™‘‰…‚}{zz|~€„ˆ‹ŒŒŒ‹ŒŒŒŠˆˆ‰‡……Œ‘‡{xyz}€…ŠŽ‹‚{}€„ˆŒŒŒŽ‘“Œxuqooqsvy{|~€ƒ†Š’”–˜¢¢¢¡¡¡¡¢¢¢¡¡¡¡¡ŸŸŸŸŸŸŸžžœ—„€ƒ„‚~{{{||‚…†‰‘’’’‹†„ƒ…‰ŒŒ‹‹‹Š‰‡…‚}||}{xvvxyzz|~…’˜› ¤¦¤¡Ÿ›”Œˆ„{zxy{~…‰‹‹Œ‹ŠŒŒŒŒŠ‰‡‡†ˆŠŽ‰}yz|}‚Š‘“‘†}wy†…†ŠŒ’–•‹„zqnnoqrvy{|„ˆ‹’•—¢¢¢¡¡¡¡¢¢¢¡¡¡¡¡ŸŸŸŸŸŸŸžœ˜‡‚„‚€}||}~€‚ƒ†‹’•–•’ŽŠˆ‰ŠŒŽŽŒ‹Š‰‡…‚}{yyyxtrqqttuyyšŸ¢¥§¦£ ˜Š„{yxyz}€ƒ‡‰ˆˆˆ‰Š‹ŒŒŒ‹‹Š‰‰‰‹Š…~~€„Š‘Œvt|†‰Š‹‹‹ŒŽ’–•ˆsmmlnnprvyz~€ƒ†‰Œ’•¡¡¡¡¡¡¡¡¢¢¡¡¡¡¡ŸŸŸŸŸŸŸœ›™“Š‚€‚ƒ‚€€‚ƒƒ‚ƒ†Š‹Œ”——”‘Ž‘ŽŒŠ‡†ƒ€|ywuvurpoorqquwzŽš¡¤¦§§¥¡Ÿ›”‹ƒ}zyyyz|€‚…††…†‡ˆŠ‹‹‹‹‹‹‹ŠŠŠ…€‚ƒ…‰ŒŽˆ{qs}ˆ‹ŠŠ‹‹ŒŽ’“•–Žƒslkklmnortwy|…ˆ‹ŒŽ’¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ŸŸŸŸŸŸžœ›™˜–…„……††ˆˆˆ‡‡††ˆŠŠ‹Œ’••“““’’‘‘‘ŽŠ‡…„}zwtsrrqonnooprqx’¢¦§§¨§£ œ•‚|yyyy{|€‚ƒƒƒƒ„…‡ˆŠ‹‹ŒŒ‹Šˆ†‚„ƒƒ†ˆ‹Œ‰}tv€ˆŒ‰††ˆ‹‘“““•—Œwmkjjjjkmpsuvxz}…ˆŠŒŽ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡ŸŸžžžŸ›š™˜—‘‰…††ˆŠ‹‹ŒŒ‹Š‰‰Š‹‹ŠŠŠ’“•”““’‘Œˆ…ƒ€~{yvrqpoonmmonoqmv”ž£¦§¦¨¦£ œ•‚}{ywwz{~~€€‚ƒ…‡ˆ‰‹‹‹‰‡…†‡‡‡‡‰Š‰„ysw‚Š‰‡…ƒ…‰Ž‘’’““”–“‚rollkkjkmortsuwy|…‡‰Œ¡¡¡¡¡¡¡¡¡¡¡¡ŸŸ Ÿžžžžž›™˜˜˜”Ž‡†††‡‰‰‰‰Š‹‹‹‹‹‹‹Š‹Œ‘’’’’‘‘‹†‚€€}zwtrqoomkjjllnqkv”ž£¥§§©§¤¡œ–Œ„{wuswy{|}~}}}}~~€ƒ…†ˆ‰Š‰‡†ˆ‰ˆ‡…†…wtx€†ˆ‡†„‚…‰’’’“——–’†~ztrpnnnnnoqqrsvy|€ƒ…‰¡¡¡¡¡¡¡¡¢¢¡¡ŸŸžžœœ™˜˜—”Š‡†…„……†††ˆ‰ŠŠ‹‹Œ‹Œ‘‘’ŽŽˆ„€{xvsqooonlhggfjsly•ž¤¦¦¨©§¥£ž—…zuqnsxyz{|}{zyzzz|…†ˆ‡†‡‡ˆˆ‡†…‚zwy}„†‡ˆ†„ƒ†‘“•™™—•‘ˆ€ztpnmmmmnoprsux|ƒ‡¡¡¡¡¡¡¡¡  ¡ žŸŸžœ›˜——–”Š††…ƒ„ƒƒƒ„‡ˆ‰‰‹ŒŒŒŽŽ‘Œ‹Š†ƒ€|ywurpnoonmifcbhsn—Ÿ¥¦§©©§¦¥¡š‘‡xsniotvxy{{ywvvvvw{€…†††‡‰‰ˆ‡†…‚~yxx{‚‡‰ˆ‚€†“”•™™—”’’Ž†‚xpnmmmmmmorstuy|€„¡¡¡¡¡¡¡ŸžŸŸŸŸŸŸžœœ™—–—“ŽŒ‡„ƒƒƒƒƒ…†††‡Š‹ŒŒŽŽ‘ŽŒ‹ˆ†€~|zxuspnmnoomie``hst‡›¡¥§¨©©§¦¦£–‹toiciorswxwvutssstvz€ƒ…††ˆˆ‡‡…„‚|ywx|‚ˆury‰““”—–’‘Ž‹ˆ€ytrrsrqoprssux{}€¡¡¡¡¡¡ Ÿžžžžžžžžžœœ™˜˜˜’ƒ‚„„ƒƒ„„……†‡‰ŠŠ‹ŽŽŒ‹‰…€}{zywtqnmlmmmlje`an}„‘ž¤¥¨ª©ª©¨§¥¡š‚wnf]`gjnsuutsrqqqrtwz~ƒ††‡‡††……„‚|ww|xhgoyŒ‘ŠŒ‘’’‘‘ŽŠ‡‚}yxywtrqqstvxyz}¡¡¡¡¡¡ žžžžžžžžžœ›š™™™”…‚ƒƒƒ„„„…†‡ˆˆ‰ŠŒŽŒŒ‹‰…‚€|zywwuspmkkjjiiifbj|Œ•œ¡¥§©«««ªª¨¦¢›“‰|pbW[abgpssrqonmnortwzƒ†††††………„|wx}zkdkt{‚‹Ž‹…ˆŒ‰†‚~}yvtttwxxxyz      Ÿžžžžžžžžžœœ›™™š™˜™”…‚‚€ƒ„…†‰Š‰ˆˆŠŒŽŒ‹‹ˆ…~|yxwtqqokhgghhhhiigevŠ”›¡£¥©«¬¬¬«ª©¨¤Ÿ˜‚s[QY]\`inonmljjjlnqtw{€„††‡†……„…„}xyyqgfmu{ƒŠ‹‡„†ŠŒŽŽŒŒ‹‰…‚€~zxxxxxxxxyžžžžžžŸžžžžžžžžœœš˜˜˜˜˜˜—”ƒ}{xxxzz~ƒ…ˆŠ‹Š‰‰ŠŠŒŒ‹Šˆ†„{yusqpnljea_]acefggijpˆ˜¤¦¨¨«¬­­­­«©©¦¢•‰tVMVZ\`gkkjjihhijmoqtx}‚…††††…„……|ywqkjpw{…†……‡‹ŒŒŒŒŒŒŒ‹Š‡„€}{ywwwxxyyyyŸŸŸŸŸŸžžžœœ›™˜˜———––“ƒxvssuvw{~„ˆ‹‹ŒŒ‹Š‰‰Š‰†ƒ€~yvtqnmljie^YWWYacdegnu“Ÿ¥ª¬¬¬­­­­­­¬«ª§¤¡™zWHQV[aghhhhgfgijlmnqtz‚…‡‡††……„‚|zwvvvy}€ƒ„…‡‰‹‹ŒŒŒŒ‹‹‹ŠŠˆ„€~|zxwwwxxyzyzžžžžžžžœœ›™š˜—–•“Œqhhkpstwyz€‡ŠŒŒ‹Š‰‡…‚}zyuromjiiihdZTRRT[`egiy†›¤ª¬®­®¯¯®­­­¬«ª©¦£”YHNS\dgfeddddfhiklmosx|„†††…„„„ƒ‚€€€€ƒ…‡ˆŠ‹ŒŒŽŽŽŽŒŒŒŠˆ†ƒ€~|{{zyxyzyzœœœšš˜—•’‰…wcTU]ejmptx}„‰ŒŽŽŒŠ†‚|wssqmjgedeghbXRONPV_gho„–¡¨¬¯¯®¯°°¯®­­­¬««¨¤Ÿ˜‚YKRW^ddca``_`bdghiklou{‚………„ƒƒ…„„…†‡ˆˆˆ‰‰‰Š‹ŽŽ‘’’‘‘Œ‰‡†…‚€€}{zzxyœœœœœœ››šš˜–“‰…|iUILPV^dinu|„‰ŒŽŽŠ…~ysnlkgdb``acdaYQNMPV`inyŽ˜Ÿ¦«®°°°°°°°¯®­­­¬¬©¥¢œdT[`baaa`^[Y\`bcefhimrw|„‚‚…†…‰‹ŒŒŒŽŽ‘’’“”••“’‘ŒŠ‰‡††„€€€}|{|œœœœœœœœœœœœ›š™ššš˜•’‰s`RKJLOU^env…ŠŒŽŽŒŠƒ{wqlgeb_^^_``b`YROORX^jv…—Ÿ£©¬¯°±±°°°¯¯¯®­­­«ª§£Ÿ–{jnplb_`]XTSUZ^`acfgkpty}€€€€„…†ˆ‹ŒŽ‘‘‘’““”•–•”’‘Ž‹Š‰ˆ†„ƒ€€€€€~œœœœœœœœœœ›šš™ššš™™™–“Ž‡raLCFJMT^gqz‚ˆ‹ŽŽŽŒŠˆƒ}wqjda^\\\]]_`^YTRSVY^m|¡¦ª­°±±±°°¯°°¯­­­­¬«¨¤ ˜‰~}|qd^\VQMKLSY[\aehkpsx{~€€€€‚„†ŠŒ‹‘’““’‘‘“•–—˜˜—•“’‘‘Ž‹Š†…„}|{{|€‚››››œœœœœœœšš™™™™™™˜—“…~zk\K>@EEISht}…ŠŽŽŒ‹‰†‚}vrlea^\[ZYZ[[][WUVYZ`p„˜£¤¨«­¯°°°°°¯¯¯¯®®®¬¬ª¨¥ œ—ˆqcYTNIHHIOTVZ`fimqswy|€ƒ†ˆŠŠ‰ŠŽ’“““’‘‘’’•—˜š›››š˜—••“’Ž‰†„ƒ~zxwtv}‚›œ›œœœœ›œœœœšš™˜˜˜˜˜—‘Š‚}seXNE@@@CEQgx‡ŒŒŠ‰†ƒ~xsngca^[YYXWWYYWX[]]cvŽŸ¤§ª¬­¯°°°°°¯¯¯¯¯¯¯­¬ª¨¥ œ›–Œo\NHCEILMORSW`eimqtvx{}~„†‡‰ˆ‡Š’’’’’“”•–—šžžžœš™˜—–”“Š†…ƒ€}{ywy|~œœœœœœ››œ›ššš™˜˜––••†€zn`RJFB@CGU^do}…‰Œ‘‹Š‡„{vqjfca][XUSSTUW\_aafy’¢¦©«¬­°±°°°¯¯°°°°°¯­¬«©§ž’Ž†}sbODFMX[ZYZYWUYcknostvy{{{ƒ„…†††‰ŒŽ‘’““”–——˜›ŸŸŸŸœ›š™˜–•‘‹‰‡…€~|z|~œœœœœœ›ššš™———–”’‘‘Ž…}xjZSOGDFLY]hsy}‚†‹ŽŽ‹‹ˆ„}xtnjf`[WRNMNRUX\^_ck|”£§ª«¬­¯°°°°¯¯°°°¯¯¯­¬«©¥™‡{rjaWJLV]`abccc`[V^kmortvwxxy}€‚„„„…ˆ‹‘’““”–—˜™›žŸ    ŸŸœ›š˜—”‹ˆ…„„‚€€œœœœœœ›™š™˜•“’’‘Š…~wk]VSPLMTdsx|„…ˆ‰ŒŒ‹‹Š‰‡‡…ƒ€}xtoke]VOJGJNPQTY\_fp‚™£§ª«¬­¯°°°°¯®°°°¯¯¯­¬«¨Ÿ‰rh`\YXUWYVSSW_cgig]Zdjmoqsuvvx|‚„„ƒ„ˆ‹‘’“”–——˜šœŸŸŸ¡¡Ÿžžœš˜—•‘ŽŒŠ‰‡‡‡…„„œœ›››š™™™˜—”‘‘‘Œ„~vndZXVSX_jy„ˆ‹Ž‹ŠŠˆ‡†…„ƒ€}zvsojaYOGDGMQQONR\dlyŒ¥¨ª««¬¯°°°°¯®¯¯¯¯¯®¬ª©¥•vc`][Z\ZVOKIILT\cjmfX[eikkmpsuxz}€€‚†Š’“•——˜™›œžžŸŸŸžžž›™—•’Œ‹Šˆˆ†……››ššš™˜˜˜˜–“ŽŒ†}tnf`[ZZ^ht~…‹‘ŒŠ‰‡†…ƒ‚‚}zwtqmg]TLHN\b`^\VTZh|— ¥¨ª«¬¬®¯¯®®®®¯¯¯¯®­ª©©¥‹g][\\\\ZQJEEFHMRW`ln`W^fgffhlquxz|}}}~…ˆŒ’”•—˜™™››œœœžžžœœš—–””“’‘‹ˆ†…„„š›ššš™™˜˜—“Œ‹…~vpkfa]^fow€†‹‘‘‘‘‰††„‚~~~|{xutqmg[RSbptme^\YXZc}•›¢¥¦¨ª««­®®¯¯¯¯¯¯¯¯®¬ª¨¨£…`USR\^\WMEBCEFGLRWepjUVcdbacflrvvwyyz|€ƒ‡‹Ž‘“•–˜™™šš™™š›œœœœ›™™˜™™˜˜—”Š†ƒššššš™™˜˜–“ŽŒ‹ˆ„xrlihilu{„Š‘““’‘‘‹ˆ…„ƒ€}|zyxwutsqnfVWhtsfVMIILQU]q›¢¤¥§ª««­®®¯°±±°°°¯­¬«§¦Ÿ€bXPL_b[UKDCCDCEKRW`mnRL[^]\_diorttvxyz|†Š‘“”–—˜™™™˜—˜˜™™™™šœ›ššš—“Ž‰†‚}{{{™šššš™˜˜™˜–“ŽŠ‰„{tonorv{€†‹‘’”“’’ŽŠ†€€€~{yvtrpppoomcYjwqaL@<>BDFKVdy—¢£¥¦¨©ª«­¯°°±±°°°®¬¬«¨¦ž…l_OOgdZSIBQTCEMQU[ajmNESWWX]aehlprtxyzz}„ˆŒ“”•–—˜˜˜˜—˜˜˜™™šœžŸœ™˜–’‰ƒ|xvusrr˜™™™š™˜˜™™˜•’‘ŽŠ‚xsopruzˆ‹‘’“””“‘Œˆ„~{{{xurokiijjkkaasscRF<:<@CCGNYj‹ž¤¥¦§¨ª«®¯°°±°°°°®¬«ª¦¥¡‘xfQTni]TJQ|sLDNRSW`llJBPUVX\_bchlnruwyz|‚‰‘“–˜˜™™™™˜˜˜˜˜™š›œ›˜•‘‹‡ztrpppr™™™™š™™˜™š™—””‰zutsx|‡‹Ž‘’“”•”“‹‡{xwwtqnkfbceefbZesjVI?;:<?AADKSay•¢¤¥§§©ª­¯°°°°¯¯®­««¨¦¤ šˆnMTsoaVNp˜nHAEIKO_pgFBOTWZ]_`bejlptwz|~ˆ‘”—™™šššš™™™™™™›œ›™˜”Š‡{wurrsv™™™™š™™˜™š˜•’Š‚}zyxy„ˆŠ‘“”•–”“‘ŒŠ„zwtrqojf`]_abc^WmsdQC<<@A>?EFIR`m‡ž¤¤¥¦¦¨«­¯°¯¯¯®­¬«©¦¥£¡œjGTpsfYQegLFFHIMVgn[ABMTY[]^]^bgloswz~€„‰‘•—˜™›››œš™™™™š››œ™™—“Œ‡~~}zwxyx˜™˜˜˜˜˜˜˜—”’ŽŠ…€}{z|€„‰“”––––”’‘ŽŒˆ‚~ytpnnkga\Z[]_]PToo^NFAB[W>ANNJSchr•¡¤¥¦¥¦©«®®®®­¬¬«©¦¤¤¤£ŽeERgle]WNEGJIKKPanhPAEMTXZ[\[]`fkosw{‚†‹Ž‘•–˜šœœœœ›››››š›œœš™˜—“‘ˆ…„„ƒ€}|zx—˜˜————––””’ˆ†…‚€€‚†‹“”—˜˜˜˜—”’’Œ‰‚~yspmkiga[YXY[XIQkkYMGCcj==CILWghgŒŸ¢¤¥¤¥¨«¬¬¬­«ªª¨¦¤£££¥ž^DGXhda^WPMMLMNWggP>DKPTWXYZ[^bglnrx{ƒ‡‹’•—™›œœœœœœ›™˜—•’Š‰‡…ƒ€|yw“”–—–––•”“”“‹Š‰‡……ˆ‹Ž‘”–˜˜™™—–”“‘Œˆ}ytqnljga[XVWXUGMeiZOJJhvP;=CKMZje_€ž¢£¤£¤¨««ªª©¨§¦¤¢¢¢¡ ¢ž‹\BBKbgc_[VSTUUW^cW@=HNSWYY[]_cgjnoty}„ˆŠŒ’•—™›œžžžžžœ›˜—•“’ŽŠ‰…ƒ€|xvs‘“”–––•”••”“Š‡‡‰ŒŽ‘“–—˜˜˜˜—–”’‘‹†€}xsnmkhf_ZWUUURCF^i`UMHKH=;BHMR`i^Ux ¦¤¢¡¡¤§¦¤£¢¡ ¡ Ÿ  žœ™†]CBEUdb][YYYYYYZVIFPZbhjjihgffikorvz~ƒ…ˆŠ‘”—™›žžžžœœš—•”’’‘Š‰…€}zurp‘’”•–––––••”‘Ž‹Š‘‘•———˜˜˜˜–•’‘ŽŠ…}wqlkhd`\YXVUVRFDWih^RLHE@?FKNXggVStž¥¡žžžžŸžœ›˜˜›žœœš˜€^L@GV`_YY[[YXY\bb^iv~~{wtqkeehlpsx|†‰‹ŒŽ‘”—™›žžžžœœ™—–•””““‘Ž‹Š‡‚|wtrpˆ‹‘’”•”””••”’‘Ž‘’“–˜˜˜˜˜™™—–”‘Š„}vplhd_[YXWXXXVMIN`nj^VNLKJNQTaldSSp˜ œšš›››™˜—•–—šš™™™š˜–i__jtwrlmprux|€†††Ž“””†~xtmilpqsw{…‹Ž’•—™›žžŸŸžœš˜————–””’‹‰‰…zyxwƒ†ˆŠŒŽ‘‘’“””•““‘‘’”—™š™™™™™˜–”’ŽŠ„{tnjfb\XUVWYZZYUOHM[ga]WSRRUX]cifZYnš™˜˜˜˜—••“’”–˜˜———˜™˜”z‚ˆŽ‘“•˜šš™™ŸŸŸ™’ˆ~ytrtttuy~„Š““”’“•—™›žžŸŸž›™———˜˜˜–•“‹Šˆ„~|z~€ƒ…‡‰ŒŽ‘’““”””’‘‘’•˜™šššššš˜–“’‰ƒ|vqmhe`\Z[]]]\ZWRKHO[_``\XZ]bkt|{pmvŽ˜——˜˜˜–””“”–—————–—š˜—”Œ‰‰‰Ž“—˜˜—™›ž¡¢¡¡¢£¤£¢Ÿš”Œˆˆ‚€~~‚‡Œ‘”–—–•––˜™›žžžž›™—˜˜˜˜˜—–•’Œ‹‡…}zxy{~€ƒ†ˆ‹Ž’’“””“’‘“–˜šš››œœ›™—“‘ˆƒ~zvqligeddggfea^\ZXW[_abacfkv„Ž‘…{}„‘˜˜——˜˜–•”“•–—————–˜œš˜˜–”‘”˜œŸ¢¦©ª«¨¦§¨¨¨¦¤£¡žš•“‘‘Ž’•—˜™š›š™˜™šœž›š˜˜˜˜˜˜—–•“‘ŽŒˆ…‚}yvrtxz{}€ƒ†ˆ‹Ž’““’‘”–˜š››œœ›š™—”Œ†‚~zxsnjgfglssrsw{}‚€€€‚…‹–›š–Žˆ‰–™™™˜˜˜—–••–——————–—œœš››˜–•–˜›Ÿ£§¬±²²¯­¬¬­¬«©¦¤£¢Ÿžš˜––—˜›ŸŸŸžœ›œœžžœ›š˜˜˜˜˜—–•”‘ŽŠ†…€zvlnstvxz}‚ƒ†ˆ‹‘’‘‘‘“—˜›œœœ››š™˜•‹†|yvrkfehmruz€ˆ“˜›œœ›™——˜šŸ —’‘”™›œ™˜————–—˜——————––›œŸž›™˜˜š¡£§­±²±°°¯­®­­¬ª¨§¥¤£ žœ››œžŸ¡¡¡  Ÿžžžžœœœœš˜˜˜˜˜—–––”‘Š‰…{gjnqsuwz|~‚…ˆŠ‹Ž‘“–˜šœœœ›šš™˜–’ˆƒ‚|ytmhgjnqwŠ‘—¢¤£¢Ÿœ›œŸ˜“‘““—›œ›™˜——–—˜—˜˜————–”–š›ž¡¡ŸœœŸ¢¤©­¯±±±°¯¯®­­­¬¬ª©¨¥£¡žžŸŸ     ¡¢¡¡¡Ÿ ŸŸœœœœ›š˜˜˜˜——––––’ŽŒ‰†cfikmptvxy}„…‰‹’•—š›š˜•Œ‡…„zuplmnqt{‡‘™ž¢§¨§§¦¥£¢¢¢¡Ÿ—”””–™œ›˜—–••—˜˜˜™———–”“–˜›ž££¢¡ Ÿ ¡£¦©­°²³³²±±°¯¯¯¯¯¬«ª©¦¤¢¡¡¡¢££££¤¤¤¤¢¡¡¡¡ Ÿžœ›šššš˜˜˜˜—––—–”’‘ŒŠ„{^adfhjmpruz}…‡ŠŒ‘•—›žžžžž›—”ŽŠˆ„€|xtrrtwˆ’š¢§ª«­­­­«ª©¨©§¤ ›——–™œžœ˜–”’•—˜™š™™™——–•—™¡£¤¤£¢£££¤¦ª®±²³³²²±±±±±°°®¬ª©§¥¤¤¤¤¤¤¤¥¥§§§§¦¤¤¤£¢¢¡ Ÿžœššš™™˜˜˜˜––—–•“’‘‰~wZ[_bdehjmqtwz}€ƒ…‡‰‹Ž“—šœžŸŸŸžš˜•“‡ƒ{vssw}‰•œ¡¦ª¬®¯¯°°¯®®­¬«¦ žš™šŸŸŸ˜•‘“•–––˜—–•’‘“›¡£¦¥¤¤£¤££¥§©¬¯²²±°°°°°±±°°®¬ª©¨§§¦¥¥¥¥¦¦§¨©ª©¨§§§§¦¥¤£¢Ÿš™šš™˜˜˜—————•““’‘‹|XZ]_`bdfhkmpsw{~„‡ŠŒ’–™›œžŸŸŸžžžœš˜”Œˆ„~||€Š‘™Ÿ¤©«¬¯°°±±±°¯®¬«¦¢Ÿœ £¢¡Ÿš’‰‰ŒŒŽŒŠ†…‡˜¡¥¦¥¤¥¥¤¤¤¥¦©ª­±°°°°°°°±±°¯­«ª©¨§¦¦¦¦¦§§¨¨©©ª«ªªªªª©¨¦¤¢ ››šš™™™˜˜˜˜˜˜—••”’’‡…VY[\^_`acfghlqvz}€ƒ…‡‹“–˜›ž    ¡¡¡ žœ˜•’ŽŒ’“™›œ ¥ª¬­°±±²²²±°®«©¥¢Ÿ £¤££ ˜‰„…†ˆˆ‡…„„„ƒ„…‡Œ“£¤¤¥¥¥¥¤¥¦§©«¬®°°°°±±±±±°¯¬ªª©¨¦¦¦¦¨¨¨¨©ªªª«¬¬«ªªª©¨¦¥£ žœ›š™™™™™™™™™™™˜—•”“ŽŽWYZ[\]]^`acdglquwy|}ƒˆŽ“—šŸ  ¡££££¢¡Ÿœ˜•••—™š›Ÿ¡£¦©«¬­¯±±²²²±°®¬©¦¢ Ÿ £¥¥¥¤’„‚…‡ˆ‡…„‚ƒ„†‡ŠŒ”˜ž£¥¥¥¤¤¥¦§©«¬¬®¯°°±±°±°°®«««©¨§¨¨¨©ªª«ªªª«¬¬­¬««ª©§§¥£¡Ÿ›™™™™™™™™™™˜™™™——–•••WYYYYZZ[]]_`bhmpstvx{}‰‘–šŸ ¡¢¤¤££¤¤¤¢ Ÿžž    ¤¦©©ªª¬­°±±²²²±°®¬«§£¢¢£¤¦¦¦¤—‹„‚…‰Š‹‹Œ‹ŒŽŽŒ‰…Œ›£¥¥¥¤¥¥¦§©ª«ª«­®¯°°°¯¯­¬«ªª©©©©ª««¬¬­¬¬¬¬¬¬­­¬¬«ª¨§¦£ Ÿ›™š™™™™™™™™˜˜™˜˜——˜™™YYYXXYYY[\]]^cilmnqtwz†–™œŸ¡£¤¤¤£¥¥¥¥¤££¤¤¤¥¥¦¨ª©ª«¬­¯±±²²²±¯­¬«§¤¢£¤¥¥¥¤ŸŒ}}€†‹ŠŒŽ’”‰†…‚|ƒ”Ÿ¤¥¥¥¥¥¤¦©©ªª©©ª««¬¬¬¬«ªª©©©ªªªª¬­­®®­­­­­­­¬­¬¬«ª¨¦¥¤¡Ÿœ›™™™™™™™™š™™™™™˜™›ššZZXXYZYZZ[\\\_chjmorvy|ƒŽ•™›Ÿ¡¢¤¤¤¥¥¦¦¥¥¥¥¦§¨¨©©ªªª««¬­¯°±²²±°¯­«ª§¦¥¤¤¥¥¤¢—qt{€}‰‘‘‘“•‡mnx‡™Ÿ£¤¥¥¦¦¥¤¥©©ª©¨¨¨©©ªªª©¨§§¦§©ªªª«¬­®¯®­­­­­­­­¬¬¬«©¨¦¥£¢ žœš™™™™™™™š›œœ›œ›››YZYYYZYYYZZ[[\^cgijlpuz‹“—šŸ¢£¤¥¥¥¥¥¦¦¦¥§¨ª«««««««¬¬­­¯°°²²±°¯­ª©¨¦¦¤¤¥¥¤ ™†urrpgk‘’’‘{dq…‘££¦§¥¥¦¦¥£¥©ª©¨¨¨¨¨©©©©¨¦¤¤¤¦¨©ªª¬­®¯¯¯®®­­®­­­­¬¬«ª¨¦¥£¢¢Ÿžœš™™™™™™›œœœ›››™ššYYXXXWWXYYYZYZ]`acehmszŠ‘–™œž¡¢¤¥¦¦¦¦¦¦§§©©«¬¬¬«««¬­­­®¯±±²²±¯®¬«©¨¦¥¤¥¦¦¤¢ ™Œ„{qn{Ž‘Ž{sˆ™Ÿ£¥§©©¨¨¨§¦¤¦©«ª©©©©©©©©©§¦¤££¦¨©ª«¬®®¯¯¯®­­­­­­­¬«««©¨¦¥¤££ Ÿžœ››šššš›š˜˜™˜™˜˜™™XYZWWUUWWWXXXZ\]_adhnu{„Œ“—™œŸ¢£¥¥¦§§§¨©©ªª«««««¬¬¬­­®¯°±±°®­­¬«©©§¦¥¥¥¦¦¦¥¤¢žœ™“‹‚‚ŒŽŒ‹ƒ˜¡¤¤§ª«ªª©©©¨¨¨ª««ªªªªªªª©©§¦¥¥¥¦©ª«¬¬®¯¯¯¯®­­­­¬­¬««ªª©¨¦¥¤££ Ÿž›šš™™˜˜—•“•––—————WX[YUSSSSSTVXZ\\^bhmt~†Œ’–˜™›Ÿ¡£¤¥¦§¨©©ª«¬¬¬«««¬­®­­­­¯°°¯­««ª©¨§§¦¥¥¦¦¨©©¨§¦¦¥¤ ›’Š‹ŒŒŠ‰‰‘¤¦¨«¬¬«ªª©ª©©ª««««ªªªªªªªª¨¦¦¥¥¨ª««¬®¯°°°°®¯¯¯¯­¬««ªªª©¨§¥£¡ žœš˜——••”“’‘’“”“”••VX[ZWSSSTTTVXZ\]`emu}‡Ž“•—™™šœž ¢¤¤¦§¨©ª«¬¬¬¬«««¬­®­­­­¯°°®«ª¨§¦¦¦¦¦¦¦¦¨©©©ª¨§¨¨¨§¦‹‹‡ˆŽ™£¦©«¬­­¬««ªªª©©ªªªªªªªªªªªª¨¦¦¥¦¨ª«¬­®°°±±¯°°°°¯®¬«ªªª¨§¦¤¡žœš™™˜–”’ŒŽ‘’’““Y\]^[WWXYYXYZ[]`djs|…’–—˜˜™š›ž £¤¥§¨©«¬¬¬¬¬«¬«¬­®­­­­­®®­ª¨§¦§§¦¦¦¦¦¦¨©©©ªªªªªªª©¤™Š‡‹• ¦¨ª«¬¬­­¬¬«ªªª©¨©©©©©©©©ª©©¨§¦¥¦¨ª«­¯¯°±±±°¯¯¯°¯®¬«ª©¦£¡ Ÿ›™˜—–•“Œ‹ŒŽ‹‹‹ŒŽ‘’““_bba^ZZZ[ZYXXZ]aipx~‡’–˜™™™™™š››£¤¥¦¨ª¬¬¬­­­­­­­­®®®­¬¬¬¬ª©§§¨§§§§§§§¦§§©©««¬««««ª¦ž“Œˆˆ¤§ªª«­­­­¬¬«ªªªªªªªª©©©©©©©©¨¨§¦§©ª«®¯°°°°°°°°¯¯®«ª§¥£žœš˜•”•“‘‹‰ˆ‡†ŠŽ‹‹‹ŒŒ‘‘’’_a`_\VUVWUTSSW]`gov}‰”—™šš™™š›œ›¢¤¥§¨ª«¬¬­­­®®®®®¯®®¬¬««ª©¨¨©©¨¨¨¨§§§¦¦§©ª¬¬­¬¬«ªª§¡–Œ„†” ¤¥¨©ª«¬­¬¬¬««ªªªªªª©©¨¨¨¨¨¨¨¨¨©¨§©ª«­®¯¯¯°°°¯®­­ª§¥¢ ™™˜•‘‹Š‰‡„€€‚‡‹ŒŒŽŽ‘‘‘]^_^[WVWXVTTVZ`bgox‚Ž–˜šššš™š›œœ¡£¥¦©ª«¬¬­­®®®®®®¯®®¬¬«ª©¨¨©ª©¨©©©¨¨¨§§¨ª«¬¬­«ª©©¨§¢˜ƒ…”ž £¦¨ª«¬¬¬¬««ª«ªªªªª¨§§¨¨¨¨¨¨¨©©§¨ª«®¯¯¯¯°¯®­««©§¤ š–’Œ‰‡………„‚€~~€†‘Ž`aba^\[[[ZXWY[^ahq|Š”—™š››šššœœ¡¢¤¦¨ª«¬¬­®¯®®®®®¯®®¬¬ªª©©©ªªªªª©©©©©¨¨©©ª«««ª¨§¦¥¥¡šŽ„–œž¡£¥¨©ªª«««ªª©©¨¨¨¨§§§©©©©©¨¨¨¨¨§©¬¯¯¯¯¯¯®¬¬«©¨¦£Ÿ™”Ž‰…ƒ‚„†‡ˆ‡ƒ€„ŠŽghhfedb_\ZYXX\`gpy„–™šœœšššœœ ¢¤¦©ª«¬¬­®®®®®®®¯¯®­¬«ªªªªª««««««ª©©©©©¨¨¨¨©§¥¥¥¤£ ™Šz‘—›Ÿ ¢¤¥¦§¨ªª©©¨§§§§§§§¨©©ªªª©©©©ª©©«®¯¯¯°°¯­¬«©§¦¥£Ÿ›–’ŽŒ‹Œ‹ˆˆŒ’‘ŽŽŽŽŽŽŽŽonnnlkkfa_\[]cjt€‡“—™›œš™šœœ ¢¥¦¨ª«¬¬®®¯¯¯®®®¯¯®¬¬««««««««««««ª©©©©¨§§¦¥¥¤¤££¡™’…y~Š”™ ¢¤¥¥¦¨©©§¦¥¥¥¥¥¦§©©©ª©©©©ª«¬¬¬­®¯¯°°®­¬ª¨¥¢   žœ™—•••––—–”‘‘’‘ŽŽŽŽŽŽtrsrrqqmieccgnv†“—™š›œœš™šœ ¢¥¦©ª«¬­®®¯¯¯®®®¯¯®­¬««««««««««««ª©©©©§¦¦¦¤£¤¢ ž›•‹ƒ~‚‰‹‹“˜› ¤¥¥¦§¨¦¤¤¤¤¤¤¦¨©©ªªªª©ªª«¬­­®¯¯¯¯®¬«ª¨¦£¡ŸŸžžœš˜˜˜˜˜™šš—“‘‘‘’’ŽŽŽŒwwvuttrqnmotz€†‹”˜šœ›šœ›š™šž £¥¦¨ª¬­®°¯¯¯¯¯¯¯°±°¯¬¬«««««««««««ª©©©©§§¦¥¥¥¤Ÿš•Œˆ‡†…‡ˆ‡††„‡Š•›œž¢¤¥¥¤¤¤¤¥¦¨©ªªªª©©««­­­®°¯¯¯¯­¬ª§§¥£¢  Ÿžœ›š™™™™š›œ›˜”’’’’“‘‘ŽŽŽŒŒŒ{{zxwvuuuv|‚†Œ“•—˜™œœ›šš™™™šž £¥§¨ª¬­¯°±°±±±±±²²±°­¬«¬¬¬¬¬¬¬«««ª©©©©§¥¤¢¡ œ–‘Œˆˆˆ‹Šˆˆˆ††…‚~~‚‡šŸ¢¤¤¤¤¤¤¦©ª©©©©©ª¬®­®¯¯¯¯®¯¯®«ª¨¦¥£¡¡ Ÿœ›šššššš™š™—”“’’’’’‘‘ŽŒ‹Š|}|zyyz|…‰ŠŽ“”••–˜˜™™™™™™™šž £¥§©«¬­¯°±±²²²²²²²²±¯®­­¬¬¬¬¬¬«««ª©©©¨¥¤¡ž›–Ž‘‘ŽŒŒŒŒŠ‡„~zx{‡“œŸ¢¢¢£££¦©ªª©©ªª¬­¯¯¯¯¯¯¯®¯¯®¬«©§¦£¡  Ÿ››šš™™™™™˜˜–”’’’’’’‘‘‘ŽŒŠ‰~€}{{}‚†‡ˆ‰ŠŒ’““”•–—˜˜™™˜™šž ¤¥§©«­®°±²²³´³²²³´³²²±°¯­¬¬¬¬¬««ª©©©¨¦¥¢Ÿœ—’’–™ššš™—–””˜ššš˜‘Œ‰…‡Œ’–šŸ¡¡¡¢£¦©ª©ªª©ª­¯¯¯¯¯¯¯¯®¯¯®­¬ª¨¦£¢Ÿžž›™™™˜˜˜™™˜——•““““““’‘‘Œ‹‰‰€‚|{~„†ˆ‡‡‰ŠŒ‘’’’’“•–———˜˜šœž ¢¥§ª¬­¯°±²²²³´´´´´´´³²²°¯®­­¬«ªª©©©¨§¦¥¡žœš™™œŸŸŸ ž›ššœž ¡ ›—–”•˜™šŸ¡£¤¦§©ªªªªªª­°°¯®®¯®­¬­­­­«©¦¥¢ ž›™˜—––———˜—–•“’“““’’‘ŽŽŽŒŒŒŒ‹ŠŠ†…~}€ƒ„†…„…‡‰Š‘“––––—˜™œœž ¢¤§ª¬¯°±²²³²³´µµµ´´µ´³³±°¯¯¬¬ªªª©©¨§§§¤¢¡ Ÿ ¡£££££¢ ž   ¢££¤£ ž›š››ž ¢¤¥§¨©ªªª««ª­¯¯®­­­¬«««««ª©¥¤¢ž›™˜˜–••–—–———•”’‘‘‘‘‘ŒŒŒŒŒ‹ŒŒ‹Š‹‹ƒŠ‹…‚ƒ€~}€„‡‰‹‹Œ‹Ž’”–––—˜™œœŸ¢¤§ª¬¯±±²³³³³µµ´´´´´´´³³²±°¯¯­«ª©¨¨§¦§¥£¤¤¤¥¥¥¤¤¢£££¡ ¡£¤¤¤¤¥¤¤¢ Ÿžžž¡¢¤¥¦§©¨ªªª«««¬­­­­¬««ª©ª©§¥¢ Ÿ›™˜˜———––———–––•”’Ž‹ŠŠŒŒŒŒŒŒŒŒŒŒ‹Š‹‹†Ž’Ž‰†‚~zyy{€‚„††††‰’“•––—˜™›œŸ¢¥§ª¬¯°±²³³³³µ´´´´´´´´³³³³±°¯®¬«ª¨¨¨§©¨§§§¦¦¦¦¥¤£¤¤¤¤£¤¥¦¥¥¥¦¦¦¤£ ŸŸ¡¢£¤¥¥§¨©©ªªª«««¬¬«««ª©©©¨§¦£ Ÿœ›š™™˜˜——————–•••“’‹‹‹ŠŠ‰Š‹ŒŒŒŒŒ‹‹‹‹Š‹‹‹‡””‹„~zyyz|~ƒƒ„‡Œ‘’“•–——˜™›› ¢¤§ª¬­¯±±²³³µµµ³³³´µµ´³³³²±°¯¯¬¬«©©¨©ªª©©©¨§¨§¦¥¥¥¦§§¦¦§¨¦¦¦§¨©¨¦¤¢¢¢£¤¥¦§¨©ªªªªªª«««ªª©ª©©©§¦¥£¡ Ÿžœ›š™˜˜————–——–•”“‘‹‰ˆ‡ˆ‰ŠŠŠŒŒŒŒŒ‹‹‹‹‹‹‹‹’—™—“„}{yyy{}€ƒƒ†ˆŒ’’“”•–—™šš ¢¤§ª¬¬®°±²³³µµµ³³³´µµ´³³³²²°¯®¬««ªªª©©ª©¨¨¨¨¨¨¨¨¨¨¨©©©¨¨¨¨§§¨©ªª§¤£¢£¥¦§¨ª«««««ªªª«««ªª©¨§¦¦¢  ŸŸŸžœœœš™˜————–••••“‘ŒŠˆ‡‰‰ŠŠ‹ŒŽŒŒŒ‹‹‹‹‹‹‹Œ”š››š•Šƒ|yz}€‚ƒ„…†Š‘“““”•—˜š ¢¥§ª¬¬®°±²²³´µ´³³²´µ´´³²²²²±°®­¬ªªªªª©©©©©©¨¨¨¨¨¨¨¨¨¨©©©©©§¦§©ª©¦££¤¦§©©ª««¬¬«««ªªªª§¤¤¤¤£¡ žœœœ››™™˜————–••”’ŽŒŠ‰‰Š‹ŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹Œ”˜šœ›˜’‰‚~|~ƒ…†…„ƒ„‰Œ‘’’’“•—šœ £¥§ª¬¬®°±²²²³µµ´´³´´´³²±±±°¯¯¯­««ª«ªªªªªªª©©©¨©©©©©©©ª©ª©©¨¦¥¥§§¤¤¤¤§¨¨©ª«««¬¬¬««¨¥¡šš›››œœ›šššššššš™™˜˜————••”’Ž‹ŠŠŠŠ‹ŒŒŒŒŒ‹‹‹‹ŒŒ‹‹‹‹‹Œ”—šœœ™–ˆƒ„Ž’’‹‡……Š‘‘‘’‘’“–˜œ ¢¤¦©¬¬¯¯°±²²²³´³³²³³²²±°°°¯¯¯¯¬¬¬¬«««¬¬¬¬ªªªªª©©ªªªªªªªª©©§¥¤£¥¥££¤¦§§¨¨©ª«««¬­­«¦ž™˜——˜——˜˜˜˜™™™™™™™™™—————–”’ŽŒ‹‰ŠŠŒŒŽŽŒŒŒ‹‹Š‹‹‹‹‹‹‹‹ŒŒ“—šœ™™•‘”››—“ŒŠ‰‘’’‘‘’”—›Ÿ¢¤¥¨ª«­¯°°±±²²²²²²²²²²±°¯°¯¯®®¬««««««¬¬­¬«ªªªª©©ªªªªªª©ªª©§¥££££¢££¦§¦¨¨©ªª««­­¬ª£œ˜˜—————˜———˜˜˜—˜˜———–————•’ŒŒŒ‹‹ŒŽŽŒŒŒ‹‹Š‹‹‹ŠŠ‹‹ŠŠŒ‘”™œ›™˜—––—˜˜’‹†…‡Œ‘”“‘’””–˜›ž¡£¤§©ª­®°°±±±±±²²±±±±±°¯®¯®­¬¬«ª«ª«««¬­­¬«ªªªª©ªªªªªª«ª©©¨¦¥¤¤¤£££¤¨©§©¨©ª«««¬¬¬©¡™˜˜———————–––––––––––––—–”Œ‹‹ŒŒŒŒŒŒ‹‹‹‹‹‹Š‹‹‹Š‹‘Ž‘“˜››™˜——˜˜–“Œ…€€†–˜•’’•–––—›ž £¤¦©«­¯°±±±±±±°°±±°±¯¯®­­¬¬«ªªªªªªª«¬­­­¬«««ª«ªªªªªªªª©©§¥¥¦§§¦¦¦¨©©©©©ªª«««¬­¬§¡ž™˜˜—˜————––––––––––––––••“‘‹‹‹‹ŒŽŽŽŒ‹‹Š‹‹‹‹‹‹‹‹ŒŒ‘’”–—šš˜˜˜—˜˜•Š…‚„Ž•—™—––——•””–—™Ÿ¢¤¦©«­¯¯®®®®¯¯¯¯¯¯¯¯­¬¬«ªªªª¨¨¨©©ª©ª¬­­­­­­¬¬«ª©©©ª©ª©©¨§¥¦©©©©©©©©©§¨©ªª««««­«¦Ÿœ˜˜˜———–—–••”””””–•––––••”’‘ŽŒŒŒŽŽŽŒ‹ŠŠŠŠŠ‹ŒŒŒŒŒŒ‘’““‘———™š™˜˜˜˜˜—“ŽŠŠ–››œ›š˜˜–’‘”–˜š¡£¦©«­­­««¬¬¬¬¬¬¬¬¬¬¬«©¨¨§§§¤¤¥¥§¨©©ªª«¬¬­­¬¬¬ª©©ªªªª©ªªª©ª«¬¬«ªªª«©¦§©ªª««ªª«©¤žš˜˜˜˜˜———–•”“““““•––——–••“’‘‘‘Œ‹ŒŒŒŒ‹‰ˆ‰‰‰Š‹ŒŒŒŒŽ‘’“”’—˜™™š˜˜˜˜˜——•‘’—›žžŸž›—–”‘’”•–—›Ÿ£¥¨ª¬¬«ªªª««ªªªªª«ª¨§¦¥£¤££¡     ¡¡¢¦¨©«¬¬¬««©©ªª©ªªª«¬¬­­­­­¬¬¬­­ª§¨©ªª«ªª©¨¦¤žš˜˜˜˜——–––••”””“”•––––••”’’’‘’““ŒŠ‰‰Š‹Š‡…‡ˆ‰ŠŠ‹‹ŒŒŽ‘’’“““˜™š››™™™˜˜™™™™™™œžžœš˜—••————˜›Ÿ£¥§©ªªª©©©©ª©©¨©©©§¥¥¥¤¤¤££¢ Ÿžžœ›¡¥¨ª««ª©¨§§§¨©ª«­¯­®®­®®®¬¬®¯®¬ªªª©©ªª©§¥£¡™˜˜˜–––””––––––••–––––•”“““““’’’Š‡‡‡ˆˆˆ†„†‡‡‰‰Š‹ŒŽ‘’’‘‘‘–™›œœ›š›œœ›žŸŸŸŸŸŸŸžžž›™™šš›ššœž¡¤¥¦§§§¨¨©©¨¨§¦¥¦§¦¥¤¤¤¤¤¤¤£¢¡ Ÿœž¢¤¦©ª«ª¨¥¤£¤¦©«¬®¯­­­­­­¬«¬¯°­««««¨¦§§¦¤¢Ÿš˜———–––•–———˜——–•–•••–•””“”“’‰…„„…†††„„…‡ˆ‰Š‹ŒŽ‘’’’‘Ž–™›œœœœ   ¡¡¢¢££££¢£¢¢ žœœœžžž ¢£¤¥¥¦¦¦¦¦¦¦¦¥¤¤¤¥§¦¦¤¤¤¤¤¤¤££¢¡ŸŸžžž¡£¤¤¦§¨©§¥¤££¨«¬­­¬««««ªªª©«®°¬ª«¬¬¨¤¤£ Ÿœš—————————————™˜—–•••••••“””“‘Œ‹‰†„„…………„‚€„ˆŠŠ‹ŒŽ‘“““‘Ž–˜›žžŸ¡£¤¤¤¥¥¥¥¤¤¤¤¤¤¤£¡  ŸžŸ  ¡¢¢£¤¤¤¥¦¦¥¤¤¤¥§¦¦¦¦§§¦¤¤¤¤¤¤¤¤££¢ ŸŸŸ ¢££¤¥¥§¨§¦¥¤¤§«¬­¬ª¨©©©§§§¨©­­¨§§ªª¨¤¢Ÿ›š™˜———˜˜™˜˜˜™˜˜—˜˜—–••••–•”“““‘Ž‹Šˆ‡‡‡ˆˆˆ†„„‚€†‹Š‹ŒŽ‘“”“’Ž—™œŸ¢¤¤¦¦¦¦¦¥¥¥¥¥¥¥¥¥¥¤¤££¢¡¡¢¡¢¢¢£¤¤¤¤¤¥¥¥¥¥¥§¨¨§¦¦¦¦¥¥¥¥¥¥¥¤¤£¢¢   ¡¢££¤¦¥§©¨¦¦¥¤¤§«««¨¥¥¥¥¤£¢¤¨ª¬¨¢Ÿ¤¥¥£¡Ÿ™————˜˜™™šš™™™™™˜˜˜–––––••”““’’‹‹‹‰ŠŠ‹‹‹‰‡„„ƒ…‰‹ŒŽ‘’“”“‘™›ž ¢¥¦¦¦¦¦¦¥¥¥¥¥¥¤£££¤¥¥¥¤¤££¢¡¡¡¢£££¤¥¥¥¤¥¥¥¥¥¥¥¥¥¥¥¥¦¥¥¥¥¥¥¥¤¤¤£¢¢¢¢¢££¤¥¦¦§¦¦¥¥£££¦§©¦¤££¢¢¡Ÿ¡£¤§§¡››Ÿ¡ Ÿžœ–“”–˜˜™™š›šš™™™™˜˜–––—–•”“’’‘‘Œ‹ŒŒŒ‹‰‡†……ˆ‹Ž‘‘“““’ŽŒŽ› £¤¦§¦¥¦¦¦¥¥¦¥¥¥¥¥¥¥¥¦¦¦¦¦¥¥£¢¡¡¢£££¤¥¥¥¤¥¥¤£££££££¤¤¥¦¦¦¦¦¦¦¦¤¤¤££££££¡¡£¥¥¥¦¥¤£¡  ¡¢¤¤¡ Ÿ ¢¡žžŸ¡ œ—˜›œœ˜Ž‘•˜˜™™š›šš™™˜˜——––––•“’‘ŽŽŒ‹‰‰ˆˆ‰‹‘‘’““““‘Ž‘™ ¢£¤¦¥¥¥¥¥¦¦¦¦¦¦¦¦¦§§¨¨¨©¨§§¥¤££££££¤¤¤¥¤¤¤£¢¢¢¢¢¢£¤¥¥¦¦¦¦¦¦¦¦¥¥¤¤¤¤£££ Ÿ¡¥§¦¦¥¤¡ŸžžžŸ¢ Ÿž¢¡š˜˜™››š˜–˜™›˜“ŒŒ‘–˜™™š›››š™™˜˜——•••”’‘ŒŒ‹ŠŠ‹‹Ž‘‘’““““’’Ž‘˜™œŸ¡£¤¥¤¥¥¦¦¦¦§¦¦§§§¨©©©©ª©©©¨§¦¥¦¦¥¥¥¥¥¥¥¥¤¤¤£££££¤¤¤¦¦¦¦¦¦¦¦¦¦¥¥¤¤¤¤££ ž £¦¦¥¥£¡Ÿžœ›œ  žžž ¡›˜—”–—˜™—˜™˜”Ž‹”˜™ššš›œ›š™™˜———•””’‘ŽŽŽŽ‘‘‘’’’“““’‘‘‘‘–˜›Ÿ¡£¤¤¥¥¦§§§¨§§¨¨¨©ªªªªªª©©ª©¨¨©©ª©¨¦¦¦¦¥¥¥¥££¤¤¤¥¥¥¦¦¦¦¦¦¦¦¦¥¥¥¥¤¤¤¤£  £¤¦¦¦¥¤¢ žœš˜šžžžžžš˜•‘”—˜–——”‘ŒŽ•™™šš™™›œ›™™™—–—•”““‘ŽŽŽŽŽŽ‘‘’’’’’’““““““‘‘‘’“““’”–™œžŸ¡£¤¥¥¦§§¨©¨¨©©©©ª©ª©©©©©©©©©©ªªª©¨¦¦¦¥¤¤¤££¤¤¤¥¥¦¦¦¦¦¦§§§§¦¥¥¥¤¤¤¤¢ £¤¥¦©¨¦¥¤¡žœš™›œ›˜”“•–––”’’—˜™™™˜˜™™™˜˜——––”””’‘ŽŒŽŽ‘‘“”””””“””’’’’‘‘’’““’‘’”˜›¡£¤¥¥¦§§§§¨©ªªªªªªª©©©©©©©©©©©©©§§§¦¥¤£¢£¤£¤¥¥¥¦¦¦¦¦§§¨©¨¨¨§¦¥¤¤¤¤£££¤¥¦¨©¦¥¥¢Ÿ›œœœœœœœš—“Œ’•———–““”–˜™™˜˜——˜˜˜—–––•”””“’‘ŽŽŽŽ‘’“•––––•••”“’’‘‘’’“““’‘’•›ž ¢¤¥¦§§¦¦©ªªªªªªªª©©©©©©©©©©¨¨¨§¦¥¥¥¤¢¢£¤¤¥¥¥¦¨¨¨¨¨¨©©©©¨©¨§¥¥¥¥¤¤¤¥¥¥¥¦¨§¦¦¥¢Ÿžžœœœœ›™–“•—˜˜˜˜—•–˜™™˜˜—––———–•–•”“““’‘‘‘‘ŽŽŽ‘’’’”•––——––••“’‘‘‘‘’’““““’‘‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒ‚‚‚‚‚‚‚ƒƒƒ‚‚‚‚‚ƒ‚‚‚ƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒƒ‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚€€€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚~~~~~~~~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€~~}}~~€€€€€€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚~~~}}}~~~~~~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚~{z{|}~‚€~~~~}}}~~~~~~€‚‚‚‚‚‚‚‚‚‚‚~{yyz|}€€~~~}{||}~}~€€‚‚‚‚‚‚‚‚‚‚‚}}yxyz{|~€€~}|z{{|}}}~~~~~€€€€€‚‚‚‚‚‚‚‚‚}{xwyz{}~€‚‚‚€}}|zxyz{|||}}}}~}~~€€€€€€€€€€€€‚‚ƒ‚‚}zxwzz{}~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€}|||zyyyz{{|||||}}}}~~~€€€€€€€€€€‚‚‚‚‚‚‚|zxxzz{|~€‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚€~|||zyyyzz{|{{||||||}}}}}~~~~€€‚€‚‚€}zyyzz|~~~‚‚€€€€‚‚‚‚€~}}|zxxxzz{{|{{||||{|||}}|||}~~~€‚€€€‚ƒ‚~zyyzz|~~€‚€€€€‚‚‚‚‚€}}}|zxxyzzz{zzzz{{{z{|{|||||}~~~€‚ƒ‚~zyyzz|~€€€‚€€€€€‚‚‚~|||{zyyzzzzz{z{{z{{zzzz{||||~~€‚ƒ„ƒƒƒ‚{yzzz|}€€€‚€€€€€€€€‚‚‚€}|||{yzzzzzzyzzzzz{{{zzzz|}}~€€~}}~‚ƒ„…„ƒƒ‚|{zzz{}~€‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€‚‚€€~}|{zzzzzzzzzzzzzzz{||{{{|}}€€€~}|~ƒƒ„„„„„‚|{zzz|}~€‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€~~~€€€€~|zzzzzzzzzzyz{zz{||{{{|}}€€€~~}~ƒ‚„…„„ƒ‚|zzzz{}€€€~€€€~~~€€€€€€}{zzzzzzz{{{{z{{{||||||~€€€€~~~€‚ƒƒ„„„„‚{yyzyz}‚‚€€€€~~~€~~~~€€€€€~|zzzzz{{z{{zz{{||||}}}‚€€€‚‚ƒ„„ƒ‚~ywxyxy|~‚‚€€€€~~~~~€€~~€€€€€}}{yzzzz{|z{z{|{{||}|}~~€€€‚‚ƒƒƒ„‚}zxxxxy{}~‚€€€€~~~}}~€€€~€€€~}}{zzzzy{|z{z{||||}~~~}~€€€‚ƒƒƒƒƒ‚‚€}zxxxyy{}~€‚‚‚‚‚‚‚€€€~€€€€€€~}|zzyzzz{|{{{|||{{|}}~}}~~€‚‚‚€~|{xxxyy{|}~€€‚‚‚‚‚‚‚‚‚€€€~~€€€‚€€€€~}|zzzzzy{|}}~~||{{{{|}}}}|}~~~€€}|{||zyyyy{||~€€~~€€€‚€~}{|zzzzz{}~}|{zzz{||}}|}~~~~€€~}|||{zxyyy{|}~€€~~€€€€~}||{zzzzzz|~}|{yyz{|}|{|}~~~~~~}}~€€~}}||{zxxyy{|}~€€€~}~€‚‚‚‚€~~}||{{{zzzzz|~~}{yyzz{|{zz|}}~}}|}|}€}|{zyyxxyz|~~€€€‚‚‚~}||{{{zyzz{}}{zyzz{|{yz{||}}}|}|}~~~~|zzzxxwwyz||}‚‚‚‚‚‚‚‚‚‚‚‚€€€‚€€~}|||{{|zzz|}~€€~{zyzzz{zxyz{|||||||}}}}}~}{zzzyywwxxz||‚‚‚‚‚‚‚‚‚€€€€€€€€~}|||{|||{{{}~€|zzzyz{zyyz{{{||||||}}}|}||{zzzzyxxwxz{{‚‚‚‚‚€€€€}~}|||}||||}~~€€€€€€}{||{{{|{{||||||}|}|}}}{||{{zzzzzyywwxzz‚‚‚‚‚‚€€€~}||{|}~}}}€€€€€€€}{{||{|||||||||||}|{{|{{z{{{{{{{{yyxwxyy‚‚‚‚‚‚‚}|{{{}~€€€€€€€€€~|||||||||||||||{{|{{{{{z{|||||{|zzyxyyy‚‚‚‚‚‚‚‚€}|{{|}~~€€€€€€€€~}}}}}~~~~}}||||z{{zzz{zz{{|||||{zzyyxyz‚‚‚‚€€‚‚‚‚‚€~}|||}}~‚€€€€€€~}~}~}}€€|{{{zzyzzzzz{z{|{||{{zzzzyyz‚‚€€€€€€€€€€}}|~~~~€€€€€€~~~~}~€€|{{zzzyyzzzzz{{{|}|||{{{zzyz€€€€€~~}}}~~}}|~~€‚‚‚‚‚€€€€€€‚‚}|||{zyyyzzz{{z|}}}}||{{|{z|€€€€€€~~~}|||||}~}}}}~€‚‚‚‚‚‚‚‚‚‚€€‚‚‚}}}|{yxxxyz{{z||}~~}}|||{{{€€€€€€€€~~~~}||{{{|}}|~ƒ‚‚‚‚‚‚‚‚‚‚€€}€€‚‚‚}}}|{yxwwyyzzz||}~~~~}|{{{{€€€€€€~~~~~~~~~||{{||||}‚ƒ„„„‚‚‚ƒƒƒ€€€~~~€‚~~}}||ywwxyyzzz{|||}}}}{{{zz€€€€€}~~~}~~}}|||{|||||€‚…†…ƒ€€‚‚‚‚‚ƒ‚~}}~}~‚‚€~}}|{ywwxyyzzzzz|||}||{{{{z€€€~~~~~~~}}||{zz{{{}€‚„…„ƒƒ‚ƒƒƒƒƒƒ‚‚€€€~~~~~~€‚€~}|{zywxxyyzzzzz{{{|{{{{{{z€€€€€~~~~~~}||{{zz{|}€‚ƒ„ƒƒƒ‚ƒ„„„„„ƒ€€€€€~}}|zyyyyyzz{||{{{{{{{{{{z{€€€€€€€€€~~~~}||{zzz{{|‚‚‚‚‚‚‚ƒ„„„ƒƒ€~}~€€€€~}|zyyyyyzz{{{{{{{{{{{z{{z~~€€€€€€€€~~}}|zyz{|}€‚‚‚ƒƒƒ„„„ƒ~}~~~~€€~}|{zyyyyzz{{{{{{{{{{{z{{z|}}~€€€€€€€€~~}|{zy{|}}~€‚‚ƒƒƒƒƒƒ‚}}}}~}}}}~~~€€~~}{{{zz{{{|{{{z{{{{{{{{zz{|}~~~€€€€~|{zzz{|~~~€€‚‚‚‚‚‚‚‚‚€~}}}}||||~~~~€€~~~~}||||{{{{{{{zz{{{z{{{zzz{||}}~~||{{||}~€€‚‚‚€~||||||||~~~€~~~~~}}}|||{{{{||{{{{{{{{zzzz{||}}~~~~}}}}}€‚‚‚‚‚‚‚‚‚‚‚‚‚‚€~}||||}|}}~~~~~~~~~~~}}|}|||||||{|||{{zzzz{||}~~~€€€€€€€€~~‚ƒƒ‚‚‚‚‚‚‚‚~}|{{z|}~~}}~~~~~~~}}}~~}}||}}|||||||zzyz{{|||||€€€€‚ƒƒƒƒ‚‚‚€‚‚‚~}|zyy|}~~}}}~~~~}}}~~~}}|}}||}|}}~zzzyzz||z{|~€€€‚ƒ‚‚‚‚‚ƒ„ƒƒƒƒƒƒ‚€‚‚ƒ}|{zz|}~~~}}}~~~}~~}}}}}~~~~~~}~~}}}~~~~zzzzzz{{{{|~€€€‚‚‚ƒƒƒ‚‚ƒƒ‚ƒƒ‚ƒƒ‚‚‚‚‚‚‚€~}|{{|}€€}}}~~~}}~~~~}}}~~~~~}~}}}|||}}}|zzyyzzzz{|}€€€‚‚‚‚ƒƒ‚ƒƒƒ‚‚ƒ„ƒ„ƒ‚‚‚‚‚‚‚‚~}||}€€~~}~~~~}}~~~~~~~}}}}}||{{|{{{{zzzzzzzz{||}€€€‚ƒ‚‚ƒƒ‚ƒ‚ƒ„„ƒƒ‚‚‚ƒƒƒ‚‚‚}}~~€€€€~~~~~~~~~}}}}}}|}|{{|{{{zyzzzzzzz{|}~€€€‚‚‚‚ƒƒƒ‚ƒ„„„ƒƒƒƒ‚‚‚‚‚‚‚~€€€€€€~~~~€}}||}}}}|{z{zz{ywzyzz{zz{}}€€€€‚‚‚ƒƒƒƒ‚ƒƒƒƒƒƒƒƒ‚‚‚‚‚‚ƒ‚€€€€€~~~~~~~}{{{||{||zz{zzzyyzyyz{{{|~~€€€€€‚ƒƒ„„„„ƒƒƒ‚‚‚‚‚ƒ„ƒ„ƒ‚„„ƒ‚€€~}~~~~}|{{{{{{{{{zzzzzyzzzzzzz{}€€€€€€‚‚‚ƒƒƒƒƒƒ‚‚‚‚‚‚ƒƒƒƒƒ‚ƒƒ‚‚‚€‚‚‚€~~~~~~~~~~~~~}||{{{{{z{z{{zzzzzz{{||{{|}€€€€€€€‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚ƒƒƒ‚‚‚‚ƒ‚€~~~~~~~~~~~~~}}}|{{{|||{z{|{zzzzz|}|||{{}€~€‚‚ƒƒƒƒƒƒƒ„ƒƒƒƒƒƒƒ„„ƒƒ‚‚€€€€~~~~€€€€}}}}||{{|{zz{{zzzzzz|}}}||{|}~}~~~~€ƒ„ƒ‚‚‚‚ƒƒƒƒ‚ƒƒƒƒ„„ƒƒƒ‚€~~~€€€€€€}}}}|{z{{{z{{{{|{{{{|}}}}|||}}}}}}}‚ƒ„ƒ‚‚‚‚ƒƒƒƒ‚‚ƒƒ‚‚‚‚‚ƒƒ‚‚‚~~€€€€€€€€€€€}}}}||zz{{y{||}||{{{}~~}}}{|}}}}}}}~‚ƒƒƒƒƒƒ‚ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€€€}}}}|{{z{{{|{||||{|{}}||||}}|{||}€€‚‚‚‚‚€€€€‚‚€€€€€‚‚€€~~~~~~}{|||{{{{|{{{{zz||{{z~~}||}}}}{|||‚‚‚€€€‚€€€€€‚‚€€€~~~~}}}|z{{|{{zzzzzzzyy||{zz~~}}~~~}||{|€€‚€€€€€~~€€€}~~~}}}|z{|||{zzz{{zzyz{zzzz€~~||}~€€€~}}}~~~~~~€‚‚€€~}}~}}}}|{{|}|{{{{{{zzyzzz{{{€€€~~~~~~}~~}}}}}}}}}}}}}}}}}}~~€€€€~~~|}}}}}|||}|{z{{{{{zzzz{{{{€€€€€~~~~~}}}}}~~~}}}}~}}}}}}}}}}}}}}}}}}}~€€€€€~~~~~~}}}}}}}|||||{{{{{z{|{zzz{z{€€€€€€€~~~~}||||}}}|}|||}}}}}}}||||||||||||}~}}}}~~}|||}||{{||{{|{{{{{{zz{{{{{~€€€€€€~~~~}|}~~}|}|}}}||}|||||||||||||||}}}}}}||}~~~}|}}}}||{|{{{{||{|{{{z{{{{z~€€€€€}}~~}}|}}}}}}}}}}}}}}}}}||||||||||||}}~~~~}}}}}}}}}|zzz||||||||{{{||~€€€€€€€~~~}}~}}}}}}}||||}}}}}}}}}|||||{{{{}|||}}~~~}}}}}}}}}}{zzz{{{|{||{{{{{{~~~~~~~~~~~~~~~~}|||~~~}~~}|}}||}}}}}}}}}}}}}}~~~~~~€€€€€€€€~~~~~~~~~~~~~~~~~~~~~~~~~}|||}~~~€€€€€€~~~~}~~}}~~~~~~~}~~~~~~~~€€€€€€€€€€€€~~~~~~~~~~~~~~~~~~~~~~}}}||||}~~€€€€€€€€~~~}}}}}}}}}}}}}}}}~~~~~€€€‚‚‚‚€€~~~~~~~~~~~~~~~~~~~~~}}}||||}~~~€€€€€€€}}}}}}}}}}}}}}}~~~~~ƒ„„„‚‚‚‚€~~~~~~~~~~~~~~~~~~~~}}}||||}~~~€€€€‚‚€€~~~~€€~~~€‚‚ƒ…„„ƒƒ‚‚€€€~~~~~~~~~~~~~~~~~~~~}}}}}}}~}}}€€‚ƒƒƒƒ‚‚‚€€€€€€€€€€€€€€€€€€€~~€‚‚‚„„……„„‚€€€€~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~€€€ƒ„„…„‚‚‚‚€€€€€€€€€€€€€€‚‚‚ƒ…††…„„‚€€~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~€€€€‚„………„ƒƒƒ€€€‚‚‚‚€€€‚€€€‚‚‚ƒ‚‚‚ƒ…††………ƒ€€~~~~~~~~~~~~~~~~~~}~~~~~~€€‚„………„„„ƒ‚‚ƒƒ‚‚‚‚‚ƒƒƒ‚‚‚ƒƒƒƒƒƒ‚‚ƒƒ……†…„„‚€~~~~~~~~~~~~€‚‚€€~~~~~~~€‚ƒ„………………ƒƒ„„ƒ„„„ƒƒ„„„„…„„„‚‚€€‚ƒƒƒƒƒƒƒƒƒƒ„………„„‚‚€~~~~~~~~~~~~~€„…††…„ƒ‚€~~~~~€‚‚ƒ„………………„„„„„…„„……„…„……„……„‚‚€€ƒ„ƒƒ„„„„ƒƒƒ…………„ƒ‚€~~~~~~~~~~~~‚„†‡‡‡†…„„‚€~~~€‚‚‚„……………………………………………„„„…„„„„‚‚€€‚ƒƒƒ„……„…ƒƒ„……„„‚‚€~~~~~~~~~€‚…‡ˆˆˆ‡†…†„‚€€€€‚‚ƒ„…………„…………………………………………„„„„‚‚‚‚ƒƒ„„ƒ„………„ƒƒ„……„„‚‚€€~~~~~~~~~€ƒ…‡ˆ‰ˆ‡††††„‚€€‚‚ƒ……„„………………………………………………………„ƒ‚‚„„„……„„„ƒ„„„„ƒƒ„………„‚‚€€~~~~~~~~~~€ƒ†ˆ‰‰ˆ‡……………„ƒ‚‚ƒ‚„…„„…………………………………„„……„ƒƒ‚‚‚‚ƒ„„„……„„„„„„„„ƒ‚„………„‚‚‚€~~~~~~~~~~€ƒˆˆˆˆˆ‡†……††…„ƒ€€‚ƒ…………„„„„…………………„„………„‚‚‚‚‚‚ƒ„…………„„„„„„„„ƒƒ„………„‚‚ƒ‚~~~~~~~~~€„ˆ‰ˆˆˆ‡††…†††„ƒ‚€€‚ƒ………„„„„„……………„„ƒƒ„……„ƒ‚‚‚‚‚ƒ„……„„„„„„„„„„„…†‡†……ƒ‚€~~~~~~~~~~€…ˆ‰‰ˆˆ‡‡………………ƒ‚‚‚ƒ„……„„„„…………………ƒƒ‚‚ƒ„„„‚‚‚‚‚‚ƒ„„„„„„„„„„„„„„…†‡†……ƒ~~~~~~~~~~~~€„‡‰Š‰‰ˆˆ††…………ƒ‚‚‚ƒ„„………„„……………………ƒƒ‚‚ƒ„…„ƒ‚‚‚‚‚ƒ„„„„„„„„„„„„„„††‡†……„‚€~~~~~~~~~~€ƒ‡‰‰Š‰ˆˆ††…………„ƒ‚‚„……††„……„……………………ƒƒƒƒƒ……ƒƒ‚‚‚‚‚„„„„„„„„„„„„„……††‡††…„ƒ~~~~~~~~~~€„‡‰‰‰‰ˆˆ‡†…………„„‚‚ƒ„…†…„„„„…………………„„„„„„„…„ƒ‚‚‚ƒƒ„„„„„„……………………††…………ƒ‚€€~~~~~~~~~~€„‰‰‰‰ˆˆˆ†……„„„…„‚ƒ„…………„„„……„„„„…„………„„…„ƒ‚ƒƒ‚‚ƒƒƒ„…………………„…………………„ƒ‚€€~~~~~~~~~~€„‰ŠŠ‰‰‰ˆ………„ƒ„„ƒ‚‚ƒ„„„„„„„…„ƒƒ‚ƒ„„………„…„„ƒƒƒ„ƒƒ‚ƒƒƒ„„…………††……„…†††……„ƒ€~~~~~~~~~€„ˆŠ‰ˆ‰‰†„„„‚‚‚‚€‚ƒ„ƒ„………„…„ƒƒ„„………………„„……„„ƒƒ„„„„„„…………†………„…†…………„‚‚€~~~~~~~~~€€‚„‡‡†††…ƒ‚‚€€€€€ƒƒƒ„„„…„……„‚€€‚„……………†††††……„……………………………………„„…„††…††„‚‚ƒ~~~~~~~~~~€‚†…„„„„ƒ‚€€€€€‚ƒ„ƒ„„……………ƒ€€ƒ…„………‡‡†‡†††††……††……„„„„„„„„„„…††…††…ƒƒ„~~~~~~~~~~€ƒ„ƒƒƒƒ‚‚‚€€€‚ƒƒƒƒƒ„„…„……ƒ‚€€€‚ƒ„………†‡‡†‡‡†………†……„„„„„„„„…„………†………††…ƒƒ„~~~~~~~~~~€‚ƒƒƒƒƒ‚‚‚ƒƒƒƒ„„…………„€€‚„………†‡‡††††………††……………………„……………†……††††…„„~~~~~~~~~~€ƒ„„„ƒ‚€€€€‚‚‚ƒƒ„…„……„…„„‚€€‚„……„………………………†………†…††††…„„„„……………††‡‡†……~~~~~~~~~€€‚„„„ƒ‚€€€‚‚‚ƒƒ„…„……„…„‚€€€€‚ƒ„„„„…„„„„„„„…††……††††††……ƒ„„…………†††‡………~~~~~~~~~€€€„„„ƒƒ‚‚ƒ„ƒ……„…„„„ƒ€€€‚„„……………………„……††……†‡‡‡‡…„…ƒƒƒ„……………††………~~~~~~~~~~ƒ„„„ƒ€€‚ƒƒƒ„„……„„…„‚‚€€€€‚‚ƒ„„„„„ƒƒƒ„……††„…††††…………„ƒƒ„„………††††††~~~~~~€€€€‚„„„‚€€ƒ„„„„…„„„„„‚‚‚€€€€‚‚‚ƒƒƒƒƒƒ……†…„†‡†…„„………„ƒ‚ƒƒƒƒ„……††…†~~~~~€€€€‚‚ƒ‚‚ƒ„‚€€ƒ………„„„„„„ƒ‚€€€€‚‚ƒƒ„„ƒ‚ƒ††………†‡†…„„„………ƒ‚‚ƒƒƒ„……………†~~~~~€€€‚ƒƒƒ‚‚ƒƒ€‚„……†„‚‚‚‚‚€€€ƒ„„„……„„‚‚‚„†‡††…†††……„„………ƒƒƒƒƒƒƒ„„„„…†~~~~~‚‚ƒƒƒƒ‚‚ƒ„„‚ƒ‚ƒ„………‚€€€~~€‚ƒ„„„„ƒƒ‚‚‚ƒ…††……†††††…„…†…„„ƒƒƒ‚‚ƒƒƒ„„…~~~~€‚„„„ƒ‚‚‚ƒƒ……„„„„ƒƒ„‚€€€~€~~~€€€€€€ƒƒƒƒƒƒƒƒ‚ƒ………………††‡‡†……††…„ƒƒƒ‚‚ƒƒ„„…~~~~~ƒ„…„‚‚‚ƒƒ„…„…„…„ƒƒ€€€€€€~~€€€€€€‚ƒƒƒƒ‚ƒ„‚‚‚ƒ……†††††‡‡†………………„„„ƒ‚‚ƒ„………~~~~ƒ„„ƒ€‚ƒƒƒƒ„…„……„ƒ‚~€‚‚ƒ‚€€€‚‚ƒ‚‚ƒƒƒ€€‚„………†‡‡†‡†„„„„……„…„‚‚‚„„………€~~~€‚€ƒƒƒƒ„……†…„‚~~€‚ƒ„„ƒ€€‚‚‚‚ƒ‚‚‚‚€‚„………†‡‡‡‡†…„„„„„„„ƒ‚‚ƒ„„………€€€€€€€€ƒƒƒ…„…†††…ƒ€‚ƒ‚‚ƒƒ€‚‚‚ƒ‚‚‚‚‚€‚„„…†††‡‡‡†…„……„ƒƒ„ƒƒƒƒ„…………‚‚€€€€€€€€€€€€ƒ‚ƒ………†††…„‚€€‚‚‚‚‚€€€‚‚ƒ‚‚‚‚‚ƒƒ‚‚‚‚‚‚ƒ„……††††††…„…†„ƒƒƒƒƒƒƒ……………ƒƒ‚€€€€€€€€€€€‚‚‚ƒ„…††††…„‚‚‚‚€€€‚„„„„ƒƒƒƒƒƒ‚‚‚‚‚‚‚‚‚ƒ„„…†††††…„…†„ƒƒƒƒ‚ƒƒ„„„„……„ƒ‚€€€€€€ƒ‚ƒ…††††……„ƒƒ‚€‚€€€‚ƒ…„„ƒƒƒ„ƒƒ‚ƒ‚ƒƒ‚‚‚ƒƒ‚ƒƒ„††……„„……………„„…ƒƒƒƒ„„„„…†…ƒ‚‚‚€~€€€‚‚„……†………„ƒƒ‚€€€€‚€€‚ƒ„„…„„„ƒƒ‚‚‚‚‚‚‚‚‚‚‚‚‚„„ƒ„ƒ„………………„…„ƒƒƒƒƒ„…††…„ƒƒƒ‚€€‚ƒ„„………„€€€€€€€€ƒ………………„ƒ‚€€€€€€€€‚‚ƒ„…„……„„…„ƒƒƒƒƒ„…†…†……„„ƒ‚€€€ƒƒ„„…~~~~~~€€‚„†ˆˆˆ‡††…‚€€€€€€€‚ƒƒƒ„„„„„„ƒƒƒƒƒ„…††…†……„ƒ‚‚€€€€€€€€ƒƒ‚~}}}}}}}€~}€‚†ˆŠ‹‹ŠŠ‰†ƒ€€€€€€€€€€€€€‚‚ƒ…ƒƒ‚‚ƒƒƒƒƒ††††………„„ƒ‚€€€€€€}|}|}}}~~}}~€‚‡Š‹ŒŒŒŠ†ƒ€€€€€‚‚‚‚‚€€€€‚ƒ„ƒ‚‚‚ƒƒƒƒƒ†††††……„……ƒ€€€€~}|}€€}|||||}~~~}}~‚‡Š‹ŒŒŠ†ƒ€€€€€‚ƒ‚ƒƒƒ‚€€€€€€ƒƒƒ‚‚ƒƒƒƒƒƒ††††††…………ƒ‚€€€€~|{|}}}|||{|}|~~~~}}}~€‚…‰‹ŒŒ‹‡ƒ‚€€€€‚‚‚‚‚‚€€€€‚ƒƒ‚‚‚ƒƒƒƒƒ†††…†……†…„ƒ€€€€~}|}}}}|{{{}~~~}}}}}~‚„‡ŠŠ‡„€€€€€€‚‚€€€€€€‚‚‚ƒƒƒƒ„ƒƒƒƒƒ††…††…………„ƒ€€~}~}}}}|||}~~~}}}}}~€€…ˆŒŒˆ…‚€€€€€€€€€€€€€‚ƒ‚‚ƒƒ„„„ƒƒ„„ƒ††††††………ƒ‚€€~}}}}}}||{}€~}}}}~~€€‚…ˆ‰‡„€€€€€€‚ƒƒƒƒ‚„…„„ƒ„„……††††…„„……ƒ‚€€€€}}~~~}}}|}~}}~~~~~€‚„‡ˆ†„€€€€€€€€€€€€€€€€€€€€€€‚‚ƒ„ƒ„………„„…………†††…„ƒƒƒ„‚€€€~~~~}~~~}}}}~~}}~~~~~…‡ˆˆ‡„‚€€€€€€€€€€€€€€€€‚ƒ„„……………………„„…………„ƒƒƒƒ€€€€€~~~~}}}}}}}}}}}}}~~~~‚†ˆˆˆ‡†ƒƒ„‚€€€€€€€€€€€€€€‚ƒ‚‚‚ƒƒ„……………„„„„……………„ƒ„‚€€€€€~~}}}}}}}}}}}}}~~„†‡‡‡†…„„„‚€€€€€€€€€€€‚‚‚‚‚‚ƒ„„…………„„„……ƒƒƒ„„ƒ„ƒ‚~~}~}}}}}}}}}}||}~€€‚ƒ……„………„„‚€€€€€€€€€€€€€€‚ƒ‚‚ƒ„„„„……†…„„……ƒ‚ƒƒƒƒ‚€€~€~~}}~~~~}|||||}}~~~‚ƒƒƒƒ„„„ƒ‚€€€€€€€€€€€€€€‚ƒ‚ƒƒ„„„……………………ƒ‚‚‚‚€€~~}}~}}~}|||}}}~~~}}}~€€‚‚€€€€€€€€€€€€€€‚ƒ‚‚ƒ„„„„…„„…†……„‚‚ƒƒƒ€€~~~~}}}~~}~~}}~~~~~~€€€€€€€€€€€€€€€€€€€‚ƒ‚‚‚‚„…„„„„…………††…‚€€‚‚‚‚€€‚€€€€~~~~~~~~~~€€~~~€€€€€€€‚‚ƒƒ„………„„………………………€€‚€€‚‚€€€€~~~~~~~~~~€€~~~€€€€€€€‚ƒƒ„……„„„„……„„……………€€‚‚‚‚€€€€€€€€€€€€€~~~~~€€€€‚ƒƒ„„…………………„„…………„€€€ƒ‚‚‚€€€€€€€€€€€€€€€€€€€€€€€€€~€€€€‚ƒ‚‚ƒƒ…………„……„„………„‚€€€€€‚€€€€€€€€€€€€€€€€€€€€€€€€‚‚‚‚‚‚„………………„…………„€€€€€€€€‚€€€€€€€€€€€€€€€€€€€€€€€€€€‚‚ƒ‚‚ƒ„……………………„……„€€‚ƒƒƒ‚ƒ€€€€€€€€€€€€€€€€€€€€€€€€€€€‚‚‚‚‚‚‚ƒ„…………………„ƒ„„„‚€€€€‚‚‚‚‚‚€€€€€€€€€€‚‚‚‚€€€ƒ„ƒƒ„……„„„„„ƒƒ„ƒƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚ƒƒƒƒƒ‚ƒƒƒ‚ƒƒƒƒ„„„„„ƒƒƒƒƒƒƒƒƒ‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€‚‚‚‚‚‚‚ƒ‚‚‚‚‚‚‚‚ƒƒƒƒƒƒ„ƒƒƒƒƒƒƒƒƒ
\ No newline at end of file
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.zip b/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.zip
new file mode 100644
index 0000000..4ac288b
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/leo_qcif.zip
Binary files differ
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/manifest.json b/tools/avm_analyzer/avm_analyzer_app/assets/manifest.json
new file mode 100644
index 0000000..926727b
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/manifest.json
@@ -0,0 +1,32 @@
+{
+  "name": "AVM Anaylzer",
+  "short_name": "avm-analyzer",
+  "icons": [
+    {
+      "src": "/icon-192x192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    },
+    {
+        "src": "/icon-256x256.png",
+        "sizes": "256x256",
+        "type": "image/png"
+    },
+    {
+        "src": "/icon-384x384.png",
+        "sizes": "384x384",
+        "type": "image/png"
+    },
+    {
+        "src": "/icon-512x512.png",
+        "sizes": "512x512",
+        "type": "image/png"
+    }
+  ],
+  "lang": "en-US",
+  "id": "/index.html",
+  "start_url": "./index.html",
+  "display": "standalone",
+  "background_color": "white",
+  "theme_color": "white"
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/assets/sw.js b/tools/avm_analyzer/avm_analyzer_app/assets/sw.js
new file mode 100644
index 0000000..ee7bf78
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/assets/sw.js
@@ -0,0 +1,25 @@
+var cacheName = 'avm-analyzer';
+var filesToCache = [
+  './',
+  './index.html',
+  './avm_analyzer.js',
+  './avm_analyzer.wasm',
+];
+
+/* Start the service worker and cache all of the app's content */
+self.addEventListener('install', function (e) {
+  e.waitUntil(
+    caches.open(cacheName).then(function (cache) {
+      return cache.addAll(filesToCache);
+    })
+  );
+});
+
+/* Serve cached content when offline */
+self.addEventListener('fetch', function (e) {
+  e.respondWith(
+    caches.match(e.request).then(function (response) {
+      return response || fetch(e.request);
+    })
+  );
+});
diff --git a/tools/avm_analyzer/avm_analyzer_app/index.html b/tools/avm_analyzer/avm_analyzer_app/index.html
new file mode 100644
index 0000000..d7d7dd7
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/index.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<!-- Forked from: https://github.com/emilk/eframe_template -->
+<html>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+<!-- Disable zooming: -->
+<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+
+<head>
+    <title>AVM Analyzer</title>
+
+    <link data-trunk rel="rust" data-wasm-opt="2" />
+    <base data-trunk-public-url />
+
+    <link data-trunk rel="icon" href="assets/favicon.ico">
+    <link data-trunk rel="copy-file" href="assets/sw.js" />
+    <link data-trunk rel="copy-file" href="assets/manifest.json" />
+    <link data-trunk rel="copy-file" href="assets/icon-192x192.png" />
+    <link data-trunk rel="copy-file" href="assets/icon-256x256.png" />
+    <link data-trunk rel="copy-file" href="assets/icon-384x384.png" />
+    <link data-trunk rel="copy-file" href="assets/icon-512x512.png" />
+    <link rel="manifest" href="manifest.json">
+    <link rel="apple-touch-icon" href="icon_ios_touch_192.png">
+    <meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
+    <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040">
+
+    <style>
+        html {
+            /* Remove touch delay: */
+            touch-action: manipulation;
+        }
+
+        body {
+            background: #909090;
+        }
+
+        @media (prefers-color-scheme: dark) {
+            body {
+                background: #404040;
+            }
+        }
+
+        /* Allow canvas to fill entire web page: */
+        html,
+        body {
+            overflow: hidden;
+            margin: 0 !important;
+            padding: 0 !important;
+            height: 100%;
+            width: 100%;
+        }
+
+        /* Position canvas in center-top: */
+        canvas {
+            margin-right: auto;
+            margin-left: auto;
+            display: block;
+            position: absolute;
+            top: 0%;
+            left: 50%;
+            transform: translate(-50%, 0%);
+        }
+
+        .centered {
+            margin-right: auto;
+            margin-left: auto;
+            display: block;
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            color: #f0f0f0;
+            font-size: 24px;
+            font-family: Ubuntu-Light, Helvetica, sans-serif;
+            text-align: center;
+        }
+
+        /* ---------------------------------------------- */
+        /* Loading animation from https://loading.io/css/ */
+        .lds-dual-ring {
+            display: inline-block;
+            width: 24px;
+            height: 24px;
+        }
+
+        .lds-dual-ring:after {
+            content: " ";
+            display: block;
+            width: 24px;
+            height: 24px;
+            margin: 0px;
+            border-radius: 50%;
+            border: 3px solid #fff;
+            border-color: #fff transparent #fff transparent;
+            animation: lds-dual-ring 1.2s linear infinite;
+        }
+
+        @keyframes lds-dual-ring {
+            0% {
+                transform: rotate(0deg);
+            }
+
+            100% {
+                transform: rotate(360deg);
+            }
+        }
+    </style>
+</head>
+
+<body>
+    <canvas id="avm_analyzer_canvas_id"></canvas>
+    <input type="file" id="local-stream-input" style="display: none;" />
+</body>
+
+</html>
\ No newline at end of file
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/app.rs b/tools/avm_analyzer/avm_analyzer_app/src/app.rs
new file mode 100644
index 0000000..1040b33
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/app.rs
@@ -0,0 +1,301 @@
+use eframe::Storage;
+use egui::{CentralPanel, TopBottomPanel};
+use egui_dock::NodeIndex;
+use egui_dock::{DockArea, DockState};
+
+use log::{info, warn};
+use wasm_bindgen::JsValue;
+use web_time::{Duration, Instant};
+
+use crate::app_state::{AppState, STATE_URL_QUERY_PARAM_NAME};
+use crate::stream::{ChangeFrame, CurrentFrame, HTTP_POLL_PERIOD_SECONDS};
+use crate::views::{
+    handle_drag_and_drop, BlockInfoViewer, CoeffsViewer, ControlsViewer, DecodeProgressViewer, DetailedPixelViewer,
+    FrameInfoViewer, FrameSelectViewer, FrameViewer, MenuBar, PerformanceViewer, RenderView, SelectedObjectKind,
+    SettingsViewer, StatsViewer, StreamSelectViewer, SymbolInfoViewer,
+};
+
+type TabType = Box<dyn RenderView>;
+
+pub struct AvmAnalyzerApp {
+    state: AppState,
+    dock_state: DockState<TabType>,
+}
+
+const SAVED_SETTINGS_KEY: &str = "settings";
+impl AvmAnalyzerApp {
+    pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
+        let mut saved_settings = None;
+        if let Some(storage) = cc.storage {
+            if let Some(settings) = storage.get_string(SAVED_SETTINGS_KEY) {
+                saved_settings = Some(settings);
+            }
+        }
+        let mut dock_state: DockState<TabType> = DockState::new(vec![Box::new(FrameViewer)]);
+        let surface = dock_state.main_surface_mut();
+
+        let [others_view, _frames_view] =
+            surface.split_above(NodeIndex::root(), 0.15, vec![Box::new(FrameSelectViewer)]);
+        let [yuv_view, _info_view] = surface.split_left(
+            others_view,
+            0.2,
+            vec![
+                Box::new(FrameInfoViewer),
+                Box::new(BlockInfoViewer),
+                Box::new(SymbolInfoViewer),
+                Box::new(StatsViewer),
+            ],
+        );
+
+        let [_, _] = surface.split_below(yuv_view, 0.8, vec![Box::new(ControlsViewer)]);
+        let state = AppState::new(saved_settings);
+
+        Self { state, dock_state }
+    }
+
+    pub fn check_keyboard_input(&mut self, ctx: &egui::Context) {
+        if let Some(stream) = self.state.stream.as_mut() {
+            if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft)) {
+                stream.change_frame(ChangeFrame::prev().order(self.state.settings.persistent.frame_sort_order));
+            } else if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight)) {
+                stream.change_frame(ChangeFrame::next().order(self.state.settings.persistent.frame_sort_order));
+            }
+        }
+    }
+
+    pub fn check_playback(&mut self) {
+        let playback = &mut self.state.settings.playback;
+        if playback.playback_running {
+            if let Some(stream) = self.state.stream.as_mut() {
+                let current_time = Instant::now();
+                let elapsed = current_time - playback.current_frame_display_instant;
+                let time_between_frames = Duration::from_secs_f32(1.0 / playback.playback_fps);
+                if elapsed > time_between_frames {
+                    playback.current_frame_display_instant = current_time;
+                    let changed = stream.change_frame(
+                        ChangeFrame::next()
+                            .order(self.state.settings.persistent.frame_sort_order)
+                            .allow_loop(playback.playback_loop)
+                            .loaded_only(playback.show_loaded_frames_only),
+                    );
+                    if !changed {
+                        playback.playback_running = false;
+                    }
+                }
+            }
+        }
+    }
+}
+
+impl eframe::App for AvmAnalyzerApp {
+    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
+        self.state.check_stream_events();
+        self.state
+            .server_decode_manager
+            .check_progress(&mut self.state.stream, &mut self.state.http_stream_manager, &self.state.settings.sharable.streams_url);
+        self.check_keyboard_input(ctx);
+        self.check_playback();
+        handle_drag_and_drop(ctx, &mut self.state);
+
+        if let Some(stream) = self.state.stream.as_mut() {
+            stream.check_promises();
+        }
+
+        ctx.input(|input| {
+            self.state
+                .performance_history
+                .on_new_frame(input.time, frame.info().cpu_usage)
+        });
+
+        ctx.request_repaint_after(Duration::from_secs_f32(HTTP_POLL_PERIOD_SECONDS));
+        if let Err(err) = self
+            .state
+            .local_stream_manager
+            .check_local_stream_ready(&mut self.state.stream)
+        {
+            warn!("{}", err);
+        };
+
+        let menu_bar = MenuBar {};
+        TopBottomPanel::top(menu_bar.title()).show(ctx, |ui| {
+            if let Err(err) = menu_bar.render(ui, &mut self.state) {
+                warn!("{}", err);
+            }
+        });
+        CentralPanel::default()
+            .frame(egui::Frame::central_panel(&ctx.style()).inner_margin(0.))
+            .show(ctx, |ui| {
+                DockArea::new(&mut self.dock_state)
+                    .show_close_buttons(false)
+                    .show_add_buttons(false)
+                    .draggable_tabs(false)
+                    .show_tab_name_on_hover(false)
+                    .show_inside(ui, &mut self.state);
+            });
+
+        let close_window = false;
+        let mut show_stream_select = self.state.settings.show_stream_select;
+        egui::Window::new("Load Stream")
+            .default_width(800.0)
+            .open(&mut show_stream_select)
+            .collapsible(false)
+            .show(ctx, |ui| {
+                if let Err(err) = StreamSelectViewer.render(ui, &mut self.state) {
+                    warn!("{}", err);
+                }
+            });
+        if !show_stream_select || close_window {
+            self.state.settings.show_stream_select = false;
+        }
+
+        let mut show_decode_progress: bool = self.state.settings.show_decode_progress;
+        egui::Window::new("Decode Progress")
+            .open(&mut show_decode_progress)
+            .default_width(800.0)
+            .default_height(600.0)
+            .collapsible(false)
+            .show(ctx, |ui| {
+                if let Err(err) = DecodeProgressViewer.render(ui, &mut self.state) {
+                    warn!("{}", err);
+                }
+            });
+        self.state.settings.show_decode_progress = show_decode_progress;
+
+        let mut show_performance_window: bool = self.state.settings.show_performance_window;
+        egui::Window::new("Performance")
+            .open(&mut show_performance_window)
+            .collapsible(false)
+            .show(ctx, |ui| {
+                if let Err(err) = PerformanceViewer.render(ui, &mut self.state) {
+                    warn!("{}", err);
+                }
+            });
+        self.state.settings.show_performance_window = show_performance_window;
+
+        let mut show_settings_window: bool = self.state.settings.show_settings_window;
+        egui::Window::new("Settings")
+            .open(&mut show_settings_window)
+            .default_width(800.0)
+            .default_height(600.0)
+            .resizable(true)
+            .collapsible(false)
+            .show(ctx, |ui| {
+                if let Err(err) = SettingsViewer.render(ui, &mut self.state) {
+                    warn!("{}", err);
+                }
+            });
+        self.state.settings.show_settings_window = show_settings_window;
+
+        let mut show_pixel_viewer: bool = self.state.settings.sharable.show_pixel_viewer;
+        let pixel_viewer_title = match &self.state.settings.selected_object {
+            None => {
+                show_pixel_viewer = false;
+                "".to_string()
+            }
+            Some(selected_object) => {
+                if let Some(frame) = self.state.stream.current_frame() {
+                    selected_object
+                        .rect(frame)
+                        .map(|rect| {
+                            format!(
+                                "Pixels: {}x{} block at (x={}, y={})",
+                                rect.width() as i32,
+                                rect.height() as i32,
+                                rect.left_top().x as i32,
+                                rect.left_top().y as i32
+                            )
+                        })
+                        .unwrap_or_default()
+                } else {
+                    "".to_string()
+                }
+            }
+        };
+        egui::Window::new(pixel_viewer_title)
+            .id("Pixels".into())
+            .open(&mut show_pixel_viewer)
+            .default_width(400.0)
+            .default_height(400.0)
+            .resizable(true)
+            .collapsible(false)
+            .show(ctx, |ui| {
+                if let Err(err) = DetailedPixelViewer.render(ui, &mut self.state) {
+                    warn!("{}", err);
+                }
+            });
+        self.state.settings.sharable.show_pixel_viewer = show_pixel_viewer;
+
+        let mut show_coeffs_viewer: bool = self.state.settings.sharable.show_coeffs_viewer;
+        let mut coeffs_viewer_title = "".to_string();
+        match &self.state.settings.selected_object {
+            None => {
+                show_coeffs_viewer = false;
+            }
+            Some(selected_object) => {
+                if let Some(frame) = self.state.stream.current_frame() {
+                    if matches!(selected_object.kind, SelectedObjectKind::TransformUnit(_)) {
+                        coeffs_viewer_title = selected_object
+                            .rect(frame)
+                            .map(|rect| {
+                                format!(
+                                    "Coeffs: {}x{} block at (x={}, y={})",
+                                    rect.width() as i32,
+                                    rect.height() as i32,
+                                    rect.left_top().x as i32,
+                                    rect.left_top().y as i32
+                                )
+                            })
+                            .unwrap_or_default();
+                    }
+                }
+            }
+        };
+        egui::Window::new(coeffs_viewer_title)
+            .id("Coeffs".into())
+            .open(&mut show_coeffs_viewer)
+            .default_width(400.0)
+            .default_height(400.0)
+            .resizable(true)
+            .collapsible(false)
+            .show(ctx, |ui| {
+                if let Err(err) = CoeffsViewer.render(ui, &mut self.state) {
+                    warn!("{}", err);
+                }
+            });
+        self.state.settings.sharable.show_coeffs_viewer = show_coeffs_viewer;
+
+        if self.state.settings.persistent.update_sharable_url {
+            let shared_state = self.state.settings.create_shared_settings(&self.state.stream);
+            if shared_state != self.state.previous_shared_state {
+                self.state.previous_shared_state = shared_state.clone();
+                let window = web_sys::window().unwrap();
+                let shared_settings_str = shared_state.encode();
+                if let Ok(history) = window.history() {
+                    if let Err(err) = history.replace_state_with_url(
+                        &JsValue::NULL,
+                        "AVM Analyzer",
+                        Some(&format!("?{STATE_URL_QUERY_PARAM_NAME}={shared_settings_str}")),
+                    ) {
+                        warn!("Unable to set URL state: {err:?}");
+                    }
+                }
+            }
+        }
+        // TODO(comc): Add selected tab to shared settings.
+        // let find_me: Box<dyn RenderView> = Box::new(StatsViewer);
+        // let idx = self.dock_state.find_tab(&find_me);
+    }
+
+    fn save(&mut self, storage: &mut dyn Storage) {
+        info!("Saved settings to local storage.");
+        if let Ok(saved_settings) = serde_json::to_string(&self.state.settings.persistent) {
+            storage.set_string(SAVED_SETTINGS_KEY, saved_settings);
+        } else {
+            warn!("Error serialize saved settings.")
+        }
+    }
+
+    fn auto_save_interval(&self) -> std::time::Duration {
+        Duration::from_secs_f32(5.0)
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/app_state.rs b/tools/avm_analyzer/avm_analyzer_app/src/app_state.rs
new file mode 100644
index 0000000..f56023c
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/app_state.rs
@@ -0,0 +1,156 @@
+use crate::views::{PerformanceHistory, RenderView, SelectedObject, SelectedObjectKind};
+
+use avm_analyzer_common::{AvmStreamInfo, DEFAULT_PROTO_PATH_FRAME_SUFFIX_TEMPLATE};
+use avm_stats::Spatial;
+use egui::{Ui, WidgetText};
+use egui_dock::TabViewer;
+
+use log::{info, warn};
+
+use crate::settings::{Settings, SharableSettings};
+use crate::stream::{
+    CacheStrategy, FrameStatus, HttpStreamManager, LocalStreamManager, ServerDecodeManager, Stream, StreamEventType,
+};
+
+pub struct AppState {
+    pub stream: Option<Stream>,
+    pub settings: Settings,
+    pub local_stream_manager: LocalStreamManager,
+    pub server_decode_manager: ServerDecodeManager,
+    pub http_stream_manager: HttpStreamManager,
+    pub performance_history: PerformanceHistory,
+    pub previous_shared_state: SharableSettings,
+}
+
+pub const STATE_URL_QUERY_PARAM_NAME: &str = "state";
+pub const LOAD_STREAM_URL_PARAM_NAME: &str = "load_stream";
+pub const NUM_FRAMES_URL_PARAM_NAME: &str = "num_frames";
+
+impl AppState {
+    pub fn new(saved_settings: Option<String>) -> Self {
+        let http_stream_manager = HttpStreamManager::new();
+        let local_stream_manager = LocalStreamManager::new();
+        let mut stream = None;
+        let mut shared_settings = None;
+        let mut load_stream_info = None;
+        let window = web_sys::window().unwrap();
+        if let Ok(search) = window.location().search() {
+            if let Ok(params) = web_sys::UrlSearchParams::new_with_str(&search) {
+                if let Some(state) = params.get(STATE_URL_QUERY_PARAM_NAME) {
+                    shared_settings = Some(state);
+                }
+                if let Some(load_stream) = params.get(LOAD_STREAM_URL_PARAM_NAME) {
+                    let num_frames = params.get(NUM_FRAMES_URL_PARAM_NAME).unwrap_or("1".into());
+                    let num_frames = num_frames.parse::<usize>().unwrap_or(1);
+                    if let Some(stream_name_end) = load_stream.find(DEFAULT_PROTO_PATH_FRAME_SUFFIX_TEMPLATE) {
+                        let stream_name = load_stream[..stream_name_end].to_string();
+                        load_stream_info = Some(AvmStreamInfo {
+                            stream_name,
+                            num_frames,
+                            proto_path_template: load_stream,
+                            thumbnail_png: None
+                        });
+                    }
+                }
+            }
+        }
+
+        let mut settings = Settings::from_shared_settings_string(shared_settings.as_deref());
+        settings.apply_saved_settings_string(saved_settings);
+        if let Some(stream_info) = load_stream_info {
+            settings.sharable.selected_stream = Some(stream_info);
+            settings.sharable.show_remote_streams = false;
+            settings.sharable.streams_url = "".into();
+        }
+
+        if settings.sharable.show_remote_streams {
+            http_stream_manager.load_stream_list();
+        }
+
+        if let Some(stream_info) = &settings.sharable.selected_stream {
+            // Unwrap is okay here, since nothing can fail immediately when creating an HTTP stream.
+            stream = Some(Stream::from_http(stream_info.clone(), false, settings.sharable.selected_frame, &settings.sharable.streams_url).unwrap());
+        } else {
+            local_stream_manager.load_demo_stream();
+            local_stream_manager
+                .check_local_stream_ready(&mut stream)
+                .expect("Failed to load demo stream");
+        }
+        Self {
+            stream,
+            settings,
+            local_stream_manager,
+            server_decode_manager: ServerDecodeManager::new(),
+            http_stream_manager,
+            performance_history: PerformanceHistory::default(),
+            previous_shared_state: SharableSettings::default(),
+        }
+    }
+    /// Called whenever the current frame changed. Should reset anything specific to the current frame, e.g. selected objects.
+    pub fn check_stream_events(&mut self) {
+        if let Some(stream) = self.stream.as_mut() {
+            let events = stream.check_events();
+            for event in events {
+                let event = event.event;
+                info!("Event: {event:?}");
+                match event {
+                    StreamEventType::NewStream => {
+                        self.settings.selected_object = None;
+                        self.settings.selected_object_leaf = None;
+                        self.settings.cached_stat_data = None;
+                        self.settings.scroll_to_index = Some(0);
+                    }
+                    StreamEventType::FrameChanged(index) => {
+                        self.settings.selected_object = None;
+                        self.settings.selected_object_leaf = None;
+                        self.settings.cached_stat_data = None;
+                        self.settings.scroll_to_index = Some(index);
+                        let limit = if self.settings.persistent.apply_cache_strategy {
+                            Some(self.settings.persistent.cache_strategy_limit)
+                        } else {
+                            None
+                        };
+                        stream.apply_cache_strategy(CacheStrategy::from_limit(limit));
+                    }
+                    StreamEventType::FirstFrameLoaded(index) => {
+                        info!("First frame loaded: {index}");
+                        if let FrameStatus::Loaded(frame) = stream.get_frame(index) {
+                            if !self.settings.have_world_bounds_from_shared_settings {
+                                self.settings.sharable.world_bounds = frame.rect();
+                            }
+                            if let Some(shared_selected_object) = self.settings.sharable.selected_object_kind.take() {
+                                if let FrameStatus::Loaded(frame) = stream.get_frame(index) {
+                                    let is_valid = match &shared_selected_object {
+                                        SelectedObjectKind::CodingUnit(obj) => obj.try_resolve(frame).is_some(),
+                                        SelectedObjectKind::TransformUnit(obj) => obj.try_resolve(frame).is_some(),
+                                        SelectedObjectKind::Superblock(obj) => obj.try_resolve(frame).is_some(),
+                                        SelectedObjectKind::Partition(obj) => obj.try_resolve(frame).is_some(),
+                                    };
+                                    if is_valid {
+                                        info!("Restored selected object: {shared_selected_object:?}");
+                                        self.settings.selected_object =
+                                            Some(SelectedObject::new(shared_selected_object));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+impl TabViewer for AppState {
+    type Tab = Box<dyn RenderView>;
+
+    fn ui(&mut self, ui: &mut Ui, tab: &mut Self::Tab) {
+        if let Err(err) = tab.render(ui, self) {
+            warn!("{}", err);
+        }
+    }
+
+    fn title(&mut self, tab: &mut Self::Tab) -> WidgetText {
+        tab.title().into()
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/image_manager/color_maps.rs b/tools/avm_analyzer/avm_analyzer_app/src/image_manager/color_maps.rs
new file mode 100644
index 0000000..1e83e64
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/image_manager/color_maps.rs
@@ -0,0 +1,259 @@
+// TODO(comc): Add other colormap options.
+pub const JET_COLORMAP: [[u8; 3]; 256] = [
+    [0, 0, 127],
+    [0, 0, 132],
+    [0, 0, 136],
+    [0, 0, 141],
+    [0, 0, 145],
+    [0, 0, 150],
+    [0, 0, 154],
+    [0, 0, 159],
+    [0, 0, 163],
+    [0, 0, 168],
+    [0, 0, 172],
+    [0, 0, 177],
+    [0, 0, 182],
+    [0, 0, 186],
+    [0, 0, 191],
+    [0, 0, 195],
+    [0, 0, 200],
+    [0, 0, 204],
+    [0, 0, 209],
+    [0, 0, 213],
+    [0, 0, 218],
+    [0, 0, 222],
+    [0, 0, 227],
+    [0, 0, 232],
+    [0, 0, 236],
+    [0, 0, 241],
+    [0, 0, 245],
+    [0, 0, 250],
+    [0, 0, 254],
+    [0, 0, 255],
+    [0, 0, 255],
+    [0, 0, 255],
+    [0, 0, 255],
+    [0, 4, 255],
+    [0, 8, 255],
+    [0, 12, 255],
+    [0, 16, 255],
+    [0, 20, 255],
+    [0, 24, 255],
+    [0, 28, 255],
+    [0, 32, 255],
+    [0, 36, 255],
+    [0, 40, 255],
+    [0, 44, 255],
+    [0, 48, 255],
+    [0, 52, 255],
+    [0, 56, 255],
+    [0, 60, 255],
+    [0, 64, 255],
+    [0, 68, 255],
+    [0, 72, 255],
+    [0, 76, 255],
+    [0, 80, 255],
+    [0, 84, 255],
+    [0, 88, 255],
+    [0, 92, 255],
+    [0, 96, 255],
+    [0, 100, 255],
+    [0, 104, 255],
+    [0, 108, 255],
+    [0, 112, 255],
+    [0, 116, 255],
+    [0, 120, 255],
+    [0, 124, 255],
+    [0, 128, 255],
+    [0, 132, 255],
+    [0, 136, 255],
+    [0, 140, 255],
+    [0, 144, 255],
+    [0, 148, 255],
+    [0, 152, 255],
+    [0, 156, 255],
+    [0, 160, 255],
+    [0, 164, 255],
+    [0, 168, 255],
+    [0, 172, 255],
+    [0, 176, 255],
+    [0, 180, 255],
+    [0, 184, 255],
+    [0, 188, 255],
+    [0, 192, 255],
+    [0, 196, 255],
+    [0, 200, 255],
+    [0, 204, 255],
+    [0, 208, 255],
+    [0, 212, 255],
+    [0, 216, 255],
+    [0, 220, 254],
+    [0, 224, 250],
+    [0, 228, 247],
+    [2, 232, 244],
+    [5, 236, 241],
+    [8, 240, 237],
+    [12, 244, 234],
+    [15, 248, 231],
+    [18, 252, 228],
+    [21, 255, 225],
+    [24, 255, 221],
+    [28, 255, 218],
+    [31, 255, 215],
+    [34, 255, 212],
+    [37, 255, 208],
+    [41, 255, 205],
+    [44, 255, 202],
+    [47, 255, 199],
+    [50, 255, 195],
+    [54, 255, 192],
+    [57, 255, 189],
+    [60, 255, 186],
+    [63, 255, 183],
+    [66, 255, 179],
+    [70, 255, 176],
+    [73, 255, 173],
+    [76, 255, 170],
+    [79, 255, 166],
+    [83, 255, 163],
+    [86, 255, 160],
+    [89, 255, 157],
+    [92, 255, 154],
+    [95, 255, 150],
+    [99, 255, 147],
+    [102, 255, 144],
+    [105, 255, 141],
+    [108, 255, 137],
+    [112, 255, 134],
+    [115, 255, 131],
+    [118, 255, 128],
+    [121, 255, 125],
+    [124, 255, 121],
+    [128, 255, 118],
+    [131, 255, 115],
+    [134, 255, 112],
+    [137, 255, 108],
+    [141, 255, 105],
+    [144, 255, 102],
+    [147, 255, 99],
+    [150, 255, 95],
+    [154, 255, 92],
+    [157, 255, 89],
+    [160, 255, 86],
+    [163, 255, 83],
+    [166, 255, 79],
+    [170, 255, 76],
+    [173, 255, 73],
+    [176, 255, 70],
+    [179, 255, 66],
+    [183, 255, 63],
+    [186, 255, 60],
+    [189, 255, 57],
+    [192, 255, 54],
+    [195, 255, 50],
+    [199, 255, 47],
+    [202, 255, 44],
+    [205, 255, 41],
+    [208, 255, 37],
+    [212, 255, 34],
+    [215, 255, 31],
+    [218, 255, 28],
+    [221, 255, 24],
+    [224, 255, 21],
+    [228, 255, 18],
+    [231, 255, 15],
+    [234, 255, 12],
+    [237, 255, 8],
+    [241, 252, 5],
+    [244, 248, 2],
+    [247, 244, 0],
+    [250, 240, 0],
+    [254, 237, 0],
+    [255, 233, 0],
+    [255, 229, 0],
+    [255, 226, 0],
+    [255, 222, 0],
+    [255, 218, 0],
+    [255, 215, 0],
+    [255, 211, 0],
+    [255, 207, 0],
+    [255, 203, 0],
+    [255, 200, 0],
+    [255, 196, 0],
+    [255, 192, 0],
+    [255, 189, 0],
+    [255, 185, 0],
+    [255, 181, 0],
+    [255, 177, 0],
+    [255, 174, 0],
+    [255, 170, 0],
+    [255, 166, 0],
+    [255, 163, 0],
+    [255, 159, 0],
+    [255, 155, 0],
+    [255, 152, 0],
+    [255, 148, 0],
+    [255, 144, 0],
+    [255, 140, 0],
+    [255, 137, 0],
+    [255, 133, 0],
+    [255, 129, 0],
+    [255, 126, 0],
+    [255, 122, 0],
+    [255, 118, 0],
+    [255, 115, 0],
+    [255, 111, 0],
+    [255, 107, 0],
+    [255, 103, 0],
+    [255, 100, 0],
+    [255, 96, 0],
+    [255, 92, 0],
+    [255, 89, 0],
+    [255, 85, 0],
+    [255, 81, 0],
+    [255, 77, 0],
+    [255, 74, 0],
+    [255, 70, 0],
+    [255, 66, 0],
+    [255, 63, 0],
+    [255, 59, 0],
+    [255, 55, 0],
+    [255, 52, 0],
+    [255, 48, 0],
+    [255, 44, 0],
+    [255, 40, 0],
+    [255, 37, 0],
+    [255, 33, 0],
+    [255, 29, 0],
+    [255, 26, 0],
+    [255, 22, 0],
+    [254, 18, 0],
+    [250, 15, 0],
+    [245, 11, 0],
+    [241, 7, 0],
+    [236, 3, 0],
+    [232, 0, 0],
+    [227, 0, 0],
+    [222, 0, 0],
+    [218, 0, 0],
+    [213, 0, 0],
+    [209, 0, 0],
+    [204, 0, 0],
+    [200, 0, 0],
+    [195, 0, 0],
+    [191, 0, 0],
+    [186, 0, 0],
+    [182, 0, 0],
+    [177, 0, 0],
+    [172, 0, 0],
+    [168, 0, 0],
+    [163, 0, 0],
+    [159, 0, 0],
+    [154, 0, 0],
+    [150, 0, 0],
+    [145, 0, 0],
+    [141, 0, 0],
+    [136, 0, 0],
+    [132, 0, 0],
+    [127, 0, 0],
+];
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/image_manager/mod.rs b/tools/avm_analyzer/avm_analyzer_app/src/image_manager/mod.rs
new file mode 100644
index 0000000..3a6c013
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/image_manager/mod.rs
@@ -0,0 +1,292 @@
+mod color_maps;
+
+use std::collections::HashMap;
+// TODO(comc): Consider using egui mutex instead of std::sync.
+use std::sync::{Arc, Mutex};
+
+use avm_stats::{
+    calculate_heatmap, Frame, FrameError, Heatmap, HeatmapSettings, PixelPlane, PixelType, Plane, PlaneType,
+};
+pub use color_maps::JET_COLORMAP;
+use egui::{ColorImage, TextureHandle, TextureOptions};
+use itertools::{Itertools, MinMaxResult};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+struct PixelPlaneKey {
+    frame_decode_index: usize,
+    plane: Plane,
+    pixel_type: PixelType,
+}
+
+impl PixelPlaneKey {
+    pub fn new(frame_decode_index: usize, plane: Plane, pixel_type: PixelType) -> Self {
+        Self {
+            frame_decode_index,
+            plane,
+            pixel_type,
+        }
+    }
+}
+
+/// Manages a collection of of different `PixelPlane` buffers.
+#[derive(Default)]
+pub struct PixelDataManager {
+    // Lazily populated with actual pixel data.
+    pixel_data: Mutex<HashMap<PixelPlaneKey, Result<Arc<PixelPlane>, FrameError>>>,
+}
+
+impl PixelDataManager {
+    pub fn get_or_create_pixels(
+        &self,
+        frame: &Frame,
+        plane: Plane,
+        pixel_type: PixelType,
+    ) -> Result<Arc<PixelPlane>, FrameError> {
+        let key = PixelPlaneKey::new(frame.decode_index(), plane, pixel_type);
+        self.pixel_data
+            .lock()
+            .unwrap()
+            .entry(key)
+            .or_insert_with(|| PixelPlane::create_from_frame(frame, key.plane, key.pixel_type).map(Arc::new))
+            .clone()
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ImageType {
+    pub plane_type: PlaneType,
+    pub pixel_type: PixelType,
+    pub show_relative_delta: bool,
+    pub is_heatmap: bool,
+}
+
+impl ImageType {
+    pub fn new(plane_type: PlaneType, pixel_type: PixelType, show_relative_delta: bool, is_heatmap: bool) -> Self {
+        Self {
+            plane_type,
+            pixel_type,
+            show_relative_delta,
+            is_heatmap,
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+struct ImageKey {
+    frame_decode_index: usize,
+    image_type: ImageType,
+}
+
+impl ImageKey {
+    fn new(frame_decode_index: usize, image_type: ImageType) -> Self {
+        Self {
+            frame_decode_index,
+            image_type,
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+struct HeatmapKey {
+    frame_decode_index: usize,
+}
+
+impl HeatmapKey {
+    fn new(frame_decode_index: usize) -> Self {
+        Self { frame_decode_index }
+    }
+}
+
+#[derive(Clone)]
+struct StoredHeatmap {
+    heatmap: Heatmap,
+    texture_handle: TextureHandle,
+}
+
+// TODO(comc): Wipe existing images when a frame is unloaded.
+#[derive(Default)]
+pub struct ImageManager {
+    // Lazily populated with actual images.
+    images: Mutex<HashMap<ImageKey, Result<TextureHandle, FrameError>>>,
+    heatmaps: Mutex<HashMap<HeatmapKey, Result<StoredHeatmap, FrameError>>>,
+}
+
+#[allow(clippy::identity_op)]
+impl ImageManager {
+    fn image_from_heatmap(ctx: &egui::Context, heatmap: &mut Heatmap) -> TextureHandle {
+        let width = heatmap.width;
+        let height = heatmap.height;
+        let raw_rgb = heatmap
+            .data
+            .drain(..)
+            .flat_map(|x| JET_COLORMAP[x as usize])
+            .collect_vec();
+        let color_image = ColorImage::from_rgb([width, height], raw_rgb.as_slice());
+        ctx.load_texture("heatmap", color_image, TextureOptions::NEAREST)
+    }
+
+    fn image_from_yuv_planes(ctx: &egui::Context, planes: &[&PixelPlane]) -> TextureHandle {
+        let width = planes[0].width as usize;
+        let height = planes[0].height as usize;
+
+        let mut raw_rgb = vec![0; width * height * 3];
+        let raw_y = planes[0].pixels.as_slice();
+        let raw_u = planes[1].pixels.as_slice();
+        let raw_v = planes[2].pixels.as_slice();
+
+        for i in 0..height {
+            for j in 0..width {
+                let y = raw_y[i * width + j] as f32;
+                let u = raw_u[(i / 2) * (width / 2) + (j / 2)] as f32;
+                let v = raw_v[(i / 2) * (width / 2) + (j / 2)] as f32;
+
+                let is_8_bit = planes[0].bit_depth == 8;
+                let y = if is_8_bit { y } else { y / 4.0 };
+                let u = if is_8_bit { u - 128.0 } else { u / 4.0 - 128.0 };
+                let v = if is_8_bit { v - 128.0 } else { v / 4.0 - 128.0 };
+                let r = (y + 1.13983 * v) as u8;
+                let g = (y - 0.39465 * u - 0.58060 * v) as u8;
+                let b = (y + 2.03211 * u) as u8;
+
+                raw_rgb[i * width * 3 + j * 3 + 0] = r;
+                raw_rgb[i * width * 3 + j * 3 + 1] = g;
+                raw_rgb[i * width * 3 + j * 3 + 2] = b;
+            }
+        }
+        let color_image = ColorImage::from_rgb([width, height], raw_rgb.as_slice());
+        ctx.load_texture("yuv", color_image, TextureOptions::NEAREST)
+    }
+
+    fn image_from_single_plane(ctx: &egui::Context, plane: &PixelPlane, show_relative_delta: bool) -> TextureHandle {
+        let width = plane.width as usize;
+        let height = plane.height as usize;
+        let mut raw_rgb = vec![0; width * height * 3];
+        let mut min = -255;
+        let mut max = 255;
+        if show_relative_delta {
+            match plane.pixels.iter().minmax() {
+                MinMaxResult::NoElements | MinMaxResult::OneElement(_) => {}
+                MinMaxResult::MinMax(&min_v, &max_v) => {
+                    min = min_v;
+                    max = max_v;
+                }
+            }
+        }
+
+        let pixel_max = 1 << plane.bit_depth;
+        for i in 0..height {
+            for j in 0..width {
+                let mut sample = plane.pixels[i * width + j];
+                if plane.pixel_type.is_delta() {
+                    if show_relative_delta {
+                        let rel = (sample - min) as f32 / (max - min) as f32;
+                        sample = (rel * 255.0) as i16;
+                    } else {
+                        sample = (sample + pixel_max - 1) / 2;
+                    }
+                }
+                let sample = if plane.bit_depth == 8 || show_relative_delta {
+                    sample
+                } else {
+                    sample / 4
+                } as u8;
+                raw_rgb[i * width * 3 + j * 3 + 0] = sample;
+                raw_rgb[i * width * 3 + j * 3 + 1] = sample;
+                raw_rgb[i * width * 3 + j * 3 + 2] = sample;
+            }
+        }
+        let color_image = ColorImage::from_rgb([width, height], raw_rgb.as_slice());
+        ctx.load_texture("yuv", color_image, TextureOptions::NEAREST)
+    }
+
+    fn create_image(
+        ctx: &egui::Context,
+        pixel_manager: &PixelDataManager,
+        frame: &Frame,
+        image_type: ImageType,
+    ) -> Result<TextureHandle, FrameError> {
+        match image_type.plane_type {
+            PlaneType::Rgb => {
+                let planes: Vec<_> = (0..3)
+                    .map(|i| pixel_manager.get_or_create_pixels(frame, Plane::from_i32(i), image_type.pixel_type))
+                    .collect::<Result<_, _>>()?;
+
+                Ok(Self::image_from_yuv_planes(
+                    ctx,
+                    planes.iter().map(|p| p.as_ref()).collect::<Vec<_>>().as_slice(),
+                ))
+            }
+            PlaneType::Planar(plane) => {
+                let pixels = pixel_manager.get_or_create_pixels(frame, plane, image_type.pixel_type)?;
+                Ok(Self::image_from_single_plane(
+                    ctx,
+                    pixels.as_ref(),
+                    image_type.show_relative_delta,
+                ))
+            }
+        }
+    }
+
+    fn create_heatmap(
+        ctx: &egui::Context,
+        frame: &Frame,
+        _image_type: ImageType,
+        heatmap_settings: &HeatmapSettings,
+    ) -> Result<StoredHeatmap, FrameError> {
+        let mut heatmap = calculate_heatmap(frame, heatmap_settings)?;
+        let texture_handle = Self::image_from_heatmap(ctx, &mut heatmap);
+        Ok(StoredHeatmap {
+            heatmap,
+            texture_handle,
+        })
+    }
+
+    pub fn get_or_create_image(
+        &self,
+        ctx: &egui::Context,
+        pixel_manager: &PixelDataManager,
+        frame: &Frame,
+        image_type: ImageType,
+        heatmap_settings: &HeatmapSettings,
+    ) -> Result<TextureHandle, FrameError> {
+        if image_type.is_heatmap {
+            let key = HeatmapKey::new(frame.decode_index());
+            let mut heatmaps = self.heatmaps.lock().unwrap();
+            heatmaps
+                .entry(key)
+                .or_insert_with(move || Self::create_heatmap(ctx, frame, image_type, heatmap_settings))
+                .as_ref()
+                .map(|h| h.texture_handle.clone())
+                .map_err(|err| err.clone())
+        } else {
+            let key = ImageKey::new(frame.decode_index(), image_type);
+            let mut images = self.images.lock().unwrap();
+            images
+                .entry(key)
+                .or_insert_with(move || Self::create_image(ctx, pixel_manager, frame, image_type))
+                .clone()
+        }
+    }
+
+    // TODO(comc): Refactor to not need clone.
+    pub fn get_or_create_heatmap(
+        &self,
+        ctx: &egui::Context,
+        frame: &Frame,
+        image_type: ImageType,
+        heatmap_settings: &HeatmapSettings,
+    ) -> Result<Heatmap, FrameError> {
+        let mut heatmaps = self.heatmaps.lock().unwrap();
+        let key = HeatmapKey::new(frame.decode_index());
+        heatmaps
+            .entry(key)
+            .or_insert_with(move || Self::create_heatmap(ctx, frame, image_type, heatmap_settings))
+            .as_ref()
+            .map(|h| h.heatmap.clone())
+            .map_err(|err| err.clone())
+    }
+
+    pub fn clear_heatmaps(&self) {
+        self.heatmaps.lock().unwrap().clear();
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/lib.rs b/tools/avm_analyzer/avm_analyzer_app/src/lib.rs
new file mode 100644
index 0000000..f6483f8
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/lib.rs
@@ -0,0 +1,8 @@
+mod app;
+mod app_state;
+mod image_manager;
+mod settings;
+mod stream;
+mod views;
+
+pub use app::AvmAnalyzerApp;
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/main.rs b/tools/avm_analyzer/avm_analyzer_app/src/main.rs
new file mode 100644
index 0000000..fb773cb
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/main.rs
@@ -0,0 +1,25 @@
+// TODO(comc): Remove non-wasm code paths.
+use re_memory::AccountingAllocator;
+
+#[global_allocator]
+static GLOBAL: AccountingAllocator<std::alloc::System> = AccountingAllocator::new(std::alloc::System);
+
+#[cfg(target_family = "wasm")]
+fn main() {
+    eframe::WebLogger::init(log::LevelFilter::Debug).ok();
+    let web_options = eframe::WebOptions::default();
+
+    wasm_bindgen_futures::spawn_local(async {
+        eframe::WebRunner::new()
+            .start(
+                "avm_analyzer_canvas_id",
+                web_options,
+                Box::new(|cc| {
+                    egui_extras::install_image_loaders(&cc.egui_ctx);
+                    Box::new(avm_analyzer_app::AvmAnalyzerApp::new(cc))
+                }),
+            )
+            .await
+            .expect("failed to start eframe");
+    });
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/settings.rs b/tools/avm_analyzer/avm_analyzer_app/src/settings.rs
new file mode 100644
index 0000000..f8e0884
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/settings.rs
@@ -0,0 +1,350 @@
+use avm_analyzer_common::AvmStreamInfo;
+use avm_stats::{FrameStatistic, HeatmapSettings, PlaneType, Sample, StatsSettings};
+use base64::{engine::general_purpose::URL_SAFE, Engine as _};
+use egui::{pos2, vec2, Color32, Pos2, Rect, Stroke};
+use log::info;
+use serde::{Deserialize, Serialize};
+use web_time::Instant;
+
+use crate::{
+    stream::{Stream, StreamSource},
+    views::{SelectedObject, SelectedObjectKind, ViewMode},
+};
+
+const GITLAB_ROOT: &str = "https://gitlab.com/AOMediaCodec/avm/-/blob/research-v6.0.0";
+const DEFAULT_STREAMS_URL: &str = "/streams";
+const DEFAULT_WORLD_BOUNDS_WIDTH: f32 = 1280.0;
+const DEFAULT_WORLD_BOUNDS_HEIGHT: f32 = 720.0;
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct OverlayStyle {
+    pub transform_unit_stroke: Stroke,
+    pub coding_unit_stroke: Stroke,
+    pub superblock_stroke: Stroke,
+    pub highlighted_object_stroke: Stroke,
+    pub selected_object_stroke: Stroke,
+    pub mode_name_color: Color32,
+    pub pixel_viewer_text_color: Color32,
+    pub enable_text_shadows: bool,
+}
+
+impl Default for OverlayStyle {
+    fn default() -> Self {
+        Self {
+            transform_unit_stroke: Stroke::new(1.0, Color32::DARK_RED),
+            coding_unit_stroke: Stroke::new(1.0, Color32::RED),
+            superblock_stroke: Stroke::new(1.0, Color32::BLUE),
+            highlighted_object_stroke: Stroke::new(4.0, Color32::LIGHT_YELLOW),
+            selected_object_stroke: Stroke::new(3.0, Color32::YELLOW),
+            mode_name_color: Color32::LIGHT_GREEN,
+            pixel_viewer_text_color: Color32::LIGHT_GREEN,
+            enable_text_shadows: true,
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct StyleSettings {
+    pub overlay: OverlayStyle,
+}
+#[allow(clippy::derivable_impls)]
+impl Default for StyleSettings {
+    fn default() -> Self {
+        Self {
+            overlay: OverlayStyle::default(),
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
+pub enum FrameSortOrder {
+    Decode,
+    Display,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct PersistentSettings {
+    pub avm_source_url: String,
+    pub style: StyleSettings,
+    pub apply_cache_strategy: bool,
+    pub cache_strategy_limit: usize,
+    pub update_sharable_url: bool,
+    pub frame_sort_order: FrameSortOrder,
+}
+
+impl Default for PersistentSettings {
+    fn default() -> Self {
+        Self {
+            avm_source_url: GITLAB_ROOT.into(),
+            style: StyleSettings::default(),
+            apply_cache_strategy: false,
+            cache_strategy_limit: 10,
+            update_sharable_url: false,
+            frame_sort_order: FrameSortOrder::Decode,
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
+pub enum CoeffViewSelect {
+    Quantized,
+    Dequantized,
+    DequantValue,
+}
+
+impl CoeffViewSelect {
+    pub fn name(&self) -> &str {
+        match self {
+            Self::Quantized => "Quantized Coefficients",
+            Self::Dequantized => "Dequantized Coefficients",
+            Self::DequantValue => "Inverse Quantization Matrix",
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
+pub enum MotionFieldColoring {
+    RefFrames,
+    PastFuture,
+    Monochrome,
+}
+
+impl MotionFieldColoring {
+    pub fn name(&self) -> &str {
+        match self {
+            Self::RefFrames => "Reference frames",
+            Self::PastFuture => "Past vs Future",
+            Self::Monochrome => "Monochrome",
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+pub struct MotionFieldSettings {
+    pub show: bool,
+    pub show_origin: bool,
+    pub normalize: bool,
+    /// In 4x4 units.
+    pub granularity: usize,
+    pub auto_granularity: bool,
+    pub coloring: MotionFieldColoring,
+    pub scale: f32,
+}
+
+impl Default for MotionFieldSettings {
+    fn default() -> Self {
+        Self {
+            show: false,
+            show_origin: true,
+            normalize: true,
+            granularity: 1,
+            auto_granularity: true,
+            coloring: MotionFieldColoring::RefFrames,
+            scale: 1.0,
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
+pub enum DistortionView {
+    Original,
+    Reconstruction,
+    Distortion,
+}
+
+impl DistortionView {
+    pub fn name(&self) -> &str {
+        match self {
+            Self::Original => "Source (pre-encode)",
+            Self::Reconstruction => "Reconstruction",
+            Self::Distortion => "Distortion",
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+pub struct SharableSettings {
+    pub view_mode: ViewMode,
+    pub current_plane: PlaneType,
+    pub show_relative_delta: bool,
+    pub show_overlay: bool,
+    pub show_yuv: bool,
+    pub motion_field: MotionFieldSettings,
+    pub selected_stat: FrameStatistic,
+    pub stats_settings: StatsSettings,
+    pub symbol_info_filter: String,
+    pub symbol_info_show_tags: bool,
+    pub world_bounds: Rect,
+    pub show_pixel_viewer: bool,
+    pub show_coeffs_viewer: bool,
+    pub pixel_viewer_bounds: Rect,
+    pub coeffs_viewer_bounds: Rect,
+    pub coeff_view_select: CoeffViewSelect,
+    pub show_heatmap_legend: bool,
+    pub heatmap_settings: HeatmapSettings,
+    pub heatmap_histogram_log_scale: bool,
+    pub show_remote_streams: bool,
+    pub streams_url: String,
+    // The following fields are used only during serialization. Otherwise their settings or state equivalent is used.
+    pub selected_object_kind: Option<SelectedObjectKind>,
+    pub selected_stream: Option<AvmStreamInfo>,
+    pub selected_frame: usize,
+}
+
+impl Default for SharableSettings {
+    fn default() -> Self {
+        Self {
+            current_plane: PlaneType::Rgb,
+            show_relative_delta: false,
+            show_overlay: true,
+            show_yuv: true,
+            motion_field: MotionFieldSettings::default(),
+            view_mode: ViewMode::CodingFlow,
+            stats_settings: StatsSettings::default(),
+            selected_stat: FrameStatistic::BlockSizes,
+            symbol_info_filter: "".into(),
+            symbol_info_show_tags: true,
+            world_bounds: Rect::from_min_size(
+                Pos2::ZERO,
+                vec2(DEFAULT_WORLD_BOUNDS_WIDTH, DEFAULT_WORLD_BOUNDS_HEIGHT),
+            ),
+            show_pixel_viewer: false,
+            show_coeffs_viewer: false,
+            pixel_viewer_bounds: Rect::from_min_max(pos2(0.0, 0.0), pos2(4.0, 4.0)),
+            coeffs_viewer_bounds: Rect::from_min_max(pos2(0.0, 0.0), pos2(4.0, 4.0)),
+            coeff_view_select: CoeffViewSelect::Dequantized,
+            show_heatmap_legend: true,
+            heatmap_settings: HeatmapSettings::default(),
+            heatmap_histogram_log_scale: false,
+            show_remote_streams: true,
+            streams_url: DEFAULT_STREAMS_URL.into(),
+            selected_object_kind: None,
+            selected_stream: None,
+            selected_frame: 0,
+        }
+    }
+}
+
+impl SharableSettings {
+    pub fn encode(&self) -> String {
+        let bytes = bincode::serialize(self).unwrap();
+        let compressed = weezl::encode::Encoder::new(weezl::BitOrder::Lsb, 8)
+            .encode(&bytes)
+            .unwrap();
+        URL_SAFE.encode(compressed)
+    }
+
+    pub fn decode(settings_str: Option<&str>) -> Option<Self> {
+        if let Some(settings_str) = settings_str {
+            if let Ok(bytes) = URL_SAFE.decode(settings_str) {
+                if let Ok(uncompressed) = weezl::decode::Decoder::new(weezl::BitOrder::Lsb, 8).decode(&bytes) {
+                    if let Ok(sharable) = bincode::deserialize::<SharableSettings>(&uncompressed) {
+                        return Some(sharable);
+                    }
+                }
+            }
+        }
+        None
+    }
+}
+
+pub struct PlaybackSettings {
+    pub playback_fps: f32,
+    pub show_loaded_frames_only: bool,
+    pub playback_loop: bool,
+    pub playback_running: bool,
+    pub current_frame_display_instant: Instant,
+}
+
+impl Default for PlaybackSettings {
+    fn default() -> Self {
+        Self {
+            playback_fps: 30.0,
+            show_loaded_frames_only: true,
+            playback_loop: true,
+            playback_running: false,
+            current_frame_display_instant: Instant::now(),
+        }
+    }
+}
+
+pub struct Settings {
+    pub show_stream_select: bool,
+    pub show_decode_progress: bool,
+    pub scroll_to_index: Option<usize>,
+    pub cached_stat_data: Option<Vec<Sample>>,
+    pub show_performance_window: bool,
+    pub show_settings_window: bool,
+    pub persistent: PersistentSettings,
+    pub sharable: SharableSettings,
+    pub selected_object: Option<SelectedObject>,
+    /// The first object that was selected when double clicked to navigate up the hierarchy.
+    pub selected_object_leaf: Option<SelectedObjectKind>,
+    pub have_world_bounds_from_shared_settings: bool,
+    pub playback: PlaybackSettings,
+}
+
+#[allow(clippy::derivable_impls)]
+impl Default for Settings {
+    fn default() -> Self {
+        Self {
+            show_stream_select: false,
+            show_decode_progress: false,
+            scroll_to_index: None,
+            cached_stat_data: None,
+            show_performance_window: false,
+            show_settings_window: false,
+            persistent: PersistentSettings::default(),
+            sharable: SharableSettings::default(),
+            selected_object: None,
+            selected_object_leaf: None,
+            have_world_bounds_from_shared_settings: false,
+            playback: PlaybackSettings::default(),
+        }
+    }
+}
+
+impl Settings {
+    pub fn apply_saved_settings_string(&mut self, settings: Option<String>) {
+        if let Some(settings) = settings {
+            if let Ok(persistent) = serde_json::from_str::<PersistentSettings>(&settings) {
+                info!("Restoring saved settings: {persistent:?}");
+                self.persistent = persistent;
+            }
+        }
+    }
+
+    pub fn from_shared_settings_string(settings_str: Option<&str>) -> Self {
+        if let Some(sharable) = SharableSettings::decode(settings_str) {
+            info!("Loaded shared settings: {sharable:?}");
+            return Self {
+                sharable,
+                have_world_bounds_from_shared_settings: true,
+                ..Default::default()
+            };
+        }
+        Self::default()
+    }
+
+    pub fn create_shared_settings(&self, stream: &Option<Stream>) -> SharableSettings {
+        let mut shared = self.sharable.clone();
+        shared.selected_frame = 0;
+        shared.selected_stream = None;
+        shared.selected_object_kind = None;
+        if let Some(stream) = stream {
+            // Only save state for HTTP streams.
+            if matches!(stream.source, StreamSource::Http(_)) {
+                shared.selected_frame = stream.current_frame_index;
+                // TODO(comc): Can be simplified with serde skip.
+                shared.selected_stream = Some(AvmStreamInfo {
+                    thumbnail_png: None,
+                    ..stream.stream_info.clone()
+                });
+                if let Some(selected_object) = &self.selected_object {
+                    shared.selected_object_kind = Some(selected_object.kind.clone());
+                }
+            }
+        }
+        shared
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/stream/http_stream_manager.rs b/tools/avm_analyzer/avm_analyzer_app/src/stream/http_stream_manager.rs
new file mode 100644
index 0000000..5aadb93
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/stream/http_stream_manager.rs
@@ -0,0 +1,51 @@
+use std::{
+    collections::HashMap,
+    sync::{Arc, Mutex},
+};
+
+use avm_analyzer_common::{AvmStreamInfo, AvmStreamList, DecodeState};
+use log::info;
+
+use super::server_decode::PendingServerDecode;
+
+pub struct HttpStreamManager {
+    pub streams: Arc<Mutex<Vec<AvmStreamInfo>>>,
+}
+
+impl HttpStreamManager {
+    pub fn new() -> Self {
+        Self {
+            streams: Arc::new(Mutex::new(Vec::new())),
+        }
+    }
+
+    pub fn load_stream_list(&self) {
+        let request = ehttp::Request::get("/stream_list");
+        let streams = self.streams.clone();
+        ehttp::fetch(request, move |response| {
+            if let Ok(response) = response {
+                if let Some(json) = response.text() {
+                    if let Ok(response) = serde_json::from_str::<AvmStreamList>(json) {
+                        info!("Found: {} existing streams on server.", response.streams.len());
+                        let mut streams = streams.lock().unwrap();
+                        *streams.as_mut() = response.streams;
+                    }
+                }
+            }
+        });
+    }
+    pub fn update_pending_decodes(&mut self, pending_decodes: &HashMap<String, PendingServerDecode>) {
+        for pending_decode in pending_decodes.values() {
+            if let DecodeState::Complete(_) = pending_decode.state {
+                let streams = self.streams.lock().unwrap();
+                // TODO(comc): Make streams a hashmap instead.
+                let already_exists = streams
+                    .iter()
+                    .any(|stream| stream.stream_name == pending_decode.stream_info.stream_name);
+                if !already_exists {
+                    self.load_stream_list();
+                }
+            }
+        }
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/stream/local_stream_manager.rs b/tools/avm_analyzer/avm_analyzer_app/src/stream/local_stream_manager.rs
new file mode 100644
index 0000000..83624f4
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/stream/local_stream_manager.rs
@@ -0,0 +1,119 @@
+use std::{
+    io::Cursor,
+    sync::{Arc, Mutex},
+};
+
+use anyhow::anyhow;
+use avm_analyzer_common::{
+    AvmStreamInfo, DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST, DEFAULT_PROTO_PATH_FRAME_SUFFIX_TEMPLATE,
+};
+use egui::DroppedFile;
+use itertools::Itertools;
+use log::info;
+use rfd::AsyncFileDialog;
+use wasm_bindgen_futures::spawn_local;
+use zip::ZipArchive;
+
+use crate::stream::Stream;
+
+use super::stream_name_from_file_name;
+
+pub struct LocalStreamInfo {
+    pub file_name: String,
+    pub zip_bytes: Arc<[u8]>,
+}
+
+impl LocalStreamInfo {
+    pub fn get_stream_info(&self) -> anyhow::Result<AvmStreamInfo> {
+        let archive = ZipArchive::new(Cursor::new(self.zip_bytes.clone()))?;
+        let proto_file_names: Vec<_> = archive.file_names().filter(|n| n.ends_with(".pb")).sorted().collect();
+        let first_proto = proto_file_names
+            .first()
+            .ok_or(anyhow!("No protobufs (.pb) found in .zip archive."))?;
+        if !first_proto.ends_with(DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST) {
+            return Err(anyhow!("Unexpected protobuf naming scheme: expected suffix: {DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST}, actual name: {first_proto}"));
+        }
+        let proto_path_template = first_proto.replace(
+            DEFAULT_PROTO_PATH_FRAME_SUFFIX_FIRST,
+            DEFAULT_PROTO_PATH_FRAME_SUFFIX_TEMPLATE,
+        );
+
+        info!("Found {} frames from .zip stream.", proto_file_names.len());
+        let stream_info = AvmStreamInfo {
+            stream_name: self.file_name.clone(),
+            num_frames: proto_file_names.len(),
+            proto_path_template,
+            thumbnail_png: None,
+        };
+        for (i, proto_file_name) in proto_file_names.iter().enumerate() {
+            let expected = stream_info.get_proto_path(i);
+            if expected != **proto_file_name {
+                return Err(anyhow!(
+                    "Unexpected protobuf in stream: Expected name: {expected}, actual name: {proto_file_name}"
+                ));
+            }
+        }
+
+        Ok(stream_info)
+    }
+}
+
+const DEMO_STREAM_BYTES: &[u8] = include_bytes!("../../assets/leo_qcif.zip");
+
+pub struct LocalStreamManager {
+    local_stream: Arc<Mutex<Option<LocalStreamInfo>>>,
+}
+
+impl LocalStreamManager {
+    pub fn new() -> Self {
+        Self {
+            local_stream: Arc::new(Mutex::new(None)),
+        }
+    }
+
+    pub fn load_demo_stream(&self) {
+        let demo_stream = Some(LocalStreamInfo {
+            file_name: "leo_qcif.ivf".into(),
+            zip_bytes: DEMO_STREAM_BYTES.into(),
+        });
+        *self.local_stream.lock().unwrap() = demo_stream;
+    }
+    /// Currently only .zip files are supported.
+    pub fn prompt_local_stream(&self) {
+        let task = AsyncFileDialog::new().add_filter("ZIP", &["zip"]).pick_file();
+        let local_stream = self.local_stream.clone();
+        spawn_local(async move {
+            let file = task.await;
+            if let Some(file) = file {
+                let bytes = file.read().await;
+                let mut local_stream = local_stream.lock().unwrap();
+                *local_stream = Some(LocalStreamInfo {
+                    file_name: stream_name_from_file_name(&file.file_name()),
+                    zip_bytes: bytes.into(),
+                });
+            }
+        });
+    }
+
+    pub fn handle_dropped_file(&self, file: DroppedFile) {
+        if let Some(bytes) = file.bytes {
+            let mut local_stream = self.local_stream.lock().unwrap();
+            *local_stream = Some(LocalStreamInfo {
+                file_name: file.name,
+                zip_bytes: bytes,
+            });
+        }
+    }
+
+    pub fn check_local_stream_ready(&self, stream: &mut Option<Stream>) -> anyhow::Result<()> {
+        if let Some(local_stream) = self.local_stream.lock().unwrap().take() {
+            info!(
+                "Loaded local stream {}: {} bytes",
+                local_stream.file_name,
+                local_stream.zip_bytes.len()
+            );
+            *stream = Some(Stream::from_local_file(local_stream)?);
+        }
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/stream/mod.rs b/tools/avm_analyzer/avm_analyzer_app/src/stream/mod.rs
new file mode 100644
index 0000000..fa4c643
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/stream/mod.rs
@@ -0,0 +1,625 @@
+mod http_stream_manager;
+mod local_stream_manager;
+mod server_decode;
+
+pub use http_stream_manager::*;
+use itertools::Itertools;
+pub use local_stream_manager::*;
+pub use server_decode::*;
+
+use std::collections::{HashMap, HashSet};
+use std::io::Cursor;
+use std::path::Path;
+use std::sync::Arc;
+use std::{io::Read, sync::Mutex};
+
+use anyhow::anyhow;
+use avm_analyzer_common::{AvmStreamInfo, DecodeState};
+use avm_stats::{Frame, Spatial};
+
+use log::{info, warn};
+use poll_promise::Promise;
+use prost::Message;
+
+use web_time::Instant;
+use zip::ZipArchive;
+
+use crate::image_manager::{ImageManager, PixelDataManager};
+use crate::settings::FrameSortOrder;
+use local_stream_manager::LocalStreamInfo;
+use server_decode::PendingServerDecode;
+
+fn frame_from_bytes(bytes: &[u8]) -> anyhow::Result<Box<Frame>> {
+    let start = Instant::now();
+    let frame = Frame::decode(bytes)?;
+    let duration = Instant::now() - start;
+    info!(
+        "Decoded frame {} in {:.2}ms:  {}x{}, {} superblocks.",
+        frame.decode_index(),
+        duration.as_secs_f32() * 1000.0,
+        frame.width(),
+        frame.height(),
+        frame.superblocks.len(),
+    );
+    Ok(Box::new(frame))
+}
+
+pub struct LocalFileZipSource {
+    zip_bytes: Arc<[u8]>,
+}
+
+impl LocalFileZipSource {
+    fn new(zip_bytes: Arc<[u8]>) -> Self {
+        Self { zip_bytes }
+    }
+
+    fn load_frame(&self, index: usize, stream_info: &AvmStreamInfo) -> anyhow::Result<FrameStatus> {
+        let proto_path = stream_info.get_proto_path(index);
+        let mut archive = ZipArchive::new(Cursor::new(self.zip_bytes.clone()))?;
+        let proto_file = archive.by_name(&proto_path)?;
+        info!(
+            "Uncompressing {} bytes from {}.",
+            proto_file.compressed_size(),
+            proto_path
+        );
+        let start = Instant::now();
+        let proto_bytes = proto_file.bytes().collect::<Result<Vec<_>, _>>()?;
+        let duration = Instant::now() - start;
+        info!(
+            "Decompressed proto frame in {:.2}ms: {} bytes",
+            duration.as_secs_f32() * 1000.0,
+            proto_bytes.len()
+        );
+
+        let frame = frame_from_bytes(proto_bytes.as_slice())?;
+        Ok(FrameStatus::Loaded(frame))
+    }
+}
+
+pub struct HttpSource {
+    url: String,
+    decode_in_progress: bool,
+}
+
+impl HttpSource {
+    fn new(url: impl AsRef<str>, decode_in_progress: bool) -> Self {
+        Self {
+            url: url.as_ref().into(),
+            decode_in_progress,
+        }
+    }
+
+    fn load_frame(
+        &self,
+        index: usize,
+        stream_info: &AvmStreamInfo,
+        promises: &mut Vec<Promise<FramePromiseResult>>,
+    ) -> anyhow::Result<FrameStatus> {
+        let url = format!("{}/{}", self.url, stream_info.get_proto_path(index));
+        info!("Loading frame proto over HTTP: {url}");
+        let (sender, promise) = Promise::new();
+        let request = ehttp::Request::get(url);
+        ehttp::fetch(request, move |response| {
+            let frame = parse_proto_response(response);
+            sender.send(FramePromiseResult { frame, index });
+        });
+        promises.push(promise);
+        Ok(FrameStatus::Pending)
+    }
+}
+
+pub enum StreamSource {
+    LocalZipFile(LocalFileZipSource),
+    Http(HttpSource),
+}
+
+#[derive(Clone)]
+pub enum FrameStatus {
+    Unloaded,
+    Decoding,
+    Pending,
+    Invalid,
+    OutOfBounds,
+    Loaded(Box<Frame>),
+}
+
+impl FrameStatus {
+    fn is_selectable(&self) -> bool {
+        match self {
+            FrameStatus::Decoding | FrameStatus::Invalid | FrameStatus::OutOfBounds => false,
+            FrameStatus::Unloaded | FrameStatus::Pending | FrameStatus::Loaded(_) => true,
+        }
+    }
+    fn is_loaded(&self) -> bool {
+        matches!(self, FrameStatus::Loaded(_))
+    }
+}
+
+struct FramePromiseResult {
+    frame: Result<Option<Box<Frame>>, anyhow::Error>,
+    index: usize,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum CacheStrategy {
+    Unlimited,
+    Limited(usize),
+}
+
+impl CacheStrategy {
+    pub fn from_limit(limit: Option<usize>) -> Self {
+        match limit {
+            Some(limit) => Self::Limited(limit),
+            None => Self::Unlimited,
+        }
+    }
+    fn keep_frame(&self, frame_index: usize, frame_visit_history: &[usize]) -> bool {
+        match self {
+            Self::Unlimited => true,
+            Self::Limited(limit) => {
+                let mut prev_frames = HashSet::new();
+                for prev_frame in frame_visit_history.iter().rev() {
+                    prev_frames.insert(prev_frame);
+                    if prev_frames.len() == *limit {
+                        break;
+                    }
+                }
+                prev_frames.contains(&frame_index)
+            }
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum StreamEventType {
+    NewStream,
+    // Note: The first frame loaded is not necessarily the first frame of the stream.
+    FirstFrameLoaded(usize),
+    FrameChanged(usize),
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct StreamEvent {
+    pub event: StreamEventType,
+    lifetime: i32,
+}
+
+impl StreamEvent {
+    pub fn new(event: StreamEventType) -> Self {
+        Self { event, lifetime: 2 }
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+enum ChangeFrameKind {
+    Next,
+    Prev,
+    First,
+    Last,
+    Index(usize),
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct ChangeFrame {
+    order: FrameSortOrder,
+    kind: ChangeFrameKind,
+    loaded_only: bool,
+    allow_loop: bool,
+}
+
+impl Default for ChangeFrame {
+    fn default() -> Self {
+        Self {
+            order: FrameSortOrder::Decode,
+            kind: ChangeFrameKind::Index(0),
+            loaded_only: false,
+            allow_loop: false,
+        }
+    }
+}
+
+impl ChangeFrame {
+    pub fn next() -> Self {
+        Self {
+            kind: ChangeFrameKind::Next,
+            ..Default::default()
+        }
+    }
+
+    pub fn prev() -> Self {
+        Self {
+            kind: ChangeFrameKind::Prev,
+            ..Default::default()
+        }
+    }
+
+    pub fn first() -> Self {
+        Self {
+            kind: ChangeFrameKind::First,
+            ..Default::default()
+        }
+    }
+
+    pub fn last() -> Self {
+        Self {
+            kind: ChangeFrameKind::Last,
+            ..Default::default()
+        }
+    }
+    pub fn index(index: usize) -> Self {
+        Self {
+            kind: ChangeFrameKind::Index(index),
+            ..Default::default()
+        }
+    }
+    pub fn loaded_only(mut self, loaded_only: bool) -> Self {
+        self.loaded_only = loaded_only;
+        self
+    }
+
+    pub fn allow_loop(mut self, allow_loop: bool) -> Self {
+        self.allow_loop = allow_loop;
+        self
+    }
+
+    pub fn order(mut self, order: FrameSortOrder) -> Self {
+        self.order = order;
+        self
+    }
+}
+
+pub struct Stream {
+    pub stream_info: AvmStreamInfo,
+    pub source: StreamSource,
+    pub frames: Vec<FrameStatus>,
+    pub images: ImageManager,
+    pub pixel_data: PixelDataManager,
+    promises: Mutex<Vec<Promise<FramePromiseResult>>>,
+    // TODO(comc): Handle decode_index to proto frame index mapping.
+    pub current_frame_index: usize,
+    events: Vec<StreamEvent>,
+    have_first_frame: bool,
+    pub frame_visit_history: Vec<usize>,
+}
+
+impl Stream {
+    fn new(stream_info: AvmStreamInfo, source: StreamSource) -> Self {
+        let mut default_frame_status = FrameStatus::Unloaded;
+        if let StreamSource::Http(ref http_source) = source {
+            if http_source.decode_in_progress {
+                default_frame_status = FrameStatus::Decoding;
+            }
+        }
+        let num_frames = stream_info.num_frames;
+        Self {
+            stream_info,
+            source,
+            frames: vec![default_frame_status; num_frames],
+            promises: Mutex::new(Vec::new()),
+            images: ImageManager::default(),
+            pixel_data: PixelDataManager::default(),
+            current_frame_index: usize::MAX,
+            events: vec![StreamEvent::new(StreamEventType::NewStream)],
+            have_first_frame: false,
+            frame_visit_history: vec![0],
+        }
+    }
+
+    pub fn from_http(
+        stream_info: AvmStreamInfo,
+        decode_in_progress: bool,
+        first_first_to_load: usize,
+        streams_url: &str
+    ) -> anyhow::Result<Self> {
+        let source = StreamSource::Http(HttpSource::new(streams_url, decode_in_progress));
+        let mut stream = Stream::new(stream_info, source);
+        stream.set_current_frame(first_first_to_load, false);
+        Ok(stream)
+    }
+
+    pub fn from_local_file(local_stream: LocalStreamInfo) -> anyhow::Result<Self> {
+        let stream_info = local_stream.get_stream_info()?;
+        let source = StreamSource::LocalZipFile(LocalFileZipSource::new(local_stream.zip_bytes));
+        let mut stream = Stream::new(stream_info, source);
+        stream.set_current_frame(0, false);
+        Ok(stream)
+    }
+
+    // Add a method on Frame for this.
+    pub fn have_orig_yuv(&self) -> bool {
+        if let FrameStatus::Loaded(frame) = self.get_frame(0) {
+            if let Some(superblock) = &frame.superblocks.first() {
+                if let Some(pixel_data) = &superblock.pixel_data.first() {
+                    if pixel_data.original.is_some() {
+                        return true;
+                    }
+                }
+            }
+        }
+        false
+    }
+
+    /// Looks up an order hint from a motion vector and converts it into a decode index.
+    pub fn lookup_order_hint(&self, order_hint: i32) -> Option<usize> {
+        for index in (0..self.current_frame_index).rev() {
+            if let FrameStatus::Loaded(frame) = self.get_frame(index) {
+                if frame.frame_params.as_ref().unwrap().raw_display_index == order_hint {
+                    return Some(index);
+                }
+            }
+        }
+        None
+    }
+
+    pub fn update_pending_decodes(&mut self, pending_decodes: &mut HashMap<String, PendingServerDecode>) {
+        for pending_decode in pending_decodes.values_mut() {
+            let pending_decode_matches = {
+                if let StreamSource::Http(ref http_source) = self.source {
+                    http_source.decode_in_progress
+                        && pending_decode.stream_info.stream_name == self.stream_info.stream_name
+                } else {
+                    false
+                }
+            };
+            let mut decode_finished = false;
+            if pending_decode_matches {
+                match pending_decode.state {
+                    DecodeState::Complete(num_frames) => {
+                        for i in 0..num_frames {
+                            let frame = &mut self.frames[i];
+                            if let FrameStatus::Decoding = frame {
+                                *frame = FrameStatus::Unloaded
+                            }
+                        }
+                        if num_frames > 0 && !pending_decode.started_loading_first_frame {
+                            self.set_current_frame(0, false);
+                            pending_decode.started_loading_first_frame = true;
+                        }
+                        decode_finished = true;
+                    }
+                    DecodeState::Pending(ref pending) => {
+                        for i in 0..pending.decoded_frames {
+                            let frame = &mut self.frames[i];
+                            if let FrameStatus::Decoding = frame {
+                                *frame = FrameStatus::Unloaded
+                            }
+                        }
+                        if pending.decoded_frames > 0 && !pending_decode.started_loading_first_frame {
+                            self.set_current_frame(0, false);
+                            pending_decode.started_loading_first_frame = true;
+                        }
+                    }
+                    DecodeState::Failed => {
+                        for frame in self.frames.iter_mut() {
+                            if let FrameStatus::Decoding = frame {
+                                *frame = FrameStatus::Invalid
+                            }
+                        }
+                    }
+                    DecodeState::Uploading => {}
+                    DecodeState::UploadComplete => {}
+                }
+            }
+            if decode_finished {
+                if let StreamSource::Http(http_source) = &mut self.source {
+                    http_source.decode_in_progress = false;
+                }
+            }
+        }
+    }
+
+    pub fn current_frame(&self) -> Option<&Frame> {
+        if let FrameStatus::Loaded(ref frame) = self.get_frame(self.current_frame_index) {
+            Some(frame)
+        } else {
+            None
+        }
+    }
+
+    /// Sets the current frame to `index`.
+    /// If `loaded_only` is true, only changed the frame if it is already loaded.
+    /// Returns true if the frame was changed.
+    fn set_current_frame(&mut self, index: usize, loaded_only: bool) -> bool {
+        if index < self.num_frames()
+            && index != self.current_frame_index
+            && self.frames[index].is_selectable()
+            && (self.frames[index].is_loaded() || !loaded_only)
+        {
+            self.current_frame_index = index;
+            if let Err(err) = self.load_frame(self.current_frame_index) {
+                warn!("Loading frame {} failed: {err:?}.", self.current_frame_index);
+            }
+            self.events.push(StreamEvent::new(StreamEventType::FrameChanged(index)));
+            self.frame_visit_history.push(index);
+            true
+        } else {
+            false
+        }
+    }
+
+    pub fn apply_cache_strategy(&mut self, cache_strategy: CacheStrategy) {
+        for frame_index in 0..self.num_frames() {
+            if !cache_strategy.keep_frame(frame_index, &self.frame_visit_history) {
+                self.unload_frame(frame_index);
+            }
+        }
+    }
+
+    pub fn unload_frame(&mut self, index: usize) {
+        if matches!(self.frames[index], FrameStatus::Loaded(_)) {
+            self.frames[index] = FrameStatus::Unloaded;
+        }
+    }
+
+    pub fn change_frame(&mut self, change: ChangeFrame) -> bool {
+        let sorted_frames = self.get_sorted_frames(change.order);
+        let Some(current_index) = sorted_frames.iter().position(|&i| i == self.current_frame_index) else {
+            return false;
+        };
+
+        match change.kind {
+            ChangeFrameKind::First => self.set_current_frame(sorted_frames[0], change.loaded_only),
+            ChangeFrameKind::Last => self.set_current_frame(sorted_frames[sorted_frames.len() - 1], change.loaded_only),
+            ChangeFrameKind::Index(index) => {
+                if let Some(sorted_index) = sorted_frames.get(index) {
+                    self.set_current_frame(*sorted_index, change.loaded_only)
+                } else {
+                    false
+                }
+            }
+            ChangeFrameKind::Prev => {
+                if current_index > 0 {
+                    self.set_current_frame(sorted_frames[current_index - 1], change.loaded_only)
+                } else {
+                    false
+                }
+            }
+            ChangeFrameKind::Next => {
+                let next_index = if change.allow_loop {
+                    (current_index + 1) % sorted_frames.len()
+                } else {
+                    current_index + 1
+                };
+                if next_index < sorted_frames.len() {
+                    // Try to change to the next frame. If it fails (e.g. because that frame is not yet loaded) and looping is on, try loading the first frame instead.
+                    let frame_changed = self.set_current_frame(sorted_frames[next_index], change.loaded_only);
+                    if !frame_changed && change.allow_loop {
+                        self.set_current_frame(sorted_frames[0], change.loaded_only)
+                    } else {
+                        frame_changed
+                    }
+                } else {
+                    false
+                }
+            }
+        }
+    }
+
+    pub fn get_sorted_frames(&self, frame_sort_order: FrameSortOrder) -> Vec<usize> {
+        let sorted_indices = match frame_sort_order {
+            FrameSortOrder::Decode => (0..self.num_frames()).collect_vec(),
+            // TODO(comc): There's not really a good way to sort frames by display order
+            // ahead-of-time unless they're already loaded. For now, the decode index is used
+            // as the sort key if we don't have the display index yet.
+            FrameSortOrder::Display => self
+                .frames
+                .iter()
+                .enumerate()
+                .sorted_by_key(|(index, frame)| match frame {
+                    FrameStatus::Loaded(frame) => (frame.display_index(), *index, true),
+                    _ => (*index, *index, false),
+                })
+                .map(|(index, _)| index)
+                .collect_vec(),
+        };
+        sorted_indices
+    }
+
+    pub fn get_frame(&self, index: usize) -> &FrameStatus {
+        self.frames.get(index).unwrap_or(&FrameStatus::OutOfBounds)
+    }
+
+    pub fn load_frame(&mut self, index: usize) -> anyhow::Result<()> {
+        let do_load = matches!(self.frames.get(index), Some(FrameStatus::Unloaded));
+        if do_load {
+            self.frames[index] = match &self.source {
+                StreamSource::LocalZipFile(local) => {
+                    let frame = local.load_frame(index, &self.stream_info)?;
+                    if !self.have_first_frame {
+                        self.have_first_frame = true;
+                        self.events
+                            .push(StreamEvent::new(StreamEventType::FirstFrameLoaded(index)));
+                    }
+                    frame
+                }
+                StreamSource::Http(http) => {
+                    http.load_frame(index, &self.stream_info, &mut self.promises.lock().unwrap())?
+                }
+            };
+        }
+        Ok(())
+    }
+
+    pub fn num_frames(&self) -> usize {
+        self.stream_info.num_frames
+    }
+
+    pub fn check_promises(&mut self) {
+        let mut promises = self.promises.lock().unwrap();
+        promises.retain_mut(|promise| {
+            if let Some(result) = promise.ready_mut() {
+                let frame = result.frame.as_mut();
+                match frame {
+                    Ok(frame) => {
+                        self.frames[result.index] = FrameStatus::Loaded(frame.take().unwrap());
+                        if !self.have_first_frame {
+                            self.have_first_frame = true;
+                            self.events
+                                .push(StreamEvent::new(StreamEventType::FirstFrameLoaded(result.index)));
+                        }
+                    }
+                    Err(_err) => {
+                        self.frames[result.index] = FrameStatus::Invalid;
+                    }
+                }
+                false
+            } else {
+                true
+            }
+        });
+    }
+
+    pub fn check_events(&mut self) -> Vec<StreamEvent> {
+        let events = self.events.clone();
+        for event in self.events.iter_mut() {
+            event.lifetime -= 1;
+        }
+        self.events.retain(|ev| ev.lifetime > 0);
+        events
+    }
+}
+
+/// Helper to check if we have a current frame on an Option<Stream>.
+pub trait CurrentFrame {
+    fn current_frame(&self) -> Option<&Frame>;
+    fn current_frame_is_loaded(&self) -> bool;
+}
+
+impl CurrentFrame for Option<Stream> {
+    fn current_frame(&self) -> Option<&Frame> {
+        let stream = self.as_ref()?;
+        stream.current_frame()
+    }
+    fn current_frame_is_loaded(&self) -> bool {
+        self.current_frame().is_some()
+    }
+}
+
+fn parse_proto_response(response: Result<ehttp::Response, String>) -> anyhow::Result<Option<Box<Frame>>> {
+    let response = response.map_err(|err| anyhow!("HTTP error: {err}"))?;
+    let content_type = response.content_type().unwrap_or_default();
+    let frame = match content_type {
+        "application/octet-stream" => frame_from_bytes(response.bytes.as_slice())?,
+        "application/zip" => {
+            let mut archive = ZipArchive::new(Cursor::new(response.bytes.as_slice()))?;
+            let proto_file_name = archive
+                .file_names()
+                .filter(|n| n.ends_with(".pb"))
+                .sorted()
+                .next()
+                .ok_or(anyhow!("No protobufs (.pb) found in .zip archive."))?
+                .to_string();
+            let proto_file = archive.by_name(&proto_file_name)?;
+            let proto_bytes = proto_file.bytes().collect::<Result<Vec<_>, _>>()?;
+            frame_from_bytes(proto_bytes.as_slice())?
+        }
+        _ => {
+            return Err(anyhow!("Unexpected content type: {content_type}"));
+        }
+    };
+    Ok(Some(frame))
+}
+
+pub fn stream_name_from_file_name(file_name: &str) -> String {
+    Path::new(file_name).file_stem().unwrap().to_string_lossy().to_string()
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/stream/server_decode.rs b/tools/avm_analyzer/avm_analyzer_app/src/stream/server_decode.rs
new file mode 100644
index 0000000..f539388
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/stream/server_decode.rs
@@ -0,0 +1,248 @@
+use avm_analyzer_common::{AvmStreamInfo, DecodeProgress, DecodeState, ProgressResponse, StartDecodeResponse};
+use egui::DroppedFile;
+
+use log::{info, warn};
+
+use rand::distributions::Alphanumeric;
+use rand::Rng;
+use rfd::AsyncFileDialog;
+use std::collections::HashMap;
+use std::io::{self, Cursor, Write};
+use std::sync::{Arc, Mutex};
+use wasm_bindgen_futures::spawn_local;
+use web_time::Instant;
+
+use crate::stream::{stream_name_from_file_name, Stream};
+
+use super::HttpStreamManager;
+pub const HTTP_POLL_PERIOD_SECONDS: f32 = 1.0;
+pub const MAX_RETRIES: i32 = 5;
+
+#[derive(Clone)]
+pub struct PendingServerDecode {
+    pub stream_info: AvmStreamInfo,
+    pub state: DecodeState,
+    pub start_time: Instant,
+    pub started_loading_first_frame: bool,
+    // For retries:
+    pub bytes: Arc<[u8]>,
+    pub retry_count: i32,
+}
+
+impl PendingServerDecode {
+    fn new(name: &str, state: DecodeState, bytes: Arc<[u8]>, retries_left: i32) -> Self {
+        let stream_info = AvmStreamInfo {
+            stream_name: name.into(),
+            proto_path_template: "".into(),
+            num_frames: 0,
+            thumbnail_png: None,
+        };
+        Self {
+            stream_info,
+            state,
+            start_time: Instant::now(),
+            started_loading_first_frame: false,
+            bytes,
+            retry_count: retries_left,
+        }
+    }
+}
+
+pub struct ServerDecodeManager {
+    pub pending_decodes: Arc<Mutex<HashMap<String, PendingServerDecode>>>,
+    last_check_request: Instant,
+}
+
+impl ServerDecodeManager {
+    pub fn new() -> Self {
+        Self {
+            pending_decodes: Arc::new(Mutex::new(HashMap::new())),
+            last_check_request: Instant::now(),
+        }
+    }
+    pub fn prompt_stream(&self) {
+        let task = AsyncFileDialog::new()
+            .add_filter("AVM Stream", &["bin", "ivf", "obu"])
+            .pick_file();
+        let pending_decodes = self.pending_decodes.clone();
+        spawn_local(async move {
+            let file = task.await;
+            if let Some(file) = file {
+                let bytes = file.read().await;
+                upload(file.file_name().as_str(), bytes.into(), pending_decodes, MAX_RETRIES);
+            }
+        });
+    }
+
+    pub fn handle_dropped_file(&self, file: DroppedFile) {
+        let pending_decodes = self.pending_decodes.clone();
+        if let Some(bytes) = file.bytes {
+            spawn_local(async move {
+                upload(file.name.as_str(), bytes, pending_decodes, MAX_RETRIES);
+            });
+        }
+    }
+
+    pub fn check_newly_confirmed_uploads(&mut self, stream: &mut Option<Stream>, streams_url: &str) {
+        let mut pending_decodes = self.pending_decodes.lock().unwrap();
+        for (_name, pending_decode) in pending_decodes.iter_mut() {
+            if let DecodeState::UploadComplete = pending_decode.state {
+                *stream = Some(Stream::from_http(pending_decode.stream_info.clone(), true, 0, streams_url).unwrap());
+                pending_decode.state = DecodeState::Pending(DecodeProgress {
+                    decoded_frames: 0,
+                    total_frames: pending_decode.stream_info.num_frames,
+                });
+            }
+        }
+    }
+    fn check_for_retries(&mut self) {
+        let mut retries = Vec::new();
+        {
+            let mut pending_decodes = self.pending_decodes.lock().unwrap();
+            for (_name, pending_decode) in pending_decodes.iter_mut() {
+                if matches!(pending_decode.state, DecodeState::Failed) {
+                    retries.push(pending_decode.clone());
+                }
+            }
+        }
+        for retry in retries {
+            if retry.retry_count > 0 {
+                warn!("Retrying stream: {}. Retries remaining: {}", retry.stream_info.stream_name, retry.retry_count - 1);
+                upload(
+                    &retry.stream_info.stream_name,
+                    retry.bytes,
+                    self.pending_decodes.clone(),
+                    retry.retry_count - 1
+                );
+            }
+            else {
+                warn!("Stream: {} failed after {} retries.", retry.stream_info.stream_name, MAX_RETRIES);
+            }
+        }
+    }
+
+    pub fn check_progress(&mut self, stream: &mut Option<Stream>, http_manager: &mut HttpStreamManager, streams_url: &str) {
+        self.check_newly_confirmed_uploads(stream, streams_url);
+        self.check_for_retries();
+        let now = Instant::now();
+        let elapsed = now - self.last_check_request;
+        if elapsed.as_secs_f32() < HTTP_POLL_PERIOD_SECONDS {
+            return;
+        }
+        self.last_check_request = now;
+        let mut pending_decodes = self.pending_decodes.lock().unwrap();
+        for (_name, pending_decode) in pending_decodes.iter() {
+            if let DecodeState::Pending(_) = &pending_decode.state {
+                let request = ehttp::Request::get(format!(
+                    "/progress?stream_name={}",
+                    pending_decode.stream_info.stream_name
+                ));
+                let inner_pending_decodes = self.pending_decodes.clone();
+                ehttp::fetch(request, move |response| {
+                    if let Ok(response) = response {
+                        if let Some(json) = response.text() {
+                            if let Ok(response) = serde_json::from_str::<ProgressResponse>(json) {
+                                info!("Progress: {response:?}");
+                                let mut pending_decodes = inner_pending_decodes.lock().unwrap();
+                                if let Some(pending_decode) = pending_decodes.get_mut(&response.stream_name) {
+                                    pending_decode.state = response.state;
+                                } else {
+                                    warn!(
+                                        "Received decode progress update for unknown stream: {}.",
+                                        response.stream_name
+                                    );
+                                }
+                            }
+                        }
+                    }
+                });
+            }
+        }
+        if let Some(stream) = stream {
+            // TODO(comc): Move this logic elsewhere.
+            http_manager.update_pending_decodes(&pending_decodes);
+            stream.update_pending_decodes(&mut pending_decodes);
+        }
+    }
+}
+
+const MULTIPART_FORM_BOUNDARY_PREFIX: &str = "--------";
+const MULTIPART_FORM_BOUNDARY_LEN: usize = 16;
+
+fn random_boundary() -> String {
+    let alphanum: String = rand::thread_rng()
+        .sample_iter(Alphanumeric)
+        .take(MULTIPART_FORM_BOUNDARY_LEN)
+        .map(char::from)
+        .collect();
+    format!("{MULTIPART_FORM_BOUNDARY_PREFIX}{alphanum}")
+}
+
+fn create_upload_request(file_name: &str, bytes: Arc<[u8]>) -> Result<ehttp::Request, io::Error> {
+    let stream_name = stream_name_from_file_name(file_name);
+    let mut request_body = Vec::new();
+    let mut cursor = Cursor::new(&mut request_body);
+    let boundary = random_boundary();
+    write!(cursor, "--{}\r\n", boundary)?;
+    write!(
+        cursor,
+        "Content-Disposition: form-data; name=\"{stream_name}\"; filename=\"{file_name}\""
+    )?;
+    write!(cursor, "\r\nContent-Type: {}", mime::APPLICATION_OCTET_STREAM)?;
+    write!(cursor, "\r\n\r\n")?;
+    cursor.write_all(&bytes)?;
+    write!(cursor, "\r\n")?;
+    write!(cursor, "--{}--\r\n", boundary)?;
+    let content_type = format!("multipart/form-data; boundary={}", boundary);
+
+    Ok(ehttp::Request {
+        method: "POST".into(),
+        url: "/upload".into(),
+        body: request_body,
+        headers: ehttp::headers(&[("Accept", "*/*"), ("Content-Type", &content_type)]),
+    })
+}
+
+// TODO(comc): Handle duplicate stream names.
+pub fn upload(file_name: &str, bytes: Arc<[u8]>, pending_decodes: Arc<Mutex<HashMap<String, PendingServerDecode>>>, retries_left: i32) {
+    let stream_name = stream_name_from_file_name(file_name);
+    {
+        let mut pending_decodes = pending_decodes.lock().unwrap();
+        if let Some(existing_decode) = pending_decodes.get(&stream_name) {
+            match existing_decode.state {
+                DecodeState::Pending(_) | DecodeState::UploadComplete | DecodeState::Uploading => {
+                    warn!("Decode request for stream {stream_name} already pending.");
+                    return;
+                }
+                _ => {}
+            }
+        }
+        let new_decode = PendingServerDecode::new(&stream_name, DecodeState::Uploading, bytes.clone(), retries_left);
+        pending_decodes.insert(stream_name.clone(), new_decode);
+    }
+    let req = create_upload_request(file_name, bytes).unwrap();
+    // TODO(comc): Automatic retry on timeout.
+    ehttp::fetch(req, move |response| {
+        let mut decode_state = DecodeState::Failed;
+        let mut updated_stream_info = None;
+
+        if let Ok(response) = response {
+            if let Some(json) = response.text() {
+                info!("Got response: {}", json);
+                if let Ok(response) = serde_json::from_str::<StartDecodeResponse>(json) {
+                    updated_stream_info = Some(response.stream_info);
+                    decode_state = DecodeState::UploadComplete;
+                }
+            }
+        }
+        let mut pending_decodes = pending_decodes.lock().unwrap();
+        if let Some(pending_decode) = pending_decodes.get_mut(&stream_name) {
+            pending_decode.state = decode_state;
+            if let Some(updated_stream_info) = updated_stream_info {
+                pending_decode.stream_info = updated_stream_info;
+            }
+        } else {
+            warn!("Received status response for {stream_name}, but it no longer exists.");
+        }
+    });
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/block_info_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/block_info_viewer.rs
new file mode 100644
index 0000000..979be7c
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/block_info_viewer.rs
@@ -0,0 +1,118 @@
+use anyhow::anyhow;
+use egui::{Button, RichText, Ui};
+use egui_extras::{Column, TableBuilder};
+use log::warn;
+
+use crate::views::stats_viewer::create_file_download;
+use crate::{app_state::AppState, stream::CurrentFrame};
+
+use crate::views::{RenderView, SelectedObject};
+
+use super::SelectedObjectKind;
+
+pub struct BlockInfoViewer;
+
+// TODO(comc): Click on a MV to load the corresponding frame and block.
+impl RenderView for BlockInfoViewer {
+    fn title(&self) -> String {
+        "Block Info".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let Some(frame) = state.stream.current_frame() else {
+            // No frame loaded yet.
+            return Ok(());
+        };
+        let Some(SelectedObject {
+            kind: selected_object_kind,
+            ..
+        }) = &state.settings.selected_object
+        else {
+            // Nothing is selected.
+            return Ok(());
+        };
+        let selected_object_kind = selected_object_kind.clone();
+        let is_transform_unit = matches!(selected_object_kind, SelectedObjectKind::TransformUnit(_));
+        let is_superblock = matches!(selected_object_kind, SelectedObjectKind::Superblock(_));
+        if ui.add_enabled(!is_superblock, Button::new("Go to parent")).clicked() {
+            if let Some(parent) = selected_object_kind.get_parent(frame) {
+                state.settings.selected_object = Some(SelectedObject::new(parent));
+            }
+        }
+        ui.end_row();
+
+        if ui.button("Show pixels for current block").clicked() {
+            state.settings.sharable.show_pixel_viewer = true;
+        }
+        ui.end_row();
+
+        if let SelectedObjectKind::CodingUnit(cu) = selected_object_kind {
+            let ctx = cu.try_resolve(frame).ok_or(anyhow!("Invalid coding unit index"))?;
+            if ui.button("Dump current block as JSON").clicked() {
+                if let Some(stream) = &state.stream {
+                    let Some(rect) = selected_object_kind.rect(frame) else {
+                        return Err(anyhow!("Invalid coding unit index"));
+                    };
+                    let file_name = format!(
+                        "{}_frame_{:04}_block_{}x{}_x{}_y{}.json",
+                        stream.stream_info.stream_name,
+                        stream.current_frame_index,
+                        rect.width() as i32,
+                        rect.height() as i32,
+                        rect.left_top().x as i32,
+                        rect.left_top().y as i32,
+                    );
+
+                    let data = serde_json::to_string_pretty(ctx.coding_unit).unwrap();
+
+                    if let Err(err) = create_file_download(data.as_bytes(), &file_name) {
+                        warn!("Failed to create file download: {err:?}");
+                    }
+                }
+            }
+        }
+
+        // TODO(comc): Grey out instead of removing?
+        if is_transform_unit && ui.button("Show transform coeffs").clicked() {
+            state.settings.sharable.show_coeffs_viewer = true;
+        }
+
+        ui.separator();
+
+        let Ok(info) = state
+            .settings
+            .selected_object
+            .as_mut()
+            .unwrap()
+            .get_or_calculate_info(frame)
+        else {
+            return Ok(());
+        };
+
+        TableBuilder::new(ui)
+            .column(Column::initial(300.0).resizable(true))
+            .column(Column::remainder())
+            .striped(true)
+            .header(20.0, |mut header| {
+                header.col(|ui| {
+                    ui.label(RichText::new("Property").strong());
+                });
+                header.col(|ui| {
+                    ui.label(RichText::new("Value").strong());
+                });
+            })
+            .body(|mut body| {
+                for field in info.fields.iter() {
+                    body.row(20.0, |mut row| {
+                        row.col(|col| {
+                            col.label(&field.name);
+                        });
+                        row.col(|col| {
+                            col.label(&field.value);
+                        });
+                    });
+                }
+            });
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/coeffs_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/coeffs_viewer.rs
new file mode 100644
index 0000000..db1df64
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/coeffs_viewer.rs
@@ -0,0 +1,189 @@
+use avm_stats::MAX_COEFFS_SIZE;
+
+use anyhow::anyhow;
+use egui::{pos2, vec2, Align2, Color32, PointerButton, Rect, RichText, Rounding, Shape, Stroke, TextStyle, Ui, Vec2};
+use itertools::{Itertools, MinMaxResult};
+
+use crate::app_state::AppState;
+use crate::settings::CoeffViewSelect;
+use crate::views::RenderView;
+use crate::views::{ScreenBounds, SelectedObjectKind};
+use crate::views::{MIN_BLOCK_HEIGHT_FOR_TEXT, MIN_BLOCK_WIDTH_FOR_TEXT};
+
+pub struct CoeffsViewer;
+
+impl RenderView for CoeffsViewer {
+    fn title(&self) -> String {
+        "Coeffs View".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let Some(stream) = &state.stream else {
+            return Ok(());
+        };
+
+        let Some(frame) = stream.current_frame() else {
+            return Ok(());
+        };
+
+        let Some(selected_object) = &state.settings.selected_object else {
+            return Ok(());
+        };
+
+        let SelectedObjectKind::TransformUnit(transform_unit_index) = selected_object.kind.clone() else {
+            return Ok(());
+        };
+
+        let transform_unit_ctx = transform_unit_index
+            .try_resolve(frame)
+            .ok_or(anyhow!("Invalid transform unit index"))?;
+        let transform_unit = transform_unit_ctx.transform_unit;
+
+        let coeff_view_options = [
+            CoeffViewSelect::Dequantized,
+            CoeffViewSelect::Quantized,
+            CoeffViewSelect::DequantValue,
+        ];
+
+        ui.label(RichText::new("WARNING: Coeff data extraction is currently buggy.").color(Color32::RED));
+        ui.end_row();
+
+        egui::ComboBox::from_label("Coeff view")
+            .selected_text(state.settings.sharable.coeff_view_select.name())
+            .show_ui(ui, |ui| {
+                for coeff_option in coeff_view_options.iter() {
+                    ui.selectable_value(
+                        &mut state.settings.sharable.coeff_view_select,
+                        *coeff_option,
+                        coeff_option.name(),
+                    );
+                }
+            });
+        ui.end_row();
+
+        let Some(mut object_rect) = selected_object.rect(frame) else {
+            return Err(anyhow!("Invalid selected object"));
+        };
+        if state.settings.sharable.current_plane.use_chroma() {
+            object_rect = object_rect * 0.5;
+        }
+        let size = ui.available_size_before_wrap();
+        let (screen_bounds, response) = ui.allocate_exact_size(size, egui::Sense::drag());
+        let mut world_bounds = state.settings.sharable.coeffs_viewer_bounds;
+        let scale = world_bounds.calc_scale(screen_bounds);
+        let mut hover_pos_world = None;
+
+        // TODO(comc): Factor out this common code with frame_viewer and detailed_pixel_viewer.
+        if response.dragged_by(PointerButton::Primary) {
+            let delta = response.drag_delta() / -scale;
+            world_bounds = world_bounds.translate(delta);
+        } else if response.hover_pos().is_some() && ui.input(|i| i.scroll_delta) != Vec2::ZERO {
+            let delta = ui.input(|i| i.scroll_delta);
+
+            if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
+                if screen_bounds.contains(mouse_pos) {
+                    let zoom = if delta.y < 0.0 {
+                        f32::powf(1.001, -delta.y)
+                    } else {
+                        1.0 / (f32::powf(1.001, delta.y))
+                    };
+                    let zoom_center = world_bounds.screen_pos_to_world(mouse_pos, screen_bounds);
+                    world_bounds.zoom_point(zoom_center, zoom);
+                }
+            }
+        } else if response.hover_pos().is_some() {
+            if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
+                hover_pos_world = Some(world_bounds.screen_pos_to_world(mouse_pos, screen_bounds));
+            }
+        }
+        state.settings.sharable.coeffs_viewer_bounds = world_bounds;
+
+        let painter = ui.painter().with_clip_rect(response.rect);
+        let clip_rect = painter.clip_rect();
+        let mut shapes = Vec::new();
+        let ui_style = ui.ctx().style();
+        let mut hover_text = None;
+        let mut min_coeff = 0;
+        let mut max_coeff = 255;
+        let coeff_view = match state.settings.sharable.coeff_view_select {
+            CoeffViewSelect::DequantValue => &transform_unit.dequantizer_values,
+            CoeffViewSelect::Dequantized => &transform_unit.dequantized_coeffs,
+            CoeffViewSelect::Quantized => &transform_unit.quantized_coeffs,
+        };
+        match coeff_view.iter().minmax() {
+            MinMaxResult::NoElements | MinMaxResult::OneElement(_) => {}
+            MinMaxResult::MinMax(&min_v, &max_v) => {
+                min_coeff = min_v;
+                max_coeff = max_v;
+            }
+        };
+
+        // TODO(comc): Manually doing an inverse DCT does not yield the expected pixels for some blocks. Verify that the Residual = (PreFiltered - Prediction) logic makes sense.
+        let coeffs_width = MAX_COEFFS_SIZE.min(object_rect.width() as usize);
+        let coeffs_height = MAX_COEFFS_SIZE.min(object_rect.height() as usize);
+
+        for y in 0..coeffs_height {
+            for x in 0..coeffs_width {
+                let stride = coeffs_width;
+                let index = y * stride + x;
+
+                let coeff = coeff_view.get(index).copied();
+                let have_coeff = coeff.is_some();
+                let coeff = coeff.unwrap_or_default();
+                let color = (coeff - min_coeff) as f32 / (max_coeff - min_coeff) as f32;
+                let world_rect = Rect::from_min_size(pos2(x as f32, y as f32), vec2(1.0, 1.0));
+                let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+                if let Some(hover_pos_world) = hover_pos_world {
+                    if world_rect.contains(hover_pos_world) && have_coeff {
+                        hover_text = Some(format!("Coeff={} (x={}, y={})", coeff, x, y));
+                    }
+                }
+
+                shapes.push(Shape::rect_filled(
+                    screen_rect,
+                    Rounding::ZERO,
+                    Color32::from_gray((color * 255.0) as u8),
+                ));
+                shapes.push(Shape::rect_stroke(
+                    screen_rect,
+                    Rounding::ZERO,
+                    Stroke::new(1.0, Color32::from_gray(20)),
+                ));
+
+                if have_coeff
+                    && screen_rect.height() >= MIN_BLOCK_HEIGHT_FOR_TEXT
+                    && screen_rect.width() >= MIN_BLOCK_WIDTH_FOR_TEXT
+                {
+                    let overlay_style = &state.settings.persistent.style.overlay;
+                    shapes.extend(painter.fonts(|f| {
+                        let colors = if overlay_style.enable_text_shadows {
+                            vec![Color32::BLACK, overlay_style.pixel_viewer_text_color]
+                        } else {
+                            vec![overlay_style.pixel_viewer_text_color]
+                        };
+                        colors
+                            .into_iter()
+                            .enumerate()
+                            .map(|(i, color)| {
+                                let pos_offset = vec2(i as f32, i as f32);
+                                Shape::text(
+                                    f,
+                                    screen_rect.center() + pos_offset,
+                                    Align2::CENTER_CENTER,
+                                    format!("{coeff}"),
+                                    TextStyle::Body.resolve(&ui_style),
+                                    color,
+                                )
+                            })
+                            .collect::<Vec<_>>()
+                    }));
+                }
+            }
+        }
+        painter.extend(shapes);
+        if let Some(hover_text) = hover_text {
+            response.on_hover_text_at_pointer(hover_text);
+        }
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/controls_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/controls_viewer.rs
new file mode 100644
index 0000000..844cb4c
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/controls_viewer.rs
@@ -0,0 +1,253 @@
+use avm_stats::{Plane, PlaneType, Spatial};
+use egui::{vec2, Button, Checkbox, RichText, Ui};
+
+use crate::app_state::AppState;
+use crate::settings::{DistortionView, FrameSortOrder, MotionFieldColoring};
+use crate::stream::{ChangeFrame, CurrentFrame};
+use crate::views::RenderView;
+
+use super::ViewMode;
+
+const PLAYBACK_BUTTON_HEIGHT: f32 = 30.0;
+const PLAYBACK_BUTTON_WIDTH: f32 = 30.0;
+const PLAYBACK_BUTTON_FONT_SIZE: f32 = 18.0;
+
+fn create_playback_button(icon: &str) -> Button {
+    Button::new(RichText::new(format!(" {icon} ")).size(PLAYBACK_BUTTON_FONT_SIZE))
+        .min_size(vec2(PLAYBACK_BUTTON_WIDTH, PLAYBACK_BUTTON_HEIGHT))
+}
+
+pub struct ControlsViewer;
+
+impl RenderView for ControlsViewer {
+    fn title(&self) -> String {
+        "Controls".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        ui.horizontal(|ui| {
+            ui.vertical(|ui| {
+                ui.label("View");
+                let frame_sort_options = [FrameSortOrder::Decode, FrameSortOrder::Display];
+                egui::ComboBox::from_label("Frame sort order")
+                    .selected_text(format!("{:?}", state.settings.persistent.frame_sort_order))
+                    .show_ui(ui, |ui| {
+                        for &sort_option in frame_sort_options.iter() {
+                            ui.selectable_value(
+                                &mut state.settings.persistent.frame_sort_order,
+                                sort_option,
+                                format!("{sort_option:?}"),
+                            );
+                        }
+                    });
+                ui.end_row();
+
+                let plane_options = [
+                    PlaneType::Planar(Plane::Y),
+                    PlaneType::Planar(Plane::U),
+                    PlaneType::Planar(Plane::V),
+                    PlaneType::Rgb,
+                ];
+
+                egui::ComboBox::from_label("Plane view")
+                    .selected_text(format!("{}", state.settings.sharable.current_plane))
+                    .show_ui(ui, |ui| {
+                        for &plane_option in plane_options.iter() {
+                            ui.selectable_value(
+                                &mut state.settings.sharable.current_plane,
+                                plane_option,
+                                format!("{plane_option}"),
+                            );
+                        }
+                    });
+                ui.end_row();
+
+                ui.add_enabled(
+                    state.settings.sharable.view_mode.view_settings().pixel_type.is_delta(),
+                    egui::Checkbox::new(&mut state.settings.sharable.show_relative_delta, "Show relative delta"),
+                );
+
+                ui.end_row();
+
+                ui.checkbox(&mut state.settings.sharable.show_overlay, "Show overlay");
+
+                ui.end_row();
+
+                ui.checkbox(&mut state.settings.sharable.show_yuv, "Show YUV");
+
+                ui.end_row();
+
+                if ui.button("Reset Zoom").clicked() {
+                    if let Some(frame) = state.stream.current_frame() {
+                        state.settings.sharable.world_bounds = frame.rect();
+                    }
+                }
+            });
+            if let Some(stream) = &mut state.stream {
+                ui.separator();
+                ui.vertical(|ui| {
+                    ui.label("Playback");
+                    ui.horizontal(|ui| {
+                        let frame_sort_order: FrameSortOrder = state.settings.persistent.frame_sort_order;
+                        if ui
+                            .add(create_playback_button("⏮"))
+                            .on_hover_text("Go to first frame")
+                            .clicked()
+                        {
+                            stream.change_frame(ChangeFrame::first().order(frame_sort_order));
+                        }
+                        if ui
+                            .add(create_playback_button("⏪"))
+                            .on_hover_text("Go to previous frame")
+                            .clicked()
+                        {
+                            stream.change_frame(ChangeFrame::prev().order(frame_sort_order));
+                        }
+                        let play_pause = if state.settings.playback.playback_running {
+                            "⏸"
+                        } else {
+                            "▶"
+                        };
+                        if ui
+                            .add(create_playback_button(play_pause))
+                            .on_hover_text("Start/stop playback")
+                            .clicked()
+                        {
+                            state.settings.playback.playback_running = !state.settings.playback.playback_running;
+                        }
+
+                        if ui
+                            .add(create_playback_button("⏩"))
+                            .on_hover_text("Go to next frame")
+                            .clicked()
+                        {
+                            stream.change_frame(ChangeFrame::next().order(frame_sort_order));
+                        }
+                        if ui
+                            .add(create_playback_button("⏭"))
+                            .on_hover_text("Go to last frame")
+                            .clicked()
+                        {
+                            stream.change_frame(ChangeFrame::last().order(frame_sort_order));
+                        }
+                    });
+                    // TODO(comc): Re-enable after adding pause logic for pending loads.
+                    // ui.checkbox(
+                    //     &mut state.settings.playback.show_loaded_frames_only,
+                    //     "Playback loaded frames only",
+                    // );
+                    ui.checkbox(&mut state.settings.playback.playback_loop, "Loop playback");
+                    ui.label("Playback FPS:");
+                    ui.add(egui::Slider::new(&mut state.settings.playback.playback_fps, 1.0..=60.0).step_by(1.0));
+                });
+            }
+
+            if state.settings.sharable.view_mode == ViewMode::Heatmap {
+                ui.separator();
+                ui.vertical(|ui| {
+                    ui.label("Heatmap");
+                    ui.checkbox(&mut state.settings.sharable.show_heatmap_legend, "Show Heatmap Legend");
+                    ui.end_row();
+                    ui.label("Histogram buckets");
+                    ui.add(egui::Slider::new(
+                        &mut state.settings.sharable.heatmap_settings.histogram_buckets,
+                        4..=100,
+                    ));
+                    ui.end_row();
+                    ui.checkbox(
+                        &mut state.settings.sharable.heatmap_histogram_log_scale,
+                        "Histogram log scale",
+                    );
+                    ui.end_row();
+                    ui.label("Symbol filter");
+                    ui.text_edit_singleline(&mut state.settings.sharable.heatmap_settings.symbol_filter);
+                    if ui.button("Recalculate heatmap").clicked() {
+                        if let Some(stream) = &state.stream {
+                            stream.images.clear_heatmaps();
+                        }
+                    }
+                });
+            }
+
+            if let ViewMode::Distortion(mut distortion_view) = state.settings.sharable.view_mode {
+                ui.separator();
+                ui.vertical(|ui| {
+                    let distortion_view_options = [
+                        DistortionView::Distortion,
+                        DistortionView::Original,
+                        DistortionView::Reconstruction,
+                    ];
+                    egui::ComboBox::from_label("Displayed pixels")
+                        .selected_text(distortion_view.name())
+                        .show_ui(ui, |ui| {
+                            for &distortion_view_option in distortion_view_options.iter() {
+                                ui.selectable_value(
+                                    &mut distortion_view,
+                                    distortion_view_option,
+                                    distortion_view_option.name(),
+                                );
+                            }
+                        });
+                    ui.end_row();
+                });
+                state.settings.sharable.view_mode = ViewMode::Distortion(distortion_view);
+            }
+
+            if state.settings.sharable.view_mode == ViewMode::Motion {
+                ui.separator();
+                ui.vertical(|ui| {
+                    ui.label("Motion");
+                    ui.checkbox(&mut state.settings.sharable.motion_field.show, "Show motion field");
+                    ui.end_row();
+                    ui.checkbox(
+                        &mut state.settings.sharable.motion_field.show_origin,
+                        "Show motion vector origin",
+                    );
+                    ui.end_row();
+                    ui.add_enabled(
+                        state.settings.sharable.motion_field.show,
+                        Checkbox::new(
+                            &mut state.settings.sharable.motion_field.normalize,
+                            "Normalize vector lengths",
+                        ),
+                    );
+                    ui.end_row();
+                    ui.label("Motion vector scale:");
+                    ui.add(egui::Slider::new(&mut state.settings.sharable.motion_field.scale, 0.1..=10.0).step_by(0.1));
+
+                    let coloring_options = [
+                        MotionFieldColoring::RefFrames,
+                        MotionFieldColoring::PastFuture,
+                        MotionFieldColoring::Monochrome,
+                    ];
+                    egui::ComboBox::from_label("Coloring scheme")
+                        .selected_text(state.settings.sharable.motion_field.coloring.name())
+                        .show_ui(ui, |ui| {
+                            for &coloring_option in coloring_options.iter() {
+                                ui.selectable_value(
+                                    &mut state.settings.sharable.motion_field.coloring,
+                                    coloring_option,
+                                    coloring_option.name(),
+                                );
+                            }
+                        });
+                    ui.end_row();
+
+                    ui.label("Granularity (in 4x4 units):");
+                    ui.checkbox(&mut state.settings.sharable.motion_field.auto_granularity, "Automatic");
+                    ui.add_enabled(
+                        !state.settings.sharable.motion_field.auto_granularity,
+                        egui::Slider::new(&mut state.settings.sharable.motion_field.granularity, 1..=32),
+                    );
+                    if let Some(frame) = state.stream.current_frame() {
+                        if state.settings.sharable.motion_field.auto_granularity {
+                            state.settings.sharable.motion_field.granularity = frame.height() as usize / 64;
+                        }
+                    }
+                    ui.end_row();
+                });
+            }
+        });
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/decode_progress_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/decode_progress_viewer.rs
new file mode 100644
index 0000000..ddbe354
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/decode_progress_viewer.rs
@@ -0,0 +1,77 @@
+use avm_analyzer_common::DecodeState;
+use egui::{Color32, ProgressBar, Ui};
+use egui_extras::{Column, TableBuilder};
+use itertools::Itertools;
+
+use crate::app_state::AppState;
+use crate::stream::Stream;
+use crate::views::render_view::RenderView;
+
+pub struct DecodeProgressViewer;
+
+impl RenderView for DecodeProgressViewer {
+    fn title(&self) -> String {
+        "Decode Progress".into()
+    }
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let mut pending_decodes = state.server_decode_manager.pending_decodes.lock().unwrap();
+        if ui.button("Clear").clicked() {
+            pending_decodes.retain(|_k, v| !matches!(v.state, DecodeState::Complete(_) | DecodeState::Failed));
+        }
+        TableBuilder::new(ui)
+            .column(Column::initial(200.0).resizable(true))
+            .column(Column::initial(200.0).resizable(true))
+            .striped(true)
+            .header(20.0, |mut header| {
+                header.col(|ui| {
+                    ui.heading("Stream");
+                });
+                header.col(|ui| {
+                    ui.heading("Status");
+                });
+            })
+            .body(|body| {
+                let sorted_pending_decodes: Vec<_> = pending_decodes.values().sorted_by_key(|p| p.start_time).collect();
+                let num_rows: usize = sorted_pending_decodes.len();
+                body.rows(20.0, num_rows, |mut row| {
+                    let stream_info = &sorted_pending_decodes[row.index()].stream_info;
+                    let stream_name = &stream_info.stream_name;
+                    let decode_state = &sorted_pending_decodes[row.index()].state;
+                    let progress_bar = match decode_state {
+                        DecodeState::Complete(_) => ProgressBar::new(1.0).fill(Color32::LIGHT_GREEN).text("Finished"),
+                        DecodeState::Failed => ProgressBar::new(1.0).fill(Color32::RED).text("FAILED"),
+                        DecodeState::Pending(progress) => {
+                            let percent = progress.decoded_frames as f32 / progress.total_frames as f32;
+                            let text = format!(
+                                "{}/{} ({:.0}%)",
+                                progress.decoded_frames,
+                                progress.total_frames,
+                                percent * 100.0
+                            );
+                            ProgressBar::new(percent).text(text).animate(true)
+                        }
+                        DecodeState::UploadComplete => {
+                            let text = format!("0/{} (0%)", stream_info.num_frames);
+                            ProgressBar::new(1.0).text(text).animate(true)
+                        }
+                        DecodeState::Uploading => ProgressBar::new(1.0).fill(Color32::GRAY).text("Uploading"),
+                    };
+                    row.col(|ui| {
+                        ui.horizontal(|ui| {
+                            // TODO(comc): We could load pending streams too, as stream_select_viewer allows.
+                            let ready_to_load = matches!(decode_state, DecodeState::Complete(_));
+                            if ui.add_enabled(ready_to_load, egui::Button::new("Load")).clicked() {
+                                state.stream = Some(Stream::from_http(stream_info.clone(), false, 0, &state.settings.sharable.streams_url).unwrap());
+                            }
+
+                            ui.label(stream_name);
+                        });
+                    });
+                    row.col(|ui| {
+                        ui.add(progress_bar);
+                    });
+                });
+            });
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/detailed_pixel_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/detailed_pixel_viewer.rs
new file mode 100644
index 0000000..ba3c273
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/detailed_pixel_viewer.rs
@@ -0,0 +1,169 @@
+use anyhow::anyhow;
+use egui::{pos2, vec2, Align2, Color32, PointerButton, Rect, Rounding, Shape, Stroke, TextStyle, Ui, Vec2};
+
+use crate::app_state::AppState;
+use crate::views::RenderView;
+use crate::views::ScreenBounds;
+use crate::views::{MIN_BLOCK_HEIGHT_FOR_TEXT, MIN_BLOCK_WIDTH_FOR_TEXT};
+
+pub const MIN_BLOCK_SIZE_FOR_GRID: f32 = 4.0;
+pub struct DetailedPixelViewer;
+
+impl RenderView for DetailedPixelViewer {
+    fn title(&self) -> String {
+        "Pixel View".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let Some(stream) = &state.stream else {
+            return Ok(());
+        };
+
+        let Some(frame) = stream.current_frame() else {
+            return Ok(());
+        };
+
+        let Some(selected_object) = &state.settings.selected_object else {
+            return Ok(());
+        };
+
+        let plane = state.settings.sharable.current_plane.to_plane();
+        let Ok(pixel_data) = stream.pixel_data.get_or_create_pixels(
+            frame,
+            plane,
+            state.settings.sharable.view_mode.view_settings().pixel_type,
+        ) else {
+            return Ok(());
+        };
+        let mut object_rect = selected_object.rect(frame).ok_or(anyhow!("Invalid selected object"))?;
+        if plane.is_chroma() {
+            object_rect = object_rect / 2.0;
+        }
+
+        let size = ui.available_size_before_wrap();
+        let (screen_bounds, response) = ui.allocate_exact_size(size, egui::Sense::drag());
+        let mut world_bounds = state.settings.sharable.pixel_viewer_bounds;
+        let scale = world_bounds.calc_scale(screen_bounds);
+        let mut hover_pos_world = None;
+
+        if response.dragged_by(PointerButton::Primary) {
+            let delta = response.drag_delta() / -scale;
+            world_bounds = world_bounds.translate(delta);
+        } else if response.hover_pos().is_some() && ui.input(|i| i.scroll_delta) != Vec2::ZERO {
+            let delta = ui.input(|i| i.scroll_delta);
+
+            if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
+                if screen_bounds.contains(mouse_pos) {
+                    let zoom = if delta.y < 0.0 {
+                        f32::powf(1.001, -delta.y)
+                    } else {
+                        1.0 / (f32::powf(1.001, delta.y))
+                    };
+                    let zoom_center = world_bounds.screen_pos_to_world(mouse_pos, screen_bounds);
+                    world_bounds.zoom_point(zoom_center, zoom);
+                }
+            }
+        } else if response.hover_pos().is_some() {
+            if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
+                hover_pos_world = Some(world_bounds.screen_pos_to_world(mouse_pos, screen_bounds));
+            }
+        }
+        state.settings.sharable.pixel_viewer_bounds = world_bounds;
+
+        let painter = ui.painter().with_clip_rect(response.rect);
+        let clip_rect = painter.clip_rect();
+        let mut shapes = Vec::new();
+        let ui_style = ui.ctx().style();
+        let mut hover_text = None;
+        for y in 0..object_rect.height() as usize {
+            for x in 0..object_rect.width() as usize {
+                let stride = pixel_data.width as usize;
+                let offset_x = object_rect.left_top().x as usize;
+                let offset_y = object_rect.left_top().y as usize;
+                let abs_x = x + offset_x;
+                let abs_y = y + offset_y;
+                let in_bounds = abs_x < pixel_data.width as usize && abs_y < pixel_data.height as usize;
+                let index = (y + offset_y) * stride + x + offset_x;
+                let pixel = if in_bounds {
+                    pixel_data.pixels.get(index).copied()
+                } else {
+                    None
+                };
+
+                let mut color = pixel.unwrap_or(0);
+                let pixel_max = 1 << pixel_data.bit_depth;
+                if pixel_data.pixel_type.is_delta() {
+                    color = (color + pixel_max - 1) / 2;
+                }
+                if pixel_data.bit_depth > 8 {
+                    color >>= pixel_data.bit_depth - 8;
+                }
+                let world_rect = Rect::from_min_size(pos2(x as f32, y as f32), vec2(1.0, 1.0));
+                let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+                if let Some(hover_pos_world) = hover_pos_world {
+                    if world_rect.contains(hover_pos_world) {
+                        if let Some(pixel) = pixel {
+                            hover_text = Some(format!(
+                                "{}, Relative: (x={}, y={}), Absolute: (x={}, y={})",
+                                pixel,
+                                x,
+                                y,
+                                x + offset_x,
+                                y + offset_y
+                            ));
+                        }
+                    }
+                }
+
+                shapes.push(Shape::rect_filled(
+                    screen_rect,
+                    Rounding::ZERO,
+                    Color32::from_gray(color as u8),
+                ));
+                if screen_rect.width() >= MIN_BLOCK_SIZE_FOR_GRID {
+                    shapes.push(Shape::rect_stroke(
+                        screen_rect,
+                        Rounding::ZERO,
+                        Stroke::new(1.0, Color32::from_gray(20)),
+                    ));
+                }
+
+                if let Some(pixel) = pixel {
+                    if screen_rect.height() >= MIN_BLOCK_HEIGHT_FOR_TEXT
+                        && screen_rect.width() >= MIN_BLOCK_WIDTH_FOR_TEXT
+                        && clip_rect.intersects(screen_rect)
+                    {
+                        let overlay_style = &state.settings.persistent.style.overlay;
+                        shapes.extend(painter.fonts(|f| {
+                            let colors = if overlay_style.enable_text_shadows {
+                                vec![Color32::BLACK, overlay_style.pixel_viewer_text_color]
+                            } else {
+                                vec![overlay_style.pixel_viewer_text_color]
+                            };
+                            colors
+                                .into_iter()
+                                .enumerate()
+                                .map(|(i, color)| {
+                                    let pos_offset = vec2(i as f32, i as f32);
+                                    Shape::text(
+                                        f,
+                                        screen_rect.center() + pos_offset,
+                                        Align2::CENTER_CENTER,
+                                        format!("{pixel}"),
+                                        TextStyle::Body.resolve(&ui_style),
+                                        color,
+                                    )
+                                })
+                                .collect::<Vec<_>>()
+                        }));
+                    }
+                }
+            }
+        }
+        painter.extend(shapes);
+        if let Some(hover_text) = hover_text {
+            response.on_hover_text_at_pointer(hover_text);
+        }
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/drag_and_drop.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/drag_and_drop.rs
new file mode 100644
index 0000000..b8bd297
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/drag_and_drop.rs
@@ -0,0 +1,65 @@
+use egui::{Align2, Color32, Id, LayerId, Order, TextStyle};
+use itertools::Itertools;
+use log::warn;
+
+use crate::app_state::AppState;
+
+const KNOWN_AVM_STREAM_EXTENSIONS: &[&str] = &[".obu", ".bin", ".ivf"];
+const KNOWN_ZIP_EXTENSIONS: &[&str] = &[".zip"];
+
+const ZIP_MIME_TYPES: &[&str] = &["application/zip"];
+const AVM_STREAM_MIME_TYPES: &[&str] = &["application/macbinary", "application/octet-stream"];
+
+pub fn handle_drag_and_drop(ctx: &egui::Context, state: &mut AppState) {
+    preview_files_being_dropped(ctx);
+    ctx.input_mut(|i| {
+        let sorted_dropped_files = i.raw.dropped_files.drain(..).sorted_by_key(|file| file.name.clone());
+        for dropped_file in sorted_dropped_files {
+            if KNOWN_AVM_STREAM_EXTENSIONS
+                .iter()
+                .any(|ext| dropped_file.name.ends_with(ext))
+            {
+                state.server_decode_manager.handle_dropped_file(dropped_file);
+                state.settings.show_decode_progress = true;
+            } else if KNOWN_ZIP_EXTENSIONS.iter().any(|ext| dropped_file.name.ends_with(ext)) {
+                state.local_stream_manager.handle_dropped_file(dropped_file);
+            } else {
+                warn!("Unknown file type: {}", dropped_file.name);
+            }
+        }
+    });
+}
+
+fn preview_files_being_dropped(ctx: &egui::Context) {
+    if !ctx.input(|i| i.raw.hovered_files.is_empty()) {
+        let text = ctx.input(|i| {
+            if i.raw.hovered_files.len() > 1 {
+                format!("Decode multiple streams: {}", i.raw.hovered_files.len())
+            } else {
+                let mime_type = i.raw.hovered_files[0].mime.as_str();
+                if ZIP_MIME_TYPES.contains(&mime_type) {
+                    "Load local stream (.zip)".to_string()
+                } else if AVM_STREAM_MIME_TYPES.contains(&mime_type) {
+                    "Decode stream on server".to_string()
+                } else {
+                    format!(
+                        "Unsupported file type: {}",
+                        if mime_type.is_empty() { "Unknown" } else { mime_type }
+                    )
+                }
+            }
+        });
+
+        let painter = ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target")));
+
+        let screen_rect = ctx.screen_rect();
+        painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192));
+        painter.text(
+            screen_rect.center(),
+            Align2::CENTER_CENTER,
+            text,
+            TextStyle::Heading.resolve(&ctx.style()),
+            Color32::WHITE,
+        );
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/frame_info_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_info_viewer.rs
new file mode 100644
index 0000000..37d09d8
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_info_viewer.rs
@@ -0,0 +1,75 @@
+use avm_stats::Frame;
+use egui::{RichText, Ui};
+use egui_extras::{Column, TableBuilder};
+
+use crate::{app_state::AppState, stream::CurrentFrame};
+
+use crate::views::render_view::RenderView;
+
+// TODO(comc): PSNR, SSIM stats if available.
+pub struct FrameInfoViewer;
+impl FrameInfoViewer {
+    fn frame_info(frame: &Frame) -> Option<Vec<(String, String)>> {
+        let mut info = Vec::new();
+
+        let params = frame.frame_params.as_ref()?;
+        info.push(("Stream".into(), frame.stream_params.as_ref()?.stream_name.to_string()));
+        info.push(("Width".into(), format!("{}", params.width)));
+        info.push(("Height".into(), format!("{}", params.height)));
+        info.push(("Frame Type".into(), frame.frame_type_name()));
+        info.push(("TIP mode".into(), frame.tip_mode_name()));
+        info.push(("Decode Index".into(), params.decode_index.to_string()));
+        info.push(("Display Index".into(), params.display_index.to_string()));
+        if let Some(superblock_size) = params.superblock_size.as_ref() {
+            info.push((
+                "Superblock size".into(),
+                format!("{}x{}", superblock_size.width, superblock_size.height),
+            ));
+        }
+        // TODO(comc): Check why this is false for inter frames.
+        // info.push(("Show frame".into(), params.show_frame.to_string()));
+        info.push(("Base QIndex".into(), params.base_qindex.to_string()));
+        info.push(("Bit depth".into(), params.bit_depth.to_string()));
+        Some(info)
+    }
+}
+impl RenderView for FrameInfoViewer {
+    fn title(&self) -> String {
+        "Frame Info".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let Some(frame) = state.stream.current_frame() else {
+            return Ok(());
+        };
+
+        let Some(frame_info) = Self::frame_info(frame) else {
+            return Ok(());
+        };
+        TableBuilder::new(ui)
+            .column(Column::initial(300.0).resizable(true))
+            .column(Column::remainder())
+            .striped(true)
+            .header(20.0, |mut header| {
+                header.col(|ui| {
+                    ui.label(RichText::new("Property").strong());
+                });
+                header.col(|ui| {
+                    ui.label(RichText::new("Value").strong());
+                });
+            })
+            .body(|mut body| {
+                for (info_name, info_value) in frame_info {
+                    body.row(20.0, |mut row| {
+                        row.col(|col| {
+                            col.label(info_name);
+                        });
+                        row.col(|col| {
+                            col.label(info_value);
+                        });
+                    });
+                }
+            });
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/frame_select_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_select_viewer.rs
new file mode 100644
index 0000000..9f98b98
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_select_viewer.rs
@@ -0,0 +1,252 @@
+use avm_stats::{PixelType, PlaneType};
+use convert_case::{Case, Casing};
+use egui::{pos2, vec2, Align2, Color32, Mesh, Rect, Rounding, Shape, Stroke, TextStyle, Ui};
+
+use crate::app_state::AppState;
+use crate::image_manager::ImageType;
+use crate::stream::{ChangeFrame, CurrentFrame, FrameStatus};
+
+use crate::views::render_view::RenderView;
+
+use super::frame_viewer::REF_FRAME_COLORS;
+use super::{SelectedObjectKind, ViewMode};
+
+// TODO(comc): There must be a built-in way to do horizontal scrolling with the mouse wheel in egui.
+const SCROLL_FACTOR: f32 = 0.2;
+pub struct FrameSelectViewer;
+impl RenderView for FrameSelectViewer {
+    fn title(&self) -> String {
+        "Frame Select".into()
+    }
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let mut set_current_frame_index = None;
+        let Some(stream) = state.stream.as_ref() else {
+            return Ok(());
+        };
+        let scroll_to = state.settings.scroll_to_index.take();
+        let mut motion_vector_arrows: Vec<(usize, i32)> = Vec::new();
+        if state.settings.sharable.view_mode == ViewMode::Motion {
+            if let Some(selected_object) = state.settings.selected_object.as_ref() {
+                if let SelectedObjectKind::CodingUnit(cu) = selected_object.kind {
+                    if let Some(frame) = stream.current_frame() {
+                        if let Some(cu) = cu.try_resolve(frame) {
+                            for mv in cu.coding_unit.prediction_mode.as_ref().unwrap().motion_vectors.iter() {
+                                if mv.ref_frame_is_inter && !mv.ref_frame_is_tip {
+                                    let order_hint = mv.ref_frame_order_hint;
+                                    if let Some(frame_index) = stream.lookup_order_hint(order_hint) {
+                                        if !motion_vector_arrows
+                                            .iter()
+                                            .any(|(other_frame_index, _)| *other_frame_index == frame_index)
+                                        {
+                                            motion_vector_arrows.push((frame_index, mv.ref_frame))
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        let sorted_frame_indices = stream.get_sorted_frames(state.settings.persistent.frame_sort_order);
+        let mut frame_rects = vec![Rect::NOTHING; sorted_frame_indices.len()];
+        egui::ScrollArea::horizontal()
+            .max_height(200.0)
+            .drag_to_scroll(true)
+            .vscroll(false)
+            .show(ui, |ui| {
+                egui::Grid::new("img_grid").show(ui, |ui| {
+                    for &frame_id in sorted_frame_indices.iter() {
+                        let frame = stream.get_frame(frame_id);
+                        let (id, rect) = ui.allocate_space(egui::vec2(100.0, 100.0));
+                        frame_rects[frame_id] = rect;
+                        if let Some(scroll_to) = scroll_to {
+                            if scroll_to == frame_id {
+                                ui.scroll_to_rect(rect, None);
+                            }
+                        }
+                        let response = ui.interact(rect, id, egui::Sense::click());
+                        let scroll_delta = ui.input(|i| i.scroll_delta);
+                        if scroll_delta.length() > 0.0 {
+                            let scroll_delta = SCROLL_FACTOR * vec2(scroll_delta.y, scroll_delta.x);
+                            ui.scroll_with_delta(scroll_delta);
+                        }
+                        ui.painter()
+                            .add(Shape::rect_filled(rect, Rounding::ZERO, Color32::GRAY));
+
+                        let frame_id_text = if let FrameStatus::Loaded(frame) = frame {
+                            let display_index = frame.frame_params.as_ref().unwrap().display_index;
+                            format!("Frame {frame_id} ({display_index})")
+                        } else {
+                            format!("Frame {frame_id}")
+                        };
+                        let text = ui.fonts(|f| {
+                            // TODO(comc): Use display or decode index instead of "frame_id":
+                            Shape::text(
+                                f,
+                                rect.center_top(),
+                                Align2::CENTER_TOP,
+                                frame_id_text,
+                                TextStyle::Body.resolve(ui.style()),
+                                Color32::BLACK,
+                            )
+                        });
+                        ui.painter().add(text);
+
+                        match frame {
+                            FrameStatus::OutOfBounds => {}
+                            FrameStatus::Decoding => {
+                                let text = ui.fonts(|f| {
+                                    Shape::text(
+                                        f,
+                                        rect.center(),
+                                        Align2::CENTER_CENTER,
+                                        "Decoding...",
+                                        TextStyle::Body.resolve(ui.style()),
+                                        Color32::BLACK,
+                                    )
+                                });
+                                ui.painter().add(text);
+                            }
+                            FrameStatus::Invalid => {
+                                let text = ui.fonts(|f| {
+                                    Shape::text(
+                                        f,
+                                        rect.center(),
+                                        Align2::CENTER_CENTER,
+                                        "ERROR",
+                                        TextStyle::Body.resolve(ui.style()),
+                                        Color32::BLACK,
+                                    )
+                                });
+                                ui.painter().add(text);
+                            }
+                            FrameStatus::Pending => {
+                                let text = ui.fonts(|f| {
+                                    Shape::text(
+                                        f,
+                                        rect.center(),
+                                        Align2::CENTER_CENTER,
+                                        "Loading...",
+                                        TextStyle::Body.resolve(ui.style()),
+                                        Color32::BLACK,
+                                    )
+                                });
+                                ui.painter().add(text);
+                            }
+                            FrameStatus::Loaded(frame) => {
+                                // TODO(comc): Don't unwrap!
+                                let image_type =
+                                    ImageType::new(PlaneType::Rgb, PixelType::Reconstruction, false, false);
+                                let Ok(texture_handle) = stream.images.get_or_create_image(
+                                    ui.ctx(),
+                                    &stream.pixel_data,
+                                    frame,
+                                    image_type,
+                                    &state.settings.sharable.heatmap_settings,
+                                ) else {
+                                    return;
+                                };
+                                let mut image_mesh = Mesh::with_texture(texture_handle.id());
+                                // TODO(comc): preserve aspect ratio.
+                                let image_uv = Rect::from_min_size(pos2(0.0, 0.0), vec2(1.0, 1.0));
+                                let mut image_rect = rect;
+                                image_rect.set_top(rect.top() + 20.0);
+                                image_rect.set_bottom(rect.bottom() - 20.0);
+                                image_mesh.add_rect_with_uv(image_rect, image_uv, Color32::WHITE);
+                                ui.painter().add(image_mesh);
+
+                                if response.clicked() {
+                                    set_current_frame_index = Some(frame_id);
+                                }
+                                let mut frame_type_name =
+                                    frame.frame_type_name().trim_end_matches("_FRAME").to_case(Case::Pascal);
+                                let tip_mode = frame.tip_mode_name();
+                                if tip_mode == "TIP_FRAME_AS_OUTPUT" {
+                                    frame_type_name = format!("{frame_type_name} (TIP)");
+                                }
+
+                                let text = ui.fonts(|f| {
+                                    Shape::text(
+                                        f,
+                                        rect.center_bottom(),
+                                        Align2::CENTER_BOTTOM,
+                                        frame_type_name,
+                                        TextStyle::Body.resolve(ui.style()),
+                                        Color32::BLACK,
+                                    )
+                                });
+                                ui.painter().add(text);
+                            }
+                            FrameStatus::Unloaded => {
+                                let text = ui.fonts(|f| {
+                                    Shape::text(
+                                        f,
+                                        rect.center(),
+                                        Align2::CENTER_CENTER,
+                                        "Click to load",
+                                        TextStyle::Body.resolve(ui.style()),
+                                        Color32::BLACK,
+                                    )
+                                });
+                                ui.painter().add(text);
+
+                                if response.clicked() {
+                                    set_current_frame_index = Some(frame_id);
+                                }
+                            }
+                        };
+
+                        if let Some(current_frame) = state.stream.current_frame() {
+                            if current_frame.decode_index() == frame_id {
+                                ui.painter().add(Shape::rect_stroke(
+                                    rect,
+                                    Rounding::ZERO,
+                                    Stroke::new(3.0, Color32::YELLOW),
+                                ));
+                            }
+                        }
+                    }
+                });
+                let painter = ui.painter();
+                for (mv_index, &(frame_index, ref_frame)) in motion_vector_arrows.iter().enumerate() {
+                    let start_rect = frame_rects[stream.current_frame_index];
+                    let end_rect = frame_rects[frame_index];
+                    let color_index = ref_frame as usize % 8;
+                    let color = REF_FRAME_COLORS[color_index];
+                    let pos_offset = vec2(0.0, mv_index as f32 * 5.0);
+                    painter.add(Shape::line_segment(
+                        [start_rect.center() + pos_offset, end_rect.center() + pos_offset],
+                        Stroke::new(2.0, color),
+                    ));
+                    painter.add(Shape::circle_filled(
+                        start_rect.center() + pos_offset,
+                        2.0,
+                        REF_FRAME_COLORS[color_index],
+                    ));
+
+                    let dir_sign = (start_rect.left() - end_rect.left()).signum();
+                    let triangle_tip = end_rect.center() + pos_offset;
+                    let triangle_vertices = vec![
+                        triangle_tip,
+                        triangle_tip + vec2(dir_sign * 3.0, 3.0),
+                        triangle_tip + vec2(dir_sign * 3.0, -3.0),
+                    ];
+                    painter.add(Shape::convex_polygon(
+                        triangle_vertices,
+                        REF_FRAME_COLORS[color_index],
+                        Stroke::NONE,
+                    ));
+
+                    painter.add(Shape::rect_stroke(end_rect, Rounding::ZERO, Stroke::new(3.0, color)));
+                }
+            });
+        if let Some(current_frame_index) = set_current_frame_index {
+            // Unwrapping is okay, since we already know stream exists at this point.
+            let stream = state.stream.as_mut().unwrap();
+            stream.change_frame(ChangeFrame::index(current_frame_index));
+            state.settings.selected_object = None;
+        }
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/frame_overlay.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/frame_overlay.rs
new file mode 100644
index 0000000..1b0cbcc
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/frame_overlay.rs
@@ -0,0 +1,422 @@
+use super::ScreenBounds;
+use crate::settings::{MotionFieldColoring, Settings};
+use crate::views::{MIN_BLOCK_HEIGHT_FOR_TEXT, MIN_BLOCK_SIZE_TO_RENDER, MIN_BLOCK_WIDTH_FOR_TEXT};
+
+use avm_stats::{CodingUnitKind, Frame, ProtoEnumMapping, Spatial, MOTION_VECTOR_PRECISION};
+use egui::{pos2, vec2, Align2, Color32, Painter, Rect, Rounding, Shape, Stroke, TextStyle};
+use itertools::Itertools;
+
+fn is_rect_too_small(rect: Rect) -> bool {
+    rect.width() < MIN_BLOCK_SIZE_TO_RENDER || rect.height() < MIN_BLOCK_SIZE_TO_RENDER
+}
+
+fn is_text_too_small(rect: Rect) -> bool {
+    rect.width() < MIN_BLOCK_WIDTH_FOR_TEXT || rect.height() < MIN_BLOCK_HEIGHT_FOR_TEXT
+}
+
+// TODO(comc): Make configurable.
+pub const REF_FRAME_COLORS: &[Color32] = &[
+    Color32::LIGHT_RED,
+    Color32::LIGHT_GREEN,
+    Color32::LIGHT_BLUE,
+    Color32::LIGHT_YELLOW,
+    Color32::BROWN,
+    Color32::KHAKI,
+    Color32::GOLD,
+    Color32::LIGHT_GRAY,
+];
+
+// TODO(comc): World space to screen space conversion could be done in a shader.
+pub struct FrameOverlay<'a> {
+    frame: &'a Frame,
+    settings: &'a Settings,
+}
+
+impl<'a> FrameOverlay<'a> {
+    pub fn new(frame: &'a Frame, settings: &'a Settings) -> Self {
+        Self { frame, settings }
+    }
+
+    pub fn draw(&self, painter: &mut Painter) {
+        let view_settings = self.settings.sharable.view_mode.view_settings();
+
+        if self.settings.sharable.show_overlay {
+            if view_settings.show_transform_units {
+                self.draw_transform_units(painter);
+            }
+
+            if view_settings.show_coding_units {
+                self.draw_coding_units(painter);
+            }
+
+            if view_settings.show_superblocks {
+                self.draw_superblocks(painter);
+            }
+
+            if view_settings.show_prediction_modes {
+                self.draw_prediction_modes(painter);
+            }
+
+            if view_settings.show_transform_types {
+                self.draw_transform_modes(painter);
+            }
+        }
+
+        if (self.settings.sharable.show_overlay || self.settings.sharable.motion_field.show)
+            && view_settings.show_motion_vectors
+        {
+            self.draw_motion_vectors(painter);
+        }
+    }
+
+    fn is_in_bounds(&self, world_rect: Rect, screen_bounds: Rect) -> bool {
+        let world_bounds = self.settings.sharable.world_bounds;
+        // Because the aspect ratio of the viewport is not necessarily the same as the current frame, we might see extra than the plain world_bounds.
+        let extended_world_bounds = world_bounds.screen_rect_to_world(screen_bounds, screen_bounds);
+        extended_world_bounds.intersects(world_rect)
+    }
+
+    fn draw_transform_units(&self, painter: &mut Painter) -> Option<()> {
+        let style = &self.settings.persistent.style.overlay;
+        let world_bounds = self.settings.sharable.world_bounds;
+        let clip_rect = painter.clip_rect();
+        let shapes = self
+            .frame
+            .iter_transform_rects(self.settings.sharable.current_plane.to_plane())
+            .filter_map(|world_rect| {
+                if !self.is_in_bounds(world_rect, clip_rect) {
+                    return None;
+                }
+                let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+                if is_rect_too_small(screen_rect) {
+                    return None;
+                }
+                let points = [
+                    screen_rect.left_top(),
+                    screen_rect.right_top(),
+                    screen_rect.right_bottom(),
+                    screen_rect.left_bottom(),
+                ];
+                Some(Shape::dashed_line(&points[..], style.transform_unit_stroke, 4.0, 4.0))
+            })
+            .flatten();
+        painter.extend(shapes);
+
+        None
+    }
+    fn draw_coding_units(&self, painter: &mut Painter) -> Option<()> {
+        let style = &self.settings.persistent.style.overlay;
+        let world_bounds = self.settings.sharable.world_bounds;
+        let clip_rect = painter.clip_rect();
+        let coding_unit_kind = self.frame.coding_unit_kind(self.settings.sharable.current_plane);
+        let shapes = self
+            .frame
+            .iter_coding_unit_rects(coding_unit_kind)
+            .filter_map(|world_rect| {
+                if !self.is_in_bounds(world_rect, clip_rect) {
+                    return None;
+                }
+                let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+                if is_rect_too_small(screen_rect) {
+                    return None;
+                }
+                Some(Shape::rect_stroke(
+                    screen_rect,
+                    Rounding::ZERO,
+                    style.coding_unit_stroke,
+                ))
+            });
+        painter.extend(shapes);
+        None
+    }
+    fn draw_motion_vectors(&self, painter: &mut Painter) -> Option<()> {
+        let _style = &self.settings.persistent.style.overlay;
+        let world_bounds = self.settings.sharable.world_bounds;
+        let clip_rect = painter.clip_rect();
+        let coding_unit_kind = self.frame.coding_unit_kind(self.settings.sharable.current_plane);
+        // Length of largest motion vector, in screen space.
+        let mut largest_mv = 1e-9;
+        let granularity = self.settings.sharable.motion_field.granularity;
+        let raw_shapes = self
+            .frame
+            .iter_coding_units(coding_unit_kind)
+            .filter_map(|ctx| {
+                let cu = ctx.coding_unit;
+                let world_rect = cu.rect();
+                let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+                let Ok(prediction_mode) = cu.get_prediction_mode() else {
+                    return None;
+                };
+                let Ok(mode) = self
+                    .frame
+                    .enum_lookup(ProtoEnumMapping::PredictionMode, prediction_mode.mode)
+                else {
+                    return None;
+                };
+                // TODO(comc): Make this a method on PredictionParams or CodingUnit.
+                let is_motion = !mode.contains("_PRED");
+                if !is_motion {
+                    return None;
+                }
+                let is_compound = mode.contains('_');
+                let num_mvs = if is_compound { 2 } else { 1 };
+                // let mv_center = world_rect.center();
+                let mvs = (0..num_mvs)
+                    .filter_map(|i| {
+                        let mv = prediction_mode.motion_vectors.get(i as usize)?;
+                        let dx = mv.dx as f32 / MOTION_VECTOR_PRECISION;
+                        let dy = mv.dy as f32 / MOTION_VECTOR_PRECISION;
+                        let ref_frame = mv.ref_frame;
+                        if ref_frame == -1 {
+                            return None;
+                        }
+
+                        let order_hint = mv.ref_frame_order_hint;
+                        // let mv_tip = mv_center + vec2(dx, dy);
+                        let mv_world = vec2(dx, dy);
+                        let mv_screen = mv_world * world_bounds.calc_scale(clip_rect);
+                        // let mv_tip_screen = world_bounds.world_pos_to_screen(mv_tip, clip_rect);
+                        // let screen_pos = screen_rect.center();
+                        // let mv_vector = mv_tip_screen - screen_pos;
+                        let magnitude = mv_screen.length();
+                        if magnitude > largest_mv {
+                            largest_mv = magnitude;
+                        }
+                        let is_future = order_hint > self.frame.frame_params.as_ref().unwrap().raw_display_index;
+                        let color = match self.settings.sharable.motion_field.coloring {
+                            MotionFieldColoring::RefFrames => {
+                                let color_index = ref_frame as usize % 8;
+                                REF_FRAME_COLORS[color_index]
+                            }
+                            MotionFieldColoring::PastFuture => REF_FRAME_COLORS[is_future as usize],
+                            MotionFieldColoring::Monochrome => REF_FRAME_COLORS[0],
+                        };
+                        Some((mv_screen, color))
+                    })
+                    .collect_vec();
+
+                if !self.is_in_bounds(world_rect, clip_rect) {
+                    return None;
+                }
+                if is_rect_too_small(screen_rect) {
+                    return None;
+                }
+                if mvs.is_empty() {
+                    return None;
+                }
+                let mv_rects = if self.settings.sharable.motion_field.show {
+                    let mut rects = Vec::new();
+                    let rows = world_rect.height() as i32;
+                    let cols = world_rect.width() as i32;
+                    let top = world_rect.top() as i32;
+                    let left = world_rect.left() as i32;
+                    let granularity_pixels = (granularity * 4) as i32;
+                    let first_row = (granularity_pixels / 2 - top).rem_euclid(granularity_pixels);
+                    let first_col = (granularity_pixels / 2 - left).rem_euclid(granularity_pixels);
+                    for row in (first_row..rows).step_by(granularity_pixels as usize) {
+                        let y = row + top;
+                        for col in (first_col..cols).step_by(granularity_pixels as usize) {
+                            let x = col + left;
+                            let rect = Rect::from_center_size(
+                                pos2(x as f32, y as f32),
+                                vec2(granularity_pixels as f32, granularity_pixels as f32),
+                            );
+                            let screen_rect = world_bounds.world_rect_to_screen(rect, clip_rect);
+                            rects.push(screen_rect);
+                        }
+                    }
+                    rects
+                } else {
+                    vec![screen_rect]
+                };
+
+                Some((mv_rects, mvs))
+            })
+            .collect_vec();
+
+        let longest_vector_world_space = granularity as f32 * 2.0;
+        let longest_vector_screen_space = world_bounds.calc_scale(clip_rect) * longest_vector_world_space;
+        let normalization_factor = longest_vector_screen_space / largest_mv;
+        let shapes = raw_shapes.iter().flat_map(|(rects, mvs)| {
+            let mut mv_shapes = Vec::new();
+            for screen_rect in rects {
+                for &(mut mv_vector, color) in mvs.iter() {
+                    if self.settings.sharable.motion_field.normalize && self.settings.sharable.motion_field.show {
+                        mv_vector *= normalization_factor;
+                    }
+                    mv_vector *= self.settings.sharable.motion_field.scale;
+                    let screen_pos = screen_rect.center();
+                    if self.settings.sharable.motion_field.show_origin {
+                        mv_shapes.push(Shape::circle_filled(screen_pos, 1.0, color));
+                    }
+                    mv_shapes.push(Shape::line_segment(
+                        [screen_pos, screen_pos + mv_vector],
+                        Stroke::new(1.5, color),
+                    ));
+                }
+            }
+            mv_shapes
+        });
+        painter.extend(shapes);
+        None
+    }
+
+    fn draw_superblocks(&self, painter: &mut Painter) -> Option<()> {
+        let style = &self.settings.persistent.style.overlay;
+        let world_bounds = self.settings.sharable.world_bounds;
+        let clip_rect = painter.clip_rect();
+        let shapes = self.frame.iter_superblock_rects().filter_map(|world_rect| {
+            if !self.is_in_bounds(world_rect, clip_rect) {
+                return None;
+            }
+            let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+            if is_rect_too_small(screen_rect) {
+                return None;
+            }
+            Some(Shape::rect_stroke(screen_rect, Rounding::ZERO, style.superblock_stroke))
+        });
+        painter.extend(shapes);
+        None
+    }
+
+    fn draw_prediction_modes(&self, painter: &mut Painter) -> Option<()> {
+        let overlay_style = &self.settings.persistent.style.overlay;
+        let world_bounds = self.settings.sharable.world_bounds;
+        let clip_rect = painter.clip_rect();
+        let coding_unit_kind = self.frame.coding_unit_kind(self.settings.sharable.current_plane);
+        let ui_style = painter.ctx().style();
+        let shapes = self
+            .frame
+            .iter_coding_units(coding_unit_kind)
+            .filter_map(|ctx| {
+                let cu = ctx.coding_unit;
+                let world_rect = cu.rect();
+                if !self.is_in_bounds(world_rect, clip_rect) {
+                    return None;
+                }
+                let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+                if is_rect_too_small(screen_rect) || is_text_too_small(screen_rect) {
+                    return None;
+                }
+
+                let Ok(prediction_mode) = cu.get_prediction_mode() else {
+                    return None;
+                };
+                let mode_name = if coding_unit_kind == CodingUnitKind::ChromaOnly {
+                    self.frame
+                        .enum_lookup(ProtoEnumMapping::UvPredictionMode, prediction_mode.uv_mode)
+                } else {
+                    self.frame
+                        .enum_lookup(ProtoEnumMapping::PredictionMode, prediction_mode.mode)
+                };
+                let Ok(mode_name) = mode_name else {
+                    return None;
+                };
+
+                let screen_pos = screen_rect.center();
+                let mode_name = mode_name.trim_end_matches("_PRED");
+                let uses_palette = match coding_unit_kind {
+                    CodingUnitKind::Shared | CodingUnitKind::LumaOnly => {
+                        prediction_mode.palette_count > 0
+                    }
+                    CodingUnitKind::ChromaOnly => prediction_mode.uv_palette_count > 0,
+                };
+                let uses_intrabc = match coding_unit_kind {
+                    CodingUnitKind::Shared | CodingUnitKind::LumaOnly => {
+                        prediction_mode.use_intrabc
+                    }
+                    CodingUnitKind::ChromaOnly => false
+                };
+                let mode_name = if uses_palette {
+                    "PALETTE"
+                } else if uses_intrabc {
+                    "INTRA_BC"
+                } else {
+                    mode_name
+                };
+
+                let colors = if overlay_style.enable_text_shadows {
+                    vec![Color32::BLACK, overlay_style.mode_name_color]
+                } else {
+                    vec![overlay_style.mode_name_color]
+                };
+                painter.fonts(|f| {
+                    Some(
+                        colors
+                            .into_iter()
+                            .enumerate()
+                            .map(|(i, color)| {
+                                let pos_offset = vec2(i as f32, i as f32);
+                                Shape::text(
+                                    f,
+                                    screen_pos + pos_offset,
+                                    Align2::CENTER_CENTER,
+                                    mode_name,
+                                    TextStyle::Body.resolve(&ui_style),
+                                    color,
+                                )
+                            })
+                            .collect::<Vec<_>>(),
+                    )
+                })
+            })
+            .flatten()
+            .collect::<Vec<_>>(); // Note: need to collect here since painter.fonts requires read-only access to painter, and we can't mutate in painter.extend at the same time.
+
+        painter.extend(shapes);
+        None
+    }
+
+    fn draw_transform_modes(&self, painter: &mut Painter) -> Option<()> {
+        let overlay_style = &self.settings.persistent.style.overlay;
+        let world_bounds = self.settings.sharable.world_bounds;
+        let clip_rect = painter.clip_rect();
+        let ui_style = painter.ctx().style();
+        let shapes = self
+            .frame
+            .iter_transform_units(self.settings.sharable.current_plane.to_plane())
+            .filter_map(|ctx| {
+                let tu = ctx.transform_unit;
+                let world_rect = tu.rect();
+                if !self.is_in_bounds(world_rect, clip_rect) {
+                    return None;
+                }
+                let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
+                if is_rect_too_small(screen_rect) || is_text_too_small(screen_rect) {
+                    return None;
+                }
+
+                let tx_type = tu.primary_tx_type_or_skip(self.frame);
+                let screen_pos = screen_rect.center();
+                let colors = if overlay_style.enable_text_shadows {
+                    vec![Color32::BLACK, overlay_style.mode_name_color]
+                } else {
+                    vec![overlay_style.mode_name_color]
+                };
+                painter.fonts(|f| {
+                    Some(
+                        colors
+                            .into_iter()
+                            .enumerate()
+                            .map(|(i, color)| {
+                                let pos_offset = vec2(i as f32, i as f32);
+                                Shape::text(
+                                    f,
+                                    screen_pos + pos_offset,
+                                    Align2::CENTER_CENTER,
+                                    tx_type.clone(),
+                                    TextStyle::Body.resolve(&ui_style),
+                                    color,
+                                )
+                            })
+                            .collect::<Vec<_>>(),
+                    )
+                })
+            })
+            .flatten()
+            .collect::<Vec<_>>(); // Note: need to collect here since painter.fonts requires read-only access to painter, and we can't mutate in painter.extend at the same time.
+
+        painter.extend(shapes);
+        None
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/mod.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/mod.rs
new file mode 100644
index 0000000..08f2529
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/mod.rs
@@ -0,0 +1,277 @@
+mod frame_overlay;
+mod screen_bounds;
+mod selected_object;
+
+use avm_stats::{Frame, Plane, PlaneType, Spatial};
+use egui::{pos2, vec2, Color32, Mesh, PointerButton, Pos2, Rect, RichText, Rounding, Shape, Stroke, Ui, Vec2};
+use egui_plot::{Bar, BarChart, Plot};
+
+use crate::image_manager::ImageType;
+use crate::stream::CurrentFrame as _;
+use crate::views::RenderView;
+use crate::{app_state::AppState, image_manager::JET_COLORMAP};
+pub use frame_overlay::{FrameOverlay, REF_FRAME_COLORS};
+pub use screen_bounds::ScreenBounds;
+pub use selected_object::{SelectedObject, SelectedObjectKind};
+
+use super::ViewMode;
+
+pub struct FrameViewer;
+
+impl FrameViewer {
+    fn check_hovered_object(
+        &self,
+        state: &AppState,
+        frame: &Frame,
+        hover_pos_world: Pos2,
+    ) -> Option<SelectedObjectKind> {
+        let view_settings = state.settings.sharable.view_mode.view_settings();
+        if view_settings.allow_transform_unit_selection {
+            if let Some(hovered_transform_unit) = frame
+                .iter_transform_units(state.settings.sharable.current_plane.to_plane())
+                .find(|ctx| ctx.transform_unit.rect().contains(hover_pos_world))
+            {
+                return Some(SelectedObjectKind::TransformUnit(hovered_transform_unit.locator));
+            }
+        }
+        if view_settings.allow_coding_unit_selection {
+            let coding_unit_kind = frame.coding_unit_kind(state.settings.sharable.current_plane);
+            if let Some(hovered_coding_unit) = frame
+                .iter_coding_units(coding_unit_kind)
+                .find(|ctx| ctx.coding_unit.rect().contains(hover_pos_world))
+            {
+                return Some(SelectedObjectKind::CodingUnit(hovered_coding_unit.locator));
+            }
+        }
+        None
+    }
+}
+impl RenderView for FrameViewer {
+    fn title(&self) -> String {
+        "Frame View".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        egui::warn_if_debug_build(ui);
+
+        let Some(frame) = state.stream.current_frame() else {
+            ui.spinner();
+            return Ok(());
+        };
+
+        // Okay to unwrap here since we already have a Frame, so we must also have a Stream.
+        let stream = state.stream.as_ref().unwrap();
+
+        let mut world_bounds = state.settings.sharable.world_bounds;
+        let mut set_selected_object = false;
+        let mut set_selected_object_parent = false;
+        let mut hover_pos_world = None;
+
+        if state.settings.sharable.view_mode == ViewMode::Transform
+            && matches!(
+                state.settings.sharable.current_plane,
+                PlaneType::Planar(Plane::U | Plane::V)
+            )
+        {
+            ui.label(
+                RichText::new("WARNING: Chroma transform tree data extraction is currently buggy.").color(Color32::RED),
+            );
+            ui.end_row();
+        }
+
+        let size = ui.available_size_before_wrap();
+        let (screen_bounds, response) = ui.allocate_exact_size(size, egui::Sense::drag());
+        let scale = world_bounds.calc_scale(screen_bounds);
+
+        if response.dragged_by(PointerButton::Primary) {
+            let delta = response.drag_delta() / -scale;
+            world_bounds = world_bounds.translate(delta);
+        } else if response.hover_pos().is_some() && ui.input(|i| i.scroll_delta) != Vec2::ZERO {
+            let delta = ui.input(|i| i.scroll_delta);
+
+            if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
+                if screen_bounds.contains(mouse_pos) {
+                    let zoom = if delta.y < 0.0 {
+                        f32::powf(1.001, -delta.y)
+                    } else {
+                        1.0 / (f32::powf(1.001, delta.y))
+                    };
+                    let zoom_center = world_bounds.screen_pos_to_world(mouse_pos, screen_bounds);
+                    world_bounds.zoom_point(zoom_center, zoom);
+                }
+            }
+        } else if response.hover_pos().is_some() {
+            if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
+                if response.double_clicked() && state.settings.selected_object_leaf.is_some() {
+                    set_selected_object_parent = true;
+                } else if response.clicked() {
+                    set_selected_object = true;
+                }
+                hover_pos_world = Some(world_bounds.screen_pos_to_world(mouse_pos, screen_bounds));
+            }
+        }
+
+        state.settings.sharable.world_bounds = world_bounds;
+        let width = frame.width();
+        let height = frame.height();
+        let view_settings = state.settings.sharable.view_mode.view_settings();
+
+        let image_type = ImageType::new(
+            state.settings.sharable.current_plane,
+            view_settings.pixel_type,
+            view_settings.pixel_type.is_delta() && state.settings.sharable.show_relative_delta,
+            view_settings.show_heatmap,
+        );
+
+        let Ok(texture_handle) = stream.images.get_or_create_image(
+            ui.ctx(),
+            &stream.pixel_data,
+            frame,
+            image_type,
+            &state.settings.sharable.heatmap_settings,
+        ) else {
+            return Ok(());
+        };
+
+        if state.settings.sharable.show_yuv {
+            let mut image_mesh = Mesh::with_texture(texture_handle.id());
+            let image_world = Rect::from_min_size(pos2(0.0, 0.0), vec2(width as f32, height as f32));
+            let image_screen = world_bounds.world_rect_to_screen(image_world, screen_bounds);
+            let image_uv = Rect::from_min_size(pos2(0.0, 0.0), vec2(1.0, 1.0));
+            image_mesh.add_rect_with_uv(image_screen, image_uv, Color32::WHITE);
+            ui.painter().with_clip_rect(response.rect).add(image_mesh);
+        }
+        let mut painter = ui.painter().with_clip_rect(response.rect);
+        let overlay = FrameOverlay::new(frame, &state.settings);
+        overlay.draw(&mut painter);
+
+        let style = &state.settings.persistent.style.overlay;
+        if state.settings.sharable.show_overlay {
+            if let Some(hover_pos_world) = hover_pos_world {
+                if let Some(hovered_object) = self.check_hovered_object(state, frame, hover_pos_world) {
+                    if let Some(world_rect) = hovered_object.rect(frame) {
+                        let screen_rect = world_bounds.world_rect_to_screen(world_rect, screen_bounds);
+                        let mut already_selected = false;
+                        if let Some(selected_object_leaf) = &state.settings.selected_object_leaf {
+                            if selected_object_leaf == &hovered_object {
+                                already_selected = true;
+                            }
+                        }
+                        if set_selected_object && !already_selected {
+                            state.settings.sharable.pixel_viewer_bounds =
+                                Rect::from_min_size(pos2(0.0, 0.0), world_rect.size());
+                            if matches!(hovered_object, SelectedObjectKind::TransformUnit(_)) {
+                                state.settings.sharable.coeffs_viewer_bounds =
+                                    Rect::from_min_size(pos2(0.0, 0.0), world_rect.size());
+                            }
+                            state.settings.selected_object_leaf = Some(hovered_object.clone());
+                            state.settings.selected_object = Some(SelectedObject::new(hovered_object));
+                        }
+                        if set_selected_object_parent && already_selected {
+                            if let Some(SelectedObject {
+                                kind: selected_object_kind,
+                                ..
+                            }) = &state.settings.selected_object
+                            {
+                                if let Some(parent) = selected_object_kind.get_parent(frame) {
+                                    state.settings.selected_object = Some(SelectedObject::new(parent));
+                                }
+                            }
+                        }
+                        painter.add(Shape::rect_stroke(
+                            screen_rect,
+                            Rounding::ZERO,
+                            style.highlighted_object_stroke,
+                        ));
+                    }
+                } else if set_selected_object {
+                    state.settings.selected_object = None;
+                    state.settings.selected_object_leaf = None;
+                }
+            }
+            if let Some(selected_object) = &state.settings.selected_object {
+                if let Some(world_rect) = selected_object.rect(frame) {
+                    let screen_rect = world_bounds.world_rect_to_screen(world_rect, screen_bounds);
+                    painter.add(Shape::rect_stroke(
+                        screen_rect,
+                        Rounding::ZERO,
+                        style.selected_object_stroke,
+                    ));
+                }
+            }
+        }
+        // TODO(comc): Refactor into separate module.
+        if view_settings.show_heatmap {
+            let mut show_heatmap_legend = state.settings.sharable.show_heatmap_legend;
+            let log_scale = state.settings.sharable.heatmap_histogram_log_scale;
+            let ui_ctx = ui.ctx();
+            egui::Window::new("Heatmap Legend")
+                .id("Heatmap Legend".into())
+                .open(&mut show_heatmap_legend)
+                .default_width(400.0)
+                .default_height(400.0)
+                .resizable(true)
+                .collapsible(false)
+                .show(ui_ctx, |ui| {
+                    Plot::new("heatmap_legend")
+                        .show_background(false)
+                        .show_axes([true, true])
+                        .clamp_grid(true)
+                        .show_grid(false)
+                        .allow_boxed_zoom(false)
+                        .allow_drag(false)
+                        .allow_zoom(false)
+                        .allow_scroll(false)
+                        .show_x(false)
+                        .show_y(false)
+                        .x_axis_label("Bits / pixel")
+                        .y_axis_formatter(move |value, _num_chars, _range| {
+                            if log_scale {
+                                (10_f64).powf(value).to_string()
+                            } else {
+                                value.to_string()
+                            }
+                        })
+                        .show(ui, |plot_ui| {
+                            if let Ok(heatmap) = stream.images.get_or_create_heatmap(
+                                ui_ctx,
+                                frame,
+                                image_type,
+                                &state.settings.sharable.heatmap_settings,
+                            ) {
+                                let histogram = heatmap.histogram;
+                                plot_ui.bar_chart(BarChart::new(
+                                    histogram
+                                        .iter()
+                                        .enumerate()
+                                        .map(|(index, &value)| {
+                                            let mut value = value as f64;
+                                            if log_scale && value > 0.0 {
+                                                value = value.log10();
+                                            }
+                                            let bar_width = heatmap.bucket_width as f64;
+                                            let x = index as f64 * bar_width;
+                                            let colormap_index = 255.0 * (index as f64 / histogram.len() as f64);
+                                            let color = JET_COLORMAP[colormap_index as usize];
+                                            let fill = Color32::from_rgb(color[0], color[1], color[2]);
+                                            Bar {
+                                                name: "bar".into(),
+                                                orientation: egui_plot::Orientation::Vertical,
+                                                argument: x,
+                                                value,
+                                                base_offset: None,
+                                                bar_width: heatmap.bucket_width as f64,
+                                                stroke: Stroke::new(1.0, Color32::BLACK),
+                                                fill,
+                                            }
+                                        })
+                                        .collect(),
+                                ));
+                            }
+                        });
+                });
+            state.settings.sharable.show_heatmap_legend = show_heatmap_legend;
+        }
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/screen_bounds.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/screen_bounds.rs
new file mode 100644
index 0000000..34924c3
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/screen_bounds.rs
@@ -0,0 +1,69 @@
+use egui::{pos2, Pos2, Rect, Vec2};
+
+pub trait ScreenBounds {
+    fn zoom_point(&mut self, center: Pos2, zoom_factor: f32);
+    fn calc_scale(&self, screen_bounds: Rect) -> f32;
+    fn translate(&mut self, delta: Vec2);
+    fn screen_pos_to_world(&self, screen_point: Pos2, screen_bounds: Rect) -> Pos2;
+    fn world_pos_to_screen(&self, world_point: Pos2, screen_bounds: Rect) -> Pos2;
+
+    fn screen_rect_to_world(&self, screen_rect: Rect, screen_bounds: Rect) -> Rect;
+    fn world_rect_to_screen(&self, world_rect: Rect, screen_bounds: Rect) -> Rect;
+}
+
+impl ScreenBounds for Rect {
+    fn zoom_point(&mut self, center: Pos2, zoom_factor: f32) {
+        let mut left = center.x - self.left();
+        let mut right = self.right() - center.x;
+        left *= zoom_factor;
+        right *= zoom_factor;
+        let mut top = center.y - self.top();
+        let mut bottom = self.bottom() - center.y;
+        top *= zoom_factor;
+        bottom *= zoom_factor;
+
+        self.set_left(center.x - left);
+        self.set_right(center.x + right);
+        self.set_top(center.y - top);
+        self.set_bottom(center.y + bottom);
+    }
+
+    fn calc_scale(&self, screen_bounds: Rect) -> f32 {
+        let x_scale = screen_bounds.width() / (self.right() - self.left());
+        let y_scale = screen_bounds.height() / (self.bottom() - self.top());
+        x_scale.min(y_scale)
+    }
+
+    fn translate(&mut self, delta: Vec2) {
+        self.set_left(self.left() - delta.x);
+        self.set_right(self.right() - delta.x);
+        self.set_top(self.top() - delta.y);
+        self.set_bottom(self.bottom() - delta.y);
+    }
+
+    fn screen_pos_to_world(&self, screen_point: Pos2, screen_bounds: Rect) -> Pos2 {
+        let scale = self.calc_scale(screen_bounds);
+        let world_x = (screen_point.x - screen_bounds.left()) / scale + self.left();
+        let world_y = (screen_point.y - screen_bounds.top()) / scale + self.top();
+        pos2(world_x, world_y)
+    }
+
+    fn world_pos_to_screen(&self, world_point: Pos2, screen_bounds: Rect) -> Pos2 {
+        let scale = self.calc_scale(screen_bounds);
+        let screen_x = (world_point.x - self.left()) * scale + screen_bounds.left();
+        let screen_y = (world_point.y - self.top()) * scale + screen_bounds.top();
+        pos2(screen_x, screen_y)
+    }
+
+    fn screen_rect_to_world(&self, screen_rect: Rect, screen_bounds: Rect) -> Rect {
+        let left_top = self.screen_pos_to_world(screen_rect.left_top(), screen_bounds);
+        let right_bottom = self.screen_pos_to_world(screen_rect.right_bottom(), screen_bounds);
+        Rect::from_min_max(left_top, right_bottom)
+    }
+
+    fn world_rect_to_screen(&self, world_rect: Rect, screen_bounds: Rect) -> Rect {
+        let left_top = self.world_pos_to_screen(world_rect.left_top(), screen_bounds);
+        let right_bottom = self.world_pos_to_screen(world_rect.right_bottom(), screen_bounds);
+        Rect::from_min_max(left_top, right_bottom)
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/selected_object.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/selected_object.rs
new file mode 100644
index 0000000..d342661
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/frame_viewer/selected_object.rs
@@ -0,0 +1,406 @@
+use avm_stats::{
+    CodingUnitKind, CodingUnitLocator, Frame, FrameError, PartitionLocator, Plane, ProtoEnumMapping, Spatial,
+    SuperblockLocator, SymbolContext, TransformUnitLocator, MISSING_SYMBOL_INFO, MOTION_VECTOR_PRECISION,
+};
+use egui::emath::Rect;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug)]
+pub enum CachedInfo<T> {
+    Missing,
+    Calculated(T),
+}
+
+#[derive(Clone, Debug)]
+pub struct SymbolEntry {
+    pub func: String,
+    pub bits: f32,
+    pub tags: Vec<String>,
+    pub file: String,
+    pub line: i32,
+    pub value: i32,
+}
+impl SymbolEntry {
+    pub fn new(ctx: SymbolContext) -> Self {
+        let bits = ctx.symbol.bits;
+        let value = ctx.symbol.value;
+        let sym_info = ctx.info.unwrap_or(&MISSING_SYMBOL_INFO);
+        let func: String = sym_info.source_function.clone();
+        let file = sym_info.source_file.clone();
+        let line = sym_info.source_line;
+        let tags = sym_info.tags.clone();
+
+        Self {
+            func,
+            bits,
+            tags,
+            file,
+            line,
+            value,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct FieldEntry {
+    pub name: String,
+    pub value: String,
+}
+
+impl FieldEntry {
+    fn new(name: String, value: String) -> Self {
+        Self { name, value }
+    }
+}
+
+#[derive(Debug)]
+pub struct SelectedObjectInfo {
+    pub fields: Vec<FieldEntry>,
+    pub symbols: Vec<SymbolEntry>,
+}
+
+impl SelectedObjectInfo {
+    fn get_symbols_coding_unit(locator: &CodingUnitLocator, frame: &Frame) -> Vec<SymbolEntry> {
+        let Some(ctx) = locator.try_resolve(frame) else {
+            return Vec::new();
+        };
+        let symbols: Vec<SymbolEntry> = ctx.iter_symbols().map(|sym| SymbolEntry::new(sym)).collect();
+        symbols
+    }
+
+    fn get_symbols_partition(locator: &PartitionLocator, frame: &Frame) -> Vec<SymbolEntry> {
+        let Some(ctx) = locator.try_resolve(frame) else {
+            return Vec::new();
+        };
+        let symbols: Vec<SymbolEntry> = ctx.iter_symbols().map(|sym| SymbolEntry::new(sym)).collect();
+        symbols
+    }
+
+    fn get_symbols_superblock(locator: &SuperblockLocator, frame: &Frame) -> Vec<SymbolEntry> {
+        let Some(ctx) = locator.try_resolve(frame) else {
+            return Vec::new();
+        };
+        let symbols: Vec<SymbolEntry> = ctx.iter_symbols(None).map(|sym| SymbolEntry::new(sym)).collect();
+        symbols
+    }
+
+    fn get_symbols_transform_unit(_locator: &TransformUnitLocator, _frame: &Frame) -> Vec<SymbolEntry> {
+        // TODO(comc): Transform units don't currently have symbol info tracked.
+        Vec::new()
+    }
+
+    fn calculate_coding_unit(locator: &CodingUnitLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
+        let ctx = locator
+            .try_resolve(frame)
+            .ok_or(FrameError::Internal("Invalid coding unit locator".into()))?;
+        let coding_unit = ctx.coding_unit;
+
+        let mut fields = Vec::new();
+
+        fields.push(FieldEntry::new("Type".into(), "Coding block".into()));
+        let width = coding_unit.width();
+        let height = coding_unit.height();
+        fields.push(FieldEntry::new("Width".into(), width.to_string()));
+        fields.push(FieldEntry::new("Height".into(), height.to_string()));
+        let x = coding_unit.x();
+        let y = coding_unit.y();
+        fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
+        if coding_unit.has_luma()? {
+            let mode_name = coding_unit.lookup_mode_name(frame)?;
+            let mode_is_directional = mode_name.ends_with("_PRED");
+            fields.push(FieldEntry::new("Prediction Mode".into(), mode_name));
+            // TODO(comc): Make this a method.
+            if mode_is_directional {
+                if let Some(delta) = coding_unit.luma_mode_angle_delta(frame) {
+                    fields.push(FieldEntry::new("Angle delta".into(), delta.to_string()));
+                }
+            } else {
+                let motion_mode = coding_unit.get_prediction_mode()?.motion_mode;
+                if let Ok(motion_mode_name) = frame.enum_lookup(ProtoEnumMapping::MotionMode, motion_mode) {
+                    // TODO(comc): Make this a method.
+                    let is_compound = motion_mode_name.contains('_');
+                    fields.push(FieldEntry::new("Motion mode".into(), motion_mode_name));
+                    if let Ok(mv_prec_name) = coding_unit.lookup_motion_vector_precision_name(frame) {
+                        fields.push(FieldEntry::new("MV precision".into(), mv_prec_name));
+                    }
+                    let num_mvs = if is_compound { 2 } else { 1 };
+                    for i in 0..num_mvs {
+                        if let Some(mv) = coding_unit.get_prediction_mode()?.motion_vectors.get(i as usize) {
+                            let ref_frame = mv.ref_frame;
+                            if ref_frame == -1 {
+                                continue;
+                            }
+                            let dx = mv.dx as f32 / MOTION_VECTOR_PRECISION;
+                            let dy = mv.dy as f32 / MOTION_VECTOR_PRECISION;
+                            let mut order_hint = mv.ref_frame_order_hint.to_string();
+                            if mv.ref_frame_is_tip {
+                                order_hint = "TIP".to_string();
+                            }
+                            fields.push(FieldEntry::new(
+                                "Motion vector".into(),
+                                format!("{} ({}): dx={}, dy={}", ref_frame, order_hint, dx, dy),
+                            ));
+                        }
+                    }
+                }
+            }
+        }
+        if coding_unit.has_chroma()? {
+            let uv_mode = coding_unit.lookup_uv_mode_name(frame)?;
+            if uv_mode != "UV_MODE_INVALID" {
+                fields.push(FieldEntry::new("UV Prediction Mode".into(), uv_mode));
+
+                if let Some(delta) = coding_unit.chroma_mode_angle_delta(frame) {
+                    fields.push(FieldEntry::new("UV angle delta".into(), delta.to_string()));
+                }
+            }
+        }
+
+        // TODO(comc): Disambiguate skip mode vs skip txfm.
+        fields.push(FieldEntry::new("Skip Mode".into(), coding_unit.skip.to_string()));
+        fields.push(FieldEntry::new(
+            "Use Intra BC".into(),
+            coding_unit.get_prediction_mode()?.use_intrabc.to_string(),
+        ));
+        fields.push(FieldEntry::new("QIndex".into(), coding_unit.qindex.to_string()));
+        let bits = ctx.total_bits();
+        fields.push(FieldEntry::new("Bits".into(), bits.to_string()));
+        let num_transform_units = coding_unit.transform_planes[0].transform_units.len();
+        fields.push(FieldEntry::new(
+            "Transform units".into(),
+            num_transform_units.to_string(),
+        ));
+        Ok(fields)
+    }
+
+    fn calculate_transform_unit(locator: &TransformUnitLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
+        let ctx = locator
+            .try_resolve(frame)
+            .ok_or(FrameError::Internal("Invalid transform unit locator".into()))?;
+        let transform_unit = ctx.transform_unit;
+        let mut fields = Vec::new();
+        let width = transform_unit.width();
+        let height = transform_unit.height();
+        fields.push(FieldEntry::new("Type".into(), "Transform block".into()));
+        fields.push(FieldEntry::new("Width".into(), width.to_string()));
+        fields.push(FieldEntry::new("Height".into(), height.to_string()));
+        let x = transform_unit.x();
+        let y = transform_unit.y();
+        fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
+
+        let tx_type = transform_unit.primary_tx_type_or_skip(frame);
+        fields.push(FieldEntry::new("TX type".into(), tx_type));
+
+        Ok(fields)
+    }
+
+    fn calculate_partition(locator: &PartitionLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
+        let ctx = locator
+            .try_resolve(frame)
+            .ok_or(FrameError::Internal("Invalid partition locator".into()))?;
+        let partition = ctx.partition;
+        let mut fields = Vec::new();
+        let width = partition.width();
+        let height = partition.height();
+        fields.push(FieldEntry::new("Type".into(), "Partition block".into()));
+        fields.push(FieldEntry::new("Width".into(), width.to_string()));
+        fields.push(FieldEntry::new("Height".into(), height.to_string()));
+        let x = partition.x();
+        let y = partition.y();
+        fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
+
+        let partition_type = partition.partition_type;
+        let partition_type_name = frame
+            .enum_lookup(ProtoEnumMapping::PartitionType, partition_type)
+            .unwrap_or("UNKNOWN".into());
+        fields.push(FieldEntry::new("Partition type".into(), partition_type_name));
+        Ok(fields)
+    }
+
+    fn calculate_superblock(locator: &SuperblockLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
+        let ctx = locator
+            .try_resolve(frame)
+            .ok_or(FrameError::Internal("Invalid superblock locator".into()))?;
+        let superblock = ctx.superblock;
+        let mut fields = Vec::new();
+        let width = superblock.width();
+        let height = superblock.height();
+        fields.push(FieldEntry::new("Type".into(), "Superblock".into()));
+        fields.push(FieldEntry::new("Width".into(), width.to_string()));
+        fields.push(FieldEntry::new("Height".into(), height.to_string()));
+        let x = superblock.x();
+        let y = superblock.y();
+        fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
+        // TODO(comc): Switch between luma and chroma partition trees here.
+        if let Some(partition_root) = &superblock.luma_partition_tree {
+            let partition_type = partition_root.partition_type;
+            let partition_type_name = frame
+                .enum_lookup(ProtoEnumMapping::PartitionType, partition_type)
+                .unwrap_or("UNKNOWN".into());
+            fields.push(FieldEntry::new("Partition type".into(), partition_type_name));
+        }
+        Ok(fields)
+    }
+
+    fn calculate(kind: &SelectedObjectKind, frame: &Frame) -> Result<Self, FrameError> {
+        let fields = match kind {
+            SelectedObjectKind::TransformUnit(obj) => Self::calculate_transform_unit(obj, frame)?,
+            SelectedObjectKind::CodingUnit(obj) => Self::calculate_coding_unit(obj, frame)?,
+            SelectedObjectKind::Partition(obj) => Self::calculate_partition(obj, frame)?,
+            SelectedObjectKind::Superblock(obj) => Self::calculate_superblock(obj, frame)?,
+        };
+        // TODO(comc): Make children selectable.
+        // for (i, child) in kind.get_children(frame).iter().enumerate() {
+        //     if let Some(rect) = child.rect(frame) {
+        //         let width = rect.width();
+        //         let height = rect.height();
+        //         let x = rect.left();
+        //         let y = rect.top();
+        //         fields.push(FieldEntry::new(
+        //             format!("Child {i}"),
+        //             format!("{width}x{height} at ({x},{y})"),
+        //         ));
+        //     }
+        // }
+
+        let symbols = match kind {
+            SelectedObjectKind::TransformUnit(obj) => Self::get_symbols_transform_unit(obj, frame),
+            SelectedObjectKind::CodingUnit(obj) => Self::get_symbols_coding_unit(obj, frame),
+            SelectedObjectKind::Partition(obj) => Self::get_symbols_partition(obj, frame),
+            SelectedObjectKind::Superblock(obj) => Self::get_symbols_superblock(obj, frame),
+        };
+        Ok(Self { fields, symbols })
+    }
+}
+
+// TODO(comc): Option to show partition blocks at user-selected depth.
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+pub enum SelectedObjectKind {
+    TransformUnit(TransformUnitLocator),
+    CodingUnit(CodingUnitLocator),
+    Partition(PartitionLocator),
+    Superblock(SuperblockLocator),
+}
+impl SelectedObjectKind {
+    pub fn rect(&self, frame: &Frame) -> Option<Rect> {
+        match self {
+            SelectedObjectKind::TransformUnit(obj) => obj.try_resolve(frame).map(|ctx| ctx.transform_unit.rect()),
+            SelectedObjectKind::CodingUnit(obj) => obj.try_resolve(frame).map(|ctx| ctx.coding_unit.rect()),
+            SelectedObjectKind::Partition(obj) => obj.try_resolve(frame).map(|ctx| ctx.partition.rect()),
+            SelectedObjectKind::Superblock(obj) => obj.try_resolve(frame).map(|ctx| ctx.superblock.rect()),
+        }
+    }
+
+    pub fn get_parent(&self, frame: &Frame) -> Option<SelectedObjectKind> {
+        match self {
+            SelectedObjectKind::TransformUnit(obj) => {
+                let ctx = obj.try_resolve(frame)?;
+                Some(SelectedObjectKind::CodingUnit(ctx.coding_unit_context.locator))
+            }
+
+            SelectedObjectKind::CodingUnit(obj) => {
+                let ctx = obj.try_resolve(frame)?;
+                let parent = ctx.find_parent_partition()?;
+                if parent.is_root() {
+                    Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
+                } else {
+                    // Partition tree leaf nodes map directly to coding units. To get the actual parent, we need to go one more level up the hierarchy.
+                    let actual_parent = parent.locator.parent().unwrap();
+                    if actual_parent.is_root() {
+                        Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
+                    } else {
+                        Some(SelectedObjectKind::Partition(actual_parent))
+                    }
+                }
+            }
+
+            SelectedObjectKind::Partition(obj) => {
+                let ctx = obj.try_resolve(frame)?;
+                if ctx.is_root() {
+                    Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
+                } else {
+                    let parent = ctx.locator.parent().unwrap();
+                    if parent.is_root() {
+                        Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
+                    } else {
+                        Some(SelectedObjectKind::Partition(ctx.locator.parent().unwrap()))
+                    }
+                }
+            }
+            _ => None,
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn get_children(&self, frame: &Frame) -> Vec<SelectedObjectKind> {
+        let mut children = Vec::new();
+        match self {
+            SelectedObjectKind::TransformUnit(_obj) => {}
+            SelectedObjectKind::CodingUnit(obj) => {
+                if let Some(ctx) = obj.try_resolve(frame) {
+                    let kind = ctx.locator.kind;
+                    let plane = match kind {
+                        CodingUnitKind::LumaOnly | CodingUnitKind::Shared => Plane::Y,
+                        CodingUnitKind::ChromaOnly => Plane::U,
+                    };
+                    // TODO(comc): Iterate over all three planes depending on plane view settings.
+                    children.extend(
+                        ctx.iter_transform_units(plane)
+                            .map(|transform_unit| SelectedObjectKind::TransformUnit(transform_unit.locator)),
+                    )
+                }
+            }
+
+            SelectedObjectKind::Partition(obj) => {
+                if let Some(ctx) = obj.try_resolve(frame) {
+                    for (i, child) in ctx.iter_direct_children().enumerate() {
+                        if child.partition.is_leaf_node {
+                            if let Some(coding_unit_range) = &child.partition.coding_unit_range {
+                                let coding_unit_index = coding_unit_range.start as usize;
+                                let locator = CodingUnitLocator::new(
+                                    ctx.superblock_context.locator,
+                                    ctx.locator.kind,
+                                    coding_unit_index,
+                                );
+                                children.push(SelectedObjectKind::CodingUnit(locator));
+                            }
+                        } else {
+                            let mut locator = ctx.locator.clone();
+                            locator.path_indices.push(i);
+                            children.push(SelectedObjectKind::Partition(locator));
+                        }
+                    }
+                }
+            }
+            _ => {}
+        }
+        children
+    }
+}
+
+pub struct SelectedObject {
+    pub kind: SelectedObjectKind,
+    pub info: CachedInfo<Result<SelectedObjectInfo, FrameError>>,
+}
+impl SelectedObject {
+    pub fn rect(&self, frame: &Frame) -> Option<Rect> {
+        self.kind.rect(frame)
+    }
+
+    // TODO(comc): Refactor.
+    pub fn get_or_calculate_info(&mut self, frame: &Frame) -> &Result<SelectedObjectInfo, FrameError> {
+        if matches!(self.info, CachedInfo::Missing) {
+            self.info = CachedInfo::Calculated(SelectedObjectInfo::calculate(&self.kind, frame));
+        }
+        let CachedInfo::Calculated(info) = &self.info else {
+            panic!("Info is not calculated.");
+        };
+        info
+    }
+    pub fn new(kind: SelectedObjectKind) -> Self {
+        Self {
+            kind,
+            info: CachedInfo::Missing,
+        }
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/menu_bar.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/menu_bar.rs
new file mode 100644
index 0000000..6120862
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/menu_bar.rs
@@ -0,0 +1,89 @@
+use egui::{RichText, Ui};
+
+use crate::app_state::AppState;
+use crate::settings::DistortionView;
+use crate::views::RenderView;
+use crate::views::ViewMode;
+
+pub struct MenuBar;
+
+impl RenderView for MenuBar {
+    fn title(&self) -> String {
+        "Menu".into()
+    }
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        egui::menu::bar(ui, |ui| {
+            ui.menu_button("File", |ui| {
+                if ui.button("Open Local Stream (.zip)").clicked() {
+                    state.local_stream_manager.prompt_local_stream();
+                    ui.close_menu();
+                }
+                if state.settings.sharable.show_remote_streams {
+                    if ui.button("Open Remote Stream").clicked() {
+                        state.settings.show_stream_select = true;
+                        ui.close_menu();
+                    }
+                    if ui.button("Decode Stream on Server").clicked() {
+                        state.settings.show_decode_progress = true;
+                        state.server_decode_manager.prompt_stream();
+                        ui.close_menu();
+                    }
+                }
+                if ui.button("Open Demo Stream").clicked() {
+                    state.local_stream_manager.load_demo_stream();
+                    ui.close_menu();
+                }
+            });
+            ui.menu_button("View Mode", |ui| {
+                let current_mode = state.settings.sharable.view_mode;
+                let mut have_orig_yuv = false;
+                if let Some(stream) = state.stream.as_ref() {
+                    if stream.have_orig_yuv() {
+                        have_orig_yuv = true;
+                    }
+                }
+
+                for view_mode in [
+                    ViewMode::CodingFlow,
+                    ViewMode::Prediction,
+                    ViewMode::Transform,
+                    ViewMode::Filters,
+                    ViewMode::Distortion(DistortionView::Distortion),
+                    ViewMode::Motion,
+                    ViewMode::Heatmap,
+                ] {
+                    let prefix = if current_mode == view_mode { "• " } else { "  " };
+                    let mut text = RichText::new(format!("{}{}", prefix, view_mode));
+                    if current_mode == view_mode {
+                        text = text.strong();
+                    }
+                    let enabled = !matches!(view_mode, ViewMode::Distortion(_)) || have_orig_yuv;
+                    if ui.add_enabled(enabled, egui::Button::new(text)).clicked() {
+                        state.settings.sharable.view_mode = view_mode;
+                        ui.close_menu();
+                    }
+                }
+            });
+
+            ui.menu_button("Settings", |ui| {
+                if ui.button("Edit Settings").clicked() {
+                    state.settings.show_settings_window = true;
+                    ui.close_menu();
+                }
+            });
+
+            ui.menu_button("Window", |ui| {
+                if ui.button("Decode Progress").clicked() {
+                    state.settings.show_decode_progress = true;
+                    ui.close_menu();
+                }
+
+                if ui.button("Performance").clicked() {
+                    state.settings.show_performance_window = true;
+                    ui.close_menu();
+                }
+            });
+        });
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/mod.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/mod.rs
new file mode 100644
index 0000000..0b74408
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/mod.rs
@@ -0,0 +1,39 @@
+mod block_info_viewer;
+mod coeffs_viewer;
+mod controls_viewer;
+mod decode_progress_viewer;
+mod detailed_pixel_viewer;
+mod drag_and_drop;
+mod frame_info_viewer;
+mod frame_select_viewer;
+mod frame_viewer;
+mod menu_bar;
+mod performance_viewer;
+mod render_view;
+mod settings_viewer;
+mod stats_viewer;
+mod stream_select_viewer;
+mod symbol_info_viewer;
+mod view_mode;
+
+pub use block_info_viewer::BlockInfoViewer;
+pub use coeffs_viewer::CoeffsViewer;
+pub use controls_viewer::ControlsViewer;
+pub use decode_progress_viewer::DecodeProgressViewer;
+pub use detailed_pixel_viewer::DetailedPixelViewer;
+pub use drag_and_drop::handle_drag_and_drop;
+pub use frame_info_viewer::FrameInfoViewer;
+pub use frame_select_viewer::FrameSelectViewer;
+pub use frame_viewer::{FrameViewer, ScreenBounds, SelectedObject, SelectedObjectKind};
+pub use menu_bar::MenuBar;
+pub use performance_viewer::{PerformanceHistory, PerformanceViewer};
+pub use render_view::RenderView;
+pub use settings_viewer::SettingsViewer;
+pub use stats_viewer::StatsViewer;
+pub use stream_select_viewer::StreamSelectViewer;
+pub use symbol_info_viewer::SymbolInfoViewer;
+pub use view_mode::ViewMode;
+
+pub const MIN_BLOCK_SIZE_TO_RENDER: f32 = 2.0;
+pub const MIN_BLOCK_HEIGHT_FOR_TEXT: f32 = 12.0;
+pub const MIN_BLOCK_WIDTH_FOR_TEXT: f32 = 30.0;
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/performance_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/performance_viewer.rs
new file mode 100644
index 0000000..e67031c
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/performance_viewer.rs
@@ -0,0 +1,61 @@
+use egui::util::History;
+use egui::Ui;
+
+use crate::app_state::AppState;
+use crate::views::render_view::RenderView;
+
+pub struct PerformanceViewer;
+
+pub struct PerformanceHistory {
+    frame_times: History<f32>,
+}
+
+impl Default for PerformanceHistory {
+    fn default() -> Self {
+        Self {
+            frame_times: History::new(2..100, 1.0),
+        }
+    }
+}
+
+impl PerformanceHistory {
+    pub fn on_new_frame(&mut self, now: f64, previous_frame_time: Option<f32>) {
+        let previous_frame_time = previous_frame_time.unwrap_or_default();
+        if let Some(latest) = self.frame_times.latest_mut() {
+            *latest = previous_frame_time;
+        }
+        self.frame_times.add(now, previous_frame_time);
+    }
+
+    pub fn mean_frame_time(&self) -> f32 {
+        self.frame_times.average().unwrap_or_default()
+    }
+
+    pub fn fps(&self) -> f32 {
+        1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
+    }
+
+    pub fn ui(&mut self, ui: &mut egui::Ui) {
+        ui.ctx().request_repaint();
+        ui.label(format!("FPS: {:.1}", self.fps()));
+        ui.label(format!(
+            "Mean CPU usage: {:.2} ms / frame",
+            1e3 * self.mean_frame_time()
+        ));
+
+        let mem_use = re_memory::MemoryUse::capture();
+        if let Some(counted) = mem_use.counted {
+            ui.label(format!("Memory usage: {}MiB", counted / 1024 / 1024));
+        }
+    }
+}
+
+impl RenderView for PerformanceViewer {
+    fn title(&self) -> String {
+        "Performance".into()
+    }
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        state.performance_history.ui(ui);
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/render_view.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/render_view.rs
new file mode 100644
index 0000000..077c402
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/render_view.rs
@@ -0,0 +1,14 @@
+use egui::Ui;
+
+use crate::app_state::AppState;
+
+pub trait RenderView {
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()>;
+    fn title(&self) -> String;
+}
+
+impl PartialEq for dyn RenderView {
+    fn eq(&self, other: &Self) -> bool {
+        self.title() == other.title()
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/settings_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/settings_viewer.rs
new file mode 100644
index 0000000..1f69d47
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/settings_viewer.rs
@@ -0,0 +1,80 @@
+use egui::{TextEdit, Ui};
+
+use crate::app_state::AppState;
+use crate::settings::PersistentSettings;
+use crate::views::render_view::RenderView;
+
+pub struct SettingsViewer;
+
+impl RenderView for SettingsViewer {
+    fn title(&self) -> String {
+        "Settings".into()
+    }
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let settings = &mut state.settings;
+        egui::ScrollArea::vertical().show(ui, |ui| {
+            if ui.button("Reset to defaults").clicked() {
+                settings.persistent = PersistentSettings::default();
+            }
+            ui.label("AVM source root URL:");
+            let text_edit = TextEdit::singleline(&mut settings.persistent.avm_source_url).desired_width(800.0);
+            text_edit.show(ui);
+
+            ui.horizontal(|ui| {
+                let mut apply_cache_strategy = settings.persistent.apply_cache_strategy;
+                let mut cache_strategy_limit = settings.persistent.cache_strategy_limit;
+                ui.checkbox(&mut apply_cache_strategy, "Limit number of frames kept in memory");
+                ui.add_enabled(
+                    apply_cache_strategy,
+                    egui::Slider::new(&mut cache_strategy_limit, 1..=100),
+                );
+                settings.persistent.apply_cache_strategy = apply_cache_strategy;
+                settings.persistent.cache_strategy_limit = cache_strategy_limit;
+            });
+
+            ui.checkbox(
+                &mut settings.persistent.update_sharable_url,
+                "Update URL with sharable state",
+            );
+
+            let style = &mut settings.persistent.style.overlay;
+            ui.horizontal(|ui| {
+                ui.label("Highlighted object color:");
+                ui.color_edit_button_srgba(&mut style.highlighted_object_stroke.color);
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Selected object color:");
+                ui.color_edit_button_srgba(&mut style.selected_object_stroke.color);
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Coding unit color:");
+                ui.color_edit_button_srgba(&mut style.coding_unit_stroke.color);
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Transform unit color:");
+                ui.color_edit_button_srgba(&mut style.transform_unit_stroke.color);
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Superblock color:");
+                ui.color_edit_button_srgba(&mut style.superblock_stroke.color);
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Mode name color:");
+                ui.color_edit_button_srgba(&mut style.mode_name_color);
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Pixel / coeffs viewer text color:");
+                ui.color_edit_button_srgba(&mut style.pixel_viewer_text_color);
+            });
+
+            ui.checkbox(&mut style.enable_text_shadows, "Enable text shadows");
+        });
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/stats_viewer/mod.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/stats_viewer/mod.rs
new file mode 100644
index 0000000..fa25a8c
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/stats_viewer/mod.rs
@@ -0,0 +1,235 @@
+mod pie_plot;
+
+use pie_plot::PiePlot;
+
+use anyhow::{anyhow, Context};
+use avm_stats::{FrameStatistic, StatSortMode};
+use egui::Ui;
+use log::warn;
+use std::fmt::Write;
+use wasm_bindgen::JsCast;
+use web_sys::HtmlElement;
+
+use crate::views::render_view::RenderView;
+use crate::{app_state::AppState, stream::CurrentFrame};
+pub struct StatsViewer;
+
+// TODO(comc): Move to common location.
+pub fn create_file_download(bytes: &[u8], file_name: &str) -> anyhow::Result<()> {
+    let uint8arr = js_sys::Uint8Array::new(&unsafe { js_sys::Uint8Array::view(bytes) }.into());
+    let array = js_sys::Array::new();
+    array.push(&uint8arr.buffer());
+    let blob = web_sys::Blob::new_with_u8_array_sequence_and_options(
+        &array,
+        web_sys::BlobPropertyBag::new().type_("application/octet-stream"),
+    )
+    .map_err(|_| anyhow!("Blob error"))?;
+    let download_url = web_sys::Url::create_object_url_with_blob(&blob).map_err(|_| anyhow!("Blob error"))?;
+    let window = web_sys::window().context("No window")?;
+    let document = window.document().context("No document")?;
+    let body = document.body().context("No body")?;
+    let dummy = document.create_element("a").map_err(|_| anyhow!("Element error"))?;
+    let dummy: HtmlElement = dummy
+        .dyn_into::<web_sys::HtmlElement>()
+        .map_err(|_| anyhow!("Element error"))?;
+    body.append_child(&dummy).map_err(|_| anyhow!("Element error"))?;
+    dummy
+        .set_attribute("href", &download_url)
+        .map_err(|_| anyhow!("Element error"))?;
+    dummy
+        .set_attribute("download", file_name)
+        .map_err(|_| anyhow!("Element error"))?;
+    dummy
+        .set_attribute("style", "display: none")
+        .map_err(|_| anyhow!("Element error"))?;
+    dummy.click();
+    web_sys::Url::revoke_object_url(&download_url).map_err(|_| anyhow!("Revoke URL error"))?;
+    Ok(())
+}
+
+// TODO(comc): Scroll wheel zoom, export stats, bar chart mode, table mode.
+impl RenderView for StatsViewer {
+    fn title(&self) -> String {
+        "Frame Stats".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let Some(frame) = state.stream.current_frame() else {
+            // No frame loaded yet.
+            return Ok(());
+        };
+        let stats_settings = &mut state.settings.sharable.stats_settings;
+        let prev_state = (state.settings.sharable.selected_stat, stats_settings.clone());
+
+        let mut selected_stat = state.settings.sharable.selected_stat;
+        egui::ComboBox::from_label("Statistic")
+            .selected_text(selected_stat.name())
+            .show_ui(ui, |ui| {
+                for stat in &[
+                    FrameStatistic::LumaModes,
+                    FrameStatistic::ChromaModes,
+                    FrameStatistic::BlockSizes,
+                    FrameStatistic::Symbols,
+                    FrameStatistic::PartitionSplit,
+                ] {
+                    ui.selectable_value(&mut selected_stat, *stat, stat.name());
+                }
+            });
+        ui.end_row();
+        state.settings.sharable.selected_stat = selected_stat;
+        if matches!(selected_stat, FrameStatistic::PartitionSplit) {
+            ui.horizontal(|ui| {
+                ui.label("Block sizes:")
+                    .on_hover_text("Comma separated list of block sizes, e.g. \"64x64,128x128\".");
+                ui.text_edit_singleline(&mut stats_settings.partition_split_block_sizes);
+                ui.end_row();
+            });
+        }
+
+        let mut export_data = false;
+        ui.horizontal(|ui| {
+            // TODO(comc): Find a way to implement screenshot on web.
+            // if ui.button("Save plot").clicked() {
+            //     ui.ctx().send_viewport_cmd(egui::ViewportCommand::Screenshot);
+            // }
+            if ui.button("Export data").clicked() {
+                export_data = true;
+            }
+        });
+        let mut sort_by = stats_settings.sort_by;
+        egui::ComboBox::from_label("Sort mode")
+            .selected_text(sort_by.name())
+            .show_ui(ui, |ui| {
+                for sort_mode in &[StatSortMode::ByName, StatSortMode::ByValue] {
+                    ui.selectable_value(&mut sort_by, *sort_mode, sort_mode.name());
+                }
+            });
+        ui.end_row();
+        stats_settings.sort_by = sort_by;
+
+        let mut show_relative_total = stats_settings.show_relative_total;
+        ui.checkbox(&mut show_relative_total, "Show relative total");
+        ui.end_row();
+        stats_settings.show_relative_total = show_relative_total;
+
+        ui.horizontal(|ui| {
+            let mut apply_limit_count = stats_settings.apply_limit_count;
+            let mut limit_count = stats_settings.limit_count;
+            ui.checkbox(&mut apply_limit_count, "Limit Top N");
+            ui.add_enabled(apply_limit_count, egui::Slider::new(&mut limit_count, 1..=50));
+            stats_settings.apply_limit_count = apply_limit_count;
+            stats_settings.limit_count = limit_count;
+        });
+        ui.end_row();
+
+        ui.horizontal(|ui| {
+            let mut apply_limit_frac = stats_settings.apply_limit_frac;
+            let mut limit_frac = stats_settings.limit_frac * 100.0;
+            ui.checkbox(&mut apply_limit_frac, "Threshold Percent");
+            ui.add_enabled(
+                apply_limit_frac,
+                egui::Slider::new(&mut limit_frac, 0.0..=50.0).step_by(0.1),
+            );
+            stats_settings.apply_limit_frac = apply_limit_frac;
+            stats_settings.limit_frac = limit_frac / 100.0;
+        });
+        ui.end_row();
+
+        ui.horizontal(|ui| {
+            ui.label("Include:");
+            let mut include_filter_exact_match = stats_settings.include_filter_exact_match;
+            ui.checkbox(&mut include_filter_exact_match, "Exact match");
+            ui.text_edit_singleline(&mut stats_settings.include_filter);
+            stats_settings.include_filter_exact_match = include_filter_exact_match;
+        });
+        ui.end_row();
+
+        ui.horizontal(|ui| {
+            ui.label("Exclude:");
+            let mut exclude_filter_exact_match = stats_settings.exclude_filter_exact_match;
+            ui.checkbox(&mut exclude_filter_exact_match, "Exact match");
+            ui.text_edit_singleline(&mut stats_settings.exclude_filter);
+            stats_settings.exclude_filter_exact_match = exclude_filter_exact_match;
+        });
+        ui.end_row();
+
+        let decimal_precision = match selected_stat {
+            FrameStatistic::LumaModes => 0,
+            FrameStatistic::ChromaModes => 0,
+            FrameStatistic::BlockSizes => 0,
+            FrameStatistic::Symbols => 2,
+            FrameStatistic::PartitionSplit => 0,
+        };
+
+        let pie_plot = PiePlot {
+            decimal_precision,
+            ..Default::default()
+        };
+
+        let changed = state.settings.sharable.selected_stat != prev_state.0 || *stats_settings != prev_state.1;
+        let calculate_data = changed || state.settings.cached_stat_data.is_none();
+
+        if calculate_data {
+            state.settings.cached_stat_data = Some(selected_stat.calculate(frame, stats_settings));
+        }
+        let _resp = pie_plot.show(ui, state.settings.cached_stat_data.as_ref().unwrap());
+        if export_data {
+            if let Some(stream) = &state.stream {
+                let file_name = format!(
+                    "{}_frame_{:04}_{}.csv",
+                    stream.stream_info.stream_name,
+                    stream.current_frame_index,
+                    selected_stat.name().replace(' ', "_")
+                );
+
+                let data = state.settings.cached_stat_data.as_ref().unwrap();
+                let total: f64 = data.iter().map(|sample| sample.value).sum();
+                let header = format!("{},Count,Percent\n", selected_stat.name());
+                let csv: String = data.iter().fold(String::new(), |mut output, sample| {
+                    let _ = writeln!(output, "{},{},{}", sample.name, sample.value, sample.value / total);
+                    output
+                });
+                let mut bytes: Vec<u8> = header.bytes().collect();
+                bytes.extend(csv.bytes());
+
+                if let Err(err) = create_file_download(&bytes, &file_name) {
+                    warn!("Failed to create file download: {err:?}");
+                }
+            }
+        }
+
+        // TODO(comc): Find a way to implement screenshot on web.
+        // let screenshot = ui.ctx().input(|i| {
+        //     for event in &i.raw.events {
+        //         if let egui::Event::Screenshot { image, .. } = event {
+        //             return Some(image.clone());
+        //         }
+        //     }
+        //     None
+        // });
+
+        // if let Some(screenshot) = screenshot {
+        //     let mut bytes = Vec::new();
+        //     let png_encoder = image::codecs::png::PngEncoder::new(Cursor::new(&mut bytes));
+        //     let pixels_per_point = ui.ctx().pixels_per_point();
+        //     let plot = screenshot.region(&resp.response.rect, Some(pixels_per_point));
+        //     match png_encoder.write_image(
+        //         plot.as_raw(),
+        //         plot.width() as u32,
+        //         plot.height() as u32,
+        //         image::ColorType::Rgba8,
+        //     ) {
+        //         Ok(_) => {
+        //             if let Err(err) = create_file_download(&bytes, "test.png") {
+        //                 warn!("Failed to create file download: {err:?}");
+        //             }
+        //         }
+        //         Err(err) => {
+        //             warn!("Failed to encode PNG: {err:?}");
+        //         }
+        //     }
+        // }
+
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/stats_viewer/pie_plot.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/stats_viewer/pie_plot.rs
new file mode 100644
index 0000000..9e2a916
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/stats_viewer/pie_plot.rs
@@ -0,0 +1,98 @@
+use std::f64::consts::PI;
+
+use avm_stats::Sample;
+use egui::RichText;
+use egui_plot::{Plot, PlotPoint, PlotPoints, PlotResponse, Polygon, Text};
+
+pub struct PiePlot {
+    pub num_vertices: usize,
+    pub decimal_precision: usize,
+}
+
+impl Default for PiePlot {
+    fn default() -> Self {
+        Self {
+            num_vertices: 360,
+            decimal_precision: 2,
+        }
+    }
+}
+
+impl PiePlot {
+    pub fn show(&self, ui: &mut egui::Ui, data: &[Sample]) -> PlotResponse<()> {
+        let total: f64 = data.iter().map(|sample| sample.value).sum();
+        let mut cumulative_sum = 0.0;
+
+        Plot::new("pie_plot")
+            .show_background(false)
+            .show_axes([false; 2])
+            .clamp_grid(true)
+            .show_grid(false)
+            .allow_boxed_zoom(false)
+            .allow_drag(false)
+            .allow_zoom(false)
+            .allow_scroll(false)
+            .data_aspect(1.0)
+            .show_x(false)
+            .show_y(false)
+            .include_x(-1.1)
+            .include_x(1.1)
+            .include_y(-1.1)
+            .include_y(1.1)
+            .show(ui, |plot_ui| {
+                for Sample { name, value } in data.iter() {
+                    let fraction = value / total;
+                    let num_vertices = (self.num_vertices as f64 * fraction).ceil() as usize;
+                    let start_angle = 2.0 * PI * cumulative_sum;
+                    let end_angle = 2.0 * PI * (cumulative_sum + fraction);
+                    cumulative_sum += fraction;
+                    let mut points = vec![];
+
+                    if data.len() > 1 {
+                        points.push([0.0, 0.0]);
+                    }
+
+                    let angle_step = (end_angle - start_angle) / num_vertices as f64;
+                    points.extend((0..=num_vertices).map(|i| {
+                        let angle = start_angle + angle_step * i as f64;
+                        [angle.sin(), angle.cos()]
+                    }));
+
+                    let center_angle = start_angle + (end_angle - start_angle) / 2.0;
+                    let center_x = 0.75 * center_angle.sin();
+                    let center_y = 0.75 * center_angle.cos();
+
+                    let hovered = plot_ui
+                        .pointer_coordinate()
+                        .map(|pointer| {
+                            let radius = pointer.y.hypot(pointer.x);
+                            let mut theta = pointer.x.atan2(pointer.y);
+                            if theta < 0.0 {
+                                theta += 2.0 * PI;
+                            }
+                            radius < 1.0 && start_angle < theta && theta < end_angle
+                        })
+                        .unwrap_or_default();
+
+                    plot_ui.polygon(Polygon::new(PlotPoints::new(points)).name(name).highlight(hovered));
+
+                    let label = format!("{} - {:.2}%", name, 100.0 * value / total);
+                    plot_ui.text(Text::new(PlotPoint::new(center_x, center_y), label));
+
+                    if hovered {
+                        let pointer = plot_ui.pointer_coordinate().unwrap();
+                        let label = format!(
+                            "{} - {:.2}% ({:.prec$}/{:.prec$})",
+                            name,
+                            100.0 * fraction,
+                            value,
+                            total,
+                            prec = self.decimal_precision
+                        );
+
+                        plot_ui.text(Text::new(pointer, RichText::new(label).heading()).name(name));
+                    }
+                }
+            })
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/stream_select_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/stream_select_viewer.rs
new file mode 100644
index 0000000..53257cb
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/stream_select_viewer.rs
@@ -0,0 +1,70 @@
+use egui::load::Bytes;
+use egui::{Image, ImageSource, RichText, Sense, Ui};
+use egui_extras::{Column, TableBuilder};
+use itertools::Itertools;
+
+use crate::app_state::AppState;
+use crate::stream::Stream;
+use crate::views::render_view::RenderView;
+
+pub struct StreamSelectViewer;
+
+impl RenderView for StreamSelectViewer {
+    fn title(&self) -> String {
+        "Stream Select".into()
+    }
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        if ui.button("Refresh stream list").clicked() {
+            state.http_stream_manager.load_stream_list();
+        }
+        let mut set_stream = None;
+        let streams = state.http_stream_manager.streams.lock().unwrap();
+        TableBuilder::new(ui)
+            .column(Column::initial(600.0).resizable(true))
+            .column(Column::remainder().resizable(false).at_least(100.0))
+            .sense(Sense::click())
+            .striped(true)
+            .header(20.0, |mut header| {
+                header.col(|ui| {
+                    ui.heading("Name");
+                });
+                header.col(|ui| {
+                    ui.heading("Preview");
+                });
+            })
+            .body(|body| {
+                let streams: Vec<_> = streams
+                    .iter()
+                    .sorted_by_key(|stream| stream.stream_name.as_str())
+                    .collect();
+                let num_rows: usize = streams.len();
+                body.rows(100.0, num_rows, |mut row| {
+                    let stream_info = streams[row.index()];
+                    row.col(|ui| {
+                        let text = RichText::new(stream_info.stream_name.as_str()).heading();
+                        ui.label(text);
+                    });
+                    row.col(|ui| {
+                        if let Some(thumbnail_png) = &stream_info.thumbnail_png {
+                            // TODO(comc): Make stream_info.thumbnail_png an Arc to avoid copy.
+                            let thumbnail_png = thumbnail_png.clone();
+                            let uri = format!("bytes://{}", stream_info.stream_name);
+                            let image = Image::new(ImageSource::Bytes {
+                                uri: uri.into(),
+                                bytes: Bytes::Shared(thumbnail_png.into()),
+                            });
+                            ui.add(image);
+                        }
+                    });
+                    if row.response().clicked() {
+                        set_stream = Some(stream_info.clone());
+                    }
+                });
+            });
+        if let Some(set_stream) = set_stream {
+            state.stream = Some(Stream::from_http(set_stream, false, 0, &state.settings.sharable.streams_url)?);
+            state.settings.show_stream_select = false;
+        }
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/symbol_info_viewer.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/symbol_info_viewer.rs
new file mode 100644
index 0000000..6fd8810
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/symbol_info_viewer.rs
@@ -0,0 +1,104 @@
+use crate::views::render_view::RenderView;
+use egui::{RichText, TextBuffer, Ui};
+use egui_extras::{Column, TableBuilder};
+
+use crate::app_state::AppState;
+use crate::stream::CurrentFrame;
+
+const AVM_SOURCE_ROOT: &str = "avm/";
+
+pub struct SymbolInfoViewer;
+
+impl RenderView for SymbolInfoViewer {
+    fn title(&self) -> String {
+        "Symbol Info".into()
+    }
+
+    fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
+        let Some(frame) = state.stream.current_frame() else {
+            // No frame loaded yet.
+            return Ok(());
+        };
+        let Some(selected_object) = &mut state.settings.selected_object else {
+            // No coding unit is selected.
+            return Ok(());
+        };
+        let Ok(info) = selected_object.get_or_calculate_info(frame) else {
+            return Ok(());
+        };
+
+        ui.horizontal(|ui| {
+            ui.label("Filter:");
+            ui.text_edit_singleline(&mut state.settings.sharable.symbol_info_filter);
+        });
+        ui.end_row();
+        ui.checkbox(&mut state.settings.sharable.symbol_info_show_tags, "Show tags");
+
+        ui.separator();
+        TableBuilder::new(ui)
+            .column(Column::initial(200.0).resizable(true))
+            .column(Column::initial(100.0).resizable(true))
+            .column(Column::remainder())
+            .striped(true)
+            .header(20.0, |mut header| {
+                header.col(|ui| {
+                    ui.label(RichText::new("Symbol Type").strong());
+                });
+                header.col(|ui| {
+                    ui.label(RichText::new("Value").strong());
+                });
+                header.col(|ui| {
+                    ui.label(RichText::new("Bits").strong());
+                });
+            })
+            .body(|body| {
+                let symbols = info.symbols.clone();
+                let symbols = if state.settings.sharable.symbol_info_filter.is_empty() {
+                    symbols
+                } else {
+                    symbols
+                        .into_iter()
+                        .filter(|row| {
+                            row.func.contains(&state.settings.sharable.symbol_info_filter)
+                                || (state.settings.sharable.symbol_info_show_tags
+                                    && row
+                                        .tags
+                                        .iter()
+                                        .any(|tag| tag.contains(&state.settings.sharable.symbol_info_filter)))
+                        })
+                        .collect()
+                };
+                body.rows(20.0, symbols.len(), |mut row| {
+                    let symbol = &symbols[row.index()];
+                    row.col(|col| {
+                        let prefix_start = symbol.file.rfind(AVM_SOURCE_ROOT);
+                        let name = if symbol.tags.is_empty() || !state.settings.sharable.symbol_info_show_tags {
+                            symbol.func.to_string()
+                        } else {
+                            format!("{} ({})", symbol.func, symbol.tags.join("+"))
+                        };
+
+                        if let Some(prefix_start) = prefix_start {
+                            let relative_path = symbol
+                                .file
+                                .char_range(prefix_start + AVM_SOURCE_ROOT.len()..symbol.file.len());
+                            let url = format!(
+                                "{}/{}#L{}",
+                                state.settings.persistent.avm_source_url, relative_path, symbol.line
+                            );
+                            col.hyperlink_to(name, url);
+                        } else {
+                            col.label(name);
+                        }
+                    });
+                    row.col(|col| {
+                        col.label(format!("{}", symbol.value));
+                    });
+                    row.col(|col| {
+                        col.label(format!("{}", symbol.bits));
+                    });
+                });
+            });
+        Ok(())
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_app/src/views/view_mode.rs b/tools/avm_analyzer/avm_analyzer_app/src/views/view_mode.rs
new file mode 100644
index 0000000..1b78509
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_app/src/views/view_mode.rs
@@ -0,0 +1,106 @@
+use avm_stats::PixelType;
+use serde::{Deserialize, Serialize};
+
+use crate::settings::DistortionView;
+
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum ViewMode {
+    #[default]
+    CodingFlow,
+    Prediction,
+    Transform,
+    Filters,
+    Distortion(DistortionView),
+    Motion,
+    Heatmap,
+}
+
+pub struct ViewSettings {
+    pub show_superblocks: bool,
+    pub show_coding_units: bool,
+    pub show_transform_units: bool,
+    pub show_prediction_modes: bool,
+    pub show_transform_types: bool,
+    pub show_motion_vectors: bool,
+    pub pixel_type: PixelType,
+    pub show_heatmap: bool,
+    pub allow_coding_unit_selection: bool,
+    pub allow_transform_unit_selection: bool,
+}
+
+impl std::fmt::Display for ViewMode {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let s = match self {
+            ViewMode::CodingFlow => "Coding Flow",
+            ViewMode::Prediction => "Prediction",
+            ViewMode::Transform => "Transform",
+            ViewMode::Filters => "Filters",
+            ViewMode::Distortion(_) => "Distortion",
+            ViewMode::Motion => "Motion",
+            ViewMode::Heatmap => "Heatmap",
+        };
+        write!(f, "{s}")
+    }
+}
+
+impl ViewMode {
+    pub fn view_settings(&self) -> ViewSettings {
+        let show_superblocks = true;
+        let show_coding_units = true;
+        let mut show_transform_units = false;
+        let mut show_prediction_modes: bool = false;
+        let mut show_transform_types: bool = false;
+        let mut show_motion_vectors: bool = false;
+        let mut pixel_type = PixelType::Reconstruction;
+        let mut show_heatmap = false;
+        let mut allow_coding_unit_selection = true;
+        let mut allow_transform_unit_selection = false;
+        match self {
+            ViewMode::CodingFlow => {
+                pixel_type = PixelType::Reconstruction;
+            }
+            ViewMode::Prediction => {
+                pixel_type = PixelType::Prediction;
+                show_transform_units = true;
+                show_prediction_modes = true;
+            }
+            ViewMode::Transform => {
+                pixel_type = PixelType::Residual;
+                show_transform_units = true;
+                show_transform_types = true;
+                allow_coding_unit_selection = false;
+                allow_transform_unit_selection = true;
+            }
+            ViewMode::Filters => {
+                pixel_type = PixelType::FilterDelta;
+            }
+            ViewMode::Distortion(distortion_view) => {
+                pixel_type = match distortion_view {
+                    DistortionView::Distortion => PixelType::Distortion,
+                    DistortionView::Original => PixelType::Original,
+                    DistortionView::Reconstruction => PixelType::Reconstruction,
+                }
+            }
+            ViewMode::Motion => {
+                show_motion_vectors = true;
+                pixel_type = PixelType::Prediction;
+            }
+            ViewMode::Heatmap => {
+                show_heatmap = true;
+            }
+        }
+
+        ViewSettings {
+            show_superblocks,
+            show_coding_units,
+            show_transform_units,
+            show_prediction_modes,
+            show_transform_types,
+            show_motion_vectors,
+            pixel_type,
+            show_heatmap,
+            allow_coding_unit_selection,
+            allow_transform_unit_selection,
+        }
+    }
+}
diff --git a/tools/avm_analyzer/avm_analyzer_common/Cargo.toml b/tools/avm_analyzer/avm_analyzer_common/Cargo.toml
new file mode 100644
index 0000000..096687a
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_common/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "avm-analyzer-common"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde = { version = "1.0", features = ["derive"] }
diff --git a/tools/avm_analyzer/avm_analyzer_common/src/lib.rs b/tools/avm_analyzer/avm_analyzer_common/src/lib.rs
new file mode 100644
index 0000000..56a6707
--- /dev/null
+++ b/tools/avm_analyzer/avm_analyzer_common/src/lib.rs
@@ -0,0 +1,62 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ProgressRequest {
+    pub stream_name: String,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct DecodeProgress {
+    pub decoded_frames: usize,
+    pub total_frames: usize,
+}
+
+// TODO(comc): Add timeout state?
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub enum DecodeState {
+    /// Upload request sent by client, not yet acknowledged by server.
+    Uploading,
+    /// Upload was successful and the server sent confirmation.
+    UploadComplete,
+    /// Decode (extract_proto) is is progress.
+    Pending(DecodeProgress),
+    /// Decoding succeeded.
+    Complete(usize),
+    /// Decode failed for any reason.
+    Failed,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ProgressResponse {
+    pub stream_name: String,
+    pub state: DecodeState,
+}
+