diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8d36e3be68f696db797915902f9ea258839196b7..b90f3ebb07cef39d1e943bf704b5454751e9bef5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,8 +16,11 @@
         c, t = get_summary(process_folder())
         using Printf
         @printf "Test coverage %.2f%%\n" 100c / t'
-Julia 1.9:
-  image: julia:1.9
+Julia 1.10:
+  image: julia:1.10
+  extends:
+    - .script
+Julia 1.11:
+  image: julia:1.11
   extends:
     - .script
-    - .coverage
diff --git a/Manifest-v1.11.toml b/Manifest-v1.11.toml
new file mode 100644
index 0000000000000000000000000000000000000000..f2d4c37b4ed7ab406a7b3b4bf344e8d1dbbfd2ed
--- /dev/null
+++ b/Manifest-v1.11.toml
@@ -0,0 +1,1756 @@
+# This file is machine-generated - editing it directly is not advised
+
+julia_version = "1.11.1"
+manifest_format = "2.0"
+project_hash = "fccf84810b56f9ba8947369bbaf414d318bc47f2"
+
+[[deps.AbstractFFTs]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef"
+uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
+version = "1.5.0"
+weakdeps = ["ChainRulesCore", "Test"]
+
+    [deps.AbstractFFTs.extensions]
+    AbstractFFTsChainRulesCoreExt = "ChainRulesCore"
+    AbstractFFTsTestExt = "Test"
+
+[[deps.AbstractTrees]]
+git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177"
+uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
+version = "0.4.5"
+
+[[deps.Adapt]]
+deps = ["LinearAlgebra", "Requires"]
+git-tree-sha1 = "50c3c56a52972d78e8be9fd135bfb91c9574c140"
+uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
+version = "4.1.1"
+weakdeps = ["StaticArrays"]
+
+    [deps.Adapt.extensions]
+    AdaptStaticArraysExt = "StaticArrays"
+
+[[deps.AdaptivePredicates]]
+git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6"
+uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7"
+version = "1.2.0"
+
+[[deps.AliasTables]]
+deps = ["PtrArrays", "Random"]
+git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff"
+uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
+version = "1.1.3"
+
+[[deps.Animations]]
+deps = ["Colors"]
+git-tree-sha1 = "e81c509d2c8e49592413bfb0bb3b08150056c79d"
+uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
+version = "0.4.1"
+
+[[deps.ArgTools]]
+uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+version = "1.1.2"
+
+[[deps.Artifacts]]
+uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
+version = "1.11.0"
+
+[[deps.Automa]]
+deps = ["PrecompileTools", "SIMD", "TranscodingStreams"]
+git-tree-sha1 = "a8f503e8e1a5f583fbef15a8440c8c7e32185df2"
+uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b"
+version = "1.1.0"
+
+[[deps.AxisAlgorithms]]
+deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
+git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712"
+uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
+version = "1.1.0"
+
+[[deps.AxisArrays]]
+deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"]
+git-tree-sha1 = "16351be62963a67ac4083f748fdb3cca58bfd52f"
+uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
+version = "0.4.7"
+
+[[deps.Base64]]
+uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
+version = "1.11.0"
+
+[[deps.Bessels]]
+git-tree-sha1 = "4435559dc39793d53a9e3d278e185e920b4619ef"
+uuid = "0e736298-9ec6-45e8-9647-e4fc86a2fe38"
+version = "0.2.8"
+
+[[deps.BitFlags]]
+git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d"
+uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
+version = "0.1.9"
+
+[[deps.Bonito]]
+deps = ["Base64", "CodecZlib", "Colors", "Dates", "Deno_jll", "HTTP", "Hyperscript", "LinearAlgebra", "Markdown", "MsgPack", "Observables", "RelocatableFolders", "SHA", "Sockets", "Tables", "ThreadPools", "URIs", "UUIDs", "WidgetsBase"]
+git-tree-sha1 = "d7635780a8cfe0cb43c075276fd358c5b166695e"
+uuid = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
+version = "3.2.4"
+
+[[deps.BufferedStreams]]
+git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
+uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
+version = "1.2.2"
+
+[[deps.Bzip2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1"
+uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
+version = "1.0.8+2"
+
+[[deps.CEnum]]
+git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
+uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
+version = "0.5.0"
+
+[[deps.CRC32c]]
+uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc"
+version = "1.11.0"
+
+[[deps.CRlibm_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc"
+uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0"
+version = "1.0.1+0"
+
+[[deps.Cairo_jll]]
+deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
+git-tree-sha1 = "009060c9a6168704143100f36ab08f06c2af4642"
+uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
+version = "1.18.2+1"
+
+[[deps.ChainRulesCore]]
+deps = ["Compat", "LinearAlgebra"]
+git-tree-sha1 = "3e4b134270b372f2ed4d4d0e936aabaefc1802bc"
+uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+version = "1.25.0"
+weakdeps = ["SparseArrays"]
+
+    [deps.ChainRulesCore.extensions]
+    ChainRulesCoreSparseArraysExt = "SparseArrays"
+
+[[deps.CircularArrays]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "e24a6f390e5563583bb4315c73035b5b3f3e7ab4"
+uuid = "7a955b69-7140-5f4e-a0ed-f168c5e2e749"
+version = "1.4.0"
+
+[[deps.CodecZlib]]
+deps = ["TranscodingStreams", "Zlib_jll"]
+git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
+uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
+version = "0.7.6"
+
+[[deps.ColorBrewer]]
+deps = ["Colors", "JSON", "Test"]
+git-tree-sha1 = "61c5334f33d91e570e1d0c3eb5465835242582c4"
+uuid = "a2cac450-b92f-5266-8821-25eda20663c8"
+version = "0.4.0"
+
+[[deps.ColorSchemes]]
+deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"]
+git-tree-sha1 = "13951eb68769ad1cd460cdb2e64e5e95f1bf123d"
+uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
+version = "3.27.0"
+
+[[deps.ColorTypes]]
+deps = ["FixedPointNumbers", "Random"]
+git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d"
+uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
+version = "0.11.5"
+
+[[deps.ColorVectorSpace]]
+deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"]
+git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249"
+uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
+version = "0.10.0"
+weakdeps = ["SpecialFunctions"]
+
+    [deps.ColorVectorSpace.extensions]
+    SpecialFunctionsExt = "SpecialFunctions"
+
+[[deps.Colors]]
+deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
+git-tree-sha1 = "362a287c3aa50601b0bc359053d5c2468f0e7ce0"
+uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
+version = "0.12.11"
+
+[[deps.Compat]]
+deps = ["TOML", "UUIDs"]
+git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215"
+uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
+version = "4.16.0"
+weakdeps = ["Dates", "LinearAlgebra"]
+
+    [deps.Compat.extensions]
+    CompatLinearAlgebraExt = "LinearAlgebra"
+
+[[deps.CompilerSupportLibraries_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
+version = "1.1.1+0"
+
+[[deps.ConcurrentUtilities]]
+deps = ["Serialization", "Sockets"]
+git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
+uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
+version = "2.4.2"
+
+[[deps.ConstructionBase]]
+git-tree-sha1 = "76219f1ed5771adbb096743bff43fb5fdd4c1157"
+uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
+version = "1.5.8"
+weakdeps = ["IntervalSets", "LinearAlgebra", "StaticArrays"]
+
+    [deps.ConstructionBase.extensions]
+    ConstructionBaseIntervalSetsExt = "IntervalSets"
+    ConstructionBaseLinearAlgebraExt = "LinearAlgebra"
+    ConstructionBaseStaticArraysExt = "StaticArrays"
+
+[[deps.Contour]]
+git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8"
+uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
+version = "0.6.3"
+
+[[deps.DataAPI]]
+git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe"
+uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
+version = "1.16.0"
+
+[[deps.DataStructures]]
+deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
+git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82"
+uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
+version = "0.18.20"
+
+[[deps.DataValueInterfaces]]
+git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
+uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
+version = "1.0.0"
+
+[[deps.Dates]]
+deps = ["Printf"]
+uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
+version = "1.11.0"
+
+[[deps.DelaunayTriangulation]]
+deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "PrecompileTools", "Random"]
+git-tree-sha1 = "89df54fbe66e5872d91d8c2cd3a375f660c3fd64"
+uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
+version = "1.6.1"
+
+[[deps.DelimitedFiles]]
+deps = ["Mmap"]
+git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae"
+uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
+version = "1.9.1"
+
+[[deps.Deno_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "cd6756e833c377e0ce9cd63fb97689a255f12323"
+uuid = "04572ae6-984a-583e-9378-9577a1c2574d"
+version = "1.33.4+0"
+
+[[deps.Distances]]
+deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
+git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d"
+uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
+version = "0.10.12"
+weakdeps = ["ChainRulesCore", "SparseArrays"]
+
+    [deps.Distances.extensions]
+    DistancesChainRulesCoreExt = "ChainRulesCore"
+    DistancesSparseArraysExt = "SparseArrays"
+
+[[deps.Distributed]]
+deps = ["Random", "Serialization", "Sockets"]
+uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
+version = "1.11.0"
+
+[[deps.Distributions]]
+deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
+git-tree-sha1 = "3101c32aab536e7a27b1763c0797dba151b899ad"
+uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
+version = "0.25.113"
+
+    [deps.Distributions.extensions]
+    DistributionsChainRulesCoreExt = "ChainRulesCore"
+    DistributionsDensityInterfaceExt = "DensityInterface"
+    DistributionsTestExt = "Test"
+
+    [deps.Distributions.weakdeps]
+    ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+    DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
+    Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[deps.DocOpt]]
+deps = ["Dates", "Printf"]
+git-tree-sha1 = "9c2681389ed8d9cf6193b1ba31796f12456b111a"
+uuid = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
+version = "0.5.0"
+
+[[deps.DocStringExtensions]]
+deps = ["LibGit2"]
+git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d"
+uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
+version = "0.9.3"
+
+[[deps.Downloads]]
+deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
+uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+version = "1.6.0"
+
+[[deps.EarCut_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053"
+uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
+version = "2.2.4+0"
+
+[[deps.EnumX]]
+git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237"
+uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
+version = "1.0.4"
+
+[[deps.ExactPredicates]]
+deps = ["IntervalArithmetic", "Random", "StaticArrays"]
+git-tree-sha1 = "b3f2ff58735b5f024c392fde763f29b057e4b025"
+uuid = "429591f6-91af-11e9-00e2-59fbe8cec110"
+version = "2.2.8"
+
+[[deps.ExceptionUnwrapping]]
+deps = ["Test"]
+git-tree-sha1 = "dcb08a0d93ec0b1cdc4af184b26b591e9695423a"
+uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
+version = "0.1.10"
+
+[[deps.Expat_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7"
+uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
+version = "2.6.2+0"
+
+[[deps.Extents]]
+git-tree-sha1 = "81023caa0021a41712685887db1fc03db26f41f5"
+uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
+version = "0.1.4"
+
+[[deps.FFMPEG_jll]]
+deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
+git-tree-sha1 = "8cc47f299902e13f90405ddb5bf87e5d474c0d38"
+uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
+version = "6.1.2+0"
+
+[[deps.FFTW]]
+deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
+git-tree-sha1 = "4820348781ae578893311153d69049a93d05f39d"
+uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
+version = "1.8.0"
+
+[[deps.FFTW_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "4d81ed14783ec49ce9f2e168208a12ce1815aa25"
+uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
+version = "3.3.10+1"
+
+[[deps.FileIO]]
+deps = ["Pkg", "Requires", "UUIDs"]
+git-tree-sha1 = "62ca0547a14c57e98154423419d8a342dca75ca9"
+uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
+version = "1.16.4"
+
+[[deps.FilePaths]]
+deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"]
+git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629"
+uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824"
+version = "0.8.3"
+
+[[deps.FilePathsBase]]
+deps = ["Compat", "Dates"]
+git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
+uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
+version = "0.9.22"
+weakdeps = ["Mmap", "Test"]
+
+    [deps.FilePathsBase.extensions]
+    FilePathsBaseMmapExt = "Mmap"
+    FilePathsBaseTestExt = "Test"
+
+[[deps.FileWatching]]
+uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
+version = "1.11.0"
+
+[[deps.FillArrays]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a"
+uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
+version = "1.13.0"
+weakdeps = ["PDMats", "SparseArrays", "Statistics"]
+
+    [deps.FillArrays.extensions]
+    FillArraysPDMatsExt = "PDMats"
+    FillArraysSparseArraysExt = "SparseArrays"
+    FillArraysStatisticsExt = "Statistics"
+
+[[deps.FixedPointNumbers]]
+deps = ["Statistics"]
+git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172"
+uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
+version = "0.8.5"
+
+[[deps.Fontconfig_jll]]
+deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"]
+git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23"
+uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
+version = "2.13.96+0"
+
+[[deps.Format]]
+git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc"
+uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
+version = "1.3.7"
+
+[[deps.FreeType]]
+deps = ["CEnum", "FreeType2_jll"]
+git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999"
+uuid = "b38be410-82b0-50bf-ab77-7b57e271db43"
+version = "4.1.1"
+
+[[deps.FreeType2_jll]]
+deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc"
+uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
+version = "2.13.2+0"
+
+[[deps.FreeTypeAbstraction]]
+deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"]
+git-tree-sha1 = "84dfe824bd6fdf2a5d73bb187ff31b5549b2a79c"
+uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
+version = "0.10.4"
+
+[[deps.FriBidi_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1ed150b39aebcc805c26b93a8d0122c940f64ce2"
+uuid = "559328eb-81f9-559d-9380-de523a88c83c"
+version = "1.0.14+0"
+
+[[deps.GeoFormatTypes]]
+git-tree-sha1 = "59107c179a586f0fe667024c5eb7033e81333271"
+uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f"
+version = "0.4.2"
+
+[[deps.GeoInterface]]
+deps = ["Extents", "GeoFormatTypes"]
+git-tree-sha1 = "826b4fd69438d9ce4d2b19de6bc2f970f45f0f88"
+uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
+version = "1.3.8"
+
+[[deps.GeometryBasics]]
+deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
+git-tree-sha1 = "b62f2b2d76cee0d61a2ef2b3118cd2a3215d3134"
+uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
+version = "0.4.11"
+
+[[deps.Gettext_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
+git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
+uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
+version = "0.21.0+0"
+
+[[deps.Giflib_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0224cce99284d997f6880a42ef715a37c99338d1"
+uuid = "59f7168a-df46-5410-90c8-f2779963d0ec"
+version = "5.2.2+0"
+
+[[deps.Glib_jll]]
+deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"]
+git-tree-sha1 = "674ff0db93fffcd11a3573986e550d66cd4fd71f"
+uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
+version = "2.80.5+0"
+
+[[deps.Graphite2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
+uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
+version = "1.3.14+0"
+
+[[deps.GridLayoutBase]]
+deps = ["GeometryBasics", "InteractiveUtils", "Observables"]
+git-tree-sha1 = "fc713f007cff99ff9e50accba6373624ddd33588"
+uuid = "3955a311-db13-416c-9275-1d80ed98e5e9"
+version = "0.11.0"
+
+[[deps.Grisu]]
+git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
+uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
+version = "1.0.2"
+
+[[deps.HDF5]]
+deps = ["Compat", "HDF5_jll", "Libdl", "MPIPreferences", "Mmap", "Preferences", "Printf", "Random", "Requires", "UUIDs"]
+git-tree-sha1 = "e856eef26cf5bf2b0f95f8f4fc37553c72c8641c"
+uuid = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
+version = "0.17.2"
+
+    [deps.HDF5.extensions]
+    MPIExt = "MPI"
+
+    [deps.HDF5.weakdeps]
+    MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
+
+[[deps.HDF5_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "LazyArtifacts", "LibCURL_jll", "Libdl", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "libaec_jll"]
+git-tree-sha1 = "38c8874692d48d5440d5752d6c74b0c6b0b60739"
+uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
+version = "1.14.2+1"
+
+[[deps.HTTP]]
+deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
+git-tree-sha1 = "bc3f416a965ae61968c20d0ad867556367f2817d"
+uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
+version = "1.10.9"
+
+[[deps.HarfBuzz_jll]]
+deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"]
+git-tree-sha1 = "401e4f3f30f43af2c8478fc008da50096ea5240f"
+uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
+version = "8.3.1+0"
+
+[[deps.Hwloc_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "dd3b49277ec2bb2c6b94eb1604d4d0616016f7a6"
+uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
+version = "2.11.2+0"
+
+[[deps.HypergeometricFunctions]]
+deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
+git-tree-sha1 = "7c4195be1649ae622304031ed46a2f4df989f1eb"
+uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
+version = "0.3.24"
+
+[[deps.Hyperscript]]
+deps = ["Test"]
+git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4"
+uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
+version = "0.0.5"
+
+[[deps.ImageAxes]]
+deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"]
+git-tree-sha1 = "2e4520d67b0cef90865b3ef727594d2a58e0e1f8"
+uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
+version = "0.6.11"
+
+[[deps.ImageBase]]
+deps = ["ImageCore", "Reexport"]
+git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909"
+uuid = "c817782e-172a-44cc-b673-b171935fbb9e"
+version = "0.1.7"
+
+[[deps.ImageCore]]
+deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"]
+git-tree-sha1 = "b2a7eaa169c13f5bcae8131a83bc30eff8f71be0"
+uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
+version = "0.10.2"
+
+[[deps.ImageIO]]
+deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"]
+git-tree-sha1 = "696144904b76e1ca433b886b4e7edd067d76cbf7"
+uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
+version = "0.6.9"
+
+[[deps.ImageMetadata]]
+deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"]
+git-tree-sha1 = "355e2b974f2e3212a75dfb60519de21361ad3cb7"
+uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
+version = "0.9.9"
+
+[[deps.Imath_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0936ba688c6d201805a83da835b55c61a180db52"
+uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1"
+version = "3.1.11+0"
+
+[[deps.IndirectArrays]]
+git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f"
+uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
+version = "1.0.0"
+
+[[deps.Inflate]]
+git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d"
+uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
+version = "0.1.5"
+
+[[deps.IntelOpenMP_jll]]
+deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
+git-tree-sha1 = "10bd689145d2c3b2a9844005d01087cc1194e79e"
+uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
+version = "2024.2.1+0"
+
+[[deps.InteractiveUtils]]
+deps = ["Markdown"]
+uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
+version = "1.11.0"
+
+[[deps.Interpolations]]
+deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
+git-tree-sha1 = "88a101217d7cb38a7b481ccd50d21876e1d1b0e0"
+uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
+version = "0.15.1"
+weakdeps = ["Unitful"]
+
+    [deps.Interpolations.extensions]
+    InterpolationsUnitfulExt = "Unitful"
+
+[[deps.IntervalArithmetic]]
+deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "RoundingEmulator"]
+git-tree-sha1 = "24c095b1ec7ee58b936985d31d5df92f9b9cfebb"
+uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
+version = "0.22.19"
+
+    [deps.IntervalArithmetic.extensions]
+    IntervalArithmeticDiffRulesExt = "DiffRules"
+    IntervalArithmeticForwardDiffExt = "ForwardDiff"
+    IntervalArithmeticIntervalSetsExt = "IntervalSets"
+    IntervalArithmeticRecipesBaseExt = "RecipesBase"
+
+    [deps.IntervalArithmetic.weakdeps]
+    DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
+    ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+    IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+
+[[deps.IntervalSets]]
+git-tree-sha1 = "dba9ddf07f77f60450fe5d2e2beb9854d9a49bd0"
+uuid = "8197267c-284f-5f27-9208-e0e47529a953"
+version = "0.7.10"
+weakdeps = ["Random", "RecipesBase", "Statistics"]
+
+    [deps.IntervalSets.extensions]
+    IntervalSetsRandomExt = "Random"
+    IntervalSetsRecipesBaseExt = "RecipesBase"
+    IntervalSetsStatisticsExt = "Statistics"
+
+[[deps.InverseFunctions]]
+git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb"
+uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
+version = "0.1.17"
+weakdeps = ["Dates", "Test"]
+
+    [deps.InverseFunctions.extensions]
+    InverseFunctionsDatesExt = "Dates"
+    InverseFunctionsTestExt = "Test"
+
+[[deps.IrrationalConstants]]
+git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
+uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
+version = "0.2.2"
+
+[[deps.Isoband]]
+deps = ["isoband_jll"]
+git-tree-sha1 = "f9b6d97355599074dc867318950adaa6f9946137"
+uuid = "f1662d9f-8043-43de-a69a-05efc1cc6ff4"
+version = "0.1.1"
+
+[[deps.IterTools]]
+git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023"
+uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
+version = "1.10.0"
+
+[[deps.IteratorInterfaceExtensions]]
+git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
+uuid = "82899510-4779-5014-852e-03e436cf321d"
+version = "1.0.0"
+
+[[deps.JLLWrappers]]
+deps = ["Artifacts", "Preferences"]
+git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
+uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
+version = "1.6.1"
+
+[[deps.JSON]]
+deps = ["Dates", "Mmap", "Parsers", "Unicode"]
+git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
+uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
+version = "0.21.4"
+
+[[deps.JSON3]]
+deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
+git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
+uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
+version = "1.14.1"
+
+    [deps.JSON3.extensions]
+    JSON3ArrowExt = ["ArrowTypes"]
+
+    [deps.JSON3.weakdeps]
+    ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
+
+[[deps.JpegTurbo]]
+deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
+git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611"
+uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0"
+version = "0.1.5"
+
+[[deps.JpegTurbo_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "25ee0be4d43d0269027024d75a24c24d6c6e590c"
+uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
+version = "3.0.4+0"
+
+[[deps.KernelDensity]]
+deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
+git-tree-sha1 = "7d703202e65efa1369de1279c162b915e245eed1"
+uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
+version = "0.6.9"
+
+[[deps.LAME_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "170b660facf5df5de098d866564877e119141cbd"
+uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
+version = "3.100.2+0"
+
+[[deps.LERC_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "36bdbc52f13a7d1dcb0f3cd694e01677a515655b"
+uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
+version = "4.0.0+0"
+
+[[deps.LLVMOpenMP_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "78211fb6cbc872f77cad3fc0b6cf647d923f4929"
+uuid = "1d63c593-3942-5779-bab2-d838dc0a180e"
+version = "18.1.7+0"
+
+[[deps.LZO_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "854a9c268c43b77b0a27f22d7fab8d33cdb3a731"
+uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
+version = "2.10.2+1"
+
+[[deps.LaTeXStrings]]
+git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
+uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
+version = "1.4.0"
+
+[[deps.LazyArtifacts]]
+deps = ["Artifacts", "Pkg"]
+uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
+version = "1.11.0"
+
+[[deps.LazyModules]]
+git-tree-sha1 = "a560dd966b386ac9ae60bdd3a3d3a326062d3c3e"
+uuid = "8cdb02fc-e678-4876-92c5-9defec4f444e"
+version = "0.3.1"
+
+[[deps.LibCURL]]
+deps = ["LibCURL_jll", "MozillaCACerts_jll"]
+uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
+version = "0.6.4"
+
+[[deps.LibCURL_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
+uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
+version = "8.6.0+0"
+
+[[deps.LibGit2]]
+deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
+uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
+version = "1.11.0"
+
+[[deps.LibGit2_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"]
+uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5"
+version = "1.7.2+0"
+
+[[deps.LibSSH2_jll]]
+deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
+uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
+version = "1.11.0+1"
+
+[[deps.Libdl]]
+uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
+version = "1.11.0"
+
+[[deps.Libffi_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290"
+uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
+version = "3.2.2+1"
+
+[[deps.Libgcrypt_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"]
+git-tree-sha1 = "8be878062e0ffa2c3f67bb58a595375eda5de80b"
+uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
+version = "1.11.0+0"
+
+[[deps.Libglvnd_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
+git-tree-sha1 = "6f73d1dd803986947b2c750138528a999a6c7733"
+uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
+version = "1.6.0+0"
+
+[[deps.Libgpg_error_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "c6ce1e19f3aec9b59186bdf06cdf3c4fc5f5f3e6"
+uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
+version = "1.50.0+0"
+
+[[deps.Libiconv_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "61dfdba58e585066d8bce214c5a51eaa0539f269"
+uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
+version = "1.17.0+1"
+
+[[deps.Libmount_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e"
+uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
+version = "2.40.1+0"
+
+[[deps.Libtiff_jll]]
+deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"]
+git-tree-sha1 = "b404131d06f7886402758c9ce2214b636eb4d54a"
+uuid = "89763e89-9b03-5906-acba-b20f662cd828"
+version = "4.7.0+0"
+
+[[deps.Libuuid_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807"
+uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
+version = "2.40.1+0"
+
+[[deps.LinearAlgebra]]
+deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
+uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+version = "1.11.0"
+
+[[deps.LogExpFunctions]]
+deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
+git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
+uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
+version = "0.3.28"
+
+    [deps.LogExpFunctions.extensions]
+    LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
+    LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables"
+    LogExpFunctionsInverseFunctionsExt = "InverseFunctions"
+
+    [deps.LogExpFunctions.weakdeps]
+    ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+    ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
+    InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
+
+[[deps.Logging]]
+uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
+version = "1.11.0"
+
+[[deps.LoggingExtras]]
+deps = ["Dates", "Logging"]
+git-tree-sha1 = "f02b56007b064fbfddb4c9cd60161b6dd0f40df3"
+uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
+version = "1.1.0"
+
+[[deps.MAT]]
+deps = ["BufferedStreams", "CodecZlib", "HDF5", "SparseArrays"]
+git-tree-sha1 = "1d2dd9b186742b0f317f2530ddcbf00eebb18e96"
+uuid = "23992714-dd62-5051-b70f-ba57cb901cac"
+version = "0.10.7"
+
+[[deps.MKL_jll]]
+deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
+git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f"
+uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
+version = "2024.2.0+0"
+
+[[deps.MPICH_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
+git-tree-sha1 = "7715e65c47ba3941c502bffb7f266a41a7f54423"
+uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4"
+version = "4.2.3+0"
+
+[[deps.MPIPreferences]]
+deps = ["Libdl", "Preferences"]
+git-tree-sha1 = "c105fe467859e7f6e9a852cb15cb4301126fac07"
+uuid = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267"
+version = "0.1.11"
+
+[[deps.MPItrampoline_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
+git-tree-sha1 = "70e830dab5d0775183c99fc75e4c24c614ed7142"
+uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748"
+version = "5.5.1+0"
+
+[[deps.MacroTools]]
+deps = ["Markdown", "Random"]
+git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df"
+uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
+version = "0.5.13"
+
+[[deps.Makie]]
+deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"]
+git-tree-sha1 = "f7907907eb914138cc9e9ee66ab46f7a9efac8e8"
+uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
+version = "0.21.15"
+
+[[deps.MakieCore]]
+deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"]
+git-tree-sha1 = "4604f03e5b057e8e62a95a44929cafc9585b0fe9"
+uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b"
+version = "0.8.9"
+
+[[deps.MappedArrays]]
+git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e"
+uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
+version = "0.4.2"
+
+[[deps.Markdown]]
+deps = ["Base64"]
+uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
+version = "1.11.0"
+
+[[deps.MathTeXEngine]]
+deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"]
+git-tree-sha1 = "f45c8916e8385976e1ccd055c9874560c257ab13"
+uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
+version = "0.6.2"
+
+[[deps.MbedTLS]]
+deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"]
+git-tree-sha1 = "c067a280ddc25f196b5e7df3877c6b226d390aaf"
+uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
+version = "1.1.9"
+
+[[deps.MbedTLS_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
+version = "2.28.6+0"
+
+[[deps.Meshes]]
+deps = ["Bessels", "CircularArrays", "Distances", "IterTools", "LinearAlgebra", "NearestNeighbors", "Random", "Rotations", "SparseArrays", "StaticArrays", "StatsBase", "Tables", "TransformsBase"]
+git-tree-sha1 = "a1a152787767f3393362276452897605584964b1"
+uuid = "eacbb407-ea5a-433e-ab97-5258b1ca43fa"
+version = "0.29.0"
+
+[[deps.MicrosoftMPI_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "f12a29c4400ba812841c6ace3f4efbb6dbb3ba01"
+uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf"
+version = "10.1.4+2"
+
+[[deps.Missings]]
+deps = ["DataAPI"]
+git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d"
+uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
+version = "1.2.0"
+
+[[deps.Mmap]]
+uuid = "a63ad114-7e13-5084-954f-fe012c677804"
+version = "1.11.0"
+
+[[deps.MosaicViews]]
+deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"]
+git-tree-sha1 = "7b86a5d4d70a9f5cdf2dacb3cbe6d251d1a61dbe"
+uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
+version = "0.3.4"
+
+[[deps.MozillaCACerts_jll]]
+uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
+version = "2023.12.12"
+
+[[deps.MsgPack]]
+deps = ["Serialization"]
+git-tree-sha1 = "f5db02ae992c260e4826fe78c942954b48e1d9c2"
+uuid = "99f44e22-a591-53d1-9472-aa23ef4bd671"
+version = "1.2.1"
+
+[[deps.NearestNeighbors]]
+deps = ["Distances", "StaticArrays"]
+git-tree-sha1 = "3cebfc94a0754cc329ebc3bab1e6c89621e791ad"
+uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
+version = "0.4.20"
+
+[[deps.Netpbm]]
+deps = ["FileIO", "ImageCore", "ImageMetadata"]
+git-tree-sha1 = "d92b107dbb887293622df7697a2223f9f8176fcd"
+uuid = "f09324ee-3d7c-5217-9330-fc30815ba969"
+version = "1.1.1"
+
+[[deps.NetworkOptions]]
+uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
+version = "1.2.0"
+
+[[deps.NyxWidgets]]
+deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
+git-tree-sha1 = "936f80aa61413c47da00f96abbc0186078698bca"
+repo-rev = "main"
+repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
+uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
+version = "0.1.1"
+
+[[deps.Observables]]
+git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
+uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
+version = "0.5.5"
+
+[[deps.ObservationPolicies]]
+deps = ["Observables"]
+git-tree-sha1 = "d54b4c26b41238806c172f85e1913736c2583b0a"
+repo-rev = "main"
+repo-url = "https://gitlab.com/dbc-nyx/ObservationPolicies.jl"
+uuid = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
+version = "0.2.4"
+
+[[deps.OffsetArrays]]
+git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e"
+uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
+version = "1.14.1"
+weakdeps = ["Adapt"]
+
+    [deps.OffsetArrays.extensions]
+    OffsetArraysAdaptExt = "Adapt"
+
+[[deps.Ogg_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f"
+uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
+version = "1.3.5+1"
+
+[[deps.OpenBLAS_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
+uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
+version = "0.3.27+1"
+
+[[deps.OpenEXR]]
+deps = ["Colors", "FileIO", "OpenEXR_jll"]
+git-tree-sha1 = "327f53360fdb54df7ecd01e96ef1983536d1e633"
+uuid = "52e1d378-f018-4a11-a4be-720524705ac7"
+version = "0.3.2"
+
+[[deps.OpenEXR_jll]]
+deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "8292dd5c8a38257111ada2174000a33745b06d4e"
+uuid = "18a262bb-aa17-5467-a713-aee519bc75cb"
+version = "3.2.4+0"
+
+[[deps.OpenLibm_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
+version = "0.8.1+2"
+
+[[deps.OpenMPI_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML", "Zlib_jll"]
+git-tree-sha1 = "bfce6d523861a6c562721b262c0d1aaeead2647f"
+uuid = "fe0851c0-eecd-5654-98d4-656369965a5c"
+version = "5.0.5+0"
+
+[[deps.OpenSSL]]
+deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
+git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4"
+uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
+version = "1.4.3"
+
+[[deps.OpenSSL_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
+uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
+version = "3.0.15+1"
+
+[[deps.OpenSpecFun_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
+uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
+version = "0.5.5+0"
+
+[[deps.Opus_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6703a85cb3781bd5909d48730a67205f3f31a575"
+uuid = "91d4177d-7536-5919-b921-800302f37372"
+version = "1.3.3+0"
+
+[[deps.OrderedCollections]]
+git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5"
+uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
+version = "1.6.3"
+
+[[deps.PCRE2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15"
+version = "10.42.0+1"
+
+[[deps.PDMats]]
+deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
+git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65"
+uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
+version = "0.11.31"
+
+[[deps.PNGFiles]]
+deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
+git-tree-sha1 = "67186a2bc9a90f9f85ff3cc8277868961fb57cbd"
+uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
+version = "0.4.3"
+
+[[deps.Packing]]
+deps = ["GeometryBasics"]
+git-tree-sha1 = "ec3edfe723df33528e085e632414499f26650501"
+uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566"
+version = "0.5.0"
+
+[[deps.PaddedViews]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "0fac6313486baae819364c52b4f483450a9d793f"
+uuid = "5432bcbf-9aad-5242-b902-cca2824c8663"
+version = "0.5.12"
+
+[[deps.Parsers]]
+deps = ["Dates", "PrecompileTools", "UUIDs"]
+git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821"
+uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
+version = "2.8.1"
+
+[[deps.Pixman_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"]
+git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b"
+uuid = "30392449-352a-5448-841d-b1acce4e97dc"
+version = "0.43.4+0"
+
+[[deps.Pkg]]
+deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
+uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
+version = "1.11.0"
+weakdeps = ["REPL"]
+
+    [deps.Pkg.extensions]
+    REPLExt = "REPL"
+
+[[deps.PkgVersion]]
+deps = ["Pkg"]
+git-tree-sha1 = "f9501cc0430a26bc3d156ae1b5b0c1b47af4d6da"
+uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
+version = "0.3.3"
+
+[[deps.PlanarLarvae]]
+deps = ["DelimitedFiles", "HDF5", "JSON3", "LinearAlgebra", "MAT", "Meshes", "OrderedCollections", "Random", "SHA", "StaticArrays", "Statistics", "StatsBase", "StructTypes"]
+git-tree-sha1 = "d964d040e319fe3bd9140e5bf91d648de6acc96f"
+repo-rev = "main"
+repo-url = "https://gitlab.pasteur.fr/nyx/planarlarvae.jl"
+uuid = "c2615984-ef14-4d40-b148-916c85b43307"
+version = "0.16.0"
+
+[[deps.PlotUtils]]
+deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"]
+git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18"
+uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
+version = "1.4.3"
+
+[[deps.PolygonOps]]
+git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6"
+uuid = "647866c9-e3ac-4575-94e7-e3d426903924"
+version = "0.1.2"
+
+[[deps.PrecompileTools]]
+deps = ["Preferences"]
+git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f"
+uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
+version = "1.2.1"
+
+[[deps.Preferences]]
+deps = ["TOML"]
+git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6"
+uuid = "21216c6a-2e73-6563-6e65-726566657250"
+version = "1.4.3"
+
+[[deps.Printf]]
+deps = ["Unicode"]
+uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
+version = "1.11.0"
+
+[[deps.ProgressMeter]]
+deps = ["Distributed", "Printf"]
+git-tree-sha1 = "8f6bc219586aef8baf0ff9a5fe16ee9c70cb65e4"
+uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
+version = "1.10.2"
+
+[[deps.PtrArrays]]
+git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
+uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
+version = "1.2.1"
+
+[[deps.QOI]]
+deps = ["ColorTypes", "FileIO", "FixedPointNumbers"]
+git-tree-sha1 = "18e8f4d1426e965c7b532ddd260599e1510d26ce"
+uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65"
+version = "1.0.0"
+
+[[deps.QuadGK]]
+deps = ["DataStructures", "LinearAlgebra"]
+git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
+uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
+version = "2.11.1"
+
+    [deps.QuadGK.extensions]
+    QuadGKEnzymeExt = "Enzyme"
+
+    [deps.QuadGK.weakdeps]
+    Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
+
+[[deps.Quaternions]]
+deps = ["LinearAlgebra", "Random", "RealDot"]
+git-tree-sha1 = "994cc27cdacca10e68feb291673ec3a76aa2fae9"
+uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
+version = "0.7.6"
+
+[[deps.REPL]]
+deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"]
+uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
+version = "1.11.0"
+
+[[deps.Random]]
+deps = ["SHA"]
+uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+version = "1.11.0"
+
+[[deps.RangeArrays]]
+git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5"
+uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d"
+version = "0.3.2"
+
+[[deps.Ratios]]
+deps = ["Requires"]
+git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b"
+uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
+version = "0.4.5"
+weakdeps = ["FixedPointNumbers"]
+
+    [deps.Ratios.extensions]
+    RatiosFixedPointNumbersExt = "FixedPointNumbers"
+
+[[deps.RealDot]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "9f0a1b71baaf7650f4fa8a1d168c7fb6ee41f0c9"
+uuid = "c1ae055f-0cd5-4b69-90a6-9a35b1a98df9"
+version = "0.1.0"
+
+[[deps.RecipesBase]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff"
+uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+version = "1.3.4"
+
+[[deps.Reexport]]
+git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
+uuid = "189a3867-3050-52da-a836-e630ba90ab69"
+version = "1.2.2"
+
+[[deps.RelocatableFolders]]
+deps = ["SHA", "Scratch"]
+git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864"
+uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
+version = "1.0.1"
+
+[[deps.Requires]]
+deps = ["UUIDs"]
+git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
+uuid = "ae029012-a4dd-5104-9daa-d747884805df"
+version = "1.3.0"
+
+[[deps.Rmath]]
+deps = ["Random", "Rmath_jll"]
+git-tree-sha1 = "852bd0f55565a9e973fcfee83a84413270224dc4"
+uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
+version = "0.8.0"
+
+[[deps.Rmath_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8"
+uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
+version = "0.5.1+0"
+
+[[deps.Rotations]]
+deps = ["LinearAlgebra", "Quaternions", "Random", "StaticArrays"]
+git-tree-sha1 = "5680a9276685d392c87407df00d57c9924d9f11e"
+uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
+version = "1.7.1"
+weakdeps = ["RecipesBase"]
+
+    [deps.Rotations.extensions]
+    RotationsRecipesBaseExt = "RecipesBase"
+
+[[deps.RoundingEmulator]]
+git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b"
+uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705"
+version = "0.2.1"
+
+[[deps.SHA]]
+uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+version = "0.7.0"
+
+[[deps.SIMD]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "98ca7c29edd6fc79cd74c61accb7010a4e7aee33"
+uuid = "fdea26ae-647d-5447-a871-4b548cad5224"
+version = "3.6.0"
+
+[[deps.Scratch]]
+deps = ["Dates"]
+git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386"
+uuid = "6c6a2e73-6563-6170-7368-637461726353"
+version = "1.2.1"
+
+[[deps.Serialization]]
+uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
+version = "1.11.0"
+
+[[deps.ShaderAbstractions]]
+deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays", "StructArrays", "Tables"]
+git-tree-sha1 = "79123bc60c5507f035e6d1d9e563bb2971954ec8"
+uuid = "65257c39-d410-5151-9873-9b3e5be5013e"
+version = "0.4.1"
+
+[[deps.SharedArrays]]
+deps = ["Distributed", "Mmap", "Random", "Serialization"]
+uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
+version = "1.11.0"
+
+[[deps.Showoff]]
+deps = ["Dates", "Grisu"]
+git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
+uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
+version = "1.0.3"
+
+[[deps.SignedDistanceFields]]
+deps = ["Random", "Statistics", "Test"]
+git-tree-sha1 = "d263a08ec505853a5ff1c1ebde2070419e3f28e9"
+uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96"
+version = "0.4.0"
+
+[[deps.SimpleBufferStream]]
+git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1"
+uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7"
+version = "1.2.0"
+
+[[deps.SimpleTraits]]
+deps = ["InteractiveUtils", "MacroTools"]
+git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
+uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
+version = "0.9.4"
+
+[[deps.Sixel]]
+deps = ["Dates", "FileIO", "ImageCore", "IndirectArrays", "OffsetArrays", "REPL", "libsixel_jll"]
+git-tree-sha1 = "2da10356e31327c7096832eb9cd86307a50b1eb6"
+uuid = "45858cf5-a6b0-47a3-bbea-62219f50df47"
+version = "0.1.3"
+
+[[deps.Sockets]]
+uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
+version = "1.11.0"
+
+[[deps.SortingAlgorithms]]
+deps = ["DataStructures"]
+git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085"
+uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
+version = "1.2.1"
+
+[[deps.SparseArrays]]
+deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"]
+uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+version = "1.11.0"
+
+[[deps.SpecialFunctions]]
+deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
+git-tree-sha1 = "2f5d4697f21388cbe1ff299430dd169ef97d7e14"
+uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
+version = "2.4.0"
+weakdeps = ["ChainRulesCore"]
+
+    [deps.SpecialFunctions.extensions]
+    SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
+
+[[deps.StableRNGs]]
+deps = ["Random"]
+git-tree-sha1 = "83e6cce8324d49dfaf9ef059227f91ed4441a8e5"
+uuid = "860ef19b-820b-49d6-a774-d7a799459cd3"
+version = "1.0.2"
+
+[[deps.StackViews]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
+uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15"
+version = "0.1.1"
+
+[[deps.StaticArrays]]
+deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
+git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f"
+uuid = "90137ffa-7385-5640-81b9-e52037218182"
+version = "1.9.8"
+weakdeps = ["ChainRulesCore", "Statistics"]
+
+    [deps.StaticArrays.extensions]
+    StaticArraysChainRulesCoreExt = "ChainRulesCore"
+    StaticArraysStatisticsExt = "Statistics"
+
+[[deps.StaticArraysCore]]
+git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682"
+uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
+version = "1.4.3"
+
+[[deps.Statistics]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0"
+uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+version = "1.11.1"
+weakdeps = ["SparseArrays"]
+
+    [deps.Statistics.extensions]
+    SparseArraysExt = ["SparseArrays"]
+
+[[deps.StatsAPI]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed"
+uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
+version = "1.7.0"
+
+[[deps.StatsBase]]
+deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
+git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21"
+uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
+version = "0.34.3"
+
+[[deps.StatsFuns]]
+deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
+git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46"
+uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
+version = "1.3.2"
+weakdeps = ["ChainRulesCore", "InverseFunctions"]
+
+    [deps.StatsFuns.extensions]
+    StatsFunsChainRulesCoreExt = "ChainRulesCore"
+    StatsFunsInverseFunctionsExt = "InverseFunctions"
+
+[[deps.StructArrays]]
+deps = ["ConstructionBase", "DataAPI", "Tables"]
+git-tree-sha1 = "f4dc295e983502292c4c3f951dbb4e985e35b3be"
+uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
+version = "0.6.18"
+
+    [deps.StructArrays.extensions]
+    StructArraysAdaptExt = "Adapt"
+    StructArraysGPUArraysCoreExt = "GPUArraysCore"
+    StructArraysSparseArraysExt = "SparseArrays"
+    StructArraysStaticArraysExt = "StaticArrays"
+
+    [deps.StructArrays.weakdeps]
+    Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
+    GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527"
+    SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+    StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
+
+[[deps.StructTypes]]
+deps = ["Dates", "UUIDs"]
+git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8"
+uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
+version = "1.11.0"
+
+[[deps.StyledStrings]]
+uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
+version = "1.11.0"
+
+[[deps.SuiteSparse]]
+deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
+uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"
+
+[[deps.SuiteSparse_jll]]
+deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
+uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
+version = "7.7.0+0"
+
+[[deps.TOML]]
+deps = ["Dates"]
+uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+version = "1.0.3"
+
+[[deps.TableTraits]]
+deps = ["IteratorInterfaceExtensions"]
+git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
+uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
+version = "1.0.1"
+
+[[deps.Tables]]
+deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
+git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297"
+uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
+version = "1.12.0"
+
+[[deps.Tar]]
+deps = ["ArgTools", "SHA"]
+uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
+version = "1.10.0"
+
+[[deps.TensorCore]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6"
+uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
+version = "0.1.1"
+
+[[deps.Test]]
+deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
+uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+version = "1.11.0"
+
+[[deps.ThreadPools]]
+deps = ["Printf", "RecipesBase", "Statistics"]
+git-tree-sha1 = "50cb5f85d5646bc1422aa0238aa5bfca99ca9ae7"
+uuid = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431"
+version = "2.1.1"
+
+[[deps.TidyObservables]]
+deps = ["Observables"]
+git-tree-sha1 = "0589ec7397374678942cae9aa356b4bb6c1e9bf4"
+repo-rev = "main"
+repo-url = "https://gitlab.com/dbc-nyx/tidyobservables.jl"
+uuid = "c8131bbd-73a8-4254-a42d-d5d4c5febb31"
+version = "0.1.1"
+
+[[deps.TiffImages]]
+deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "ProgressMeter", "SIMD", "UUIDs"]
+git-tree-sha1 = "6ee0c220d0aecad18792c277ae358129cc50a475"
+uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
+version = "0.11.0"
+
+[[deps.TranscodingStreams]]
+git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
+uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
+version = "0.11.3"
+
+[[deps.TransformsBase]]
+deps = ["AbstractTrees"]
+git-tree-sha1 = "2412fb54902b0063c69c2bcfbec6b571120cc856"
+uuid = "28dd2a49-a57a-4bfb-84ca-1a49db9b96b8"
+version = "0.1.2"
+
+[[deps.TriplotBase]]
+git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b"
+uuid = "981d1d27-644d-49a2-9326-4793e63143c3"
+version = "0.1.0"
+
+[[deps.URIs]]
+git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
+uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
+version = "1.5.1"
+
+[[deps.UUIDs]]
+deps = ["Random", "SHA"]
+uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
+version = "1.11.0"
+
+[[deps.Unicode]]
+uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
+version = "1.11.0"
+
+[[deps.UnicodeFun]]
+deps = ["REPL"]
+git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
+uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
+version = "0.4.1"
+
+[[deps.Unitful]]
+deps = ["Dates", "LinearAlgebra", "Random"]
+git-tree-sha1 = "d95fe458f26209c66a187b1114df96fd70839efd"
+uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
+version = "1.21.0"
+weakdeps = ["ConstructionBase", "InverseFunctions"]
+
+    [deps.Unitful.extensions]
+    ConstructionBaseUnitfulExt = "ConstructionBase"
+    InverseFunctionsUnitfulExt = "InverseFunctions"
+
+[[deps.WGLMakie]]
+deps = ["Bonito", "Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "LinearAlgebra", "Makie", "Observables", "PNGFiles", "PrecompileTools", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
+git-tree-sha1 = "13cab94d885d7760d487d7e2f30c2d6df4643880"
+uuid = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
+version = "0.10.15"
+
+[[deps.WebP]]
+deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"]
+git-tree-sha1 = "f1f6d497ff84039deeb37f264396dac0c2250497"
+uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1"
+version = "0.1.2"
+
+[[deps.WidgetsBase]]
+deps = ["Observables"]
+git-tree-sha1 = "30a1d631eb06e8c868c559599f915a62d55c2601"
+uuid = "eead4739-05f7-45a1-878c-cee36b57321c"
+version = "0.1.4"
+
+[[deps.WoodburyMatrices]]
+deps = ["LinearAlgebra", "SparseArrays"]
+git-tree-sha1 = "c1a7aa6219628fcd757dede0ca95e245c5cd9511"
+uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
+version = "1.0.0"
+
+[[deps.XML2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
+git-tree-sha1 = "6a451c6f33a176150f315726eba8b92fbfdb9ae7"
+uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
+version = "2.13.4+0"
+
+[[deps.XSLT_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"]
+git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc"
+uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
+version = "1.1.41+0"
+
+[[deps.XZ_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "15e637a697345f6743674f1322beefbc5dcd5cfc"
+uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800"
+version = "5.6.3+0"
+
+[[deps.Xorg_libX11_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
+git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495"
+uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
+version = "1.8.6+0"
+
+[[deps.Xorg_libXau_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8"
+uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
+version = "1.0.11+0"
+
+[[deps.Xorg_libXdmcp_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7"
+uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
+version = "1.1.4+0"
+
+[[deps.Xorg_libXext_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
+git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85"
+uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
+version = "1.3.6+0"
+
+[[deps.Xorg_libXrender_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"]
+git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe"
+uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
+version = "0.9.11+0"
+
+[[deps.Xorg_libpthread_stubs_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9"
+uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
+version = "0.1.1+0"
+
+[[deps.Xorg_libxcb_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
+git-tree-sha1 = "bcd466676fef0878338c61e655629fa7bbc69d8e"
+uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
+version = "1.17.0+0"
+
+[[deps.Xorg_xtrans_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77"
+uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
+version = "1.5.0+0"
+
+[[deps.Zlib_jll]]
+deps = ["Libdl"]
+uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
+version = "1.2.13+1"
+
+[[deps.Zstd_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
+uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
+version = "1.5.6+1"
+
+[[deps.isoband_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "51b5eeb3f98367157a7a12a1fb0aa5328946c03c"
+uuid = "9a68df92-36a6-505f-a73e-abb412b6bfb4"
+version = "0.2.3+0"
+
+[[deps.libaec_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "46bf7be2917b59b761247be3f317ddf75e50e997"
+uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0"
+version = "1.1.2+0"
+
+[[deps.libaom_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1827acba325fdcdf1d2647fc8d5301dd9ba43a9d"
+uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b"
+version = "3.9.0+0"
+
+[[deps.libass_jll]]
+deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "e17c115d55c5fbb7e52ebedb427a0dca79d4484e"
+uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
+version = "0.15.2+0"
+
+[[deps.libblastrampoline_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
+version = "5.11.0+0"
+
+[[deps.libfdk_aac_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "8a22cf860a7d27e4f3498a0fe0811a7957badb38"
+uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
+version = "2.0.3+0"
+
+[[deps.libpng_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "b70c870239dc3d7bc094eb2d6be9b73d27bef280"
+uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
+version = "1.6.44+0"
+
+[[deps.libsixel_jll]]
+deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "libpng_jll"]
+git-tree-sha1 = "7dfa0fd9c783d3d0cc43ea1af53d69ba45c447df"
+uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
+version = "1.10.3+1"
+
+[[deps.libvorbis_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
+git-tree-sha1 = "490376214c4721cdaca654041f635213c6165cb3"
+uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
+version = "1.3.7+2"
+
+[[deps.libwebp_jll]]
+deps = ["Artifacts", "Giflib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libglvnd_jll", "Libtiff_jll", "libpng_jll"]
+git-tree-sha1 = "ccbb625a89ec6195856a50aa2b668a5c08712c94"
+uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2"
+version = "1.4.0+0"
+
+[[deps.nghttp2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
+version = "1.59.0+0"
+
+[[deps.oneTBB_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "7d0ea0f4895ef2f5cb83645fa689e52cb55cf493"
+uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e"
+version = "2021.12.0+0"
+
+[[deps.p7zip_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
+version = "17.4.0+2"
+
+[[deps.x264_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "35976a1216d6c066ea32cba2150c4fa682b276fc"
+uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
+version = "10164.0.0+0"
+
+[[deps.x265_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "dcc541bb19ed5b0ede95581fb2e41ecf179527d2"
+uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
+version = "3.6.0+0"
diff --git a/Manifest.toml b/Manifest.toml
index 46f7deabd22ffeb010ad88b55b91e6219e087baa..d02ed0ce6003ec651f18217bf255430a7306468b 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -1,8 +1,8 @@
 # This file is machine-generated - editing it directly is not advised
 
-julia_version = "1.10.3"
+julia_version = "1.10.6"
 manifest_format = "2.0"
-project_hash = "cb952709b0f0743fc5b93ca3d08c93ff9353644c"
+project_hash = "0fb14e688a33d3d8ba0bbce1542d1ada113967c7"
 
 [[deps.AbstractFFTs]]
 deps = ["LinearAlgebra"]
@@ -22,14 +22,19 @@ version = "0.4.5"
 
 [[deps.Adapt]]
 deps = ["LinearAlgebra", "Requires"]
-git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099"
+git-tree-sha1 = "50c3c56a52972d78e8be9fd135bfb91c9574c140"
 uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
-version = "4.0.4"
+version = "4.1.1"
 weakdeps = ["StaticArrays"]
 
     [deps.Adapt.extensions]
     AdaptStaticArraysExt = "StaticArrays"
 
+[[deps.AdaptivePredicates]]
+git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6"
+uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7"
+version = "1.2.0"
+
 [[deps.AliasTables]]
 deps = ["PtrArrays", "Random"]
 git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff"
@@ -50,10 +55,10 @@ version = "1.1.1"
 uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
 
 [[deps.Automa]]
-deps = ["TranscodingStreams"]
-git-tree-sha1 = "ef9997b3d5547c48b41c7bd8899e812a917b409d"
+deps = ["PrecompileTools", "SIMD", "TranscodingStreams"]
+git-tree-sha1 = "a8f503e8e1a5f583fbef15a8440c8c7e32185df2"
 uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b"
-version = "0.8.4"
+version = "1.1.0"
 
 [[deps.AxisAlgorithms]]
 deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
@@ -75,39 +80,53 @@ git-tree-sha1 = "4435559dc39793d53a9e3d278e185e920b4619ef"
 uuid = "0e736298-9ec6-45e8-9647-e4fc86a2fe38"
 version = "0.2.8"
 
+[[deps.BitFlags]]
+git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d"
+uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35"
+version = "0.1.9"
+
+[[deps.Bonito]]
+deps = ["Base64", "CodecZlib", "Colors", "Dates", "Deno_jll", "HTTP", "Hyperscript", "LinearAlgebra", "Markdown", "MsgPack", "Observables", "RelocatableFolders", "SHA", "Sockets", "Tables", "ThreadPools", "URIs", "UUIDs", "WidgetsBase"]
+git-tree-sha1 = "d7635780a8cfe0cb43c075276fd358c5b166695e"
+uuid = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
+version = "3.2.4"
+
 [[deps.BufferedStreams]]
-git-tree-sha1 = "4ae47f9a4b1dc19897d3743ff13685925c5202ec"
+git-tree-sha1 = "6863c5b7fc997eadcabdbaf6c5f201dc30032643"
 uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
-version = "1.2.1"
+version = "1.2.2"
 
 [[deps.Bzip2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd"
+git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1"
 uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
-version = "1.0.8+1"
+version = "1.0.8+2"
 
 [[deps.CEnum]]
 git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
 uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
 version = "0.5.0"
 
+[[deps.CRC32c]]
+uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc"
+
+[[deps.CRlibm_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc"
+uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0"
+version = "1.0.1+0"
+
 [[deps.Cairo_jll]]
 deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
-git-tree-sha1 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd"
+git-tree-sha1 = "009060c9a6168704143100f36ab08f06c2af4642"
 uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
-version = "1.18.0+2"
-
-[[deps.Calculus]]
-deps = ["LinearAlgebra"]
-git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
-uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
-version = "0.5.1"
+version = "1.18.2+1"
 
 [[deps.ChainRulesCore]]
 deps = ["Compat", "LinearAlgebra"]
-git-tree-sha1 = "575cd02e080939a33b6df6c5853d14924c08e35b"
+git-tree-sha1 = "3e4b134270b372f2ed4d4d0e936aabaefc1802bc"
 uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
-version = "1.23.0"
+version = "1.25.0"
 weakdeps = ["SparseArrays"]
 
     [deps.ChainRulesCore.extensions]
@@ -121,9 +140,9 @@ version = "1.4.0"
 
 [[deps.CodecZlib]]
 deps = ["TranscodingStreams", "Zlib_jll"]
-git-tree-sha1 = "59939d8a997469ee05c4b4944560a820f9ba0d73"
+git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
 uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
-version = "0.7.4"
+version = "0.7.6"
 
 [[deps.ColorBrewer]]
 deps = ["Colors", "JSON", "Test"]
@@ -133,9 +152,9 @@ version = "0.4.0"
 
 [[deps.ColorSchemes]]
 deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"]
-git-tree-sha1 = "4b270d6465eb21ae89b732182c20dc165f8bf9f2"
+git-tree-sha1 = "c785dfb1b3bfddd1da557e861b919819b82bbe5b"
 uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
-version = "3.25.0"
+version = "3.27.1"
 
 [[deps.ColorTypes]]
 deps = ["FixedPointNumbers", "Random"]
@@ -144,10 +163,14 @@ uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
 version = "0.11.5"
 
 [[deps.ColorVectorSpace]]
-deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
-git-tree-sha1 = "600cc5508d66b78aae350f7accdb58763ac18589"
+deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"]
+git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249"
 uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
-version = "0.9.10"
+version = "0.10.0"
+weakdeps = ["SpecialFunctions"]
+
+    [deps.ColorVectorSpace.extensions]
+    SpecialFunctionsExt = "SpecialFunctions"
 
 [[deps.Colors]]
 deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
@@ -157,9 +180,9 @@ version = "0.12.11"
 
 [[deps.Compat]]
 deps = ["TOML", "UUIDs"]
-git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248"
+git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215"
 uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
-version = "4.15.0"
+version = "4.16.0"
 weakdeps = ["Dates", "LinearAlgebra"]
 
     [deps.Compat.extensions]
@@ -170,15 +193,21 @@ deps = ["Artifacts", "Libdl"]
 uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
 version = "1.1.1+0"
 
+[[deps.ConcurrentUtilities]]
+deps = ["Serialization", "Sockets"]
+git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1"
+uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
+version = "2.4.2"
+
 [[deps.ConstructionBase]]
-deps = ["LinearAlgebra"]
-git-tree-sha1 = "260fd2400ed2dab602a7c15cf10c1933c59930a2"
+git-tree-sha1 = "76219f1ed5771adbb096743bff43fb5fdd4c1157"
 uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
-version = "1.5.5"
-weakdeps = ["IntervalSets", "StaticArrays"]
+version = "1.5.8"
+weakdeps = ["IntervalSets", "LinearAlgebra", "StaticArrays"]
 
     [deps.ConstructionBase.extensions]
     ConstructionBaseIntervalSetsExt = "IntervalSets"
+    ConstructionBaseLinearAlgebraExt = "LinearAlgebra"
     ConstructionBaseStaticArraysExt = "StaticArrays"
 
 [[deps.Contour]]
@@ -206,17 +235,29 @@ version = "1.0.0"
 deps = ["Printf"]
 uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
 
+[[deps.DelaunayTriangulation]]
+deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "PrecompileTools", "Random"]
+git-tree-sha1 = "89df54fbe66e5872d91d8c2cd3a375f660c3fd64"
+uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
+version = "1.6.1"
+
 [[deps.DelimitedFiles]]
 deps = ["Mmap"]
 git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae"
 uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
 version = "1.9.1"
 
+[[deps.Deno_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "cd6756e833c377e0ce9cd63fb97689a255f12323"
+uuid = "04572ae6-984a-583e-9378-9577a1c2574d"
+version = "1.33.4+0"
+
 [[deps.Distances]]
 deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
-git-tree-sha1 = "66c4c81f259586e8f002eacebc177e1fb06363b0"
+git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d"
 uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
-version = "0.10.11"
+version = "0.10.12"
 weakdeps = ["ChainRulesCore", "SparseArrays"]
 
     [deps.Distances.extensions]
@@ -229,9 +270,9 @@ uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
 
 [[deps.Distributions]]
 deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
-git-tree-sha1 = "22c595ca4146c07b16bcf9c8bea86f731f7109d2"
+git-tree-sha1 = "3101c32aab536e7a27b1763c0797dba151b899ad"
 uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
-version = "0.25.108"
+version = "0.25.113"
 
     [deps.Distributions.extensions]
     DistributionsChainRulesCoreExt = "ChainRulesCore"
@@ -260,18 +301,29 @@ deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
 uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
 version = "1.6.0"
 
-[[deps.DualNumbers]]
-deps = ["Calculus", "NaNMath", "SpecialFunctions"]
-git-tree-sha1 = "5837a837389fccf076445fce071c8ddaea35a566"
-uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
-version = "0.6.8"
-
 [[deps.EarCut_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
 git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053"
 uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
 version = "2.2.4+0"
 
+[[deps.EnumX]]
+git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237"
+uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
+version = "1.0.4"
+
+[[deps.ExactPredicates]]
+deps = ["IntervalArithmetic", "Random", "StaticArrays"]
+git-tree-sha1 = "b3f2ff58735b5f024c392fde763f29b057e4b025"
+uuid = "429591f6-91af-11e9-00e2-59fbe8cec110"
+version = "2.2.8"
+
+[[deps.ExceptionUnwrapping]]
+deps = ["Test"]
+git-tree-sha1 = "dcb08a0d93ec0b1cdc4af184b26b591e9695423a"
+uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
+version = "0.1.10"
+
 [[deps.Expat_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
 git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7"
@@ -279,21 +331,15 @@ uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
 version = "2.6.2+0"
 
 [[deps.Extents]]
-git-tree-sha1 = "2140cd04483da90b2da7f99b2add0750504fc39c"
+git-tree-sha1 = "81023caa0021a41712685887db1fc03db26f41f5"
 uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
-version = "0.1.2"
-
-[[deps.FFMPEG]]
-deps = ["FFMPEG_jll"]
-git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
-uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
-version = "0.4.1"
+version = "0.1.4"
 
 [[deps.FFMPEG_jll]]
 deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
-git-tree-sha1 = "466d45dc38e15794ec7d5d63ec03d776a9aff36e"
+git-tree-sha1 = "8cc47f299902e13f90405ddb5bf87e5d474c0d38"
 uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
-version = "4.4.4+1"
+version = "6.1.2+0"
 
 [[deps.FFTW]]
 deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
@@ -303,24 +349,41 @@ version = "1.8.0"
 
 [[deps.FFTW_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "c6033cc3892d0ef5bb9cd29b7f2f0331ea5184ea"
+git-tree-sha1 = "4d81ed14783ec49ce9f2e168208a12ce1815aa25"
 uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
-version = "3.3.10+0"
+version = "3.3.10+1"
 
 [[deps.FileIO]]
 deps = ["Pkg", "Requires", "UUIDs"]
-git-tree-sha1 = "82d8afa92ecf4b52d78d869f038ebfb881267322"
+git-tree-sha1 = "91e0e5c68d02bcdaae76d3c8ceb4361e8f28d2e9"
 uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
-version = "1.16.3"
+version = "1.16.5"
+
+[[deps.FilePaths]]
+deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"]
+git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629"
+uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824"
+version = "0.8.3"
+
+[[deps.FilePathsBase]]
+deps = ["Compat", "Dates"]
+git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
+uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
+version = "0.9.22"
+weakdeps = ["Mmap", "Test"]
+
+    [deps.FilePathsBase.extensions]
+    FilePathsBaseMmapExt = "Mmap"
+    FilePathsBaseTestExt = "Test"
 
 [[deps.FileWatching]]
 uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
 
 [[deps.FillArrays]]
 deps = ["LinearAlgebra"]
-git-tree-sha1 = "0653c0a2396a6da5bc4766c43041ef5fd3efbe57"
+git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a"
 uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
-version = "1.11.0"
+version = "1.13.0"
 weakdeps = ["PDMats", "SparseArrays", "Statistics"]
 
     [deps.FillArrays.extensions]
@@ -345,12 +408,6 @@ git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc"
 uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
 version = "1.3.7"
 
-[[deps.Formatting]]
-deps = ["Logging", "Printf"]
-git-tree-sha1 = "fb409abab2caf118986fc597ba84b50cbaf00b87"
-uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
-version = "0.4.3"
-
 [[deps.FreeType]]
 deps = ["CEnum", "FreeType2_jll"]
 git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999"
@@ -365,9 +422,9 @@ version = "2.13.2+0"
 
 [[deps.FreeTypeAbstraction]]
 deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"]
-git-tree-sha1 = "b5c7fe9cea653443736d264b85466bad8c574f4a"
+git-tree-sha1 = "77e2b094e61d939f9626181ab23d0b76e78f9fd3"
 uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
-version = "0.9.9"
+version = "0.10.5"
 
 [[deps.FriBidi_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -375,11 +432,16 @@ git-tree-sha1 = "1ed150b39aebcc805c26b93a8d0122c940f64ce2"
 uuid = "559328eb-81f9-559d-9380-de523a88c83c"
 version = "1.0.14+0"
 
+[[deps.GeoFormatTypes]]
+git-tree-sha1 = "59107c179a586f0fe667024c5eb7033e81333271"
+uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f"
+version = "0.4.2"
+
 [[deps.GeoInterface]]
-deps = ["Extents"]
-git-tree-sha1 = "801aef8228f7f04972e596b09d4dba481807c913"
+deps = ["Extents", "GeoFormatTypes"]
+git-tree-sha1 = "826b4fd69438d9ce4d2b19de6bc2f970f45f0f88"
 uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
-version = "1.3.4"
+version = "1.3.8"
 
 [[deps.GeometryBasics]]
 deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
@@ -393,23 +455,17 @@ git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
 uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
 version = "0.21.0+0"
 
-[[deps.Ghostscript_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "43ba3d3c82c18d88471cfd2924931658838c9d8f"
-uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b"
-version = "9.55.0+4"
+[[deps.Giflib_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "0224cce99284d997f6880a42ef715a37c99338d1"
+uuid = "59f7168a-df46-5410-90c8-f2779963d0ec"
+version = "5.2.2+0"
 
 [[deps.Glib_jll]]
 deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"]
-git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba"
+git-tree-sha1 = "674ff0db93fffcd11a3573986e550d66cd4fd71f"
 uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
-version = "2.80.2+0"
-
-[[deps.Graphics]]
-deps = ["Colors", "LinearAlgebra", "NaNMath"]
-git-tree-sha1 = "d61890399bc535850c4bf08e4e0d3a7ad0f21cbd"
-uuid = "a2bd30eb-e257-5431-a919-1863eab51364"
-version = "1.1.2"
+version = "2.80.5+0"
 
 [[deps.Graphite2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -419,9 +475,9 @@ version = "1.3.14+0"
 
 [[deps.GridLayoutBase]]
 deps = ["GeometryBasics", "InteractiveUtils", "Observables"]
-git-tree-sha1 = "f57a64794b336d4990d90f80b147474b869b1bc4"
+git-tree-sha1 = "fc713f007cff99ff9e50accba6373624ddd33588"
 uuid = "3955a311-db13-416c-9275-1d80ed98e5e9"
-version = "0.9.2"
+version = "0.11.0"
 
 [[deps.Grisu]]
 git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
@@ -447,76 +503,64 @@ uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
 version = "1.14.2+1"
 
 [[deps.HTTP]]
-deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
-git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a"
+deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
+git-tree-sha1 = "1336e07ba2eb75614c99496501a8f4b233e9fafe"
 uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
-version = "0.9.17"
+version = "1.10.10"
 
 [[deps.HarfBuzz_jll]]
-deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"]
-git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3"
+deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"]
+git-tree-sha1 = "401e4f3f30f43af2c8478fc008da50096ea5240f"
 uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
-version = "2.8.1+1"
+version = "8.3.1+0"
 
 [[deps.Hwloc_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "ca0f6bf568b4bfc807e7537f081c81e35ceca114"
+git-tree-sha1 = "50aedf345a709ab75872f80a2779568dc0bb461b"
 uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
-version = "2.10.0+0"
+version = "2.11.2+1"
 
 [[deps.HypergeometricFunctions]]
-deps = ["DualNumbers", "LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
-git-tree-sha1 = "f218fe3736ddf977e0e772bc9a586b2383da2685"
+deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
+git-tree-sha1 = "b1c2585431c382e3fe5805874bda6aea90a95de9"
 uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
-version = "0.3.23"
+version = "0.3.25"
 
 [[deps.Hyperscript]]
 deps = ["Test"]
-git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
+git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4"
 uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
-version = "0.0.4"
+version = "0.0.5"
 
 [[deps.ImageAxes]]
 deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"]
-git-tree-sha1 = "2e4520d67b0cef90865b3ef727594d2a58e0e1f8"
+git-tree-sha1 = "e12629406c6c4442539436581041d372d69c55ba"
 uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
-version = "0.6.11"
+version = "0.6.12"
 
 [[deps.ImageBase]]
 deps = ["ImageCore", "Reexport"]
-git-tree-sha1 = "b51bb8cae22c66d0f6357e3bcb6363145ef20835"
+git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909"
 uuid = "c817782e-172a-44cc-b673-b171935fbb9e"
-version = "0.1.5"
+version = "0.1.7"
 
 [[deps.ImageCore]]
-deps = ["AbstractFFTs", "ColorVectorSpace", "Colors", "FixedPointNumbers", "Graphics", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "Reexport"]
-git-tree-sha1 = "acf614720ef026d38400b3817614c45882d75500"
+deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"]
+git-tree-sha1 = "8c193230235bbcee22c8066b0374f63b5683c2d3"
 uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
-version = "0.9.4"
+version = "0.10.5"
 
 [[deps.ImageIO]]
-deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs"]
-git-tree-sha1 = "437abb322a41d527c197fa800455f79d414f0a3c"
+deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"]
+git-tree-sha1 = "696144904b76e1ca433b886b4e7edd067d76cbf7"
 uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
-version = "0.6.8"
-
-[[deps.ImageMagick]]
-deps = ["FileIO", "ImageCore", "ImageMagick_jll", "InteractiveUtils", "Libdl", "Pkg", "Random"]
-git-tree-sha1 = "5bc1cb62e0c5f1005868358db0692c994c3a13c6"
-uuid = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
-version = "1.2.1"
-
-[[deps.ImageMagick_jll]]
-deps = ["Artifacts", "Ghostscript_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Zlib_jll", "libpng_jll"]
-git-tree-sha1 = "d65554bad8b16d9562050c67e7223abf91eaba2f"
-uuid = "c73af94c-d91f-53ed-93a7-00f77d67a9d7"
-version = "6.9.13+0"
+version = "0.6.9"
 
 [[deps.ImageMetadata]]
 deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"]
-git-tree-sha1 = "355e2b974f2e3212a75dfb60519de21361ad3cb7"
+git-tree-sha1 = "2a81c3897be6fbcde0802a0ebe6796d0562f63ec"
 uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
-version = "0.9.9"
+version = "0.9.10"
 
 [[deps.Imath_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -530,20 +574,15 @@ uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
 version = "1.0.0"
 
 [[deps.Inflate]]
-git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381"
+git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d"
 uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
-version = "0.1.4"
-
-[[deps.IniFile]]
-git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625"
-uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
-version = "0.5.1"
+version = "0.1.5"
 
 [[deps.IntelOpenMP_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "be50fe8df3acbffa0274a744f1a99d29c45a57f4"
+deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
+git-tree-sha1 = "10bd689145d2c3b2a9844005d01087cc1194e79e"
 uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
-version = "2024.1.0+0"
+version = "2024.2.1+0"
 
 [[deps.InteractiveUtils]]
 deps = ["Markdown"]
@@ -554,27 +593,49 @@ deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArr
 git-tree-sha1 = "88a101217d7cb38a7b481ccd50d21876e1d1b0e0"
 uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
 version = "0.15.1"
+weakdeps = ["Unitful"]
 
     [deps.Interpolations.extensions]
     InterpolationsUnitfulExt = "Unitful"
 
-    [deps.Interpolations.weakdeps]
-    Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
+[[deps.IntervalArithmetic]]
+deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "RoundingEmulator"]
+git-tree-sha1 = "24c095b1ec7ee58b936985d31d5df92f9b9cfebb"
+uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
+version = "0.22.19"
+
+    [deps.IntervalArithmetic.extensions]
+    IntervalArithmeticDiffRulesExt = "DiffRules"
+    IntervalArithmeticForwardDiffExt = "ForwardDiff"
+    IntervalArithmeticIntervalSetsExt = "IntervalSets"
+    IntervalArithmeticRecipesBaseExt = "RecipesBase"
+
+    [deps.IntervalArithmetic.weakdeps]
+    DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
+    ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+    IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
 
 [[deps.IntervalSets]]
 git-tree-sha1 = "dba9ddf07f77f60450fe5d2e2beb9854d9a49bd0"
 uuid = "8197267c-284f-5f27-9208-e0e47529a953"
 version = "0.7.10"
+weakdeps = ["Random", "RecipesBase", "Statistics"]
 
     [deps.IntervalSets.extensions]
     IntervalSetsRandomExt = "Random"
     IntervalSetsRecipesBaseExt = "RecipesBase"
     IntervalSetsStatisticsExt = "Statistics"
 
-    [deps.IntervalSets.weakdeps]
-    Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
-    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
-    Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+[[deps.InverseFunctions]]
+git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb"
+uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
+version = "0.1.17"
+weakdeps = ["Dates", "Test"]
+
+    [deps.InverseFunctions.extensions]
+    InverseFunctionsDatesExt = "Dates"
+    InverseFunctionsTestExt = "Test"
 
 [[deps.IrrationalConstants]]
 git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2"
@@ -599,9 +660,9 @@ version = "1.0.0"
 
 [[deps.JLLWrappers]]
 deps = ["Artifacts", "Preferences"]
-git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca"
+git-tree-sha1 = "be3dc50a92e5a386872a493a10050136d4703f9b"
 uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
-version = "1.5.0"
+version = "1.6.1"
 
 [[deps.JSON]]
 deps = ["Dates", "Mmap", "Parsers", "Unicode"]
@@ -611,9 +672,9 @@ version = "0.21.4"
 
 [[deps.JSON3]]
 deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"]
-git-tree-sha1 = "eb3edce0ed4fa32f75a0a11217433c31d56bd48b"
+git-tree-sha1 = "1d322381ef7b087548321d3f878cb4c9bd8f8f9b"
 uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
-version = "1.14.0"
+version = "1.14.1"
 
     [deps.JSON3.extensions]
     JSON3ArrowExt = ["ArrowTypes"]
@@ -621,12 +682,6 @@ version = "1.14.0"
     [deps.JSON3.weakdeps]
     ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
 
-[[deps.JSServe]]
-deps = ["Base64", "CodecZlib", "Colors", "HTTP", "Hyperscript", "JSON3", "LinearAlgebra", "Markdown", "MsgPack", "Observables", "RelocatableFolders", "SHA", "Sockets", "Tables", "Test", "UUIDs", "WebSockets", "WidgetsBase"]
-git-tree-sha1 = "4cd7c5f723cad3cbbdfb295215e45b15b6924a19"
-uuid = "824d6782-a2ef-11e9-3a09-e5662e0c26f9"
-version = "1.2.9"
-
 [[deps.JpegTurbo]]
 deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
 git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611"
@@ -635,9 +690,9 @@ version = "0.1.5"
 
 [[deps.JpegTurbo_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637"
+git-tree-sha1 = "25ee0be4d43d0269027024d75a24c24d6c6e590c"
 uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
-version = "3.0.3+0"
+version = "3.0.4+0"
 
 [[deps.KernelDensity]]
 deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
@@ -652,27 +707,27 @@ uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
 version = "3.100.2+0"
 
 [[deps.LERC_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "36bdbc52f13a7d1dcb0f3cd694e01677a515655b"
 uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
-version = "3.0.0+1"
+version = "4.0.0+0"
 
 [[deps.LLVMOpenMP_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713"
+git-tree-sha1 = "78211fb6cbc872f77cad3fc0b6cf647d923f4929"
 uuid = "1d63c593-3942-5779-bab2-d838dc0a180e"
-version = "15.0.7+0"
+version = "18.1.7+0"
 
 [[deps.LZO_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d"
+git-tree-sha1 = "854a9c268c43b77b0a27f22d7fab8d33cdb3a731"
 uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
-version = "2.10.2+0"
+version = "2.10.2+1"
 
 [[deps.LaTeXStrings]]
-git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec"
+git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
 uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
-version = "1.3.1"
+version = "1.4.0"
 
 [[deps.LazyArtifacts]]
 deps = ["Artifacts", "Pkg"]
@@ -718,21 +773,27 @@ version = "3.2.2+1"
 
 [[deps.Libgcrypt_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"]
-git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673"
+git-tree-sha1 = "8be878062e0ffa2c3f67bb58a595375eda5de80b"
 uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
-version = "1.8.11+0"
+version = "1.11.0+0"
+
+[[deps.Libglvnd_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
+git-tree-sha1 = "6f73d1dd803986947b2c750138528a999a6c7733"
+uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
+version = "1.6.0+0"
 
 [[deps.Libgpg_error_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed"
+git-tree-sha1 = "c6ce1e19f3aec9b59186bdf06cdf3c4fc5f5f3e6"
 uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
-version = "1.49.0+0"
+version = "1.50.0+0"
 
 [[deps.Libiconv_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175"
+git-tree-sha1 = "61dfdba58e585066d8bce214c5a51eaa0539f269"
 uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
-version = "1.17.0+0"
+version = "1.17.0+1"
 
 [[deps.Libmount_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -742,9 +803,9 @@ version = "2.40.1+0"
 
 [[deps.Libtiff_jll]]
 deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"]
-git-tree-sha1 = "6355fb9a4d22d867318db186fd09b09b35bd2ed7"
+git-tree-sha1 = "b404131d06f7886402758c9ce2214b636eb4d54a"
 uuid = "89763e89-9b03-5906-acba-b20f662cd828"
-version = "4.6.0+0"
+version = "4.7.0+0"
 
 [[deps.Libuuid_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -756,17 +817,11 @@ version = "2.40.1+0"
 deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
 uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 
-[[deps.LittleCMS_jll]]
-deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll"]
-git-tree-sha1 = "fa7fd067dca76cadd880f1ca937b4f387975a9f5"
-uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f"
-version = "2.16.0+0"
-
 [[deps.LogExpFunctions]]
 deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
-git-tree-sha1 = "18144f3e9cbe9b15b070288eef858f71b291ce37"
+git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea"
 uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
-version = "0.3.27"
+version = "0.3.28"
 
     [deps.LogExpFunctions.extensions]
     LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
@@ -781,6 +836,12 @@ version = "0.3.27"
 [[deps.Logging]]
 uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
 
+[[deps.LoggingExtras]]
+deps = ["Dates", "Logging"]
+git-tree-sha1 = "f02b56007b064fbfddb4c9cd60161b6dd0f40df3"
+uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
+version = "1.1.0"
+
 [[deps.MAT]]
 deps = ["BufferedStreams", "CodecZlib", "HDF5", "SparseArrays"]
 git-tree-sha1 = "1d2dd9b186742b0f317f2530ddcbf00eebb18e96"
@@ -789,15 +850,15 @@ version = "0.10.7"
 
 [[deps.MKL_jll]]
 deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
-git-tree-sha1 = "80b2833b56d466b3858d565adcd16a4a05f2089b"
+git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f"
 uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
-version = "2024.1.0+0"
+version = "2024.2.0+0"
 
 [[deps.MPICH_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "4099bb6809ac109bfc17d521dad33763bcf026b7"
+git-tree-sha1 = "7715e65c47ba3941c502bffb7f266a41a7f54423"
 uuid = "7cb0a576-ebde-5e09-9194-50597f1243b4"
-version = "4.2.1+1"
+version = "4.2.3+0"
 
 [[deps.MPIPreferences]]
 deps = ["Libdl", "Preferences"]
@@ -807,9 +868,9 @@ version = "0.1.11"
 
 [[deps.MPItrampoline_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"]
-git-tree-sha1 = "8c35d5420193841b2f367e658540e8d9e0601ed0"
+git-tree-sha1 = "70e830dab5d0775183c99fc75e4c24c614ed7142"
 uuid = "f1f71cc9-e9ae-5b93-9b94-4fe0e1ad3748"
-version = "5.4.0+0"
+version = "5.5.1+0"
 
 [[deps.MacroTools]]
 deps = ["Markdown", "Random"]
@@ -818,17 +879,16 @@ uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
 version = "0.5.13"
 
 [[deps.Makie]]
-deps = ["Animations", "Base64", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Distributions", "DocStringExtensions", "FFMPEG", "FileIO", "FixedPointNumbers", "Formatting", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageIO", "IntervalSets", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MakieCore", "Markdown", "Match", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "Printf", "Random", "RelocatableFolders", "Serialization", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "UnicodeFun"]
-git-tree-sha1 = "b0323393a7190c9bf5b03af442fc115756df8e59"
-pinned = true
+deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"]
+git-tree-sha1 = "5e4e0e027642293da251bf35dac408d692ccba8b"
 uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
-version = "0.17.13"
+version = "0.21.16"
 
 [[deps.MakieCore]]
-deps = ["Observables"]
-git-tree-sha1 = "fbf705d2bdea8fc93f1ae8ca2965d8e03d4ca98c"
+deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"]
+git-tree-sha1 = "ae4dbe0fcf1594ed98594e5f4ee685295a2a6f74"
 uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b"
-version = "0.4.0"
+version = "0.8.10"
 
 [[deps.MappedArrays]]
 git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e"
@@ -839,16 +899,11 @@ version = "0.4.2"
 deps = ["Base64"]
 uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
 
-[[deps.Match]]
-git-tree-sha1 = "1d9bc5c1a6e7ee24effb93f175c9342f9154d97f"
-uuid = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf"
-version = "1.2.0"
-
 [[deps.MathTeXEngine]]
-deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "Test"]
-git-tree-sha1 = "114ef48a73aea632b8aebcb84f796afcc510ac7c"
+deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"]
+git-tree-sha1 = "f45c8916e8385976e1ccd055c9874560c257ab13"
 uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
-version = "0.4.3"
+version = "0.6.2"
 
 [[deps.MbedTLS]]
 deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"]
@@ -898,17 +953,11 @@ git-tree-sha1 = "f5db02ae992c260e4826fe78c942954b48e1d9c2"
 uuid = "99f44e22-a591-53d1-9472-aa23ef4bd671"
 version = "1.2.1"
 
-[[deps.NaNMath]]
-deps = ["OpenLibm_jll"]
-git-tree-sha1 = "0877504529a3e5c3343c6f8b4c0381e57e4387e4"
-uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
-version = "1.0.2"
-
 [[deps.NearestNeighbors]]
 deps = ["Distances", "StaticArrays"]
-git-tree-sha1 = "ded64ff6d4fdd1cb68dfcbb818c69e144a5b2e4c"
+git-tree-sha1 = "8a3271d8309285f4db73b4f662b1b290c715e85e"
 uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
-version = "0.4.16"
+version = "0.4.21"
 
 [[deps.Netpbm]]
 deps = ["FileIO", "ImageCore", "ImageMetadata"]
@@ -920,6 +969,14 @@ version = "1.1.1"
 uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
 version = "1.2.0"
 
+[[deps.NyxWidgets]]
+deps = ["Bonito", "Colors", "Format", "LazyArtifacts", "Observables"]
+git-tree-sha1 = "936f80aa61413c47da00f96abbc0186078698bca"
+repo-rev = "main"
+repo-url = "https://gitlab.com/dbc-nyx/NyxWidgets.jl"
+uuid = "c288fd06-43d3-4b04-8307-797133353e2e"
+version = "0.1.1"
+
 [[deps.Observables]]
 git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
 uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
@@ -929,14 +986,14 @@ version = "0.5.5"
 deps = ["Observables"]
 git-tree-sha1 = "d54b4c26b41238806c172f85e1913736c2583b0a"
 repo-rev = "main"
-repo-url = "https://gitlab.com/dbc-nyx/observationpolicies.jl"
+repo-url = "https://gitlab.com/dbc-nyx/ObservationPolicies.jl"
 uuid = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
 version = "0.2.4"
 
 [[deps.OffsetArrays]]
-git-tree-sha1 = "e64b4f5ea6b7389f6f046d13d4896a8f9c1ba71e"
+git-tree-sha1 = "1a27764e945a152f7ca7efa04de513d473e9542e"
 uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "1.14.0"
+version = "1.14.1"
 weakdeps = ["Adapt"]
 
     [deps.OffsetArrays.extensions]
@@ -955,9 +1012,9 @@ version = "0.3.23+4"
 
 [[deps.OpenEXR]]
 deps = ["Colors", "FileIO", "OpenEXR_jll"]
-git-tree-sha1 = "327f53360fdb54df7ecd01e96ef1983536d1e633"
+git-tree-sha1 = "97db9e07fe2091882c765380ef58ec553074e9c7"
 uuid = "52e1d378-f018-4a11-a4be-720524705ac7"
-version = "0.3.2"
+version = "0.3.3"
 
 [[deps.OpenEXR_jll]]
 deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
@@ -965,12 +1022,6 @@ git-tree-sha1 = "8292dd5c8a38257111ada2174000a33745b06d4e"
 uuid = "18a262bb-aa17-5467-a713-aee519bc75cb"
 version = "3.2.4+0"
 
-[[deps.OpenJpeg_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "libpng_jll"]
-git-tree-sha1 = "f4cb457ffac5f5cf695699f82c537073958a6a6c"
-uuid = "643b3616-a352-519d-856d-80112ee9badc"
-version = "2.5.2+0"
-
 [[deps.OpenLibm_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
@@ -978,15 +1029,21 @@ version = "0.8.1+2"
 
 [[deps.OpenMPI_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML", "Zlib_jll"]
-git-tree-sha1 = "a9de2f1fc98b92f8856c640bf4aec1ac9b2a0d86"
+git-tree-sha1 = "bfce6d523861a6c562721b262c0d1aaeead2647f"
 uuid = "fe0851c0-eecd-5654-98d4-656369965a5c"
-version = "5.0.3+0"
+version = "5.0.5+0"
+
+[[deps.OpenSSL]]
+deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
+git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4"
+uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
+version = "1.4.3"
 
 [[deps.OpenSSL_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "3da7367955dcc5c54c1ba4d402ccdc09a1a3e046"
+git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10"
 uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
-version = "3.0.13+1"
+version = "3.0.15+1"
 
 [[deps.OpenSpecFun_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
@@ -995,10 +1052,10 @@ uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
 version = "0.5.5+0"
 
 [[deps.Opus_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6703a85cb3781bd5909d48730a67205f3f31a575"
 uuid = "91d4177d-7536-5919-b921-800302f37372"
-version = "1.3.2+0"
+version = "1.3.3+0"
 
 [[deps.OrderedCollections]]
 git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5"
@@ -1024,9 +1081,9 @@ version = "0.4.3"
 
 [[deps.Packing]]
 deps = ["GeometryBasics"]
-git-tree-sha1 = "1155f6f937fa2b94104162f01fa400e192e4272f"
+git-tree-sha1 = "ec3edfe723df33528e085e632414499f26650501"
 uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566"
-version = "0.4.2"
+version = "0.5.0"
 
 [[deps.PaddedViews]]
 deps = ["OffsetArrays"]
@@ -1066,10 +1123,10 @@ uuid = "c2615984-ef14-4d40-b148-916c85b43307"
 version = "0.16.0"
 
 [[deps.PlotUtils]]
-deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "Statistics"]
-git-tree-sha1 = "7b1a9df27f072ac4c9c7cbe5efb198489258d1f5"
+deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"]
+git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18"
 uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
-version = "1.4.1"
+version = "1.4.3"
 
 [[deps.PolygonOps]]
 git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6"
@@ -1094,26 +1151,32 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
 
 [[deps.ProgressMeter]]
 deps = ["Distributed", "Printf"]
-git-tree-sha1 = "763a8ceb07833dd51bb9e3bbca372de32c0605ad"
+git-tree-sha1 = "8f6bc219586aef8baf0ff9a5fe16ee9c70cb65e4"
 uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
-version = "1.10.0"
+version = "1.10.2"
 
 [[deps.PtrArrays]]
-git-tree-sha1 = "f011fbb92c4d401059b2212c05c0601b70f8b759"
+git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f"
 uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
-version = "1.2.0"
+version = "1.2.1"
 
 [[deps.QOI]]
 deps = ["ColorTypes", "FileIO", "FixedPointNumbers"]
-git-tree-sha1 = "18e8f4d1426e965c7b532ddd260599e1510d26ce"
+git-tree-sha1 = "8b3fc30bc0390abdce15f8822c889f669baed73d"
 uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65"
-version = "1.0.0"
+version = "1.0.1"
 
 [[deps.QuadGK]]
 deps = ["DataStructures", "LinearAlgebra"]
-git-tree-sha1 = "9b23c31e76e333e6fb4c1595ae6afa74966a729e"
+git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da"
 uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
-version = "2.9.4"
+version = "2.11.1"
+
+    [deps.QuadGK.extensions]
+    QuadGKEnzymeExt = "Enzyme"
+
+    [deps.QuadGK.weakdeps]
+    Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
 
 [[deps.Quaternions]]
 deps = ["LinearAlgebra", "Random", "RealDot"]
@@ -1150,6 +1213,12 @@ git-tree-sha1 = "9f0a1b71baaf7650f4fa8a1d168c7fb6ee41f0c9"
 uuid = "c1ae055f-0cd5-4b69-90a6-9a35b1a98df9"
 version = "0.1.0"
 
+[[deps.RecipesBase]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff"
+uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+version = "1.3.4"
+
 [[deps.Reexport]]
 git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
 uuid = "189a3867-3050-52da-a836-e630ba90ab69"
@@ -1157,9 +1226,9 @@ version = "1.2.2"
 
 [[deps.RelocatableFolders]]
 deps = ["SHA", "Scratch"]
-git-tree-sha1 = "307761d71804208c0c62abdbd0ea6822aa5bbefd"
+git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864"
 uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
-version = "0.2.0"
+version = "1.0.1"
 
 [[deps.Requires]]
 deps = ["UUIDs"]
@@ -1169,27 +1238,30 @@ version = "1.3.0"
 
 [[deps.Rmath]]
 deps = ["Random", "Rmath_jll"]
-git-tree-sha1 = "f65dcb5fa46aee0cf9ed6274ccbd597adc49aa7b"
+git-tree-sha1 = "852bd0f55565a9e973fcfee83a84413270224dc4"
 uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
-version = "0.7.1"
+version = "0.8.0"
 
 [[deps.Rmath_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "d483cd324ce5cf5d61b77930f0bbd6cb61927d21"
+git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8"
 uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
-version = "0.4.2+0"
+version = "0.5.1+0"
 
 [[deps.Rotations]]
 deps = ["LinearAlgebra", "Quaternions", "Random", "StaticArrays"]
 git-tree-sha1 = "5680a9276685d392c87407df00d57c9924d9f11e"
 uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
 version = "1.7.1"
+weakdeps = ["RecipesBase"]
 
     [deps.Rotations.extensions]
     RotationsRecipesBaseExt = "RecipesBase"
 
-    [deps.Rotations.weakdeps]
-    RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+[[deps.RoundingEmulator]]
+git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b"
+uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705"
+version = "0.2.1"
 
 [[deps.SHA]]
 uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
@@ -1197,9 +1269,9 @@ version = "0.7.0"
 
 [[deps.SIMD]]
 deps = ["PrecompileTools"]
-git-tree-sha1 = "2803cab51702db743f3fda07dd1745aadfbf43bd"
+git-tree-sha1 = "52af86e35dd1b177d051b12681e1c581f53c281b"
 uuid = "fdea26ae-647d-5447-a871-4b548cad5224"
-version = "3.5.0"
+version = "3.7.0"
 
 [[deps.Scratch]]
 deps = ["Dates"]
@@ -1212,9 +1284,9 @@ uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
 
 [[deps.ShaderAbstractions]]
 deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays", "StructArrays", "Tables"]
-git-tree-sha1 = "6b5bba824b515ec026064d1e7f5d61432e954b71"
+git-tree-sha1 = "79123bc60c5507f035e6d1d9e563bb2971954ec8"
 uuid = "65257c39-d410-5151-9873-9b3e5be5013e"
-version = "0.2.9"
+version = "0.4.1"
 
 [[deps.SharedArrays]]
 deps = ["Distributed", "Mmap", "Random", "Serialization"]
@@ -1232,6 +1304,11 @@ git-tree-sha1 = "d263a08ec505853a5ff1c1ebde2070419e3f28e9"
 uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96"
 version = "0.4.0"
 
+[[deps.SimpleBufferStream]]
+git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1"
+uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7"
+version = "1.2.0"
+
 [[deps.SimpleTraits]]
 deps = ["InteractiveUtils", "MacroTools"]
 git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
@@ -1268,6 +1345,12 @@ weakdeps = ["ChainRulesCore"]
     [deps.SpecialFunctions.extensions]
     SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
 
+[[deps.StableRNGs]]
+deps = ["Random"]
+git-tree-sha1 = "83e6cce8324d49dfaf9ef059227f91ed4441a8e5"
+uuid = "860ef19b-820b-49d6-a774-d7a799459cd3"
+version = "1.0.2"
+
 [[deps.StackViews]]
 deps = ["OffsetArrays"]
 git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
@@ -1276,9 +1359,9 @@ version = "0.1.1"
 
 [[deps.StaticArrays]]
 deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
-git-tree-sha1 = "9ae599cd7529cfce7fea36cf00a62cfc56f0f37c"
+git-tree-sha1 = "777657803913ffc7e8cc20f0fd04b634f871af8f"
 uuid = "90137ffa-7385-5640-81b9-e52037218182"
-version = "1.9.4"
+version = "1.9.8"
 weakdeps = ["ChainRulesCore", "Statistics"]
 
     [deps.StaticArrays.extensions]
@@ -1286,9 +1369,9 @@ weakdeps = ["ChainRulesCore", "Statistics"]
     StaticArraysStatisticsExt = "Statistics"
 
 [[deps.StaticArraysCore]]
-git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d"
+git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682"
 uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
-version = "1.4.2"
+version = "1.4.3"
 
 [[deps.Statistics]]
 deps = ["LinearAlgebra", "SparseArrays"]
@@ -1303,24 +1386,21 @@ version = "1.7.0"
 
 [[deps.StatsBase]]
 deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
-git-tree-sha1 = "d1bf48bfcc554a3761a133fe3a9bb01488e06916"
+git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21"
 uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
-version = "0.33.21"
+version = "0.34.3"
 
 [[deps.StatsFuns]]
 deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
-git-tree-sha1 = "cef0472124fab0695b58ca35a77c6fb942fdab8a"
+git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46"
 uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
-version = "1.3.1"
+version = "1.3.2"
+weakdeps = ["ChainRulesCore", "InverseFunctions"]
 
     [deps.StatsFuns.extensions]
     StatsFunsChainRulesCoreExt = "ChainRulesCore"
     StatsFunsInverseFunctionsExt = "InverseFunctions"
 
-    [deps.StatsFuns.weakdeps]
-    ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
-    InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
-
 [[deps.StructArrays]]
 deps = ["ConstructionBase", "DataAPI", "Tables"]
 git-tree-sha1 = "f4dc295e983502292c4c3f951dbb4e985e35b3be"
@@ -1341,9 +1421,9 @@ version = "0.6.18"
 
 [[deps.StructTypes]]
 deps = ["Dates", "UUIDs"]
-git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70"
+git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8"
 uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
-version = "1.10.0"
+version = "1.11.0"
 
 [[deps.SuiteSparse]]
 deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
@@ -1366,10 +1446,10 @@ uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
 version = "1.0.1"
 
 [[deps.Tables]]
-deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits"]
-git-tree-sha1 = "cb76cf677714c095e535e3501ac7954732aeea2d"
+deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
+git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297"
 uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
-version = "1.11.1"
+version = "1.12.0"
 
 [[deps.Tar]]
 deps = ["ArgTools", "SHA"]
@@ -1386,25 +1466,30 @@ version = "0.1.1"
 deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
 uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 
+[[deps.ThreadPools]]
+deps = ["Printf", "RecipesBase", "Statistics"]
+git-tree-sha1 = "50cb5f85d5646bc1422aa0238aa5bfca99ca9ae7"
+uuid = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431"
+version = "2.1.1"
+
 [[deps.TidyObservables]]
 deps = ["Observables"]
 git-tree-sha1 = "0589ec7397374678942cae9aa356b4bb6c1e9bf4"
 repo-rev = "main"
-repo-url = "https://gitlab.com/dbc-nyx/tidyobservables.jl"
+repo-url = "https://gitlab.com/dbc-nyx/TidyObservables.jl"
 uuid = "c8131bbd-73a8-4254-a42d-d5d4c5febb31"
 version = "0.1.1"
 
 [[deps.TiffImages]]
 deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "ProgressMeter", "SIMD", "UUIDs"]
-git-tree-sha1 = "bc7fd5c91041f44636b2c134041f7e5263ce58ae"
+git-tree-sha1 = "0248b1b2210285652fbc67fd6ced9bf0394bcfec"
 uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
-version = "0.10.0"
+version = "0.11.1"
 
 [[deps.TranscodingStreams]]
-deps = ["Random", "Test"]
-git-tree-sha1 = "9a6ae7ed916312b41236fcef7e0af564ef934769"
+git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
 uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
-version = "0.9.13"
+version = "0.11.3"
 
 [[deps.TransformsBase]]
 deps = ["AbstractTrees"]
@@ -1412,6 +1497,11 @@ git-tree-sha1 = "2412fb54902b0063c69c2bcfbec6b571120cc856"
 uuid = "28dd2a49-a57a-4bfb-84ca-1a49db9b96b8"
 version = "0.1.2"
 
+[[deps.TriplotBase]]
+git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b"
+uuid = "981d1d27-644d-49a2-9326-4793e63143c3"
+version = "0.1.0"
+
 [[deps.URIs]]
 git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b"
 uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
@@ -1430,17 +1520,28 @@ git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
 uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
 version = "0.4.1"
 
+[[deps.Unitful]]
+deps = ["Dates", "LinearAlgebra", "Random"]
+git-tree-sha1 = "d95fe458f26209c66a187b1114df96fd70839efd"
+uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
+version = "1.21.0"
+weakdeps = ["ConstructionBase", "InverseFunctions"]
+
+    [deps.Unitful.extensions]
+    ConstructionBaseUnitfulExt = "ConstructionBase"
+    InverseFunctionsUnitfulExt = "InverseFunctions"
+
 [[deps.WGLMakie]]
-deps = ["Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "ImageMagick", "JSServe", "LinearAlgebra", "Makie", "Observables", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
-git-tree-sha1 = "0c7a980515d2072b1bc094668b5ca94671d020de"
+deps = ["Bonito", "Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "LinearAlgebra", "Makie", "Observables", "PNGFiles", "PrecompileTools", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"]
+git-tree-sha1 = "8ac9150de978e8215e7c9ab437d6f0a673748999"
 uuid = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
-version = "0.6.13"
+version = "0.10.16"
 
-[[deps.WebSockets]]
-deps = ["Base64", "Dates", "HTTP", "Logging", "Sockets"]
-git-tree-sha1 = "f91a602e25fe6b89afc93cf02a4ae18ee9384ce3"
-uuid = "104b5d7c-a370-577a-8038-80a2059c5097"
-version = "1.5.9"
+[[deps.WebP]]
+deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"]
+git-tree-sha1 = "aa1ca3c47f119fbdae8770c29820e5e6119b83f2"
+uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1"
+version = "0.1.3"
 
 [[deps.WidgetsBase]]
 deps = ["Observables"]
@@ -1456,21 +1557,21 @@ version = "1.0.0"
 
 [[deps.XML2_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"]
-git-tree-sha1 = "52ff2af32e591541550bd753c0da8b9bc92bb9d9"
+git-tree-sha1 = "6a451c6f33a176150f315726eba8b92fbfdb9ae7"
 uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
-version = "2.12.7+0"
+version = "2.13.4+0"
 
 [[deps.XSLT_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
-git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"]
+git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc"
 uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
-version = "1.1.34+0"
+version = "1.1.41+0"
 
 [[deps.XZ_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "ac88fb95ae6447c8dda6a5503f3bafd496ae8632"
+git-tree-sha1 = "15e637a697345f6743674f1322beefbc5dcd5cfc"
 uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800"
-version = "5.4.6+0"
+version = "5.6.3+0"
 
 [[deps.Xorg_libX11_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
@@ -1510,9 +1611,9 @@ version = "0.1.1+0"
 
 [[deps.Xorg_libxcb_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
-git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d"
+git-tree-sha1 = "bcd466676fef0878338c61e655629fa7bbc69d8e"
 uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
-version = "1.15.0+0"
+version = "1.17.0+0"
 
 [[deps.Xorg_xtrans_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -1527,9 +1628,9 @@ version = "1.2.13+1"
 
 [[deps.Zstd_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b"
+git-tree-sha1 = "555d1076590a6cc2fdee2ef1469451f872d8b41b"
 uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
-version = "1.5.6+0"
+version = "1.5.6+1"
 
 [[deps.isoband_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
@@ -1550,39 +1651,45 @@ uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b"
 version = "3.9.0+0"
 
 [[deps.libass_jll]]
-deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
-git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47"
+deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "e17c115d55c5fbb7e52ebedb427a0dca79d4484e"
 uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
-version = "0.15.1+0"
+version = "0.15.2+0"
 
 [[deps.libblastrampoline_jll]]
 deps = ["Artifacts", "Libdl"]
 uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
-version = "5.8.0+1"
+version = "5.11.0+0"
 
 [[deps.libfdk_aac_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "8a22cf860a7d27e4f3498a0fe0811a7957badb38"
 uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
-version = "2.0.2+0"
+version = "2.0.3+0"
 
 [[deps.libpng_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4"
+git-tree-sha1 = "b70c870239dc3d7bc094eb2d6be9b73d27bef280"
 uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
-version = "1.6.43+1"
+version = "1.6.44+0"
 
 [[deps.libsixel_jll]]
 deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "libpng_jll"]
-git-tree-sha1 = "d4f63314c8aa1e48cd22aa0c17ed76cd1ae48c3c"
+git-tree-sha1 = "7dfa0fd9c783d3d0cc43ea1af53d69ba45c447df"
 uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
-version = "1.10.3+0"
+version = "1.10.3+1"
 
 [[deps.libvorbis_jll]]
 deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
-git-tree-sha1 = "b910cb81ef3fe6e78bf6acee440bda86fd6ae00c"
+git-tree-sha1 = "490376214c4721cdaca654041f635213c6165cb3"
 uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
-version = "1.3.7+1"
+version = "1.3.7+2"
+
+[[deps.libwebp_jll]]
+deps = ["Artifacts", "Giflib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libglvnd_jll", "Libtiff_jll", "libpng_jll"]
+git-tree-sha1 = "ccbb625a89ec6195856a50aa2b668a5c08712c94"
+uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2"
+version = "1.4.0+0"
 
 [[deps.nghttp2_jll]]
 deps = ["Artifacts", "Libdl"]
@@ -1601,13 +1708,13 @@ uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
 version = "17.4.0+2"
 
 [[deps.x264_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "35976a1216d6c066ea32cba2150c4fa682b276fc"
 uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
-version = "2021.5.5+0"
+version = "10164.0.0+0"
 
 [[deps.x265_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9"
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "dcc541bb19ed5b0ede95581fb2e41ecf179527d2"
 uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
-version = "3.5.0+0"
+version = "3.6.0+0"
diff --git a/Project.toml b/Project.toml
index 7578ac2fb8e7b080fbc9d0f1b7a426f38e8ff1a3..64da699db05a973147106bdfa26c67f49c02551a 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,19 +1,20 @@
 name = "LarvaTagger"
 uuid = "8b3b36f1-dfed-446e-8561-ea19fe966a4d"
 authors = ["François Laurent", "Institut Pasteur"]
-version = "0.18.4"
+version = "0.19.0"
 
 [deps]
+Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
 Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
 Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
 DocOpt = "968ba79b-81e4-546f-ab3a-2eecfa62a9db"
 Format = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
-JSServe = "824d6782-a2ef-11e9-3a09-e5662e0c26f9"
 LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
 Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
 Meshes = "eacbb407-ea5a-433e-ab97-5258b1ca43fa"
 NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
+NyxWidgets = "c288fd06-43d3-4b04-8307-797133353e2e"
 Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
 ObservationPolicies = "6317928a-6b1a-42e8-b853-b8e2fc3e9ca3"
 OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
@@ -25,8 +26,11 @@ TidyObservables = "c8131bbd-73a8-4254-a42d-d5d4c5febb31"
 WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"
 
 [compat]
-Makie = "< 0.18.0"
+Bonito = "< 4.0.0"
+NyxWidgets = "0.1.1"
+ObservationPolicies = "0.2.4"
 PlanarLarvae = ">= 0.11.2"
+TidyObservables = "0.1.1"
 julia = "1.6"
 
 [extras]
diff --git a/README.md b/README.md
index 5636442e7d09ab77e21cb0b498b7f0100701e828..1b9a1b286b9ae0235f27bf794435e79375c3edfd 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,9 @@ This package features a web-based graphical user interface (GUI) for visualizing
 
 A command-line interface (CLI) is also available for batch processing, including the automatic tagging of track data files, training new taggers from labeled data, etc.
 
-Although *LarvaTagger.jl* alone comes with no automatic tagger, it is designed to work in combination with [*MaggotUBA*](https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter) for the identification of larval actions or postures.
+Although *LarvaTagger.jl* alone comes with no automatic tagger, it is designed to work primarily in combination with [*MaggotUBA*](https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter) for the identification of larval actions or postures.
 
-> The term *LarvaTagger* refers to the full software with all the currently available tagging capabilities included.
+> The term *LarvaTagger* refers to the full software with MaggotUBA-based tagging capabilities included.
 
 *Docker* images are available for *LarvaTagger*. See the [dedicated instructions page](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/tree/main/recipes) or the [Quick start](#quick-start-with-docker) section below.
 
@@ -37,6 +37,8 @@ The present section will guide you through the following steps:
 
 These instructions only require [*Docker*](https://docs.docker.com/get-docker/) to be available, more specifically the *Docker Engine*. On Windows and macOS, *Docker Engine* is provided by the *Docker Desktop* product. *Docker Desktop* is free of charge.
 
+> On macOS, make sure to enable Rosetta 2 for Apple Silicon architecture as specified in the [“System requirements” section](https://docs.docker.com/desktop/setup/install/mac-install/). Docker VMM may be a better option, especially for M2+ computers, but has not been tested so far.
+
 Ensure the currently logged-in user has access to *Docker*, and the *Docker* engine is running.
 
 The *Docker* image is obtained and operated using a script:
@@ -56,16 +58,16 @@ chmod a+x larvatagger.sh
 
 The demo data can be opened in the web browser for visual inspection, on macOS and Linux with:
 ```
-./larvatagger.sh open Masson_et_al_2020.label --browser
+./larvatagger.sh open Masson_et_al_2020.label
 ```
 and on Windows with:
 ```
-larvatagger.bat open Masson_et_al_2020.label --browser
+larvatagger.bat open Masson_et_al_2020.label
 ```
 
-The viewer should open in a new tab in the default web browser. If no tabs appear, open a new one and go to [http://127.0.0.1:9284](http://127.0.0.1:9284).
+Once told to do so, go to [http://127.0.0.1:9284](http://127.0.0.1:9284) in a web browser.
 
-Not all the web browsers are supported. Prefer Firefox or Chrome-like browsers.
+Not all the web browsers are supported. Prefer Firefox or Chrome-like browsers. In particular, Safari is not supported.
 
 Note that, on the first execution, the *Docker* image will be downloaded. This is done once, and can be considered as the installation step.
 
@@ -73,7 +75,9 @@ Note that, on the first execution, the *Docker* image will be downloaded. This i
 
 ### Main controls
 
-The tracked larvae can be animated pressing the "Play/Pause" button of the player below the 2D view.
+> If the 2D viewer looks small, restart LarvaTagger with additional option `--view-factor 2`.
+
+The tracked larvae can be animated pressing the play button of the player below the 2D view.
 
 From the view displaying multiple larvae, a larva can be selected. As a result, the 2D view displays the selected larva only. From this single-larva view, the top right button allows going back to the multi-larva view.
 
@@ -92,6 +96,8 @@ Alternatively, tags can be similarly *un*assigned at individual time steps or ov
 
 [Using the *Docker* image](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/tree/main/recipes) may come handy, as it ships *LarvaTagger* with the default *MaggotUBA*-based tagger as a standalone package and is easier to update.
 
+Alternatively, an install script is provided to install LarvaTagger with MaggotUBA-based tagging capabilities.
+
 ### Using the *scripts/install.sh* script
 
 On macOS, Linux and WSL, a [script](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/blob/dev/scripts/install.sh?ref_type=heads) is provided to automate the installation process from source. This script installs LarvaTagger.jl and Julia if missing. It also adds the *larvatagger* command to the user's path variable.
@@ -99,7 +105,7 @@ The *scripts/install.sh* script can be run with:
 ```
 /bin/bash -c "$(curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false")"
 ```
-To install the full LarvaTagger suite (Linux and WSL only!), run instead:
+To install the full LarvaTagger suite on Linux or WSL, run instead:
 ```
 curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false" | /bin/bash -s -- --with-default-backend
 ```
@@ -110,11 +116,16 @@ If `pyenv` is available, the script will use this tool to install Python.
 Otherwise, `python3.8` and `python3.8-venv` may have to be manually installed.
 On WSL, the script will attempt to install `pyenv` and Python (tested with Ubuntu 20.04).
 
+On macOS, the full LarvaTagger suite can be installed only with the `--with-backend --experimental` options:
+```
+curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false" | /bin/bash -s -- --with-backend --experimental
+```
+
 This script can also uninstall LarvaTagger (if installed with the same script) with: `curl -sSL "https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/raw/dev/scripts/install.sh?ref_type=heads&inline=false" | /bin/bash -s -- --uninstall` which can be useful for example prior to reinstalling after failure.
 
 ### Manually from source
 
-To natively run *LarvaTagger.jl* instead, you will need [*julia>=1.7*](https://julialang.org/downloads/).
+To manually install *LarvaTagger.jl* (without automatic tagging capabilities), you will need [*julia>=1.7*](https://julialang.org/downloads/).
 
 If you are not familiar with installing *Julia*, you may appreciate installation helpers such as [*Juliaup*](https://github.com/JuliaLang/juliaup) or [*jill*](https://pypi.org/project/jill/).
 
@@ -143,6 +154,7 @@ mkdir LarvaTagger && cd $_ && \
 julia --project=. -e 'using Pkg; Pkg.add([ \
   PackageSpec(url="https://gitlab.com/dbc-nyx/ObservationPolicies.jl"), \
   PackageSpec(url="https://gitlab.com/dbc-nyx/TidyObservables.jl"), \
+  PackageSpec(url="https://gitlab.com/dbc-nyx/NyxWidgets.jl"), \
   PackageSpec(url="https://gitlab.pasteur.fr/nyx/PlanarLarvae.jl"), \
   PackageSpec(url="https://gitlab.pasteur.fr/nyx/LarvaTagger.jl")])'
 ```
@@ -166,7 +178,7 @@ If the alternative installation procedure was followed instead, go to the *Larva
 julia --project=. -e 'using Pkg; Pkg.update()'
 ```
 
-## Launching the graphical user interface
+## Launching the graphical/web user interface
 
 The GUI is provided by a web server and can be accessed using a web browser, preferably Firefox or Chrome-like browsers.
 
@@ -174,6 +186,12 @@ Running *LarvaTagger.jl* sets the server up. Once the server is ready, the app c
 
 See the [Quick start](#main-controls) section for information about some of the controls available in the GUI.
 
+The launching procedure depends on the followed installation procedure.
+* With manual installations using Julia, the web UI can be served from the `julia` REPL (advanced).
+* The *larvatagger* script can be used instead to launch the web UI from the command-line.
+* Docker users should use the *larvatagger.sh* or *larvatagger.ps1* scripts instead; beware that their usage differs from that of the *larvatagger* script.
+* Users of the *install.sh* script benefit from a slightly simplified *larvatagger* command, added to the PATH environment variable.
+
 ### Using the *larvatagger* script
 
 If you cloned the repository, we recommend you run *LarvaTagger* using the *larvatagger* script to be found in the *scripts* directory:
@@ -182,22 +200,22 @@ If you cloned the repository, we recommend you run *LarvaTagger* using the *larv
 scripts/larvatagger open path/to/data/file --browser
 ```
 
-If LarvaTagger was installed using the *scripts/install.sh* script, the *larvatagger* script should be in the user's path environment variable and, as a consequence, available from everywhere in the commandline:
+If LarvaTagger was installed using the *scripts/install.sh* script, the *larvatagger* script should be in the user's path environment variable and, as a consequence, available from everywhere in the command-line:
 ```
-larvatagger open path/to/data/file --browser
+larvatagger open path/to/data/file
 ```
 
 The script will actually open a *Julia* interpreter, and give some guidance on how to exit the interpreter.
 
-> For now, `larvatagger open` cannot be run with no input arguments. A track data file is required.
+> Since version 0.19, the data file argument is no longer required. Data files can be opened from the Web UI.
 
-The `--browser` argument may open a new tab in your web browser, but this feature is known to be ineffective in some situations. In such an event, open a new tab and go to [http://localhost:9284](http://127.0.0.1:9284).
+The `--browser` argument may open a new tab in your web browser, but this feature is known to be ineffective in some situations. In such an event, open a new tab and go to [http://localhost:9284](http://127.0.0.1:9284). The argument is passed by default by the *larvatagger* command, in contrast to the *scripts/larvatagger* script.
 
 The first time the application is loaded, it may take a while for a window in your web browser to open, and the data to be plotted.
 
 ### From the *Julia* interpreter
 
-As an alternative to the *larvatagger* script, in the *LarvaTagger* directory created above, launch the *Julia* interpreter:
+As an alternative to the *larvatagger* script or command, in the *LarvaTagger* directory created above, launch the *Julia* interpreter:
 ```
 julia --project=.
 ```
@@ -210,16 +228,15 @@ To exit the interpreter, type `exit()` or press Ctrl+D.
 
 ### macOS
 
-On macOS computers, the 2D larva view may show up twice as small as expected. To mitigate this undesired behavior,
-`larvatagger open` admits a `--view-factor` option, and equivalently `larvaeditor` admits a `viewfactor` argument.
-This option/argument is 2 per default on macOS, 1 on the other platforms.
+On some computers (typically macOS computers), the 2D larva view may show up twice as small as expected.
+To mitigate this undesired behavior, `larvatagger open` admits a `--view-factor` option, and equivalently `larvaeditor` admits a `viewfactor` argument.
 Feel free to adjust the value if the 2D view is too small or large.
 
 ## Automatic tagging
 
-*LarvaTagger.jl* comes with no automatic tagger per default, unless installed with the *scripts/install.sh* script and the `--with-default-backend` option.
+*LarvaTagger.jl* comes with no automatic tagger per default, unless run using Docker or installed with the *scripts/install.sh* script and the `--with-default-backend` option.
 
-To extend the editor with *MaggotUBA*-based automatic tagging, see the [recommended installation steps for *TaggingBackends* and *MaggotUBA*](https://gitlab.pasteur.fr/nyx/TaggingBackends#recommended-installation).
+To extend the editor with automatic tagging capabilities, see the [recommended installation steps for *TaggingBackends* and *MaggotUBA*](https://gitlab.pasteur.fr/nyx/TaggingBackends#recommended-installation).
 
 > Strictly speaking, the action identification package (or tagging backend) is called *MaggotUBA-adapter*.
 > It is based on *MaggotUBA*, which is an autoencoder for action-agnostic behavior analysis.
@@ -238,6 +255,7 @@ Similarly, to let *LarvaTagger* know about *MaggotUBA*:
 ```
 scripts/larvatagger open <path/to/data/file> --backends=<path/to/MaggotUBA/parent/directory> --browser
 ```
+The `--backends` argument is not necessary with the *larvatagger* **command** (`scripts/larvatagger` denotes the *larvatagger* **script** to be found in the *scripts* directory of the LarvaTagger.jl project).
 
 The *larvatagger* script can also be used to train a new tagger:
 ```
@@ -248,6 +266,11 @@ and apply this tagger to a tracking data file:
 scripts/larvatagger predict <path/to/backend> <tagger-name> <path/to/data/file>
 ```
 
+With the *larvatagger* command, the path-to-backend argument is still required (may change in the future), and the full command to run the default 20230311 tagger for example is:
+```
+larvatagger predict ~/.local/share/larvatagger/MaggotUBA 20230311 <path/to/data/file>
+```
+
 Among the many optional arguments to the `train` command, an important one is `--iterations`. It allows specifying the training budget. In several applications, higher training scores were achieved increasing the value for this argument. The default for *MaggotUBA-adapter* tagging backend is 1000, which may be too few iterations in many cases.
 
 *MaggotUBA-adapter* admits either a single value or a comma-separated pair of values. Indeed, *MaggotUBA-adapter* training is performed in two phases: first the classifier stage is trained, with static weights in the pretrained *MaggotUBA* encoder; second both the classifier and encoder are fine-tuned. A higher training budget for the second fine-tuning stage may significantly increase the training accuracy.
@@ -292,3 +315,9 @@ On calling `larvatagger predict` using a *MaggotUBA*-based tagger, if *CUDA* com
 .../torch/cuda/__init__.py:... Can't initialize NVML
 ```
 you might have upgraded your *NVIDIA* drivers and not rebooted the OS. Try restarting your computer.
+
+### Display size
+
+On some computers, the 2D viewer is abnormally small. This results from multiple sizing modalities (pixels for the 2D viewer, font sizes for the other parts of the web UI) and local defaults that cannot be probed. As a consequence, the user is invited to pass argument `--view-factor` followed by a value that scales the 2D viewer only (try values `1.5` and `2` for example).
+
+See also issue [#16](https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/issues/16).
diff --git a/recipes/Dockerfile b/recipes/Dockerfile
index d32d5f099714a92d387127d8cbc3d50fc81f8674..4489a17ed460154b4c004ea8f3fc2a33409d1a45 100644
--- a/recipes/Dockerfile
+++ b/recipes/Dockerfile
@@ -1,4 +1,4 @@
-FROM julia:1.10.4-bullseye AS base
+FROM julia:1.10.6-bullseye AS base
 
 ARG PROJECT_DIR=/app
 ARG BRANCH=main
@@ -28,7 +28,7 @@ FROM base AS prebuild
 ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
     PIP_NO_CACHE_DIR=1 \
     POETRY_VIRTUALENVS_IN_PROJECT=1 \
-    POETRY_VERSION=1.2.0
+    POETRY_VERSION=1.8.3
 
 RUN apt-get update \
  && apt-get install --no-install-recommends -y python3-dev python3-pip \
diff --git a/recipes/Dockerfile.pasteurjanelia b/recipes/Dockerfile.pasteurjanelia
new file mode 100644
index 0000000000000000000000000000000000000000..64924602d24a6ad3087c08e86a79eeaebc48d865
--- /dev/null
+++ b/recipes/Dockerfile.pasteurjanelia
@@ -0,0 +1,31 @@
+ARG BASE=latest
+
+FROM flaur/larvatagger:${BASE} AS prebuild
+
+ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
+    PIP_NO_CACHE_DIR=1 \
+    POETRY_VIRTUALENVS_IN_PROJECT=1 \
+    POETRY_VERSION=1.8.3
+
+RUN apt-get update \
+ && apt-get install --no-install-recommends -y make wget unzip \
+ && if ! command -v poetry &>/dev/null; then \
+    apt-get install --no-install-recommends -y python3-dev python3-pip \
+ && pip install "poetry==$POETRY_VERSION"; \
+    fi \
+ && rm -rf /var/lib/apt/lists/*
+
+
+FROM prebuild AS backend
+
+ARG PROJECT_DIR=/app
+ARG BACKEND_BRANCH=main
+
+RUN cd $PROJECT_DIR \
+ && git clone --depth 1 --no-tags --single-branch -b $BACKEND_BRANCH https://gitlab.pasteur.fr/nyx/PasteurJanelia-adapter PasteurJanelia \
+ && cd PasteurJanelia \
+ && make package \
+ && rm -rf bin/matlab/2023b/bin/glnxa64/matlab_startup_plugins/matlab_graphics_ui \
+ && rm -rf bin/matlab/2023b/bin/glnxa64/matlab_startup_plugins/foundation/platform/pf_matlab_integ \
+ && rm -rf .git ~/.cache
+
diff --git a/recipes/README.md b/recipes/README.md
index 4259994144c7f0c6582e969aedabba0550729c37..5ad9e8eba19161dc6f1bc7f2593a96e387537981 100644
--- a/recipes/README.md
+++ b/recipes/README.md
@@ -177,7 +177,7 @@ docker pull flaur/larvatagger
 ```
 
 Beware that images that ship with a tagging backend are relatively large files (>5GB on disk).
-If you are not interested in automatic tagging, use the `flaur/larvatagger:0.18-standalone` image instead.
+If you are not interested in automatic tagging, use the `flaur/larvatagger:0.19-standalone` image instead.
 
 ### Upgrading
 
diff --git a/scripts/install.sh b/scripts/install.sh
index af1f164f583f0d90ed1d8c1454c416a5ac2fd915..a1a2b1de1c3777dc486c423956a4455c27dc4f57 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -4,9 +4,14 @@
 
 if [ -z "$BIN_DIR" ]; then
   BIN_DIR=~/.local/bin
+else
+  echo "Using environment variable: BIN_DIR= $BIN_DIR"
+  echo "Only ~/.local/bin is fully supported at the moment"
 fi
 if [ -z "$LARVATAGGER_PATH" ]; then
   LARVATAGGER_PATH=~/.local/share/larvatagger
+else
+  echo "Using environment variable: LARVATAGGER_PATH= $LARVATAGGER_PATH"
 fi
 
 if [ "$1" = "--uninstall" ]; then
@@ -18,102 +23,226 @@ if [ "$1" = "--uninstall" ]; then
   done
   rm -rf "$BIN_DIR/larvatagger"
   rm -rf "$LARVATAGGER_PATH"
+
+  # testing only; deletes Poetry, PyEnv and JuliaUp, and does not check beforehands
+  # whether these dependencies were installed by the present script or not
+  if [ "$2" = "--full" ]; then
+    PYTHON="python3"
+    if [[ "`python3 -V`" =~ "Python 3.6" ]] && command -v python3.8 &>/dev/null; then
+      # issue on Maestro
+      PYTHON="python3.8"
+    fi
+    command -v poetry &>/dev/null && curl -sSL https://install.python-poetry.org | $PYTHON - --uninstall
+    command -v pyenv &>/dev/null && rm -rf $(pyenv root)
+    command -v juliaup &>/dev/null && juliaup self uninstall
+    rm -rf ~/.juliaup
+    # TODO: clean up .bash_profile for pyenv-related stuff, .bashrc/.zshrc for PATH manipulations, and restart the shell
+    rm -rf ~/.julia
+  fi
 else
 
 PYTHON_VERSION=3.8
+# the internal_<VAR> variables need to be set non-empty only if
+# the corresponding <VAR> variable is non-empty; they are used to
+# determine whether or not to report externally sourced variables
+internal_WITH_BACKEND=
+internal_MAGGOTUBA_ADAPTER_BRANCH=
+internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=
 for arg in "$@"; do
   if [ "$arg" = "--with-default-backend" ]; then
     WITH_BACKEND=1
+    internal_WITH_BACKEND=1
     MAGGOTUBA_CORE_BRANCH=
     MAGGOTUBA_ADAPTER_BRANCH=
     break
   elif [ "$arg" = "--with-backend" ]; then
     WITH_BACKEND=1
+    internal_WITH_BACKEND=1
   elif [ "$arg" = "--experimental" ]; then
     MAGGOTUBA_CORE_BRANCH=
     MAGGOTUBA_ADAPTER_BRANCH=torch2
+    internal_MAGGOTUBA_ADAPTER_BRANCH=1
     PYTHON_VERSION=3.11
+  elif [ "$arg" = "--free-python-dependencies" ]; then
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
+  elif [ "$arg" = "--lock-python-dependencies" ]; then
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=0
   fi
 done
 
-if ! command -v curl >/dev/null; then
-  if command -v brew >/dev/null; then
-    # macOS users are not given the choice as they usually do not care about freedom
-    brew install curl
-  fi
-fi
-if ! command -v curl >/dev/null; then
-  echo "Command curl required; aborting"
-else
+PYTHON="python$PYTHON_VERSION"
 
 check_brew() {
   if [ "`uname`" = "Darwin" ]; then
     # macOS
-    if ! command -v brew >/dev/null; then
-      echo "Installing Homebrew; admin rights will be requested"
-      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+    if ! command -v brew &>/dev/null; then
+      if ! [ -f /opt/homebrew/bin/brew ]; then
+        echo "Installing Homebrew; admin rights will be requested"
+        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+      fi
+      # not sure why zsh does not support ~/ below, while it seems to behave properly elsewhere...
+      cat <<"EOF" >>$HOME/.zprofile
+
+eval "$(/opt/homebrew/bin/brew shellenv)"
+EOF
+      eval "$(/opt/homebrew/bin/brew shellenv)"
     fi
   fi
 }
 
-if ! command -v realpath >/dev/null; then
+if ! command -v realpath &>/dev/null; then
   # macOS
   check_brew
-  if command -v brew >/dev/null; then
+  if command -v brew &>/dev/null; then
     brew install coreutils
   fi
 fi
 
-if ! command -v julia >/dev/null; then
+if ! command -v curl &>/dev/null; then
+  if [ "`uname`" = "Darwin" ]; then
+    check_brew
+    # macOS users are not given the choice as they usually do not care about freedom
+    brew install curl
+  fi
+fi
+if ! command -v curl &>/dev/null; then
+  echo "Command curl required; aborting"
+else
+
+if [ -z "$JULIA_VERSION" ]; then
+  JULIA_VERSION=1.10
+  JULIA_CHANNEL=lts
+else
+  echo "Using environment variable: JULIA_VERSION= $JULIA_VERSION"
+  if [ -z "$JULIA_CHANNEL" ]; then
+    JULIA_CHANNEL=$JULIA_VERSION
+  else
+    echo "Using environment variable: JULIA_CHANNEL= $JULIA_CHANNEL"
+  fi
+fi
+
+JULIA="julia"
+
+install_juliaup() {
   if [ -z "$JULIA_INSTALL_ARGS" ]; then
     JULIA_INSTALL_ARGS=-y
+  else
+    echo "Using environment variable: JULIA_INSTALL_ARGS= $JULIA_INSTALL_ARGS"
   fi
-  curl -fsSL https://install.julialang.org | sh -s -- $JULIA_INSTALL_ARGS
-  if [ -f ~/.bashrc ]; then
-    source ~/.bashrc
-  elif [ -f ~/.bash_profile ]; then
-    source ~/.bash_profile
+  curl -fsSL https://install.julialang.org | sh -s -- $JULIA_INSTALL_ARGS --default-channel $JULIA_VERSION
+  export PATH=~/.juliaup/bin:$PATH
+}
+if ! command -v $JULIA &>/dev/null; then
+  install_juliaup
+elif ! [[ "`$JULIA -v`" =~ "julia version $JULIA_VERSION" ]]; then
+  if ! command -v juliaup &>/dev/null; then
+    install_juliaup
   fi
 fi
+if command -v juliaup &>/dev/null; then
+  juliaup add $JULIA_CHANNEL
+  juliaup default $JULIA_CHANNEL
+fi
+
+add_local_bin_to_path() {
+  if [ "`realpath $BIN_DIR`" = "`realpath ~/.local/bin`" ]; then
+    # ~/.local/bin not in $PATH?
+    if [ "$SHELL" = "/bin/zsh" ]; then
+      rcfile=~/.zshrc
+    elif [ "$SHELL" = "/bin/bash" ]; then
+      if [ -f ~/.bashrc ]; then
+        rcfile=~/.bashrc
+      elif [ -f ~/.bash_profile ]; then
+        rcfile=~/.bash_profile
+      fi
+    fi
+    echo "Extending the PATH environment variable in $rcfile"
+    cat <<"EOF" >>$rcfile
+
+export PATH=$PATH:~/.local/bin
+EOF
+  else
+    echo "the larvatagger command is available in directory:"
+    echo "  $BIN_DIR"
+    echo "consider adding the directory to the PATH variable in your rc or profile file"
+  fi
+  export PATH=$PATH:$BIN_DIR
+}
+
+install_pyenv_on_ubuntu2004() {
+  echo
+  echo "INFO: installing pyenv and its dependencies"
+  echo
+  if ! [ -d ~/.pyenv ]; then
+    sudo apt-get update
+    sudo apt-get install --no-install-recommends curl build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev xz-utils libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev #tk-dev
+    curl https://pyenv.run | bash
+  fi
+  export PATH=~/.pyenv/bin:$PATH
+  eval "$(~/.pyenv/bin/pyenv init -)"
+  cat <<"EOF" >>~/.bash_profile
+
+command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
+eval "$(~/.pyenv/bin/pyenv init -)"
+EOF
+  pyenv install $PYTHON_VERSION
+}
+
+install_pyenv() {
+  echo
+  echo "INFO: installing pyenv"
+  echo
+  if ! [ -d ~/.pyenv ]; then
+    curl https://pyenv.run | bash
+  fi
+  if ! command -v pyenv &>/dev/null; then
+    export PATH=~/.pyenv/bin:$PATH
+    eval "$(~/.pyenv/bin/pyenv init -)"
+    cat <<"EOF" >>~/.bash_profile
+
+command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
+eval "$(~/.pyenv/bin/pyenv init -)"
+EOF
+  fi
+  pyenv install $PYTHON_VERSION
+  pyenv shell $PYTHON_VERSION
+}
 
 if [ -n "$WITH_BACKEND" ]; then
   if [ "`uname`" = "Darwin" ]; then
     echo "WARNING: the default tagging backend is not supported by macOS"
   fi
-  if ! command -v python$PYTHON_VERSION >/dev/null; then
-    if command -v pyenv >/dev/null; then
+  if ! command -v python$PYTHON_VERSION &>/dev/null; then
+    if command -v pyenv &>/dev/null; then
       [ `pyenv versions | grep $PYTHON_VERSION` ] || pyenv install $PYTHON_VERSION
     elif [[ "`uname -r`" =~ "-microsoft-standard-WSL2" ]]; then
-      echo
-      echo "INFO: installing pyenv and its dependencies"
-      echo
-      if ! [ -d ~/.pyenv ]; then
-        sudo apt-get update
-        sudo apt-get install --no-install-recommends curl build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev xz-utils libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev #tk-dev
-        curl https://pyenv.run | bash
-      fi
-      export PATH=~/.pyenv/bin:$PATH
-      eval "$(~/.pyenv/bin/pyenv init -)"
-      cat <<"EOF" >>~/.bashrc
-
-command -v pyenv >/dev/null || export PATH=~/.pyenv/bin:$PATH
-eval "$(~/.pyenv/bin/pyenv init -)"
-EOF
-      pyenv install $PYTHON_VERSION
+      install_pyenv_on_ubuntu2004
+    elif [[ "`hostname`" =~ ".cluster.embl.de" ]]; then
+      install_pyenv
+    elif [ "`hostname`" = "maestro-submit" ]; then
+      # we could use `module load Python/<version>` but modules are removed without notice
+      install_pyenv
     else
       check_brew
-      if command -v brew >/dev/null; then
+      if command -v brew &>/dev/null; then
         brew install python@$PYTHON_VERSION
+        if ! command -v python$PYTHON_VERSION &>/dev/null; then
+          echo "Try instead $0 --with-backend --experimental"
+          echo "Aborting..."
+          exit 1
+        fi
       else
-        echo "WARNING: command python$PYTHON_VERSION not found"
+        echo "WARNING: command $PYTHON not found"
       fi
     fi
   fi
-  if ! command -v poetry >/dev/null; then
-    if command -v pipx >/dev/null; then
+  if ! command -v poetry &>/dev/null; then
+    if command -v pipx &>/dev/null; then
       pipx install poetry
     else
-      curl -fsSL https://install.python-poetry.org | python3 -
+      # explicit Python version for Maestro
+      curl -fsSL https://install.python-poetry.org | $PYTHON -
+      command -v poetry &>/dev/null || add_local_bin_to_path
     fi
   fi
 fi
@@ -127,6 +256,7 @@ else
   if [ -z "$PLANARLARVAE_BRANCH" ]; then
     PLANARLARVAE_BRANCH=main
   else
+    echo "Using environment variable: PLANARLARVAE_BRANCH= $PLANARLARVAE_BRANCH"
     curl -fsSL https://gitlab.pasteur.fr/nyx/planarlarvae.jl/-/archive/${PLANARLARVAE_BRANCH}/planarlarvae.jl-${PLANARLARVAE_BRANCH}.tar.gz | tar zxv
     mv planarlarvae.jl-${PLANARLARVAE_BRANCH} PlanarLarvae
   fi
@@ -137,12 +267,14 @@ if [ -d LarvaTagger.jl ]; then
 else
   if [ -z "$LARVATAGGER_BRANCH" ]; then
     LARVATAGGER_BRANCH=dev
+  else
+    echo "Using environment variable: LARVATAGGER_BRANCH= $LARVATAGGER_BRANCH"
   fi
   curl -fsSL https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/archive/${LARVATAGGER_BRANCH}/larvatagger.jl-${LARVATAGGER_BRANCH}.tar.gz | tar zxv
   mv larvatagger.jl-${LARVATAGGER_BRANCH} LarvaTagger.jl
 fi
-(cd LarvaTagger.jl && julia --project=. -e 'using Pkg; Pkg.instantiate()')
-[ -d PlanarLarvae ] && (cd LarvaTagger.jl && julia --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
+(cd LarvaTagger.jl && $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
+[ -d PlanarLarvae ] && (cd LarvaTagger.jl && $JULIA --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
 
 if [ -z "$WITH_BACKEND" ]; then
 
@@ -150,14 +282,25 @@ mkdir -p "$BIN_DIR"
 cat <<EOF >"$BIN_DIR"/larvatagger
 #!/usr/bin/env bash
 
+if command -v juliaup &>/dev/null; then
+  PREVIOUS_CHANNEL=\`juliaup status | grep '*' | cut -d'*' -f2 | cut -d\\  -f3\`
+  juliaup default $JULIA_VERSION &>/dev/null
+fi
+
 OPEN_BROWSER=1 "$LARVATAGGER_PATH/LarvaTagger.jl/scripts/larvatagger" \$@
+
+if command -v juliaup &>/dev/null; then
+  juliaup default \$PREVIOUS_CHANNEL &>/dev/null
+fi
 EOF
 
 else
 
+[ -z "$internal_WITH_BACKEND" ] && echo "Using environment variable: WITH_BACKEND= $WITH_BACKEND"
+
 activate() {
   # pyenv activation is necessary on WSL
-  command -v pyenv >/dev/null && pyenv local $PYTHON_VERSION
+  command -v pyenv &>/dev/null && pyenv local $PYTHON_VERSION
   poetry env use $PYTHON_VERSION
 }
 
@@ -166,12 +309,14 @@ if [ -d TaggingBackends ]; then
 else
   if [ -z "$TAGGINGBACKENDS_BRANCH" ]; then
     TAGGINGBACKENDS_BRANCH=main
+  else
+    echo "Using environment variable: TAGGINGBACKENDS_BRANCH= $TAGGINGBACKENDS_BRANCH"
   fi
   curl -fsSL https://gitlab.pasteur.fr/nyx/TaggingBackends/-/archive/${TAGGINGBACKENDS_BRANCH}/TaggingBackends-${TAGGINGBACKENDS_BRANCH}.tar.gz | tar zxv
   mv TaggingBackends-${TAGGINGBACKENDS_BRANCH} TaggingBackends
 fi
-(cd TaggingBackends && julia --project=. -e 'using Pkg; Pkg.instantiate()')
-[ -d PlanarLarvae ] && (cd TaggingBackends && julia --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
+(cd TaggingBackends && activate && PYTHON="`poetry env info`/bin/python" $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
+[ -d PlanarLarvae ] && (cd TaggingBackends && $JULIA --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
 (cd TaggingBackends && activate && JULIA_PROJECT=$(pwd) poetry install)
 
 if [ -d MaggotUBA-core ]; then
@@ -180,6 +325,7 @@ else
   if [ -z "$MAGGOTUBA_CORE_BRANCH" ]; then
     MAGGOTUBA_CORE_BRANCH=main
   else
+    echo "Using environment variable: MAGGOTUBA_CORE_BRANCH= $MAGGOTUBA_CORE_BRANCH"
     curl -fsSL https://gitlab.pasteur.fr/nyx/maggotuba-core/-/archive/${MAGGOTUBA_CORE_BRANCH}/maggotuba-core-${MAGGOTUBA_CORE_BRANCH}.tar.gz | tar zxv
     mv maggotuba-core-${MAGGOTUBA_CORE_BRANCH} MaggotUBA-core
   fi
@@ -190,6 +336,8 @@ if [ -d MaggotUBA ]; then
 else
   if [ -z "$MAGGOTUBA_ADAPTER_BRANCH" ]; then
     MAGGOTUBA_ADAPTER_BRANCH=main
+  elif [ -z "$internal_MAGGOTUBA_ADAPTER_BRANCH" ]; then
+    echo "Using environment variable: MAGGOTUBA_ADAPTER_BRANCH= $MAGGOTUBA_ADAPTER_BRANCH"
   fi
   curl -fsSL https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter/-/archive/${MAGGOTUBA_ADAPTER_BRANCH}/MaggotUBA-adapter-${MAGGOTUBA_ADAPTER_BRANCH}.tar.gz | tar zxv
   mv MaggotUBA-adapter-${MAGGOTUBA_ADAPTER_BRANCH} MaggotUBA #-adapter
@@ -197,39 +345,46 @@ else
 fi
 # setting JULIA_PROJECT may not be necessary at this point
 export JULIA_PROJECT=$(realpath TaggingBackends)
-(cd MaggotUBA && activate && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"' || true) && poetry install -v)
+
+if [ -z "$internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES" ]; then
+  if [ "`uname`" = "Darwin" ]; then
+    # PyTorch requirements in the requirements.txt file are Cuda-based, suitable for Windows and Linux
+    # while alternative libraries are used on macOS
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
+  else
+    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=0
+  fi
+fi
+if [ "$internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES" = "0" ]; then
+  (cd MaggotUBA && activate && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"' || true) && poetry install -v)
+else
+  (cd MaggotUBA && activate && poetry install -v)
+fi
 [ -d TaggingBackends ] && (cd MaggotUBA && activate && poetry remove taggingbackends && poetry add ../TaggingBackends)
 [ -d MaggotUBA-core ] && (cd MaggotUBA && activate && poetry remove maggotuba-core && poetry add ../MaggotUBA-core)
-(cd MaggotUBA && scripts/make_models.jl default)
+(cd MaggotUBA && scripts/make_models.jl default) # julia version does not matter here
 
 mkdir -p "$BIN_DIR"
 cat <<EOF >"$BIN_DIR"/larvatagger
 #!/usr/bin/env bash
 
+if command -v juliaup &>/dev/null; then
+  PREVIOUS_CHANNEL=\`juliaup status | grep '*' | cut -d'*' -f2 | cut -d\\  -f3\`
+  juliaup default $JULIA_VERSION &>/dev/null
+fi
+
 JULIA_PROJECT="$(realpath "$LARVATAGGER_PATH/TaggingBackends")" BACKENDS_PATH="$LARVATAGGER_PATH" OPEN_BROWSER=1 "$LARVATAGGER_PATH/LarvaTagger.jl/scripts/larvatagger" \$@
+
+if command -v juliaup &>/dev/null; then
+  juliaup default \$PREVIOUS_CHANNEL &>/dev/null
+fi
 EOF
 
 fi
 
 chmod a+x "$BIN_DIR/larvatagger"
 
-if ! command -v larvatagger >/dev/null; then
-  # ~/.local/bin not in $PATH?
-  if [ "$SHELL" = "/bin/zsh" ]; then
-    rcfile=~/.zshrc
-  elif [ "$SHELL" = "/bin/bash" ]; then
-    if [ -f ~/.bashrc ]; then
-      rcfile=~/.bashrc
-    elif [ -f ~/.bash_profile ]; then
-      rcfile=~/.bash_profile
-    fi
-  fi
-  cat <<"EOF" >>$rcfile
-
-export PATH=$PATH:~/.local/bin
-EOF
-  export PATH=$PATH:~/.local/bin
-fi
+command -v larvatagger &>/dev/null || add_local_bin_to_path
 
 ##
 popd
diff --git a/scripts/larvatagger b/scripts/larvatagger
index db34a6ccdfeaa2001095bb07d9ec88d562b1fda7..5a0a65aed7cc2f537d6e5701784cba00852c3d00 100755
--- a/scripts/larvatagger
+++ b/scripts/larvatagger
@@ -23,10 +23,10 @@ open)
   shift
   ltargs=
   if [ -n "$BACKENDS_PATH" ]; then
-    ltargs="--backends=\"$BACKENDS_PATH\" $args"
+    ltargs="--backends=\"$BACKENDS_PATH\" $ltargs"
   fi
   if [ -n "$OPEN_BROWSER" ]; then
-    ltargs="--browser $args"
+    ltargs="--browser $ltargs"
   fi
   eval "\"$currentdir/larvatagger-gui.jl\" $jlargs\"$datapath\" $ltargs$@"
   ;;
@@ -52,7 +52,7 @@ import|merge|--version|-V)
 LarvaTagger
 
 Usage:
-  larvatagger open <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
+  larvatagger open [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
   larvatagger import <input-path> [<output-file>] [--id=<id>] [--framerate=<fps>] [--pixelsize=<μm>] [--overrides=<comma-separated-list>] [--default-label=<label>] [--manual-label=<label>] [--decode] [--copy-labels]
   larvatagger train <backend-path> <data-path> <model-instance> [--pretrained-model=<instance>] [--labels=<comma-separated-list>] [--sample-size=<N>] [--balancing-strategy=<strategy>] [--class-weights=<csv>] [--manual-label=<label>] [--layers=<N>] [--iterations=<N>] [--seed=<seed>] [--debug]
   larvatagger train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>] [--debug]
@@ -101,6 +101,7 @@ Commands:
 
   open      Launch the server-based GUI.
 
+    The optional positional argument <file-path> can also be the data root directory.
     Backends defined in LarvaTagger project root directory are automatically found. Other
     backend locations can be specified with the --backends argument.
 
diff --git a/scripts/larvatagger-gui.jl b/scripts/larvatagger-gui.jl
index 1bc01e8db4d536ee67bbd9cdbfb5144ce6954b03..36ac2abbfd3b0379a3e28d9d6bd84a44444d9d56 100755
--- a/scripts/larvatagger-gui.jl
+++ b/scripts/larvatagger-gui.jl
@@ -9,7 +9,9 @@ fi
 FLAGS=
 if [ "$1" = "--sysimage" -o "$1" = "-J" ]; then FLAGS="--sysimage $2 "; shift 2; fi
 if [ "${1:0:2}" = "-J" ]; then FLAGS="$1 "; shift; fi
-if [ -n "$1" -a -f "$1" ]; then FLAGS="$FLAGS -iq "; fi
+HELP=
+for i in "$@"; do if [ "$i" = "-h" -o "$i" = "--help" ]; then HELP=1; break; fi; done
+if [ -z $HELP ]; then FLAGS="$FLAGS -iq "; fi
 if [ -z "$JULIA" ]; then JULIA=julia; fi
     exec $JULIA --project="$PROJECT_DIR" --color=yes --startup-file=no $FLAGS\
     "${BASH_SOURCE[0]}" "$@"
diff --git a/scripts/larvatagger.sh b/scripts/larvatagger.sh
index a261c70bdf86db7854c25d1e7399a0d8c61f341c..3cf76b9281fb3a4978dd88f2b717148b0639bf20 100755
--- a/scripts/larvatagger.sh
+++ b/scripts/larvatagger.sh
@@ -45,14 +45,15 @@ if [ -z "$without_rm" ]; then
 fi
 
 if [ -z "$docker" ]; then
-  if docker --version &> /dev/null; then
-    docker=docker
-  else
+  docker=docker
+  if command -v podman &> /dev/null; then
     docker=podman
     if [ "$cmd" != "build" ]; then
       DOCKER_ARGS="${DOCKER_ARGS}--security-opt label=disable "
     fi
   fi
+else
+  echo "Using environment variable: docker= $docker"
 fi
 
 if [ -z "$LARVATAGGER_IMAGE" ]; then
@@ -61,6 +62,8 @@ LARVATAGGER_IMAGE=larvatagger
 else
 LARVATAGGER_IMAGE=flaur/larvatagger
 fi
+else
+echo "Using environment variable: LARVATAGGER_IMAGE= $LARVATAGGER_IMAGE"
 fi
 
 HOST_UID=$(id -u $USER)
@@ -104,7 +107,10 @@ DOCKER_ARGS="--target $TARGET $DOCKER_ARGS"
 fi
 if [ -z "$DOCKERFILE" ]; then
   DOCKERFILE=recipes/Dockerfile
+else
+  echo "Using environment variable DOCKERFILE= $DOCKERFILE"
 fi
+DOCKER_ARGS="-f \"$DOCKERFILE\" $DOCKER_ARGS"
 if [ "$BUILD" == "--dev" ]; then
 if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:dev"; fi
 PROJECT_ROOT=$(basename $(pwd))
@@ -112,7 +118,7 @@ cd ..
 DOCKER_BUILDKIT=1 $docker build -t "$LARVATAGGER_IMAGE" -f "$PROJECT_ROOT/recipes/Dockerfile.local" ${DOCKER_ARGS}.
 elif [ "$BUILD" == "--stable" ]; then
 if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:stable"; fi
-$docker build -t "$LARVATAGGER_IMAGE" -f recipes/Dockerfile ${DOCKER_ARGS}.
+$docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}.
 else
 if ! [[ "$LARVATAGGER_IMAGE" == *:* ]]; then LARVATAGGER_IMAGE="${LARVATAGGER_IMAGE}:latest"; fi
 if [ -z "$LARVATAGGER_BRANCH" ]; then
@@ -122,12 +128,18 @@ if [ -z "$LARVATAGGER_BRANCH" ]; then
     echo "Deprecation notice: LARVATAGGER_DEFAULT_BRANCH has been renamed LARVATAGGER_BRANCH"
     LARVATAGGER_BRANCH=$LARVATAGGER_DEFAULT_BRANCH
   fi
+else
+  echo "Using environment variable: LARVATAGGER_BRANCH= $LARVATAGGER_BRANCH"
 fi
 if [ -z "$TAGGINGBACKENDS_BRANCH" ]; then
   TAGGINGBACKENDS_BRANCH=$LARVATAGGER_BRANCH
+else
+  echo "Using environment variable: TAGGINGBACKENDS_BRANCH= $TAGGINGBACKENDS_BRANCH"
 fi
 DOCKER_ARGS="--build-arg TAGGINGBACKENDS_BRANCH=$TAGGINGBACKENDS_BRANCH $DOCKER_ARGS"
-$docker build -t "$LARVATAGGER_IMAGE" -f $DOCKERFILE ${DOCKER_ARGS}--build-arg BRANCH=$LARVATAGGER_BRANCH .
+DOCKER_BUILD="$docker build -t "$LARVATAGGER_IMAGE" ${DOCKER_ARGS}--build-arg BRANCH=$LARVATAGGER_BRANCH ."
+echo $DOCKER_BUILD
+eval $DOCKER_BUILD
 fi
 ;;
 
@@ -137,6 +149,7 @@ if [ -z "$LARVATAGGER_PORT" -o "$LARVATAGGER_PORT" == "9284" ]; then
 DOCKER_ARGS="-p 9284:9284 $DOCKER_ARGS"
 TAGGER_ARGS=
 else
+echo "Using environment variable: LARVATAGGER_PORT= $LARVATAGGER_PORT"
 DOCKER_ARGS="-p $LARVATAGGER_PORT:$LARVATAGGER_PORT $DOCKER_ARGS"
 TAGGER_ARGS="--port=$LARVATAGGER_PORT"
 fi
@@ -150,7 +163,7 @@ RUN_ARGS="$RUN_ARGS -v \"$parentdir\":/data"
 backend=MaggotUBA
 while [ -n "$1" -a "$1" = "--external-instance" ]; do
 instance=$2; shift 2
-if [ -z "$(which realpath)" ]; then
+if ! command -v realpath &>/dev/null; then
 echo "realpath: command not found"
 echo "on macOS: brew install coreutils"
 exit 1
@@ -269,7 +282,7 @@ fi
 done
 
 if [ -n "$tagger_path" ]; then
-if [ -z "$(which realpath)" ]; then
+if ! command -v realpath &>/dev/null; then
 echo "realpath: command not found"
 echo "on macOS: brew install coreutils"
 exit 1
diff --git a/src/LarvaTagger.jl b/src/LarvaTagger.jl
index b3484f945e8f30276e0fd401583c7cc814e09862..00d344cc349bbf9d23082cd6168c350f5e7cb788 100644
--- a/src/LarvaTagger.jl
+++ b/src/LarvaTagger.jl
@@ -3,13 +3,13 @@ module LarvaTagger
 using PlanarLarvae, PlanarLarvae.Datasets, PlanarLarvae.Formats
 using ObservationPolicies
 using TidyObservables
+using NyxWidgets, NyxWidgets.Players, NyxWidgets.FileBrowsers, NyxWidgets.FilePickers
+import NyxWidgets.Base: lowerdom, dom_id, dom_selector
 
 using Logging
-Logging.disable_logging(Logging.Warn) # prior to loading old libraries
-using JSServe, WGLMakie
-using JSServe: evaljs, onjs
+using Bonito, WGLMakie
+using Bonito: evaljs, onjs
 using Makie
-Logging.disable_logging(Logging.Debug) # restore default
 using Format
 using Colors
 using StaticArrays
diff --git a/src/Taggers.jl b/src/Taggers.jl
index 77987c966c9c40edbd7bd80b11e4c021e42100b3..b66ef40de5e133bc456ed95eedb44ec27a99e826 100644
--- a/src/Taggers.jl
+++ b/src/Taggers.jl
@@ -17,17 +17,37 @@ end
 Tagger(backend_dir, model_instance) = Tagger(string(backend_dir), string(model_instance))
 
 function isolate(tagger)
-    mkpath(joinpath(tagger.backend_dir, "data", "raw"))
-    rawdatadir = mktempdir(joinpath(tagger.backend_dir, "data", "raw"); cleanup=false)
+    rawdatadir = joinpath(tagger.backend_dir, "data", "raw")
+    mkdir(rawdatadir)
+    rawdatadir = mktempdir(rawdatadir; cleanup=false)
     Tagger(tagger.backend_dir, tagger.model_instance, basename(rawdatadir),
            tagger.output_filenames)
 end
 
-isbackend(path) = isdir(joinpath(path, "models")) &&
-                  isfile(joinpath(path, "pyproject.toml")) &&
-                  isfile(joinpath(path, "poetry.lock"))
+const envdir = get(ENV, "VENV_DIR", "venv")
+
+function isbackend(path)
+    bindir = Sys.iswindows() ? "Scripts" : "bin"
+    return isdir(joinpath(path, "models")) &&
+           isfile(joinpath(path, "pyproject.toml")) &&
+           (isfile(joinpath(path, "poetry.lock")) ||
+            (isfile(joinpath(path, envdir, bindir, "python")) &&
+             isfile(joinpath(path, envdir, bindir, "tagging-backend"))))
+end
 isbackend(tagger::Tagger) = isbackend(tagger.backend_dir)
 
+function tagging_backend_command(path)
+    bindir = Sys.iswindows() ? "Scripts" : "bin"
+    python = joinpath(path, envdir, bindir, "python")
+    tagging_backend_script = joinpath(path, envdir, bindir, "tagging-backend")
+    if isfile(python) && isfile(tagging_backend_script)
+        `$python $tagging_backend_script`
+    else
+        `poetry run tagging-backend`
+    end
+end
+tagging_backend_command(tagger::Tagger) = tagging_backend_command(tagger.backend_dir)
+
 modeldir(tagger::Tagger) = joinpath(tagger.backend_dir, "models", tagger.model_instance)
 
 datadir(tagger::Tagger, stage::String) = joinpath(tagger.backend_dir, "data", stage,
@@ -149,7 +169,8 @@ end
 function pull(tagger::Tagger, dest_dir::String)
     proc_data_dir = datadir(tagger, "processed")
     isdir(proc_data_dir) || throw("no processed data directory found")
-    dest_dir = realpath(dest_dir) # strip end slash
+    # dest_dir can be empty on macOS
+    dest_dir = isempty(dest_dir) ? pwd() : realpath(dest_dir) # strip end slash
     dest_files = String[]
     for (parent, _, files) in walkdir(proc_data_dir)
         if !isempty(files)
@@ -205,56 +226,34 @@ function parsekwargs!(args, kwargs)
     return args
 end
 
-function train(tagger::Tagger; pretrained_instance=nothing, kwargs...)
-    args = ["--model-instance", tagger.model_instance]
-    if !isnothing(pretrained_instance)
-        push!(args, "--pretrained-model-instance")
-        push!(args, pretrained_instance)
-    end
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
+function run(tagger, switch, kwargs)
+    kwargs = Dict{Symbol, Any}(kwargs)
+    kwargs[:model_instance] = tagger.model_instance
+    kwargs[:sandbox] = tagger.sandbox
+    args = Any[]
     parsekwargs!(args, kwargs)
-    ret = run(Cmd(`poetry run tagging-backend train $(args)`; dir=tagger.backend_dir))
+    cmd = tagging_backend_command(tagger)
+    Base.run(Cmd(`$cmd $switch $args`; dir=tagger.backend_dir))
+end
+
+function train(tagger::Tagger; pretrained_instance=None, kwargs...)
+    kwargs = Dict{Symbol, Any}(kwargs)
+    kwargs[:pretrained_model_instance] = pretrained_instance
+    ret = run(tagger, "train", kwargs)
     @assert isdir(modeldir(tagger))
     return ret
 end
 
-function predict(tagger::Tagger; kwargs...)
-    args = ["--model-instance", tagger.model_instance]
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
-    parsekwargs!(args, kwargs)
-    run(Cmd(`poetry run tagging-backend predict $(args)`; dir=tagger.backend_dir))
-end
+predict(tagger::Tagger; kwargs...) = run(tagger, "predict", kwargs)
 
 function finetune(tagger::Tagger; original_instance=nothing, kwargs...)
-    args = ["--model-instance", tagger.model_instance]
-    if !isnothing(original_instance)
-        push!(args, "--original-model-instance")
-        push!(args, original_instance)
-    end
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
-    parsekwargs!(args, kwargs)
-    ret = run(Cmd(`poetry run tagging-backend finetune $(args)`; dir=tagger.backend_dir))
+    kwargs = Dict{Symbol, Any}(kwargs)
+    kwargs[:original_model_instance] = original_instance
+    ret = run(tagger, "finetune", kwargs)
     @assert isdir(modeldir(tagger))
     return ret
 end
 
-function embed(tagger::Tagger; kwargs...)
-    args = ["--model-instance", tagger.model_instance]
-    if !isnothing(tagger.sandbox)
-        push!(args, "--sandbox")
-        push!(args, tagger.sandbox)
-    end
-    parsekwargs!(args, kwargs)
-    run(Cmd(`poetry run tagging-backend embed $(args)`; dir=tagger.backend_dir))
-end
+embed(tagger::Tagger; kwargs...) = run(tagger, "embed", kwargs)
 
 end # module
diff --git a/src/cli.jl b/src/cli.jl
index c0a25fbc05090bc4e0ee55eb5c23388c8b7e62d6..23e8fdb34ca5ea3b672fe5da51891cb9c72a8d57 100644
--- a/src/cli.jl
+++ b/src/cli.jl
@@ -9,7 +9,7 @@ using .Toolkit
 usage = """Larva Tagger.
 
 Usage:
-  larvatagger.jl open <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
+  larvatagger.jl open [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
   larvatagger.jl import <input-path> [<output-file>] [--id=<id>] [--framerate=<fps>] [--pixelsize=<μm>] [--overrides=<comma-separated-list>] [--default-label=<label>] [--manual-label=<label>] [--decode] [--copy-labels]
   larvatagger.jl train <backend-path> <data-path> <model-instance> [--pretrained-model=<instance>] [--labels=<comma-separated-list>] [--sample-size=<N>] [--balancing-strategy=<strategy>] [--class-weights=<csv>] [--manual-label=<label>] [--layers=<N>] [--iterations=<N>] [--seed=<seed>] [--debug]
   larvatagger.jl train <backend-path> <data-path> <model-instance> --fine-tune=<instance> [--balancing-strategy=<strategy>] [--manual-label=<label>] [--iterations=<N>] [--seed=<seed>] [--debug]
@@ -58,6 +58,7 @@ Commands:
 
   open      Launch the server-based GUI.
 
+    The optional positional argument <file-path> can also be the data root directory.
     Backends defined in LarvaTagger project root directory are automatically found. Other
     backend locations can be specified with the --backends argument.
 
diff --git a/src/cli_open.jl b/src/cli_open.jl
index f46a52a26e98d40c4effd6775a2c873f2ffbf8aa..56ab8b563ed874cd35e429b3623872a417399aea 100644
--- a/src/cli_open.jl
+++ b/src/cli_open.jl
@@ -2,14 +2,14 @@ module GUI
 
 using DocOpt
 using LarvaTagger
-using JSServe: JSServe, Server
+using Bonito: Bonito, Server
 
 export main
 
 usage = """LarvaTagger.jl - launch the server-based GUI.
 
 Usage:
-  larvatagger-gui.jl <file-path> [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
+  larvatagger-gui.jl [<file-path>] [--backends=<path>] [--port=<number>] [--quiet] [--viewer] [--browser] [--view-factor=<real>] [--manual-label=<label>]
   larvatagger-gui.jl -h | --help
 
 Options:
@@ -22,6 +22,7 @@ Options:
   --view-factor=<real>    Scaling factor for the larva views; default is 2 on macOS, 1 elsewhere.
   --manual-label=<label>  Secondary label for manually labelled data [default: edited].
 
+The optional positional argument <file-path> can also be the data root directory.
 Backends defined in LarvaTagger project root directory are automatically found. Other
 backend locations can be specified with the --backends argument.
 """
@@ -47,17 +48,25 @@ function main(args=ARGS; exit_on_error=false)
 
     verbose = !parsed_args["--quiet"]
     infile = parsed_args["<file-path>"]
-    if !isfile(infile)
-        @error "File not found; did you specify a file path?" infile
-        exit()
+    if isempty(infile)
+        infile = nothing
+    elseif !isfile(infile)
+        if isdir(infile)
+            dataroot = infile
+            infile = nothing
+            cd(dataroot)
+        else
+            @error "File not found; did you specify a file path?" infile
+            exit()
+        end
     end
 
     kwargs = Dict{Symbol, Any}()
     viewfactor = parsed_args["--view-factor"]
     if !isnothing(viewfactor)
         kwargs[:viewfactor] = parse(Float64, viewfactor)
-    elseif Sys.isapple()
-        kwargs[:viewfactor] = 2
+    # elseif Sys.isapple()
+    #     kwargs[:viewfactor] = 2
     end
 
     if parsed_args["--viewer"]
@@ -75,7 +84,7 @@ function main(args=ARGS; exit_on_error=false)
     port = isnothing(port) ? 9284 : parse(Int, port)
     server = Server(app, "0.0.0.0", port)
     if parsed_args["--browser"]
-        JSServe.openurl("http://127.0.0.1:$(port)")
+        Bonito.HTTPServer.openurl("http://127.0.0.1:$(port)")
     end
     if verbose
         @info "The server is ready at http://127.0.0.1:$(port)"
diff --git a/src/controllers.jl b/src/controllers.jl
index bf5906beb44e138f07047e0b5b5149c79ef70ccd..0482579159b7448a9deb1759c8bec5c9ae96a2ea 100644
--- a/src/controllers.jl
+++ b/src/controllers.jl
@@ -170,7 +170,7 @@ struct LarvaController
     model::Dict{<:Integer, LarvaModel}
     tag_lut::AbstractObservable{<:TagLUT}
     activelarva::AbstractObservable{<:ActiveLarva}
-    player::AbstractAnimator
+    player::AbstractPlayer
     # could make LarvaController mutable instead...
     boundingbox::Ref{<:NTuple{4, <:AbstractFloat}}
     medianlarvasize::AbstractFloat
@@ -179,7 +179,7 @@ end
 function LarvaController(main::ControllerHub,
         model::Dict{<:Integer, LarvaModel},
         tag_lut::AbstractObservable{<:TagLUT},
-        player::AbstractAnimator)
+        player::AbstractPlayer)
     activelarva = Observable{ActiveLarva}(nothing)
     boundingbox = Ref((0.0, 10.0, 0.0, 10.0))
     mediansize = medianlarvasize(model)
@@ -190,7 +190,7 @@ function LarvaController(main::ControllerHub,
         model::Dict{<:Integer, LarvaModel},
         tag_lut::TagLUT,
         times::Vector{PlanarLarvae.Time})
-    player = playcontroller(times)
+    player = timecontroller(times)
     LarvaController(main, model, Observable(tag_lut), player)
 end
 
@@ -216,7 +216,7 @@ setboundingbox!(c::LarvaController, bb) = (c.boundingbox[] = bb)
 getmedianlarvasize(c) = getmedianlarvasize(gethub(c)[:larva])
 getmedianlarvasize(c::LarvaController) = c.medianlarvasize
 
-getplayer(c::AbstractAnimator) = c
+getplayer(c::AbstractPlayer) = c
 getplayer(c::LarvaController) = c.player
 getplayer(c) = getplayer(gethub(c))
 getplayer(hub::ControllerHub) = haskey(hub, :player) ? hub[:player] : getplayer(hub[:larva])
@@ -307,9 +307,9 @@ end
 setbounds!(view::Axis, lb, ub) = limits!(view, lb[1], ub[1], lb[2], ub[2])
 
 settimebounds!(c, larva) = settimebounds!(getplayer(c), larva)
-settimebounds!(::AbstractAnimator, _) = nothing
+settimebounds!(::AbstractPlayer, _) = nothing
 unsettimebounds!(c) = unsettimebounds!(getplayer(c))
-unsettimebounds!(::AbstractAnimator) = nothing
+unsettimebounds!(::AbstractPlayer) = nothing
 
 function slave(master::Observable, policy::ObservationPolicy=IndependentObservables())
     newobservable(policy, master)
@@ -362,8 +362,6 @@ function slave(master::LarvaController,
                     master.medianlarvasize)
 end
 
-stop!(controller::LarvaController) = stop!(controller.player)
-
 # history
 
 transactions(c) = get!(gethub(c), :transactions, Transaction[])
diff --git a/src/editor.jl b/src/editor.jl
index 61c33882a40150683a38145aa3c2ab10f7f9e8c2..abf8b403d3f060476ffa83f071b7b1e165e7cbf9 100644
--- a/src/editor.jl
+++ b/src/editor.jl
@@ -10,13 +10,12 @@ struct EditorView
     twooptiondialog::TwoOptionDialog
 end
 
-function JSServe.jsrender(session::Session, ev::EditorView)
+function Bonito.jsrender(session::Session, ev::EditorView)
     r(session, LarvaTaggerJS)
-    r(session, LarvaTaggerCSS)
     r(session, LoadAwesomeCSS)
-    r(session, JSServe.TailwindCSS)
+    r(session, Bonito.TailwindCSS)
     evaljs(session, js"window.addEventListener('beforeunload', LarvaTagger.confirmDialog)")
-    r(session,
+    dom = r(session,
       DOM.div(ev.larvaviewer,
               cp(ControlPanel(ev.tagfilter; id="tag-panel", title="Tags"),
                  ControlPanel(ev.larvafilter; id="larva-panel", title="Tracks"),
@@ -25,6 +24,8 @@ function JSServe.jsrender(session::Session, ev::EditorView)
               ev.loadanimation,
               ev.twooptiondialog;
               class="flex flex-row"))
+    r(session, LarvaTaggerCSS)
+    return dom
 end
 
 projectdir = dirname(Base.active_project())
@@ -33,16 +34,17 @@ function larvaeditor(path=nothing;
         allow_multiple_tags::Union{Nothing, Bool}=nothing,
         backend_directory::AbstractString=projectdir,
         manualtag::Union{Nothing, String, Symbol}="edited",
+        title="LarvaTagger",
         kwargs...)
 
     # to (re-)load a file, the app is reloaded with the filepath as sole information
     # from previous session
     input = Ref{Union{Nothing, String}}(path)
 
-    App() do session::Session
+    App(title=title) do session::Session
 
         controller = ControllerHub()
-        # used for method dispatch (here `Session` is taken to represent JSServe)
+        # used for method dispatch (here `Session` is taken to represent Bonito)
         controller[:frontend] = Session
 
         tryopenfile(controller, input)
@@ -59,7 +61,7 @@ function larvaeditor(path=nothing;
                             loadanimation(controller),
                             twooptiondialog(controller))
 
-        dom = JSServe.jsrender(session, editor)
+        dom = Bonito.jsrender(session, editor)
 
         turn_load_animation_off(controller)
 
diff --git a/src/files.jl b/src/files.jl
index 273e385e54911b683ebd1532a84c8e14b5dc7038..397c78160d2df38634a5202d36675cc61b279c54 100644
--- a/src/files.jl
+++ b/src/files.jl
@@ -1,4 +1,5 @@
 
+# deprecated; may be replaced by FileBrowsers.Model.FileBrowser
 struct WorkingDirectory
     controller
     root::String # could be hidden (not stored)
@@ -20,40 +21,40 @@ function workingdir(controller, root::String, path::String=""; secure::Bool=true
         startswith(path, "..") && throw(ArgumentError("path not below root: $path"))
     end
     wd = WorkingDirectory(controller, root, path)
-    on(wd.path, Val(1)) do newpath
-        try
-            if isempty(newpath)
-                newpath = "."
-            elseif isabspath(newpath)
-                fullpath = newpath
-                newpath = relpath(newpath, root)
-            end
-            fullpath = joinpath(root, newpath)
-            if secure
-                fullpath = realpath(fullpath)
-            end
-            if startswith(relpath(fullpath, root), "..")
-                @logmsg SecurityAlert "Path outside of chroot" path=newpath client=identifyclient(controller)
-                throw(Exception)
-            elseif !isdir(fullpath)
-                @debug "Path is not a directory" path=newpath
-                tryopenfile(controller, fullpath; reload=true)
-                throw(Exception)
-            end
-            return Validate(newpath)
-        catch
-            return Invalidate()
-        end
-    end
-    on(wd.path.model) do dir
-        @assert !isempty(dir)
-        content = [entry for entry in readdir(joinpath(root, dir))
-                   if entry[1] != '.']
-        if dir != "."
-            pushfirst!(content, "..")
-        end
-        wd.content[] = content
-    end
+    # on(wd.path, Val(1)) do newpath
+    #     try
+    #         if isempty(newpath)
+    #             newpath = "."
+    #         elseif isabspath(newpath)
+    #             fullpath = newpath
+    #             newpath = relpath(newpath, root)
+    #         end
+    #         fullpath = joinpath(root, newpath)
+    #         if secure
+    #             fullpath = realpath(fullpath)
+    #         end
+    #         if startswith(relpath(fullpath, root), "..")
+    #             @logmsg SecurityAlert "Path outside of chroot" path=newpath client=identifyclient(controller)
+    #             throw(Exception)
+    #         elseif !isdir(fullpath)
+    #             @debug "Path is not a directory" path=newpath
+    #             tryopenfile(controller, fullpath; reload=true)
+    #             throw(Exception)
+    #         end
+    #         return Validate(newpath)
+    #     catch
+    #         return Invalidate()
+    #     end
+    # end
+    # on(wd.path.model) do dir
+    #     @assert !isempty(dir)
+    #     content = [entry for entry in readdir(joinpath(root, dir))
+    #                if entry[1] != '.']
+    #     if dir != "."
+    #         pushfirst!(content, "..")
+    #     end
+    #     wd.content[] = content
+    # end
     return wd
 end
 
@@ -483,3 +484,25 @@ function getoutputfile(controller)
     return outputfile
 end
 
+const valid_inputfile_formats = (".mat", ".spine", ".outline", ".csv", ".label", ".json",
+                                 ".labels")
+
+function saveinputfile(path, content)
+    parts = splitext(path)
+    if length(parts) == 2 && parts[2] in valid_inputfile_formats
+        if ispath(path)
+            if isfile(path)
+                @warn "File already exists" path
+            else
+                @error "File already exists and is not a regular file" path
+                return
+            end
+        end
+        open(path, "w") do f
+            write(f, content)
+        end
+        Taggers.check_permissions(path)
+    else
+        @error "File format not admitted" fileparts=parts
+    end
+end
diff --git a/src/larvatagger.css b/src/larvatagger.css
index 6d9bfd011eaabccc09a2cf2f5a04fdf7ad1cdd38..0ead1806f7f3b3194136418789a3d16ca34ad755 100644
--- a/src/larvatagger.css
+++ b/src/larvatagger.css
@@ -58,6 +58,11 @@ canvas {
 	background: var(--theme-main-color);
 }
 
+.cp-tab .cp-tab-switch:checked ~ .control-panel .nyx-icon-button div {
+  /* restore default style, otherwise overridden by an above rule */
+  background-color: var(--nyx-icon-fill-color);
+}
+
 div.scrollable {
 	position: relative;
 	margin-right: 0;
@@ -87,16 +92,18 @@ table {
 	width: 30rem;
 }
 
+.nyx-filebrowser {
+  width: unset;
+  box-shadow: unset;
+  padding: unset;
+  margin: unset;
+}
+
 #larva-panel th {
 	padding-left: 1rem;
 	padding-right: 1rem;
 }
 
-#filemenu-panel input:first-child {
-	border-width: 0.0625rem;
-	padding: 0.2rem;
-}
-
 #metadata-panel input {
 	border-width: 0.0625rem;
 	margin: 0.0625rem 0.0625rem 0 0;
@@ -175,12 +182,9 @@ input[type=checkbox] {
 	border-width: 8px;
 }
 
-.player button {
+.nyx-player {
 	margin-left: 0.25rem;
 }
-.player input[type=range]:focus {
-	outline: none;
-}
 
 #tag-selector {
 	width: 100%;
diff --git a/src/larvatagger.js b/src/larvatagger.js
index 51b1478c28e87ce5887df5b73f831020d3d73a1c..d65fb7dfd6428106854c18bced86df6aa57e887a 100644
--- a/src/larvatagger.js
+++ b/src/larvatagger.js
@@ -1,8 +1,4 @@
 const LarvaTagger = (function () {
-	function toggle(jlstate) {
-		const buttonstate = JSServe.get_observable(jlstate);
-		JSServe.update_obs(jlstate, !buttonstate);
-	}
 
 	function timeSlider(element, input, stepmin, stepmax) {
 		const cooldown = 0.1; // in seconds
@@ -24,21 +20,21 @@ const LarvaTagger = (function () {
 				slavepos = heldpos;
 				heldpos = null;
 				// check bounds
-				mastermax = JSServe.get_observable(stepmax);
+				mastermax = stepmax.value;
 				if (mastermax < slavepos) {
 					slavepos = mastermax;
 					element.value = String(slavepos);
 				} else {
-					mastermin = JSServe.get_observable(stepmin);
+					mastermin = stepmin.value;
 					if (slavepos < mastermin) {
 						slavepos = mastermin;
 						element.value = String(slavepos);
 					}
 				}
 				// update
-				masterpos = JSServe.get_observable(input);
+				masterpos = input.value;
 				if (slavepos != masterpos) {
-					JSServe.update_obs(input, slavepos);
+					input.notify(slavepos);
 				}
 				// start cooldown
 				window.setTimeout(this.timeout, cooldown);
@@ -55,7 +51,7 @@ const LarvaTagger = (function () {
 	function focusOnTimeSlider(view) {
 		if (view === undefined)
 			view = document.getElementById("trackviewer");
-		view.querySelector("input[type=range]").focus()
+		//view.querySelector("input[type=range]").focus()
 	}
 
 	function discardLarvaEdits(checkbox, observer, label) {
@@ -64,7 +60,7 @@ const LarvaTagger = (function () {
 		} else {
 			var answer = confirm(`Discard changes for larva ${label}?`);
 			if (answer) {
-				JSServe.update_obs(observer, false);
+				observer.notify(false);
 			} else {
 				checkbox.checked = true;
 			}
@@ -79,7 +75,7 @@ const LarvaTagger = (function () {
 			const includeallcheckbox = document.getElementById('includeall');
 			includeallcheckbox.checked = false;
 		}
-		JSServe.update_obs(jlstate, jsstate.checked);
+		jlstate.notify(jsstate.checked);
 	}
 
 	function includeAllLarvae(jsstate, jlstate, button) {
@@ -91,7 +87,7 @@ const LarvaTagger = (function () {
 			button = document.getElementById('saveas');
 		}
 		button.disabled = !jsstate.checked;
-		JSServe.update_obs(jlstate, jsstate.checked);
+		jlstate.notify(jsstate.checked);
 	}
 
 	function insertNewTag(table, html) {
@@ -111,22 +107,24 @@ const LarvaTagger = (function () {
 		jlTagSelection = obs;
 	}
 	function toggleTagAtPointer(event) {
-		var that = event.target,
-		    scroll = that.parentElement.scrollTop;
-		event.preventDefault();
-		that.selected = !that.selected;
-		setTimeout(function(){that.parentElement.scrollTop = scroll;}, 0);
-		JSServe.update_obs(jlTagSelection, that.value);
+    if (event !== undefined) {
+      var that = event.target,
+          scroll = that.parentElement.scrollTop;
+      event.preventDefault();
+      that.selected = !that.selected;
+      setTimeout(function(){that.parentElement.scrollTop = scroll;}, 0);
+      jlTagSelection.notify(that.value);
+    }
 		return false;
 	}
 
 	function setOutputFilename(obs) {
-		var defaultfilepath = JSServe.get_observable(obs);
+		var defaultfilepath = obs.value;
 		if (defaultfilepath === null) {
 			defaultfilepath = "{yyyymmdd_HHMMSS}.label";
 		}
 		let filepath = prompt("File path: ", defaultfilepath);
-		JSServe.update_obs(obs, filepath);
+		obs.notify(filepath);
 	}
 
 	function updateSelectOptions(selectElement, selectOptions, jlObserver) {
@@ -139,7 +137,7 @@ const LarvaTagger = (function () {
 			option.text = selectOptions[i];
 			if (jlObserver !== undefined) {
 				option.ondblclick = () => {
-					JSServe.update_obs(jlObserver, selectOptions[i]);
+					jlObserver.notify(selectOptions[i]);
 				};
 			}
 			selectElement.append(option);
@@ -154,7 +152,7 @@ const LarvaTagger = (function () {
 			let files = Array.from(input.files);
 			if (files.length > 0) {
 				console.log(files);
-				JSServe.update_obs(jlObserver, files);
+				jlObserver.notify(files);
 			}
 		};
 		input.click();
@@ -179,7 +177,6 @@ const LarvaTagger = (function () {
 	}
 
 	return {
-		toggle,
 		timeSlider,
 		focusOnTimeSlider,
 		discardLarvaEdits,
diff --git a/src/models.jl b/src/models.jl
index 2c537415dc328090bbf2133ac7e5b982be09c88c..9910a28cadded6c41161031c487c93d567b6fc9e 100644
--- a/src/models.jl
+++ b/src/models.jl
@@ -310,7 +310,8 @@ end
 
 medianlarvasize(larvae::Dict; n::Int=100) = medianlarvasize(collect(values(larvae)); n=n)
 
-function medianlarvasize(larvae::Vector{LarvaModel}; n::Int=100)
+function medianlarvasize(larvae::Vector{LarvaModel}; n::Int=100)::Float32
+    isempty(larvae) && return 0.0f0
     larvae = Random.shuffle(larvae)
     sizes = Float64[]
     sizehint!(sizes, n)
@@ -324,7 +325,8 @@ function medianlarvasize(larvae::Vector{LarvaModel}; n::Int=100)
     return median(sizes)
 end
 
-function simultaneouslarvae(larvae)
+function simultaneouslarvae(larvae)::Int
+    isempty(larvae) && return 0
     laststep = maximum([larva.alignedsteps[end] for larva in values(larvae)])
     n = 0
     for step in 1:20:laststep
diff --git a/src/players.jl b/src/players.jl
index ff1f5fd5ae8ed5fa98a213c2aee584ace3fc1903..0cf3f3a05f6717c2d43db81e842cd13c6736f8b0 100644
--- a/src/players.jl
+++ b/src/players.jl
@@ -1,250 +1,71 @@
 
-abstract type AbstractAnimator end
-
-function stop! end
-function play! end
-function pause! end
-function playpause! end
-function isplaying end
-
-"""
-Plans are to make `Animator` not mutable; don't rely on its mutability.
-"""
-mutable struct Animator <: AbstractAnimator
-    times::Vector{PlanarLarvae.Time}
-    timestep::Observable
-    speed::Observable
-    isplaying::Observable
-end
-
-function playcontroller end
-
-function bind! end
-
-function playercontrols end
-
-## implementation
-
 const O{T} = Union{T, Observable{T}}
+using PlanarLarvae: Time
 asobservable(arg) = Observable(arg)
 asobservable(arg::Observable) = arg
 
-Animator(times::Vector{PlanarLarvae.Time},
-         step::O{Int}=1,
-         speed::O{<:AbstractFloat}=1.0,
-        ) = Animator(times,
-                     asobservable(step),
-                     asobservable(speed),
-                     Observable(false),
-                    )
-
-isplaying(controller::AbstractAnimator) = controller.isplaying[]
-isplaying!(controller::AbstractAnimator, state::Bool) = (controller.isplaying[] = state)
-
-function playnow!(controller::AbstractAnimator)
-    @info "Playing" speed=controller.speed[]
-    ts = controller.times
-    step = controller.timestep[]
-    stop = length(ts)
-    #
-    lag, n = 0, 0
-    clock_t = time()
-    #
-    while step<stop
-        t, t′ = ts[step], ts[step + 1]
-        dt = t′ - t
-        dt′= dt / controller.speed[] - lag
-        0 < dt′ && sleep(dt′)
-        #
-        step′ = ObservationPolicies.input(controller.timestep)[]
-        if isplaying(controller)
-            if step == step′
-                step′ = step + 1
-                @async controller.timestep[] = step′
-            end
-        else
-            break
-        end
-        # adjust lag correction
-        clock_t′ = time()
-        lag′= clock_t′- clock_t - dt
-        lag = (lag * n + lag′) / (n + 1)
-        @debug "Step timing" expected=dt actual=clock_t′-clock_t adjustment=lag
-        #
-        step = step′
-        clock_t = clock_t′
-        n += 1
-    end
-    @info "Pause/stop"
-    pause!(controller)
-end
-
-function bind!(controller::Animator)
-    on(controller.isplaying) do playnow
-        if playnow
-            @async playnow!(controller)
-        end
-    end
-    return controller
-end
-
-function bind!(controller::Animator, timeslider::Makie.Slider)
-    controller.timestep = timeslider.selected_index
-    #bind!(controller)
-end
-
-function bind!(controller::Animator, scene)
-    on(events(scene).window_open) do isopen
-        if !isopen
-            stop!(controller)
-        end
-    end
-    return controller
-end
-
-play!(controller::AbstractAnimator) = (controller.isplaying[] = true)
-pause!(controller::AbstractAnimator) = (controller.isplaying[] = false)
-playpause!(controller::AbstractAnimator) = (controller.isplaying[] = !controller.isplaying[])
-stop!(controller) = pause!(controller)
-
-function playcontroller(times; step::O{Int}=1, speed::O{<:AbstractFloat}=1.0)
-    controller = Animator(times, step, speed)
-    bind!(controller)
-    return controller
-end
-
-gettime(controller::AbstractAnimator, step::Int) = controller.times[step]
-
-struct TimeController <: AbstractAnimator
-    times::Vector{Float64}
-    time::AbstractObservable{Float64}
-    timestep::AbstractObservable{Int64}
+struct TimeController <: AbstractPlayer
+    player::AbstractPlayer
     stepmin::AbstractObservable{Int64}
     stepmax::AbstractObservable{Int64}
     boundreached::AbstractObservable{Bool}
-    playing::AbstractObservable{Bool}
-    speed::AbstractObservable{Float64}
 end
 
-isplaying(controller::TimeController) = controller.playing[]
-play!(controller::TimeController) = (controller.playing[] = true)
-pause!(controller::TimeController) = (controller.playing[] = false)
-playpause!(controller::TimeController) = (controller.playing[] = !controller.playing[])
-
-function stepforward!(controller::TimeController)
-    step = controller.timestep[]
-    if step < controller.stepmax[]
-        controller.timestep[] = step + 1
-    elseif isplaying(controller)
-        pause!(controller)
-    end
+function TimeController(player::AbstractPlayer)
+    stepmin = Observable(min(player))
+    stepmax = Observable(max(player))
+    TimeController(player, stepmin, stepmax, Observable(false))
 end
 
-function stepbackward!(controller::TimeController)
-    step = controller.timestep[]
-    if controller.stepmin[] < step
-        controller.timestep[] = step - 1
-    elseif isplaying(controller)
-        pause!(controller)
-    end
+Players.timestep(controller::TimeController) = timestep(controller.player)
+Players.isplaying(controller::TimeController) = isplaying(controller.player)
+Base.min(controller::TimeController) = controller.stepmin[]
+Base.max(controller::TimeController) = controller.stepmax[]
+Players.timestamps(controller::TimeController) = timestamps(controller.player)
+Players.speed(controller::TimeController) = Players.speed(controller.player)
+Players.repeat(controller::TimeController) = Players.repeat(controller.player)
+Players.Model.task(controller::TimeController) = Players.Model.task(controller.player)
+function Players.Model.task!(controller::TimeController, task)
+    Players.Model.task!(controller.player, task)
 end
 
 function timecontroller(times::Vector{Float64}; speed=1.0)
-    issorted(times) || throw(ArgumentError("timestamps are not sorted"))
-    initialstep = 1
-    timestep = newobservable(Cooldown(0.1), initialstep)
-    time = Observable(times[initialstep])
-    nsteps = length(times)
-    stepmin = Observable(1)
-    stepmax = Observable(nsteps)
-    boundreached = Observable(true)
-    initialized = Ref(false) # useful for skipping a "Time bound reached" message at startup
-    #
-    on(timestep) do step
-        min, max = stepmin[], stepmax[]
-        if step < min
-            timestep.val = min
-            @warn "step < stepmin"
-        elseif max < step
-            timestep.val = max
-            @warn "step > stepmax"
-        else
-            time[] = times[step]
-            if step == min || step == max
-                boundreached[] = true
-            elseif boundreached[]
-                boundreached[] = false
-            end
-        end
-    end
-    on(boundreached) do b
-        b && initialized[] && @info "Time bound reached"
-        initialized[] = true
-    end
-    on(stepmin) do bound
-        0 < bound || throw(DomainError("stepmin < 1"))
-        if timestep[] < bound
-            timestep[] = bound
-        end
-    end
-    on(stepmax) do bound
-        bound <= nsteps || throw(DomainError("stepmax > #steps=$(nsteps)"))
-        if bound < timestep[]
-            timestep[] = bound
-        end
-    end
-    #
-    playing = Observable(false)
-    if !(speed isa AbstractObservable)
-        speed = Observable(speed)
-    end
-    controller = TimeController(times, time, timestep,
-                                stepmin, stepmax, boundreached,
-                                playing, speed)
-    on(playing) do playnow
-        if playnow
-            @async playnow!(controller)
-        end
-    end
-    return controller
+    player = Player(times; speed=speed, loop=nothing)
+    ctrl = TimeController(player)
+    Players.Model.bind(ctrl, Players.Model.basicloop)
+    return ctrl
 end
 
 function slave(master::TimeController, policy::ObservationPolicy=IndependentObservables)
-    slave_timestep = newobservable(policy, master.timestep)
-    slave_time = map(slave_timestep) do step
-        master.times[step]
-    end
-    TimeController(master.times,
-                   slave_time,
-                   slave_timestep,
-                   master.stepmin,
-                   master.stepmax,
-                   master.boundreached,
-                   master.playing,
-                   master.speed)
-end
-
-isslave(controller::TimeController) = ObservationPolicies.haspolicy(controller.timestep)
-
-function stop!(controller::TimeController)
-    pause!(controller)
-    if isslave(controller)
-        # duck typing
-        try
-            ObservationPolicies.stop!(controller.timestep.policy)
-        catch
-            @debug begin
-                typ = typeof(controller.timestep.policy)
-                "`stop!` not implemented for slave controller $(typ)"
-            end
-        end
-    end
+    slave_timestep = newobservable(policy, timestep(master))
+    slave_player = Player(timestamps(master),
+                          slave_timestep,
+                          Players.speed(master),
+                          isplaying(master),
+                          Players.repeat(master),
+                          player.task)
+    TimeController(slave_player, master.stepmin, master.stepmax, master.boundreached)
 end
 
-gettimes(c::AbstractAnimator) = c.times
-gettimestep(c::AbstractAnimator) = c.timestep
-settimestep!(c::AbstractAnimator, t) = (c.timestep[] = t)
-
+isslave(controller::TimeController) = ObservationPolicies.haspolicy(timestep(controller))
+
+# function stop!(controller::TimeController)
+#     pause(controller)
+#     if isslave(controller)
+#         policy = timestep(controller).policy
+#         # duck typing
+#         try
+#             ObservationPolicies.stop!(policy)
+#         catch
+#             @debug "`stop!` not implemented for slave controller $(typeof(policy))"
+#         end
+#     end
+# end
+
+# aliases
+gettimes(c::AbstractPlayer) = timestamps(c)[]
+gettimestep(c::AbstractPlayer) = timestep(c)
+settimestep!(c::AbstractPlayer, t) = (timestep(c)[] = t)
 gettimes(c) = gettimes(getplayer(c))
 gettimestep(c) = gettimestep(getplayer(c))
 settimestep!(c, t) = settimestep!(getplayer(c), t)
@@ -256,6 +77,6 @@ function settimebounds!(c::TimeController, larva)
 end
 function unsettimebounds!(c::TimeController)
     c.stepmin[] = 1
-    c.stepmax[] = length(c.times)
+    c.stepmax[] = length(timestamps(c)[])
     @debug "Unbounding time" stepmin=c.stepmin[] stepmax=c.stepmax[]
 end
diff --git a/src/plots.jl b/src/plots.jl
index 104d85655e7879bf56ea429146331184c37907ba..4a76a07e37abd5b8b02ec19cc83924fb95b49504 100644
--- a/src/plots.jl
+++ b/src/plots.jl
@@ -25,8 +25,6 @@ withalpha(outline_color::Observable, alpha_value::Observable) = lift(withalpha,
 
 const MinimalLarva = Tuple{PathOrOutline, PathOrOutline, Color, Bool}
 
-const wgl = startswith(string(Makie.current_backend[]), "WGL")
-
 function Makie.plot!(plot::LarvaPlot{<:MinimalLarva})
     path = plot[1]
     shape_outline = plot[2]
@@ -41,13 +39,6 @@ function Makie.plot!(plot::LarvaPlot{<:MinimalLarva})
            linewidth=plot.shape_linewidth,
            visible=visibility)
     translate!(plot.plots[end], Makie.Vec3f(0, 0, 1))
-    if !wgl
-        poly!(plot, shape_outline;
-              color=withalpha(shape_color, plot.shape_alpha),
-              transparency=true,
-              visible=visibility)
-        translate!(plot.plots[end], Makie.Vec3f(0, 0, 0.5))
-    end
 end
 
 """
@@ -169,7 +160,6 @@ function SingleLarvaView(larvae::Vector{LarvaModel}, controller; editabletags::B
 
     tags = gettags(controller)
     timestep = gettimestep(controller)
-    player = getplayer(controller)
 
     f = on(usertags[]) do _
         _, color = gettag(tags[], model[], timestep[], fallback_color)
@@ -207,7 +197,6 @@ function SingleLarvaView(larvae::Vector{LarvaModel}, controller; editabletags::B
             shape_color.val = html_color(color)
             #
             visible[] = true # notify all subplots
-            timestep == last_timestep && pause!(player)
         elseif visible[]
             visible[] = false
         end
@@ -599,12 +588,19 @@ function DecoratedLarvae(larvae::Vector{DecoratedLarva})
             end
         end
     end
-    center = Meshes.coordinates ∘ Meshes.centroid
-    centers = cat((center(larva.activearea) for larva in larvae)...; dims=2)
-    norm2 = sum(centers .* centers, dims=1)
+    centers = zeros(Float32, (2, 0))
+    norm2 = zeros(Float32, (1, 0))
+    if ~isempty(larvae)
+        center = Meshes.coordinates ∘ Meshes.centroid
+        centers = cat((center(larva.activearea) for larva in larvae)...; dims=2)
+        norm2 = sum(centers .* centers, dims=1)
+    end
     DecoratedLarvae(larvae, centers, norm2, hovered_larva, hovering_active)
 end
 
+Base.isempty(larvae::DecoratedLarvae) = isempty(larvae.larvae)
+Base.length(larvae::DecoratedLarvae) = length(larvae.larvae)
+
 function find(larvae::DecoratedLarvae, position)
     pos = Vector(position)'
     p2 = pos * pos'
@@ -641,7 +637,13 @@ end
 
 Meshes.boundingbox(larva::StatefulLarva) = Meshes.boundingbox(larva.model)
 Meshes.boundingbox(larva::DecoratedLarva) = Meshes.boundingbox(larva.larva)
-Meshes.boundingbox(larvae::DecoratedLarvae) = Meshes.boundingbox(map(Meshes.boundingbox, larvae.larvae))
+function Meshes.boundingbox(larvae::DecoratedLarvae)
+    if isempty(larvae)
+        Meshes.Box(Meshes.Point(0.0, 1.0), Meshes.Point(0.0, 1.0))
+    else
+        Meshes.boundingbox(map(Meshes.boundingbox, larvae.larvae))
+    end
+end
 
 function setmouseevents!(scene, plot::DecoratedLarvae, ctrl; consume=false)
     on(events(scene).mousebutton) do mb
diff --git a/src/viewer.jl b/src/viewer.jl
index 7463492dd4eef02ba03de68d9bd57efd5c12df6a..b2344b44c0937f0cb60ae6270712619a7ce975aa 100644
--- a/src/viewer.jl
+++ b/src/viewer.jl
@@ -1,51 +1,58 @@
 
 struct ViewerView
     assayplot::AssayViewer
-    trackplot::TrackViewer
+    trackplot::Union{Nothing, TrackViewer}
 end
 
-function JSServe.jsrender(session::Session, vv::ViewerView)
-    controller = vv.trackplot.plot.controller
+function Bonito.jsrender(session::Session, vv::ViewerView)
     delayed_controller = vv.assayplot.plot.controller
 
-    assaydom = r(session, vv.assayplot)
-    trackdom = r(session, vv.trackplot)
-
-    on(larvaevents(controller).activated) do _
-        vv.assayplot.visible[] = false
-        JSServe.evaljs(session,
-                       js"""
-                       const assayviewer = $(assaydom);
-                       const trackviewer = $(trackdom);
-                       assayviewer.style.display = "none";
-                       trackviewer.style.display = "block";
-                       """)
-    end
-    on(larvaevents(controller).deactivated) do _
-        vv.assayplot.visible[] = true
-        JSServe.evaljs(session,
-                       js"""
-                       const assayviewer = $(assaydom);
-                       const trackviewer = $(trackdom);
-                       trackviewer.style.display = "none";
-                       assayviewer.style.display = "block";
-                       """)
-    end
-
     on(session.on_close) do closed
         if closed
-            stop!(delayed_controller)
+            pause(delayed_controller.player)
         end
     end
 
-    r(session,
-      DOM.div(JSServe.TailwindCSS, assaydom, trackdom; class="flex flex-row"))
+    assaydom = r(session, vv.assayplot)
+    trackdom = nothing
+
+    if !isnothing(vv.trackplot)
+        controller = vv.trackplot.plot.controller
+        trackdom = r(session, vv.trackplot)
+
+        on(session, larvaevents(controller).activated) do _
+            vv.assayplot.visible[] = false
+            evaljs(session,
+                js"""
+                const assayviewer = $(assaydom);
+                const trackviewer = $(trackdom);
+                assayviewer.style.display = "none";
+                trackviewer.style.display = "block";
+                """)
+        end
+        on(session, larvaevents(controller).deactivated) do _
+            vv.assayplot.visible[] = true
+            evaljs(session,
+                js"""
+                const assayviewer = $(assaydom);
+                const trackviewer = $(trackdom);
+                trackviewer.style.display = "none";
+                assayviewer.style.display = "block";
+                """)
+        end
+
+        r(session,
+          DOM.div(Bonito.TailwindCSS, assaydom, trackdom; class="flex flex-row"))
+
+    else
+        r(session, DOM.div(Bonito.TailwindCSS, assaydom))
+    end
 end
 
 function larvaviewer(path::String; allow_multiple_tags::Union{Nothing, Bool}=false,
-    kwargs...)
+    title="LarvaTagger", kwargs...)
 
-    App() do session::Session
+    App(title=title) do session::Session
 
         controller = ControllerHub()
 
@@ -54,9 +61,12 @@ function larvaviewer(path::String; allow_multiple_tags::Union{Nothing, Bool}=fal
         viewer = larvaviewer(controller; multipletags=allow_multiple_tags, kwargs...)
 
         r(session, LarvaTaggerJS)
+
+        dom = Bonito.jsrender(session, viewer)
+
         r(session, LarvaTaggerCSS)
 
-        JSServe.jsrender(session, viewer)
+        return dom
     end
 end
 
@@ -65,9 +75,14 @@ function larvaviewer(controller;
         multipletags::Union{Nothing, Bool}=false,
         viewfactor::Real=1,
     )
-    model = getlarvae(controller)
-    times = gettimes(controller)
-    tag_lut = gettags(controller)
+    model = LarvaModel[]
+    times = PlanarLarvae.Time[0.0, 1.0]
+    tag_lut = Observable(TagLUT())
+    if haskey(controller, :larva)
+        model = getlarvae(controller)
+        times = gettimes(controller)
+        tag_lut = gettags(controller)
+    end
 
     controller[:player] = player = timecontroller(times)
     controller[:larva] = larva = LarvaController(controller, model, tag_lut, player)
diff --git a/src/wgl.jl b/src/wgl.jl
index 66408bbd99a3ed35ee7d82005f1836f9672c153d..9bdb2a73ed716cd420fa39d7bcd5b54b57b76f2d 100644
--- a/src/wgl.jl
+++ b/src/wgl.jl
@@ -1,28 +1,15 @@
 getsession(c) = gethub(c)[:session]
 setsession!(c, session) = (gethub(c)[:session] = session)
 
-const r = JSServe.jsrender
+const r = Bonito.jsrender
 
-const LarvaTaggerJS = JSServe.Dependency(:LarvaTagger,
-                                         [joinpath(@__DIR__, "larvatagger.js")])
-const LarvaTaggerCSS = JSServe.Asset(joinpath(@__DIR__, "larvatagger.css"))
-const LoadAwesomeCSS = JSServe.Asset(joinpath(@__DIR__, "loadawesome.css"))
+const LarvaTaggerJS = Bonito.Asset(joinpath(@__DIR__, "larvatagger.js"))
+const LarvaTaggerCSS = Bonito.Asset(joinpath(@__DIR__, "larvatagger.css"))
+const LoadAwesomeCSS = Bonito.Asset(joinpath(@__DIR__, "loadawesome.css"))
 
 scrollable(elements...) = DOM.div(DOM.table(elements...);
                                   class="scrollable", scrolling="auto")
 
-const Trigger = Observable{Bool}
-const Toggle = Observable{Bool}
-
-function simpletrigger(cls::DataType, args...)
-    click = Trigger()
-    on(click) do _
-        #click.val = false
-        @debug "Click on $cls"
-    end
-    return cls(click, args...)
-end
-
 function with_attributes(kwargs; class="", style="", kwargs′...)
     attributes = Dict{Symbol, Any}(kwargs′)
     merge!(attributes, Dict{Symbol, Any}(kwargs))
@@ -42,36 +29,19 @@ function with_attributes(kwargs; class="", style="", kwargs′...)
     return attributes
 end
 
-css(lines...) = join(lines, "; ")
-
 css_button = "button"
 
-function DOMtrigger(label, trigger, attributes)
-    DOM.button(label;
-               type="button",
-               onclick=js"""
-               JSServe.update_obs($trigger, true);
-               """,
-               attributes...
-              )
-end
-
-DOMtrigger(label, trigger; kwargs...) = DOMtrigger(label, trigger, with_attributes(kwargs; class=css_button))
-
-function DOMtoggle(label, jlstate, attributes)
-    DOM.button(label;
-               type="button",
-               onclick=js"LarvaTagger.toggle($jlstate)",
-               attributes...
-              )
-end
-
 struct ControlPanel
     id::String
     title::String
     active::Bool
     elements
     attributes
+    dom_id
+end
+
+function ControlPanel(id::String, title::String, active::Bool, elements, attributes)
+    ControlPanel(id, title, active, elements, attributes, dom_id())
 end
 
 function ControlPanel(elements...; id::String, title::String, active::Bool=false, attributes...)
@@ -79,34 +49,23 @@ function ControlPanel(elements...; id::String, title::String, active::Bool=false
     ControlPanel(id, title, active, elements, attributes)
 end
 
-function JSServe.jsrender(session::Session, panel::ControlPanel)
+function lowerdom(panel::ControlPanel)
     tabid = "select-" * panel.id
     escaped_attrs = (:for=>tabid,)
-    r(session,
-      DOM.div(DOM.input(; type="radio", name="tab", id=tabid, class="cp-tab-switch",
-                          checked=panel.active),
-              DOM.label(panel.title; class="cp-tab-label", escaped_attrs...),
-              DOM.div(panel.elements...; id=panel.id, panel.attributes...);
-              class="cp-tab"))
+    return DOM.div(DOM.input(; type="radio", name="tab", id=tabid, class="cp-tab-switch",
+                             checked=panel.active),
+                   DOM.label(panel.title; class="cp-tab-label", escaped_attrs...),
+                   DOM.div(panel.elements...; id=panel.id, panel.attributes...);
+                   class="cp-tab")
 end
+Bonito.jsrender(session::Session, panel::ControlPanel) = lowerdom(session, panel)
 
 cp(panels...) = DOM.div(panels...; class="cp-tabs")
 
-struct HomeButton
-    click::Trigger
-    attributes
-end
-
-function homebutton(kwargs...)
-    simpletrigger(HomeButton,
-                  with_attributes(kwargs;
-                                  class=css_button,
-                                  style="position:absolute; top:1.5625rem; right:2.5rem;"))
-end
-
-function JSServe.jsrender(session::Session, b::HomeButton)
-    r(session,
-      DOMtrigger("⌂", b.click, b.attributes))
+function homebutton()
+    NyxWidgets.Button("⌂";
+                      class=css_button,
+                      style="position:absolute; top:1.5625rem; right:2.5rem;")
 end
 
 struct AssayPlot
@@ -114,22 +73,30 @@ struct AssayPlot
     figure::Figure
     axis::Axis
     larvae::DecoratedLarvae
-    homebutton::HomeButton
+    homebutton::NyxWidgets.Button
+    dom_id
+end
+
+function AssayPlot(controller, figure::Figure, axis::Axis, larvae::DecoratedLarvae,
+        homebutton::NyxWidgets.Button)
+    AssayPlot(controller, figure, axis, larvae, homebutton, dom_id())
 end
 
 function AssayPlot(ctrl, larvae::DecoratedLarvae; size=FIGSIZE)
     gethub(ctrl)[:decoratedlarvae] = larvae
-    fig = Figure(resolution=size)
+    fig = Figure(size=size)
     width = 0.1f0 # try to get 1px
     color = RGBAf(0, 0, 0, 0.36)
     ax = Axis(fig.layout[1, 1], aspect=DataAspect(), xgridwidth=width, ygridwidth=width,
               xgridcolor=color, ygridcolor=color)
     autosize!(ax, size)
-    plot = larvaplot!(ax, larvae)
-    setkeyboardevents!(plot.parent, getplayer(ctrl))
-    setbounds!(ax, ctrl, larvae)
+    if !isempty(larvae)
+        plot = larvaplot!(ax, larvae)
+        setkeyboardevents!(plot.parent, getplayer(ctrl))
+        setbounds!(ax, ctrl, larvae)
+    end
     button = homebutton()
-    on(button.click) do _
+    on(button) do _
         setbounds!(ax, ctrl, larvae)
     end
     AssayPlot(ctrl, fig, ax, larvae, button)
@@ -162,12 +129,8 @@ function assayplot(data, ctrl, args...; kwargs...)
     return plot
 end
 
-function JSServe.jsrender(session::Session, p::AssayPlot)
-    r(session,
-      DOM.div(r(session, p.homebutton),
-              p.figure;
-              class="relative"))
-end
+lowerdom(p::AssayPlot) = DOM.div(p.homebutton, p.figure; class="relative")
+Bonito.jsrender(session::Session, p::AssayPlot) = lowerdom(session, p)
 
 function setmouseevents!(plot::AssayPlot; kwargs...)
     setmouseevents!(plot.axis.scene, plot.larvae, plot.controller; kwargs...)
@@ -179,13 +142,19 @@ struct TrackPlot
     axis::Axis
     larvae::Vector{LarvaModel}
     view::SingleLarvaView
-    homebutton::HomeButton
+    homebutton::NyxWidgets.Button
+    dom_id
+end
+
+function TrackPlot(controller, figure, axis::Axis, larvae::Vector{LarvaModel},
+        view::SingleLarvaView, homebutton::NyxWidgets.Button)
+    TrackPlot(controller, figure, axis, larvae, view, homebutton, dom_id())
 end
 
 function TrackPlot(controller, larvae::Vector{LarvaModel};
         size=FIGSIZE, editabletags::Bool=true)
     view = SingleLarvaView(larvae, controller; editabletags=editabletags)
-    fig = Figure(resolution=size)
+    fig = Figure(size=size)
     width = 0.1f0 # try to get 1px
     color = RGBAf(0, 0, 0, 0.36)
     ax = Axis(fig.layout[1, 1], aspect=DataAspect(), xgridwidth=width, ygridwidth=width,
@@ -205,7 +174,7 @@ function TrackPlot(controller, larvae::Vector{LarvaModel};
         end
     end
     button = homebutton()
-    on(button.click) do _
+    on(button) do _
         deactivatelarva!(controller)
     end
     TrackPlot(controller, fig, ax, larvae, view, button)
@@ -215,212 +184,38 @@ TrackPlot(ctrl, model::Dict{<:Integer, LarvaModel}, args...; kwargs...) = TrackP
 
 trackplot(model, controller; kwargs...) = TrackPlot(controller, model; kwargs...)
 
-function JSServe.jsrender(session::Session, p::TrackPlot)
-    # TODO: read client window size
-    r(session,
-      DOM.div(r(session, p.homebutton),
-              p.figure;
-              class="relative"))
-end
-
-struct PlayPauseButton
-    playing::AbstractObservable{Bool}
-    attributes::Dict{Symbol, Any}
-end
-
-function playpausebutton(ctrl::TimeController; kwargs...)
-    PlayPauseButton(ctrl.playing, with_attributes(kwargs; class=css_button))
-end
-
-function JSServe.jsrender(session::Session, b::PlayPauseButton)
-    r(session,
-      DOMtoggle("Play/Pause", b.playing, b.attributes))
-end
-
-struct StepForwardButton
-    click::Trigger
-    attributes::Dict{Symbol, Any}
-end
-
-function stepforwardbutton(ctrl::TimeController; kwargs...)
-    button = simpletrigger(StepForwardButton, with_attributes(kwargs; class=css_button))
-    on(button.click) do _
-        stepforward!(ctrl)
-    end
-    return button
-end
-
-function JSServe.jsrender(session::Session, b::StepForwardButton)
-    r(session,
-      DOMtrigger(">", b.click, b.attributes))
-end
-
-struct StepBackwardButton
-    click::Trigger
-    attributes::Dict{Symbol, Any}
-end
-
-function stepbackwardbutton(ctrl::TimeController; kwargs...)
-    button = simpletrigger(StepBackwardButton, with_attributes(kwargs; class=css_button))
-    on(button.click) do _
-        stepbackward!(ctrl)
-    end
-    return button
-end
-
-function JSServe.jsrender(session::Session, b::StepBackwardButton)
-    r(session,
-      DOMtrigger("<", b.click, b.attributes))
-end
-
-struct TimeSlider
-    master::TimeController
-    attributes::Dict{Symbol, Any}
-end
-
-function timeslider(master::TimeController; kwargs...)
-    TimeSlider(master,
-               with_attributes(kwargs;
-                               style="width: 100%",
-                               class="ml-1 mr-1"))
-end
-timeslider(times::Vector{Float64}; kwargs...) = timeslider(timecontroller(times); kwargs...)
-
-function JSServe.jsrender(session::Session, ts::TimeSlider)
-    master = ts.master
-    input = ObservationPolicies.input(master.timestep)
-    output = ObservationPolicies.output(master.timestep)
-    moveslider = js"LarvaTagger.timeSlider(this, $input, $(master.stepmin), $(master.stepmax)).moveto(value)"
-    slider = DOM.input(type="range",
-                       min=1,
-                       max=length(master.times),
-                       value=output,
-                       step=1,
-                       oninput=moveslider;
-                       ts.attributes...)
-    JSServe.onload(session, slider, js"(slider)=>{ slider.value = 1; }")
-    r(session, slider)
-end
-
-struct TimeLabel
-    time::AbstractObservable{Float64}
-    ndecimals::Int
-    attributes::Dict{Symbol, Any}
-end
-
-function timelabel(time::AbstractObservable{Float64};
-                   ndecimals=2, kwargs...)
-    TimeLabel(time,
-              ndecimals,
-              with_attributes(kwargs,
-                              style=css("width: 12.5rem",
-                                        "display: flex",
-                                        "flex-direction: row",
-                                        "align-items: center")))
-end
-timelabel(master::TimeController; kwargs...) = timelabel(master.time; kwargs...)
-
-function JSServe.jsrender(session::Session, tl::TimeLabel)
-    fmt = FormatExpr("t={: .$(tl.ndecimals)f} s")
-    formatter = t -> format(fmt, t)
-    r(session,
-      DOM.label(lift(formatter, tl.time);
-                tl.attributes...))
-end
-
-struct PlaybackSpeedList
-    backspeed::AbstractObservable{Float64}
-    frontspeed::AbstractObservable{Float64}
-    attributes::Dict{Symbol, Any}
-end
-
-function playbackspeedlist(animator; kwargs...)
-    backspeed = animator.speed
-    frontspeed = map(a -> a, backspeed)
-    PlaybackSpeedList(backspeed, frontspeed, with_attributes(kwargs; class="speed"))
-end
-
-function JSServe.jsrender(session::Session, sl::PlaybackSpeedList)
-    dom = r(session,
-            DOM.select(DOM.option("0.25"; value=0.25),
-                       DOM.option("0.5"; value=0.5),
-                       DOM.option("0.75"; value=0.75),
-                       DOM.option("1.0"; value=1.0),
-                       DOM.option("1.25"; value=1.25),
-                       DOM.option("1.5"; value=1.5),
-                       DOM.option("2.0"; value=2.0);
-                       onchange=js"JSServe.update_obs($(sl.backspeed), parseFloat(this.value))",
-                       sl.attributes...))
-    on(sl.frontspeed) do speed
-        frontspeed = if speed < 0.375
-            "0.25"
-        elseif speed < 0.625
-            "0.5"
-        elseif speed < 0.875
-            "0.75"
-        elseif speed < 1.125
-            "1.0"
-        elseif speed < 1.375
-            "1.25"
-        elseif speed < 1.75
-            "1.5"
-        else
-            "2.0"
-        end
-        evaljs(session, js"$(dom).value = $frontspeed;")
-    end
-    notify(sl.frontspeed)
-    return dom
-end
-
-struct Player
-    controller::TimeController
-    timeslider::TimeSlider
-    timelabel::TimeLabel
-    playpausebutton::PlayPauseButton
-    stepbackwardbutton::StepBackwardButton
-    stepforwardbutton::StepForwardButton
-    playbackspeedlist::PlaybackSpeedList
-    attributes::Dict{Symbol, Any}
-end
-
+lowerdom(p::TrackPlot) = DOM.div(p.homebutton, p.figure; class="relative")
+Bonito.jsrender(session::Session, p::TrackPlot) = lowerdom(session, p)
 
 player(times::Vector; kwargs...) = player(timecontroller(times); kwargs...)
 
 player(controller; kwargs...) = player(getplayer(controller); kwargs...)
 
 function player(animator::TimeController; kwargs...)
-    Player(animator,
-           timeslider(animator),
-           timelabel(animator),
-           playpausebutton(animator),
-           stepbackwardbutton(animator),
-           stepforwardbutton(animator),
-           playbackspeedlist(animator),
-           with_attributes(kwargs;
-                           class="flex flex-row player",
-                           style="width: 100%; height: 2.5rem"),
-          )
-end
-
-function JSServe.jsrender(session::Session, p::Player)
-    r(session,
-      DOM.div(r(session, p.playpausebutton),
-              r(session, p.stepbackwardbutton),
-              r(session, p.stepforwardbutton),
-              r(session, p.playbackspeedlist),
-              r(session, p.timeslider),
-              r(session, p.timelabel);
-              onmouseenter=js"LarvaTagger.focusOnTimeSlider(this)",
-              p.attributes...
-             ))
-end
-
-mutable struct AssayViewer
+    step = timestep(animator)
+    stepmin = animator.stepmin
+    stepmax = animator.stepmax
+    playbackspeeds = ["0.25", "0.5", "0.75", "1.0", "1.25", "1.5", "2"]
+    view = PlayerView(animator, playbackspeeds)
+    oninput = js"""(evt) => {
+        var step = evt.srcElement.value;
+        LarvaTagger.timeSlider(evt.srcElement, $step, $stepmin, $stepmax).moveto(step)
+    }"""
+    # set oninput attribute in immutable structs
+    slider = view.timeslider
+    slider = Players.TimeSlider(slider.player, slider.index, slider.playpause, oninput,
+                                slider.attributes, view.dom_id)
+    view = PlayerView(view.player, view.playpause, view.stepforward, view.stepbackward,
+                      view.repeat, view.speedselector, slider, view.timelabel,
+                      view.attributes, view.dom_id)
+    return view
+end
+
+struct AssayViewer
     plot::AssayPlot
-    player::Player
+    player::PlayerView
     visible::AbstractObservable{Bool}
-    dom
+    dom_id
 end
 
 function assayviewer(controller; kwargs...)
@@ -434,18 +229,14 @@ function assayviewer(controller; kwargs...)
             notify(gettimestep(controller))
         end
     end
-    AssayViewer(plot, play, visible, nothing)
+    AssayViewer(plot, play, visible, dom_id())
 end
 
-function JSServe.jsrender(session::Session, av::AssayViewer)
-    dom = DOM.div(r(session, av.plot),
-                  r(session, av.player);
-                  class="flex flex-col",
-                  onmouseenter=js"LarvaTagger.focusOnTimeSlider(this)",
-                 )
-    av.dom = dom
-    return r(session, dom)
+function lowerdom(av::AssayViewer)
+    focus = js"(evt)=>{ LarvaTagger.focusOnTimeSlider(evt.srcElement); }"
+    return DOM.div(av.plot, av.player; class="flex flex-col", onmouseenter=focus)
 end
+Bonito.jsrender(session::Session, av::AssayViewer) = lowerdom(session, av)
 
 struct LarvaInfo
     controller
@@ -455,6 +246,7 @@ struct LarvaInfo
     reviewed::AbstractObservable{Bool}
     edited::AbstractObservable{Bool}
     included::AbstractObservable{Bool}
+    dom_id
 end
 
 function larvainfo(controller, id)
@@ -516,28 +308,30 @@ function larvainfo(controller, id)
               clicked,
               reviewed,
               edited,
-              included)
+              included,
+              dom_id())
 end
 
-JSServe.jsrender(session::Session, li::LarvaInfo) = r(session, prerender(li))
+Bonito.jsrender(session::Session, li::LarvaInfo) = lowerdom(session, li)
 
-function prerender(li::LarvaInfo)
+function lowerdom(li::LarvaInfo)
     label = "#$(li.id)"
-    discard_larva_edits = js"LarvaTagger.discardLarvaEdits(this, $(li.edited), $label)"
     label = DOM.label(label,
-                      onmouseenter=js"JSServe.update_obs($(li.hovered), true)",
-                      onmouseleave=js"JSServe.update_obs($(li.hovered), false)",
-                      onmouseup=js"JSServe.update_obs($(li.clicked), true)")
+                      onmouseenter=js"()=>{ $(li.hovered).notify(true); }",
+                      onmouseleave=js"()=>{ $(li.hovered).notify(false); }",
+                      onmouseup=js"()=>{ $(li.clicked).notify(true); }")
+    toggle_reviewed = js"(evt)=>{ $(li.reviewed).notify(evt.srcElement.checked); }"
     reviewed_checkbox = DOM.input(type="checkbox",
                                   checked=li.reviewed,
-                                  onchange=js"""
-                                  JSServe.update_obs($(li.reviewed), this.checked);
-                                  """)
+                                  onchange=toggle_reviewed)
+    discard_larva_edits = js"""(evt)=>{
+        LarvaTagger.discardLarvaEdits(evt.srcElement, $(li.edited), $label);
+    }"""
     edited_checkbox = DOM.input(type="checkbox",
                                 checked=li.edited,
                                 # onchange is not triggered from Julia
                                 onchange=discard_larva_edits)
-    include = js"LarvaTagger.includeLarva(this, $(li.included))"
+    include = js"(evt)=>{ LarvaTagger.includeLarva(evt.srcElement, $(li.included)); }"
     included_checkbox = DOM.input(type="checkbox",
                                   checked=li.included,
                                   class="included",
@@ -554,6 +348,12 @@ struct LarvaFilter
     includeall::AbstractObservable{Bool}
     saveas::AbstractObservable{Bool}
     attributes
+    dom_id
+end
+
+function LarvaFilter(controller, entries::Vector{LarvaInfo},
+        includeall::AbstractObservable{Bool}, saveas::AbstractObservable{Bool}, attributes)
+    LarvaFilter(controller, entries, includeall, saveas, attributes, dom_id())
 end
 
 function larvafilter(controller::ControllerHub; kwargs...)
@@ -586,18 +386,21 @@ end
 
 getlarvafilter(controller) = gethub(controller)[:larvafilter]
 
-function JSServe.jsrender(session::Session, lf::LarvaFilter)
-    outputfile = getoutputfile(lf.controller)
+
+function lowerdom(lf::LarvaFilter)
+    outputfilename = getoutputfile(lf.controller).name
     disabled = map(!, lf.saveas)
-    button = r(session,
-               DOM.button("Save as...";
-                          type="button",
-                          id="saveas",
-                          onclick=js"LarvaTagger.setOutputFilename($(outputfile.name))",
-                          disabled=disabled,
-                          style="width: 100%",
-                          class=css_button))
-    includeall = js"LarvaTagger.includeAllLarvae(this, $(lf.includeall), $button)"
+    setoutputfilename = js"()=>{ LarvaTagger.setOutputFilename($outputfilename); }"
+    button = DOM.button("Save as...";
+                        type="button",
+                        id="saveas",
+                        onclick=setoutputfilename,
+                        disabled=disabled,
+                        style="width: 100%",
+                        class=css_button)
+    includeall = js"""(evt)=>{
+        LarvaTagger.includeAllLarvae(evt.srcElement, $(lf.includeall), $button);
+    }"""
     table = scrollable(DOM.tr(DOM.th(""),
                               DOM.th("Reviewed"),
                               DOM.th("Edited"),
@@ -610,15 +413,19 @@ function JSServe.jsrender(session::Session, lf::LarvaFilter)
                                                id="includeall",
                                                onchange=includeall),
                                      style="text-align: center;")),
-                       prerender.(lf.entries)...)
-    JSServe.onload(session, table, js"LarvaTagger.uncheckAllCheckboxes")
-    r(session,
-      DOM.div(table,
-              button;
-              with_attributes(lf.attributes; class="panel")...))
+                       lowerdom.(lf.entries)...)
+    return DOM.div(table,
+                   button;
+                   with_attributes(lf.attributes; class="panel")...)
 end
 
-abstract type AbstractView end
+function Bonito.jsrender(session::Session, lf::LarvaFilter)
+    node = lowerdom(session, lf)
+    Bonito.onload(session, node, js"""(parent)=>{
+        LarvaTagger.uncheckAllCheckboxes(parent.firstElementChild);
+    }""")
+    return node
+end
 
 mutable struct TagController
     hub::ControllerHub
@@ -646,32 +453,21 @@ end
 
 getmanualtag(c) = gethub(c)[:tag].manualtag
 
-function setcallbacks!(controller, tag, checkbox, label, colorpicker)
-    session = getsession(controller)
-    JSServe.attribute_render(session, label, "value", tag.name)
-    JSServe.attribute_render(session, colorpicker, "value", tag.color)
-    #
-    on(tagevents(controller).renamefailed) do (name, failure)
-        name == tag.name[] || return
-        @info "Reverting to \"$(name)\" after failure: \"$(failure)\""
-        evaljs(session,
-               js"""
-               const label = $(JSServe.selector(label));
-               label.value = $(name);
-               """)
-    end
-end
-
 newtagname(::TagController) = NoTag
 
 struct IndividualTagView
     controller::TagController
     tag::ObservableTag # view
+    dom_id
+end
+
+function IndividualTagView(controller::TagController, tag::ObservableTag)
+    IndividualTagView(controller, tag, dom_id())
 end
 
 tagview(controller, tag) = IndividualTagView(controller, tag)
 
-function JSServe.jsrender(session::Session, ti::IndividualTagView)
+function lowerdom(ti::IndividualTagView)
     tag = ti.tag
     active = tag.active
     disabled = map(!, active)
@@ -680,73 +476,63 @@ function JSServe.jsrender(session::Session, ti::IndividualTagView)
     #
     label = DOM.input(type="text",
                       value=name[],
-                      onchange=js"""
-                      JSServe.update_obs($(name), this.value);
-                      """,
+                      onchange=js"(evt)=>{ $name.notify(evt.srcElement.value); }",
                       disabled=disabled,
-                      class="tag-name",
-                     )
+                      class="tag-name")
     colorpicker = DOM.input(type="color",
                             value=color[],
-                            onchange=js"""
-                            JSServe.update_obs($(color), this.value);
-                            """,
+                            onchange=js"(evt)=>{ $color.notify(evt.srcElement.value); }",
                             disabled=disabled,
-                            class="h-8 w-8 rounded shadow tag-color",
-                           )
+                            class="h-8 w-8 rounded shadow tag-color")
     checkbox = DOM.input(type="checkbox",
                          checked=active,
-                         onchange=js"""
-                         JSServe.update_obs($(active), this.checked);
-                         """,
+                         onchange=js"(evt)=>{ $active.notify(evt.srcElement.checked); }",
                          class="tag-active")
-    setcallbacks!(ti.controller, tag, checkbox, label, colorpicker)
-    notify(active)
-    r(session,
-      DOM.tr(DOM.td(checkbox),
-             DOM.td(label),
-             DOM.td(colorpicker),
-            ))
+    return DOM.tr(DOM.td(checkbox),
+                  DOM.td(label),
+                  DOM.td(colorpicker))
+end
+function Bonito.jsrender(session::Session, ti::IndividualTagView)
+    node = lowerdom(session, ti)
+    tag = ti.tag
+    selector = dom_selector(ti) * " .tag-name"
+    events = tagevents(ti.controller)
+    on(events.renamefailed) do (name, failure)
+        name == tag.name[] || return
+        @info "Reverting to \"$(name)\" after failure: \"$(failure)\""
+        evaljs(session, js"document.querySelector($selector).value = $name;")
+    end
+    notify(tag.active)
+    return node
 end
 
 function addtag! end
 
-mutable struct AddTagButton
-    click::Trigger
-    attributes::Dict{Symbol, Any}
-end
-
-function addtagbutton(controller; kwargs...)
-    button = simpletrigger(AddTagButton,
-                           with_attributes(kwargs;
-                                           class=css_button,
-                                           style="width: 100%; height: 2rem; padding: 0px",
-                                          ))
-    on(button.click) do _
+function addtagbutton(controller)
+    button = NyxWidgets.Button(" + ";
+                               title="New tag",
+                               class=css_button,
+                               style="width: 100%; height: 2rem; padding: 0px")
+    on(button) do _
         addtag!(controller)
     end
     return button
 end
 
-function JSServe.jsrender(session::Session, b::AddTagButton)
-    r(session, DOMtrigger(" + ", b.click, b.attributes))
-end
-
-mutable struct TagFilter
+struct TagFilter
     controller::TagController
     views::Vector{IndividualTagView}
-    addtagbutton::AddTagButton
-    domtable::Union{Nothing, DOM.Hyperscript.Node{DOM.Hyperscript.HTMLSVG}}
+    addtagbutton::NyxWidgets.Button
     attributes
+    dom_id
 end
 
 function TagFilter(controller::TagController; kwargs...)
     TagFilter(controller,
               IndividualTagView[],
               addtagbutton(controller),
-              nothing,
               kwargs,
-             )
+              dom_id())
 end
 
 function tagfilter(controller; manualtag=nothing)
@@ -755,19 +541,18 @@ function tagfilter(controller; manualtag=nothing)
     return tc.view
 end
 
-function JSServe.jsrender(session::Session, tf::TagFilter)
-    setsession!(tf.controller, session)
+function lowerdom(tf::TagFilter)
     tags = newview!(gethub(tf.controller)[:taghooks])
     for tag in reverse(tags)
-        push!(tf.views,
-              tagview(tf.controller,
-                      tag))
+        push!(tf.views, tagview(tf.controller, tag))
     end
-    tf.domtable = r(session, scrollable(tf.views...))
-    r(session,
-      DOM.div(tf.domtable,
-              tf.addtagbutton;
-              with_attributes(tf.attributes; class="panel")...))
+    return DOM.div(scrollable(tf.views...),
+                   tf.addtagbutton;
+                   with_attributes(tf.attributes; class="panel")...)
+end
+function Bonito.jsrender(session::Session, tf::TagFilter)
+    setsession!(tf.controller, session)
+    return lowerdom(session, tf)
 end
 
 function addtag!(parent::TagController)
@@ -782,19 +567,31 @@ function addtag!(parent::TagController)
     #
     # model ordering: highest priority is first, lowest is last
     view = parent.view
-    childview = tagview(parent,
-                        newview!(tag))
+    childview = tagview(parent, newview!(tag))
     addtag!(view, childview)
     notify(taglut)
 end
 
 function addtag!(view::TagFilter, newtag::IndividualTagView)
     session = getsession(view.controller)
-    js_dom = r(session, newtag)
-    html_dom = "<table><tbody>" * replace(string(js_dom), "'" => "\\'") * "</tbody></table>"
-    evaljs(session, js"LarvaTagger.insertNewTag($(view.domtable), $html_dom)")
+    tag = newtag.tag
+    id0 = dom_id(session)
+    @assert id0 isa Int
+    tagactive = dom_selector(id0+3)
+    tagname = dom_selector(id0+5)
+    tagcolor = dom_selector(id0+7)
+    jsdom = r(session, newtag)
+    htmldom = "<table><tbody>" * replace(string(jsdom), "'" => "\\'") * "</tbody></table>"
+    domtable = dom_selector(view)
+    evaljs(session, js"""
+    let domtable = document.querySelector($domtable).firstElementChild;
+    LarvaTagger.insertNewTag(domtable, $htmldom);
+    document.querySelector($tagactive).addEventListener('change', (evt)=>{ $(tag.active).notify(evt.srcElement.checked); });
+    document.querySelector($tagname).addEventListener('change', (evt)=>{ $(tag.name).notify(evt.srcElement.value); });
+    document.querySelector($tagcolor).addEventListener('change', (evt)=>{ $(tag.color).notify(evt.srcElement.value); });
+    """)
     push!(view.views, newtag)
-    return js_dom
+    return jsdom
 end
 
 struct TagSelector
@@ -803,6 +600,8 @@ struct TagSelector
     selected::Observable{Vector{Tuple{String, Observable{Bool}}}}
     selectable::Bool
     showtoggle::Bool
+    fromjs::Union{Nothing, Observable{String}}
+    dom_id
 end
 
 function TagSelector(controller::TagController,
@@ -821,11 +620,27 @@ function TagSelector(controller::TagController,
     if !isnothing(multiple)
         multitag[] = multiple
     end
+    if selectable
+        fromjs = Observable("")
+        on(fromjs) do actuatedtag
+            for (tag, selected) in selectedtags[]
+                if tag == actuatedtag
+                    toggletag!(controller, tag, selected, selectedtags, selectable)
+                    flag_active_larva_as_edited(controller)
+                    break
+                end
+            end
+        end
+    else
+        fromjs = nothing
+    end
     ctrl = TagSelector(controller,
                        tagnames,
                        selectedtags,
                        selectable,
-                       isnothing(multiple))
+                       isnothing(multiple),
+                       fromjs,
+                       dom_id())
     refresh_view = Observable(false)
     on(tagnames) do names
         for i in length(registered_observables)+1:length(names)
@@ -888,7 +703,7 @@ function refresh_selected_tags(controller, id, timestep, taglut, selectedtags, s
     end
     isempty(jscode) && return
     evaljs(getsession(controller),
-           JSServe.JSCode([JSServe.JSString(join(jscode, "\n"))]))
+           Bonito.JSCode([Bonito.JSString(join(jscode, "\n"))]))
 end
 
 function refresh_selected_tag(controller, id, timestep, taglut, selectedtags, selectable)
@@ -922,7 +737,7 @@ function refresh_selected_tag(controller,
     end
     isempty(jscode) && return
     evaljs(getsession(controller),
-           JSServe.JSCode([JSServe.JSString(join(jscode, "\n"))]))
+           Bonito.JSCode([Bonito.JSString(join(jscode, "\n"))]))
 end
 
 function toggletag!(controller, tagname, selected, selectedtags, selectable)
@@ -957,62 +772,58 @@ function toggletag!(controller, tagname, selected, selectedtags, selectable)
     notify(larva.usertags)
 end
 
-function to_dom(selectedtags, selectable)
+function lowertags(selectedtags, selectable)
+    toggle = selectable ? js"LarvaTagger.toggleTagAtPointer" : nothing
     DOM.select(DOM.option(tagname;
                           id=tagname,
                           value=tagname,
                           selected=selected[],
-                          onmousedown=selectable ? js"LarvaTagger.toggleTagAtPointer(event)" : nothing,
-                         ) for (tagname, selected) in selectedtags;
+                          onmousedown=toggle)
+               for (tagname, selected) in selectedtags;
                multiple=true,
-               size=1,
-              )
+               size=1)
 end
 
-function JSServe.jsrender(session::Session, ts::TagSelector)
-    setsession!(ts.controller, session)
-    if ts.selectable
-        controller = ts.controller
-        selectedtags = ts.selected
-        fromjs = Observable("")
-        on(fromjs) do actuatedtag
-            for (tag, selected) in selectedtags[]
-                if tag == actuatedtag
-                    toggletag!(controller, tag, selected, selectedtags, ts.selectable)
-                    flag_active_larva_as_edited(controller)
-                    break
-                end
-            end
-        end
-        evaljs(session, js"LarvaTagger.setTagSelector($fromjs)")
-    end
-    taglist = r(session, DOM.div(to_dom(ts.selected[], ts.selectable); id="tag-selector"))
-    on(ts.selected) do selected
-        js_dom = JSServe.jsrender(session, to_dom(selected, ts.selectable))
-        html_dom = replace(string(js_dom), "'" => "\\'")
-        evaljs(session,
-               js"""
-               const select = $(taglist);
-               select.innerHTML = $html_dom;
-               """)
-    end
+function lowerdom(ts::TagSelector)
+    taglist = DOM.div(lowertags(ts.selected[], ts.selectable); id="tag-selector")
     elements = [taglist]
     if ts.showtoggle
         multitag = filterevents(ts.controller).allow_multiple_tags
+        toggle_multitag = js"(evt)=>{ $multitag.notify(evt.srcElement.checked); }"
         checkbox = DOM.div(DOM.input(type="checkbox",
                                      checked=multitag,
-                                     onchange=js"""
-                                     JSServe.update_obs($(multitag), this.checked);
-                                     """,
+                                     onchange=toggle_multitag,
                                      class="m-1"),
                            DOM.label("Multiple tags per time step");
                            class="flex flex-row")
         push!(elements, checkbox)
     end
-    JSServe.jsrender(session,
-                     DOM.div(elements...;
-                             class="flex flex-col",
-                             onmouseenter=js"LarvaTagger.focusOnTimeSlider()"))
+    return DOM.div(elements...;
+                   class="flex flex-col",
+                   onmouseenter=js"LarvaTagger.focusOnTimeSlider")
+end
+function Bonito.jsrender(session::Session, ts::TagSelector)
+    setsession!(ts.controller, session)
+    node = lowerdom(session, ts)
+    if ts.selectable
+        evaljs(session, js"LarvaTagger.setTagSelector($(ts.fromjs))")
+    end
+    selectedtags = ts.selected
+    prevtags = Ref(empty(selectedtags[]))
+    on(session, selectedtags) do selected
+        selected == prevtags[] && return
+        prevtags[] = selected
+        jsdom = r(session, lowertags(selected, ts.selectable))
+        htmldom = replace(string(jsdom), "'" => "\\'")
+        evaljs(session, js"""
+        document.getElementById('tag-selector').innerHTML = $htmldom;
+        let tags = document.querySelectorAll('#tag-selector option');
+        for (let i = 0; i < tags.length; i++) {
+            tags[i].addEventListener('mousedown', LarvaTagger.toggleTagAtPointer);
+        }
+        """)
+    end
+    return node
 end
 
 function tagselector(controller;
@@ -1025,9 +836,9 @@ end
 
 mutable struct TrackViewer
     plot::TrackPlot
-    player::Player
+    player::PlayerView
     tagselector::TagSelector
-    dom
+    dom_id
 end
 
 function trackviewer(controller;
@@ -1036,102 +847,86 @@ function trackviewer(controller;
         kwargs...
     )
     model = getlarvae(controller)
+    isempty(model) && return nothing
     plot = trackplot(model, controller; editabletags=editabletags, kwargs...)
     play = player(controller)
     sele = tagselector(controller; editabletags=editabletags, multipletags=multipletags)
-    TrackViewer(plot, play, sele, nothing)
+    TrackViewer(plot, play, sele, dom_id())
 end
 
-function JSServe.jsrender(session::Session, tv::TrackViewer)
-    dom = DOM.div(r(session, tv.plot),
-                  r(session, tv.player),
-                  r(session, tv.tagselector);
-                  class="flex flex-col",
-                  style="display: none;",
-                  id="trackviewer",
-                  onmouseenter=js"LarvaTagger.focusOnTimeSlider(this)",
-                 )
-    tv.dom = dom
-    r(session, dom)
+function lowerdom(tv::TrackViewer)
+    focus = js"(evt)=>{ LarvaTagger.focusOnTimeSlider(evt.srcElement); }"
+    return DOM.div(tv.plot,
+                   tv.player,
+                   tv.tagselector;
+                   class="flex flex-col",
+                   style="display: none;",
+                   id="trackviewer",
+                   onmouseenter=focus)
 end
+Bonito.jsrender(session::Session, tv::TrackViewer) = lowerdom(session, tv)
 
 struct MetadataEditor
     controller
     max_entries::Int
+    dom_id
 end
 
-MetadataEditor(controller) = MetadataEditor(controller, 10)
+MetadataEditor(controller) = MetadataEditor(controller, 10, dom_id())
 
 metadataeditor(controller) = MetadataEditor(controller)
 
-function JSServe.jsrender(session::Session, me::MetadataEditor)
+function lowerdom(me::MetadataEditor)
     table = getmetadatatable(me.controller, me.max_entries)
-    dom = DOM.div(DOM.div(DOM.input(value=fieldname,
-                                    type="text",
-                                    class="headerfield",
-                                    onchange=js"""
-                                    JSServe.update_obs($(fieldname), this.value);
-                                    """),
-                          DOM.input(value=fieldvalue,
-                                    type="text",
-                                    class="valuefield",
-                                    onchange=js"""
-                                    JSServe.update_obs($(fieldvalue), this.value);
-                                    """);
-                          class="flex flex-row")
-                  for (fieldname, fieldvalue) in pairs(table);
-                  id="metadata-panel",
-                  class="flex flex-col panel")
-    r(session, dom)
+    return DOM.div(DOM.div(DOM.input(value=fieldname,
+                                     type="text",
+                                     class="headerfield",
+                                     onchange=js"""(evt)=>{
+                                        $fieldname.notify(evt.srcElement.value);
+                                     }"""),
+                           DOM.input(value=fieldvalue,
+                                     type="text",
+                                     class="valuefield",
+                                     onchange=js"""(evt)=>{
+                                        $fieldvalue.notify(evt.srcElement.value);
+                                     }""");
+                           class="flex flex-row")
+                   for (fieldname, fieldvalue) in pairs(table);
+                   id="metadata-panel",
+                   class="flex flex-col panel")
 end
+Bonito.jsrender(session::Session, me::MetadataEditor) = lowerdom(session, me)
 
 struct FileMenu
     controller::ControllerHub
-    menuselection::Observable{String}
-    attributes
+    browser::FileBrowser
 end
 
 function filemenu(controller; kwargs...)
-    selection = Observable("")
-    menu = FileMenu(gethub(controller),
-                    selection,
-                    with_attributes(kwargs; size=5))
     wd = getworkingdir(controller)
-    on(selection) do file
-        if '/' in file
-            @logmsg SecurityAlert "'/' character found in file name" file client=identifyclient(controller)
-        elseif !isempty(file)
-            cwd(wd, file)
-        end
+    dir = joinpath(wd.root, wd.path[])
+    browser = FileBrowser(dir; root=wd.root, upload_button=false)
+    on(FileBrowsers.selectedfile(browser)) do file
+        tryopenfile(controller, file; reload=true)
     end
-    return menu
-end
-
-function JSServe.jsrender(session::Session, fm::FileMenu)
-    wdcontent = getwdcontent(fm.controller)
-    inputfile = getinputfile(fm.controller)
-    select = r(session,
-               DOM.select(multiple=true;
-                          with_attributes(fm.attributes; class="directory-content")...))
-    onjs(session, wdcontent, js"(directory_entries) =>
-         LarvaTagger.updateSelectOptions($select, directory_entries, $(fm.menuselection))")
-    wd = map(getpath(fm.controller)) do path
-        @assert !isempty(path)
-        if isempty(path) || path == "."
-            "."
-        else
-            parts = splitpath(path)
-            length(parts) == 1 ? "./" * parts[1] : ".../" * parts[end]
+    workingdir = FileBrowsers.workingdir(browser)
+    on(workingdir) do _
+        dir = workingdir[]
+        # maintain the deprecated WorkingDirectory as it may still be in use elsewhere
+        wd.path[] = dir
+        wd.content[] = [d for (_, d) in browser.model.content[]]
+    end
+    if FileBrowsers.supports_upload(browser)
+        on(FilePickers.uploadedfile(browser)) do fileinfo
+            filepath = joinpath(workingdir[], fileinfo["name"])
+            saveinputfile(filepath, fileinfo["content"])
         end
     end
-    dom = DOM.div(DOM.input(value=wd,
-                            type="text",
-                            readonly=true),
-                  select;
-                  id="filemenu-panel",
-                  class="flex flex-col panel")
-    notify(wdcontent) # populate the select element
-    r(session, dom)
+    return FileMenu(gethub(controller), browser)
+end
+
+function Bonito.jsrender(session::Session, fm::FileMenu)
+    r(session, DOM.div(fm.browser; class="panel"))
 end
 
 function globalrefresh(::Type{Session}, controller)
@@ -1161,41 +956,54 @@ end
 
 struct BackendMenu
     backends::Backends
-    send2backend::Observable
+    send2backend::NyxWidgets.Button
+    dom_id
 end
 
 function backendmenu(controller, location)
     backends = getbackends(controller, location)
-    send2backend = Observable(false)
+    disabled = map(isempty, backends.model_instances)
+    send2backend = NyxWidgets.Button("Autotag"; disabled=disabled)
     on(send2backend) do _
         predict(backends, controller[:input][])
     end
-    BackendMenu(backends, send2backend)
+    BackendMenu(backends, send2backend, dom_id())
 end
 
-function JSServe.jsrender(session::Session, bs::BackendMenu)
+function lowerdom(bs::BackendMenu)
     models = bs.backends.model_instances
+    model_instance = bs.backends.model_instance
+    update_model_instance = js"(evt)=>{ $model_instance.notify(evt.srcElement.value); }"
     select_dom = DOM.select(DOM.option(model; value=model) for model in models[];
-                         onchange=js"JSServe.update_obs($(bs.backends.model_instance), this.value)",
-                         class=css_button)
-    onjs(session, models, js"(options) => LarvaTagger.updateSelectOptions($select_dom, options)")
-    disabled = map(isempty, models)
-    dom = DOM.div(DOM.label("Backend"),
-                  DOM.select(DOM.option(backend; value=backend) for backend in bs.backends.backends;
-                             onchange=js"JSServe.update_obs($(bs.backends.active_backend), this.value)",
-                             class=css_button),
-                  DOM.label("Model instance"),
-                  select_dom,
-                  DOMtrigger("Autotag", bs.send2backend; disabled=disabled);
-                  class="flex flex-col panel")
-    r(session, dom)
+                            onchange=update_model_instance,
+                            class=css_button)
+    active_backend = bs.backends.active_backend
+    update_active_backend = js"(evt)=>{ $active_backend.notify(evt.srcElement.value); }"
+    return DOM.div(DOM.label("Backend"),
+                   DOM.select(DOM.option(backend; value=backend)
+                              for backend in bs.backends.backends;
+                              onchange=update_active_backend,
+                              class=css_button),
+                   DOM.label("Model instance"),
+                   select_dom,
+                   bs.send2backend;
+                   class="flex flex-col panel")
+end
+function Bonito.jsrender(session::Session, bs::BackendMenu)
+    node = lowerdom(session, bs)
+    onjs(session, bs.backends.model_instances, js"""(options) => {
+         select = document.querySelectorAll($(dom_selector(bs)) + ' > select')[1];
+         LarvaTagger.updateSelectOptions(select, options);
+    }""")
+    return node
 end
 
 struct LoadAnimation
     visible::Observable{Bool}
+    dom_id
 end
 
-LoadAnimation() = LoadAnimation(Observable(true))
+LoadAnimation() = LoadAnimation(Observable(true), dom_id())
 
 function loadanimation(controller)
     la = LoadAnimation()
@@ -1203,12 +1011,13 @@ function loadanimation(controller)
     return la
 end
 
-function JSServe.jsrender(session::Session, la::LoadAnimation)
-    dom = DOM.div(DOM.div();
-                  id="load-animation",
-                  class="la-ball-clip-rotate")
-    onjs(session, la.visible, js"(b) => $(dom).style.display = (b ? 'block' : 'none')")
-    r(session, dom)
+function lowerdom(::LoadAnimation)
+    DOM.div(DOM.div(); id="load-animation", class="la-ball-clip-rotate")
+end
+function Bonito.jsrender(session::Session, la::LoadAnimation)
+    node = lowerdom(session, la)
+    onjs(session, la.visible, js"(b)=>{ $node.style.display = (b ? 'block' : 'none'); }")
+    return node
 end
 
 struct TwoOptionDialog
@@ -1219,6 +1028,7 @@ struct TwoOptionDialog
     button2::Observable{String}
     visible::Observable{Bool}
     attributes
+    dom_id
 end
 
 TwoOptionDialog(; kwargs...) = TwoOptionDialog(Observable(true),
@@ -1227,7 +1037,8 @@ TwoOptionDialog(; kwargs...) = TwoOptionDialog(Observable(true),
                                                Observable(""),
                                                Observable(""),
                                                Observable(false),
-                                               with_attributes(kwargs; style="display:none"))
+                                               with_attributes(kwargs; style="display:none"),
+                                               dom_id())
 
 twooptiondialog(controller) = get!(gethub(controller), :twooptiondialog, TwoOptionDialog())
 
@@ -1251,28 +1062,27 @@ function twooptiondialog(controller, answer, title, message, button1, button2)
     end
 end
 
-function JSServe.jsrender(session::Session, tod::TwoOptionDialog)
-    dom = DOM.div(DOM.div(tod.title[]; id="two-option-dialog-title"),
-                  DOM.div(tod.prompt[]; id="two-option-dialog-prompt"),
-                  DOM.div(
-                  DOM.button(tod.button1[];
-                             type="button",
-                             id="two-option-dialog-button1",
-                             class="button",
-                             onclick=js"""
-                             JSServe.update_obs($(tod.answer), true);
-                             """),
-                  DOM.button(tod.button2[];
-                             type="button",
-                             id="two-option-dialog-button2",
-                             class="button",
-                             onclick=js"""
-                             JSServe.update_obs($(tod.answer), false);
-                             """);
-                  id="two-option-dialog-buttons");
-                  id="two-option-dialog",
-                  tod.attributes...)
-    onjs(session, tod.visible, js"(b) => $(dom).style.display = (b ? 'block' : 'none')")
-    r(session, dom)
+function lowerdom(tod::TwoOptionDialog)
+    DOM.div(DOM.div(tod.title[]; id="two-option-dialog-title"),
+            DOM.div(tod.prompt[]; id="two-option-dialog-prompt"),
+            DOM.div(
+                    DOM.button(tod.button1[];
+                               type="button",
+                               id="two-option-dialog-button1",
+                               class="button",
+                               onclick=js"()=>{ $(tod.answer).notify(true); }"),
+                    DOM.button(tod.button2[];
+                               type="button",
+                               id="two-option-dialog-button2",
+                               class="button",
+                               onclick=js"()=>{ $(tod.answer).notify(false); }");
+                    id="two-option-dialog-buttons");
+            id="two-option-dialog",
+            tod.attributes...)
+end
+function Bonito.jsrender(session::Session, tod::TwoOptionDialog)
+    node = lowerdom(session, tod)
+    onjs(session, tod.visible, js"(b)=>{ $node.style.display = (b ? 'block' : 'none'); }")
+    return node
 end
 
diff --git a/test/deploy_and_test.sh b/test/deploy_and_test.sh
index 42a2db685c2f28bdff0a97d47ef549c181182743..942ffd26d6355a6156b610668ec3de26bc7b69eb 100755
--- a/test/deploy_and_test.sh
+++ b/test/deploy_and_test.sh
@@ -10,22 +10,21 @@
 # * LOCAL_SCENARII: can be set to 1 to overwrite LarvaTagger.jl's test/scenarii.sh file
 #   with the scenarii.sh file that comes along the present script (same directory)
 # * LAUNCHER: default is "srun" if Slurm is available, otherwise nothing
-# * JULIA_PROJECT: where to find PyCall (do not modify)
 
 # Example on Maestro (GPU-accelerated):
 #   SLURM_OPTS='-p dbc_pmo -q dbc -c 4' JULIA_THREADS=4 ./deploy_and_test.sh
 # or (CPU-only):
 #   SLURM_OPTS='-p dedicated -q fast -c 16' JULIA_THREADS=16 ./deploy_and_test.sh
 
-if [ -n "$1" ] && [ "$1" = "-f" ] || [ "$1" = "--force" ]; then
-  rm -rf LarvaTagger
-elif [ -d LarvaTagger ]; then
-  echo "\`$0\` needs to make a local LarvaTagger directory"
-  echo "but this directory already exists."
-  echo "Run \`$0 --force\` or pick another location."
+if ! [ -f scripts/install.sh ]; then
+  echo "Call `basename $0` from the project's root directory:"
+  echo "    test/deploy_and_test.sh"
   exit 1
 fi
 
+scripts/install.sh --uninstall
+scripts/install.sh --with-backend --experimental
+
 #############
 ## Maestro ##
 #############
@@ -33,13 +32,6 @@ fi
 # SSL verification can be disabled for git clone to work:
 #   export GIT_SSL_NO_VERIFY=true
 
-if ! julia -v &> /dev/null; then
-  # do NOT use juliaup to install Julia
-  module load Python &> /dev/null # Maestro
-  python3 -m pip install jill
-  python3 -m jill install -v 1.10.0 -c
-fi
-
 # h5diff (for tests) on Maestro
 if ! h5diff -V &> /dev/null; then
   module load hdf5 &> /dev/null
@@ -60,68 +52,38 @@ fi
 #############
 
 CURDIR=$(pwd)
-
-mkdir -p LarvaTagger && cd $_
+LTROOT=$HOME/.local/share/larvatagger/LarvaTagger.jl
 
 # shunit2 is for tests specifically
 if [ -z "$(which shunit2)" ]; then
-  git clone -c advice.detachedHead=false -b v2.1.8 https://github.com/kward/shunit2
+  (cd "$LTROOT/test" && \
+    git clone -c advice.detachedHead=false -b v2.1.8 https://github.com/kward/shunit2)
 fi
 
-## comment out the below line to build a development environment
-#CLONE_OPTS=" --depth 1 --no-tags --single-branch -b dev"
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/planarlarvae.jl PlanarLarvae
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/LarvaTagger.jl
-BRANCH=dev
-wget -O- https://gitlab.pasteur.fr/nyx/planarlarvae.jl/-/archive/${BRANCH}/planarlarvae.jl-${BRANCH}.tar.gz | tar zxv && mv planarlarvae.jl-${BRANCH} PlanarLarvae
-wget -O- https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/archive/${BRANCH}/larvatagger.jl-${BRANCH}.tar.gz | tar zxv && mv larvatagger.jl-${BRANCH} LarvaTagger.jl
-ln -s $(realpath LarvaTagger.jl) LarvaTagger
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/TaggingBackends
-#git clone$CLONE_OPTS https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter
-wget -O- https://gitlab.pasteur.fr/nyx/TaggingBackends/-/archive/${BRANCH}/TaggingBackends-${BRANCH}.tar.gz | tar zxv && mv TaggingBackends-${BRANCH} TaggingBackends
-wget -O- https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter/-/archive/${BRANCH}/MaggotUBA-adapter-${BRANCH}.tar.gz | tar zxv && mv MaggotUBA-adapter-${BRANCH} MaggotUBA-adapter
-ln -s $(realpath MaggotUBA-adapter) MaggotUBA
-
-# the poetry install step may not be necessary
-(cd TaggingBackends && julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.develop(path="../PlanarLarvae")' && poetry env use python3.8 && poetry install)
-# the step below may not be necessary
-(cd TaggingBackends && JULIA_PROJECT=$(pwd) poetry run python -c 'import julia; julia.install()')
-
-(cd MaggotUBA && poetry env use python3.8 && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"') && poetry install -v && poetry remove taggingbackends && poetry add ../TaggingBackends && scripts/make_models.jl default)
-
-cd LarvaTagger || exit 1
-julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.develop(path="../PlanarLarvae")'
-
-# the remainder of this script is test-specific
-
-if [ -f ../../LarvaTagger_test_data.tgz ]; then
+if [ -f LarvaTagger_test_data.tgz ]; then
   # To generate the test data, first run the tests with environment variable KEEP_MODEL_FILES=1
   # copy the LarvaTagger/MaggotUBA/models/test_train_* directories into LarvaTagger/LarvaTagger.jl/test/data
   # and build the archive from the LarvaTagger/LarvaTagger.jl directory.
   # For example:
   #   KEEP_MODEL_FILES=1 ./deploy_and_test.sh
-  #   cd LarvaTagger/LarvaTagger.jl
+  #   cd ~/.local/share/larvatagger/LarvaTagger/LarvaTagger.jl
   #   rm -rf test/data/test_train_*
   #   cp -Rp ../MaggotUBA/models/test_train_* test/data/
   #   tar zcvf LarvaTagger_test_data.tgz test/data/*
-  #   mv LarvaTagger_test_data.tgz ../../
-  tar zxvf ../../LarvaTagger_test_data.tgz
+  (cd "$LTROOT" && tar zxvf LarvaTagger_test_data.tgz)
 else
   # Not recommended; reproducibility is not guarantee across hosts or architectures yet
-  #wget -O- https://dl.pasteur.fr/fop/8MvgygD4/LarvaTagger_test_data.tgz | tar zxv
-  wget -O- https://dl.pasteur.fr/fop/WdnyHdKR/240208_LarvaTagger_test_data.tgz | tar zxv
+  (cd "$LTROOT" && \
+    wget -O- https://dl.pasteur.fr/fop/ppk8GBQf/241127_LarvaTagger_test_data.tgz | tar zxv)
 fi
 
 if [ "$LOCAL_SCENARII" = "1" ]; then
-  if [ -f "$CURDIR/scenarii.sh" ]; then cp "$CURDIR/scenarii.sh" test/
-  elif [ -f "$CURDIR/test/scenarii.sh" ]; then cp "$CURDIR/test/scenarii.sh" test/
-  else echo "Cannot find a local scenarii.sh file"
+  if [ -f "test/scenarii.sh" ]; then
+    cp "test/scenarii.sh" "$LTROOT/test/"
+  else
+    echo "Cannot find a local scenarii.sh file"
   fi
 fi
 
-if [ -z "$(which shunit2)" ]; then
-  ln -s "$CURDIR/LarvaTagger/shunit2/shunit2" test/
-fi
-
 echo "${launcher}test/scenarii.sh"
-${launcher}test/scenarii.sh
+(cd "$LTROOT" && ${launcher}test/scenarii.sh)