From 401bfc931334b10f2ad5498438d8f380b7df2405 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 00:03:00 +0000
Subject: [PATCH 01/49] Chore(deps): Bump undici from 5.28.4 to 5.28.5

Bumps [undici](https://github.com/nodejs/undici) from 5.28.4 to 5.28.5.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.4...v5.28.5)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 package-lock.json | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index d07b2090e..baea24248 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20382,9 +20382,10 @@
       }
     },
     "node_modules/undici": {
-      "version": "5.28.4",
-      "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
-      "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
+      "version": "5.28.5",
+      "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
+      "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
+      "license": "MIT",
       "dependencies": {
         "@fastify/busboy": "^2.0.0"
       },
@@ -35220,9 +35221,9 @@
       "requires": {}
     },
     "undici": {
-      "version": "5.28.4",
-      "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
-      "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
+      "version": "5.28.5",
+      "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
+      "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
       "requires": {
         "@fastify/busboy": "^2.0.0"
       }

From db5fd0d901c018cdade45d94918d28912d19372a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 22 Jan 2025 00:03:04 +0000
Subject: [PATCH 02/49] Chore(deps-dev): Bump vite from 4.5.3 to 4.5.9

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.3 to 4.5.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.9/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 package-lock.json | 15 ++++++++-------
 package.json      |  2 +-
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index d07b2090e..7f25d7c8d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -126,7 +126,7 @@
         "ts2json-schema": "^1.4.0",
         "tslib": "^2.5.0",
         "typescript": "^4.7.4",
-        "vite": "^4.5.3"
+        "vite": "^4.5.9"
       }
     },
     "node_modules/@aashutoshrathi/word-wrap": {
@@ -20562,9 +20562,10 @@
       }
     },
     "node_modules/vite": {
-      "version": "4.5.3",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
-      "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
+      "version": "4.5.9",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz",
+      "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==",
+      "license": "MIT",
       "dependencies": {
         "esbuild": "^0.18.10",
         "postcss": "^8.4.27",
@@ -35340,9 +35341,9 @@
       }
     },
     "vite": {
-      "version": "4.5.3",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
-      "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
+      "version": "4.5.9",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz",
+      "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==",
       "requires": {
         "esbuild": "^0.18.10",
         "fsevents": "~2.3.2",
diff --git a/package.json b/package.json
index 9ab61fc96..01dc8c875 100644
--- a/package.json
+++ b/package.json
@@ -277,6 +277,6 @@
     "ts2json-schema": "^1.4.0",
     "tslib": "^2.5.0",
     "typescript": "^4.7.4",
-    "vite": "^4.5.3"
+    "vite": "^4.5.9"
   }
 }

From c0920bff9f112f33aab903c3441408490089869c Mon Sep 17 00:00:00 2001
From: Osmwithspace <>
Date: Mon, 27 Jan 2025 14:48:22 +0100
Subject: [PATCH 03/49] copy from mapcomplete studio

---
 .../layers/group_campsite/group_campsite.json | 235 ++++++++++++++++++
 1 file changed, 235 insertions(+)
 create mode 100644 assets/layers/group_campsite/group_campsite.json

diff --git a/assets/layers/group_campsite/group_campsite.json b/assets/layers/group_campsite/group_campsite.json
new file mode 100644
index 000000000..2e1d4c740
--- /dev/null
+++ b/assets/layers/group_campsite/group_campsite.json
@@ -0,0 +1,235 @@
+{
+  "credits": "Osmwithspace",
+  "minzoom": 7,
+  "filter": [
+    {
+      "id": "capacity_persons_filter",
+      "options": [
+        {
+          "question": {
+            "en": "persons",
+            "de": "Personen"
+          }
+        },
+        {
+          "question": {
+            "en": "1-20"
+          },
+          "osmTags": {
+            "and": [
+              "capacity:persons>=1",
+              "capacity:persons<=20"
+            ]
+          }
+        },
+        {
+          "question": {
+            "en": "21-50"
+          },
+          "osmTags": {
+            "and": [
+              "capacity:persons>=21",
+              "capacity:persons<=50"
+            ]
+          }
+        },
+        {
+          "question": {
+            "en": "51-100"
+          },
+          "osmTags": {
+            "and": [
+              "capacity:persons>=51",
+              "capacity:persons<=100"
+            ]
+          }
+        },
+        {
+          "question": {
+            "en": "101-200"
+          },
+          "osmTags": {
+            "and": [
+              "capacity:persons>=101",
+              "capacity:persons<=200"
+            ]
+          }
+        },
+        {
+          "question": {
+            "en": "201-500"
+          },
+          "osmTags": {
+            "and": [
+              "capacity:persons>=201",
+              "capacity:persons<=500"
+            ]
+          }
+        },
+        {
+          "question": {
+            "en": "500+"
+          },
+          "osmTags": "capacity:persons>=501"
+        },
+        {
+          "question": {
+            "en": "?"
+          },
+          "osmTags": "capacity:persons="
+        }
+      ]
+    }
+  ],
+  "pointRendering": [
+    {
+      "location": [
+        "point",
+        "centroid"
+      ],
+      "marker": [
+        {
+          "icon": {
+            "render": "https://opencampingmap.org/markers/l_standard.svg",
+            "mappings": [
+              {
+                "if": {
+                  "or": [
+                    "scout=yes",
+                    "group_only=yes"
+                  ]
+                },
+                "then": "https://wiki.openstreetmap.org/w/images/6/6b/OCM_l_group_only.svg"
+              }
+            ]
+          }
+        }
+      ]
+    }
+  ],
+  "tagRenderings": [
+    {
+      "question": {
+        "de": "Wie heißt dieser Zeltplatz?",
+        "en": "What is the name of this campsite?"
+      },
+      "render": {
+        "en": "The name of this campsite is {name}",
+        "de": "Dieser Zeltplatz heißt {name}"
+      },
+      "freeform": {
+        "key": "name"
+      },
+      "id": "Name"
+    },
+    {
+      "id": "fee",
+      "question": {
+        "en": "Is a fee charged here?",
+        "de": "Wird hier eine Gebühr erhoben?"
+      },
+      "render": {
+        "en": "A fee of {charge} should be paid for here",
+        "de": "Hier wird eine Gebühr von {charge} erhoben"
+      },
+      "freeform": {
+        "key": "charge",
+        "type": "currency",
+        "addExtraTags": [
+          "fee=yes"
+        ],
+        "inline": true
+      },
+      "mappings": [
+        {
+          "if": "fee=no",
+          "addExtraTags": [
+            "charge="
+          ],
+          "then": {
+            "en": "The campsite is free of charge",
+            "de": "Der Zeltplatz ist kostenlos"
+          }
+        },
+        {
+          "if": {
+            "and": [
+              "fee=yes",
+              "charge="
+            ]
+          },
+          "then": {
+            "en": "A fee is charged here.",
+            "de": "Hier wird eine Gebühr erhoben."
+          },
+          "hideInAnswer": "charge~*"
+        }
+      ]
+    },
+    {
+      "question": {
+        "de": "Wie viele Personen können hier übernachten?",
+        "en": "How many people can stay here?"
+      },
+      "render": {
+        "en": "{capacity:persons} people can stay here",
+        "de": "Hier können {capacity:persons} Personen übernachten"
+      },
+      "freeform": {
+        "key": "capacity:persons",
+        "type": "pnat"
+      },
+      "id": "capacity:persons"
+    },
+    "contact",
+    "questions",
+    "mastodon",
+    "images"
+  ],
+  "lineRendering": [
+    {
+      "width": 1,
+      "color": "blue"
+    }
+  ],
+  "credits:uid": 8770388,
+  "id": "groupcampsites",
+  "name": {
+    "en": "Campsites for groups and scouts",
+    "de": "Zeltplatz für Gruppen/Pfadfinder:innen"
+  },
+  "description": {
+    "en": "Campsites for groups and scouts",
+    "de": "Zeltplatz für Gruppen/Pfadfinder:innen"
+  },
+  "title": {
+    "render": {
+      "en": "{name}"
+    }
+  },
+  "source": {
+    "osmTags": {
+      "and": [
+        "tourism=camp_site",
+        {
+          "or": [
+            "scout=yes",
+            "group_only=yes"
+          ]
+        }
+      ]
+    }
+  },
+  "shownByDefault": true,
+  "presets": [
+    {
+      "title": {
+        "en": "campsite for groups"
+      },
+      "tags": [
+        "tourism=camp_site",
+        "group_only=yes"
+      ]
+    }
+  ]
+}
\ No newline at end of file

From 1cd37c0fead7451f6fef671d9f8fcc92eccb5cd6 Mon Sep 17 00:00:00 2001
From: Weblate Admin <pietervdvn@posteo.net>
Date: Tue, 28 Jan 2025 14:42:29 +0000
Subject: [PATCH 04/49] Translated using Weblate (Dutch)

Currently translated at 80.1% (3309 of 4126 strings)

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/nl/
---
 langs/layers/nl.json | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/langs/layers/nl.json b/langs/layers/nl.json
index c6e4cd01e..08f61e7d7 100644
--- a/langs/layers/nl.json
+++ b/langs/layers/nl.json
@@ -10908,5 +10908,13 @@
             },
             "render": "windturbine"
         }
+    },
+    "cyclist_waiting_aid": {
+        "title": {
+            "render": "Steuntje voor wachtende fietsers"
+        }
+    },
+    "disaster_response": {
+        "description": "Deze laag bevat organisaties met hoofddoelstelling om burgers te helpen tijdens en na rampen door in de getroffen gebieden te gaan helpen."
     }
 }
\ No newline at end of file

From cfbd00a2f50090e9e3b752db130a4302cd7d3089 Mon Sep 17 00:00:00 2001
From: Weblate <noreply@weblate.org>
Date: Wed, 29 Jan 2025 16:35:05 +0100
Subject: [PATCH 05/49] Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/
---
 langs/layers/nl.json | 42 ++----------------------------------------
 1 file changed, 2 insertions(+), 40 deletions(-)

diff --git a/langs/layers/nl.json b/langs/layers/nl.json
index 08f61e7d7..a8ed36b6f 100644
--- a/langs/layers/nl.json
+++ b/langs/layers/nl.json
@@ -2049,9 +2049,6 @@
         },
         "title": {
             "mappings": {
-                "0": {
-                    "then": "{name}"
-                },
                 "1": {
                     "then": "Vogelkijkhut {name}"
                 },
@@ -6376,11 +6373,6 @@
             }
         },
         "title": {
-            "mappings": {
-                "0": {
-                    "then": "{name}"
-                }
-            },
             "render": "Natuurgebied"
         }
     },
@@ -6912,21 +6904,6 @@
             "render": "Picknicktafel"
         }
     },
-    "play_forest": {
-        "description": "Een speelbos is een vrij toegankelijke zone in een bos",
-        "name": "Speelbossen",
-        "title": {
-            "mappings": {
-                "0": {
-                    "then": "{name}"
-                },
-                "1": {
-                    "then": "Speelbos {name}"
-                }
-            },
-            "render": "Speelbos"
-        }
-    },
     "playground": {
         "deletion": {
             "nonDeleteMappings": {
@@ -8493,9 +8470,6 @@
         },
         "title": {
             "mappings": {
-                "0": {
-                    "then": "{name}"
-                },
                 "1": {
                     "then": "Voetpad"
                 },
@@ -10661,25 +10635,13 @@
         }
     },
     "village_green": {
-        "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)",
-        "name": "Speelweide",
-        "title": {
-            "mappings": {
-                "0": {
-                    "then": "{name}"
-                }
-            },
-            "render": "Speelweide"
-        }
+        "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)"
     },
     "visitor_information_centre": {
         "description": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.",
         "name": "Bezoekerscentrum",
         "title": {
             "mappings": {
-                "0": {
-                    "then": "{name:nl}"
-                },
                 "1": {
                     "then": "{name}"
                 }
@@ -10917,4 +10879,4 @@
     "disaster_response": {
         "description": "Deze laag bevat organisaties met hoofddoelstelling om burgers te helpen tijdens en na rampen door in de getroffen gebieden te gaan helpen."
     }
-}
\ No newline at end of file
+}

From ab0e96592a09b04a1e99d9da8a9fd37f71c9c9e5 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Wed, 29 Jan 2025 21:33:10 +0100
Subject: [PATCH 06/49] Config: add app.mapcomplete.org

---
 Docs/ServerConfig/hetzner/Caddyfile | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/Docs/ServerConfig/hetzner/Caddyfile b/Docs/ServerConfig/hetzner/Caddyfile
index 33c4f55d8..33f97d039 100644
--- a/Docs/ServerConfig/hetzner/Caddyfile
+++ b/Docs/ServerConfig/hetzner/Caddyfile
@@ -46,6 +46,13 @@ single.mapcomplete.org {
 	}
 }
 
+app.mapcomplete.org {
+	root * app/
+	file_server
+	header {
+		+Permissions-Policy "interest-cohort=()"
+	}
+}
 
 velopark.mapcomplete.org {
 	root * single_theme_builds/velopark/

From 541b8f8960fa258223b9ea059301fd02aabb47be Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Thu, 30 Jan 2025 00:52:26 +0100
Subject: [PATCH 07/49] Themes(shops): fix stupid typos and bugs in healthcare
 service tagging

---
 assets/layers/shops/shops.json | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json
index a4b05ca4d..94bcbbe04 100644
--- a/assets/layers/shops/shops.json
+++ b/assets/layers/shops/shops.json
@@ -565,10 +565,7 @@
       "mappings": [
         {
           "if": "healthcare=optometrist",
-          "ifnot": "not:healtcare=optometrist",
-          "addExtraTags": [
-            "not:healthcare=optometrist"
-          ],
+          "ifnot": "not:healthcare=optometrist",
           "then": {
             "en": "This shop offers eye exams by certified optometrists",
             "nl": "Hier kan men een oogtest door een erkende optometrist laten uitvoeren",

From 47d1b6ddb1333c8cb8e3f06f834e2e0554005bf3 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Thu, 30 Jan 2025 02:03:35 +0100
Subject: [PATCH 08/49] Docs(languageElement): improve docs

---
 src/UI/Popup/LanguageElement/LanguageElement.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/UI/Popup/LanguageElement/LanguageElement.ts b/src/UI/Popup/LanguageElement/LanguageElement.ts
index 1f2dc8c6b..43e382f2a 100644
--- a/src/UI/Popup/LanguageElement/LanguageElement.ts
+++ b/src/UI/Popup/LanguageElement/LanguageElement.ts
@@ -17,7 +17,7 @@ export class LanguageElement implements SpecialVisualization {
         {
             name: "key",
             required: true,
-            doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked ",
+            doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `<key>:nl=yes` if _nl_ is picked "
         },
         {
             name: "question",

From f26f22a95c3fbd6fbe89829c9c448d241dd55225 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Thu, 30 Jan 2025 14:37:53 +0100
Subject: [PATCH 09/49] Themes(school): remove `school:for`, replace with
 school:special_needs

---
 assets/layers/school/school.json | 228 +++++++++++++++++++------------
 1 file changed, 140 insertions(+), 88 deletions(-)

diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json
index 23adb084d..06ea3eb57 100644
--- a/assets/layers/school/school.json
+++ b/assets/layers/school/school.json
@@ -312,9 +312,20 @@
         "nl": "Waarop wordt een leerling voorbereid?"
       },
       "condition": {
-        "or": [
-          "school~i~(.+;)?upper_secondary(;.+)?",
-          "school~i~(.+;)?secondary(;.+)?"
+        "and": [
+          {
+            "or": [
+              "school~i~(.+;)?upper_secondary(;.+)?",
+              "school~i~(.+;)?secondary(;.+)?"
+            ]
+          },
+          {
+            "or": [
+              "school:special_needs=no",
+              "school:special_needs="
+            ]
+          },
+          "special_needs:.*!~~yes"
         ]
       },
       "multiAnswer": true,
@@ -506,115 +517,139 @@
       }
     },
     {
-      "id": "target-audience",
+      "id": "is_special_needs",
       "question": {
-        "en": "Does this school target students with a special need? Which structural facilities does this school have?",
-        "nl": "Richt deze school zich op leerlingen met een speciale zorgbehoefte? Welke structurele faciliteiten heeft deze school voor leerlingen met een extra zorgbehoefte?",
-        "de": "Richtet sich diese Schule an Schüler mit besonderem Förderbedarf? Über welche strukturellen Einrichtungen verfügt diese Schule?",
-        "fr": "Est-ce que cet établissement scolaire s'adresse aux étudiants ayant des besoins particuliers? Quels types d'installation est-ce que cet établissement possède?",
-        "ca": "Aquesta escola es dirigeix a estudiants amb necessitats especials? Quines instal·lacions estructurals té aquesta escola?",
-        "cs": "Zaměřuje se tato škola na studenty se speciálními potřebami? Jaké stavební vybavení má tato škola?",
-        "es": "¿Está esta escuela dirigida a alumnos con necesidades especiales? ¿Qué instalaciones estructurales tiene esta escuela?"
+        "en": "Does this school target students with a special need?",
+        "nl": "Richt deze school zich op leerlingen met een speciale zorgbehoefte?",
+        "de": "Richtet sich diese Schule an Schüler mit besonderem Förderbedarf??",
+        "fr": "Est-ce que cet établissement scolaire s'adresse aux étudiants ayant des besoins particuliers?",
+        "ca": "Aquesta escola es dirigeix a estudiants amb necessitats especials?",
+        "cs": "Zaměřuje se tato škola na studenty se speciálními potřebami?",
+        "es": "¿Está esta escuela dirigida a alumnos con necesidades especiales?"
       },
-      "multiAnswer": true,
-      "render": {
-        "en": "This school has facilities for students with {school:for}",
-        "nl": "Deze school richt zich op studenten met {school:for}",
-        "de": "Diese Schule verfügt über Einrichtungen für Schüler mit {school:for}",
-        "fr": "Cet établissement scolaire a des installations pour étudiants ayant {school:for}",
-        "ca": "Aquesta escola té instal·lacions per a estudiants amb {school:for}",
-        "cs": "Tato škola má zařízení pro studenty se {school:for}",
-        "es": "Esta escuela cuenta con instalaciones para alumnos con {school:for}"
-      },
-      "freeform": {
-        "key": "school:for",
-        "inline": true
+      "questionHint": {
+        "en": "A special needs school has expertise and supports students with a (severe) diagnosis. In many countries, a certificate is needed to enroll.",
+        "nl": "Een buitengewone school is een school waar leerlingen met een attest op speciale zorg terecht kunnen."
       },
       "mappings": [
         {
-          "if": "school:for=mainstream",
-          "alsoShowIf": "school:for=",
+          "if": "school:special_needs=only",
           "then": {
-            "en": "This is a school for students without special needs<div class='subtle'>This includes students who can follow the courses with small, ad hoc measurements</div>",
-            "nl": "Deze school richt zich op studenten zonder extra zorgbehoefte<div class='subtle>Dit omvat leerlingen waarbij kleine, ad-hoc maatregelen volstaan om de lessen te volgen.</div>",
-            "de": "Dies ist eine Schule für Schüler ohne besondere Bedürfnisse<div class='subtle'>Dazu gehören auch Schüler, die den Kursen mit kleinen Ad-hoc-Maßnahmen folgen können</div>",
-            "fr": "C'est un établissement scolaire pour étudiants sans besoins particuliers. <div class='subtle'>Sont inclus les étudiants qui peuvent suivre les cours avec de petites adaptations.</div>",
-            "ca": "Aquesta és una escola per a estudiants sense necessitats especials <div class='subtle'> Açò inclou alumnes que poden seguir les classes amb petites mesures</div>",
-            "cs": "Toto je škola pro studenty bez speciálních potřeb<div class='subtle'>To zahrnuje studenty, kteří mohou absolvovat kurzy s malými, ad hoc měřeními</div>",
-            "es": "Esta es una escuela para alumnos sin necesidades especiales<div class='subtle'>Esto incluye alumnos que pueden seguir los cursos con pequeñas medidas improvisadas</div>"
+            "en": "This school is only for special need students; a certificate is needed to enroll",
+            "nl": "Deze school is enkel voor buitengewone leerlingen. Je hebt een attest nodig om hier school te mogen lopen."
           }
         },
         {
-          "if": "school:for=learning_disabilities",
+          "if": "school:special_needs=separated",
           "then": {
-            "en": "This is a school for students with learning disabilities",
-            "nl": "Deze school richt zich op leerlingen met een leerprobleem",
-            "de": "Dies ist eine Schule für Schüler mit Lernschwierigkeiten",
-            "fr": "C'est un établissement scolaire pour les étudiants ayant des troubles d’apprentissage",
-            "ca": "Aquesta és una escola per a estudiants amb dificultats de l'aprenentatge",
-            "cs": "Jedná se o školu pro žáky s poruchami učení",
-            "es": "Esta es una escuela para alumnos con dificultades de aprendizaje"
+            "en": "This school has a separate section for special need students.",
+            "nl": "Deze school heeft een apart deel voor buitengewone leerlingen. "
           }
         },
         {
-          "if": "school:for=blind",
+          "if": "school:special_needs=mixed",
           "then": {
-            "en": "This is a school for blind students or students with sight impairments",
-            "nl": "Deze school richt zich op blinde en slechtziende studenten",
-            "de": "Dies ist eine Schule für blinde oder sehbehinderte Schüler",
-            "fr": "C'est un établissement scolaire pour les étudiants aveugles ou malvoyants",
-            "ca": "Aquesta és una escola per a estudiants cecs o estudiants amb deficiències visuals",
-            "cs": "Jedná se o školu pro nevidomé studenty nebo studenty se zrakovým postižením",
-            "es": "Esta es una escuela para alumnos ciegos o con discapacidad visual"
+            "en": "Students with special needs and non-special need students have classes together.",
+            "nl": "Buitengewone (geattesteerde) leerlingen en leerlingen zonder extra zorgnood zitten samen in de klas."
           }
         },
         {
-          "if": "school:for=deaf",
+          "if": "school:special_needs=limited",
           "then": {
-            "en": "This is a school for deaf students or students with hearing impairments",
-            "nl": "Deze school richt zich op dove en hardhorende studenten",
-            "de": "Dies ist eine Schule für gehörlose oder hörgeschädigte Schüler",
-            "fr": "C'est un établissement scolaire pour les étudiants sourds ou malentendants",
-            "ca": "Aquesta és una escola per a estudiants sords o amb dificultats auditives",
-            "pl": "To jest szkoła dla uczniów głuchych i słabosłyszących",
-            "cs": "Jedná se o školu pro neslyšící studenty nebo studenty se sluchovým postižením",
-            "es": "Esta es una escuela para alumnos sordos o con discapacidad auditiva"
+            "en": "This school offers limited, ad hoc support but has no significant expertise and is not considered a special needs school.",
+            "nl": "Deze school biedt ad hoc, beperkte extra zorg aan maar telt niet als buitengwoon onderwij.s"
           }
         },
         {
-          "if": "school:for=disabilities",
+          "if": "school:special_needs=no",
           "then": {
-            "en": "This is a school for students with disabilities",
-            "nl": "Deze school richt zich op studenten met een beperking",
-            "de": "Dies ist eine Schule für Schüler mit Behinderungen",
-            "fr": "C'est un établissement scolaire pour les étudiants en situation de handicap",
-            "ca": "Aquesta és una escola per a estudiants amb discapacitats",
-            "pl": "To jest szkoła dla uczniów z niepełnosprawnościami",
-            "cs": "Jedná se o školu pro studenty se zdravotním postižením",
-            "es": "Esta es una escuela para alumnos con discapacidad"
-          }
-        },
-        {
-          "if": "school:for=special_needs",
-          "then": {
-            "en": "This is a school for students with special needs",
-            "nl": "Deze school richt zich op studenten met extra zorgbehoeften",
-            "de": "Dies ist eine Schule für Schüler mit besonderen Bedürfnissen",
-            "fr": "C'est un établissement scolaire pour les étudiants ayant des besoins particuliers",
-            "ca": "Aquesta és una escola per a estudiants amb necessitats especials",
-            "pl": "To jest szkoła dla uczniów z specjalnymi potrzebami",
-            "cs": "Jedná se o školu pro žáky se speciálními potřebami",
-            "es": "Esta es una escuela para alumnos con necesidades especiales"
+            "en": "This school has no support for special need students.",
+            "nl": "Deze school heeft geen ondersteuning voor buitengewone leerlingen."
           }
         }
-      ],
-      "questionHint": {
-        "en": "Ad-hoc measures are not enough to count as a special-needs school",
-        "nl": "Ad-hoc maatregelen zijn niet voldoende ",
-        "de": "Ad-hoc-Maßnahmen reichen nicht aus, um als Förderschule zu gelten",
-        "cs": "Ad hoc opatření nestačí k tomu, aby se škola považovala za školu pro žáky se speciálními vzdělávacími potřebami",
-        "es": "Las medidas improvisadas no son suficientes para considerarse una escuela de necesidades especiales"
-      }
+      ]
+    },
+    {
+      "id": "special_needs_categories_be",
+      "condition": {
+        "and": [
+          "_country=be",
+          "school:special_needs!~no",
+          "school:special_needs!="
+        ]
+      },
+      "question": {
+        "en": "What type of special needs are given here?",
+        "nl": "Welke soorten zorg voor buitengewone leerlingen is hier beschikbaar?"
+      },
+      "multiAnswer": true,
+      "mappings": [
+        {
+          "if": "special_needs:intellectual_disability=yes",
+          "ifnot": "special_needs:intellectual_disability=no",
+          "then": {
+            "nl": "Voor leerlingen met een verstandelijke beperking (type 2)",
+            "en": "For students with an intellectual disability (type 2)"
+          }
+        },
+        {
+          "if": "special_needs:emotional_behavioural_disorder=yes",
+          "ifnot": "special_needs:emotional_behavioural_disorder=no",
+          "then": {
+            "nl": "Voor leerlingen met een gedragsstoornis (type 3)",
+            "en": "For students with an emotional and behavioural problem (type 3)"
+          }
+        },
+        {
+          "if": "special_needs:physical_disability=yes",
+          "ifnot": "special_needs:physical_disability=no",
+          "then": {
+            "nl": "Voor leerlingen met een motorische of lichamelijke beperking (type 4)",
+            "en": "For students with an physical disability (type 4)"
+          }
+        },
+        {
+          "if": "special_needs:blind=yes",
+          "ifnot": "special_needs:blind=no",
+          "then": {
+            "nl": "Voor blinde en slechtziende leerlingen (type 6)",
+            "en": "For blind and visually impaired students (type 6)"
+          }
+        },
+        {
+          "if": "special_needs:deaf=yes",
+          "ifnot": "special_needs:deaf=no",
+          "then": {
+            "nl": "Voor dove leerlingen and leerlingen met een auditieve beperking (type 7)",
+            "en": "For deaf students and students with hearing loss (type 7)"
+          }
+        },
+        {
+          "if": "special_needs:language_disorder=yes",
+          "ifnot": "special_needs:language_disorder=no",
+          "then": {
+            "nl": "Voor leerlingen met een Spraak- of Taalontwikkelingsstoornis (type 7 - STOS/TOS)",
+            "en": "For students with a Developemental Language Disorder (type 7 - DLD)"
+          }
+        },
+        {
+          "if": "special_needs:autism=yes",
+          "ifnot": "special_needs:autism=no",
+          "then": {
+            "nl": "Voor leerlingen met een autisme spectrum stoornis (type 9)",
+            "en": "For students with an autism spectrum disorder (type 9)"
+          }
+        },
+        {
+          "if": "special_needs:learning_disabilities=yes",
+          "ifnot": "special_needs:learning_disabilities=no",
+          "hideInAnswer": "school!~.*primary.*",
+          "then": {
+            "nl": "Voor leerlingen die omwille van een leerprobleem niet in het gewone onderwijs terecht kunnen (basisaanbod)",
+            "en": "For students with a learning disability (basic offering )"
+          }
+        }
+      ]
     },
     {
       "id": "school-language",
@@ -755,6 +790,23 @@
           }
         }
       ]
+    },
+    {
+      "id": "has_special_needs",
+      "options": [
+        {
+          "question": {
+            "en": "Has special education",
+            "nl": "Buitengewoon onderwijs"
+          },
+          "osmTags": {
+            "and": [
+              "school:special_needs!=",
+              "school:special_needs!=no"
+            ]
+          }
+        }
+      ]
     }
   ],
   "allowMove": {

From 1af21714f21a8fdab670a27b0977333d493f705c Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Thu, 30 Jan 2025 14:50:40 +0100
Subject: [PATCH 10/49] UI: fix stray element in theme.html

---
 src/index.ts |  6 +++++-
 theme.html   | 32 +++++++++++++++-----------------
 2 files changed, 20 insertions(+), 18 deletions(-)

diff --git a/src/index.ts b/src/index.ts
index 36ee18a74..7a3409f4c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,13 +5,17 @@ import CustomThemeError from "./UI/CustomThemeError.svelte"
 async function main() {
     const target = document.getElementById("maindiv")
     const childs = Array.from(target.children)
+    try {
+        childs.forEach((ch) => target.removeChild(ch))
+    } catch (e) {
+        console.error("Huh? Couldn't remove child!")
+    }
     try {
         const theme = await DetermineTheme.getTheme()
         new SingleThemeGui({
             target,
             props: { theme },
         })
-        childs.forEach((ch) => target.removeChild(ch))
         Array.from(document.getElementsByClassName("delete-on-load")).forEach((el) => {
             el.parentElement.removeChild(el)
         })
diff --git a/theme.html b/theme.html
index 824e219ca..6be69eef0 100644
--- a/theme.html
+++ b/theme.html
@@ -54,27 +54,25 @@
 <a rel="me" href="https://en.osm.town/@MapComplete" class="hidden">Mastodon</a>
 
 
-<div class="h-screen" id="maindiv">
-    <div id="default-main h-full">
-        <div class="w-full h-screen flex flex-col items-center justify-between  p-8">
-            <div class="w-full h-full flex flex-col items-center">
+<div class="default-main h-screen" id="maindiv">
+  <div class="w-full h-screen flex flex-col items-center justify-between  p-8">
+    <div class="w-full h-full flex flex-col items-center">
 
-                <div id="default-title">
-                    Loading MapComplete, hang on...
-                </div>
+      <div id="default-title">
+        Loading MapComplete, hang on...
+      </div>
 
-                <p class="text-xl" id="descriptions-while-loading">
-                    <!-- DESCRIPTION START -->
-                    MapComplete is an easy to use map viewer and map editor
-                    <!-- DESCRIPTION END -->
-                </p>
-                <p>
-                    Made with OpenStreetMap
-                </p>
-            </div>
+      <p class="text-xl" id="descriptions-while-loading">
+        <!-- DESCRIPTION START -->
+        MapComplete is an easy to use map viewer and map editor
+        <!-- DESCRIPTION END -->
+      </p>
+      <p>
+        Made with OpenStreetMap
+      </p>
+    </div>
 
 
-        </div>
     </div>
 </div>
 

From ccff21338d4f8586dcda35b323be9a71c2a4e357 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 31 Jan 2025 00:04:22 +0000
Subject: [PATCH 11/49] Chore(deps): Bump dompurify

Bumps  and [dompurify](https://github.com/cure53/DOMPurify). These dependencies needed to be updated together.

Updates `dompurify` from 3.0.5 to 3.2.4
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.0.5...3.2.4)

Updates `dompurify` from 2.4.7 to 3.2.4
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.0.5...3.2.4)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
- dependency-name: dompurify
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 package-lock.json | 26 ++++++++++++++++++++------
 package.json      |  2 +-
 2 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 1de2073bf..f4816f432 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,7 +37,7 @@
         "crypto": "^1.0.1",
         "csv-parse": "^5.1.0",
         "doctest-ts-improved": "^0.8.8",
-        "dompurify": "^3.0.5",
+        "dompurify": "^3.2.4",
         "email-validator": "^2.0.4",
         "escape-html": "^1.0.3",
         "exifreader": "^4.23.5",
@@ -6917,7 +6917,9 @@
       "license": "MIT"
     },
     "node_modules/@types/trusted-types": {
-      "version": "2.0.4",
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
       "license": "MIT"
     },
     "node_modules/@types/uritemplate": {
@@ -9473,8 +9475,13 @@
       "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ=="
     },
     "node_modules/dompurify": {
-      "version": "3.0.5",
-      "license": "(MPL-2.0 OR Apache-2.0)"
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
+      "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
+      "license": "(MPL-2.0 OR Apache-2.0)",
+      "optionalDependencies": {
+        "@types/trusted-types": "^2.0.7"
+      }
     },
     "node_modules/domutils": {
       "version": "1.3.0",
@@ -26288,7 +26295,9 @@
       "version": "1.3.5"
     },
     "@types/trusted-types": {
-      "version": "2.0.4"
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
     },
     "@types/uritemplate": {
       "version": "0.3.6"
@@ -27950,7 +27959,12 @@
       "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ=="
     },
     "dompurify": {
-      "version": "3.0.5"
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
+      "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
+      "requires": {
+        "@types/trusted-types": "^2.0.7"
+      }
     },
     "domutils": {
       "version": "1.3.0",
diff --git a/package.json b/package.json
index 01dc8c875..393286916 100644
--- a/package.json
+++ b/package.json
@@ -188,7 +188,7 @@
     "crypto": "^1.0.1",
     "csv-parse": "^5.1.0",
     "doctest-ts-improved": "^0.8.8",
-    "dompurify": "^3.0.5",
+    "dompurify": "^3.2.4",
     "email-validator": "^2.0.4",
     "escape-html": "^1.0.3",
     "exifreader": "^4.23.5",

From 6c4c880127b5561656de833f785e381914b6bdeb Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Fri, 31 Jan 2025 15:15:46 +0100
Subject: [PATCH 12/49] Chore: translation sync

---
 .../cyclist_waiting_aid.json                  |   3 +-
 .../disaster_response/disaster_response.json  |   3 +-
 langs/layers/ca.json                          |  27 +----
 langs/layers/cs.json                          |  28 +----
 langs/layers/de.json                          |  28 +----
 langs/layers/en.json                          |  74 ++++++++++--
 langs/layers/es.json                          |  28 +----
 langs/layers/fr.json                          |  27 +----
 langs/layers/nl.json                          | 113 ++++++++++++++----
 langs/layers/pl.json                          |  13 --
 langs/themes/ca.json                          |   4 -
 langs/themes/cs.json                          |   4 -
 langs/themes/da.json                          |   4 -
 langs/themes/de.json                          |   4 -
 langs/themes/en.json                          |   4 -
 langs/themes/es.json                          |   4 -
 langs/themes/fr.json                          |   4 -
 langs/themes/hu.json                          |   3 -
 langs/themes/ko.json                          |   4 -
 langs/themes/nl.json                          |   4 -
 langs/themes/pa_PK.json                       |   3 -
 langs/themes/pl.json                          |   4 -
 langs/themes/zh_Hant.json                     |   3 -
 23 files changed, 176 insertions(+), 217 deletions(-)

diff --git a/assets/layers/cyclist_waiting_aid/cyclist_waiting_aid.json b/assets/layers/cyclist_waiting_aid/cyclist_waiting_aid.json
index 7016e3426..1d9f9e660 100644
--- a/assets/layers/cyclist_waiting_aid/cyclist_waiting_aid.json
+++ b/assets/layers/cyclist_waiting_aid/cyclist_waiting_aid.json
@@ -17,7 +17,8 @@
     "render": {
       "en": "Cyclist Waiting Aid",
       "de": "Radfahrer-Wartehilfe",
-      "es": "Ayuda para ciclistas en espera"
+      "es": "Ayuda para ciclistas en espera",
+      "nl": "Steuntje voor wachtende fietsers"
     }
   },
   "pointRendering": [
diff --git a/assets/layers/disaster_response/disaster_response.json b/assets/layers/disaster_response/disaster_response.json
index c6e20423c..0bc77b2f4 100644
--- a/assets/layers/disaster_response/disaster_response.json
+++ b/assets/layers/disaster_response/disaster_response.json
@@ -11,7 +11,8 @@
     "it": "Questo livello contiene organizzazioni che hanno come obiettivo principale quello di aiutare la popolazione civile durante e dopo disastri naturali o antropogenici, lavorando nell'area colpita.",
     "de": "Diese Ebene umfasst Organisationen, deren Hauptziel es ist, der Zivilbevölkerung während und nach Natur- oder anthropogenen Katastrophen zu helfen, indem sie in dem betroffenen Gebiet tätig sind.",
     "ca": "Aquesta capa conté organitzacions que tenen com a objectiu principal ajudar la població civil durant i després de desastres naturals o antropogènics treballant a la zona afectada.",
-    "es": "Esta capa contiene organizaciones que tienen como objetivo principal ayudar a la población civil durante y después de desastres naturales o antropogénicos trabajando en el área afectada"
+    "es": "Esta capa contiene organizaciones que tienen como objetivo principal ayudar a la población civil durante y después de desastres naturales o antropogénicos trabajando en el área afectada",
+    "nl": "Deze laag bevat organisaties met hoofddoelstelling om burgers te helpen tijdens en na rampen door in de getroffen gebieden te gaan helpen."
   },
   "source": {
     "osmTags": {
diff --git a/langs/layers/ca.json b/langs/layers/ca.json
index e86719019..391fd853e 100644
--- a/langs/layers/ca.json
+++ b/langs/layers/ca.json
@@ -7015,6 +7015,9 @@
                 },
                 "question": "Quins gèneres poden inscriure's a aquesta escola?"
             },
+            "is_special_needs": {
+                "question": "Aquesta escola es dirigeix a estudiants amb necessitats especials?"
+            },
             "school-language": {
                 "render": {
                     "special": {
@@ -7028,30 +7031,6 @@
             "school-name": {
                 "question": "Quin és el nom d'aquesta escola?",
                 "render": "L'escola s'anomena {name}"
-            },
-            "target-audience": {
-                "mappings": {
-                    "0": {
-                        "then": "Aquesta és una escola per a estudiants sense necessitats especials <div class='subtle'> Açò inclou alumnes que poden seguir les classes amb petites mesures</div>"
-                    },
-                    "1": {
-                        "then": "Aquesta és una escola per a estudiants amb dificultats de l'aprenentatge"
-                    },
-                    "2": {
-                        "then": "Aquesta és una escola per a estudiants cecs o estudiants amb deficiències visuals"
-                    },
-                    "3": {
-                        "then": "Aquesta és una escola per a estudiants sords o amb dificultats auditives"
-                    },
-                    "4": {
-                        "then": "Aquesta és una escola per a estudiants amb discapacitats"
-                    },
-                    "5": {
-                        "then": "Aquesta és una escola per a estudiants amb necessitats especials"
-                    }
-                },
-                "question": "Aquesta escola es dirigeix a estudiants amb necessitats especials? Quines instal·lacions estructurals té aquesta escola?",
-                "render": "Aquesta escola té instal·lacions per a estudiants amb {school:for}"
             }
         },
         "title": {
diff --git a/langs/layers/cs.json b/langs/layers/cs.json
index 1e5103f83..5c8ff9d2a 100644
--- a/langs/layers/cs.json
+++ b/langs/layers/cs.json
@@ -7997,6 +7997,9 @@
                 },
                 "question": "Které pohlaví se může na tuto školu přihlásit?"
             },
+            "is_special_needs": {
+                "question": "Zaměřuje se tato škola na studenty se speciálními potřebami?"
+            },
             "pedagogy": {
                 "mappings": {
                     "0": {
@@ -8043,31 +8046,6 @@
             "school-name": {
                 "question": "Jak se jmenuje tato škola?",
                 "render": "Tato škola se jmenuje {name}"
-            },
-            "target-audience": {
-                "mappings": {
-                    "0": {
-                        "then": "Toto je škola pro studenty bez speciálních potřeb<div class='subtle'>To zahrnuje studenty, kteří mohou absolvovat kurzy s malými, ad hoc měřeními</div>"
-                    },
-                    "1": {
-                        "then": "Jedná se o školu pro žáky s poruchami učení"
-                    },
-                    "2": {
-                        "then": "Jedná se o školu pro nevidomé studenty nebo studenty se zrakovým postižením"
-                    },
-                    "3": {
-                        "then": "Jedná se o školu pro neslyšící studenty nebo studenty se sluchovým postižením"
-                    },
-                    "4": {
-                        "then": "Jedná se o školu pro studenty se zdravotním postižením"
-                    },
-                    "5": {
-                        "then": "Jedná se o školu pro žáky se speciálními potřebami"
-                    }
-                },
-                "question": "Zaměřuje se tato škola na studenty se speciálními potřebami? Jaké stavební vybavení má tato škola?",
-                "questionHint": "Ad hoc opatření nestačí k tomu, aby se škola považovala za školu pro žáky se speciálními vzdělávacími potřebami",
-                "render": "Tato škola má zařízení pro studenty se {school:for}"
             }
         },
         "title": {
diff --git a/langs/layers/de.json b/langs/layers/de.json
index 6cff35d07..779ba635a 100644
--- a/langs/layers/de.json
+++ b/langs/layers/de.json
@@ -9520,6 +9520,9 @@
                 },
                 "question": "Welche Geschlechter können sich an dieser Schule anmelden?"
             },
+            "is_special_needs": {
+                "question": "Richtet sich diese Schule an Schüler mit besonderem Förderbedarf??"
+            },
             "pedagogy": {
                 "mappings": {
                     "0": {
@@ -9566,31 +9569,6 @@
             "school-name": {
                 "question": "Wie lautet der Name dieser Schule?",
                 "render": "Diese Schule heißt {name}"
-            },
-            "target-audience": {
-                "mappings": {
-                    "0": {
-                        "then": "Dies ist eine Schule für Schüler ohne besondere Bedürfnisse<div class='subtle'>Dazu gehören auch Schüler, die den Kursen mit kleinen Ad-hoc-Maßnahmen folgen können</div>"
-                    },
-                    "1": {
-                        "then": "Dies ist eine Schule für Schüler mit Lernschwierigkeiten"
-                    },
-                    "2": {
-                        "then": "Dies ist eine Schule für blinde oder sehbehinderte Schüler"
-                    },
-                    "3": {
-                        "then": "Dies ist eine Schule für gehörlose oder hörgeschädigte Schüler"
-                    },
-                    "4": {
-                        "then": "Dies ist eine Schule für Schüler mit Behinderungen"
-                    },
-                    "5": {
-                        "then": "Dies ist eine Schule für Schüler mit besonderen Bedürfnissen"
-                    }
-                },
-                "question": "Richtet sich diese Schule an Schüler mit besonderem Förderbedarf? Über welche strukturellen Einrichtungen verfügt diese Schule?",
-                "questionHint": "Ad-hoc-Maßnahmen reichen nicht aus, um als Förderschule zu gelten",
-                "render": "Diese Schule verfügt über Einrichtungen für Schüler mit {school:for}"
             }
         },
         "title": {
diff --git a/langs/layers/en.json b/langs/layers/en.json
index a5a0ee485..a497a5aa9 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -3,6 +3,13 @@
         "description": "Addresses",
         "name": "Known addresses in OSM",
         "tagRenderings": {
+            "address_joined": {
+                "render": {
+                    "special": {
+                        "render": "{addr:street} {addr:housenumber}"
+                    }
+                }
+            },
             "fixme": {
                 "question": "What should be fixed here? Please explain"
             },
@@ -9497,6 +9504,19 @@
                         "question": "Unknown school level"
                     }
                 }
+            },
+            "2": {
+                "options": {
+                    "0": {
+                        "question": "Does this school have special need education?"
+                    },
+                    "1": {
+                        "question": "Has special education"
+                    },
+                    "2": {
+                        "question": "No or limited special need education"
+                    }
+                }
             }
         },
         "name": "Primary and secondary schools",
@@ -9556,6 +9576,30 @@
                 },
                 "question": "Which genders can enroll at this school?"
             },
+            "is_special_needs": {
+                "mappings": {
+                    "0": {
+                        "then": "This school is only for special need students; a certificate is needed to enroll"
+                    },
+                    "1": {
+                        "then": "This school has a separate section for special need students."
+                    },
+                    "2": {
+                        "then": "Students with special needs and non-special need students have classes together."
+                    },
+                    "3": {
+                        "then": "This school offers limited, ad hoc support but has no significant expertise and is not considered a special needs school."
+                    },
+                    "4": {
+                        "then": "This school has no support for special need students."
+                    },
+                    "5": {
+                        "then": "This school is for special need students."
+                    }
+                },
+                "question": "Does this school target students with a special need?",
+                "questionHint": "A special needs school has expertise and supports students with a (severe) diagnosis. In many countries, a certificate is needed to enroll."
+            },
             "orientation_belgium": {
                 "mappings": {
                     "0": {
@@ -9617,30 +9661,34 @@
                 "question": "What is the name of this school?",
                 "render": "This school is named {name}"
             },
-            "target-audience": {
+            "special_needs_categories_be": {
                 "mappings": {
                     "0": {
-                        "then": "This is a school for students without special needs<div class='subtle'>This includes students who can follow the courses with small, ad hoc measurements</div>"
+                        "then": "For students with an intellectual disability (type 2)"
                     },
                     "1": {
-                        "then": "This is a school for students with learning disabilities"
+                        "then": "For students with an emotional and behavioural problem (type 3)"
                     },
                     "2": {
-                        "then": "This is a school for blind students or students with sight impairments"
+                        "then": "For students with an physical disability (type 4)"
                     },
                     "3": {
-                        "then": "This is a school for deaf students or students with hearing impairments"
+                        "then": "For blind and visually impaired students (type 6)"
                     },
                     "4": {
-                        "then": "This is a school for students with disabilities"
+                        "then": "For deaf students and students with hearing loss (type 7)"
                     },
                     "5": {
-                        "then": "This is a school for students with special needs"
+                        "then": "For students with a Developemental Language Disorder (type 7 - DLD)"
+                    },
+                    "6": {
+                        "then": "For students with an autism spectrum disorder (type 9)"
+                    },
+                    "7": {
+                        "then": "For students with a learning disability (basic offering )"
                     }
                 },
-                "question": "Does this school target students with a special need? Which structural facilities does this school have?",
-                "questionHint": "Ad-hoc measures are not enough to count as a special-needs school",
-                "render": "This school has facilities for students with {school:for}"
+                "question": "What type of special needs are given here?"
             }
         },
         "title": {
@@ -12646,9 +12694,15 @@
                     }
                 }
             },
+            "debug-gps-title": {
+                "render": "GPS and gyroscope data"
+            },
             "debug-title": {
                 "render": "<h3>Debugging options</h3>"
             },
+            "debug_accordeon_title": {
+                "render": "Debug information"
+            },
             "edit-profile": {
                 "render": {
                     "special": {
diff --git a/langs/layers/es.json b/langs/layers/es.json
index 68aeb5f9b..b55421284 100644
--- a/langs/layers/es.json
+++ b/langs/layers/es.json
@@ -9301,6 +9301,9 @@
                 },
                 "question": "¿Qué géneros pueden matricularse en esta escuela?"
             },
+            "is_special_needs": {
+                "question": "¿Está esta escuela dirigida a alumnos con necesidades especiales?"
+            },
             "pedagogy": {
                 "mappings": {
                     "0": {
@@ -9347,31 +9350,6 @@
             "school-name": {
                 "question": "¿Cómo se llama esta escuela?",
                 "render": "Esta escuela se llama {name}"
-            },
-            "target-audience": {
-                "mappings": {
-                    "0": {
-                        "then": "Esta es una escuela para alumnos sin necesidades especiales<div class='subtle'>Esto incluye alumnos que pueden seguir los cursos con pequeñas medidas improvisadas</div>"
-                    },
-                    "1": {
-                        "then": "Esta es una escuela para alumnos con dificultades de aprendizaje"
-                    },
-                    "2": {
-                        "then": "Esta es una escuela para alumnos ciegos o con discapacidad visual"
-                    },
-                    "3": {
-                        "then": "Esta es una escuela para alumnos sordos o con discapacidad auditiva"
-                    },
-                    "4": {
-                        "then": "Esta es una escuela para alumnos con discapacidad"
-                    },
-                    "5": {
-                        "then": "Esta es una escuela para alumnos con necesidades especiales"
-                    }
-                },
-                "question": "¿Está esta escuela dirigida a alumnos con necesidades especiales? ¿Qué instalaciones estructurales tiene esta escuela?",
-                "questionHint": "Las medidas improvisadas no son suficientes para considerarse una escuela de necesidades especiales",
-                "render": "Esta escuela cuenta con instalaciones para alumnos con {school:for}"
             }
         },
         "title": {
diff --git a/langs/layers/fr.json b/langs/layers/fr.json
index e9349d50b..121aa8712 100644
--- a/langs/layers/fr.json
+++ b/langs/layers/fr.json
@@ -5743,6 +5743,9 @@
                 },
                 "question": "Quels genres de personnes peuvent s'inscrire dans cette école ?"
             },
+            "is_special_needs": {
+                "question": "Est-ce que cet établissement scolaire s'adresse aux étudiants ayant des besoins particuliers?"
+            },
             "school-language": {
                 "render": {
                     "special": {
@@ -5756,30 +5759,6 @@
             "school-name": {
                 "question": "Quel est le nom de cet établissement scolaire?",
                 "render": "Cet établissement scolaire s'appelle {name}"
-            },
-            "target-audience": {
-                "mappings": {
-                    "0": {
-                        "then": "C'est un établissement scolaire pour étudiants sans besoins particuliers. <div class='subtle'>Sont inclus les étudiants qui peuvent suivre les cours avec de petites adaptations.</div>"
-                    },
-                    "1": {
-                        "then": "C'est un établissement scolaire pour les étudiants ayant des troubles d’apprentissage"
-                    },
-                    "2": {
-                        "then": "C'est un établissement scolaire pour les étudiants aveugles ou malvoyants"
-                    },
-                    "3": {
-                        "then": "C'est un établissement scolaire pour les étudiants sourds ou malentendants"
-                    },
-                    "4": {
-                        "then": "C'est un établissement scolaire pour les étudiants en situation de handicap"
-                    },
-                    "5": {
-                        "then": "C'est un établissement scolaire pour les étudiants ayant des besoins particuliers"
-                    }
-                },
-                "question": "Est-ce que cet établissement scolaire s'adresse aux étudiants ayant des besoins particuliers? Quels types d'installation est-ce que cet établissement possède?",
-                "render": "Cet établissement scolaire a des installations pour étudiants ayant {school:for}"
             }
         },
         "title": {
diff --git a/langs/layers/nl.json b/langs/layers/nl.json
index 3d947e95c..210e73781 100644
--- a/langs/layers/nl.json
+++ b/langs/layers/nl.json
@@ -2049,6 +2049,9 @@
         },
         "title": {
             "mappings": {
+                "0": {
+                    "then": "{name}"
+                },
                 "1": {
                     "then": "Vogelkijkhut {name}"
                 },
@@ -4045,6 +4048,11 @@
             "render": "Weg"
         }
     },
+    "cyclist_waiting_aid": {
+        "title": {
+            "render": "Steuntje voor wachtende fietsers"
+        }
+    },
     "defibrillator": {
         "description": "Een laag die defibrillatoren toont die je kan gebruiken bij noodgevallen. Dit omvat zowel publiek beschikbare toestellen als defibrillatoren waarvoor het toestel enkel door personeel aangeboden kan worden",
         "name": "Defibrillatoren",
@@ -4202,6 +4210,9 @@
         "description": "Deze laag toont de oriëntatie van een object",
         "name": "Richtingsvisualisatie"
     },
+    "disaster_response": {
+        "description": "Deze laag bevat organisaties met hoofddoelstelling om burgers te helpen tijdens en na rampen door in de getroffen gebieden te gaan helpen."
+    },
     "doctors": {
         "description": "Deze laag toont dokterspraktijken",
         "name": "Dokters",
@@ -6356,6 +6367,11 @@
             }
         },
         "title": {
+            "mappings": {
+                "0": {
+                    "then": "{name}"
+                }
+            },
             "render": "Natuurgebied"
         }
     },
@@ -6887,6 +6903,21 @@
             "render": "Picknicktafel"
         }
     },
+    "play_forest": {
+        "description": "Een speelbos is een vrij toegankelijke zone in een bos",
+        "name": "Speelbossen",
+        "title": {
+            "mappings": {
+                "0": {
+                    "then": "{name}"
+                },
+                "1": {
+                    "then": "Speelbos {name}"
+                }
+            },
+            "render": "Speelbos"
+        }
+    },
     "playground": {
         "deletion": {
             "nonDeleteMappings": {
@@ -8090,6 +8121,13 @@
                         "question": "Heeft specialisatiejaar"
                     }
                 }
+            },
+            "2": {
+                "options": {
+                    "1": {
+                        "question": "Buitengewoon onderwijs"
+                    }
+                }
             }
         },
         "name": "Lagere en middelbare scholen",
@@ -8149,6 +8187,30 @@
                 },
                 "question": "Mogen jongens en meisjes les volgen op deze school?"
             },
+            "is_special_needs": {
+                "mappings": {
+                    "0": {
+                        "then": "Deze school is enkel voor buitengewone leerlingen. Je hebt een attest nodig om hier school te mogen lopen."
+                    },
+                    "1": {
+                        "then": "Deze school heeft een apart deel voor buitengewone leerlingen. "
+                    },
+                    "2": {
+                        "then": "Buitengewone (geattesteerde) leerlingen en leerlingen zonder extra zorgnood zitten samen in de klas."
+                    },
+                    "3": {
+                        "then": "Deze school biedt ad hoc, beperkte extra zorg aan maar telt niet als buitengwoon onderwij.s"
+                    },
+                    "4": {
+                        "then": "Deze school heeft geen ondersteuning voor buitengewone leerlingen."
+                    },
+                    "5": {
+                        "then": "Deze school is voor buitengewone leerlingen."
+                    }
+                },
+                "question": "Richt deze school zich op leerlingen met een speciale zorgbehoefte?",
+                "questionHint": "Een buitengewone school is een school waar leerlingen met een attest op speciale zorg terecht kunnen."
+            },
             "orientation_belgium": {
                 "mappings": {
                     "0": {
@@ -8175,30 +8237,34 @@
                 "question": "Wat is de naam van deze school?",
                 "render": "Deze school heet <b>{name}</b>"
             },
-            "target-audience": {
+            "special_needs_categories_be": {
                 "mappings": {
                     "0": {
-                        "then": "Deze school richt zich op studenten zonder extra zorgbehoefte<div class='subtle>Dit omvat leerlingen waarbij kleine, ad-hoc maatregelen volstaan om de lessen te volgen.</div>"
+                        "then": "Voor leerlingen met een verstandelijke beperking (type 2)"
                     },
                     "1": {
-                        "then": "Deze school richt zich op leerlingen met een leerprobleem"
+                        "then": "Voor leerlingen met een gedragsstoornis (type 3)"
                     },
                     "2": {
-                        "then": "Deze school richt zich op blinde en slechtziende studenten"
+                        "then": "Voor leerlingen met een motorische of lichamelijke beperking (type 4)"
                     },
                     "3": {
-                        "then": "Deze school richt zich op dove en hardhorende studenten"
+                        "then": "Voor blinde en slechtziende leerlingen (type 6)"
                     },
                     "4": {
-                        "then": "Deze school richt zich op studenten met een beperking"
+                        "then": "Voor dove leerlingen and leerlingen met een auditieve beperking (type 7)"
                     },
                     "5": {
-                        "then": "Deze school richt zich op studenten met extra zorgbehoeften"
+                        "then": "Voor leerlingen met een Spraak- of Taalontwikkelingsstoornis (type 7 - STOS/TOS)"
+                    },
+                    "6": {
+                        "then": "Voor leerlingen met een autisme spectrum stoornis (type 9)"
+                    },
+                    "7": {
+                        "then": "Voor leerlingen die omwille van een leerprobleem niet in het gewone onderwijs terecht kunnen (basisaanbod)"
                     }
                 },
-                "question": "Richt deze school zich op leerlingen met een speciale zorgbehoefte? Welke structurele faciliteiten heeft deze school voor leerlingen met een extra zorgbehoefte?",
-                "questionHint": "Ad-hoc maatregelen zijn niet voldoende ",
-                "render": "Deze school richt zich op studenten met {school:for}"
+                "question": "Welke soorten zorg voor buitengewone leerlingen is hier beschikbaar?"
             }
         },
         "title": {
@@ -8488,6 +8554,9 @@
         },
         "title": {
             "mappings": {
+                "0": {
+                    "then": "{name}"
+                },
                 "1": {
                     "then": "Voetpad"
                 },
@@ -10653,13 +10722,25 @@
         }
     },
     "village_green": {
-        "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)"
+        "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)",
+        "name": "Speelweide",
+        "title": {
+            "mappings": {
+                "0": {
+                    "then": "{name}"
+                }
+            },
+            "render": "Speelweide"
+        }
     },
     "visitor_information_centre": {
         "description": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.",
         "name": "Bezoekerscentrum",
         "title": {
             "mappings": {
+                "0": {
+                    "then": "{name:nl}"
+                },
                 "1": {
                     "then": "{name}"
                 }
@@ -10888,13 +10969,5 @@
             },
             "render": "windturbine"
         }
-    },
-    "cyclist_waiting_aid": {
-        "title": {
-            "render": "Steuntje voor wachtende fietsers"
-        }
-    },
-    "disaster_response": {
-        "description": "Deze laag bevat organisaties met hoofddoelstelling om burgers te helpen tijdens en na rampen door in de getroffen gebieden te gaan helpen."
     }
-}
+}
\ No newline at end of file
diff --git a/langs/layers/pl.json b/langs/layers/pl.json
index 5361d29b3..15c00f623 100644
--- a/langs/layers/pl.json
+++ b/langs/layers/pl.json
@@ -3463,19 +3463,6 @@
             "school-name": {
                 "question": "Jaką nazwę ma ta szkoła?",
                 "render": "Ta szkoła nazywa się {name}"
-            },
-            "target-audience": {
-                "mappings": {
-                    "3": {
-                        "then": "To jest szkoła dla uczniów głuchych i słabosłyszących"
-                    },
-                    "4": {
-                        "then": "To jest szkoła dla uczniów z niepełnosprawnościami"
-                    },
-                    "5": {
-                        "then": "To jest szkoła dla uczniów z specjalnymi potrzebami"
-                    }
-                }
             }
         },
         "title": {
diff --git a/langs/themes/ca.json b/langs/themes/ca.json
index 3a65773e9..ee35db900 100644
--- a/langs/themes/ca.json
+++ b/langs/themes/ca.json
@@ -700,10 +700,6 @@
     "lighthouses": {
         "title": "Fars"
     },
-    "maproulette": {
-        "description": "Tema que mostra les tasques de MapRoulette, que us permet cercar-les, filtrar-les i solucionar-les.",
-        "title": "Tasques de MapRoulette"
-    },
     "maps": {
         "description": "En aquest mapa podeu trobar tots els mapes que OpenStreetMap coneix, normalment un mapa gran en un tauler informatiu que mostra la zona, la ciutat o la regió, p. un mapa turístic al dors d'una tanca publicitària, un mapa d'una reserva natural, un mapa de les xarxes ciclistes de la regió, ...) <br/><br/>Si falta un mapa, podeu mapejar aquest mapa fàcilment a OpenStreetMap.",
         "shortDescription": "Aquest tema mostra tots els mapes (turístics) que OpenStreetMap coneix",
diff --git a/langs/themes/cs.json b/langs/themes/cs.json
index c36b592d9..8095d514f 100644
--- a/langs/themes/cs.json
+++ b/langs/themes/cs.json
@@ -885,10 +885,6 @@
         "shortDescription": "Zobrazuje změny provedené nástrojem MapComplete",
         "title": "Změny provedené pomocí MapComplete"
     },
-    "maproulette": {
-        "description": "Téma zobrazující úkoly MapRoulette, které umožňuje vyhledávat, filtrovat a opravovat je.",
-        "title": "Úkoly MapRoulette"
-    },
     "maps": {
         "description": "Na této mapě najdete všechny mapy, které OpenStreetMap zná - typicky je zde velká mapa na informační tabuli zobrazující oblast, město nebo region, (např. turistická mapa na zadní straně billboardu, mapa přírodní rezervace, mapa cyklistických sítí v regionu, ...). <br/><br/>Pokud mapa chybí, můžete ji snadno zmapovat na OpenStreetMap.",
         "shortDescription": "Toto téma zobrazuje všechny (turistické) mapy, které zná OpenStreetMap",
diff --git a/langs/themes/da.json b/langs/themes/da.json
index 47d410f2b..e0be64759 100644
--- a/langs/themes/da.json
+++ b/langs/themes/da.json
@@ -521,10 +521,6 @@
     "lighthouses": {
         "title": "Fyrtårne"
     },
-    "maproulette": {
-        "description": "Tema, der viser MapRoulette-opgaver, så du kan søge, filtrere og rette dem.",
-        "title": "KortRoulette-opgaver"
-    },
     "maps": {
         "description": "På dette kort kan du finde alle kort, OpenStreetMap kender - typisk et stort kort på en informationstavle, der viser området, byen eller regionen, f.eks. et turistkort på bagsiden af en tavle, et kort over et naturreservat, et kort over cykelnetværk i regionen, ...) <br/><br/>Hvis der mangler et kort, kan du nemt kortlægge dette kort på OpenStreetMap.",
         "shortDescription": "Dette tema viser alle (turisme) kort, som OpenStreetMap kender til"
diff --git a/langs/themes/de.json b/langs/themes/de.json
index a9c35043a..a99b1b018 100644
--- a/langs/themes/de.json
+++ b/langs/themes/de.json
@@ -885,10 +885,6 @@
         "shortDescription": "Zeigt die von MapComplete vorgenommenen Änderungen an",
         "title": "Änderungen mit MapComplete"
     },
-    "maproulette": {
-        "description": "Thema mit MapRoulette-Aufgaben, die Sie suchen, filtern und beheben können.",
-        "title": "MapRoulette-Aufgaben"
-    },
     "maps": {
         "description": "Auf dieser Karte findest du alle Karten, die OpenStreetMap kennt - typischerweise eine große Karte auf einer Informationstafel, die das Gebiet, die Stadt oder die Region zeigt, z.B. eine touristische Karte auf der Rückseite einer Plakatwand, eine Karte eines Naturschutzgebietes, eine Karte der Radwegenetze in der Region, ...) <br/><br/>Wenn eine Karte fehlt, können Sie diese leicht auf OpenStreetMap kartieren.",
         "shortDescription": "Dieses Thema zeigt alle (touristischen) Karten, die OpenStreetMap kennt",
diff --git a/langs/themes/en.json b/langs/themes/en.json
index aaf755eba..226ae9eb8 100644
--- a/langs/themes/en.json
+++ b/langs/themes/en.json
@@ -885,10 +885,6 @@
         "shortDescription": "Shows changes made by MapComplete",
         "title": "Changes made with MapComplete"
     },
-    "maproulette": {
-        "description": "Theme showing MapRoulette tasks, allowing you to search, filter and fix them.",
-        "title": "MapRoulette Tasks"
-    },
     "maps": {
         "description": "On this map you can find all maps OpenStreetMap knows - typically a big map on an information board showing the area, city or region, e.g. a tourist map on the back of a billboard, a map of a nature reserve, a map of cycling networks in the region, ...) <br/><br/>If a map is missing, you can easily map this map on OpenStreetMap.",
         "shortDescription": "This theme shows all (touristic) maps that OpenStreetMap knows of",
diff --git a/langs/themes/es.json b/langs/themes/es.json
index a4a0b4fd6..446d633b5 100644
--- a/langs/themes/es.json
+++ b/langs/themes/es.json
@@ -876,10 +876,6 @@
         "shortDescription": "Muestra los cambios realizados por MapComplete",
         "title": "Cambios realizados con MapComplete"
     },
-    "maproulette": {
-        "description": "Tema que muestra las tareas de MapRoulette, permitiéndote buscarlas, filtrarlas y solucionarlas.",
-        "title": "Tareas de MapRoulette"
-    },
     "maps": {
         "description": "En este mapa puedes encontrar todos los mapas que conoce OpenStreetMap; normalmente un mapa grande en un tablero de información que muestra el área, la ciudad o la región, por ejemplo, un mapa turístico en la parte trasera de una cartelera, un mapa de una reserva natural, un mapa de redes ciclistas en la región, ... <br/><br/>Si falta un mapa, puedes mapear fácilmente este mapa en OpenStreetMap.",
         "shortDescription": "Este tema muestra todos los mapas (turísticos) que OpenStreetMap conoce",
diff --git a/langs/themes/fr.json b/langs/themes/fr.json
index ff4137d1e..d9d87aa0c 100644
--- a/langs/themes/fr.json
+++ b/langs/themes/fr.json
@@ -755,10 +755,6 @@
         "shortDescription": "Afficher les modifications faites avec MapComplete",
         "title": "Modifications faites avec MapComplete"
     },
-    "maproulette": {
-        "description": "Thème MapRoulette permettant d’afficher, rechercher, filtrer et résoudre les tâches.",
-        "title": "Tâches MapRoulette"
-    },
     "maps": {
         "description": "Sur cette carte sont affichées les cartes (plans) mappées dans OpenStreetMap.<br/><br/>Si une carte est manquante, vous pouvez l'ajouer facilement avec un compte OpenStreetMap.",
         "shortDescription": "Cette carte affiche toutes les cartes (plans) mappés dans OpenStreetMap",
diff --git a/langs/themes/hu.json b/langs/themes/hu.json
index 33eccbda2..576fcea09 100644
--- a/langs/themes/hu.json
+++ b/langs/themes/hu.json
@@ -322,9 +322,6 @@
     "lighthouses": {
         "title": "Világítótornyok"
     },
-    "maproulette": {
-        "title": "MapRoulette-feladatok"
-    },
     "maps": {
         "title": "Térképek térképe"
     },
diff --git a/langs/themes/ko.json b/langs/themes/ko.json
index b796f3015..077ba2d08 100644
--- a/langs/themes/ko.json
+++ b/langs/themes/ko.json
@@ -885,10 +885,6 @@
         "shortDescription": "MapComplete를 통해 이루어진 변경 사항을 표시합니다",
         "title": "MapComplete로 이루어진 변경 사항"
     },
-    "maproulette": {
-        "description": "MapRoulette 작업을 표시하는 테마로, 작업을 검색, 필터링 허용될 수 있습니다.",
-        "title": "MapRoulette 작업"
-    },
     "maps": {
         "description": "이 지도에서는 OpenStreetMap에서 확인 가능한 모든 지도를 찾을수 있습니다. 일반적으로 장소, 도시 또는 지역을 보여주는 정보 게시판의 대형 지도(예: 관광지의 안내판 뒷면에 있는 지도, 자연 보호구역 지도, 지역 자전거 네트워크 지도 등)가 포함됩니다. <br/><br/>만약 누락된 지도가 있다면 OpenStreetMap에서 손쉽게 추가할 수 있습니다.",
         "shortDescription": "이 테마는 OpenStreetMap에 등록된 모든 (관광) 지도를 표시합니다",
diff --git a/langs/themes/nl.json b/langs/themes/nl.json
index fc4637708..aafd86378 100644
--- a/langs/themes/nl.json
+++ b/langs/themes/nl.json
@@ -936,10 +936,6 @@
         "shortDescription": "Toont wijzigingen gemaakt met MapComplete",
         "title": "Wijzigingen gemaakt met MapComplete"
     },
-    "maproulette": {
-        "description": "Thema met MapRoulette taken, waar je ze kunt zoeken, filteren en oplossen.",
-        "title": "MapRoulette taken"
-    },
     "maps": {
         "description": "Op deze kaart kan je alle kaarten zien die OpenStreetMap kent.<br/><br/>Ontbreekt er een kaart, dan kan je die kaart hier ook gemakelijk aan deze kaart toevoegen.",
         "shortDescription": "Dit thema toont alle (toeristische) kaarten die OpenStreetMap kent",
diff --git a/langs/themes/pa_PK.json b/langs/themes/pa_PK.json
index 198a2d63e..7bae549da 100644
--- a/langs/themes/pa_PK.json
+++ b/langs/themes/pa_PK.json
@@ -122,9 +122,6 @@
     "indoors": {
         "title": "اندروں"
     },
-    "maproulette": {
-        "title": "میپ‌رولیٹ دے کم"
-    },
     "maxspeed": {
         "title": "حد رفتار"
     },
diff --git a/langs/themes/pl.json b/langs/themes/pl.json
index da7455ef3..3fcbfaae0 100644
--- a/langs/themes/pl.json
+++ b/langs/themes/pl.json
@@ -679,10 +679,6 @@
             }
         }
     },
-    "maproulette": {
-        "description": "Temat pokazujący zadania MapRoulette, umożliwiający ich wyszukiwanie, filtrowanie i naprawianie.",
-        "title": "Zadania MapRoulette"
-    },
     "maps": {
         "description": "Na tej mapie możesz znaleźć wszystkie mapy, jakie zna OpenStreetMap - zazwyczaj duże mapy na tablicy informacyjnej pokazująca obszar, miasto lub region, np. mapy turystyczne na odwrocie billboardu, mapy rezerwatu przyrody, mapy sieci rowerowych w regionie, ...) <br/><br/>Jeśli brakuje mapy, możesz łatwo zmapować ją na OpenStreetMap.",
         "shortDescription": "Ten motyw pokazuje wszystkie mapy (turystyczne), które zna OpenStreetMap",
diff --git a/langs/themes/zh_Hant.json b/langs/themes/zh_Hant.json
index 3aa2a6600..178a92e46 100644
--- a/langs/themes/zh_Hant.json
+++ b/langs/themes/zh_Hant.json
@@ -556,9 +556,6 @@
     "indoors": {
         "title": "室內"
     },
-    "maproulette": {
-        "title": "MapRoulette 任務"
-    },
     "maps": {
         "description": "在這份地圖你可以找到所在在開放街圖上已知的地圖 - 特別是顯示地區、城市、區域的資訊版面上的大型地圖,例如佈告欄背面的旅遊地圖,自然保護區的地圖,區域的單車網路地圖,...)<br/><br/>如果有缺少的地圖,你可以輕易在開放街圖上新增這地圖。",
         "shortDescription": "這份主題顯示所有已知的開放街圖上的 (旅遊) 地圖",

From 2ec369f259db20ba8441748d21c17ea575a0c584 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Fri, 31 Jan 2025 15:16:42 +0100
Subject: [PATCH 13/49] Themes: fix generation of automatic filters with
 multiAnswer, add test

---
 src/Models/ThemeConfig/Conversion/ExpandFilter.ts | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/Models/ThemeConfig/Conversion/ExpandFilter.ts b/src/Models/ThemeConfig/Conversion/ExpandFilter.ts
index 9542ea9b0..43bc4735c 100644
--- a/src/Models/ThemeConfig/Conversion/ExpandFilter.ts
+++ b/src/Models/ThemeConfig/Conversion/ExpandFilter.ts
@@ -132,6 +132,16 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
         return filters
     }
 
+    /**
+     *
+     * import FilterConfig from "../FilterConfig"
+     *
+     * // A multi-answer tagRendering should match any subkey
+     * const tr = {id: "test", multiAnswer: true, mappings:[{if: "x=a", then: "A"}, {if: "x=b", then:"B"}]}
+     * const filter = ExpandFilter.buildFilterFromTagRendering(tr,  ConversionContext.test("ExpandFilter"))
+     * const f = new FilterConfig(filter, "test")
+     * f.options[1].osmTags.matchesProperties({x:"a;b"}) // => true
+     */
     public static buildFilterFromTagRendering(
         tr: TagRenderingConfigJson,
         context: ConversionContext
@@ -153,7 +163,7 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
             if (qtr.multiAnswer && osmTags instanceof Tag) {
                 osmTags = new RegexTag(
                     osmTags.key,
-                    new RegExp("^(.+;)?" + osmTags.value + "(;.+)$", "is")
+                    new RegExp("^(.+;)?" + osmTags.value + "(;.+)?$", "is")
                 )
             }
             if (mapping.alsoShowIf) {

From f3aa8e015b07a6cf7cbc269aa9c07df205dd6ab4 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Fri, 31 Jan 2025 15:17:27 +0100
Subject: [PATCH 14/49] Themes(school): small fixes to special need school
 tagging

---
 assets/layers/school/school.json | 34 +++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json
index 06ea3eb57..b10738200 100644
--- a/assets/layers/school/school.json
+++ b/assets/layers/school/school.json
@@ -159,6 +159,7 @@
       "id": "school-name"
     },
     "contact",
+    "address.address",
     {
       "id": "capacity",
       "question": {
@@ -322,13 +323,14 @@
           {
             "or": [
               "school:special_needs=no",
-              "school:special_needs="
+              "school:special_needs=",
+              "school:special_needs=limited"
             ]
-          },
-          "special_needs:.*!~~yes"
+          }
         ]
       },
       "multiAnswer": true,
+      "filter": true,
       "mappings": [
         {
           "if": "school:orientation=academic",
@@ -555,6 +557,7 @@
         },
         {
           "if": "school:special_needs=limited",
+          "alsoShowIf": "school:special_needs=",
           "then": {
             "en": "This school offers limited, ad hoc support but has no significant expertise and is not considered a special needs school.",
             "nl": "Deze school biedt ad hoc, beperkte extra zorg aan maar telt niet als buitengwoon onderwij.s"
@@ -566,6 +569,14 @@
             "en": "This school has no support for special need students.",
             "nl": "Deze school heeft geen ondersteuning voor buitengewone leerlingen."
           }
+        },
+        {
+          "if": "school:special_needs=yes",
+          "hideInAnswer": true,
+          "then": {
+            "en": "This school is for special need students.",
+            "nl": "Deze school is voor buitengewone leerlingen."
+          }
         }
       ]
     },
@@ -794,6 +805,11 @@
     {
       "id": "has_special_needs",
       "options": [
+        {
+          "question": {
+            "en": "Does this school have special need education?"
+          }
+        },
         {
           "question": {
             "en": "Has special education",
@@ -805,6 +821,18 @@
               "school:special_needs!=no"
             ]
           }
+        },
+        {
+          "question": {
+            "en": "No or limited special need education"
+          },
+          "osmTags": {
+            "or": [
+              "school:special_needs=",
+              "school:special_needs=no",
+              "school:special_needs=limited"
+            ]
+          }
         }
       ]
     }

From 57a7d36617cdb67fae319ac70386248d440f279b Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Fri, 31 Jan 2025 15:53:04 +0100
Subject: [PATCH 15/49] Themes(education): add minzoom

---
 assets/layers/tertiary_education/tertiary_education.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/assets/layers/tertiary_education/tertiary_education.json b/assets/layers/tertiary_education/tertiary_education.json
index 6465e4c62..0129dc228 100644
--- a/assets/layers/tertiary_education/tertiary_education.json
+++ b/assets/layers/tertiary_education/tertiary_education.json
@@ -11,6 +11,7 @@
     "es": "Universidades y colegios"
   },
   "description": "Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)",
+  "minzoom": 14,
   "source": {
     "osmTags": {
       "or": [

From 3ca0d42eb6f35e66554f379db7820fb135d8c1d4 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Sat, 1 Feb 2025 20:00:34 +0100
Subject: [PATCH 16/49] Fix: GroupedView.svelte now shows the questions
 directly, so that non-answered questions show up

---
 src/UI/Popup/GroupedView.svelte | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/src/UI/Popup/GroupedView.svelte b/src/UI/Popup/GroupedView.svelte
index f37b3843a..577dd2921 100644
--- a/src/UI/Popup/GroupedView.svelte
+++ b/src/UI/Popup/GroupedView.svelte
@@ -7,6 +7,8 @@
   import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
   import SelectedElementView from "../BigComponents/SelectedElementView.svelte"
   import TagRenderingAnswer from "./TagRendering/TagRenderingAnswer.svelte"
+  import TagRenderingEditableDynamic from "./TagRendering/TagRenderingEditableDynamic.svelte"
+  import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 
   export let state: SpecialVisualizationState
   export let selectedElement: Feature
@@ -16,11 +18,32 @@
   export let layer: LayerConfig
 
   let headerTr = layer.tagRenderings.find((tr) => tr.id === header)
+  let trgs: TagRenderingConfig[] = []
+  let seenIds = new Set<string>()
+  for (const label of labels) {
+    for (const tr of layer.tagRenderings) {
+      if (seenIds.has(tr.id)) {
+        continue
+      }
+      if (label === tr.id || tr.labels.some(l => l === label)) {
+        trgs.push(tr)
+        seenIds.add(tr.id)
+      }
+    }
+  }
 </script>
 
 <AccordionSingle>
   <div slot="header">
     <TagRenderingAnswer {tags} {layer} config={headerTr} {state} {selectedElement} />
   </div>
-  <SelectedElementView mustMatchLabels={new Set(labels)} {state} {layer} {tags} {selectedElement} />
+  {#each trgs as config (config.id)}
+    <TagRenderingEditableDynamic
+      {tags}
+      {config}
+      {state}
+      {selectedElement}
+      {layer}
+    />
+  {/each}
 </AccordionSingle>

From b6a5b963501ce636255bbecf41d020cc6394eaf3 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Sat, 1 Feb 2025 20:06:15 +0100
Subject: [PATCH 17/49] Themes(address): add simple address feature (useable as
 'address.address')

---
 assets/layers/address/address.json | 100 ++++++++++++++++++++++-------
 1 file changed, 77 insertions(+), 23 deletions(-)

diff --git a/assets/layers/address/address.json b/assets/layers/address/address.json
index 667783acf..7ec081e46 100644
--- a/assets/layers/address/address.json
+++ b/assets/layers/address/address.json
@@ -61,12 +61,6 @@
       ]
     }
   },
-  "calculatedTags": [
-    "_closest_3_street_names=closestn(feat)('named_streets',3, 'name').map(f => f.feat.properties.name)",
-    "_closest_street:0:name=JSON.parse(feat.properties._closest_3_street_names)[0]",
-    "_closest_street:1:name=JSON.parse(feat.properties._closest_3_street_names)[1]",
-    "_closest_street:2:name=JSON.parse(feat.properties._closest_3_street_names)[2]"
-  ],
   "minzoom": 18,
   "title": {
     "render": {
@@ -155,8 +149,50 @@
     }
   ],
   "tagRenderings": [
+    {
+      "id": "address_joined",
+      "labels": [
+        "address"
+      ],
+      "render": {
+        "special": {
+          "type": "group",
+          "header": "header",
+          "labels": "street;housenumber;unit"
+        }
+      }
+    },
+    {
+      "id": "header",
+      "labels": [
+        "address",
+        "hidden"
+      ],
+      "render": {
+        "en": "{addr:street} <b>{addr:housenumber}</b> {addr:unit}"
+      },
+      "mappings": [
+        {
+          "if": {
+            "and": [
+              "addr:street=",
+              "addr:unit=",
+              "addr:housenumber="
+            ]
+          },
+          "then": {
+            "en": "No address is known",
+            "nl": "Geen adresgegevens bekend"
+          }
+        }
+      ]
+    },
     {
       "id": "housenumber",
+      "labels": [
+        "address",
+        "hidden"
+      ],
       "render": {
         "en": "The house number is <b>{addr:housenumber}</b>",
         "nl": "Het huisnummer is <b>{addr:housenumber}</b>",
@@ -250,6 +286,10 @@
     },
     {
       "id": "street",
+      "labels": [
+        "address",
+        "hidden"
+      ],
       "render": {
         "en": "This address is in street <b>{addr:street}</b>",
         "de": "Diese Adresse befindet sich in der Straße <b>{addr:street}</b>",
@@ -293,32 +333,46 @@
         "zh_Hant": "地址所在的道路是?",
         "uk": "На якій вулиці знаходиться ця адреса?"
       },
+      "questionHint": {
+        "en": "Do not include the house number"
+      },
       "freeform": {
+        "inline": false,
         "key": "addr:street"
       },
-      "mappings": [
-        {
-          "if": "addr:street:={_closest_street:0:name}",
-          "then": "Located in <b>{_closest_street:0:name}</b>",
-          "hideInAnswer": "_closest_street:0:name="
-        },
-        {
-          "if": "addr:street:={_closest_street:1:name}",
-          "then": "Located in <b>{_closest_street:1:name}</b>",
-          "hideInAnswer": "_closest_street:1:name="
-        },
-        {
-          "if": "addr:street:={_closest_street:2:name}",
-          "then": "Located in <b>{_closest_street:2:name}</b>",
-          "hideInAnswer": "_closest_street:2:name="
-        }
-      ],
       "condition": {
         "and": [
           "nohousenumber!~yes"
         ]
       }
     },
+    {
+      "id": "unit",
+      "labels": [
+        "address",
+        "hidden"
+      ],
+      "question": {
+        "en": "What is the unit number or letter?",
+        "nl": "Wat is het busnummer?"
+      },
+      "render": {
+        "en": "The unit number is <b>{addr:unit}</b>",
+        "nl": "De bus is <b>{addr:unit}</b>"
+      },
+      "freeform": {
+        "key": "addr:unit"
+      },
+      "mappings": [
+        {
+          "if": "addr:unit=",
+          "then": {
+            "en": "No unit number",
+            "nl": "Geen apart busnummer of letter"
+          }
+        }
+      ]
+    },
     {
       "id": "fixme",
       "render": "<b>Fixme description</b>{fixme}",

From ab47919e17034266828c901a78c6ac8a9b1d06fa Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 00:20:08 +0100
Subject: [PATCH 18/49] Chore: translation sync

---
 langs/layers/en.json | 25 ++++++++++++++++++-------
 langs/layers/nl.json | 16 ++++++++++++++++
 2 files changed, 34 insertions(+), 7 deletions(-)

diff --git a/langs/layers/en.json b/langs/layers/en.json
index a497a5aa9..45b9be879 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -3,16 +3,17 @@
         "description": "Addresses",
         "name": "Known addresses in OSM",
         "tagRenderings": {
-            "address_joined": {
-                "render": {
-                    "special": {
-                        "render": "{addr:street} {addr:housenumber}"
-                    }
-                }
-            },
             "fixme": {
                 "question": "What should be fixed here? Please explain"
             },
+            "header": {
+                "mappings": {
+                    "0": {
+                        "then": "No address is known"
+                    }
+                },
+                "render": "{addr:street} <b>{addr:housenumber}</b> {addr:unit}"
+            },
             "housenumber": {
                 "mappings": {
                     "0": {
@@ -24,7 +25,17 @@
             },
             "street": {
                 "question": "What street is this address located in?",
+                "questionHint": "Do not include the house number",
                 "render": "This address is in street <b>{addr:street}</b>"
+            },
+            "unit": {
+                "mappings": {
+                    "0": {
+                        "then": "No unit number"
+                    }
+                },
+                "question": "What is the unit number or letter?",
+                "render": "The unit number is <b>{addr:unit}</b>"
             }
         },
         "title": {
diff --git a/langs/layers/nl.json b/langs/layers/nl.json
index 210e73781..3011a9d4b 100644
--- a/langs/layers/nl.json
+++ b/langs/layers/nl.json
@@ -6,6 +6,13 @@
             "fixme": {
                 "question": "Wat moet hier gecorrigeerd worden? Leg het uit"
             },
+            "header": {
+                "mappings": {
+                    "0": {
+                        "then": "Geen adresgegevens bekend"
+                    }
+                }
+            },
             "housenumber": {
                 "mappings": {
                     "0": {
@@ -18,6 +25,15 @@
             "street": {
                 "question": "In welke straat bevindt dit adres zich?",
                 "render": "Dit adres bevindt zich in de straat <b>{addr:street}</b>"
+            },
+            "unit": {
+                "mappings": {
+                    "0": {
+                        "then": "Geen apart busnummer of letter"
+                    }
+                },
+                "question": "Wat is het busnummer?",
+                "render": "De bus is <b>{addr:unit}</b>"
             }
         },
         "title": {

From 19941d492e63d14d5421cf6cc4c41f73ab12ca68 Mon Sep 17 00:00:00 2001
From: Osmwithspace <>
Date: Mon, 3 Feb 2025 02:18:10 +0100
Subject: [PATCH 19/49] image changed

---
 .../layers/group_campsite/group_campsite.json | 12 +---
 .../layers/group_campsite/group_campsite.svg  | 65 +++++++++++++++++++
 .../group_campsite/group_campsite.svg.license |  2 +
 .../layers/group_campsite/license_info.json   | 12 ++++
 4 files changed, 80 insertions(+), 11 deletions(-)
 create mode 100644 assets/layers/group_campsite/group_campsite.svg
 create mode 100644 assets/layers/group_campsite/group_campsite.svg.license
 create mode 100644 assets/layers/group_campsite/license_info.json

diff --git a/assets/layers/group_campsite/group_campsite.json b/assets/layers/group_campsite/group_campsite.json
index 2e1d4c740..09477c366 100644
--- a/assets/layers/group_campsite/group_campsite.json
+++ b/assets/layers/group_campsite/group_campsite.json
@@ -90,17 +90,7 @@
       "marker": [
         {
           "icon": {
-            "render": "https://opencampingmap.org/markers/l_standard.svg",
-            "mappings": [
-              {
-                "if": {
-                  "or": [
-                    "scout=yes",
-                    "group_only=yes"
-                  ]
-                },
-                "then": "https://wiki.openstreetmap.org/w/images/6/6b/OCM_l_group_only.svg"
-              }
+            "render": "./assets/layers/group_campsite/group_campsite.svg",
             ]
           }
         }
diff --git a/assets/layers/group_campsite/group_campsite.svg b/assets/layers/group_campsite/group_campsite.svg
new file mode 100644
index 000000000..a41a68d3e
--- /dev/null
+++ b/assets/layers/group_campsite/group_campsite.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   id="svg2"
+   viewBox="0 0 30 30"
+   height="32"
+   width="32"
+   version="1.1"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs8" />
+  <circle
+     style="fill:#552200;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:square;stroke-dasharray:none;paint-order:stroke fill markers;stop-color:#000000"
+     id="path961"
+     cx="15"
+     cy="15"
+     r="15" />
+  <rect
+     style="visibility:hidden;fill:none;stroke:none"
+     id="canvas"
+     y="23.5"
+     x="0"
+     height="14"
+     width="14" />
+  <circle
+     style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;paint-order:stroke fill markers;stop-color:#000000"
+     id="path1449-5-0-9-2-9"
+     cx="14.999711"
+     cy="15.000001"
+     r="12.65625" />
+  <path
+     style="fill:#552200;fill-opacity:1;stroke-width:1.77489"
+     d="m 6.8428091,9.483844 0.567334,1.70045 -3.4009021,6.23602 h 7.93647 l -3.4009009,-6.23602 0.565783,-1.70045 h -0.565783 l -0.567334,1.133117 -0.567333,-1.133117 z m 1.134667,3.968235 1.700451,3.400901 h -3.400902 z"
+     id="camping-5-6" />
+  <path
+     style="fill:#552200;fill-opacity:1;stroke-width:1.77488"
+     d="m 12.234371,7.741551 0.567334,1.70045 -3.4009019,6.236019 H 17.337274 L 13.936372,9.442001 14.502156,7.741551 H 13.936372 L 13.369039,8.874668 12.801705,7.741551 Z m 1.134668,3.968234 1.70045,3.400902 h -3.400901 z"
+     id="camping-5-6-92" />
+  <path
+     style="fill:#552200;fill-opacity:1;stroke-width:1.77488"
+     d="m 19.531338,5.284174 0.567334,1.700452 -3.400902,6.236019 h 7.936471 L 21.233339,6.984626 21.799123,5.284174 H 21.233339 L 20.666006,6.417292 20.098672,5.284174 Z m 1.134668,3.968236 1.700451,3.400901 h -3.400902 z"
+     id="camping-5-6-02" />
+  <path
+     style="fill:#552200;fill-opacity:1;stroke-width:1.77488"
+     d="m 13.865333,17.73558 0.567334,1.700451 -3.400902,6.23602 h 7.93647 l -3.400901,-6.23602 0.565783,-1.700451 H 15.567334 L 15,18.868698 14.432667,17.73558 Z M 15,21.703816 l 1.700451,3.400901 h -3.400902 z"
+     id="camping-5-6-3" />
+  <path
+     style="fill:#552200;fill-opacity:1;stroke-width:1.77488"
+     d="m 18.360294,14.139291 0.567334,1.700451 -3.400902,6.23602 h 7.93647 l -3.400901,-6.23602 0.565784,-1.700451 h -0.565784 l -0.567334,1.133118 -0.567333,-1.133118 z m 1.134667,3.968236 1.700451,3.400902 h -3.400901 z"
+     id="camping-5-6-7" />
+</svg>
diff --git a/assets/layers/group_campsite/group_campsite.svg.license b/assets/layers/group_campsite/group_campsite.svg.license
new file mode 100644
index 000000000..e8792752d
--- /dev/null
+++ b/assets/layers/group_campsite/group_campsite.svg.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: Sven Geggus
+SPDX-License-Identifier: CC0-1.0
\ No newline at end of file
diff --git a/assets/layers/group_campsite/license_info.json b/assets/layers/group_campsite/license_info.json
new file mode 100644
index 000000000..fb972c320
--- /dev/null
+++ b/assets/layers/group_campsite/license_info.json
@@ -0,0 +1,12 @@
+[
+  {
+    "path": "group_campsite.svg",
+    "license": "CC0-1.0",
+    "authors": [
+      "Sven Geggus"
+    ],
+    "sources": [
+      "https://github.com/giggls/opencampsitemap/blob/master/markers/l_group_only.svg"
+    ]
+  }
+]
\ No newline at end of file

From c0fe38d27668f56d0c3ea0b4e74ee28c36c10f03 Mon Sep 17 00:00:00 2001
From: Osmwithspace <>
Date: Mon, 3 Feb 2025 02:22:09 +0100
Subject: [PATCH 20/49] edit ids

---
 assets/layers/group_campsite/group_campsite.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/assets/layers/group_campsite/group_campsite.json b/assets/layers/group_campsite/group_campsite.json
index 09477c366..c1af757b6 100644
--- a/assets/layers/group_campsite/group_campsite.json
+++ b/assets/layers/group_campsite/group_campsite.json
@@ -110,7 +110,7 @@
       "freeform": {
         "key": "name"
       },
-      "id": "Name"
+      "id": "name"
     },
     {
       "id": "fee",
@@ -169,7 +169,7 @@
         "key": "capacity:persons",
         "type": "pnat"
       },
-      "id": "capacity:persons"
+      "id": "capacity_persons"
     },
     "contact",
     "questions",
@@ -183,7 +183,7 @@
     }
   ],
   "credits:uid": 8770388,
-  "id": "groupcampsites",
+  "id": "group_campsite",
   "name": {
     "en": "Campsites for groups and scouts",
     "de": "Zeltplatz für Gruppen/Pfadfinder:innen"

From 3cc7d05e03809616823cdf4f0143ae230e195638 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 02:32:06 +0100
Subject: [PATCH 21/49] Fix: don't save big objects to the cache, see #1919

---
 .../Actors/SaveFeatureSourceToLocalStorage.ts | 21 ++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts
index 59cf8b51b..502d27407 100644
--- a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts
+++ b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts
@@ -13,6 +13,7 @@ class SingleTileSaver {
     private readonly _registeredIds = new Set<string>()
     private readonly _featureProperties: FeaturePropertiesStore
     private readonly _isDirty = new UIEventSource(false)
+
     constructor(
         storage: UIEventSource<Feature[]> & { flush: () => void },
         featureProperties: FeaturePropertiesStore
@@ -62,6 +63,7 @@ class SingleTileSaver {
 export default class SaveFeatureSourceToLocalStorage {
     public readonly storage: TileLocalStorage<Feature[]>
     private readonly zoomlevel: number
+
     constructor(
         backend: string,
         layername: string,
@@ -75,8 +77,25 @@ export default class SaveFeatureSourceToLocalStorage {
         this.storage = storage
         const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
         features.features.addCallbackAndRunD((features) => {
+            if (features.some(f => {
+                let totalPoints = 0
+                if (f.geometry.type === "MultiPolygon") {
+                    totalPoints = f.geometry.coordinates.map(rings => rings.map(ring => ring.length).reduce((a, b) => a + b)).reduce((a, b) => a + b)
+                } else if (f.geometry.type === "Polygon" || f.geometry.type === "MultiLineString") {
+                    totalPoints = f.geometry.coordinates.map(ring => ring.length).reduce((a, b) => a + b)
+                } else if (f.geometry.type === "LineString") {
+                    totalPoints = f.geometry.coordinates.length
+                }
+                if (totalPoints > 1000) {
+                    console.warn(`Not caching tiles, detected a big object (${totalPoints} points for ${f.properties.id})`)
+                    return true
+                }
+                return false
+            })) {
+                // Has big objects
+                return
+            }
             const sliced = GeoOperations.spreadIntoBboxes(features, zoomlevel)
-
             sliced.forEach((features, tileIndex) => {
                 let tileSaver = singleTileSavers.get(tileIndex)
                 if (tileSaver === undefined) {

From a66c9f7cbe83bd80dfa41910eb8372eb0613bd25 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 02:32:34 +0100
Subject: [PATCH 22/49] Fix: fix crash in movewizard

---
 src/UI/Popup/MoveWizardState.ts | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/src/UI/Popup/MoveWizardState.ts b/src/UI/Popup/MoveWizardState.ts
index 80de7cb16..3bda9b4c2 100644
--- a/src/UI/Popup/MoveWizardState.ts
+++ b/src/UI/Popup/MoveWizardState.ts
@@ -1,7 +1,6 @@
 import { UIEventSource } from "../../Logic/UIEventSource"
 import Translations from "../i18n/Translations"
 import { Translation } from "../i18n/Translation"
-import BaseUIElement from "../BaseUIElement"
 import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
 import MoveConfig from "../../Models/ThemeConfig/MoveConfig"
 import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
@@ -9,9 +8,6 @@ import { And } from "../../Logic/Tags/And"
 import { Tag } from "../../Logic/Tags/Tag"
 import { SpecialVisualizationState } from "../SpecialVisualization"
 import { Feature, Point } from "geojson"
-import SvelteUIElement from "../Base/SvelteUIElement"
-import Relocation from "../../assets/svg/Relocation.svelte"
-import Location from "../../assets/svg/Location.svelte"
 import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 import { WayId } from "../../Models/OsmFeature"
 
@@ -104,7 +100,7 @@ export class MoveWizardState {
         for (const layerId of matchingPreset) {
             const snapOntoLayer = this._state.theme.getLayer(layerId)
             const text = <Translation>(
-                t.reasons.reasonSnapTo.PartialSubsTr("name", snapOntoLayer.snapName)
+                t.reasons.reasonSnapTo.PartialSubsTr("name", snapOntoLayer?.snapName)
             )
             reasons.push({
                 text,
@@ -117,7 +113,7 @@ export class MoveWizardState {
                 startZoom: 19,
                 minZoom: 16,
                 eraseAddressFields: false,
-                snapTo: [snapOntoLayer.id],
+                snapTo: [snapOntoLayer?.id],
                 maxSnapDistance: 5,
             })
         }

From f878824e0b0f47979c830a0379649e7254de5a8e Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 02:32:49 +0100
Subject: [PATCH 23/49] Chore: remove unneeded warning

---
 src/UI/Popup/TagRendering/SpecialTranslation.svelte | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/src/UI/Popup/TagRendering/SpecialTranslation.svelte b/src/UI/Popup/TagRendering/SpecialTranslation.svelte
index e83b647d1..361d2f7e9 100644
--- a/src/UI/Popup/TagRendering/SpecialTranslation.svelte
+++ b/src/UI/Popup/TagRendering/SpecialTranslation.svelte
@@ -2,10 +2,7 @@
   import { Translation } from "../../i18n/Translation"
   import SpecialVisualizations from "../../SpecialVisualizations"
   import Locale from "../../i18n/Locale"
-  import type {
-    RenderingSpecification,
-    SpecialVisualizationState,
-  } from "../../SpecialVisualization"
+  import type { RenderingSpecification, SpecialVisualizationState } from "../../SpecialVisualization"
   import { Utils } from "../../../Utils.js"
   import type { Feature } from "geojson"
   import { UIEventSource } from "../../../Logic/UIEventSource.js"
@@ -49,13 +46,9 @@
   function createVisualisation(specpart: Exclude<RenderingSpecification, string>): BaseUIElement {
     {
       try {
-        const uiEl = specpart.func
+        return specpart.func
           .constr(state, tags, specpart.args, feature, layer)
           ?.SetClass(specpart.style)
-        if (uiEl === undefined) {
-          console.error("Invalid special translation")
-        }
-        return uiEl
       } catch (e) {
         console.error(
           "Could not construct a special visualisation with specification",

From e80fa80867339e3cd070a87a857b0261ed7cfb22 Mon Sep 17 00:00:00 2001
From: Osmwithspace <>
Date: Mon, 3 Feb 2025 02:37:05 +0100
Subject: [PATCH 24/49] change from group_campsite to campsite

---
 .../campsite.json}                            | 28 ++++++++++---------
 .../campsite.svg}                             |  0
 .../campsite.svg.license}                     |  0
 .../license_info.json                         |  2 +-
 4 files changed, 16 insertions(+), 14 deletions(-)
 rename assets/layers/{group_campsite/group_campsite.json => campsite/campsite.json} (90%)
 rename assets/layers/{group_campsite/group_campsite.svg => campsite/campsite.svg} (100%)
 rename assets/layers/{group_campsite/group_campsite.svg.license => campsite/campsite.svg.license} (100%)
 rename assets/layers/{group_campsite => campsite}/license_info.json (84%)

diff --git a/assets/layers/group_campsite/group_campsite.json b/assets/layers/campsite/campsite.json
similarity index 90%
rename from assets/layers/group_campsite/group_campsite.json
rename to assets/layers/campsite/campsite.json
index c1af757b6..49f13e5ed 100644
--- a/assets/layers/group_campsite/group_campsite.json
+++ b/assets/layers/campsite/campsite.json
@@ -90,7 +90,7 @@
       "marker": [
         {
           "icon": {
-            "render": "./assets/layers/group_campsite/group_campsite.svg",
+            "render": "./assets/layers/campsite/campsite.svg",
             ]
           }
         }
@@ -183,14 +183,14 @@
     }
   ],
   "credits:uid": 8770388,
-  "id": "group_campsite",
+  "id": "campsite",
   "name": {
-    "en": "Campsites for groups and scouts",
-    "de": "Zeltplatz für Gruppen/Pfadfinder:innen"
+    "en": "Campsites",
+    "de": "Zeltplätze"
   },
   "description": {
-    "en": "Campsites for groups and scouts",
-    "de": "Zeltplatz für Gruppen/Pfadfinder:innen"
+    "en": "Campsites",
+    "de": "Zeltplätze"
   },
   "title": {
     "render": {
@@ -200,18 +200,20 @@
   "source": {
     "osmTags": {
       "and": [
-        "tourism=camp_site",
-        {
-          "or": [
-            "scout=yes",
-            "group_only=yes"
-          ]
-        }
+        "tourism=camp_site"
       ]
     }
   },
   "shownByDefault": true,
   "presets": [
+    {
+      "title": {
+        "en": "campsite"
+      },
+      "tags": [
+        "tourism=camp_site"
+      ]
+    },
     {
       "title": {
         "en": "campsite for groups"
diff --git a/assets/layers/group_campsite/group_campsite.svg b/assets/layers/campsite/campsite.svg
similarity index 100%
rename from assets/layers/group_campsite/group_campsite.svg
rename to assets/layers/campsite/campsite.svg
diff --git a/assets/layers/group_campsite/group_campsite.svg.license b/assets/layers/campsite/campsite.svg.license
similarity index 100%
rename from assets/layers/group_campsite/group_campsite.svg.license
rename to assets/layers/campsite/campsite.svg.license
diff --git a/assets/layers/group_campsite/license_info.json b/assets/layers/campsite/license_info.json
similarity index 84%
rename from assets/layers/group_campsite/license_info.json
rename to assets/layers/campsite/license_info.json
index fb972c320..951e90bfb 100644
--- a/assets/layers/group_campsite/license_info.json
+++ b/assets/layers/campsite/license_info.json
@@ -1,6 +1,6 @@
 [
   {
-    "path": "group_campsite.svg",
+    "path": "campsite.svg",
     "license": "CC0-1.0",
     "authors": [
       "Sven Geggus"

From 582bdf39f27cd7941cd060a3be75b653fb16cb7a Mon Sep 17 00:00:00 2001
From: Osmwithspace <>
Date: Mon, 3 Feb 2025 02:55:51 +0100
Subject: [PATCH 25/49] add group_campsite

---
 assets/themes/scouting/scouting.json | 36 +++++++++++++++++++++++++++-
 1 file changed, 35 insertions(+), 1 deletion(-)

diff --git a/assets/themes/scouting/scouting.json b/assets/themes/scouting/scouting.json
index 068ed8e5b..9874814b2 100644
--- a/assets/themes/scouting/scouting.json
+++ b/assets/themes/scouting/scouting.json
@@ -11,6 +11,40 @@
   "icon": "./assets/layers/scouting_group/scouting.svg",
   "defaultBackgroundId": "protomaps.dark",
   "layers": [
-    "scouting_group"
+    "scouting_group",
+    {
+      "builtin": "campsite",
+      "override": {
+        "id": "group_campsite",
+        "name": {
+          "en": "Group Campsites"
+        },
+        "=presets": [],
+        "source": {
+          "=osmTags": {
+            "and": [
+              "tourism=camp_site",
+              {
+                "or": [
+                  "scout=yes",
+                  "group_only=yes"
+                ]
+              }
+            ]
+
+          }
+        },
+        "=filter": []
+      }
+    },
+    {
+      "builtin": "campsite",
+      "override": {
+        "minzoom": 18,
+        "filter": null,
+        "name": null,
+        "isCounted": false
+      }
+    }
   ]
 }

From 910ca8708a58f71763de5b73f83c6f4d1bf038d0 Mon Sep 17 00:00:00 2001
From: mike140 <mic140@ukr.net>
Date: Sun, 2 Feb 2025 01:50:15 +0000
Subject: [PATCH 26/49] Translated using Weblate (Ukrainian)

Currently translated at 84.0% (563 of 670 strings)

Translation: MapComplete/core
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/core/uk/
---
 langs/uk.json | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/langs/uk.json b/langs/uk.json
index d76ca3473..0ef17aa55 100644
--- a/langs/uk.json
+++ b/langs/uk.json
@@ -611,7 +611,8 @@
         "about": "Про MapComplete",
         "intro": "Тематичні мапи, до створення яких ви можете долучитися",
         "learnMore": "Дізнатися більше",
-        "logIn": "Увійдіть, щоб переглянути інші теми, які ви відвідували раніше"
+        "logIn": "Увійдіть, щоб переглянути інші теми, які ви відвідували раніше",
+        "title": "Ласкаво просимо до MapComplete!"
     },
     "inspector": {
         "aggregateView": "Агрегат",
@@ -735,5 +736,8 @@
             "description": "посилання на веб-сайт",
             "spamSite": "{host} вважається неякісним веб-сайтом. Використання цього веб-сайту заборонено."
         }
+    },
+    "split": {
+        "inviteToSplit": "Розділіть цю дорогу на менші сегменти. Це дозволяє надати різним частинам дороги різні властивості."
     }
-}
\ No newline at end of file
+}

From 815e39d3615a8cce70ff6294aac906a7b875c2bc Mon Sep 17 00:00:00 2001
From: mike140 <mic140@ukr.net>
Date: Sun, 2 Feb 2025 01:43:26 +0000
Subject: [PATCH 27/49] Translated using Weblate (Ukrainian)

Currently translated at 51.0% (223 of 437 strings)

Translation: MapComplete/themes
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/themes/uk/
---
 langs/themes/uk.json | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/langs/themes/uk.json b/langs/themes/uk.json
index 00a76a057..267c42034 100644
--- a/langs/themes/uk.json
+++ b/langs/themes/uk.json
@@ -509,7 +509,24 @@
     },
     "pets": {
         "description": "На цій мапі ви знайдете різні цікаві місця для ваших домашніх улюбленців: ветеринари, парки для собак, зоомагазини, ресторани, дружні до собак, …",
-        "title": "Ветеринари, собачі парки та інші зручності для домашніх тварин"
+        "title": "Ветеринари, собачі парки та інші зручності для домашніх тварин",
+        "layers": {
+            "4": {
+                "override": {
+                    "name": "Заклади харчування, дружні до собак"
+                }
+            },
+            "6": {
+                "override": {
+                    "name": "Магазини, дружні до собак"
+                }
+            },
+            "8": {
+                "override": {
+                    "name=": "Кошики для сміття з дозаторами для пакетів для екскрементів"
+                }
+            }
+        }
     },
     "playgrounds": {
         "description": "На цій мапі ви знайдете дитячі майданчики та зможете додати додаткову інформацію",
@@ -555,7 +572,8 @@
         "title": "Громадські туалети"
     },
     "transit": {
-        "title": "Автобусні маршрути"
+        "title": "Автобусні маршрути",
+        "description": "Сплануйте свою подорож за допомогою системи громадського транспорту."
     },
     "trees": {
         "shortDescription": "Додайте на мапу всі дерева",
@@ -573,5 +591,9 @@
         "description": "На цій мапі ви знайдете найближчі до вас контейнери для сміття. Якщо на мапі відсутній кошик для сміття, ви можете додати його самостійно.",
         "shortDescription": "Мапа з урнами для сміття",
         "title": "Урни для сміття"
+    },
+    "scouting": {
+        "description": "Скаутський загін - це громадський молодіжний рух, що робить акцент на активному відпочинку на природі. Заходи варіюються від таборування, піших прогулянок, водних видів спорту, рюкзаків, дослідження природи, ...",
+        "title": "Скаутські групи"
     }
-}
\ No newline at end of file
+}

From 7ba1b95075debe2203b28975c67d0bd926294759 Mon Sep 17 00:00:00 2001
From: Weblate <noreply@weblate.org>
Date: Mon, 3 Feb 2025 03:35:05 +0100
Subject: [PATCH 28/49] Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: MapComplete/themes
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/themes/
---
 langs/themes/nl.json | 114 +------------------------------------------
 1 file changed, 2 insertions(+), 112 deletions(-)

diff --git a/langs/themes/nl.json b/langs/themes/nl.json
index fc4637708..8170fe891 100644
--- a/langs/themes/nl.json
+++ b/langs/themes/nl.json
@@ -653,37 +653,8 @@
                     "building type": {
                         "question": "Wat voor soort gebouw is dit?"
                     },
-                    "grb-fixme": {
-                        "mappings": {
-                            "0": {
-                                "then": "Geen fixme"
-                            }
-                        },
-                        "question": "Wat zegt de fixme?",
-                        "render": "De fixme is <b>{fixme}</b>"
-                    },
-                    "grb-housenumber": {
-                        "mappings": {
-                            "0": {
-                                "then": "Geen huisnummer"
-                            }
-                        },
-                        "question": "Wat is het huisnummer?",
-                        "render": "Het huisnummer is <b>{addr:housenumber}</b>"
-                    },
-                    "grb-min-level": {
-                        "question": "Hoeveel verdiepingen ontbreken?",
-                        "render": "Dit gebouw begint maar op de {building:min_level} verdieping"
-                    },
                     "grb-reference": {
                         "render": "Werd geïmporteerd vanuit GRB, het referentienummer is {source:geometry:ref}"
-                    },
-                    "grb-street": {
-                        "question": "Wat is de straat?",
-                        "render": "De straat is <b>{addr:street}</b>"
-                    },
-                    "grb-unit": {
-                        "render": "De wooneenheid-aanduiding is <b>{addr:unit}</b> "
                     }
                 }
             },
@@ -700,35 +671,8 @@
                         }
                     }
                 }
-            },
-            "5": {
-                "override": {
-                    "tagRenderings+": {
-                        "0": {
-                            "mappings": {
-                                "0": {
-                                    "then": "Geen omliggend OSM-gebouw gevonden"
-                                }
-                            }
-                        },
-                        "3": {
-                            "mappings": {
-                                "0": {
-                                    "then": "Geen omliggend OSM-gebouw gevonden. Een omliggend gebouw is nodig om dit punt als adres punt toe te voegen. <div class=subtle>Importeer eerst de gebouwen. Vernieuw dan de pagina om losse adressen toe te voegen</div>"
-                                }
-                            },
-                            "render": {
-                                "special": {
-                                    "text": "Voeg dit adres als een nieuw adrespunt toe"
-                                }
-                            }
-                        }
-                    }
-                }
             }
-        },
-        "shortDescription": "Grb import helper tool",
-        "title": "GRB import helper"
+        }
     },
     "guideposts": {
         "description": "Wegwijzers (ook wel handwijzer genoemd) zijn vaak te vinden langs officiële wandel-, fiets-, ski- of paardrijroutes om de richtingen naar verschillende bestemmingen aan te geven. Vaak zijn ze vernoemd naar een regio of plaats en geven ze de hoogte aan.\n\nDe positie van een wegwijzer kan door een wandelaar/fietser/renner/skiër worden gebruikt als bevestiging van de huidige positie, vooral als ze een gedrukte kaart zonder GPS-ontvanger gebruiken. ",
@@ -1156,11 +1100,6 @@
         },
         "title": "Dierenartsen, hondenloopzones en andere huisdiervriendelijke plaatsen"
     },
-    "play_forests": {
-        "description": "Een speelbos is een zone in een bos die vrij toegankelijk is voor spelende kinderen. Deze wordt  in bossen van het Agentschap Natuur en bos altijd aangeduid met het overeenkomstige bord.",
-        "shortDescription": "Deze kaart toont speelbossen",
-        "title": "Speelbossen"
-    },
     "playgrounds": {
         "description": "Op deze kaart vind je speeltuinen en kan je zelf meer informatie en foto's toevoegen",
         "shortDescription": "Een kaart met speeltuinen",
@@ -1234,47 +1173,6 @@
         "description": "Alles om te skiën",
         "title": "Skipistes en kabelbanen"
     },
-    "speelplekken": {
-        "description": "<h3>Welkom bij de Groendoener!</h3>De Zuidrand dat is spelen, ravotten, chillen, wandelen,… in het groen. Meer dan <b>200 grote en kleine speelplekken</b> liggen er in parken, in bossen en op pleintjes te wachten om ontdekt te worden. De verschillende speelplekken werden getest én goedgekeurd door kinder- en jongerenreporters uit de Zuidrand. Met leuke challenges dagen de reporters jou uit om ook op ontdekking te gaan. Klik op een speelplek op de kaart, bekijk het filmpje en ga op verkenning!<br/><br/>Het project groendoener kadert binnen het strategisch project <a href='https://www.provincieantwerpen.be/aanbod/dlm/samenwerkingsverbanden/zuidrand/projecten/strategisch-project-beleefbare-open-ruimte.html' target='_blank'>Beleefbare Open Ruimte in de Antwerpse Zuidrand</a> en is een samenwerking tussen het departement Leefmilieu van provincie Antwerpen, Sportpret vzw, een OpenStreetMap-België Consultent en Createlli vzw. Het project kwam tot stand met steun van Departement Omgeving van de Vlaamse Overheid.<br/><img class='w-full md:w-1/2' src='./assets/themes/speelplekken/provincie_antwerpen.jpg'/><img class='w-full md:w-1/2' src='./assets/themes/speelplekken/Departement_Omgeving_Vlaanderen.png'/>",
-        "layers": {
-            "6": {
-                "name": "Wandelroutes van provincie Antwerpen",
-                "tagRenderings": {
-                    "walk-description": {
-                        "render": "<h3>Korte beschrijving:</h3>{description}"
-                    },
-                    "walk-length": {
-                        "render": "Deze wandeling is <b>{_length:km}km</b> lang"
-                    },
-                    "walk-operator": {
-                        "question": "Wie beheert deze wandeling en plaatst dus de signalisatiebordjes?"
-                    },
-                    "walk-operator-email": {
-                        "question": "Naar wie kan men emailen bij problemen rond signalisatie?",
-                        "render": "Bij problemen met signalisatie kan men emailen naar <a href='mailto:{operator:email}'>{operator:email}</a>"
-                    },
-                    "walk-type": {
-                        "mappings": {
-                            "0": {
-                                "then": "Dit is een internationale wandelroute"
-                            },
-                            "1": {
-                                "then": "Dit is een nationale wandelroute"
-                            },
-                            "2": {
-                                "then": "Dit is een regionale wandelroute"
-                            },
-                            "3": {
-                                "then": "Dit is een lokale wandelroute"
-                            }
-                        }
-                    }
-                }
-            }
-        },
-        "shortDescription": "Speelplekken in de Antwerpse Zuidrand",
-        "title": "Welkom bij de groendoener!"
-    },
     "sport_pitches": {
         "description": "Een sportveld is een ingerichte plaats met infrastructuur om een sport te beoefenen",
         "shortDescription": "Deze kaart toont sportvelden",
@@ -1395,10 +1293,6 @@
         },
         "title": "Straatverlichting"
     },
-    "street_lighting_assen": {
-        "description": "Op deze kaart vind je alles over straatlantaarns + een dataset van Assen",
-        "title": "Straatverlichting - Assen"
-    },
     "surveillance": {
         "description": "Op deze open kaart kan je bewakingscamera's vinden.",
         "shortDescription": "Bewakingscameras en dergelijke",
@@ -1512,13 +1406,9 @@
         "description": "Kaart met afvalbakken en recyclingfaciliteiten.",
         "title": "Afval"
     },
-    "waste_assen": {
-        "description": "Kaart met afvalbakken en recyclingfaciliteiten + een dataset voor Assen.",
-        "title": "Afval - Assen"
-    },
     "waste_basket": {
         "description": "Op deze kaart vind je afvalbakken bij jou in de buurt. Als er een afvalbak ontbreekt op deze kaart, kun je deze zelf toevoegen",
         "shortDescription": "Een kaart met vuilnisbakken",
         "title": "Vuilnisbakken"
     }
-}
\ No newline at end of file
+}

From 2f343010fc619f5b33b983b3fb10714a53bdc35d Mon Sep 17 00:00:00 2001
From: mike140 <mic140@ukr.net>
Date: Sun, 2 Feb 2025 23:37:58 +0000
Subject: [PATCH 29/49] Translated using Weblate (Ukrainian)

Currently translated at 18.2% (753 of 4126 strings)

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/uk/
---
 langs/layers/uk.json | 225 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 217 insertions(+), 8 deletions(-)

diff --git a/langs/layers/uk.json b/langs/layers/uk.json
index 58acd7dfb..630fa0788 100644
--- a/langs/layers/uk.json
+++ b/langs/layers/uk.json
@@ -719,7 +719,8 @@
                     "then": "Пункт прокату велосипедів <i>{name}</i>"
                 }
             }
-        }
+        },
+        "name": "Ремонт/магазин велосипедів"
     },
     "cafe_pub": {
         "deletion": {
@@ -775,6 +776,13 @@
                         "question": "Має <div style='display: inline-block'><b><b>USB</b> для зарядки телефонів і малої електроніки</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div> роз'єм"
                     }
                 }
+            },
+            "1": {
+                "options": {
+                    "0": {
+                        "question": "Тільки робочі зарядні станції"
+                    }
+                }
             }
         },
         "presets": {
@@ -794,8 +802,30 @@
                 "mappings": {
                     "2": {
                         "then": "Доступна автентифікація за допомогою телефонного дзвінка"
+                    },
+                    "0": {
+                        "then": "Аутентифікація за допомогою членської картки"
+                    },
+                    "1": {
+                        "then": "Автентифікація за допомогою програми"
+                    },
+                    "3": {
+                        "then": "Доступна автентифікація за допомогою SMS"
+                    },
+                    "4": {
+                        "then": "Доступна автентифікація за допомогою NFC"
+                    },
+                    "6": {
+                        "then": "Доступна автентифікація за допомогою дебетової картки"
+                    },
+                    "7": {
+                        "then": "Зарядка тут (також) можлива без автентифікації"
+                    },
+                    "5": {
+                        "then": "Доступна автентифікація за допомогою грошової картки"
                     }
-                }
+                },
+                "question": "Яка автентифікація доступна на зарядній станції?"
             },
             "Available_charging_stations (generated)": {
                 "mappings": {
@@ -820,7 +850,25 @@
                 "render": "Частина мережі <b>{network}</b>"
             },
             "access": {
-                "render": "Доступ – {access}"
+                "render": "Доступ – {access}",
+                "question": "Хто може користуватися цією зарядною станцією?",
+                "mappings": {
+                    "0": {
+                        "then": "Будь-хто може скористатися цією зарядною станцією (може знадобитися оплата)"
+                    },
+                    "4": {
+                        "then": "Не доступні для широкої громадськості (наприклад, доступні лише для власників, працівників, ...)"
+                    },
+                    "5": {
+                        "then": "Ця зарядна станція доступна для громадськості в певні години або за певних умов. Можуть застосовуватися обмеження, але загальне використання дозволено."
+                    },
+                    "2": {
+                        "then": "Лише клієнти місця, до якого належить ця станція, можуть використовувати цю зарядну станцію<br/><span class='subtle'>Напр. зарядна станція готелю, якою можуть користуватися лише його гості</span>"
+                    },
+                    "3": {
+                        "then": "Щоб отримати доступ до цієї зарядної станції, необхідно отримати <b>ключ</b><br/><span class='subtle'>Напр. зарядна станція, якою керує готель, якою можуть користуватися лише його гості, які отримують ключ від стійки реєстрації, щоб розблокувати зарядну станцію</span>"
+                    }
+                }
             },
             "email": {
                 "question": "Яка електронна адреса оператора?"
@@ -841,8 +889,81 @@
             "website": {
                 "question": "На якому веб-сайті можна знайти більше інформації про цю зарядну станцію?",
                 "render": "Більше інформації на <a href='{website}'>{website}</a>"
+            },
+            "Operational status": {
+                "mappings": {
+                    "1": {
+                        "then": "Ця зарядна станція несправна"
+                    },
+                    "2": {
+                        "then": "Тут планується встановити зарядну станцію"
+                    },
+                    "3": {
+                        "then": "Тут побудовано зарядну станцію"
+                    },
+                    "4": {
+                        "then": "Цю зарядну станцію було назавжди вимкнено, вона більше не використовується, але її все ще можна побачити"
+                    },
+                    "0": {
+                        "then": "Ця зарядна станція працює"
+                    }
+                },
+                "question": "Чи використовується ця точка зарядки?"
+            },
+            "Type": {
+                "mappings": {
+                    "0": {
+                        "then": "Тут можна заряджати <b>велосипеди</b>"
+                    },
+                    "1": {
+                        "then": "<b>Автомобілі</b> можна зарядити тут"
+                    },
+                    "4": {
+                        "then": "<b>Автобуси</b> можна зарядити тут"
+                    },
+                    "2": {
+                        "then": "<b>Самокати</b> можна зарядити тут"
+                    },
+                    "3": {
+                        "then": "Тут можна заряджати <b>вантажні транспортні засоби</b> (наприклад, вантажівки)"
+                    }
+                },
+                "question": "Які транспортні засоби можуть тут заряджатися?"
+            },
+            "capacity": {
+                "question": "Скільки транспортних засобів можна зарядити тут одночасно?",
+                "render": "Тут можуть заряджатися одночасно {capacity} транспортних засобів"
+            },
+            "fee": {
+                "mappings": {
+                    "0": {
+                        "then": "Безкоштовне використання (без автентифікації)"
+                    },
+                    "4": {
+                        "then": "Платне користування"
+                    },
+                    "1": {
+                        "then": "Безкоштовне використання, але потрібно пройти аутентифікацію"
+                    },
+                    "3": {
+                        "then": "Платне користування, але безкоштовне для клієнтів готелю/пабу/лікарні/..., який експлуатує зарядну станцію"
+                    }
+                },
+                "question": "Чи потрібно платити за користування цією зарядною станцією?"
             }
-        }
+        },
+        "title": {
+            "render": "Зарядна станція",
+            "mappings": {
+                "1": {
+                    "then": "Зарядна станція для автомобілів"
+                },
+                "0": {
+                    "then": "Зарядна станція для електровелосипедів"
+                }
+            }
+        },
+        "name": "Зарядні станції"
     },
     "climbing": {
         "tagRenderings": {
@@ -1944,6 +2065,23 @@
             "shops-name": {
                 "question": "Як називається цей магазин?",
                 "render": "Цей магазин називається <i>{name}</i>"
+            },
+            "repairs_bikes": {
+                "mappings": {
+                    "2": {
+                        "then": "Ця майстерня ремонтує тільки куплені тут велосипеди"
+                    },
+                    "3": {
+                        "then": "Ця майстерня ремонтує велосипеди лише певної марки"
+                    },
+                    "1": {
+                        "then": "Ця майстерня не ремонтує велосипеди"
+                    },
+                    "0": {
+                        "then": "У цій майстерні ремонтують велосипеди"
+                    }
+                },
+                "question": "Чи ремонтує ця майстерня велосипеди?"
             }
         }
     },
@@ -2030,7 +2168,34 @@
                         "then": "Поверхня цієї доріжки - тартан, синтетична, злегка пружиниста, пориста поверхня"
                     }
                 },
-                "render": "Поверхня - <b>{surface}</b>"
+                "render": "Поверхня - <b>{surface}</b>",
+                "question": "Яка поверхня цього спортивного майданчика?"
+            },
+            "sport_pitch-sport": {
+                "question": "Яким видом спорту тут можна займатися?",
+                "mappings": {
+                    "0": {
+                        "then": "Тут грають у баскетбол"
+                    },
+                    "2": {
+                        "then": "Це стіл для пінг-понгу"
+                    },
+                    "3": {
+                        "then": "Тут грають у теніс"
+                    },
+                    "4": {
+                        "then": "Тут грають у корфбол"
+                    },
+                    "7": {
+                        "then": "Це манеж для верхової їзди"
+                    },
+                    "6": {
+                        "then": "Це скейт-парк"
+                    },
+                    "1": {
+                        "then": "Тут грають у футбол"
+                    }
+                }
             }
         }
     },
@@ -2313,7 +2478,18 @@
             },
             "show_crosshair": {
                 "question": "Чи потрібно показувати перехрестя по центру екрана?",
-                "questionHint": "Це може допомогти точно позиціонувати новий елемент"
+                "questionHint": "Це може допомогти точно позиціонувати новий елемент",
+                "mappings": {
+                    "0": {
+                        "then": "Показувати перехрестя в центрі мапи при збільшенні масштабу вище 17 рівня"
+                    },
+                    "1": {
+                        "then": "Не показуйте перехрестя в центрі карти"
+                    },
+                    "3": {
+                        "then": "Завжди показуйте перехрестя в центрі мапи"
+                    }
+                }
             },
             "show_debug": {
                 "mappings": {
@@ -2408,7 +2584,7 @@
                         "then": "Показати кнопку для швидкого відкриття перекладів при використанні MapComplete на великому екрані"
                     },
                     "2": {
-                        "then": "Завжди показуйте кнопки перекладу, в тому числі на мобільних пристроях"
+                        "then": "Завжди показувати кнопки перекладу, в тому числі на мобільних пристроях"
                     }
                 },
                 "question": "Ви хочете допомогти з перекладом MapComplete?"
@@ -2632,5 +2808,38 @@
         "title": {
             "render": "Утилізація відходів"
         }
+    },
+    "dog_toilet": {
+        "name": "Собачі туалети"
+    },
+    "scouting_group": {
+        "tagRenderings": {
+            "name": {
+                "render": "Назва цієї групи: {name}"
+            },
+            "association": {
+                "question": "До якої скаутської асоціації належить {name}?",
+                "render": "Скаутська асоціація цієї групи: {brand}"
+            }
+        },
+        "name": "Скаутські групи"
+    },
+    "veterinary": {
+        "name": "ветеринарія"
+    },
+    "dogpark": {
+        "name": "собачі парки"
+    },
+    "animal_shelter": {
+        "tagRenderings": {
+            "2": {
+                "question": "Як називається цей притулок для тварин?",
+                "render": "Цей притулок для тварин називається <b>{name}</b>"
+            }
+        },
+        "name": "Притулки для тварин"
+    },
+    "charge_point": {
+        "name": "Пункти зарядки"
     }
-}
\ No newline at end of file
+}

From a552606c5204c57d3e222cb17b3cf6a246bd9400 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 11:27:31 +0100
Subject: [PATCH 30/49] Fix: fix typo in campsite.json, translation sync

---
 assets/layers/campsite/campsite.json |  8 ++--
 assets/themes/scouting/scouting.json |  1 -
 langs/layers/de.json                 | 35 +++++++++++++++
 langs/layers/en.json                 | 67 ++++++++++++++++++++++++++++
 langs/themes/en.json                 |  7 +++
 5 files changed, 113 insertions(+), 5 deletions(-)

diff --git a/assets/layers/campsite/campsite.json b/assets/layers/campsite/campsite.json
index 49f13e5ed..3f1c7b12e 100644
--- a/assets/layers/campsite/campsite.json
+++ b/assets/layers/campsite/campsite.json
@@ -90,8 +90,7 @@
       "marker": [
         {
           "icon": {
-            "render": "./assets/layers/campsite/campsite.svg",
-            ]
+            "render": "./assets/layers/campsite/campsite.svg"
           }
         }
       ]
@@ -223,5 +222,6 @@
         "group_only=yes"
       ]
     }
-  ]
-}
\ No newline at end of file
+  ],
+  "allowMove": false
+}
diff --git a/assets/themes/scouting/scouting.json b/assets/themes/scouting/scouting.json
index 9874814b2..4fd607463 100644
--- a/assets/themes/scouting/scouting.json
+++ b/assets/themes/scouting/scouting.json
@@ -31,7 +31,6 @@
                 ]
               }
             ]
-
           }
         },
         "=filter": []
diff --git a/langs/layers/de.json b/langs/layers/de.json
index 779ba635a..a81468706 100644
--- a/langs/layers/de.json
+++ b/langs/layers/de.json
@@ -2188,6 +2188,41 @@
             "render": "Kneipe"
         }
     },
+    "campsite": {
+        "description": "Zeltplätze",
+        "filter": {
+            "0": {
+                "options": {
+                    "0": {
+                        "question": "Personen"
+                    }
+                }
+            }
+        },
+        "name": "Zeltplätze",
+        "tagRenderings": {
+            "capacity_persons": {
+                "question": "Wie viele Personen können hier übernachten?",
+                "render": "Hier können {capacity:persons} Personen übernachten"
+            },
+            "fee": {
+                "mappings": {
+                    "0": {
+                        "then": "Der Zeltplatz ist kostenlos"
+                    },
+                    "1": {
+                        "then": "Hier wird eine Gebühr erhoben."
+                    }
+                },
+                "question": "Wird hier eine Gebühr erhoben?",
+                "render": "Hier wird eine Gebühr von {charge} erhoben"
+            },
+            "name": {
+                "question": "Wie heißt dieser Zeltplatz?",
+                "render": "Dieser Zeltplatz heißt {name}"
+            }
+        }
+    },
     "car_rental": {
         "description": "Orte, an denen Sie ein Auto mieten können",
         "name": "Autovermietung",
diff --git a/langs/layers/en.json b/langs/layers/en.json
index 45b9be879..134b687b1 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -2206,6 +2206,73 @@
             "render": "Pub"
         }
     },
+    "campsite": {
+        "description": "Campsites",
+        "filter": {
+            "0": {
+                "options": {
+                    "0": {
+                        "question": "persons"
+                    },
+                    "1": {
+                        "question": "1-20"
+                    },
+                    "2": {
+                        "question": "21-50"
+                    },
+                    "3": {
+                        "question": "51-100"
+                    },
+                    "4": {
+                        "question": "101-200"
+                    },
+                    "5": {
+                        "question": "201-500"
+                    },
+                    "6": {
+                        "question": "500+"
+                    },
+                    "7": {
+                        "question": "?"
+                    }
+                }
+            }
+        },
+        "name": "Campsites",
+        "presets": {
+            "0": {
+                "title": "campsite"
+            },
+            "1": {
+                "title": "campsite for groups"
+            }
+        },
+        "tagRenderings": {
+            "capacity_persons": {
+                "question": "How many people can stay here?",
+                "render": "{capacity:persons} people can stay here"
+            },
+            "fee": {
+                "mappings": {
+                    "0": {
+                        "then": "The campsite is free of charge"
+                    },
+                    "1": {
+                        "then": "A fee is charged here."
+                    }
+                },
+                "question": "Is a fee charged here?",
+                "render": "A fee of {charge} should be paid for here"
+            },
+            "name": {
+                "question": "What is the name of this campsite?",
+                "render": "The name of this campsite is {name}"
+            }
+        },
+        "title": {
+            "render": "{name}"
+        }
+    },
     "car_rental": {
         "description": "Places where you can rent a car",
         "name": "Car Rental",
diff --git a/langs/themes/en.json b/langs/themes/en.json
index 226ae9eb8..3cac383a3 100644
--- a/langs/themes/en.json
+++ b/langs/themes/en.json
@@ -1167,6 +1167,13 @@
     },
     "scouting": {
         "description": "A scouting group is a social youth movement with a heavy emphasis on the outdoors. Activities range from camping, hiking, aquatics, backpacking, exploring nature, ...",
+        "layers": {
+            "1": {
+                "override": {
+                    "name": "Group Campsites"
+                }
+            }
+        },
         "title": "Scouting groups"
     },
     "shops": {

From d00ccc928261a46657882734acf47dc34282ab4b Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 11:28:27 +0100
Subject: [PATCH 31/49] Chore: lint themes

---
 assets/layers/campsite/campsite.json          | 288 +++++++++---------
 .../tertiary_education.json                   |   2 +-
 2 files changed, 145 insertions(+), 145 deletions(-)

diff --git a/assets/layers/campsite/campsite.json b/assets/layers/campsite/campsite.json
index 3f1c7b12e..82cfa3d06 100644
--- a/assets/layers/campsite/campsite.json
+++ b/assets/layers/campsite/campsite.json
@@ -1,6 +1,146 @@
 {
-  "credits": "Osmwithspace",
+  "id": "campsite",
+  "name": {
+    "en": "Campsites",
+    "de": "Zeltplätze"
+  },
+  "description": {
+    "en": "Campsites",
+    "de": "Zeltplätze"
+  },
+  "source": {
+    "osmTags": {
+      "and": [
+        "tourism=camp_site"
+      ]
+    }
+  },
   "minzoom": 7,
+  "shownByDefault": true,
+  "title": {
+    "render": {
+      "en": "{name}"
+    }
+  },
+  "pointRendering": [
+    {
+      "location": [
+        "point",
+        "centroid"
+      ],
+      "marker": [
+        {
+          "icon": {
+            "render": "./assets/layers/campsite/campsite.svg"
+          }
+        }
+      ]
+    }
+  ],
+  "lineRendering": [
+    {
+      "width": 1,
+      "color": "blue"
+    }
+  ],
+  "presets": [
+    {
+      "title": {
+        "en": "campsite"
+      },
+      "tags": [
+        "tourism=camp_site"
+      ]
+    },
+    {
+      "title": {
+        "en": "campsite for groups"
+      },
+      "tags": [
+        "tourism=camp_site",
+        "group_only=yes"
+      ]
+    }
+  ],
+  "tagRenderings": [
+    {
+      "question": {
+        "en": "What is the name of this campsite?",
+        "de": "Wie heißt dieser Zeltplatz?"
+      },
+      "render": {
+        "en": "The name of this campsite is {name}",
+        "de": "Dieser Zeltplatz heißt {name}"
+      },
+      "freeform": {
+        "key": "name"
+      },
+      "id": "name"
+    },
+    {
+      "id": "fee",
+      "question": {
+        "en": "Is a fee charged here?",
+        "de": "Wird hier eine Gebühr erhoben?"
+      },
+      "render": {
+        "en": "A fee of {charge} should be paid for here",
+        "de": "Hier wird eine Gebühr von {charge} erhoben"
+      },
+      "freeform": {
+        "key": "charge",
+        "type": "currency",
+        "addExtraTags": [
+          "fee=yes"
+        ],
+        "inline": true
+      },
+      "mappings": [
+        {
+          "if": "fee=no",
+          "addExtraTags": [
+            "charge="
+          ],
+          "then": {
+            "en": "The campsite is free of charge",
+            "de": "Der Zeltplatz ist kostenlos"
+          }
+        },
+        {
+          "if": {
+            "and": [
+              "fee=yes",
+              "charge="
+            ]
+          },
+          "then": {
+            "en": "A fee is charged here.",
+            "de": "Hier wird eine Gebühr erhoben."
+          },
+          "hideInAnswer": "charge~*"
+        }
+      ]
+    },
+    {
+      "question": {
+        "de": "Wie viele Personen können hier übernachten?",
+        "en": "How many people can stay here?"
+      },
+      "render": {
+        "en": "{capacity:persons} people can stay here",
+        "de": "Hier können {capacity:persons} Personen übernachten"
+      },
+      "freeform": {
+        "key": "capacity:persons",
+        "type": "pnat"
+      },
+      "id": "capacity_persons"
+    },
+    "contact",
+    "questions",
+    "mastodon",
+    "images"
+  ],
   "filter": [
     {
       "id": "capacity_persons_filter",
@@ -81,147 +221,7 @@
       ]
     }
   ],
-  "pointRendering": [
-    {
-      "location": [
-        "point",
-        "centroid"
-      ],
-      "marker": [
-        {
-          "icon": {
-            "render": "./assets/layers/campsite/campsite.svg"
-          }
-        }
-      ]
-    }
-  ],
-  "tagRenderings": [
-    {
-      "question": {
-        "de": "Wie heißt dieser Zeltplatz?",
-        "en": "What is the name of this campsite?"
-      },
-      "render": {
-        "en": "The name of this campsite is {name}",
-        "de": "Dieser Zeltplatz heißt {name}"
-      },
-      "freeform": {
-        "key": "name"
-      },
-      "id": "name"
-    },
-    {
-      "id": "fee",
-      "question": {
-        "en": "Is a fee charged here?",
-        "de": "Wird hier eine Gebühr erhoben?"
-      },
-      "render": {
-        "en": "A fee of {charge} should be paid for here",
-        "de": "Hier wird eine Gebühr von {charge} erhoben"
-      },
-      "freeform": {
-        "key": "charge",
-        "type": "currency",
-        "addExtraTags": [
-          "fee=yes"
-        ],
-        "inline": true
-      },
-      "mappings": [
-        {
-          "if": "fee=no",
-          "addExtraTags": [
-            "charge="
-          ],
-          "then": {
-            "en": "The campsite is free of charge",
-            "de": "Der Zeltplatz ist kostenlos"
-          }
-        },
-        {
-          "if": {
-            "and": [
-              "fee=yes",
-              "charge="
-            ]
-          },
-          "then": {
-            "en": "A fee is charged here.",
-            "de": "Hier wird eine Gebühr erhoben."
-          },
-          "hideInAnswer": "charge~*"
-        }
-      ]
-    },
-    {
-      "question": {
-        "de": "Wie viele Personen können hier übernachten?",
-        "en": "How many people can stay here?"
-      },
-      "render": {
-        "en": "{capacity:persons} people can stay here",
-        "de": "Hier können {capacity:persons} Personen übernachten"
-      },
-      "freeform": {
-        "key": "capacity:persons",
-        "type": "pnat"
-      },
-      "id": "capacity_persons"
-    },
-    "contact",
-    "questions",
-    "mastodon",
-    "images"
-  ],
-  "lineRendering": [
-    {
-      "width": 1,
-      "color": "blue"
-    }
-  ],
-  "credits:uid": 8770388,
-  "id": "campsite",
-  "name": {
-    "en": "Campsites",
-    "de": "Zeltplätze"
-  },
-  "description": {
-    "en": "Campsites",
-    "de": "Zeltplätze"
-  },
-  "title": {
-    "render": {
-      "en": "{name}"
-    }
-  },
-  "source": {
-    "osmTags": {
-      "and": [
-        "tourism=camp_site"
-      ]
-    }
-  },
-  "shownByDefault": true,
-  "presets": [
-    {
-      "title": {
-        "en": "campsite"
-      },
-      "tags": [
-        "tourism=camp_site"
-      ]
-    },
-    {
-      "title": {
-        "en": "campsite for groups"
-      },
-      "tags": [
-        "tourism=camp_site",
-        "group_only=yes"
-      ]
-    }
-  ],
-  "allowMove": false
+  "allowMove": false,
+  "credits": "Osmwithspace",
+  "credits:uid": 8770388
 }
diff --git a/assets/layers/tertiary_education/tertiary_education.json b/assets/layers/tertiary_education/tertiary_education.json
index 0129dc228..ed37438aa 100644
--- a/assets/layers/tertiary_education/tertiary_education.json
+++ b/assets/layers/tertiary_education/tertiary_education.json
@@ -11,7 +11,6 @@
     "es": "Universidades y colegios"
   },
   "description": "Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)",
-  "minzoom": 14,
   "source": {
     "osmTags": {
       "or": [
@@ -31,6 +30,7 @@
       ]
     }
   },
+  "minzoom": 14,
   "title": {
     "mappings": [
       {

From 1ecc63e5c86227704532f72dc96d16f029d2c5e6 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 11:43:43 +0100
Subject: [PATCH 32/49] UX: search doesn't eternally spin anymore, add debug
 name  for searchProviders

---
 src/Logic/Search/CombinedSearcher.ts       | 7 ++-----
 src/Logic/Search/CoordinateSearch.ts       | 2 ++
 src/Logic/Search/GeocodingProvider.ts      | 1 +
 src/Logic/Search/LocalElementSearch.ts     | 4 ++--
 src/Logic/Search/NominatimGeocoding.ts     | 1 +
 src/Logic/Search/OpenLocationCodeSearch.ts | 4 ++--
 src/Logic/Search/OpenStreetMapIdSearch.ts  | 2 +-
 src/Logic/Search/PhotonSearch.ts           | 3 ++-
 src/Logic/State/SearchState.ts             | 2 +-
 src/UI/Search/GeocodeResults.svelte        | 1 -
 10 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/src/Logic/Search/CombinedSearcher.ts b/src/Logic/Search/CombinedSearcher.ts
index ad5dc65b9..64fb0d816 100644
--- a/src/Logic/Search/CombinedSearcher.ts
+++ b/src/Logic/Search/CombinedSearcher.ts
@@ -1,12 +1,9 @@
-import GeocodingProvider, {
-    SearchResult,
-    GeocodingOptions,
-    GeocodeResult,
-} from "./GeocodingProvider"
+import GeocodingProvider, { GeocodeResult, GeocodingOptions, SearchResult } from "./GeocodingProvider"
 import { Utils } from "../../Utils"
 import { Store, Stores } from "../UIEventSource"
 
 export default class CombinedSearcher implements GeocodingProvider {
+    public readonly name = "CombinedSearcher"
     private _providers: ReadonlyArray<GeocodingProvider>
     private _providersWithSuggest: ReadonlyArray<GeocodingProvider>
 
diff --git a/src/Logic/Search/CoordinateSearch.ts b/src/Logic/Search/CoordinateSearch.ts
index 58e7f7e7a..c2c3c1b57 100644
--- a/src/Logic/Search/CoordinateSearch.ts
+++ b/src/Logic/Search/CoordinateSearch.ts
@@ -2,10 +2,12 @@ import GeocodingProvider, { GeocodeResult } from "./GeocodingProvider"
 import { Utils } from "../../Utils"
 import { ImmutableStore, Store } from "../UIEventSource"
 import CoordinateParser from "coordinate-parser"
+
 /**
  * A simple search-class which interprets possible locations
  */
 export default class CoordinateSearch implements GeocodingProvider {
+    public readonly name = "CoordinateSearch"
     private static readonly latLonRegexes: ReadonlyArray<RegExp> = [
         /^ *(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/,
         /^ *(-?[0-9]+,[0-9]+)[ ;/\\]+(-?[0-9]+,[0-9]+)/,
diff --git a/src/Logic/Search/GeocodingProvider.ts b/src/Logic/Search/GeocodingProvider.ts
index ea6187e0d..2e21f0481 100644
--- a/src/Logic/Search/GeocodingProvider.ts
+++ b/src/Logic/Search/GeocodingProvider.ts
@@ -49,6 +49,7 @@ export interface GeocodingOptions {
 }
 
 export default interface GeocodingProvider {
+    readonly name: string
     search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]>
 
     /**
diff --git a/src/Logic/Search/LocalElementSearch.ts b/src/Logic/Search/LocalElementSearch.ts
index 46f28b434..4e3d180f5 100644
--- a/src/Logic/Search/LocalElementSearch.ts
+++ b/src/Logic/Search/LocalElementSearch.ts
@@ -1,4 +1,4 @@
-import GeocodingProvider, { SearchResult, GeocodingOptions } from "./GeocodingProvider"
+import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingProvider"
 import ThemeViewState from "../../Models/ThemeViewState"
 import { Utils } from "../../Utils"
 import { Feature } from "geojson"
@@ -20,7 +20,7 @@ type IntermediateResult = {
 export default class LocalElementSearch implements GeocodingProvider {
     private readonly _state: ThemeViewState
     private readonly _limit: number
-
+    public readonly name = "LocalElementSearch"
     constructor(state: ThemeViewState, limit: number) {
         this._state = state
         this._limit = limit
diff --git a/src/Logic/Search/NominatimGeocoding.ts b/src/Logic/Search/NominatimGeocoding.ts
index ece8c9071..f19130609 100644
--- a/src/Logic/Search/NominatimGeocoding.ts
+++ b/src/Logic/Search/NominatimGeocoding.ts
@@ -8,6 +8,7 @@ import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingPr
 export class NominatimGeocoding implements GeocodingProvider {
     private readonly _host
     private readonly limit: number
+    public readonly name = "Nominatim"
 
     constructor(limit: number = 3, host: string = Constants.nominatimEndpoint) {
         this.limit = limit
diff --git a/src/Logic/Search/OpenLocationCodeSearch.ts b/src/Logic/Search/OpenLocationCodeSearch.ts
index a41fd3772..e294eaff3 100644
--- a/src/Logic/Search/OpenLocationCodeSearch.ts
+++ b/src/Logic/Search/OpenLocationCodeSearch.ts
@@ -8,7 +8,7 @@ export default class OpenLocationCodeSearch implements GeocodingProvider {
      */
     public static readonly _isPlusCode =
         /^([2-9CFGHJMPQRVWX]{2}|00){2,4}\+([2-9CFGHJMPQRVWX]{2,3})?$/
-
+    public readonly name = "OpenLocationCodeSearch"
     /**
      *
      * OpenLocationCodeSearch.isPlusCode("9FFW84J9+XG") // => true
@@ -26,7 +26,7 @@ export default class OpenLocationCodeSearch implements GeocodingProvider {
 
     async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> {
         if (!OpenLocationCodeSearch.isPlusCode(query)) {
-            return undefined
+            return [] // Must be an empty list and not "undefined", the latter is interpreted as 'still searching'
         }
         const { latitude, longitude } = pluscode_decode(query)
 
diff --git a/src/Logic/Search/OpenStreetMapIdSearch.ts b/src/Logic/Search/OpenStreetMapIdSearch.ts
index 5e07399a1..94f7607cd 100644
--- a/src/Logic/Search/OpenStreetMapIdSearch.ts
+++ b/src/Logic/Search/OpenStreetMapIdSearch.ts
@@ -7,7 +7,7 @@ import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
 export default class OpenStreetMapIdSearch implements GeocodingProvider {
     private static readonly regex =
         /((https?:\/\/)?(www.)?(osm|openstreetmap).org\/)?(n|node|w|way|r|relation)[/ ]?([0-9]+)/
-
+    public readonly name = "OpenStreetMapId"
     private static readonly types: Readonly<Record<string, "node" | "way" | "relation">> = {
         n: "node",
         w: "way",
diff --git a/src/Logic/Search/PhotonSearch.ts b/src/Logic/Search/PhotonSearch.ts
index 6b16a6250..153f74665 100644
--- a/src/Logic/Search/PhotonSearch.ts
+++ b/src/Logic/Search/PhotonSearch.ts
@@ -5,7 +5,7 @@ import GeocodingProvider, {
     GeocodingOptions,
     GeocodingUtils,
     ReverseGeocodingProvider,
-    ReverseGeocodingResult,
+    ReverseGeocodingResult
 } from "./GeocodingProvider"
 import { Utils } from "../../Utils"
 import { Feature, FeatureCollection } from "geojson"
@@ -15,6 +15,7 @@ import { Store, Stores } from "../UIEventSource"
 
 export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider {
     private readonly _endpoint: string
+    public readonly name = "photon"
     private supportedLanguages = ["en", "de", "fr"]
     private static readonly types = {
         R: "relation",
diff --git a/src/Logic/State/SearchState.ts b/src/Logic/State/SearchState.ts
index cc5808857..1d0173795 100644
--- a/src/Logic/State/SearchState.ts
+++ b/src/Logic/State/SearchState.ts
@@ -60,7 +60,7 @@ export default class SearchState {
                 return new ImmutableStore(true)
             }
             return Stores.concat(suggestions).map((suggestions) =>
-                suggestions.some((list) => list === undefined)
+                suggestions.some((list, i) => list === undefined)
             )
         })
         this.suggestions = suggestionsList.bindD((suggestions) =>
diff --git a/src/UI/Search/GeocodeResults.svelte b/src/UI/Search/GeocodeResults.svelte
index ca1d3419a..98f731659 100644
--- a/src/UI/Search/GeocodeResults.svelte
+++ b/src/UI/Search/GeocodeResults.svelte
@@ -5,7 +5,6 @@
   import Translations from "../i18n/Translations"
   import { Store } from "../../Logic/UIEventSource"
   import SidebarUnit from "../Base/SidebarUnit.svelte"
-  import type { SpecialVisualizationState } from "../SpecialVisualization"
   import Loading from "../Base/Loading.svelte"
   import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte"
   import Tr from "../Base/Tr.svelte"

From 123c50a5c0f144f4622937be87ba427c87870bbb Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 11:48:05 +0100
Subject: [PATCH 33/49] Themes(healthcare): add address info to all layers, fix
 #1202

---
 assets/layers/dentist/dentist.json                         | 5 ++---
 assets/layers/doctors/doctors.json                         | 5 ++---
 assets/layers/hospital/hospital.json                       | 1 +
 assets/layers/pharmacy/pharmacy.json                       | 1 +
 assets/layers/physiotherapist/physiotherapist.json         | 5 ++---
 assets/themes/mapcomplete-changes/mapcomplete-changes.json | 4 ----
 6 files changed, 8 insertions(+), 13 deletions(-)

diff --git a/assets/layers/dentist/dentist.json b/assets/layers/dentist/dentist.json
index a0764af7e..33d89fdc5 100644
--- a/assets/layers/dentist/dentist.json
+++ b/assets/layers/dentist/dentist.json
@@ -89,9 +89,8 @@
   "tagRenderings": [
     "images",
     "opening_hours",
-    "phone",
-    "email",
-    "website",
+    "contact",
+    "address.address",
     {
       "question": {
         "en": "What is the name of this dentist?",
diff --git a/assets/layers/doctors/doctors.json b/assets/layers/doctors/doctors.json
index 4deb5e7f2..be9d5997f 100644
--- a/assets/layers/doctors/doctors.json
+++ b/assets/layers/doctors/doctors.json
@@ -102,9 +102,8 @@
       "id": "name"
     },
     "opening_hours_by_appointment",
-    "phone",
-    "email",
-    "website",
+    "contact",
+    "address.address",
     {
       "id": "specialty",
       "render": {
diff --git a/assets/layers/hospital/hospital.json b/assets/layers/hospital/hospital.json
index c806050eb..d06f26ef7 100644
--- a/assets/layers/hospital/hospital.json
+++ b/assets/layers/hospital/hospital.json
@@ -144,6 +144,7 @@
       }
     },
     "contact",
+    "address.address",
     {
       "id": "oh-visitor",
       "question": {
diff --git a/assets/layers/pharmacy/pharmacy.json b/assets/layers/pharmacy/pharmacy.json
index 14cc6ae8f..a36d84f73 100644
--- a/assets/layers/pharmacy/pharmacy.json
+++ b/assets/layers/pharmacy/pharmacy.json
@@ -148,6 +148,7 @@
     },
     "opening_hours",
     "contact",
+    "address.address",
     "payment-options",
     "wheelchair"
   ],
diff --git a/assets/layers/physiotherapist/physiotherapist.json b/assets/layers/physiotherapist/physiotherapist.json
index 0a0f6327a..db3c24661 100644
--- a/assets/layers/physiotherapist/physiotherapist.json
+++ b/assets/layers/physiotherapist/physiotherapist.json
@@ -95,9 +95,8 @@
       "id": "name"
     },
     "opening_hours_by_appointment",
-    "phone",
-    "email",
-    "website"
+    "contact",
+    "address.address"
   ],
   "filter": [
     "open_now"
diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json
index e3ffb4d2e..da52bbaa4 100644
--- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json
+++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json
@@ -418,10 +418,6 @@
                     "if": "theme=mapcomplete-changes",
                     "then": "./assets/svg/logo.svg"
                   },
-                  {
-                    "if": "theme=maproulette",
-                    "then": "./assets/layers/maproulette/logomark.svg"
-                  },
                   {
                     "if": "theme=maps",
                     "then": "./assets/themes/maps/logo.svg"

From 103ad364f9f76a9d7e3c66d8845b166fe1ec5287 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 14:04:25 +0100
Subject: [PATCH 34/49] Docs: improve docs

---
 src/Logic/Search/GeocodingProvider.ts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/Logic/Search/GeocodingProvider.ts b/src/Logic/Search/GeocodingProvider.ts
index 2e21f0481..a698e3aa9 100644
--- a/src/Logic/Search/GeocodingProvider.ts
+++ b/src/Logic/Search/GeocodingProvider.ts
@@ -50,6 +50,12 @@ export interface GeocodingOptions {
 
 export default interface GeocodingProvider {
     readonly name: string
+
+    /**
+     * Performs search.
+     * Note: the result _must_ return an empty list in the case of no results.
+     * Undefined might be interpreted by clients as "still running"
+     */
     search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]>
 
     /**

From a6c12e5efb4dbe388b550f990bd9a0b2d703cd77 Mon Sep 17 00:00:00 2001
From: Osmwithspace <>
Date: Mon, 3 Feb 2025 14:34:13 +0100
Subject: [PATCH 35/49] set minzoom to 16

---
 assets/themes/scouting/scouting.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assets/themes/scouting/scouting.json b/assets/themes/scouting/scouting.json
index 4fd607463..c6f213a86 100644
--- a/assets/themes/scouting/scouting.json
+++ b/assets/themes/scouting/scouting.json
@@ -39,7 +39,7 @@
     {
       "builtin": "campsite",
       "override": {
-        "minzoom": 18,
+        "minzoom": 16,
         "filter": null,
         "name": null,
         "isCounted": false

From 7ba0fa3b613a5e45e88e8805354115961d0b9d90 Mon Sep 17 00:00:00 2001
From: Osmwithspace <>
Date: Mon, 3 Feb 2025 15:06:08 +0100
Subject: [PATCH 36/49] add tagRendering for group_only=yes

---
 assets/layers/campsite/campsite.json | 31 ++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/assets/layers/campsite/campsite.json b/assets/layers/campsite/campsite.json
index 82cfa3d06..3e1a6a170 100644
--- a/assets/layers/campsite/campsite.json
+++ b/assets/layers/campsite/campsite.json
@@ -63,6 +63,37 @@
     }
   ],
   "tagRenderings": [
+    {
+      "question": {
+        "de": "Ist dieser Zeltplatz ausschließlich für Gruppen?",
+        "en": "Is this campsite exclusively for groups?"
+      },
+      "mappings": [
+        {
+          "if": {
+            "and": [
+              "group_only=yes"
+            ]
+          },
+          "then": {
+            "en": "This campsite is exclusively for groups",
+            "de": "Dieser Zeltplatz ist ausschließlich für Gruppen"
+          }
+        },
+        {
+          "if": {
+            "and": [
+              "group_only=no"
+            ]
+          },
+          "then": {
+            "en": "This campsite is not exclusively for groups",
+            "de": "Dieser Zeltplatz ist nicht ausschließlich für Gruppen"
+          }
+        }
+      ],
+      "id": "group_only"
+    },
     {
       "question": {
         "en": "What is the name of this campsite?",

From 2a9b23088f89b1b6781d1af4bf264021fe53cddb Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 19:16:06 +0100
Subject: [PATCH 37/49] chore(release): 0.48.6

---
 CHANGELOG.md      | 26 ++++++++++++++++++++++++++
 package-lock.json |  4 ++--
 package.json      |  2 +-
 3 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 933002ae0..578deb4ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,32 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+### [0.48.6](https://github.com/pietervdvn/mapcomplete/compare/v0.48.5...v0.48.6) (2025-02-03)
+
+
+### Bug Fixes
+
+* actually don't show grey loading placeholder for wikidata/wikipedia, add tests ([87ab165](https://github.com/pietervdvn/mapcomplete/commits/87ab165e58472c4c88cf44c45f823706f1969c93))
+* don't check if someone is logged in for a taghint ([2f7e35a](https://github.com/pietervdvn/mapcomplete/commits/2f7e35a16e9896216d3fc4fce5d93d4272dc6219))
+* don't save big objects to the cache, see [#1919](https://github.com/pietervdvn/MapComplete/issues/1919) ([3cc7d05](https://github.com/pietervdvn/mapcomplete/commits/3cc7d05e03809616823cdf4f0143ae230e195638))
+* fix crash in movewizard ([a66c9f7](https://github.com/pietervdvn/mapcomplete/commits/a66c9f7cbe83bd80dfa41910eb8372eb0613bd25))
+* fix typo in campsite.json, translation sync ([a552606](https://github.com/pietervdvn/mapcomplete/commits/a552606c5204c57d3e222cb17b3cf6a246bd9400))
+* fix wikimedia attribution, fix [#2332](https://github.com/pietervdvn/MapComplete/issues/2332) ([6c0d54d](https://github.com/pietervdvn/mapcomplete/commits/6c0d54d7694292f3c7038ba336489d6de5231060))
+* GroupedView.svelte now shows the questions directly, so that non-answered questions show up ([3ca0d42](https://github.com/pietervdvn/mapcomplete/commits/3ca0d42eb6f35e66554f379db7820fb135d8c1d4))
+* whitelist data.Mapcomplete.org ([6444452](https://github.com/pietervdvn/mapcomplete/commits/644445248c19ecd051589b1e46feda65b3749e69))
+
+
+### Theme improvements
+
+* **address:** add simple address feature (useable as 'address.address') ([b6a5b96](https://github.com/pietervdvn/mapcomplete/commits/b6a5b963501ce636255bbecf41d020cc6394eaf3))
+* **education:** add minzoom ([57a7d36](https://github.com/pietervdvn/mapcomplete/commits/57a7d36617cdb67fae319ac70386248d440f279b))
+* **education:** move 'kindergarten' into 'school', add 'school:orientation' (only in Belgium), add more differentiated icons ([6d68ca7](https://github.com/pietervdvn/mapcomplete/commits/6d68ca7988bc6db0982ebae35efd90b5c829f435))
+* fix generation of automatic filters with multiAnswer, add test ([2ec369f](https://github.com/pietervdvn/mapcomplete/commits/2ec369f259db20ba8441748d21c17ea575a0c584))
+* **healthcare:** add address info to all layers, fix [#1202](https://github.com/pietervdvn/MapComplete/issues/1202) ([123c50a](https://github.com/pietervdvn/mapcomplete/commits/123c50a5c0f144f4622937be87ba427c87870bbb))
+* **school:** remove `school:for`, replace with school:special_needs ([f26f22a](https://github.com/pietervdvn/mapcomplete/commits/f26f22a95c3fbd6fbe89829c9c448d241dd55225))
+* **school:** small fixes to special need school tagging ([f3aa8e0](https://github.com/pietervdvn/mapcomplete/commits/f3aa8e015b07a6cf7cbc269aa9c07df205dd6ab4))
+* **shops:** fix stupid typos and bugs in healthcare service tagging ([541b8f8](https://github.com/pietervdvn/mapcomplete/commits/541b8f8960fa258223b9ea059301fd02aabb47be))
+
 ### [0.48.5](https://github.com/pietervdvn/mapcomplete/compare/v0.48.4...v0.48.5) (2025-01-28)
 
 
diff --git a/package-lock.json b/package-lock.json
index 86d1e433a..595a58d25 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "mapcomplete",
-  "version": "0.48.5",
+  "version": "0.48.6",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "mapcomplete",
-      "version": "0.48.5",
+      "version": "0.48.6",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@comunica/core": "^3.0.1",
diff --git a/package.json b/package.json
index c2ff2e4ca..2b2518e45 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mapcomplete",
-  "version": "0.48.5",
+  "version": "0.48.6",
   "repository": "https://github.com/pietervdvn/MapComplete",
   "description": "A small website to edit OSM easily",
   "bugs": "https://github.com/pietervdvn/MapComplete/issues",

From 833b82752336c9d64182cfd3dfc50853c60d5842 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 19:21:59 +0100
Subject: [PATCH 38/49] Chore: translation sync

---
 .../layers/animal_shelter/animal_shelter.json |   9 +-
 assets/layers/bike_shop/bike_shop.json        |   3 +-
 assets/layers/charge_point/charge_point.json  |   3 +-
 .../charging_station/charging_station.json    | 114 ++++--
 assets/layers/dog_toilet/dog_toilet.json      |   3 +-
 assets/layers/dogpark/dogpark.json            |   3 +-
 .../layers/scouting_group/scouting_group.json |  12 +-
 assets/layers/shops/shops.json                |  15 +-
 assets/layers/sport_pitch/sport_pitch.json    |  27 +-
 assets/layers/usersettings/usersettings.json  |  11 +-
 assets/layers/veterinary/veterinary.json      |   3 +-
 assets/themes/pets/pets.json                  |   9 +-
 assets/themes/scouting/scouting.json          |   6 +-
 assets/themes/transit/transit.json            |   3 +-
 langs/layers/de.json                          |  11 +
 langs/layers/en.json                          |  11 +
 langs/layers/uk.json                          | 348 +++++++++---------
 langs/themes/nl.json                          | 114 +++++-
 langs/themes/uk.json                          |  18 +-
 langs/uk.json                                 |   8 +-
 20 files changed, 468 insertions(+), 263 deletions(-)

diff --git a/assets/layers/animal_shelter/animal_shelter.json b/assets/layers/animal_shelter/animal_shelter.json
index 54663d9b0..f611c2ec6 100644
--- a/assets/layers/animal_shelter/animal_shelter.json
+++ b/assets/layers/animal_shelter/animal_shelter.json
@@ -11,7 +11,8 @@
     "fr": "Abri pour animaux",
     "cs": "Útulky pro zvířata",
     "ru": "Приюты для животных",
-    "nl": "Dierenasielen"
+    "nl": "Dierenasielen",
+    "uk": "Притулки для тварин"
   },
   "description": {
     "en": "An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres.",
@@ -113,7 +114,8 @@
         "pl": "Jak nazywa się to schronisko dla zwierząt?",
         "fr": "Quel est le nom de ce refuge animalier ?",
         "cs": "Jak se jmenuje tento útulek pro zvířata?",
-        "nl": "Wat is de naam van dit dierenasiel?"
+        "nl": "Wat is de naam van dit dierenasiel?",
+        "uk": "Як називається цей притулок для тварин?"
       },
       "render": {
         "en": "This animal shelter is named <b>{name}</b>",
@@ -123,7 +125,8 @@
         "pl": "To schronisko dla zwierząt nazywa się <b>{name}</b>",
         "fr": "Ce refuge s'appelle <b>{name}</b>",
         "cs": "Tento útulek pro zvířata se jmenuje <b>{name}</b>",
-        "nl": "De naam van dit dierenasiel is <b>{name}</b>"
+        "nl": "De naam van dit dierenasiel is <b>{name}</b>",
+        "uk": "Цей притулок для тварин називається <b>{name}</b>"
       }
     },
     "website",
diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json
index 020df97be..5ac2d7433 100644
--- a/assets/layers/bike_shop/bike_shop.json
+++ b/assets/layers/bike_shop/bike_shop.json
@@ -13,7 +13,8 @@
     "ca": "Botiga/reparació de bicicletes",
     "es": "Reparación/tienda de bicicletas",
     "da": "Cykelreparation/butik",
-    "cs": "Opravna/obchod s jízdními koly"
+    "cs": "Opravna/obchod s jízdními koly",
+    "uk": "Ремонт/магазин велосипедів"
   },
   "description": {
     "en": "A shop specifically selling bicycles or related items",
diff --git a/assets/layers/charge_point/charge_point.json b/assets/layers/charge_point/charge_point.json
index 27da18bca..528df5347 100644
--- a/assets/layers/charge_point/charge_point.json
+++ b/assets/layers/charge_point/charge_point.json
@@ -2,7 +2,8 @@
   "id": "charge_point",
   "name": {
     "en": "Charge points",
-    "de": "Ladesäulen"
+    "de": "Ladesäulen",
+    "uk": "Пункти зарядки"
   },
   "description": {
     "en": "Layer showing individual charge points within a charging station",
diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json
index d11b04b08..c547120dd 100644
--- a/assets/layers/charging_station/charging_station.json
+++ b/assets/layers/charging_station/charging_station.json
@@ -6,7 +6,8 @@
     "ca": "Estacions de càrrega",
     "cs": "Nabíjecí stanice",
     "de": "Ladestationen",
-    "es": "Puntos de carga"
+    "es": "Puntos de carga",
+    "uk": "Зарядні станції"
   },
   "minzoom": 10,
   "source": {
@@ -28,7 +29,8 @@
       "en": "Charging station",
       "nl": "Oplaadpunt",
       "de": "Ladestation",
-      "es": "Punto de carga"
+      "es": "Punto de carga",
+      "uk": "Зарядна станція"
     },
     "mappings": [
       {
@@ -47,7 +49,8 @@
           "en": "Charging station for electrical bicycles",
           "nl": "Oplaadpunt voor elektrische fietsen",
           "de": "Ladestation für Elektrofahrräder",
-          "es": "Punto de carga para bicicletas eléctricas"
+          "es": "Punto de carga para bicicletas eléctricas",
+          "uk": "Зарядна станція для електровелосипедів"
         }
       },
       {
@@ -66,7 +69,8 @@
           "en": "Charging station for cars",
           "nl": "Oplaadpunt voor elektrische auto's",
           "de": "Ladestation für Autos",
-          "es": "Punto de carga para coches"
+          "es": "Punto de carga para coches",
+          "uk": "Зарядна станція для автомобілів"
         }
       }
     ]
@@ -93,7 +97,8 @@
         "cs": "Která vozidla zde smí nabíjet?",
         "de": "Welche Fahrzeuge können hier laden?",
         "es": "¿Qué vehículos pueden cargar aquí?",
-        "pl": "Jakie pojazdy mogą być tutaj ładowane?"
+        "pl": "Jakie pojazdy mogą być tutaj ładowane?",
+        "uk": "Які транспортні засоби можуть тут заряджатися?"
       },
       "multiAnswer": true,
       "mappings": [
@@ -107,7 +112,8 @@
             "cs": "<b>Jízdní kola</b> lze nabíjet zde",
             "de": "Hier können <b>Fahrräder</b> laden",
             "es": "Aquí se pueden cargar <b>bicicletas</b>",
-            "pl": "Mogą tutaj być ładowane <b>rowery</b>"
+            "pl": "Mogą tutaj być ładowane <b>rowery</b>",
+            "uk": "Тут можна заряджати <b>велосипеди</b>"
           }
         },
         {
@@ -120,7 +126,8 @@
             "cs": "<b>Auta</b> lze nabíjet zde",
             "de": "Hier können <b>Autos</b> laden",
             "es": "Aquí se pueden cargar <b>coches</b>",
-            "pl": "Mogą tutaj być ładowane <b>samochody</b>"
+            "pl": "Mogą tutaj być ładowane <b>samochody</b>",
+            "uk": "<b>Автомобілі</b> можна зарядити тут"
           }
         },
         {
@@ -133,7 +140,8 @@
             "cs": "<b>Skútry</b> lze nabíjet zde",
             "de": "Hier können <b>Roller</b> laden",
             "es": "Aquí se pueden cargar <b>patinetes</b>",
-            "pl": "Mogą być tutaj ładowane <b>hulajnogi<b>"
+            "pl": "Mogą być tutaj ładowane <b>hulajnogi<b>",
+            "uk": "<b>Самокати</b> можна зарядити тут"
           }
         },
         {
@@ -145,7 +153,8 @@
             "ca": "Aquí es poden carregar <b>camions o trailers</b>",
             "cs": "<b>Těžká nákladní vozidla</b> (například nákladní automobily) lze nabíjet zde",
             "de": "Hier können <b>LKW</b> laden",
-            "es": "Aquí se pueden cargar <b>vehículos de mercancías pesadas</b> (como camiones)"
+            "es": "Aquí se pueden cargar <b>vehículos de mercancías pesadas</b> (como camiones)",
+            "uk": "Тут можна заряджати <b>вантажні транспортні засоби</b> (наприклад, вантажівки)"
           }
         },
         {
@@ -158,7 +167,8 @@
             "cs": "<b>Autobusy</b> lze nabíjet zde",
             "de": "Hier können <b>Busse</b> laden",
             "es": "Aquí se pueden cargar <b>autobuses</b>",
-            "pl": "Mogą być tutaj ładowane <b>autobusy</b>"
+            "pl": "Mogą być tutaj ładowane <b>autobusy</b>",
+            "uk": "<b>Автобуси</b> можна зарядити тут"
           }
         }
       ]
@@ -171,7 +181,8 @@
         "ca": "Qui pot utilitzar aquesta estació de càrrega?",
         "de": "Wer darf diese Ladestation benutzen?",
         "es": "¿Quién puede usar este punto de carga?",
-        "pl": "Kto może używać tej stacji ładowania?"
+        "pl": "Kto może używać tej stacji ładowania?",
+        "uk": "Хто може користуватися цією зарядною станцією?"
       },
       "render": {
         "en": "Access is {access}",
@@ -197,7 +208,8 @@
             "cs": "Tuto nabíjecí stanici může používat kdokoli (může být vyžadována platba)",
             "de": "Jeder kann die Station nutzen (eventuell gegen Bezahlung)",
             "es": "Cualquiera puede usar este punto de carga (puede ser necesario el pago)",
-            "pl": "Każdy może używać tej stacji ładowania (opłata może być wymagana)"
+            "pl": "Każdy może używać tej stacji ładowania (opłata może być wymagana)",
+            "uk": "Будь-хто може скористатися цією зарядною станцією (може знадобитися оплата)"
           }
         },
         {
@@ -221,7 +233,8 @@
             "ca": "Sols cliente del lloc al que pertany aquest punt de càrrega poden utilitzar-lo <br/><span class='subtle'>p.e. un punt de càrrega d'un hotel que sols poden utilizar-los els hostes</span>",
             "cs": "Tuto nabíjecí stanici mohou používat pouze zákazníci místa, kam tato stanice patří<br/><span class='subtle'>Např. nabíjecí stanice provozovaná hotelem, kterou mohou používat pouze jeho hosté</span>",
             "de": "Nur Kunden des Ortes, zu dem diese Station gehört, können diese Ladestation nutzen<br/><span class='subtle'>Z.B. eine von einem Hotel betriebene Ladestation, die nur von dessen Gästen genutzt werden kann</span>",
-            "es": "Solo los clientes del lugar al que pertenece esta estación pueden usar este punto de carga<br/><span class='subtle'>Por ejemplo, un punto de carga operado por un hotel que solo pueden usar sus huéspedes</span>"
+            "es": "Solo los clientes del lugar al que pertenece esta estación pueden usar este punto de carga<br/><span class='subtle'>Por ejemplo, un punto de carga operado por un hotel que solo pueden usar sus huéspedes</span>",
+            "uk": "Лише клієнти місця, до якого належить ця станція, можуть використовувати цю зарядну станцію<br/><span class='subtle'>Напр. зарядна станція готелю, якою можуть користуватися лише його гості</span>"
           }
         },
         {
@@ -231,7 +244,8 @@
             "nl": "Een <b>sleutel</b> is nodig om dit oplaadpunt te gebruiken<br/><span class='subtle'>Bv. voor klanten van een hotel of een bar, die de sleutel aan de receptie kunnen krijgen</span>",
             "ca": "S'ha de sol·licitar una <b>clau</b> per a utilitzar aquest punt de càrrega<br/><span class='subtle'>p.e un punt de càrrega operat per un hotel nomes utilitzable pel seus hostes, els quals reben una clau des de recepció per a desbloquejar el punt de càrrega</span>",
             "de": "Für den Zugang zur Station muss ein <b>Schlüssel</b> angefordert werden<br/><span class='subtle'>z.B. eine von einem Hotel betriebene Ladestation, die nur von dessen Gästen genutzt werden kann, die an der Rezeption einen Schlüssel erhalten, um die Ladestation aufzuschließen</span>",
-            "es": "Se debe solicitar una <b>llave</b> para acceder a este punto de carga<br/><span class='subtle'>Por ejemplo, un punto de carga operado por un hotel que solo pueden usar sus huéspedes, que reciben una llave de la recepción para desbloquear el punto de carga</span>"
+            "es": "Se debe solicitar una <b>llave</b> para acceder a este punto de carga<br/><span class='subtle'>Por ejemplo, un punto de carga operado por un hotel que solo pueden usar sus huéspedes, que reciben una llave de la recepción para desbloquear el punto de carga</span>",
+            "uk": "Щоб отримати доступ до цієї зарядної станції, необхідно отримати <b>ключ</b><br/><span class='subtle'>Напр. зарядна станція, якою керує готель, якою можуть користуватися лише його гості, які отримують ключ від стійки реєстрації, щоб розблокувати зарядну станцію</span>"
           }
         },
         {
@@ -241,7 +255,8 @@
             "nl": "Niet toegankelijk voor het publiek <br/><span class='subtle'>Bv. enkel toegankelijk voor de eigenaar, medewerkers ,...</span> ",
             "ca": "No accessible per al públic general (p.e. només accessible pels propietaris, empleats, ...)",
             "de": "Die Station ist nicht für die Allgemeinheit zugänglich (z. B. nur für die Eigentümer, Mitarbeiter, ...)",
-            "es": "No accesible al público en general (por ejemplo, solo accesible para los propietarios, empleados, ...)"
+            "es": "No accesible al público en general (por ejemplo, solo accesible para los propietarios, empleados, ...)",
+            "uk": "Не доступні для широкої громадськості (наприклад, доступні лише для власників, працівників, ...)"
           }
         },
         {
@@ -251,7 +266,8 @@
             "nl": "Dit oplaadstation is publiek toegankelijk onder voorwaarden (bv. enkel tijdens bepaalde uren). ",
             "ca": "Aquesta estació de càrrega és accessible al públic durant certes hores o condicions. Es poden aplicar restriccions, però es permet l'ús general.",
             "de": "Diese Ladestation ist zu gewissen Öffnungszeiten oder Bedingungen öffentlich zugänglich. Einschränkungen sind möglich, aber generelle Nutzung ist erlaubt.",
-            "es": "Este punto de carga es accesible al público durante ciertas horas o condiciones Pueden aplicarse restricciones, pero se permite el uso general"
+            "es": "Este punto de carga es accesible al público durante ciertas horas o condiciones Pueden aplicarse restricciones, pero se permite el uso general",
+            "uk": "Ця зарядна станція доступна для громадськості в певні години або за певних умов. Можуть застосовуватися обмеження, але загальне використання дозволено."
           }
         }
       ]
@@ -264,7 +280,8 @@
         "ca": "Aquí poden carregar {capacity} vehicles a l'hora",
         "de": "Hier können {capacity} Fahrzeuge gleichzeitig laden",
         "es": "{capacity} vehículos se pueden cargar aquí al mismo tiempo",
-        "pl": "{capacity} pojazdów może być tutaj ładowanych jednocześnie"
+        "pl": "{capacity} pojazdów może być tutaj ładowanych jednocześnie",
+        "uk": "Тут можуть заряджатися одночасно {capacity} транспортних засобів"
       },
       "question": {
         "en": "How much vehicles can be charged here at the same time?",
@@ -272,7 +289,8 @@
         "ca": "Quants vehicles poden carregar a la vegada?",
         "de": "Wie viele Fahrzeuge können hier gleichzeitig laden?",
         "es": "¿Cuántos vehículos se pueden cargar aquí al mismo tiempo?",
-        "pl": "Ile pojazdów może być tutaj ładowanych jednocześnie?"
+        "pl": "Ile pojazdów może być tutaj ładowanych jednocześnie?",
+        "uk": "Скільки транспортних засобів можна зарядити тут одночасно?"
       },
       "freeform": {
         "key": "capacity",
@@ -2317,7 +2335,8 @@
         "nl": "Moet men betalen om dit oplaadpunt te gebruiken?",
         "ca": "Hi ha que pagar per utilitzar aquest punt de càrrega?",
         "de": "Muss man für die Nutzung dieser Ladestation bezahlen?",
-        "es": "¿Hay que pagar para usar este punto de carga?"
+        "es": "¿Hay que pagar para usar este punto de carga?",
+        "uk": "Чи потрібно платити за користування цією зарядною станцією?"
       },
       "mappings": [
         {
@@ -2334,7 +2353,8 @@
             "en": "Free to use (without authenticating)",
             "ca": "Ús gratuït (sense autentificació)",
             "de": "Die Nutzung ist kostenlos, keine Authentifizierung erforderlich",
-            "es": "De uso gratuito (sin autenticación)"
+            "es": "De uso gratuito (sin autenticación)",
+            "uk": "Безкоштовне використання (без автентифікації)"
           }
         },
         {
@@ -2351,7 +2371,8 @@
             "en": "Free to use, but one has to authenticate",
             "ca": "Ús gratuït, però un s'ha d'autentificar",
             "de": "Die Nutzung ist kostenlos, Authentifizierung erforderlich",
-            "es": "De uso gratuito, pero hay que autenticarse"
+            "es": "De uso gratuito, pero hay que autenticarse",
+            "uk": "Безкоштовне використання, але потрібно пройти аутентифікацію"
           }
         },
         {
@@ -2382,7 +2403,8 @@
             "en": "Paid use, but free for customers of the hotel/pub/hospital/... who operates the charging station",
             "ca": "De pagament, però gratuït per als clients de l'hotel/bar/hospital/... que gestiona l'estació de càrrega",
             "de": "Die Nutzung ist kostenpflichtig, aber für Kunden des Betreibers der Einrichtung, wie Hotel, Krankenhaus, ... kostenlos",
-            "es": "De pago, pero gratuito para clientes del hotel/pub/hospital/... que opera el punto de carga"
+            "es": "De pago, pero gratuito para clientes del hotel/pub/hospital/... que opera el punto de carga",
+            "uk": "Платне користування, але безкоштовне для клієнтів готелю/пабу/лікарні/..., який експлуатує зарядну станцію"
           }
         },
         {
@@ -2397,7 +2419,8 @@
             "en": "Paid use",
             "ca": "Ús de pagament",
             "de": "Die Nutzung ist kostenpflichtig",
-            "es": "De pago"
+            "es": "De pago",
+            "uk": "Платне користування"
           }
         }
       ]
@@ -2444,7 +2467,8 @@
         "ca": "Quin tipus d'autenticació hi ha disponible a l'estació de càrrega?",
         "cs": "Jaký druh autentizace je na nabíjecí stanici k dispozici?",
         "de": "Welche Art der Authentifizierung ist an der Ladestation möglich?",
-        "es": "¿Qué tipo de autenticación está disponible en el punto de carga?"
+        "es": "¿Qué tipo de autenticación está disponible en el punto de carga?",
+        "uk": "Яка автентифікація доступна на зарядній станції?"
       },
       "multiAnswer": true,
       "mappings": [
@@ -2458,7 +2482,8 @@
             "cs": "Autentizace členskou kartou",
             "de": "Authentifizierung durch eine Mitgliedskarte",
             "es": "Autenticación mediante tarjeta de socio",
-            "fr": "Authentification par carte de membre"
+            "fr": "Authentification par carte de membre",
+            "uk": "Аутентифікація за допомогою членської картки"
           }
         },
         {
@@ -2471,7 +2496,8 @@
             "cs": "Autentizace pomocí aplikace",
             "de": "Authentifizierung per App",
             "es": "Autenticación mediante una aplicación",
-            "fr": "Authentification par une application"
+            "fr": "Authentification par une application",
+            "uk": "Автентифікація за допомогою програми"
           }
         },
         {
@@ -2496,7 +2522,8 @@
             "cs": "K dispozici je autentizace pomocí SMS",
             "de": "Authentifizierung per SMS ist möglich",
             "es": "Hay disponible autenticación por SMS",
-            "fr": "Authentification possible par SMS"
+            "fr": "Authentification possible par SMS",
+            "uk": "Доступна автентифікація за допомогою SMS"
           }
         },
         {
@@ -2508,7 +2535,8 @@
             "ca": "L'autenticació mitjançant NFC està disponible",
             "cs": "K dispozici je autentizace přes NFC",
             "de": "Authentifizierung per NFC ist möglich",
-            "es": "Hay disponible autenticación por NFC"
+            "es": "Hay disponible autenticación por NFC",
+            "uk": "Доступна автентифікація за допомогою NFC"
           }
         },
         {
@@ -2520,7 +2548,8 @@
             "ca": "L'autenticació mitjançant targeta de pagament està disponible",
             "cs": "K dispozici je autentizace prostřednictvím Money Card",
             "de": "Authentifizierung per Geldkarte ist möglich",
-            "es": "Hay disponible autenticación por tarjeta de crédito"
+            "es": "Hay disponible autenticación por tarjeta de crédito",
+            "uk": "Доступна автентифікація за допомогою грошової картки"
           }
         },
         {
@@ -2532,7 +2561,8 @@
             "ca": "L'autenticació mitjançant targeta de debit està disponible",
             "cs": "K dispozici je autentizace prostřednictvím debetní karty",
             "de": "Authentifizierung per Kreditkarte ist möglich",
-            "es": "Hay disponible autenticación por tarjeta de débito"
+            "es": "Hay disponible autenticación por tarjeta de débito",
+            "uk": "Доступна автентифікація за допомогою дебетової картки"
           }
         },
         {
@@ -2544,7 +2574,8 @@
             "ca": "Carregar aquí (també) és possible sense autenticació",
             "cs": "Nabíjení je zde (také) možné bez autentizace",
             "de": "Das Laden ist hier (auch) ohne Authentifizierung möglich",
-            "es": "La carga aquí también es posible sin autenticación"
+            "es": "La carga aquí también es posible sin autenticación",
+            "uk": "Зарядка тут (також) можлива без автентифікації"
           }
         }
       ],
@@ -2831,7 +2862,8 @@
         "ca": "Està en ús aquest punt de càrrega?",
         "cs": "Používá se tento nabíjecí bod?",
         "de": "Ist die Station in Betrieb?",
-        "es": "¿Este punto de carga está en uso?"
+        "es": "¿Este punto de carga está en uso?",
+        "uk": "Чи використовується ця точка зарядки?"
       },
       "mappings": [
         {
@@ -2852,7 +2884,8 @@
             "de": "Die Station ist in Betrieb",
             "es": "Este punto de carga funciona",
             "fr": "Cette station de recharge fonctionne",
-            "pl": "Ta stacja ładowania działa"
+            "pl": "Ta stacja ładowania działa",
+            "uk": "Ця зарядна станція працює"
           }
         },
         {
@@ -2872,7 +2905,8 @@
             "cs": "Tato nabíjecí stanice je rozbitá",
             "de": "Die Station ist defekt",
             "es": "Este punto de carga está roto",
-            "pl": "Ta stacja ładowania jest zepsuta"
+            "pl": "Ta stacja ładowania jest zepsuta",
+            "uk": "Ця зарядна станція несправна"
           }
         },
         {
@@ -2892,7 +2926,8 @@
             "cs": "Plánuje se zde nabíjecí stanice",
             "de": "Die Station ist erst in Planung",
             "es": "Aquí se planea un punto de carga",
-            "pl": "Planowana jest tutaj stacja ładowania"
+            "pl": "Planowana jest tutaj stacja ładowania",
+            "uk": "Тут планується встановити зарядну станцію"
           }
         },
         {
@@ -2911,7 +2946,8 @@
             "cs": "Zde je budována dobíjecí stanice",
             "de": "Die Station ist aktuell im Bau",
             "es": "Aquí se está construyendo un punto de carga",
-            "pl": "Budowana jest tutaj stacja ładowania"
+            "pl": "Budowana jest tutaj stacja ładowania",
+            "uk": "Тут побудовано зарядну станцію"
           }
         },
         {
@@ -2930,7 +2966,8 @@
             "ca": "Aquesta estació de recàrrega s'ha desactivat permanentment i ja no s'utilitza, però encara és visible",
             "cs": "Tato nabíjecí stanice byla trvale deaktivována a již se nepoužívá, ale je stále viditelná",
             "de": "Die Station ist dauerhaft geschlossen und nicht mehr in Nutzung, aber noch sichtbar",
-            "es": "Este punto de carga ha sido deshabilitado permanentemente y ya no está en uso, pero sigue siendo visible"
+            "es": "Este punto de carga ha sido deshabilitado permanentemente y ya no está en uso, pero sigue siendo visible",
+            "uk": "Цю зарядну станцію було назавжди вимкнено, вона більше не використовується, але її все ще можна побачити"
           }
         }
       ]
@@ -3161,7 +3198,8 @@
             "de": "Nur Ladestationen in Betrieb",
             "es": "Solo puntos de carga en funcionamiento",
             "fr": "Seulement les stations de recharge qui fonctionnent",
-            "it": "Solo stazioni di ricarica funzionanti"
+            "it": "Solo stazioni di ricarica funzionanti",
+            "uk": "Тільки робочі зарядні станції"
           },
           "osmTags": {
             "and": [
diff --git a/assets/layers/dog_toilet/dog_toilet.json b/assets/layers/dog_toilet/dog_toilet.json
index ad21f2891..316846237 100644
--- a/assets/layers/dog_toilet/dog_toilet.json
+++ b/assets/layers/dog_toilet/dog_toilet.json
@@ -2,7 +2,8 @@
   "id": "dog_toilet",
   "name": {
     "en": "Dog toilets",
-    "de": "Hundetoiletten"
+    "de": "Hundetoiletten",
+    "uk": "Собачі туалети"
   },
   "description": {
     "en": "A dog toilet is a facility designated for dogs to urinate and excrete. This can be a designated, signposted patch of grass, a sand pit or a fenced area.",
diff --git a/assets/layers/dogpark/dogpark.json b/assets/layers/dogpark/dogpark.json
index 67da75e1b..21debd0e4 100644
--- a/assets/layers/dogpark/dogpark.json
+++ b/assets/layers/dogpark/dogpark.json
@@ -8,7 +8,8 @@
     "nl": "hondenweides",
     "ca": "parcs de gossos",
     "cs": "psí parky",
-    "ru": "парки для собак"
+    "ru": "парки для собак",
+    "uk": "собачі парки"
   },
   "description": "A layer showing dogparks, which are areas where dog are allowed to run without a leash",
   "source": {
diff --git a/assets/layers/scouting_group/scouting_group.json b/assets/layers/scouting_group/scouting_group.json
index d009dbcf0..848010fda 100644
--- a/assets/layers/scouting_group/scouting_group.json
+++ b/assets/layers/scouting_group/scouting_group.json
@@ -2,7 +2,8 @@
   "id": "scouting_group",
   "name": {
     "en": "Scouting groups",
-    "de": "Pfadfinder:innenstämme"
+    "de": "Pfadfinder:innenstämme",
+    "uk": "Скаутські групи"
   },
   "description": {
     "en": "A map showing scouting groups.",
@@ -61,7 +62,8 @@
       },
       "render": {
         "en": "The name of this group is {name}",
-        "de": "Dieser Stamm heißt {name}"
+        "de": "Dieser Stamm heißt {name}",
+        "uk": "Назва цієї групи: {name}"
       },
       "freeform": {
         "key": "name"
@@ -71,11 +73,13 @@
     {
       "question": {
         "de": "Zu welchem Verband/Bund gehört {name}",
-        "en": "To which scout association does {name} belong?"
+        "en": "To which scout association does {name} belong?",
+        "uk": "До якої скаутської асоціації належить {name}?"
       },
       "render": {
         "en": "The scout association of this group is {brand}",
-        "de": "Dieser Stamm ist im Verband {brand}"
+        "de": "Dieser Stamm ist im Verband {brand}",
+        "uk": "Скаутська асоціація цієї групи: {brand}"
       },
       "freeform": {
         "key": "brand"
diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json
index 94bcbbe04..bbec1a51d 100644
--- a/assets/layers/shops/shops.json
+++ b/assets/layers/shops/shops.json
@@ -811,7 +811,8 @@
         "es": "¿Esta tienda repara bicicletas?",
         "da": "Reparerer denne butik cykler?",
         "ca": "Aquesta botiga repara bicicletes?",
-        "cs": "Opravuje tento obchod jízdní kola?"
+        "cs": "Opravuje tento obchod jízdní kola?",
+        "uk": "Чи ремонтує ця майстерня велосипеди?"
       },
       "mappings": [
         {
@@ -829,7 +830,8 @@
             "es": "Esta tienda repara bicicletas",
             "da": "Denne butik reparerer cykler",
             "ca": "Aquesta botiga repara bicis",
-            "cs": "Tento obchod opravuje jízdní kola"
+            "cs": "Tento obchod opravuje jízdní kola",
+            "uk": "У цій майстерні ремонтують велосипеди"
           }
         },
         {
@@ -847,7 +849,8 @@
             "es": "Esta tienda no repara bicicletas",
             "da": "Denne butik reparerer ikke cykler",
             "ca": "Aquesta botiga no repara bicis",
-            "cs": "Tento obchod neopravuje jízdní kola"
+            "cs": "Tento obchod neopravuje jízdní kola",
+            "uk": "Ця майстерня не ремонтує велосипеди"
           }
         },
         {
@@ -865,7 +868,8 @@
             "es": "Esta tienda solo repara bicicletas compradas aquí",
             "da": "Denne butik reparerer kun cykler købt her",
             "ca": "Aquesta botiga sols repara bicis comprades aquí",
-            "cs": "Tento obchod opravuje pouze zde zakoupená kola"
+            "cs": "Tento obchod opravuje pouze zde zakoupená kola",
+            "uk": "Ця майстерня ремонтує тільки куплені тут велосипеди"
           }
         },
         {
@@ -883,7 +887,8 @@
             "es": "Esta tienda solo repara bicicletas de una determinada marca",
             "da": "Dette værksted reparerer kun cykler af et bestemt mærke",
             "ca": "Aquesta tenda sols repara bicis d’una marca concreta",
-            "cs": "Tento obchod opravuje pouze kola určité značky"
+            "cs": "Tento obchod opravuje pouze kola určité značky",
+            "uk": "Ця майстерня ремонтує велосипеди лише певної марки"
           }
         }
       ]
diff --git a/assets/layers/sport_pitch/sport_pitch.json b/assets/layers/sport_pitch/sport_pitch.json
index 3a6bd8780..1412b973d 100644
--- a/assets/layers/sport_pitch/sport_pitch.json
+++ b/assets/layers/sport_pitch/sport_pitch.json
@@ -214,7 +214,8 @@
         "de": "Welche Sportarten können hier gespielt werden?",
         "es": "¿Qué deporte se puede practicar aquí?",
         "ca": "Quin esport es pot practicar aquí?",
-        "cs": "Jaký sport se zde dá provozovat?"
+        "cs": "Jaký sport se zde dá provozovat?",
+        "uk": "Яким видом спорту тут можна займатися?"
       },
       "multiAnswer": true,
       "mappings": [
@@ -233,7 +234,8 @@
             "de": "Hier wird Basketball gespielt",
             "es": "Aquí se juega al baloncesto",
             "ca": "Aquí es juga bàsquet",
-            "cs": "Zde se hraje basketbal"
+            "cs": "Zde se hraje basketbal",
+            "uk": "Тут грають у баскетбол"
           }
         },
         {
@@ -251,7 +253,8 @@
             "de": "Hier wird Fußball gespielt",
             "es": "Aquí se juega al fútbol",
             "ca": "Aquí es juga futbol",
-            "cs": "Zde se hraje fotbal"
+            "cs": "Zde se hraje fotbal",
+            "uk": "Тут грають у футбол"
           }
         },
         {
@@ -269,7 +272,8 @@
             "de": "Dies ist eine Tischtennisplatte",
             "es": "Esta es una mesa de ping-pong",
             "ca": "Aquí es juga ping pong",
-            "cs": "Toto je pingpongový stůl"
+            "cs": "Toto je pingpongový stůl",
+            "uk": "Це стіл для пінг-понгу"
           }
         },
         {
@@ -287,7 +291,8 @@
             "de": "Hier wird Tennis gespielt",
             "es": "Aquí se juega al tenis",
             "ca": "Aquí es juga al tenis",
-            "cs": "Zde se hraje tenis"
+            "cs": "Zde se hraje tenis",
+            "uk": "Тут грають у теніс"
           }
         },
         {
@@ -305,7 +310,8 @@
             "de": "Hier wird Korfball gespielt",
             "ca": "Aquí es juga al corfbol",
             "cs": "Zde se hraje korfbal",
-            "es": "Aquí se juega al Korfbal"
+            "es": "Aquí se juega al Korfbal",
+            "uk": "Тут грають у корфбол"
           }
         },
         {
@@ -339,7 +345,8 @@
             "de": "Dies ist ein Skatepark",
             "ca": "Açò és un skatepark",
             "cs": "Toto je skatepark",
-            "es": "Este es un skatepark"
+            "es": "Este es un skatepark",
+            "uk": "Це скейт-парк"
           }
         },
         {
@@ -351,7 +358,8 @@
           "then": {
             "en": "This is a horse riding arena",
             "de": "Dies ist ein Reitplatz",
-            "es": "Esta es una pista ecuestre"
+            "es": "Esta es una pista ecuestre",
+            "uk": "Це манеж для верхової їзди"
           }
         }
       ],
@@ -421,7 +429,8 @@
         "de": "Welchen Belag hat der Sportplatz?",
         "es": "¿Cuál es la superficie de esta cancha deportiva?",
         "ca": "Quina és la superfície d'aquest camp esportiu?",
-        "cs": "Jaký je povrch tohoto sportovního hřiště?"
+        "cs": "Jaký je povrch tohoto sportovního hřiště?",
+        "uk": "Яка поверхня цього спортивного майданчика?"
       },
       "render": {
         "nl": "De ondergrond is <b>{surface}</b>",
diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json
index 099c5d149..f05d8aff6 100644
--- a/assets/layers/usersettings/usersettings.json
+++ b/assets/layers/usersettings/usersettings.json
@@ -593,14 +593,16 @@
           "if": "mapcomplete-show_crosshair=yes",
           "then": {
             "en": "Show a crosshair in the center of the map when zoomed in above level 17",
-            "de": "Zeige ein Fadenkreuz in der Mitte der Karte, wenn über Stufe 17 hinaus gezoomt wird"
+            "de": "Zeige ein Fadenkreuz in der Mitte der Karte, wenn über Stufe 17 hinaus gezoomt wird",
+            "uk": "Показувати перехрестя в центрі мапи при збільшенні масштабу вище 17 рівня"
           }
         },
         {
           "if": "mapcomplete-show_crosshair=no",
           "then": {
             "en": "Do not show a crosshair in the center of the map",
-            "de": "Zeige kein Fadenkreuz in der Mitte der Karte an"
+            "de": "Zeige kein Fadenkreuz in der Mitte der Karte an",
+            "uk": "Не показуйте перехрестя в центрі карти"
           }
         },
         {
@@ -615,7 +617,8 @@
           "if": "mapcomplete-show_crosshair=always",
           "then": {
             "en": "Always show a crosshair in the center of the map",
-            "de": "Zeige immer ein Fadenkreuz in der Mitte der Karte an"
+            "de": "Zeige immer ein Fadenkreuz in der Mitte der Karte an",
+            "uk": "Завжди показуйте перехрестя в центрі мапи"
           }
         }
       ],
@@ -1257,7 +1260,7 @@
             "cs": "Vždy zobrazovat tlačítka překladu, a to i na mobilu",
             "nl": "Toon een knopje om snel vertalingen te maken op mobiel en op grote schermen",
             "hu": "Mindig jelenítse meg a fordítási gombokat, mobilon is",
-            "uk": "Завжди показуйте кнопки перекладу, в тому числі на мобільних пристроях",
+            "uk": "Завжди показувати кнопки перекладу, в тому числі на мобільних пристроях",
             "es": "Mostrar siempre los botones de traducción, incluso en dispositivos móviles"
           }
         }
diff --git a/assets/layers/veterinary/veterinary.json b/assets/layers/veterinary/veterinary.json
index 3cebcb176..039ca12f5 100644
--- a/assets/layers/veterinary/veterinary.json
+++ b/assets/layers/veterinary/veterinary.json
@@ -10,7 +10,8 @@
     "ca": "Veterinari",
     "cs": "veterinář",
     "pl": "weterynaryjny",
-    "ru": "ветеринария"
+    "ru": "ветеринария",
+    "uk": "ветеринарія"
   },
   "description": "A layer showing veterinarians",
   "source": {
diff --git a/assets/themes/pets/pets.json b/assets/themes/pets/pets.json
index 919e769cc..a5dd427db 100644
--- a/assets/themes/pets/pets.json
+++ b/assets/themes/pets/pets.json
@@ -61,7 +61,8 @@
           "pl": "Restauracje przyjazne psom",
           "it": "Ristoranti che accettano i cani",
           "ru": "Заведения, где можно поесть с собаками",
-          "ko": "반려견 친화적 식당"
+          "ko": "반려견 친화적 식당",
+          "uk": "Заклади харчування, дружні до собак"
         },
         "pointRendering": [
           {
@@ -115,7 +116,8 @@
           "zh_Hant": "寵物友善商家",
           "pl": "Sklepy przyjazne psom",
           "it": "Negozi che accettano i cani",
-          "ko": "반려견 친화적 상점"
+          "ko": "반려견 친화적 상점",
+          "uk": "Магазини, дружні до собак"
         },
         "pointRendering": [
           {
@@ -156,7 +158,8 @@
         "name=": {
           "en": "Waste baskets with excrement bag dispensers",
           "nl": "Vuilnisbakken met verdelers voor hondenpoepzakjes",
-          "de": "Mülleimer mit Spender für Kotbeutel"
+          "de": "Mülleimer mit Spender für Kotbeutel",
+          "uk": "Кошики для сміття з дозаторами для пакетів для екскрементів"
         },
         "presets=": [],
         "filter=": [],
diff --git a/assets/themes/scouting/scouting.json b/assets/themes/scouting/scouting.json
index c6f213a86..5446b20ab 100644
--- a/assets/themes/scouting/scouting.json
+++ b/assets/themes/scouting/scouting.json
@@ -2,11 +2,13 @@
   "id": "scouting",
   "title": {
     "en": "Scouting groups",
-    "de": "Pfadfindergruppen"
+    "de": "Pfadfindergruppen",
+    "uk": "Скаутські групи"
   },
   "description": {
     "en": "A scouting group is a social youth movement with a heavy emphasis on the outdoors. Activities range from camping, hiking, aquatics, backpacking, exploring nature, ...",
-    "de": "Eine Pfadfindergruppe ist eine soziale Jugendbewegung mit einem starken Fokus auf Aktivitäten im Freien. Die Aktivitäten reichen von Camping, Wandern, Wassersport, Rucksacktouren, Erkundung der Natur, ..."
+    "de": "Eine Pfadfindergruppe ist eine soziale Jugendbewegung mit einem starken Fokus auf Aktivitäten im Freien. Die Aktivitäten reichen von Camping, Wandern, Wassersport, Rucksacktouren, Erkundung der Natur, ...",
+    "uk": "Скаутський загін - це громадський молодіжний рух, що робить акцент на активному відпочинку на природі. Заходи варіюються від таборування, піших прогулянок, водних видів спорту, рюкзаків, дослідження природи, ..."
   },
   "icon": "./assets/layers/scouting_group/scouting.svg",
   "defaultBackgroundId": "protomaps.dark",
diff --git a/assets/themes/transit/transit.json b/assets/themes/transit/transit.json
index 349ac75f0..313a6fb06 100644
--- a/assets/themes/transit/transit.json
+++ b/assets/themes/transit/transit.json
@@ -30,7 +30,8 @@
     "cs": "Naplánujte si cestu pomocí systému veřejné dopravy.",
     "zh_Hant": "藉由大眾運輸系統來計畫你的旅程。",
     "pl": "Zaplanuj swoją podróż korzystając z systemu transportu publicznego.",
-    "ko": "대중교통 시스템을 이용한 여행 계획 세우기."
+    "ko": "대중교통 시스템을 이용한 여행 계획 세우기.",
+    "uk": "Сплануйте свою подорож за допомогою системи громадського транспорту."
   },
   "icon": "./assets/layers/transit_stops/bus_stop.svg",
   "startZoom": 20,
diff --git a/langs/layers/de.json b/langs/layers/de.json
index a81468706..be10415c2 100644
--- a/langs/layers/de.json
+++ b/langs/layers/de.json
@@ -2217,6 +2217,17 @@
                 "question": "Wird hier eine Gebühr erhoben?",
                 "render": "Hier wird eine Gebühr von {charge} erhoben"
             },
+            "group_only": {
+                "mappings": {
+                    "0": {
+                        "then": "Dieser Zeltplatz ist ausschließlich für Gruppen"
+                    },
+                    "1": {
+                        "then": "Dieser Zeltplatz ist nicht ausschließlich für Gruppen"
+                    }
+                },
+                "question": "Ist dieser Zeltplatz ausschließlich für Gruppen?"
+            },
             "name": {
                 "question": "Wie heißt dieser Zeltplatz?",
                 "render": "Dieser Zeltplatz heißt {name}"
diff --git a/langs/layers/en.json b/langs/layers/en.json
index 134b687b1..ec1eaef30 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -2264,6 +2264,17 @@
                 "question": "Is a fee charged here?",
                 "render": "A fee of {charge} should be paid for here"
             },
+            "group_only": {
+                "mappings": {
+                    "0": {
+                        "then": "This campsite is exclusively for groups"
+                    },
+                    "1": {
+                        "then": "This campsite is not exclusively for groups"
+                    }
+                },
+                "question": "Is this campsite exclusively for groups?"
+            },
             "name": {
                 "question": "What is the name of this campsite?",
                 "render": "The name of this campsite is {name}"
diff --git a/langs/layers/uk.json b/langs/layers/uk.json
index 630fa0788..50f1ea33d 100644
--- a/langs/layers/uk.json
+++ b/langs/layers/uk.json
@@ -248,6 +248,15 @@
             }
         }
     },
+    "animal_shelter": {
+        "name": "Притулки для тварин",
+        "tagRenderings": {
+            "2": {
+                "question": "Як називається цей притулок для тварин?",
+                "render": "Цей притулок для тварин називається <b>{name}</b>"
+            }
+        }
+    },
     "artwork": {
         "description": "Відкрита мапа статуй, бюстів, графіті та інших творів мистецтва по всьому світу",
         "tagRenderings": {
@@ -708,6 +717,7 @@
         }
     },
     "bike_shop": {
+        "name": "Ремонт/магазин велосипедів",
         "presets": {
             "0": {
                 "title": "майстерня з ремонту велосипедів"
@@ -719,8 +729,7 @@
                     "then": "Пункт прокату велосипедів <i>{name}</i>"
                 }
             }
-        },
-        "name": "Ремонт/магазин велосипедів"
+        }
     },
     "cafe_pub": {
         "deletion": {
@@ -768,23 +777,27 @@
             }
         }
     },
+    "charge_point": {
+        "name": "Пункти зарядки"
+    },
     "charging_station": {
         "filter": {
-            "2": {
-                "options": {
-                    "14": {
-                        "question": "Має <div style='display: inline-block'><b><b>USB</b> для зарядки телефонів і малої електроніки</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div> роз'єм"
-                    }
-                }
-            },
             "1": {
                 "options": {
                     "0": {
                         "question": "Тільки робочі зарядні станції"
                     }
                 }
+            },
+            "2": {
+                "options": {
+                    "14": {
+                        "question": "Має <div style='display: inline-block'><b><b>USB</b> для зарядки телефонів і малої електроніки</b> <img style='width:1rem; display: inline-block' src='./assets/layers/charging_station/usb_port.svg'/></div> роз'єм"
+                    }
+                }
             }
         },
+        "name": "Зарядні станції",
         "presets": {
             "0": {
                 "title": "зарядна станція для електровелосипедів"
@@ -800,29 +813,29 @@
             },
             "Authentication": {
                 "mappings": {
-                    "2": {
-                        "then": "Доступна автентифікація за допомогою телефонного дзвінка"
-                    },
                     "0": {
                         "then": "Аутентифікація за допомогою членської картки"
                     },
                     "1": {
                         "then": "Автентифікація за допомогою програми"
                     },
+                    "2": {
+                        "then": "Доступна автентифікація за допомогою телефонного дзвінка"
+                    },
                     "3": {
                         "then": "Доступна автентифікація за допомогою SMS"
                     },
                     "4": {
                         "then": "Доступна автентифікація за допомогою NFC"
                     },
+                    "5": {
+                        "then": "Доступна автентифікація за допомогою грошової картки"
+                    },
                     "6": {
                         "then": "Доступна автентифікація за допомогою дебетової картки"
                     },
                     "7": {
                         "then": "Зарядка тут (також) можлива без автентифікації"
-                    },
-                    "5": {
-                        "then": "Доступна автентифікація за допомогою грошової картки"
                     }
                 },
                 "question": "Яка автентифікація доступна на зарядній станції?"
@@ -849,30 +862,91 @@
                 "question": "Чи є ця зарядна станція частиною мережі?",
                 "render": "Частина мережі <b>{network}</b>"
             },
+            "Operational status": {
+                "mappings": {
+                    "0": {
+                        "then": "Ця зарядна станція працює"
+                    },
+                    "1": {
+                        "then": "Ця зарядна станція несправна"
+                    },
+                    "2": {
+                        "then": "Тут планується встановити зарядну станцію"
+                    },
+                    "3": {
+                        "then": "Тут побудовано зарядну станцію"
+                    },
+                    "4": {
+                        "then": "Цю зарядну станцію було назавжди вимкнено, вона більше не використовується, але її все ще можна побачити"
+                    }
+                },
+                "question": "Чи використовується ця точка зарядки?"
+            },
+            "Type": {
+                "mappings": {
+                    "0": {
+                        "then": "Тут можна заряджати <b>велосипеди</b>"
+                    },
+                    "1": {
+                        "then": "<b>Автомобілі</b> можна зарядити тут"
+                    },
+                    "2": {
+                        "then": "<b>Самокати</b> можна зарядити тут"
+                    },
+                    "3": {
+                        "then": "Тут можна заряджати <b>вантажні транспортні засоби</b> (наприклад, вантажівки)"
+                    },
+                    "4": {
+                        "then": "<b>Автобуси</b> можна зарядити тут"
+                    }
+                },
+                "question": "Які транспортні засоби можуть тут заряджатися?"
+            },
             "access": {
-                "render": "Доступ – {access}",
-                "question": "Хто може користуватися цією зарядною станцією?",
                 "mappings": {
                     "0": {
                         "then": "Будь-хто може скористатися цією зарядною станцією (може знадобитися оплата)"
                     },
-                    "4": {
-                        "then": "Не доступні для широкої громадськості (наприклад, доступні лише для власників, працівників, ...)"
-                    },
-                    "5": {
-                        "then": "Ця зарядна станція доступна для громадськості в певні години або за певних умов. Можуть застосовуватися обмеження, але загальне використання дозволено."
-                    },
                     "2": {
                         "then": "Лише клієнти місця, до якого належить ця станція, можуть використовувати цю зарядну станцію<br/><span class='subtle'>Напр. зарядна станція готелю, якою можуть користуватися лише його гості</span>"
                     },
                     "3": {
                         "then": "Щоб отримати доступ до цієї зарядної станції, необхідно отримати <b>ключ</b><br/><span class='subtle'>Напр. зарядна станція, якою керує готель, якою можуть користуватися лише його гості, які отримують ключ від стійки реєстрації, щоб розблокувати зарядну станцію</span>"
+                    },
+                    "4": {
+                        "then": "Не доступні для широкої громадськості (наприклад, доступні лише для власників, працівників, ...)"
+                    },
+                    "5": {
+                        "then": "Ця зарядна станція доступна для громадськості в певні години або за певних умов. Можуть застосовуватися обмеження, але загальне використання дозволено."
                     }
-                }
+                },
+                "question": "Хто може користуватися цією зарядною станцією?",
+                "render": "Доступ – {access}"
+            },
+            "capacity": {
+                "question": "Скільки транспортних засобів можна зарядити тут одночасно?",
+                "render": "Тут можуть заряджатися одночасно {capacity} транспортних засобів"
             },
             "email": {
                 "question": "Яка електронна адреса оператора?"
             },
+            "fee": {
+                "mappings": {
+                    "0": {
+                        "then": "Безкоштовне використання (без автентифікації)"
+                    },
+                    "1": {
+                        "then": "Безкоштовне використання, але потрібно пройти аутентифікацію"
+                    },
+                    "3": {
+                        "then": "Платне користування, але безкоштовне для клієнтів готелю/пабу/лікарні/..., який експлуатує зарядну станцію"
+                    },
+                    "4": {
+                        "then": "Платне користування"
+                    }
+                },
+                "question": "Чи потрібно платити за користування цією зарядною станцією?"
+            },
             "phone": {
                 "question": "За яким номером можна зателефонувати, якщо виникла проблема з цією зарядною станцією?",
                 "render": "У разі виникнення проблем телефонуйте <a href='tel:{phone}'>{phone}</a>"
@@ -889,81 +963,19 @@
             "website": {
                 "question": "На якому веб-сайті можна знайти більше інформації про цю зарядну станцію?",
                 "render": "Більше інформації на <a href='{website}'>{website}</a>"
-            },
-            "Operational status": {
-                "mappings": {
-                    "1": {
-                        "then": "Ця зарядна станція несправна"
-                    },
-                    "2": {
-                        "then": "Тут планується встановити зарядну станцію"
-                    },
-                    "3": {
-                        "then": "Тут побудовано зарядну станцію"
-                    },
-                    "4": {
-                        "then": "Цю зарядну станцію було назавжди вимкнено, вона більше не використовується, але її все ще можна побачити"
-                    },
-                    "0": {
-                        "then": "Ця зарядна станція працює"
-                    }
-                },
-                "question": "Чи використовується ця точка зарядки?"
-            },
-            "Type": {
-                "mappings": {
-                    "0": {
-                        "then": "Тут можна заряджати <b>велосипеди</b>"
-                    },
-                    "1": {
-                        "then": "<b>Автомобілі</b> можна зарядити тут"
-                    },
-                    "4": {
-                        "then": "<b>Автобуси</b> можна зарядити тут"
-                    },
-                    "2": {
-                        "then": "<b>Самокати</b> можна зарядити тут"
-                    },
-                    "3": {
-                        "then": "Тут можна заряджати <b>вантажні транспортні засоби</b> (наприклад, вантажівки)"
-                    }
-                },
-                "question": "Які транспортні засоби можуть тут заряджатися?"
-            },
-            "capacity": {
-                "question": "Скільки транспортних засобів можна зарядити тут одночасно?",
-                "render": "Тут можуть заряджатися одночасно {capacity} транспортних засобів"
-            },
-            "fee": {
-                "mappings": {
-                    "0": {
-                        "then": "Безкоштовне використання (без автентифікації)"
-                    },
-                    "4": {
-                        "then": "Платне користування"
-                    },
-                    "1": {
-                        "then": "Безкоштовне використання, але потрібно пройти аутентифікацію"
-                    },
-                    "3": {
-                        "then": "Платне користування, але безкоштовне для клієнтів готелю/пабу/лікарні/..., який експлуатує зарядну станцію"
-                    }
-                },
-                "question": "Чи потрібно платити за користування цією зарядною станцією?"
             }
         },
         "title": {
-            "render": "Зарядна станція",
             "mappings": {
-                "1": {
-                    "then": "Зарядна станція для автомобілів"
-                },
                 "0": {
                     "then": "Зарядна станція для електровелосипедів"
+                },
+                "1": {
+                    "then": "Зарядна станція для автомобілів"
                 }
-            }
-        },
-        "name": "Зарядні станції"
+            },
+            "render": "Зарядна станція"
+        }
     },
     "climbing": {
         "tagRenderings": {
@@ -1035,6 +1047,12 @@
             }
         }
     },
+    "dog_toilet": {
+        "name": "Собачі туалети"
+    },
+    "dogpark": {
+        "name": "собачі парки"
+    },
     "drinking_water": {
         "deletion": {
             "nonDeleteMappings": {
@@ -1964,6 +1982,18 @@
             }
         }
     },
+    "scouting_group": {
+        "name": "Скаутські групи",
+        "tagRenderings": {
+            "association": {
+                "question": "До якої скаутської асоціації належить {name}?",
+                "render": "Скаутська асоціація цієї групи: {brand}"
+            },
+            "name": {
+                "render": "Назва цієї групи: {name}"
+            }
+        }
+    },
     "shelter": {
         "tagRenderings": {
             "shelter-type": {
@@ -2048,6 +2078,23 @@
                     "question": "Що це за магазин?"
                 }
             },
+            "repairs_bikes": {
+                "mappings": {
+                    "0": {
+                        "then": "У цій майстерні ремонтують велосипеди"
+                    },
+                    "1": {
+                        "then": "Ця майстерня не ремонтує велосипеди"
+                    },
+                    "2": {
+                        "then": "Ця майстерня ремонтує тільки куплені тут велосипеди"
+                    },
+                    "3": {
+                        "then": "Ця майстерня ремонтує велосипеди лише певної марки"
+                    }
+                },
+                "question": "Чи ремонтує ця майстерня велосипеди?"
+            },
             "second_hand": {
                 "question": "Чи продає цей магазин вживані речі?"
             },
@@ -2065,23 +2112,6 @@
             "shops-name": {
                 "question": "Як називається цей магазин?",
                 "render": "Цей магазин називається <i>{name}</i>"
-            },
-            "repairs_bikes": {
-                "mappings": {
-                    "2": {
-                        "then": "Ця майстерня ремонтує тільки куплені тут велосипеди"
-                    },
-                    "3": {
-                        "then": "Ця майстерня ремонтує велосипеди лише певної марки"
-                    },
-                    "1": {
-                        "then": "Ця майстерня не ремонтує велосипеди"
-                    },
-                    "0": {
-                        "then": "У цій майстерні ремонтують велосипеди"
-                    }
-                },
-                "question": "Чи ремонтує ця майстерня велосипеди?"
             }
         }
     },
@@ -2144,6 +2174,32 @@
             "sport_pitch-phone": {
                 "question": "Який номер телефону оператора?"
             },
+            "sport_pitch-sport": {
+                "mappings": {
+                    "0": {
+                        "then": "Тут грають у баскетбол"
+                    },
+                    "1": {
+                        "then": "Тут грають у футбол"
+                    },
+                    "2": {
+                        "then": "Це стіл для пінг-понгу"
+                    },
+                    "3": {
+                        "then": "Тут грають у теніс"
+                    },
+                    "4": {
+                        "then": "Тут грають у корфбол"
+                    },
+                    "6": {
+                        "then": "Це скейт-парк"
+                    },
+                    "7": {
+                        "then": "Це манеж для верхової їзди"
+                    }
+                },
+                "question": "Яким видом спорту тут можна займатися?"
+            },
             "sport_pitch-surface": {
                 "mappings": {
                     "0": {
@@ -2168,34 +2224,8 @@
                         "then": "Поверхня цієї доріжки - тартан, синтетична, злегка пружиниста, пориста поверхня"
                     }
                 },
-                "render": "Поверхня - <b>{surface}</b>",
-                "question": "Яка поверхня цього спортивного майданчика?"
-            },
-            "sport_pitch-sport": {
-                "question": "Яким видом спорту тут можна займатися?",
-                "mappings": {
-                    "0": {
-                        "then": "Тут грають у баскетбол"
-                    },
-                    "2": {
-                        "then": "Це стіл для пінг-понгу"
-                    },
-                    "3": {
-                        "then": "Тут грають у теніс"
-                    },
-                    "4": {
-                        "then": "Тут грають у корфбол"
-                    },
-                    "7": {
-                        "then": "Це манеж для верхової їзди"
-                    },
-                    "6": {
-                        "then": "Це скейт-парк"
-                    },
-                    "1": {
-                        "then": "Тут грають у футбол"
-                    }
-                }
+                "question": "Яка поверхня цього спортивного майданчика?",
+                "render": "Поверхня - <b>{surface}</b>"
             }
         }
     },
@@ -2477,8 +2507,6 @@
                 "question": "Чи потрібно показувати масштабну лінійку на карті?"
             },
             "show_crosshair": {
-                "question": "Чи потрібно показувати перехрестя по центру екрана?",
-                "questionHint": "Це може допомогти точно позиціонувати новий елемент",
                 "mappings": {
                     "0": {
                         "then": "Показувати перехрестя в центрі мапи при збільшенні масштабу вище 17 рівня"
@@ -2489,7 +2517,9 @@
                     "3": {
                         "then": "Завжди показуйте перехрестя в центрі мапи"
                     }
-                }
+                },
+                "question": "Чи потрібно показувати перехрестя по центру екрана?",
+                "questionHint": "Це може допомогти точно позиціонувати новий елемент"
             },
             "show_debug": {
                 "mappings": {
@@ -2741,6 +2771,9 @@
             "render": "Торговий автомат"
         }
     },
+    "veterinary": {
+        "name": "ветеринарія"
+    },
     "waste_basket": {
         "filter": {
             "1": {
@@ -2808,38 +2841,5 @@
         "title": {
             "render": "Утилізація відходів"
         }
-    },
-    "dog_toilet": {
-        "name": "Собачі туалети"
-    },
-    "scouting_group": {
-        "tagRenderings": {
-            "name": {
-                "render": "Назва цієї групи: {name}"
-            },
-            "association": {
-                "question": "До якої скаутської асоціації належить {name}?",
-                "render": "Скаутська асоціація цієї групи: {brand}"
-            }
-        },
-        "name": "Скаутські групи"
-    },
-    "veterinary": {
-        "name": "ветеринарія"
-    },
-    "dogpark": {
-        "name": "собачі парки"
-    },
-    "animal_shelter": {
-        "tagRenderings": {
-            "2": {
-                "question": "Як називається цей притулок для тварин?",
-                "render": "Цей притулок для тварин називається <b>{name}</b>"
-            }
-        },
-        "name": "Притулки для тварин"
-    },
-    "charge_point": {
-        "name": "Пункти зарядки"
     }
-}
+}
\ No newline at end of file
diff --git a/langs/themes/nl.json b/langs/themes/nl.json
index c6e52ac67..aafd86378 100644
--- a/langs/themes/nl.json
+++ b/langs/themes/nl.json
@@ -653,8 +653,37 @@
                     "building type": {
                         "question": "Wat voor soort gebouw is dit?"
                     },
+                    "grb-fixme": {
+                        "mappings": {
+                            "0": {
+                                "then": "Geen fixme"
+                            }
+                        },
+                        "question": "Wat zegt de fixme?",
+                        "render": "De fixme is <b>{fixme}</b>"
+                    },
+                    "grb-housenumber": {
+                        "mappings": {
+                            "0": {
+                                "then": "Geen huisnummer"
+                            }
+                        },
+                        "question": "Wat is het huisnummer?",
+                        "render": "Het huisnummer is <b>{addr:housenumber}</b>"
+                    },
+                    "grb-min-level": {
+                        "question": "Hoeveel verdiepingen ontbreken?",
+                        "render": "Dit gebouw begint maar op de {building:min_level} verdieping"
+                    },
                     "grb-reference": {
                         "render": "Werd geïmporteerd vanuit GRB, het referentienummer is {source:geometry:ref}"
+                    },
+                    "grb-street": {
+                        "question": "Wat is de straat?",
+                        "render": "De straat is <b>{addr:street}</b>"
+                    },
+                    "grb-unit": {
+                        "render": "De wooneenheid-aanduiding is <b>{addr:unit}</b> "
                     }
                 }
             },
@@ -671,8 +700,35 @@
                         }
                     }
                 }
+            },
+            "5": {
+                "override": {
+                    "tagRenderings+": {
+                        "0": {
+                            "mappings": {
+                                "0": {
+                                    "then": "Geen omliggend OSM-gebouw gevonden"
+                                }
+                            }
+                        },
+                        "3": {
+                            "mappings": {
+                                "0": {
+                                    "then": "Geen omliggend OSM-gebouw gevonden. Een omliggend gebouw is nodig om dit punt als adres punt toe te voegen. <div class=subtle>Importeer eerst de gebouwen. Vernieuw dan de pagina om losse adressen toe te voegen</div>"
+                                }
+                            },
+                            "render": {
+                                "special": {
+                                    "text": "Voeg dit adres als een nieuw adrespunt toe"
+                                }
+                            }
+                        }
+                    }
+                }
             }
-        }
+        },
+        "shortDescription": "Grb import helper tool",
+        "title": "GRB import helper"
     },
     "guideposts": {
         "description": "Wegwijzers (ook wel handwijzer genoemd) zijn vaak te vinden langs officiële wandel-, fiets-, ski- of paardrijroutes om de richtingen naar verschillende bestemmingen aan te geven. Vaak zijn ze vernoemd naar een regio of plaats en geven ze de hoogte aan.\n\nDe positie van een wegwijzer kan door een wandelaar/fietser/renner/skiër worden gebruikt als bevestiging van de huidige positie, vooral als ze een gedrukte kaart zonder GPS-ontvanger gebruiken. ",
@@ -1096,6 +1152,11 @@
         },
         "title": "Dierenartsen, hondenloopzones en andere huisdiervriendelijke plaatsen"
     },
+    "play_forests": {
+        "description": "Een speelbos is een zone in een bos die vrij toegankelijk is voor spelende kinderen. Deze wordt  in bossen van het Agentschap Natuur en bos altijd aangeduid met het overeenkomstige bord.",
+        "shortDescription": "Deze kaart toont speelbossen",
+        "title": "Speelbossen"
+    },
     "playgrounds": {
         "description": "Op deze kaart vind je speeltuinen en kan je zelf meer informatie en foto's toevoegen",
         "shortDescription": "Een kaart met speeltuinen",
@@ -1169,6 +1230,47 @@
         "description": "Alles om te skiën",
         "title": "Skipistes en kabelbanen"
     },
+    "speelplekken": {
+        "description": "<h3>Welkom bij de Groendoener!</h3>De Zuidrand dat is spelen, ravotten, chillen, wandelen,… in het groen. Meer dan <b>200 grote en kleine speelplekken</b> liggen er in parken, in bossen en op pleintjes te wachten om ontdekt te worden. De verschillende speelplekken werden getest én goedgekeurd door kinder- en jongerenreporters uit de Zuidrand. Met leuke challenges dagen de reporters jou uit om ook op ontdekking te gaan. Klik op een speelplek op de kaart, bekijk het filmpje en ga op verkenning!<br/><br/>Het project groendoener kadert binnen het strategisch project <a href='https://www.provincieantwerpen.be/aanbod/dlm/samenwerkingsverbanden/zuidrand/projecten/strategisch-project-beleefbare-open-ruimte.html' target='_blank'>Beleefbare Open Ruimte in de Antwerpse Zuidrand</a> en is een samenwerking tussen het departement Leefmilieu van provincie Antwerpen, Sportpret vzw, een OpenStreetMap-België Consultent en Createlli vzw. Het project kwam tot stand met steun van Departement Omgeving van de Vlaamse Overheid.<br/><img class='w-full md:w-1/2' src='./assets/themes/speelplekken/provincie_antwerpen.jpg'/><img class='w-full md:w-1/2' src='./assets/themes/speelplekken/Departement_Omgeving_Vlaanderen.png'/>",
+        "layers": {
+            "6": {
+                "name": "Wandelroutes van provincie Antwerpen",
+                "tagRenderings": {
+                    "walk-description": {
+                        "render": "<h3>Korte beschrijving:</h3>{description}"
+                    },
+                    "walk-length": {
+                        "render": "Deze wandeling is <b>{_length:km}km</b> lang"
+                    },
+                    "walk-operator": {
+                        "question": "Wie beheert deze wandeling en plaatst dus de signalisatiebordjes?"
+                    },
+                    "walk-operator-email": {
+                        "question": "Naar wie kan men emailen bij problemen rond signalisatie?",
+                        "render": "Bij problemen met signalisatie kan men emailen naar <a href='mailto:{operator:email}'>{operator:email}</a>"
+                    },
+                    "walk-type": {
+                        "mappings": {
+                            "0": {
+                                "then": "Dit is een internationale wandelroute"
+                            },
+                            "1": {
+                                "then": "Dit is een nationale wandelroute"
+                            },
+                            "2": {
+                                "then": "Dit is een regionale wandelroute"
+                            },
+                            "3": {
+                                "then": "Dit is een lokale wandelroute"
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        "shortDescription": "Speelplekken in de Antwerpse Zuidrand",
+        "title": "Welkom bij de groendoener!"
+    },
     "sport_pitches": {
         "description": "Een sportveld is een ingerichte plaats met infrastructuur om een sport te beoefenen",
         "shortDescription": "Deze kaart toont sportvelden",
@@ -1289,6 +1391,10 @@
         },
         "title": "Straatverlichting"
     },
+    "street_lighting_assen": {
+        "description": "Op deze kaart vind je alles over straatlantaarns + een dataset van Assen",
+        "title": "Straatverlichting - Assen"
+    },
     "surveillance": {
         "description": "Op deze open kaart kan je bewakingscamera's vinden.",
         "shortDescription": "Bewakingscameras en dergelijke",
@@ -1402,9 +1508,13 @@
         "description": "Kaart met afvalbakken en recyclingfaciliteiten.",
         "title": "Afval"
     },
+    "waste_assen": {
+        "description": "Kaart met afvalbakken en recyclingfaciliteiten + een dataset voor Assen.",
+        "title": "Afval - Assen"
+    },
     "waste_basket": {
         "description": "Op deze kaart vind je afvalbakken bij jou in de buurt. Als er een afvalbak ontbreekt op deze kaart, kun je deze zelf toevoegen",
         "shortDescription": "Een kaart met vuilnisbakken",
         "title": "Vuilnisbakken"
     }
-}
+}
\ No newline at end of file
diff --git a/langs/themes/uk.json b/langs/themes/uk.json
index 267c42034..ae3329d7c 100644
--- a/langs/themes/uk.json
+++ b/langs/themes/uk.json
@@ -509,7 +509,6 @@
     },
     "pets": {
         "description": "На цій мапі ви знайдете різні цікаві місця для ваших домашніх улюбленців: ветеринари, парки для собак, зоомагазини, ресторани, дружні до собак, …",
-        "title": "Ветеринари, собачі парки та інші зручності для домашніх тварин",
         "layers": {
             "4": {
                 "override": {
@@ -526,7 +525,8 @@
                     "name=": "Кошики для сміття з дозаторами для пакетів для екскрементів"
                 }
             }
-        }
+        },
+        "title": "Ветеринари, собачі парки та інші зручності для домашніх тварин"
     },
     "playgrounds": {
         "description": "На цій мапі ви знайдете дитячі майданчики та зможете додати додаткову інформацію",
@@ -541,6 +541,10 @@
         "description": "На цій мапі показуються пішохідні переходи позначені кольорами веселки, вони також можуть бути легко додані",
         "title": "Веселкові пішохідні переходи"
     },
+    "scouting": {
+        "description": "Скаутський загін - це громадський молодіжний рух, що робить акцент на активному відпочинку на природі. Заходи варіюються від таборування, піших прогулянок, водних видів спорту, рюкзаків, дослідження природи, ...",
+        "title": "Скаутські групи"
+    },
     "shops": {
         "description": "На цій мапі можна позначити основну інформацію про магазини, додати години роботи та номери телефонів",
         "shortDescription": "Мапа з можливістю редагування з основною інформацією про магазин",
@@ -572,8 +576,8 @@
         "title": "Громадські туалети"
     },
     "transit": {
-        "title": "Автобусні маршрути",
-        "description": "Сплануйте свою подорож за допомогою системи громадського транспорту."
+        "description": "Сплануйте свою подорож за допомогою системи громадського транспорту.",
+        "title": "Автобусні маршрути"
     },
     "trees": {
         "shortDescription": "Додайте на мапу всі дерева",
@@ -591,9 +595,5 @@
         "description": "На цій мапі ви знайдете найближчі до вас контейнери для сміття. Якщо на мапі відсутній кошик для сміття, ви можете додати його самостійно.",
         "shortDescription": "Мапа з урнами для сміття",
         "title": "Урни для сміття"
-    },
-    "scouting": {
-        "description": "Скаутський загін - це громадський молодіжний рух, що робить акцент на активному відпочинку на природі. Заходи варіюються від таборування, піших прогулянок, водних видів спорту, рюкзаків, дослідження природи, ...",
-        "title": "Скаутські групи"
     }
-}
+}
\ No newline at end of file
diff --git a/langs/uk.json b/langs/uk.json
index 0ef17aa55..31f60b1a0 100644
--- a/langs/uk.json
+++ b/langs/uk.json
@@ -717,6 +717,9 @@
         "save": "Зберегти відгук",
         "saved": "Відгук збережено. Дякуємо, що поділилися!"
     },
+    "split": {
+        "inviteToSplit": "Розділіть цю дорогу на менші сегменти. Це дозволяє надати різним частинам дороги різні властивості."
+    },
     "translations": {
         "activateButton": "Допоможіть перекласти MapComplete"
     },
@@ -736,8 +739,5 @@
             "description": "посилання на веб-сайт",
             "spamSite": "{host} вважається неякісним веб-сайтом. Використання цього веб-сайту заборонено."
         }
-    },
-    "split": {
-        "inviteToSplit": "Розділіть цю дорогу на менші сегменти. Це дозволяє надати різним частинам дороги різні властивості."
     }
-}
+}
\ No newline at end of file

From b5b79601f9cdd6e3eb89e92b645bd79b702f6750 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 22:03:14 +0100
Subject: [PATCH 39/49] Debug: add localstorage debug overview

---
 assets/layers/usersettings/usersettings.json  | 28 +++++++++++++++++++
 .../SettingsVisualisations.ts                 | 20 +++++++++++--
 2 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json
index f05d8aff6..382092523 100644
--- a/assets/layers/usersettings/usersettings.json
+++ b/assets/layers/usersettings/usersettings.json
@@ -1520,6 +1520,34 @@
       ],
       "condition": "mapcomplete-show_debug=yes",
       "render": "{all_tags()}"
+    },
+    {
+      "id": "debug_storage_accordeon",
+      "render": {
+        "special": {
+          "type": "group",
+          "header": "debug_storage_accordeon_title",
+          "labels": "debug_storage"
+        }
+      }
+    },
+    {
+      "id": "debug_storage_accordeon_title",
+      "labels": [
+        "hidden"
+      ],
+      "render": {
+        "en": "Debug information about local storage"
+      }
+    },
+    {
+      "id": "debug_storage",
+      "labels": [
+        "hidden"
+      ],
+      "render": {
+        "*": "{storage_all_tags()}"
+      }
     }
   ],
   "allowMove": false
diff --git a/src/UI/SpecialVisualisations/SettingsVisualisations.ts b/src/UI/SpecialVisualisations/SettingsVisualisations.ts
index b47aac4f7..ee1655b48 100644
--- a/src/UI/SpecialVisualisations/SettingsVisualisations.ts
+++ b/src/UI/SpecialVisualisations/SettingsVisualisations.ts
@@ -7,7 +7,7 @@ import LoginButton from "../Base/LoginButton.svelte"
 import ThemeViewState from "../../Models/ThemeViewState"
 import OrientationDebugPanel from "../Debug/OrientationDebugPanel.svelte"
 import AllTagsPanel from "../Popup/AllTagsPanel.svelte"
-import { UIEventSource } from "../../Logic/UIEventSource"
+import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
 import ClearCaches from "../Popup/ClearCaches.svelte"
 import Locale from "../i18n/Locale"
 import LanguageUtils from "../../Utils/LanguageUtils"
@@ -75,6 +75,22 @@ export class SettingsVisualisations {
                     })
                 }
             },
+            {
+                funcName: "storage_all_tags",
+                group: "settings",
+                docs: "Shows the current state of storage",
+                args: [],
+                constr(
+                    state: SpecialVisualizationState
+                ): SvelteUIElement {
+                    const data = {}
+                    for (const key in localStorage) {
+                        data[key] = localStorage[key]
+                    }
+                    const tags = new ImmutableStore(data)
+                    return new SvelteUIElement(AllTagsPanel, { state, tags })
+                }
+            },
             {
                 funcName: "clear_caches",
                 docs: "A button which clears the locally downloaded data and the service worker. Login status etc will be kept",
@@ -89,7 +105,7 @@ export class SettingsVisualisations {
                 constr(
                     _: SpecialVisualizationState,
                     __: UIEventSource<Record<string, string>>,
-                    argument: string[],
+                    argument: string[]
                 ): SvelteUIElement {
                     return new SvelteUIElement(ClearCaches, {
                         msg: argument[0] ?? "Clear local caches"

From 1307360c0bacd8d094c7a02a38f46a308c2721cc Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 22:15:54 +0100
Subject: [PATCH 40/49] Docs: improve docs about filters

---
 .../ThemeConfig/Json/LayerConfigJson.ts       | 25 ++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/src/Models/ThemeConfig/Json/LayerConfigJson.ts b/src/Models/ThemeConfig/Json/LayerConfigJson.ts
index f6ca6d014..a826d463a 100644
--- a/src/Models/ThemeConfig/Json/LayerConfigJson.ts
+++ b/src/Models/ThemeConfig/Json/LayerConfigJson.ts
@@ -436,13 +436,26 @@ export interface LayerConfigJson {
     )[]
 
     /**
-     * All the extra questions for filtering.
-     * If a string is given, mapComplete will search in
-     * 1. The tagrenderings for a match on ID and use the mappings as options
-     * 2. search 'filters.json' for the appropriate filter or
-     * 3. will try to parse it as `layername.filterid` and us that one.
+     * Filters are a way to temporarily hide the data from the map (but the data is still loaded from overpass/OSM/the specified source).
+     * This is used to e.g. show "shops open now", "toilets with wheelchair access", "free toilets", ...
      *
-     * Note: adding "#filter":"no-auto" will disable the filters added by tagRenderings
+     * Filters can be added in various ways:
+     *
+     * - You can specify one explicitly here
+     * - You can specify the id (as a string) of a tagRendering. The tagrendering will then automatically be converted to a filter
+     *      If the ID is not found locally, it will be searched in `filters.json`.
+     *      If a dot is present, the ID will be interpreted as "<layername>.<filterId>" instead
+     * - A tagRendering might specify `filter: true`. This will add the tagRendering to the filter list automatically
+     *      This might introduce filters from _imported_ tagRenderings
+     *      Note: adding "#filter":"no-auto" in the layer object will disable this
+     *
+     * A special case is setting `filter: {sameAs: "layerId"}`.
+     * This is only done with twin layers, where one layer is mostly visible (e.g. all shops offering some kind of bicycle service)
+     * and another layer (e.g. "all shops") shows up at high zoom levels (typically 17 or 18). This way, people can mark an already existing shop
+     * as bicycle shop and don't create a duplicate entry.
+     * Of course, if one applies a filter (e.g. "open now") the user will expect the "all shops" layer to also only show shops open now.
+     * As is often the case, the secondary layer will be hidden from the filter view, so they won't be able to enable those filters.
+     * In this case, we let the secondary layer follow the first layer with a `sameAs`.
      *
      * group: filters
      */

From 34998b10827ee79950bea6dcac1ed2d611cbb11f Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 22:19:08 +0100
Subject: [PATCH 41/49] UX: actually hide debug accordeons if debugging is of

---
 assets/layers/usersettings/usersettings.json | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json
index 382092523..315515163 100644
--- a/assets/layers/usersettings/usersettings.json
+++ b/assets/layers/usersettings/usersettings.json
@@ -1469,6 +1469,7 @@
     },
     {
       "id": "gps_accordeon",
+      "condition": "mapcomplete-show_debug=yes",
       "render": {
         "special": {
           "type": "group",
@@ -1496,6 +1497,7 @@
     },
     {
       "id": "debug_accordeon",
+      "condition": "mapcomplete-show_debug=yes",
       "render": {
         "special": {
           "type": "group",
@@ -1509,6 +1511,7 @@
       "labels": [
         "hidden"
       ],
+      "condition": "mapcomplete-show_debug=yes",
       "render": {
         "en": "Debug information"
       }
@@ -1523,6 +1526,7 @@
     },
     {
       "id": "debug_storage_accordeon",
+      "condition": "mapcomplete-show_debug=yes",
       "render": {
         "special": {
           "type": "group",

From 963eabae23f2ef1b878dbafa9acabfa3663f5500 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Mon, 3 Feb 2025 23:57:15 +0100
Subject: [PATCH 42/49] Chore: remove obsolete code

---
 scripts/generateImageAnalysis.ts | 55 +-------------------------------
 1 file changed, 1 insertion(+), 54 deletions(-)

diff --git a/scripts/generateImageAnalysis.ts b/scripts/generateImageAnalysis.ts
index 884124313..9b9afe811 100644
--- a/scripts/generateImageAnalysis.ts
+++ b/scripts/generateImageAnalysis.ts
@@ -4,7 +4,7 @@ import { RegexTag } from "../src/Logic/Tags/RegexTag"
 import { ImmutableStore } from "../src/Logic/UIEventSource"
 import { BBox } from "../src/Logic/BBox"
 import * as fs from "fs"
-import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
+import { writeFileSync } from "fs"
 import { Feature } from "geojson"
 import ScriptUtils from "./ScriptUtils"
 import { Imgur } from "../src/Logic/ImageProviders/Imgur"
@@ -192,59 +192,6 @@ export default class GenerateImageAnalysis extends Script {
             }
         }
     }
-
-    async downloadViews(datapath: string): Promise<void> {
-        const { allImages, imageSource } = this.loadImageUrls(datapath)
-        console.log("Detected", allImages.size, "images")
-        const results: [string, number][] = []
-        const today = new Date().toISOString().substring(0, "YYYY-MM-DD".length)
-        const viewDir = datapath + "/views_" + today
-        if (!existsSync(viewDir)) {
-            mkdirSync(viewDir)
-        }
-        const targetpath = datapath + "/views.csv"
-
-        const total = allImages.size
-        let dloaded = 0
-        let skipped = 0
-        let err = 0
-        for (const image of Array.from(allImages)) {
-            const cachedView = viewDir + "/" + image.replace(/\//g, "_")
-            let attribution: LicenseInfo
-            if (existsSync(cachedView)) {
-                attribution = JSON.parse(readFileSync(cachedView, "utf8"))
-                skipped++
-            } else {
-                try {
-                    attribution = await Imgur.singleton.DownloadAttribution({ url: image })
-                    await ScriptUtils.sleep(500)
-                    writeFileSync(cachedView, JSON.stringify(attribution))
-                    dloaded++
-                } catch (e) {
-                    err++
-                    continue
-                }
-            }
-            results.push([image, attribution.views])
-            if (dloaded % 50 === 0) {
-                console.log({
-                    dloaded,
-                    skipped,
-                    total,
-                    err,
-                    progress: Math.round(dloaded + skipped + err),
-                })
-            }
-
-            if ((dloaded + skipped + err) % 100 === 0) {
-                console.log("Writing views to", targetpath)
-                fs.writeFileSync(targetpath, results.map((r) => r.join(",")).join("\n"))
-            }
-        }
-        console.log("Writing views to", targetpath)
-        fs.writeFileSync(targetpath, results.map((r) => r.join(",")).join("\n"))
-    }
-
     async downloadImage(url: string, imagePath: string): Promise<boolean> {
         const filenameLong = url.replace(/[\/:.\-%]/g, "_") + ".jpg"
         const targetPathLong = imagePath + "/" + filenameLong

From 52d4adee84b23464c29064616410230c48fbb5e3 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Tue, 4 Feb 2025 01:02:57 +0100
Subject: [PATCH 43/49] Fix: fix
 https://source.mapcomplete.org/MapComplete/MapComplete/issues/2307

---
 src/Logic/ImageProviders/AllImageProviders.ts |  3 ++
 src/UI/Image/DeletableImage.svelte            | 36 +++++++++++++------
 2 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/Logic/ImageProviders/AllImageProviders.ts b/src/Logic/ImageProviders/AllImageProviders.ts
index bfab05e34..457e37d5d 100644
--- a/src/Logic/ImageProviders/AllImageProviders.ts
+++ b/src/Logic/ImageProviders/AllImageProviders.ts
@@ -90,6 +90,9 @@ export default class AllImageProviders {
         const allPrefixes = Utils.Dedup(prefixes ?? [].concat(...sources.map(s => s.defaultKeyPrefixes)))
         for (const prefix of allPrefixes) {
             for (const k in tags) {
+                if (!tags[k]) {
+                    continue
+                }
                 if (k === prefix || k.startsWith(prefix + ":")) {
                     count++
                     continue
diff --git a/src/UI/Image/DeletableImage.svelte b/src/UI/Image/DeletableImage.svelte
index 3831621e0..1a3ce3299 100644
--- a/src/UI/Image/DeletableImage.svelte
+++ b/src/UI/Image/DeletableImage.svelte
@@ -18,6 +18,7 @@
   import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
   import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
   import { Tag } from "../../Logic/Tags/Tag"
+  import { MenuState } from "../../Models/MenuState"
 
   export let image: ProvidedImage
   export let state: SpecialVisualizationState
@@ -26,7 +27,7 @@
   onDestroy(
     showDeleteDialog.addCallbackAndRunD((shown) => {
       if (shown) {
-        state.previewedImage.set(undefined)
+        MenuState.previewedImage.set(undefined)
       }
     })
   )
@@ -53,18 +54,31 @@
       issue: reportReason.data,
       sequence_id: imageInfo.collection,
       reporter_comments: (reportFreeText.data ?? "") + "\n\n" + "Reported from " + url,
-      reporter_email,
+      reporter_email
     })
     reported.set(true)
   }
 
   async function unlink() {
-    await state?.changes?.applyAction(
-      new ChangeTagAction(tags.data.id, new Tag(image.key, ""), tags.data, {
-        changeType: "delete-image",
-        theme: state.theme.id,
-      })
-    )
+    console.log("Unlinking image", image.key, image.id)
+    if (image.id.length < 10) {
+      console.error("Suspicious value, not deleting ", image.id)
+      return
+    }
+    // The "key" is the provider key, but not necessarely the actual key that should be reset
+    // We iterate over all tags. *Every* tag for which the value contains the id will be deleted
+    const tgs = tags.data
+    for (const key in tgs) {
+      if (typeof tgs[key] !== "string" || tgs[key].indexOf(image.id) < 0) {
+        continue
+      }
+
+      await state?.changes?.applyAction(
+        new ChangeTagAction(tgs.id, new Tag(key, ""), tgs, {
+          changeType: "delete-image",
+          theme: state.theme.id
+        }))
+    }
   }
 
   const t = Translations.t.image.panoramax
@@ -161,7 +175,7 @@
 </div>
 
 <style>
-  :global(.carousel-max-height) {
-    max-height: var(--image-carousel-height);
-  }
+    :global(.carousel-max-height) {
+        max-height: var(--image-carousel-height);
+    }
 </style>

From 4e73d9b5f9221a2481980965d0fb4d97b03acfa3 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Tue, 4 Feb 2025 01:06:55 +0100
Subject: [PATCH 44/49] Fix: fix
 https://github.com/pietervdvn/MapComplete/issues/2331

---
 src/Logic/SimpleMetaTagger.ts | 134 ++++++++++++++++++++++++++--------
 1 file changed, 104 insertions(+), 30 deletions(-)

diff --git a/src/Logic/SimpleMetaTagger.ts b/src/Logic/SimpleMetaTagger.ts
index 3e0595b38..f47a9fb4a 100644
--- a/src/Logic/SimpleMetaTagger.ts
+++ b/src/Logic/SimpleMetaTagger.ts
@@ -5,7 +5,7 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 import { CountryCoder } from "latlon2country"
 import Constants from "../Models/Constants"
 import { TagUtils } from "./Tags/TagUtils"
-import { Feature, LineString } from "geojson"
+import { Feature, LineString, MultiPolygon, Polygon } from "geojson"
 import { OsmTags } from "../Models/OsmFeature"
 import { UIEventSource } from "./UIEventSource"
 import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
@@ -80,7 +80,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
         super({
             keys: ["_referencing_ways"],
             isLazy: true,
-            doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. ",
+            doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. "
         })
     }
 
@@ -116,7 +116,7 @@ class CountryTagger extends SimpleMetaTagger {
         super({
             keys: ["_country"],
             doc: "The country codes of the of the country/countries that the feature is located in (with latlon2country). Might contain _multiple_ countries, separated by a `;`",
-            includesDates: false,
+            includesDates: false
         })
     }
 
@@ -213,9 +213,9 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
                 "_last_edit:changeset",
                 "_last_edit:timestamp",
                 "_version_number",
-                "_backend",
+                "_backend"
             ],
-            doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass",
+            doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass"
         })
     }
 
@@ -244,6 +244,69 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
     }
 }
 
+class NormalizePanoramax extends SimpleMetaTagger {
+    constructor() {
+        super(
+            {
+                keys: ["panoramax"],
+                doc: "Converts a `panoramax=hash1;hash2;hash3;...` into `panoramax=hash1`,`panoramax:0=hash1`...",
+                isLazy: false,
+                cleanupRetagger: true
+            })
+    }
+
+    private addValue(comesFromKey: string, tags: Record<string, string>, hashesToAdd: string[], postfix?: string) {
+        let basekey = "panoramax"
+        if (postfix) {
+            basekey = "panoramax:" + postfix
+        }
+        let index = -1
+        for (let i = 0; i < hashesToAdd.length; i++) {
+            let k = basekey
+            do {
+                if (index >= 0) {
+                    k = `${basekey}:${index}`
+                }
+                index++
+            } while (k !== comesFromKey && tags[k])
+            tags[k] = hashesToAdd[i]
+        }
+    }
+
+    /**
+     * const tags = new UIEventSource({panoramax: "abc;def;ghi", "panoramax:2": "xyz;uvw", "panoramax:streetsign":"a;b;c"})
+     * const _ = undefined
+     * new NormalizePanoramax().applyMetaTagsOnFeature(_, _, tags, _)
+     * tags.data // => {"panoramax": "abc", "panoramax:0" : "def", "panoramax:1": "ghi", "panoramax:2":"xyz", "panoramax:3":"uvw", "panoramax:streetsign":"a", "panoramax:streetsign:0":"b","panoramax:streetsign:1": "c"}
+     */
+    applyMetaTagsOnFeature(feature: Feature, layer: LayerConfig, tags: UIEventSource<Record<string, string>>): boolean {
+        const tgs = tags.data
+        let somethingChanged = false
+        for (const key in tgs) {
+            if (!(key === "panoramax" || key.startsWith("panoramax:"))) {
+                continue
+            }
+            const v = tgs[key]
+            if (v.indexOf(";") < 0) {
+                continue
+            }
+            const parts = v.split(";")
+            if (key === "panoramax" || key.match("panoramax:[0-9]+")) {
+                this.addValue(key, tgs, parts)
+                somethingChanged = true
+            } else {
+
+                const postfix = key.match(/panoramax:([^:]+)(:[0-9]+)?/)?.[1]
+                if (postfix) {
+                    this.addValue(key, tgs, parts, postfix)
+                    somethingChanged = true
+                }
+            }
+        }
+        return somethingChanged
+    }
+}
+
 export default class SimpleMetaTaggers {
     /**
      * A simple metatagger which rewrites various metatags as needed
@@ -253,7 +316,7 @@ export default class SimpleMetaTaggers {
     public static geometryType = new InlineMetaTagger(
         {
             keys: ["_geometry:type"],
-            doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`",
+            doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`"
         },
         (feature) => {
             const changed = feature.properties["_geometry:type"] === feature.geometry.type
@@ -262,6 +325,7 @@ export default class SimpleMetaTaggers {
         }
     )
     public static referencingWays = new ReferencingWaysMetaTagger()
+    private static normalizePanoramax = new NormalizePanoramax()
     private static readonly cardinalDirections = {
         N: 0,
         NNE: 22.5,
@@ -278,12 +342,12 @@ export default class SimpleMetaTaggers {
         W: 270,
         WNW: 292.5,
         NW: 315,
-        NNW: 337.5,
+        NNW: 337.5
     }
     private static latlon = new InlineMetaTagger(
         {
             keys: ["_lat", "_lon"],
-            doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)",
+            doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)"
         },
         (feature) => {
             const centerPoint = GeoOperations.centerpoint(feature)
@@ -298,7 +362,7 @@ export default class SimpleMetaTaggers {
         {
             doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.",
             keys: ["_layer"],
-            includesDates: false,
+            includesDates: false
         },
         (feature, layer) => {
             if (feature.properties._layer === layer.id) {
@@ -314,11 +378,11 @@ export default class SimpleMetaTaggers {
                 "sidewalk:left",
                 "sidewalk:right",
                 "generic_key:left:property",
-                "generic_key:right:property",
+                "generic_key:right:property"
             ],
             doc: "Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined",
             includesDates: false,
-            cleanupRetagger: true,
+            cleanupRetagger: true
         },
         (feature, layer) => {
             if (!layer.lineRendering.some((lr) => lr.leftRightSensitive)) {
@@ -332,11 +396,15 @@ export default class SimpleMetaTaggers {
         {
             keys: ["_surface"],
             doc: "The surface area of the feature in square meters. Not set on points and ways",
-            isLazy: true,
+            isLazy: true
         },
         (feature) => {
+            if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
+                return
+            }
+            const f = <Feature<Polygon | MultiPolygon>>feature
             Utils.AddLazyProperty(feature.properties, "_surface", () => {
-                return "" + GeoOperations.surfaceAreaInSqMeters(feature)
+                return "" + GeoOperations.surfaceAreaInSqMeters(f)
             })
 
             return true
@@ -346,11 +414,15 @@ export default class SimpleMetaTaggers {
         {
             keys: ["_surface:ha"],
             doc: "The surface area of the feature in hectare. Not set on points and ways",
-            isLazy: true,
+            isLazy: true
         },
         (feature) => {
+            if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
+                return
+            }
+            const f = <Feature<Polygon | MultiPolygon>>feature
             Utils.AddLazyProperty(feature.properties, "_surface:ha", () => {
-                const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature)
+                const sqMeters = GeoOperations.surfaceAreaInSqMeters(f)
                 return "" + Math.floor(sqMeters / 1000) / 10
             })
 
@@ -360,7 +432,7 @@ export default class SimpleMetaTaggers {
     private static levels = new InlineMetaTagger(
         {
             doc: "Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.",
-            keys: ["_level"],
+            keys: ["_level"]
         },
         (feature) => {
             let somethingChanged = false
@@ -395,7 +467,7 @@ export default class SimpleMetaTaggers {
     private static canonicalize = new InlineMetaTagger(
         {
             doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`; `1` will be rewritten to `1m` as well)",
-            keys: ["Theme-defined keys"],
+            keys: ["Theme-defined keys"]
         },
         (feature, _, __, state) => {
             const units = Utils.NoNull(
@@ -452,7 +524,7 @@ export default class SimpleMetaTaggers {
     private static lngth = new InlineMetaTagger(
         {
             keys: ["_length", "_length:km"],
-            doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter",
+            doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter"
         },
         (feature) => {
             const l = GeoOperations.lengthInMeters(feature)
@@ -468,7 +540,7 @@ export default class SimpleMetaTaggers {
             keys: ["_isOpen"],
             doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
             includesDates: true,
-            isLazy: true,
+            isLazy: true
         },
         (feature) => {
             if (Utils.runningFromConsole) {
@@ -507,8 +579,8 @@ export default class SimpleMetaTaggers {
                                 lon: lon,
                                 address: {
                                     country_code: tags._country.toLowerCase(),
-                                    state: undefined,
-                                },
+                                    state: undefined
+                                }
                             },
                             <any>{ tag_key: "opening_hours" }
                         )
@@ -520,14 +592,14 @@ export default class SimpleMetaTaggers {
                         delete tags._isOpen
                         tags["_isOpen"] = "parse_error"
                     }
-                },
+                }
             })
         }
     )
     private static directionSimplified = new InlineMetaTagger(
         {
             keys: ["_direction:numerical", "_direction:leftright"],
-            doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map",
+            doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map"
         },
         (feature) => {
             const tags = feature.properties
@@ -552,7 +624,7 @@ export default class SimpleMetaTaggers {
         {
             keys: ["_direction:centerpoint"],
             isLazy: true,
-            doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint.",
+            doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint."
         },
         (feature: Feature) => {
             if (feature.geometry.type !== "LineString") {
@@ -575,7 +647,7 @@ export default class SimpleMetaTaggers {
                     delete feature.properties["_direction:centerpoint"]
                     feature.properties["_direction:centerpoint"] = bearing
                     return bearing
-                },
+                }
             })
 
             return true
@@ -585,7 +657,7 @@ export default class SimpleMetaTaggers {
         {
             keys: ["_now:date", "_now:datetime"],
             doc: "Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely",
-            includesDates: true,
+            includesDates: true
         },
         (feature) => {
             const now = new Date()
@@ -609,7 +681,7 @@ export default class SimpleMetaTaggers {
             keys: ["_last_edit:passed_time"],
             doc: "Gives the number of seconds since the last edit. Note that this will _not_ update, but rather be the number of seconds elapsed at the moment this tag is read first",
             isLazy: true,
-            includesDates: true,
+            includesDates: true
         },
         (feature) => {
             Utils.AddLazyProperty(feature.properties, "_last_edit:passed_time", () => {
@@ -628,7 +700,7 @@ export default class SimpleMetaTaggers {
         {
             keys: ["_currency"],
             doc: "Adds the currency valid for the object, based on country or explicit tagging. Can be a single currency or a semicolon-separated list of currencies. Empty if no currency is found.",
-            isLazy: true,
+            isLazy: true
         },
         (feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => {
             if (tagsStore === undefined) {
@@ -670,6 +742,7 @@ export default class SimpleMetaTaggers {
         }
     )
 
+
     public static metatags: SimpleMetaTagger[] = [
         SimpleMetaTaggers.latlon,
         SimpleMetaTaggers.layerInfo,
@@ -689,6 +762,7 @@ export default class SimpleMetaTaggers {
         SimpleMetaTaggers.referencingWays,
         SimpleMetaTaggers.timeSinceLastEdit,
         SimpleMetaTaggers.currency,
+        SimpleMetaTaggers.normalizePanoramax
     ]
 
     /**
@@ -770,8 +844,8 @@ export default class SimpleMetaTaggers {
             [
                 "Metatags are extra tags available, in order to display more data or to give better questions.",
                 "They are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.",
-                "**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object",
-            ].join("\n"),
+                "**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object"
+            ].join("\n")
         ]
 
         subElements.push("## Metatags calculated by MapComplete")

From a2e3eef2269f85fdaa49091de1398e84f1d8fcf6 Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Tue, 4 Feb 2025 15:55:37 +0100
Subject: [PATCH 45/49] Config: configure www. domain

---
 Docs/ServerConfig/hetzner/Caddyfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Docs/ServerConfig/hetzner/Caddyfile b/Docs/ServerConfig/hetzner/Caddyfile
index 33f97d039..f5a8f952c 100644
--- a/Docs/ServerConfig/hetzner/Caddyfile
+++ b/Docs/ServerConfig/hetzner/Caddyfile
@@ -6,7 +6,7 @@ builds.mapcomplete.org {
 	}
 }
 
-mapcomplete.org {
+mapcomplete.org, www.mapcomplete.org {
     root * public/master/
     	try_files {path}.html
     	file_server

From 7aa07724a3d69b36ba12836c3ab11c17b1ac13ec Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Tue, 4 Feb 2025 19:56:05 +0100
Subject: [PATCH 46/49] Themes(school): add school:orientation for special
 needs schools

---
 assets/layers/school/school.json | 178 ++++++++++++++++++++-----------
 1 file changed, 116 insertions(+), 62 deletions(-)

diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json
index b10738200..2e5df1f6b 100644
--- a/assets/layers/school/school.json
+++ b/assets/layers/school/school.json
@@ -306,6 +306,68 @@
       ],
       "multiAnswer": true
     },
+    {
+      "id": "is_special_needs",
+      "question": {
+        "en": "Does this school target students with a special need?",
+        "nl": "Richt deze school zich op leerlingen met een speciale zorgbehoefte?",
+        "de": "Richtet sich diese Schule an Schüler mit besonderem Förderbedarf??",
+        "fr": "Est-ce que cet établissement scolaire s'adresse aux étudiants ayant des besoins particuliers?",
+        "ca": "Aquesta escola es dirigeix a estudiants amb necessitats especials?",
+        "cs": "Zaměřuje se tato škola na studenty se speciálními potřebami?",
+        "es": "¿Está esta escuela dirigida a alumnos con necesidades especiales?"
+      },
+      "questionHint": {
+        "en": "A special needs school has expertise and supports students with a (severe) diagnosis. In many countries, a certificate is needed to enroll.",
+        "nl": "Een buitengewone school is een school waar leerlingen met een attest op speciale zorg terecht kunnen."
+      },
+      "mappings": [
+        {
+          "if": "school:special_needs=only",
+          "then": {
+            "en": "This school is only for special need students; a certificate is needed to enroll",
+            "nl": "Deze school is enkel voor buitengewone leerlingen. Je hebt een attest nodig om hier school te mogen lopen."
+          }
+        },
+        {
+          "if": "school:special_needs=separated",
+          "then": {
+            "en": "This school has a separate section for special need students.",
+            "nl": "Deze school heeft een apart deel voor buitengewone leerlingen. "
+          }
+        },
+        {
+          "if": "school:special_needs=mixed",
+          "then": {
+            "en": "Students with special needs and non-special need students have classes together.",
+            "nl": "Buitengewone (geattesteerde) leerlingen en leerlingen zonder extra zorgnood zitten samen in de klas."
+          }
+        },
+        {
+          "if": "school:special_needs=limited",
+          "alsoShowIf": "school:special_needs=",
+          "then": {
+            "en": "This school offers limited, ad hoc support but has no significant expertise and is not considered a special needs school.",
+            "nl": "Deze school biedt ad hoc, beperkte extra zorg aan maar telt niet als buitengwoon onderwij.s"
+          }
+        },
+        {
+          "if": "school:special_needs=no",
+          "then": {
+            "en": "This school has no support for special need students.",
+            "nl": "Deze school heeft geen ondersteuning voor buitengewone leerlingen."
+          }
+        },
+        {
+          "if": "school:special_needs=yes",
+          "hideInAnswer": true,
+          "then": {
+            "en": "This school is for special need students.",
+            "nl": "Deze school is voor buitengewone leerlingen."
+          }
+        }
+      ]
+    },
     {
       "id": "orientation_belgium",
       "question": {
@@ -355,6 +417,60 @@
         }
       ]
     },
+    {
+      "id": "orientation_belgium_special_needs",
+      "question": {
+        "en": "What does this school train pupils for?",
+        "nl": "Waar traint deze school de leerlingen voor?"
+      },
+      "condition": {
+        "and": [
+          {
+            "or": [
+              "school~i~(.+;)?upper_secondary(;.+)?",
+              "school~i~(.+;)?secondary(;.+)?"
+            ]
+          },
+          {
+            "or": [
+              "school:special_needs=yes",
+              "school:special_needs=only"
+            ]
+          }
+        ]
+      },
+      "multiAnswer": true,
+      "mappings": [
+        {
+          "if": "school:orientation=care_institution",
+          "then": {
+            "en": "<b>Training type 1</b>: trains elementary life skills to live in an institution. There is no intention to do a (paid) job after training",
+            "nl": "<b>Onderwijsvorm type 1</b>: leert elementaire vaardigheden om te functioneren in een zorginstelling"
+          }
+        },
+        {
+          "if": "school:orientation=sheltered_workshop",
+          "then": {
+            "en": "<b>Training type 2</b>: prepares to work in an environment with extra care and facilities such as a sheltered workshop",
+            "nl": "<b>Onderwijsvorm type 2</b>: leert vaardigheden om te werken in een een beschermde arbeidsomgeving zoals een maatwerkbedrijf"
+          }
+        },
+        {
+          "if": "school:orientation=vocational",
+          "then": {
+            "en": "<b>Training type 3</b>: prepares for a job and a (more-or-less) independent life in society",
+            "nl": "<b>Onderwijsvorm type 3</b>: leert vaardigheden voor een een job op de reguliere arbeidsmarkt en zelfstandig wonen"
+          }
+        },
+        {
+          "if": "school:orientation=professional",
+          "then": {
+            "en": "<b>Training type 4</b>: prepares for a job or continued education",
+            "nl": "<b>Onderwijsvorm type 4</b>: leert vaardigheden voor een een job op de reguliere arbeidsmarkt of voor voortgezette studies"
+          }
+        }
+      ]
+    },
     {
       "id": "gender",
       "question": {
@@ -518,68 +634,6 @@
         "key": "pedagogy"
       }
     },
-    {
-      "id": "is_special_needs",
-      "question": {
-        "en": "Does this school target students with a special need?",
-        "nl": "Richt deze school zich op leerlingen met een speciale zorgbehoefte?",
-        "de": "Richtet sich diese Schule an Schüler mit besonderem Förderbedarf??",
-        "fr": "Est-ce que cet établissement scolaire s'adresse aux étudiants ayant des besoins particuliers?",
-        "ca": "Aquesta escola es dirigeix a estudiants amb necessitats especials?",
-        "cs": "Zaměřuje se tato škola na studenty se speciálními potřebami?",
-        "es": "¿Está esta escuela dirigida a alumnos con necesidades especiales?"
-      },
-      "questionHint": {
-        "en": "A special needs school has expertise and supports students with a (severe) diagnosis. In many countries, a certificate is needed to enroll.",
-        "nl": "Een buitengewone school is een school waar leerlingen met een attest op speciale zorg terecht kunnen."
-      },
-      "mappings": [
-        {
-          "if": "school:special_needs=only",
-          "then": {
-            "en": "This school is only for special need students; a certificate is needed to enroll",
-            "nl": "Deze school is enkel voor buitengewone leerlingen. Je hebt een attest nodig om hier school te mogen lopen."
-          }
-        },
-        {
-          "if": "school:special_needs=separated",
-          "then": {
-            "en": "This school has a separate section for special need students.",
-            "nl": "Deze school heeft een apart deel voor buitengewone leerlingen. "
-          }
-        },
-        {
-          "if": "school:special_needs=mixed",
-          "then": {
-            "en": "Students with special needs and non-special need students have classes together.",
-            "nl": "Buitengewone (geattesteerde) leerlingen en leerlingen zonder extra zorgnood zitten samen in de klas."
-          }
-        },
-        {
-          "if": "school:special_needs=limited",
-          "alsoShowIf": "school:special_needs=",
-          "then": {
-            "en": "This school offers limited, ad hoc support but has no significant expertise and is not considered a special needs school.",
-            "nl": "Deze school biedt ad hoc, beperkte extra zorg aan maar telt niet als buitengwoon onderwij.s"
-          }
-        },
-        {
-          "if": "school:special_needs=no",
-          "then": {
-            "en": "This school has no support for special need students.",
-            "nl": "Deze school heeft geen ondersteuning voor buitengewone leerlingen."
-          }
-        },
-        {
-          "if": "school:special_needs=yes",
-          "hideInAnswer": true,
-          "then": {
-            "en": "This school is for special need students.",
-            "nl": "Deze school is voor buitengewone leerlingen."
-          }
-        }
-      ]
-    },
     {
       "id": "special_needs_categories_be",
       "condition": {

From d9c8785d9b4ac437aa4442cc259d752478772c5d Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Wed, 5 Feb 2025 01:15:00 +0100
Subject: [PATCH 47/49] Fix: revive missing 'login' button

---
 src/Logic/Osm/OsmConnection.ts      | 152 +++++++++++-----------------
 src/Logic/Osm/OsmPreferences.ts     |   8 +-
 src/Logic/State/UserRelatedState.ts |   2 +-
 3 files changed, 63 insertions(+), 99 deletions(-)

diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts
index c09fa5d1e..319994f51 100644
--- a/src/Logic/Osm/OsmConnection.ts
+++ b/src/Logic/Osm/OsmConnection.ts
@@ -17,10 +17,14 @@ interface OsmUserInfo {
     changesets: { count: number }
     traces: { count: number }
     blocks: { received: { count: number; active: number } }
+    img?: { href: string }
+    home: { lat: number, lon: number }
+    languages?: string[]
+    messages: { received: { count: number, unread: number }, sent: { count: number } }
 }
 
 export default class UserDetails {
-    public name = "Not logged in"
+    public name
     public uid: number
     public csCount = 0
     public img?: string
@@ -91,7 +95,6 @@ export class OsmConnection {
     public readonly _oauth_config: AuthConfig
     private readonly _dryRun: Store<boolean>
     private readonly fakeUser: boolean
-    private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []
     private readonly _iframeMode: boolean
     private readonly _singlePage: boolean
     private isChecking = false
@@ -128,7 +131,7 @@ export class OsmConnection {
         }
 
         this.userDetails = new UIEventSource<UserDetails>(
-            new UserDetails(this._oauth_config.url),
+            undefined,
             "userDetails"
         )
         if (options.fakeUser) {
@@ -197,15 +200,9 @@ export class OsmConnection {
         return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
     }
 
-    public OnLoggedIn(action: (userDetails: UserDetails) => void) {
-        this._onLoggedIn.push(action)
-    }
-
     public LogOut() {
         this.auth.logout()
-        this.userDetails.data.csCount = 0
-        this.userDetails.data.name = ""
-        this.userDetails.ping()
+        this.userDetails.setData(undefined)
         console.log("Logged out")
         this.loadingStatus.setData("not-attempted")
     }
@@ -219,7 +216,7 @@ export class OsmConnection {
         return this._oauth_config.url
     }
 
-    public AttemptLogin() {
+    public async AttemptLogin() {
         this.UpdateCapabilities()
         if (this.loadingStatus.data !== "logged-in") {
             // Stay 'logged-in' if we are already logged in; this simply means we are checking for messages
@@ -235,81 +232,46 @@ export class OsmConnection {
         LocalStorageSource.get("location_before_login").setData(
             Utils.runningFromConsole ? undefined : window.location.href
         )
-        this.auth.xhr(
-            {
-                method: "GET",
-                path: "/api/0.6/user/details",
-            },
-            (err, details: XMLDocument) => {
-                if (err != null) {
-                    console.log("Could not login due to:", err)
-                    this.loadingStatus.setData("error")
-                    if (err.status == 401) {
-                        console.log("Clearing tokens...")
-                        // Not authorized - our token probably got revoked
-                        this.auth.logout()
-                        this.LogOut()
-                    } else {
-                        console.log("Other error. Status:", err.status)
-                        this.apiIsOnline.setData("unreachable")
-                    }
-                    return
-                }
+        try {
 
-                if (details == null) {
-                    this.loadingStatus.setData("error")
-                    return
-                }
+            const u = <OsmUserInfo>JSON.parse(await this.interact("user/details.json", "GET", {
+                "accept-encoding": "application/json"
+            })).user
 
-                // details is an XML DOM of user details
-                const userInfo = details.getElementsByTagName("user")[0]
-
-                const data = this.userDetails.data
-                console.log("Login completed, userinfo is ", userInfo)
-                data.name = userInfo.getAttribute("display_name")
-                data.account_created = userInfo.getAttribute("account_created")
-                data.uid = Number(userInfo.getAttribute("id"))
-                data.languages = Array.from(
-                    userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang")
-                ).map((l) => l.textContent)
-                data.csCount = Number.parseInt(
-                    userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0"
-                )
-                data.tracesCount = Number.parseInt(
-                    userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0"
-                )
-
-                data.img = undefined
-                const imgEl = userInfo.getElementsByTagName("img")
-                if (imgEl !== undefined && imgEl[0] !== undefined) {
-                    data.img = imgEl[0].getAttribute("href")
-                }
-
-                const description = userInfo.getElementsByTagName("description")
-                if (description !== undefined && description[0] !== undefined) {
-                    data.description = description[0]?.innerHTML
-                }
-                const homeEl = userInfo.getElementsByTagName("home")
-                if (homeEl !== undefined && homeEl[0] !== undefined) {
-                    const lat = parseFloat(homeEl[0].getAttribute("lat"))
-                    const lon = parseFloat(homeEl[0].getAttribute("lon"))
-                    data.home = { lat: lat, lon: lon }
-                }
-
-                this.loadingStatus.setData("logged-in")
-                const messages = userInfo
-                    .getElementsByTagName("messages")[0]
-                    .getElementsByTagName("received")[0]
-                data.unreadMessages = parseInt(messages.getAttribute("unread"))
-                data.totalMessages = parseInt(messages.getAttribute("count"))
-
-                this.userDetails.ping()
-                for (const action of this._onLoggedIn) {
-                    action(this.userDetails.data)
-                }
-                this._onLoggedIn = []
+            if (!u) {
+                this.loadingStatus.setData("error")
+                return
             }
-        )
+
+            this.userDetails.set({
+                name: u.display_name,
+                img: u.img?.href,
+                unreadMessages: u.messages.received.unread,
+                tracesCount: u.traces.count,
+                uid: u.id,
+                account_created: u.account_created,
+                totalMessages: u.messages.received.count,
+                languages: u.languages,
+                home: u.home,
+                backend: this.Backend(),
+                description: u.description,
+                csCount: u.changesets.count
+            })
+            this.loadingStatus.setData("logged-in")
+        } catch (err) {
+            console.log("Could not login due to:", err)
+            this.loadingStatus.setData("error")
+            if (err.status == 401) {
+                console.log("Clearing tokens...")
+                // Not authorized - our token probably got revoked
+                this.auth.logout()
+                this.LogOut()
+            } else {
+                console.log("Other error. Status:", err.status)
+                this.apiIsOnline.setData("unreachable")
+            }
+        }
+
     }
 
     /**
@@ -349,9 +311,9 @@ export class OsmConnection {
                     method,
                     headers: header,
                     content,
-                    path: `/api/0.6/${path}`,
+                    path: `/api/0.6/${path}`
                 },
-                function (err, response) {
+                function(err, response) {
                     if (err !== null) {
                         error(err)
                     } else {
@@ -431,7 +393,7 @@ export class OsmConnection {
             "notes.json",
             content,
             {
-                "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
+                "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
             },
             true
         )
@@ -476,7 +438,7 @@ export class OsmConnection {
             file: gpx,
             description: options.description,
             tags: options.labels?.join(",") ?? "",
-            visibility: options.visibility,
+            visibility: options.visibility
         }
 
         if (!contents.description) {
@@ -484,9 +446,9 @@ export class OsmConnection {
         }
         const extras = {
             file:
-                '; filename="' +
+                "; filename=\"" +
                 (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
-                '"\r\nContent-Type: application/gpx+xml',
+                "\"\r\nContent-Type: application/gpx+xml"
         }
 
         const boundary = "987654"
@@ -494,7 +456,7 @@ export class OsmConnection {
         let body = ""
         for (const key in contents) {
             body += "--" + boundary + "\r\n"
-            body += 'Content-Disposition: form-data; name="' + key + '"'
+            body += "Content-Disposition: form-data; name=\"" + key + "\""
             if (extras[key] !== undefined) {
                 body += extras[key]
             }
@@ -505,7 +467,7 @@ export class OsmConnection {
 
         const response = await this.post("gpx/create", body, {
             "Content-Type": "multipart/form-data; boundary=" + boundary,
-            "Content-Length": "" + body.length,
+            "Content-Length": "" + body.length
         })
         const parsed = JSON.parse(response)
         console.log("Uploaded GPX track", parsed)
@@ -526,9 +488,9 @@ export class OsmConnection {
                 {
                     method: "POST",
 
-                    path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
+                    path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`
                 },
-                function (err) {
+                function(err) {
                     if (err !== null) {
                         error(err)
                     } else {
@@ -543,7 +505,7 @@ export class OsmConnection {
      * To be called by land.html
      */
     public finishLogin(callback: (previousURL: string) => void) {
-        this.auth.authenticate(function () {
+        this.auth.authenticate(function() {
             // Fully authed at this point
             console.log("Authentication successful!")
             const previousLocation = LocalStorageSource.get("location_before_login")
@@ -564,7 +526,7 @@ export class OsmConnection {
              */
             singlepage: !this._iframeMode,
             auto: autoLogin,
-            apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
+            apiUrl: this._oauth_config.api_url ?? this._oauth_config.url
         })
     }
 
diff --git a/src/Logic/Osm/OsmPreferences.ts b/src/Logic/Osm/OsmPreferences.ts
index e54ed0008..1d9c1ef56 100644
--- a/src/Logic/Osm/OsmPreferences.ts
+++ b/src/Logic/Osm/OsmPreferences.ts
@@ -32,9 +32,11 @@ export class OsmPreferences {
         this.auth = auth
         this._fakeUser = fakeUser
         this.osmConnection = osmConnection
-        osmConnection.OnLoggedIn(() => {
-            this.loadBulkPreferences()
-            return true
+        this.osmConnection.isLoggedIn.addCallbackAndRunD(loggedIn => {
+            if (loggedIn) {
+                this.loadBulkPreferences()
+                return true
+            }
         })
     }
 
diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts
index bd7eaa2e9..e496a4067 100644
--- a/src/Logic/State/UserRelatedState.ts
+++ b/src/Logic/State/UserRelatedState.ts
@@ -497,7 +497,7 @@ export default class UserRelatedState {
             amendedPrefs.ping()
         })
 
-        osmConnection.userDetails.addCallback((userDetails) => {
+        osmConnection.userDetails.addCallbackD((userDetails) => {
             for (const k in userDetails) {
                 amendedPrefs.data["_" + k] = "" + userDetails[k]
             }

From 3645559b19044a7eada82a55f8d2e6bf1e7a61da Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Wed, 5 Feb 2025 01:22:53 +0100
Subject: [PATCH 48/49] Experiment: globe mode

---
 package-lock.json             | 506 ++++++++++++----------------------
 package.json                  |   2 +-
 src/UI/Map/MapLibreAdaptor.ts |  11 +-
 src/UI/ThemeViewGUI.svelte    |   1 +
 4 files changed, 180 insertions(+), 340 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index ba54c917a..d602b6f4f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -56,7 +56,7 @@
         "libphonenumber-js": "^1.10.8",
         "mangrove-reviews-typescript": "^1.1.0",
         "maplibre": "^0.0.1-security",
-        "maplibre-gl": "^4.1.1",
+        "maplibre-gl": "^5.1.0",
         "marked": "^12.0.2",
         "monaco-editor": "^0.46.0",
         "mvt-to-geojson": "^0.0.5",
@@ -4853,6 +4853,8 @@
     },
     "node_modules/@mapbox/jsonlint-lines-primitives": {
       "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
+      "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
       "engines": {
         "node": ">= 0.6"
       }
@@ -4876,7 +4878,8 @@
     },
     "node_modules/@mapbox/unitbezier": {
       "version": "0.0.1",
-      "license": "BSD-2-Clause"
+      "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
+      "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="
     },
     "node_modules/@mapbox/vector-tile": {
       "version": "1.3.1",
@@ -4893,15 +4896,17 @@
       }
     },
     "node_modules/@maplibre/maplibre-gl-style-spec": {
-      "version": "20.1.1",
-      "license": "ISC",
+      "version": "23.1.0",
+      "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.1.0.tgz",
+      "integrity": "sha512-R6/ihEuC5KRexmKIYkWqUv84Gm+/QwsOUgHyt1yy2XqCdGdLvlBWVWIIeTZWN4NGdwmY6xDzdSGU2R9oBLNg2w==",
       "dependencies": {
         "@mapbox/jsonlint-lines-primitives": "~2.0.2",
         "@mapbox/unitbezier": "^0.0.1",
         "json-stringify-pretty-compact": "^4.0.0",
         "minimist": "^1.2.8",
+        "quickselect": "^3.0.0",
         "rw": "^1.3.3",
-        "sort-object": "^3.0.3"
+        "tinyqueue": "^3.0.0"
       },
       "bin": {
         "gl-style-format": "dist/gl-style-format.mjs",
@@ -4909,6 +4914,16 @@
         "gl-style-validate": "dist/gl-style-validate.mjs"
       }
     },
+    "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/quickselect": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
+      "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="
+    },
+    "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/tinyqueue": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
+      "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="
+    },
     "node_modules/@monaco-editor/loader": {
       "version": "1.4.0",
       "dev": true,
@@ -6680,8 +6695,9 @@
       }
     },
     "node_modules/@types/geojson": {
-      "version": "7946.0.14",
-      "license": "MIT"
+      "version": "7946.0.16",
+      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+      "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="
     },
     "node_modules/@types/geojson-vt": {
       "version": "3.2.5",
@@ -7533,13 +7549,6 @@
       "version": "2.0.1",
       "license": "Python-2.0"
     },
-    "node_modules/arr-union": {
-      "version": "3.1.0",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/array-equal": {
       "version": "1.0.0",
       "license": "MIT"
@@ -7613,13 +7622,6 @@
         "node": "*"
       }
     },
-    "node_modules/assign-symbols": {
-      "version": "1.0.0",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/async": {
       "version": "3.2.5",
       "license": "MIT"
@@ -7928,21 +7930,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/bytewise": {
-      "version": "1.1.0",
-      "license": "MIT",
-      "dependencies": {
-        "bytewise-core": "^1.2.2",
-        "typewise": "^1.0.3"
-      }
-    },
-    "node_modules/bytewise-core": {
-      "version": "1.2.3",
-      "license": "MIT",
-      "dependencies": {
-        "typewise-core": "^1.2"
-      }
-    },
     "node_modules/cac": {
       "version": "6.7.14",
       "license": "MIT",
@@ -10183,16 +10170,6 @@
       "version": "3.0.2",
       "license": "MIT"
     },
-    "node_modules/extend-shallow": {
-      "version": "2.0.1",
-      "license": "MIT",
-      "dependencies": {
-        "is-extendable": "^0.1.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/extsprintf": {
       "version": "1.3.0",
       "engines": [
@@ -10790,8 +10767,9 @@
       }
     },
     "node_modules/geojson-vt": {
-      "version": "3.2.1",
-      "license": "ISC"
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz",
+      "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A=="
     },
     "node_modules/geojson2svg": {
       "version": "1.3.3",
@@ -10916,13 +10894,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/get-value": {
-      "version": "2.0.6",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/getpass": {
       "version": "0.1.7",
       "license": "MIT",
@@ -11066,25 +11037,46 @@
       }
     },
     "node_modules/global-prefix": {
-      "version": "3.0.0",
-      "license": "MIT",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz",
+      "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==",
       "dependencies": {
-        "ini": "^1.3.5",
-        "kind-of": "^6.0.2",
-        "which": "^1.3.1"
+        "ini": "^4.1.3",
+        "kind-of": "^6.0.3",
+        "which": "^4.0.0"
       },
       "engines": {
-        "node": ">=6"
+        "node": ">=16"
+      }
+    },
+    "node_modules/global-prefix/node_modules/ini": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
+      "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==",
+      "engines": {
+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+      }
+    },
+    "node_modules/global-prefix/node_modules/isexe": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
+      "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
+      "engines": {
+        "node": ">=16"
       }
     },
     "node_modules/global-prefix/node_modules/which": {
-      "version": "1.3.1",
-      "license": "ISC",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
+      "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
       "dependencies": {
-        "isexe": "^2.0.0"
+        "isexe": "^3.1.1"
       },
       "bin": {
-        "which": "bin/which"
+        "node-which": "bin/which.js"
+      },
+      "engines": {
+        "node": "^16.13.0 || >=18.0.0"
       }
     },
     "node_modules/globals": {
@@ -11577,6 +11569,7 @@
     },
     "node_modules/ini": {
       "version": "1.3.8",
+      "dev": true,
       "license": "ISC"
     },
     "node_modules/interpret": {
@@ -11656,13 +11649,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/is-extendable": {
-      "version": "0.1.1",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/is-extglob": {
       "version": "2.1.1",
       "license": "MIT",
@@ -11776,16 +11762,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/is-plain-object": {
-      "version": "2.0.4",
-      "license": "MIT",
-      "dependencies": {
-        "isobject": "^3.0.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/is-potential-custom-element-name": {
       "version": "1.0.1",
       "license": "MIT",
@@ -11870,13 +11846,6 @@
       "version": "2.0.0",
       "license": "ISC"
     },
-    "node_modules/isobject": {
-      "version": "3.0.1",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/isstream": {
       "version": "0.1.2",
       "license": "MIT"
@@ -12089,7 +12058,8 @@
     },
     "node_modules/json-stringify-pretty-compact": {
       "version": "4.0.0",
-      "license": "MIT"
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
+      "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="
     },
     "node_modules/json-stringify-safe": {
       "version": "5.0.1",
@@ -12673,8 +12643,9 @@
       "integrity": "sha512-XawLsomeCq3O+x3IYTlU1QH52m9JvgEZvffgzWZ9P61HdSghJFzLUJjGXvtwV3hEuuZy9v9iSCG7W8pfr8p4Eg=="
     },
     "node_modules/maplibre-gl": {
-      "version": "4.1.2",
-      "license": "BSD-3-Clause",
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.1.0.tgz",
+      "integrity": "sha512-6lbf7qAnqAVm1T/vJBMmRtP+g8G/O/Z52IBtWX31SbFj7sEdlrk4YugxJen8IdV/pFjLFnDOw7HiHZl5nYdVjg==",
       "dependencies": {
         "@mapbox/geojson-rewind": "^0.5.2",
         "@mapbox/jsonlint-lines-primitives": "^2.0.2",
@@ -12683,24 +12654,24 @@
         "@mapbox/unitbezier": "^0.0.1",
         "@mapbox/vector-tile": "^1.3.1",
         "@mapbox/whoots-js": "^3.1.0",
-        "@maplibre/maplibre-gl-style-spec": "^20.1.1",
-        "@types/geojson": "^7946.0.14",
+        "@maplibre/maplibre-gl-style-spec": "^23.1.0",
+        "@types/geojson": "^7946.0.16",
         "@types/geojson-vt": "3.2.5",
         "@types/mapbox__point-geometry": "^0.1.4",
         "@types/mapbox__vector-tile": "^1.3.4",
         "@types/pbf": "^3.0.5",
         "@types/supercluster": "^7.1.3",
-        "earcut": "^2.2.4",
-        "geojson-vt": "^3.2.1",
+        "earcut": "^3.0.1",
+        "geojson-vt": "^4.0.2",
         "gl-matrix": "^3.4.3",
-        "global-prefix": "^3.0.0",
+        "global-prefix": "^4.0.0",
         "kdbush": "^4.0.2",
         "murmurhash-js": "^1.0.0",
-        "pbf": "^3.2.1",
+        "pbf": "^3.3.0",
         "potpack": "^2.0.0",
-        "quickselect": "^2.0.0",
+        "quickselect": "^3.0.0",
         "supercluster": "^8.0.1",
-        "tinyqueue": "^2.0.3",
+        "tinyqueue": "^3.0.0",
         "vt-pbf": "^3.1.3"
       },
       "engines": {
@@ -12711,13 +12682,24 @@
         "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
       }
     },
+    "node_modules/maplibre-gl/node_modules/earcut": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz",
+      "integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw=="
+    },
     "node_modules/maplibre-gl/node_modules/kdbush": {
       "version": "4.0.2",
       "license": "ISC"
     },
     "node_modules/maplibre-gl/node_modules/quickselect": {
-      "version": "2.0.0",
-      "license": "ISC"
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
+      "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="
+    },
+    "node_modules/maplibre-gl/node_modules/tinyqueue": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
+      "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="
     },
     "node_modules/marked": {
       "version": "12.0.2",
@@ -16239,8 +16221,9 @@
       }
     },
     "node_modules/pbf": {
-      "version": "3.2.1",
-      "license": "BSD-3-Clause",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz",
+      "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==",
       "dependencies": {
         "ieee754": "^1.1.12",
         "resolve-protobuf-schema": "^2.1.0"
@@ -18036,7 +18019,8 @@
     },
     "node_modules/rw": {
       "version": "1.3.3",
-      "license": "BSD-3-Clause"
+      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+      "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="
     },
     "node_modules/sade": {
       "version": "1.8.1",
@@ -18193,19 +18177,6 @@
         "randombytes": "^2.1.0"
       }
     },
-    "node_modules/set-value": {
-      "version": "2.0.1",
-      "license": "MIT",
-      "dependencies": {
-        "extend-shallow": "^2.0.1",
-        "is-extendable": "^0.1.1",
-        "is-plain-object": "^2.0.3",
-        "split-string": "^3.0.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/setimmediate": {
       "version": "1.0.5",
       "license": "MIT"
@@ -18503,35 +18474,6 @@
         "sorcery": "bin/sorcery"
       }
     },
-    "node_modules/sort-asc": {
-      "version": "0.2.0",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/sort-desc": {
-      "version": "0.2.0",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/sort-object": {
-      "version": "3.0.3",
-      "license": "MIT",
-      "dependencies": {
-        "bytewise": "^1.1.0",
-        "get-value": "^2.0.2",
-        "is-extendable": "^0.1.1",
-        "sort-asc": "^0.2.0",
-        "sort-desc": "^0.2.0",
-        "union-value": "^1.0.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/source-map": {
       "version": "0.6.1",
       "license": "BSD-3-Clause",
@@ -18702,37 +18644,6 @@
         "node": "*"
       }
     },
-    "node_modules/split-string": {
-      "version": "3.1.0",
-      "license": "MIT",
-      "dependencies": {
-        "extend-shallow": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/split-string/node_modules/extend-shallow": {
-      "version": "3.0.2",
-      "license": "MIT",
-      "dependencies": {
-        "assign-symbols": "^1.0.0",
-        "is-extendable": "^1.0.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/split-string/node_modules/is-extendable": {
-      "version": "1.0.1",
-      "license": "MIT",
-      "dependencies": {
-        "is-plain-object": "^2.0.4"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/split2": {
       "version": "4.2.0",
       "license": "ISC",
@@ -20353,17 +20264,6 @@
         "node": ">=4.2.0"
       }
     },
-    "node_modules/typewise": {
-      "version": "1.0.3",
-      "license": "MIT",
-      "dependencies": {
-        "typewise-core": "^1.2.0"
-      }
-    },
-    "node_modules/typewise-core": {
-      "version": "1.2.0",
-      "license": "MIT"
-    },
     "node_modules/ufo": {
       "version": "1.0.1",
       "license": "MIT"
@@ -20446,19 +20346,6 @@
       "version": "1.0.2",
       "license": "MIT"
     },
-    "node_modules/union-value": {
-      "version": "1.0.1",
-      "license": "MIT",
-      "dependencies": {
-        "arr-union": "^3.1.0",
-        "get-value": "^2.0.6",
-        "is-extendable": "^0.1.1",
-        "set-value": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/universalify": {
       "version": "0.2.0",
       "license": "MIT",
@@ -24853,7 +24740,9 @@
       }
     },
     "@mapbox/jsonlint-lines-primitives": {
-      "version": "2.0.2"
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
+      "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="
     },
     "@mapbox/point-geometry": {
       "version": "0.1.0"
@@ -24865,7 +24754,9 @@
       "version": "2.0.6"
     },
     "@mapbox/unitbezier": {
-      "version": "0.0.1"
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
+      "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="
     },
     "@mapbox/vector-tile": {
       "version": "1.3.1",
@@ -24877,14 +24768,29 @@
       "version": "3.1.0"
     },
     "@maplibre/maplibre-gl-style-spec": {
-      "version": "20.1.1",
+      "version": "23.1.0",
+      "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.1.0.tgz",
+      "integrity": "sha512-R6/ihEuC5KRexmKIYkWqUv84Gm+/QwsOUgHyt1yy2XqCdGdLvlBWVWIIeTZWN4NGdwmY6xDzdSGU2R9oBLNg2w==",
       "requires": {
         "@mapbox/jsonlint-lines-primitives": "~2.0.2",
         "@mapbox/unitbezier": "^0.0.1",
         "json-stringify-pretty-compact": "^4.0.0",
         "minimist": "^1.2.8",
+        "quickselect": "^3.0.0",
         "rw": "^1.3.3",
-        "sort-object": "^3.0.3"
+        "tinyqueue": "^3.0.0"
+      },
+      "dependencies": {
+        "quickselect": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
+          "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="
+        },
+        "tinyqueue": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
+          "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="
+        }
       }
     },
     "@monaco-editor/loader": {
@@ -26111,7 +26017,9 @@
       }
     },
     "@types/geojson": {
-      "version": "7946.0.14"
+      "version": "7946.0.16",
+      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+      "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="
     },
     "@types/geojson-vt": {
       "version": "3.2.5",
@@ -26689,9 +26597,6 @@
     "argparse": {
       "version": "2.0.1"
     },
-    "arr-union": {
-      "version": "3.1.0"
-    },
     "array-equal": {
       "version": "1.0.0"
     },
@@ -26745,9 +26650,6 @@
     "assertion-error": {
       "version": "1.1.0"
     },
-    "assign-symbols": {
-      "version": "1.0.0"
-    },
     "async": {
       "version": "3.2.5"
     },
@@ -26919,19 +26821,6 @@
     "buffer-writer": {
       "version": "2.0.0"
     },
-    "bytewise": {
-      "version": "1.1.0",
-      "requires": {
-        "bytewise-core": "^1.2.2",
-        "typewise": "^1.0.3"
-      }
-    },
-    "bytewise-core": {
-      "version": "1.2.3",
-      "requires": {
-        "typewise-core": "^1.2"
-      }
-    },
     "cac": {
       "version": "6.7.14"
     },
@@ -28431,12 +28320,6 @@
     "extend": {
       "version": "3.0.2"
     },
-    "extend-shallow": {
-      "version": "2.0.1",
-      "requires": {
-        "is-extendable": "^0.1.0"
-      }
-    },
     "extsprintf": {
       "version": "1.3.0"
     },
@@ -28844,7 +28727,9 @@
       }
     },
     "geojson-vt": {
-      "version": "3.2.1"
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz",
+      "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A=="
     },
     "geojson2svg": {
       "version": "1.3.3",
@@ -28932,9 +28817,6 @@
     "get-stream": {
       "version": "6.0.1"
     },
-    "get-value": {
-      "version": "2.0.6"
-    },
     "getpass": {
       "version": "0.1.7",
       "requires": {
@@ -29043,17 +28925,31 @@
       }
     },
     "global-prefix": {
-      "version": "3.0.0",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz",
+      "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==",
       "requires": {
-        "ini": "^1.3.5",
-        "kind-of": "^6.0.2",
-        "which": "^1.3.1"
+        "ini": "^4.1.3",
+        "kind-of": "^6.0.3",
+        "which": "^4.0.0"
       },
       "dependencies": {
+        "ini": {
+          "version": "4.1.3",
+          "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
+          "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="
+        },
+        "isexe": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
+          "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="
+        },
         "which": {
-          "version": "1.3.1",
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
+          "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
           "requires": {
-            "isexe": "^2.0.0"
+            "isexe": "^3.1.1"
           }
         }
       }
@@ -29367,7 +29263,8 @@
       "version": "2.0.4"
     },
     "ini": {
-      "version": "1.3.8"
+      "version": "1.3.8",
+      "dev": true
     },
     "interpret": {
       "version": "3.1.1",
@@ -29413,9 +29310,6 @@
         "has-tostringtag": "^1.0.0"
       }
     },
-    "is-extendable": {
-      "version": "0.1.1"
-    },
     "is-extglob": {
       "version": "2.1.1"
     },
@@ -29479,12 +29373,6 @@
     "is-plain-obj": {
       "version": "2.1.0"
     },
-    "is-plain-object": {
-      "version": "2.0.4",
-      "requires": {
-        "isobject": "^3.0.1"
-      }
-    },
     "is-potential-custom-element-name": {
       "version": "1.0.1",
       "optional": true,
@@ -29535,9 +29423,6 @@
     "isexe": {
       "version": "2.0.0"
     },
-    "isobject": {
-      "version": "3.0.1"
-    },
     "isstream": {
       "version": "0.1.2"
     },
@@ -29681,7 +29566,9 @@
       "dev": true
     },
     "json-stringify-pretty-compact": {
-      "version": "4.0.0"
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
+      "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="
     },
     "json-stringify-safe": {
       "version": "5.0.1"
@@ -30084,7 +29971,9 @@
       "integrity": "sha512-XawLsomeCq3O+x3IYTlU1QH52m9JvgEZvffgzWZ9P61HdSghJFzLUJjGXvtwV3hEuuZy9v9iSCG7W8pfr8p4Eg=="
     },
     "maplibre-gl": {
-      "version": "4.1.2",
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.1.0.tgz",
+      "integrity": "sha512-6lbf7qAnqAVm1T/vJBMmRtP+g8G/O/Z52IBtWX31SbFj7sEdlrk4YugxJen8IdV/pFjLFnDOw7HiHZl5nYdVjg==",
       "requires": {
         "@mapbox/geojson-rewind": "^0.5.2",
         "@mapbox/jsonlint-lines-primitives": "^2.0.2",
@@ -30093,32 +29982,44 @@
         "@mapbox/unitbezier": "^0.0.1",
         "@mapbox/vector-tile": "^1.3.1",
         "@mapbox/whoots-js": "^3.1.0",
-        "@maplibre/maplibre-gl-style-spec": "^20.1.1",
-        "@types/geojson": "^7946.0.14",
+        "@maplibre/maplibre-gl-style-spec": "^23.1.0",
+        "@types/geojson": "^7946.0.16",
         "@types/geojson-vt": "3.2.5",
         "@types/mapbox__point-geometry": "^0.1.4",
         "@types/mapbox__vector-tile": "^1.3.4",
         "@types/pbf": "^3.0.5",
         "@types/supercluster": "^7.1.3",
-        "earcut": "^2.2.4",
-        "geojson-vt": "^3.2.1",
+        "earcut": "^3.0.1",
+        "geojson-vt": "^4.0.2",
         "gl-matrix": "^3.4.3",
-        "global-prefix": "^3.0.0",
+        "global-prefix": "^4.0.0",
         "kdbush": "^4.0.2",
         "murmurhash-js": "^1.0.0",
-        "pbf": "^3.2.1",
+        "pbf": "^3.3.0",
         "potpack": "^2.0.0",
-        "quickselect": "^2.0.0",
+        "quickselect": "^3.0.0",
         "supercluster": "^8.0.1",
-        "tinyqueue": "^2.0.3",
+        "tinyqueue": "^3.0.0",
         "vt-pbf": "^3.1.3"
       },
       "dependencies": {
+        "earcut": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz",
+          "integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw=="
+        },
         "kdbush": {
           "version": "4.0.2"
         },
         "quickselect": {
-          "version": "2.0.0"
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
+          "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="
+        },
+        "tinyqueue": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
+          "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="
         }
       }
     },
@@ -32401,7 +32302,9 @@
       "version": "1.1.1"
     },
     "pbf": {
-      "version": "3.2.1",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz",
+      "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==",
       "requires": {
         "ieee754": "^1.1.12",
         "resolve-protobuf-schema": "^2.1.0"
@@ -33617,7 +33520,9 @@
       "integrity": "sha512-KPDNauF2Tpnm3nG0+0LJuJxwBFrhAdthpM8bVdDvjWQA7pWP7QoNwEl1+dJ7WVJj81AQP/i6kl6JUmAk7tg3Og=="
     },
     "rw": {
-      "version": "1.3.3"
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+      "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="
     },
     "sade": {
       "version": "1.8.1",
@@ -33720,15 +33625,6 @@
         "randombytes": "^2.1.0"
       }
     },
-    "set-value": {
-      "version": "2.0.1",
-      "requires": {
-        "extend-shallow": "^2.0.1",
-        "is-extendable": "^0.1.1",
-        "is-plain-object": "^2.0.3",
-        "split-string": "^3.0.1"
-      }
-    },
     "setimmediate": {
       "version": "1.0.5"
     },
@@ -33915,23 +33811,6 @@
         "sander": "^0.5.0"
       }
     },
-    "sort-asc": {
-      "version": "0.2.0"
-    },
-    "sort-desc": {
-      "version": "0.2.0"
-    },
-    "sort-object": {
-      "version": "3.0.3",
-      "requires": {
-        "bytewise": "^1.1.0",
-        "get-value": "^2.0.2",
-        "is-extendable": "^0.1.1",
-        "sort-asc": "^0.2.0",
-        "sort-desc": "^0.2.0",
-        "union-value": "^1.0.1"
-      }
-    },
     "source-map": {
       "version": "0.6.1"
     },
@@ -34070,27 +33949,6 @@
         "through": "2"
       }
     },
-    "split-string": {
-      "version": "3.1.0",
-      "requires": {
-        "extend-shallow": "^3.0.0"
-      },
-      "dependencies": {
-        "extend-shallow": {
-          "version": "3.0.2",
-          "requires": {
-            "assign-symbols": "^1.0.0",
-            "is-extendable": "^1.0.1"
-          }
-        },
-        "is-extendable": {
-          "version": "1.0.1",
-          "requires": {
-            "is-plain-object": "^2.0.4"
-          }
-        }
-      }
-    },
     "split2": {
       "version": "4.2.0"
     },
@@ -35220,15 +35078,6 @@
         }
       }
     },
-    "typewise": {
-      "version": "1.0.3",
-      "requires": {
-        "typewise-core": "^1.2.0"
-      }
-    },
-    "typewise-core": {
-      "version": "1.2.0"
-    },
     "ufo": {
       "version": "1.0.1"
     },
@@ -35277,15 +35126,6 @@
     "union-find": {
       "version": "1.0.2"
     },
-    "union-value": {
-      "version": "1.0.1",
-      "requires": {
-        "arr-union": "^3.1.0",
-        "get-value": "^2.0.6",
-        "is-extendable": "^0.1.1",
-        "set-value": "^2.0.1"
-      }
-    },
     "universalify": {
       "version": "0.2.0",
       "optional": true,
diff --git a/package.json b/package.json
index fc5674bae..bc17c4a41 100644
--- a/package.json
+++ b/package.json
@@ -209,7 +209,7 @@
     "libphonenumber-js": "^1.10.8",
     "mangrove-reviews-typescript": "^1.1.0",
     "maplibre": "^0.0.1-security",
-    "maplibre-gl": "^4.1.1",
+    "maplibre-gl": "^5.1.0  ",
     "marked": "^12.0.2",
     "monaco-editor": "^0.46.0",
     "mvt-to-geojson": "^0.0.5",
diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts
index 1a4140f0d..a97f35274 100644
--- a/src/UI/Map/MapLibreAdaptor.ts
+++ b/src/UI/Map/MapLibreAdaptor.ts
@@ -1,10 +1,5 @@
 import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
-import maplibregl, {
-    Map as MLMap,
-    Map as MlMap,
-    ScaleControl,
-    SourceSpecification,
-} from "maplibre-gl"
+import maplibregl, { Map as MLMap, Map as MlMap, ScaleControl, SourceSpecification } from "maplibre-gl"
 import { RasterLayerPolygon } from "../../Models/RasterLayers"
 import { Utils } from "../../Utils"
 import { BBox } from "../../Logic/BBox"
@@ -179,6 +174,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
 
         maplibreMap.addCallbackAndRunD((map) => {
             map.on("load", () => {
+                console.log("Setting projection")
+                map.setProjection({
+                    type: "globe" // Set projection to globe
+                })
                 self.MoveMapToCurrentLoc(self.location.data)
                 self.SetZoom(self.zoom.data)
                 self.setMaxBounds(self.maxbounds.data)
diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte
index f9b0ff82f..5d64a5882 100644
--- a/src/UI/ThemeViewGUI.svelte
+++ b/src/UI/ThemeViewGUI.svelte
@@ -176,6 +176,7 @@
 </script>
 
 <main>
+  <div class="absolute top-0 left-0 h-screen w-screen" style="background-color: #cccccc"></div>
   <!-- Main map -->
   <div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
     <MaplibreMap map={maplibremap} mapProperties={mapproperties} autorecovery={true} />

From 6260bc2897d3ddd77ea6194bb530a9bffabbe76b Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Wed, 5 Feb 2025 01:26:20 +0100
Subject: [PATCH 49/49] Fix: fix broken index page for anonymous users

---
 src/Logic/State/UserRelatedState.ts |  4 ++--
 src/UI/AllThemesGui.svelte          | 11 +++++++----
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts
index e496a4067..8ca36200a 100644
--- a/src/Logic/State/UserRelatedState.ts
+++ b/src/Logic/State/UserRelatedState.ts
@@ -350,10 +350,10 @@ export default class UserRelatedState {
      * List of all hidden themes that have been seen before
      * @param osmConnection
      */
-    public static initDiscoveredHiddenThemes(osmConnection: OsmConnection): Store<string[]> {
+    public static initDiscoveredHiddenThemes(osmConnection: OsmConnection): Store<undefined | string[]> {
         const prefix = "mapcomplete-hidden-theme-"
         const userPreferences = osmConnection.preferencesHandler.allPreferences
-        return userPreferences.map((preferences) =>
+        return userPreferences.mapD((preferences) =>
             Object.keys(preferences)
                 .filter((key) => key.startsWith(prefix))
                 .map((key) => key.substring(prefix.length, key.length - "-enabled".length))
diff --git a/src/UI/AllThemesGui.svelte b/src/UI/AllThemesGui.svelte
index ae30a0c56..14c38a216 100644
--- a/src/UI/AllThemesGui.svelte
+++ b/src/UI/AllThemesGui.svelte
@@ -40,7 +40,7 @@
   const tu = Translations.t.general
   const tr = Translations.t.general.morescreen
 
-  let userLanguages = osmConnection.userDetails.map((ud) => ud.languages)
+  let userLanguages = osmConnection.userDetails.map((ud) => ud?.languages ?? [])
   let search: UIEventSource<string | undefined> = new UIEventSource<string>("")
   let searchStable = search.stabilized(100)
 
@@ -52,12 +52,12 @@
   const hiddenThemes: MinimalThemeInformation[] = ThemeSearch.officialThemes.themes.filter(
     (th) => th.hideFromOverview === true
   )
-  let visitedHiddenThemes: Store<MinimalThemeInformation[]> =
-    UserRelatedState.initDiscoveredHiddenThemes(state.osmConnection).map((knownIds) =>
+  let visitedHiddenThemes: Store<undefined | MinimalThemeInformation[]> =
+    UserRelatedState.initDiscoveredHiddenThemes(state.osmConnection).mapD((knownIds) =>
       hiddenThemes.filter(
         (theme) =>
           knownIds.indexOf(theme.id) >= 0 ||
-          state.osmConnection.userDetails.data.name === "Pieter Vander Vennet"
+          state.osmConnection.userDetails?.data?.name === "Pieter Vander Vennet"
       )
     )
 
@@ -67,6 +67,9 @@
   function filtered(themes: Store<MinimalThemeInformation[]>): Store<MinimalThemeInformation[]> {
     return searchStable.map(
       (search) => {
+        if (!themes.data) {
+          return []
+        }
         if (!search) {
           return themes.data
         }