From c6b2eb3ccef18173b4088c7e068c6277f1317148 Mon Sep 17 00:00:00 2001 From: PJ Date: Thu, 10 Sep 2026 02:57:55 +0530 Subject: [PATCH] fix android trivia experience --- .gitignore | 2 +- AGENTS.md | 1 + README.md | 2 +- androidApp/src/main/AndroidManifest.xml | 2 + .../kotlin/app/bertsatlas/MainActivity.kt | 10 + .../main/res/drawable/app_icon_foreground.xml | 17 + .../main/res/drawable/splash_background.xml | 5 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3163 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3163 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1930 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1930 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4368 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 4368 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6799 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 6799 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9107 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 9107 bytes androidApp/src/main/res/values-v31/styles.xml | 12 + androidApp/src/main/res/values/colors.xml | 4 + androidApp/src/main/res/values/styles.xml | 6 +- .../kotlin/app/bertsatlas/catalogtool/Main.kt | 8 + .../drawable/bert_atlas_mark.svg | 17 + .../drawable/bert_correct.svg | 8 +- .../composeResources/drawable/bert_fact.svg | 8 +- .../composeResources/drawable/bert_idle.svg | 16 +- .../drawable/bert_incorrect.svg | 8 +- .../composeResources/files/bert_correct.json | 153 +- .../composeResources/files/bert_fact.json | 116 +- .../composeResources/files/bert_hint.json | 114 +- .../composeResources/files/bert_idle.json | 158 +- .../files/bert_incorrect.json | 125 +- .../composeResources/files/bert_overtime.json | 113 +- .../composeResources/files/bert_results.json | 132 +- .../composeResources/files/bert_stamp.json | 159 +- .../composeResources/files/bert_streak.json | 113 +- .../files/catalog/catalog-v1.json | 2572 +++++++++-------- .../commonMain/kotlin/app/bertsatlas/App.kt | 70 +- .../kotlin/app/bertsatlas/AppController.kt | 189 +- .../app/bertsatlas/catalog/CatalogLoader.kt | 67 +- .../app/bertsatlas/data/PreferencesStore.kt | 20 +- .../app/bertsatlas/ui/AtlasComponents.kt | 186 +- .../kotlin/app/bertsatlas/ui/AtlasTheme.kt | 37 +- .../bertsatlas/ui/HomeAndOptionsScreens.kt | 101 +- .../kotlin/app/bertsatlas/ui/QuizScreens.kt | 176 +- .../ui/ResultsAndProgressScreens.kt | 25 +- .../app/bertsatlas/ui/SettingsScreens.kt | 11 +- content/assets-and-sources.md | 4 + content/brand/berts-atlas-mark.svg | 14 + content/brand/berts-atlas-splash.svg | 7 + content/voice-and-feedback-recommendations.md | 24 + .../kotlin/app/bertsatlas/domain/Contracts.kt | 4 +- .../app/bertsatlas/domain/QuestionSelector.kt | 52 +- .../app/bertsatlas/domain/QuizEngine.kt | 76 +- .../kotlin/app/bertsatlas/domain/Scoring.kt | 6 +- .../app/bertsatlas/domain/model/Progress.kt | 2 + .../kotlin/app/bertsatlas/domain/model/Run.kt | 9 +- iosApp/iosApp.xcodeproj/project.pbxproj | 4 + .../AppIcon.appiconset/AppIcon-1024.png | Bin 0 -> 64799 bytes .../AppIcon.appiconset/Contents.json | 14 + iosApp/iosApp/Assets.xcassets/Contents.json | 6 + .../LaunchBackground.colorset/Contents.json | 20 + .../LaunchLogo.imageset/Contents.json | 23 + .../LaunchLogo.imageset/LaunchLogo.png | Bin 0 -> 6361 bytes .../LaunchLogo.imageset/LaunchLogo@2x.png | Bin 0 -> 13055 bytes .../LaunchLogo.imageset/LaunchLogo@3x.png | Bin 0 -> 19997 bytes iosApp/iosApp/Info.plist | 9 +- spec.md | 18 +- 69 files changed, 3493 insertions(+), 1572 deletions(-) create mode 100644 androidApp/src/main/res/drawable/app_icon_foreground.xml create mode 100644 androidApp/src/main/res/drawable/splash_background.xml create mode 100644 androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 androidApp/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 androidApp/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 androidApp/src/main/res/values-v31/styles.xml create mode 100644 androidApp/src/main/res/values/colors.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/bert_atlas_mark.svg create mode 100644 content/brand/berts-atlas-mark.svg create mode 100644 content/brand/berts-atlas-splash.svg create mode 100644 content/voice-and-feedback-recommendations.md create mode 100644 iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png create mode 100644 iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/LaunchBackground.colorset/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/LaunchLogo.png create mode 100644 iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@2x.png create mode 100644 iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@3x.png diff --git a/.gitignore b/.gitignore index ee779db..6e8c876 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ local.properties DerivedData/ *.xcuserstate xcuserdata/ - +.DS_Store diff --git a/AGENTS.md b/AGENTS.md index 2b981cb..76e825a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,7 @@ ## Coding Guidelines - Keep code simple and easy to read. + - Never use em dashes in app user-facing copy. - Avoid excessive comments. We want code itself to be super readable. Only comment when absolutely necessary. - Do not create or modify implementation code unless the user explicitly asks for code changes. - Do not create or modify tests unless the user explicitly asks for tests. diff --git a/README.md b/README.md index 0afc8c7..7fb806c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Bert's Atlas is an offline geography trivia game for Android and iOS. Product be ## Architecture -- `domain`: pure Kotlin Multiplatform quiz, timing, scoring, mastery, streak, and selection rules +- `domain`: pure Kotlin Multiplatform trivia, timing, scoring, mastery, streak, and selection rules - `composeApp`: shared Compose UI, bundled resources, Room persistence, and platform adapters - `catalogTool`: non-test JVM catalog validator and editorial report generator - `androidApp`: thin Android application host diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index ee39898..65973db 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -3,7 +3,9 @@ + + + + + + + + + + + + + + + + diff --git a/androidApp/src/main/res/drawable/splash_background.xml b/androidApp/src/main/res/drawable/splash_background.xml new file mode 100644 index 0000000..10fa406 --- /dev/null +++ b/androidApp/src/main/res/drawable/splash_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..67a340f --- /dev/null +++ b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..67a340f --- /dev/null +++ b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..37d7d186b40d607e2018da39b02f26356a28670e GIT binary patch literal 3163 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 B89e|1 literal 0 HcmV?d00001 diff --git a/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..37d7d186b40d607e2018da39b02f26356a28670e GIT binary patch literal 3163 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 B89e|1 literal 0 HcmV?d00001 diff --git a/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2b0ac9155cf6125838b8cb259eee20f9ae3dca00 GIT binary patch literal 1930 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 KMNUMnLSTZDXo-pd literal 0 HcmV?d00001 diff --git a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..2718df93123c85a5b9766a3133f6b1575a80acf9 GIT binary patch literal 4368 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 KMNUMnLSTZDXo-pd literal 0 HcmV?d00001 diff --git a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..084f1489f707fa514af4089db2108c74ce10e608 GIT binary patch literal 6799 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 literal 0 HcmV?d00001 diff --git a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..084f1489f707fa514af4089db2108c74ce10e608 GIT binary patch literal 6799 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 literal 0 HcmV?d00001 diff --git a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..5c77a07ef78e73a4af349e8967dd7bc03023cb24 GIT binary patch literal 9107 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 + + + diff --git a/androidApp/src/main/res/values/colors.xml b/androidApp/src/main/res/values/colors.xml new file mode 100644 index 0000000..0ec3d51 --- /dev/null +++ b/androidApp/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #F6E9E1 + diff --git a/androidApp/src/main/res/values/styles.xml b/androidApp/src/main/res/values/styles.xml index 98d9277..bb6561a 100644 --- a/androidApp/src/main/res/values/styles.xml +++ b/androidApp/src/main/res/values/styles.xml @@ -3,8 +3,8 @@ - diff --git a/catalogTool/src/main/kotlin/app/bertsatlas/catalogtool/Main.kt b/catalogTool/src/main/kotlin/app/bertsatlas/catalogtool/Main.kt index cd1b496..0e618dc 100644 --- a/catalogTool/src/main/kotlin/app/bertsatlas/catalogtool/Main.kt +++ b/catalogTool/src/main/kotlin/app/bertsatlas/catalogtool/Main.kt @@ -123,6 +123,11 @@ private class Validator(private val path: Path) { val label = "fact ${id(fact)}" expectInt(fact, "schemaVersion", 1, label) listOf("heading", "text").forEach { text(fact, it, label) } + val heading = value(fact, "heading") + val copy = value(fact, "text") + if (heading == "Atlas placement" || copy.contains("UN M49", ignoreCase = true)) { + errors += "$label exposes catalog metadata as lesson copy" + } refs(label, strings(fact, "countryIds"), countryIds, "country") val sourcesForFact = strings(fact, "sourceIds") if (sourcesForFact.isEmpty()) errors += "$label has no factual source" @@ -207,6 +212,9 @@ private class Validator(private val path: Path) { } val hints = strings(question, "supportedHints") (hints - validHints).forEach { errors += "$label has unsupported hint $it" } + if ("factual_clue" in hints && value(question, "explanation").contains(answer, ignoreCase = true)) { + errors += "$label factual clue contains its answer" + } if (format != "letter_bank" && "reveal_letter" in hints) errors += "$label has an incompatible letter hint" if (format != "multiple_choice" && "remove_choices" in hints) errors += "$label has incompatible choice removal" refs(label, strings(question, "countryIds"), countryIds, "country") diff --git a/composeApp/src/commonMain/composeResources/drawable/bert_atlas_mark.svg b/composeApp/src/commonMain/composeResources/drawable/bert_atlas_mark.svg new file mode 100644 index 0000000..ecd2101 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/bert_atlas_mark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/bert_correct.svg b/composeApp/src/commonMain/composeResources/drawable/bert_correct.svg index 8dd28d2..ae08126 100644 --- a/composeApp/src/commonMain/composeResources/drawable/bert_correct.svg +++ b/composeApp/src/commonMain/composeResources/drawable/bert_correct.svg @@ -1,7 +1,7 @@ Bert cheering after a correct answer - - - - + + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/bert_fact.svg b/composeApp/src/commonMain/composeResources/drawable/bert_fact.svg index caea540..8b9fa44 100644 --- a/composeApp/src/commonMain/composeResources/drawable/bert_fact.svg +++ b/composeApp/src/commonMain/composeResources/drawable/bert_fact.svg @@ -1,7 +1,7 @@ Bert delighted by a surprising fact - - - - + + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/bert_idle.svg b/composeApp/src/commonMain/composeResources/drawable/bert_idle.svg index 1c919c4..14cf2a7 100644 --- a/composeApp/src/commonMain/composeResources/drawable/bert_idle.svg +++ b/composeApp/src/commonMain/composeResources/drawable/bert_idle.svg @@ -1,11 +1,11 @@ Bert smiling behind oversized glasses - - - - - - - - + + + + + + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/bert_incorrect.svg b/composeApp/src/commonMain/composeResources/drawable/bert_incorrect.svg index 8db5061..98b34c6 100644 --- a/composeApp/src/commonMain/composeResources/drawable/bert_incorrect.svg +++ b/composeApp/src/commonMain/composeResources/drawable/bert_incorrect.svg @@ -1,7 +1,7 @@ Bert offering encouragement after a wrong answer - - - - + + + + diff --git a/composeApp/src/commonMain/composeResources/files/bert_correct.json b/composeApp/src/commonMain/composeResources/files/bert_correct.json index 9c9dfa3..5bcf5b2 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_correct.json +++ b/composeApp/src/commonMain/composeResources/files/bert_correct.json @@ -1 +1,152 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":45,"w":512,"h":512,"nm":"bert correct accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"success star","ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"t":0,"s":[-12]},{"t":22,"s":[12]},{"t":45,"s":[-12]}]},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"t":0,"s":[70,70,100]},{"t":22,"s":[110,110,100]},{"t":45,"s":[70,70,100]}]}},"shapes":[{"ty":"sr","sy":1,"pt":{"a":0,"k":5},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"or":{"a":0,"k":100},"os":{"a":0,"k":0},"ir":{"a":0,"k":45},"is":{"a":0,"k":0}},{"ty":"fl","c":{"a":0,"k":[0.08,0.46,0.41,1]},"o":{"a":0,"k":100},"r":1}],"ip":0,"op":45,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 45, + "w": 512, + "h": 512, + "nm": "bert correct accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "success star", + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + -12 + ] + }, + { + "t": 22, + "s": [ + 12 + ] + }, + { + "t": 45, + "s": [ + -12 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 420, + 95, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 70, + 70, + 100 + ] + }, + { + "t": 22, + "s": [ + 110, + 110, + 100 + ] + }, + { + "t": 45, + "s": [ + 70, + 70, + 100 + ] + } + ] + } + }, + "shapes": [ + { + "ty": "sr", + "sy": 1, + "pt": { + "a": 0, + "k": 5 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "or": { + "a": 0, + "k": 42 + }, + "os": { + "a": 0, + "k": 0 + }, + "ir": { + "a": 0, + "k": 18 + }, + "is": { + "a": 0, + "k": 0 + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.152941, + 0.388235, + 0.356863, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1 + } + ], + "ip": 0, + "op": 45, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_fact.json b/composeApp/src/commonMain/composeResources/files/bert_fact.json index dd414bc..081f7e4 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_fact.json +++ b/composeApp/src/commonMain/composeResources/files/bert_fact.json @@ -1 +1,115 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":60,"w":512,"h":512,"nm":"bert fact accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"fact bubble","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"t":0,"s":[256,270,0]},{"t":30,"s":[256,242,0]},{"t":60,"s":[256,270,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[190,150]}},{"ty":"fl","c":{"a":0,"k":[0.09,0.39,0.49,1]},"o":{"a":0,"k":100},"r":1}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 60, + "w": 512, + "h": 512, + "nm": "bert fact accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "fact bubble", + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 0, + "k": 0 + }, + "p": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 420, + 100, + 0 + ] + }, + { + "t": 30, + "s": [ + 420, + 82, + 0 + ] + }, + { + "t": 60, + "s": [ + 420, + 100, + 0 + ] + } + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "shapes": [ + { + "ty": "el", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 72, + 54 + ] + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.466667, + 0.545098, + 0.647059, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1 + } + ], + "ip": 0, + "op": 60, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_hint.json b/composeApp/src/commonMain/composeResources/files/bert_hint.json index ed18d02..b839f28 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_hint.json +++ b/composeApp/src/commonMain/composeResources/files/bert_hint.json @@ -1 +1,113 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":60,"w":512,"h":512,"nm":"bert hint accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"hint glow","ks":{"o":{"a":1,"k":[{"t":0,"s":[45]},{"t":30,"s":[100]},{"t":60,"s":[45]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[180,180]}},{"ty":"fl","c":{"a":0,"k":[0.89,0.68,0.21,1]},"o":{"a":0,"k":100},"r":1}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 60, + "w": 512, + "h": 512, + "nm": "bert hint accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "hint glow", + "ks": { + "o": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 45 + ] + }, + { + "t": 30, + "s": [ + 100 + ] + }, + { + "t": 60, + "s": [ + 45 + ] + } + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 420, + 95, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "shapes": [ + { + "ty": "el", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 64, + 64 + ] + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.913725, + 0.686275, + 0.545098, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1 + } + ], + "ip": 0, + "op": 60, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_idle.json b/composeApp/src/commonMain/composeResources/files/bert_idle.json index ae07e11..d559c8d 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_idle.json +++ b/composeApp/src/commonMain/composeResources/files/bert_idle.json @@ -1 +1,157 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":90,"w":512,"h":512,"nm":"bert idle","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"spark","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"t":0,"s":[-8]},{"t":45,"s":[8]},{"t":90,"s":[-8]}]},"p":{"a":0,"k":[410,105,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"t":0,"s":[85,85,100]},{"t":45,"s":[110,110,100]},{"t":90,"s":[85,85,100]}]}},"ao":0,"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[48,48]},"nm":"spark circle"},{"ty":"fl","c":{"a":0,"k":[0.09,0.45,0.41,1]},"o":{"a":0,"k":100},"r":1,"nm":"teal fill"}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"bert","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"t":0,"s":[256,266,0]},{"t":45,"s":[256,258,0]},{"t":90,"s":[256,266,0]}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[310,270]},"nm":"brain silhouette"},{"ty":"fl","c":{"a":0,"k":[0.91,0.43,0.36,1]},"o":{"a":0,"k":100},"r":1,"nm":"coral fill"},{"ty":"st","c":{"a":0,"k":[0.24,0.14,0.28,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":12},"lc":2,"lj":2,"nm":"plum stroke"}],"ip":0,"op":90,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 90, + "w": 512, + "h": 512, + "nm": "bert idle accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "curiosity spark", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 45 + ] + }, + { + "t": 45, + "s": [ + 100 + ] + }, + { + "t": 90, + "s": [ + 45 + ] + } + ] + }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + -8 + ] + }, + { + "t": 45, + "s": [ + 8 + ] + }, + { + "t": 90, + "s": [ + -8 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 412, + 102, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 75, + 75, + 100 + ] + }, + { + "t": 45, + "s": [ + 105, + 105, + 100 + ] + }, + { + "t": 90, + "s": [ + 75, + 75, + 100 + ] + } + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "el", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 34, + 34 + ] + }, + "nm": "spark dot" + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.152941, + 0.388235, + 0.356863, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1, + "nm": "teal fill" + } + ], + "ip": 0, + "op": 90, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_incorrect.json b/composeApp/src/commonMain/composeResources/files/bert_incorrect.json index 5d9b077..96bf750 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_incorrect.json +++ b/composeApp/src/commonMain/composeResources/files/bert_incorrect.json @@ -1 +1,124 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":45,"w":512,"h":512,"nm":"bert incorrect accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"kind wobble","ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"t":0,"s":[-4]},{"t":15,"s":[4]},{"t":30,"s":[-4]},{"t":45,"s":[0]}]},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[170,170]}},{"ty":"st","c":{"a":0,"k":[0.61,0.24,0.2,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":18},"lc":2,"lj":2}],"ip":0,"op":45,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 45, + "w": 512, + "h": 512, + "nm": "bert incorrect accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "kind wobble", + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + -4 + ] + }, + { + "t": 15, + "s": [ + 4 + ] + }, + { + "t": 30, + "s": [ + -4 + ] + }, + { + "t": 45, + "s": [ + 0 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 420, + 95, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "shapes": [ + { + "ty": "el", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 42, + 42 + ] + } + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.882353, + 0.505882, + 0.305882, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "w": { + "a": 0, + "k": 18 + }, + "lc": 2, + "lj": 2 + } + ], + "ip": 0, + "op": 45, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_overtime.json b/composeApp/src/commonMain/composeResources/files/bert_overtime.json index 2b26a83..992c095 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_overtime.json +++ b/composeApp/src/commonMain/composeResources/files/bert_overtime.json @@ -1 +1,112 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":60,"w":512,"h":512,"nm":"bert overtime accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"clock","ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"t":0,"s":[0]},{"t":60,"s":[360]}]},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[190,190]}},{"ty":"st","c":{"a":0,"k":[0.89,0.68,0.21,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":20},"lc":2,"lj":2}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 60, + "w": 512, + "h": 512, + "nm": "bert overtime accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "clock", + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 60, + "s": [ + 360 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 420, + 95, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "shapes": [ + { + "ty": "el", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 70, + 70 + ] + } + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.913725, + 0.686275, + 0.545098, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "w": { + "a": 0, + "k": 20 + }, + "lc": 2, + "lj": 2 + } + ], + "ip": 0, + "op": 60, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_results.json b/composeApp/src/commonMain/composeResources/files/bert_results.json index e4ceab1..000a771 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_results.json +++ b/composeApp/src/commonMain/composeResources/files/bert_results.json @@ -1 +1,131 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":45,"w":512,"h":512,"nm":"bert results accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"result seal","ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"t":0,"s":[-8]},{"t":22,"s":[8]},{"t":45,"s":[-8]}]},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"sr","sy":1,"pt":{"a":0,"k":12},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"or":{"a":0,"k":110},"os":{"a":0,"k":0},"ir":{"a":0,"k":90},"is":{"a":0,"k":0}},{"ty":"fl","c":{"a":0,"k":[0.09,0.39,0.49,1]},"o":{"a":0,"k":100},"r":1}],"ip":0,"op":45,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 45, + "w": 512, + "h": 512, + "nm": "bert results accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "result seal", + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + -8 + ] + }, + { + "t": 22, + "s": [ + 8 + ] + }, + { + "t": 45, + "s": [ + -8 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 420, + 95, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "shapes": [ + { + "ty": "sr", + "sy": 1, + "pt": { + "a": 0, + "k": 12 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "or": { + "a": 0, + "k": 42 + }, + "os": { + "a": 0, + "k": 0 + }, + "ir": { + "a": 0, + "k": 32 + }, + "is": { + "a": 0, + "k": 0 + } + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.466667, + 0.545098, + 0.647059, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1 + } + ], + "ip": 0, + "op": 45, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_stamp.json b/composeApp/src/commonMain/composeResources/files/bert_stamp.json index 9c501a9..a824f7b 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_stamp.json +++ b/composeApp/src/commonMain/composeResources/files/bert_stamp.json @@ -1 +1,158 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":30,"w":512,"h":512,"nm":"bert passport stamp accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"stamp","ks":{"o":{"a":1,"k":[{"t":0,"s":[0]},{"t":10,"s":[100]},{"t":30,"s":[100]}]},"r":{"a":1,"k":[{"t":0,"s":[-18]},{"t":10,"s":[3]},{"t":30,"s":[0]}]},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"t":0,"s":[150,150,100]},{"t":10,"s":[95,95,100]},{"t":30,"s":[100,100,100]}]}},"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[220,220]}},{"ty":"st","c":{"a":0,"k":[0.09,0.39,0.49,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":20},"lc":2,"lj":2}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 30, + "w": 512, + "h": 512, + "nm": "bert passport stamp accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "stamp", + "ks": { + "o": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 10, + "s": [ + 100 + ] + }, + { + "t": 30, + "s": [ + 100 + ] + } + ] + }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + -18 + ] + }, + { + "t": 10, + "s": [ + 3 + ] + }, + { + "t": 30, + "s": [ + 0 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 420, + 95, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 150, + 150, + 100 + ] + }, + { + "t": 10, + "s": [ + 95, + 95, + 100 + ] + }, + { + "t": 30, + "s": [ + 100, + 100, + 100 + ] + } + ] + } + }, + "shapes": [ + { + "ty": "el", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 72, + 72 + ] + } + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.466667, + 0.545098, + 0.647059, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "w": { + "a": 0, + "k": 20 + }, + "lc": 2, + "lj": 2 + } + ], + "ip": 0, + "op": 30, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/bert_streak.json b/composeApp/src/commonMain/composeResources/files/bert_streak.json index ed2a1a5..eccb5f6 100644 --- a/composeApp/src/commonMain/composeResources/files/bert_streak.json +++ b/composeApp/src/commonMain/composeResources/files/bert_streak.json @@ -1 +1,112 @@ -{"v":"5.7.4","fr":30,"ip":0,"op":60,"w":512,"h":512,"nm":"bert streak accent","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"streak ring","ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"t":0,"s":[0]},{"t":60,"s":[360]}]},"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"shapes":[{"ty":"el","p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[210,210]}},{"ty":"st","c":{"a":0,"k":[0.91,0.43,0.36,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":22},"lc":2,"lj":2}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} +{ + "v": "5.7.4", + "fr": 30, + "ip": 0, + "op": 60, + "w": 512, + "h": 512, + "nm": "bert streak accent", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "streak ring", + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 60, + "s": [ + 360 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 420, + 95, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "shapes": [ + { + "ty": "el", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 70, + 70 + ] + } + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.968627, + 0.74902, + 0.745098, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "w": { + "a": 0, + "k": 22 + }, + "lc": 2, + "lj": 2 + } + ], + "ip": 0, + "op": 60, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/composeApp/src/commonMain/composeResources/files/catalog/catalog-v1.json b/composeApp/src/commonMain/composeResources/files/catalog/catalog-v1.json index 5a9a4b5..e4553b4 100644 --- a/composeApp/src/commonMain/composeResources/files/catalog/catalog-v1.json +++ b/composeApp/src/commonMain/composeResources/files/catalog/catalog-v1.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "catalogVersion": "1.0.0-draft.1", + "catalogVersion": "1.0.0-draft.2", "countryCanon": "un193-plus-palestine-and-vatican-city", "editorialPolicyVersion": "1.0.0", "generatedFromPinnedDataAt": "2026-09-09", @@ -52,6 +52,126 @@ "publisher": "Hampus Borgos and contributors", "retrievedAt": "2026-09-09", "revision": "c09927e63705529bbf59ca6684cd9b23225dddad" + }, + { + "id": "source-rest-countries", + "title": "REST Countries V4 country data", + "url": "https://gitlab.com/restcountries/restcountries/-/tree/43f6260db10c54df9972e6e554ee14c2e7c85ef5", + "publisher": "REST Countries contributors", + "retrievedAt": "2026-09-10", + "revision": "43f6260db10c54df9972e6e554ee14c2e7c85ef5" + }, + { + "id": "source-experience-oman-musandam", + "title": "Experience Oman: Musandam", + "url": "https://booking.experienceoman.om/en/destination/msd-93", + "publisher": "Ministry of Heritage and Tourism, Sultanate of Oman", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-philippines-tourism-islands", + "title": "Island Hopping", + "url": "https://www.tourism.gov.ph/article/island-hopping-0-21/", + "publisher": "Department of Tourism, Republic of the Philippines", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-geoscience-australia", + "title": "Australia's size compared", + "url": "https://www.ga.gov.au/scientific-topics/national-location-information/dimensions/australias-size-compared", + "publisher": "Geoscience Australia", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-ibge-brazil", + "title": "Brazil: territory introduction", + "url": "https://educa.ibge.gov.br/jovens/conheca-o-brasil/territorio/20591-introducao.html", + "publisher": "Brazilian Institute of Geography and Statistics", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-canada-coastlines", + "title": "Canada's coastlines", + "url": "https://www.canada.ca/en/services/environment/our-environment/nature-based-climate-solutions/coastlines.html", + "publisher": "Government of Canada", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-incredible-india-dal-lake", + "title": "Dal Lake", + "url": "https://www.incredibleindia.gov.in/en/jammu-and-kashmir/srinagar/dal-lake", + "publisher": "Ministry of Tourism, Government of India", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-gsi-japan-islands", + "title": "Re-counting the number of islands in Japan", + "url": "https://www.gsi.go.jp/kihonjohochousa/islands_index.html", + "publisher": "Geospatial Information Authority of Japan", + "retrievedAt": "2026-09-10", + "revision": "2022 digital-map recount published 2023-02-28" + }, + { + "id": "source-mexico-flag-history", + "title": "Learn about the history of the Mexican flag", + "url": "https://www.gob.mx/sre/articulos/learn-about-the-history-of-the-mexican-flag-esmibandera", + "publisher": "Government of Mexico, Secretariat of Foreign Affairs", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-south-africa-provinces", + "title": "South Africa's provinces", + "url": "https://www.gov.za/about-sa/south-africas-provinces", + "publisher": "Government of South Africa", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-rmg-greenwich", + "title": "Discover the home of Greenwich Mean Time", + "url": "https://www.rmg.co.uk/plan-your-visit/visit-greenwich/discover-home-of-time", + "publisher": "Royal Museums Greenwich", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-nz-womens-suffrage", + "title": "Women and the vote", + "url": "https://nzhistory.govt.nz/politics/womens-suffrage", + "publisher": "New Zealand Ministry for Culture and Heritage", + "retrievedAt": "2026-09-10", + "revision": "web snapshot 2026-09-10" + }, + { + "id": "source-nepal-film-guide", + "title": "Filming in Nepal: a guide", + "url": "https://film.gov.np/media/filmgov/uploads/Filming_in_Nepal_2010_a_Guide_bOok_Chiranjibi_Guragain1.pdf", + "publisher": "Government of Nepal Film Development Board", + "retrievedAt": "2026-09-10", + "revision": "2010 guide" + }, + { + "id": "source-netherlands-govtech", + "title": "GovTech in the Netherlands", + "url": "https://www.government.nl/binaries/government/documenten/reports/2021/06/30/govtech-in-the-netherlands/PUBLIC-GovTech-NL.pdf", + "publisher": "Government of the Netherlands", + "retrievedAt": "2026-09-10", + "revision": "2021-06 report" + }, + { + "id": "source-cia-monaco-flag-archive", + "title": "Monaco flag", + "url": "https://www.cia.gov/the-world-factbook/about/archives/2023/countries/monaco/flag/", + "publisher": "CIA World Factbook archive", + "retrievedAt": "2026-09-10", + "revision": "2023 archive" } ], "countries": [ @@ -89,7 +209,7 @@ "letter_bank" ], "factIds": [ - "fact-af-m49-region" + "fact-af-land-neighbours" ], "review": { "state": "draft", @@ -131,7 +251,7 @@ "letter_bank" ], "factIds": [ - "fact-al-m49-region" + "fact-al-land-neighbours" ], "review": { "state": "draft", @@ -173,7 +293,7 @@ "letter_bank" ], "factIds": [ - "fact-dz-m49-region" + "fact-dz-land-neighbours" ], "review": { "state": "draft", @@ -215,7 +335,7 @@ "letter_bank" ], "factIds": [ - "fact-ad-m49-region" + "fact-ad-land-neighbours" ], "review": { "state": "draft", @@ -257,7 +377,7 @@ "letter_bank" ], "factIds": [ - "fact-ao-m49-region" + "fact-ao-land-neighbours" ], "review": { "state": "draft", @@ -301,7 +421,7 @@ "letter_bank" ], "factIds": [ - "fact-ag-m49-region" + "fact-ag-land-neighbours" ], "review": { "state": "draft", @@ -343,7 +463,7 @@ "letter_bank" ], "factIds": [ - "fact-ar-m49-region" + "fact-ar-land-neighbours" ], "review": { "state": "draft", @@ -385,7 +505,7 @@ "letter_bank" ], "factIds": [ - "fact-am-m49-region" + "fact-am-land-neighbours" ], "review": { "state": "draft", @@ -427,7 +547,7 @@ "letter_bank" ], "factIds": [ - "fact-au-m49-region" + "fact-au-land-neighbours" ], "review": { "state": "draft", @@ -469,7 +589,7 @@ "letter_bank" ], "factIds": [ - "fact-at-m49-region" + "fact-at-land-neighbours" ], "review": { "state": "draft", @@ -511,7 +631,7 @@ "letter_bank" ], "factIds": [ - "fact-az-m49-region" + "fact-az-land-neighbours" ], "review": { "state": "draft", @@ -553,7 +673,7 @@ "letter_bank" ], "factIds": [ - "fact-bs-m49-region" + "fact-bs-land-neighbours" ], "review": { "state": "draft", @@ -595,7 +715,7 @@ "letter_bank" ], "factIds": [ - "fact-bh-m49-region" + "fact-bh-land-neighbours" ], "review": { "state": "draft", @@ -637,7 +757,7 @@ "letter_bank" ], "factIds": [ - "fact-bd-m49-region" + "fact-bd-land-neighbours" ], "review": { "state": "draft", @@ -679,7 +799,7 @@ "letter_bank" ], "factIds": [ - "fact-bb-m49-region" + "fact-bb-land-neighbours" ], "review": { "state": "draft", @@ -721,7 +841,7 @@ "letter_bank" ], "factIds": [ - "fact-by-m49-region" + "fact-by-land-neighbours" ], "review": { "state": "draft", @@ -763,7 +883,7 @@ "letter_bank" ], "factIds": [ - "fact-be-m49-region" + "fact-be-land-neighbours" ], "review": { "state": "draft", @@ -805,7 +925,7 @@ "letter_bank" ], "factIds": [ - "fact-bz-m49-region" + "fact-bz-land-neighbours" ], "review": { "state": "draft", @@ -848,7 +968,7 @@ "letter_bank" ], "factIds": [ - "fact-bj-m49-region" + "fact-bj-land-neighbours" ], "review": { "state": "draft", @@ -890,7 +1010,7 @@ "letter_bank" ], "factIds": [ - "fact-bt-m49-region" + "fact-bt-land-neighbours" ], "review": { "state": "draft", @@ -935,7 +1055,7 @@ "letter_bank" ], "factIds": [ - "fact-bo-m49-region" + "fact-bo-land-neighbours" ], "review": { "state": "draft", @@ -979,7 +1099,7 @@ "letter_bank" ], "factIds": [ - "fact-ba-m49-region" + "fact-ba-land-neighbours" ], "review": { "state": "draft", @@ -1021,7 +1141,7 @@ "letter_bank" ], "factIds": [ - "fact-bw-m49-region" + "fact-bw-land-neighbours" ], "review": { "state": "draft", @@ -1063,7 +1183,7 @@ "letter_bank" ], "factIds": [ - "fact-br-m49-region" + "fact-br-land-neighbours" ], "review": { "state": "draft", @@ -1107,7 +1227,7 @@ "letter_bank" ], "factIds": [ - "fact-bn-m49-region" + "fact-bn-land-neighbours" ], "review": { "state": "draft", @@ -1149,7 +1269,7 @@ "letter_bank" ], "factIds": [ - "fact-bg-m49-region" + "fact-bg-land-neighbours" ], "review": { "state": "draft", @@ -1191,7 +1311,7 @@ "letter_bank" ], "factIds": [ - "fact-bf-m49-region" + "fact-bf-land-neighbours" ], "review": { "state": "draft", @@ -1233,7 +1353,7 @@ "letter_bank" ], "factIds": [ - "fact-bi-m49-region" + "fact-bi-land-neighbours" ], "review": { "state": "draft", @@ -1275,7 +1395,7 @@ "letter_bank" ], "factIds": [ - "fact-kh-m49-region" + "fact-kh-land-neighbours" ], "review": { "state": "draft", @@ -1317,7 +1437,7 @@ "letter_bank" ], "factIds": [ - "fact-cm-m49-region" + "fact-cm-land-neighbours" ], "review": { "state": "draft", @@ -1359,7 +1479,7 @@ "letter_bank" ], "factIds": [ - "fact-ca-m49-region" + "fact-ca-land-neighbours" ], "review": { "state": "draft", @@ -1401,7 +1521,7 @@ "letter_bank" ], "factIds": [ - "fact-cv-m49-region" + "fact-cv-land-neighbours" ], "review": { "state": "draft", @@ -1443,7 +1563,7 @@ "letter_bank" ], "factIds": [ - "fact-cf-m49-region" + "fact-cf-land-neighbours" ], "review": { "state": "draft", @@ -1485,7 +1605,7 @@ "letter_bank" ], "factIds": [ - "fact-td-m49-region" + "fact-td-land-neighbours" ], "review": { "state": "draft", @@ -1527,7 +1647,7 @@ "letter_bank" ], "factIds": [ - "fact-cl-m49-region" + "fact-cl-land-neighbours" ], "review": { "state": "draft", @@ -1569,7 +1689,7 @@ "letter_bank" ], "factIds": [ - "fact-cn-m49-region" + "fact-cn-land-neighbours" ], "review": { "state": "draft", @@ -1611,7 +1731,7 @@ "letter_bank" ], "factIds": [ - "fact-co-m49-region" + "fact-co-land-neighbours" ], "review": { "state": "draft", @@ -1653,7 +1773,7 @@ "letter_bank" ], "factIds": [ - "fact-km-m49-region" + "fact-km-land-neighbours" ], "review": { "state": "draft", @@ -1695,7 +1815,7 @@ "letter_bank" ], "factIds": [ - "fact-cr-m49-region" + "fact-cr-land-neighbours" ], "review": { "state": "draft", @@ -1737,7 +1857,7 @@ "letter_bank" ], "factIds": [ - "fact-hr-m49-region" + "fact-hr-land-neighbours" ], "review": { "state": "draft", @@ -1779,7 +1899,7 @@ "letter_bank" ], "factIds": [ - "fact-cu-m49-region" + "fact-cu-land-neighbours" ], "review": { "state": "draft", @@ -1821,7 +1941,7 @@ "letter_bank" ], "factIds": [ - "fact-cy-m49-region" + "fact-cy-land-neighbours" ], "review": { "state": "draft", @@ -1865,7 +1985,7 @@ "letter_bank" ], "factIds": [ - "fact-cz-m49-region" + "fact-cz-land-neighbours" ], "review": { "state": "draft", @@ -1911,7 +2031,7 @@ "letter_bank" ], "factIds": [ - "fact-ci-m49-region" + "fact-ci-land-neighbours" ], "review": { "state": "draft", @@ -1957,7 +2077,7 @@ "letter_bank" ], "factIds": [ - "fact-cd-m49-region" + "fact-cd-land-neighbours" ], "review": { "state": "draft", @@ -1999,7 +2119,7 @@ "letter_bank" ], "factIds": [ - "fact-dk-m49-region" + "fact-dk-land-neighbours" ], "review": { "state": "draft", @@ -2041,7 +2161,7 @@ "letter_bank" ], "factIds": [ - "fact-dj-m49-region" + "fact-dj-land-neighbours" ], "review": { "state": "draft", @@ -2083,7 +2203,7 @@ "letter_bank" ], "factIds": [ - "fact-dm-m49-region" + "fact-dm-land-neighbours" ], "review": { "state": "draft", @@ -2125,7 +2245,7 @@ "letter_bank" ], "factIds": [ - "fact-do-m49-region" + "fact-do-land-neighbours" ], "review": { "state": "draft", @@ -2167,7 +2287,7 @@ "letter_bank" ], "factIds": [ - "fact-ec-m49-region" + "fact-ec-land-neighbours" ], "review": { "state": "draft", @@ -2209,7 +2329,7 @@ "letter_bank" ], "factIds": [ - "fact-eg-m49-region" + "fact-eg-land-neighbours" ], "review": { "state": "draft", @@ -2251,7 +2371,7 @@ "letter_bank" ], "factIds": [ - "fact-sv-m49-region" + "fact-sv-land-neighbours" ], "review": { "state": "draft", @@ -2293,7 +2413,7 @@ "letter_bank" ], "factIds": [ - "fact-gq-m49-region" + "fact-gq-land-neighbours" ], "review": { "state": "draft", @@ -2335,7 +2455,7 @@ "letter_bank" ], "factIds": [ - "fact-er-m49-region" + "fact-er-land-neighbours" ], "review": { "state": "draft", @@ -2377,7 +2497,7 @@ "letter_bank" ], "factIds": [ - "fact-ee-m49-region" + "fact-ee-land-neighbours" ], "review": { "state": "draft", @@ -2420,7 +2540,7 @@ "letter_bank" ], "factIds": [ - "fact-sz-m49-region" + "fact-sz-land-neighbours" ], "review": { "state": "draft", @@ -2462,7 +2582,7 @@ "letter_bank" ], "factIds": [ - "fact-et-m49-region" + "fact-et-land-neighbours" ], "review": { "state": "draft", @@ -2504,7 +2624,7 @@ "letter_bank" ], "factIds": [ - "fact-fj-m49-region" + "fact-fj-land-neighbours" ], "review": { "state": "draft", @@ -2546,7 +2666,7 @@ "letter_bank" ], "factIds": [ - "fact-fi-m49-region" + "fact-fi-land-neighbours" ], "review": { "state": "draft", @@ -2588,7 +2708,7 @@ "letter_bank" ], "factIds": [ - "fact-fr-m49-region" + "fact-fr-land-neighbours" ], "review": { "state": "draft", @@ -2630,7 +2750,7 @@ "letter_bank" ], "factIds": [ - "fact-ga-m49-region" + "fact-ga-land-neighbours" ], "review": { "state": "draft", @@ -2674,7 +2794,7 @@ "letter_bank" ], "factIds": [ - "fact-gm-m49-region" + "fact-gm-land-neighbours" ], "review": { "state": "draft", @@ -2716,7 +2836,7 @@ "letter_bank" ], "factIds": [ - "fact-ge-m49-region" + "fact-ge-land-neighbours" ], "review": { "state": "draft", @@ -2758,7 +2878,7 @@ "letter_bank" ], "factIds": [ - "fact-de-m49-region" + "fact-de-land-neighbours" ], "review": { "state": "draft", @@ -2800,7 +2920,7 @@ "letter_bank" ], "factIds": [ - "fact-gh-m49-region" + "fact-gh-land-neighbours" ], "review": { "state": "draft", @@ -2842,7 +2962,7 @@ "letter_bank" ], "factIds": [ - "fact-gr-m49-region" + "fact-gr-land-neighbours" ], "review": { "state": "draft", @@ -2884,7 +3004,7 @@ "letter_bank" ], "factIds": [ - "fact-gd-m49-region" + "fact-gd-land-neighbours" ], "review": { "state": "draft", @@ -2926,7 +3046,7 @@ "letter_bank" ], "factIds": [ - "fact-gt-m49-region" + "fact-gt-land-neighbours" ], "review": { "state": "draft", @@ -2968,7 +3088,7 @@ "letter_bank" ], "factIds": [ - "fact-gn-m49-region" + "fact-gn-land-neighbours" ], "review": { "state": "draft", @@ -3010,7 +3130,7 @@ "letter_bank" ], "factIds": [ - "fact-gw-m49-region" + "fact-gw-land-neighbours" ], "review": { "state": "draft", @@ -3052,7 +3172,7 @@ "letter_bank" ], "factIds": [ - "fact-gy-m49-region" + "fact-gy-land-neighbours" ], "review": { "state": "draft", @@ -3094,7 +3214,7 @@ "letter_bank" ], "factIds": [ - "fact-ht-m49-region" + "fact-ht-land-neighbours" ], "review": { "state": "draft", @@ -3136,7 +3256,7 @@ "letter_bank" ], "factIds": [ - "fact-hn-m49-region" + "fact-hn-land-neighbours" ], "review": { "state": "draft", @@ -3178,7 +3298,7 @@ "letter_bank" ], "factIds": [ - "fact-hu-m49-region" + "fact-hu-land-neighbours" ], "review": { "state": "draft", @@ -3220,7 +3340,7 @@ "letter_bank" ], "factIds": [ - "fact-is-m49-region" + "fact-is-land-neighbours" ], "review": { "state": "draft", @@ -3262,7 +3382,7 @@ "letter_bank" ], "factIds": [ - "fact-in-m49-region" + "fact-in-land-neighbours" ], "review": { "state": "draft", @@ -3305,7 +3425,7 @@ "letter_bank" ], "factIds": [ - "fact-id-m49-region" + "fact-id-land-neighbours" ], "review": { "state": "draft", @@ -3349,7 +3469,7 @@ "letter_bank" ], "factIds": [ - "fact-ir-m49-region" + "fact-ir-land-neighbours" ], "review": { "state": "draft", @@ -3391,7 +3511,7 @@ "letter_bank" ], "factIds": [ - "fact-iq-m49-region" + "fact-iq-land-neighbours" ], "review": { "state": "draft", @@ -3433,7 +3553,7 @@ "letter_bank" ], "factIds": [ - "fact-ie-m49-region" + "fact-ie-land-neighbours" ], "review": { "state": "draft", @@ -3475,7 +3595,7 @@ "letter_bank" ], "factIds": [ - "fact-il-m49-region" + "fact-il-land-neighbours" ], "review": { "state": "draft", @@ -3517,7 +3637,7 @@ "letter_bank" ], "factIds": [ - "fact-it-m49-region" + "fact-it-land-neighbours" ], "review": { "state": "draft", @@ -3559,7 +3679,7 @@ "letter_bank" ], "factIds": [ - "fact-jm-m49-region" + "fact-jm-land-neighbours" ], "review": { "state": "draft", @@ -3601,7 +3721,7 @@ "letter_bank" ], "factIds": [ - "fact-jp-m49-region" + "fact-jp-land-neighbours" ], "review": { "state": "draft", @@ -3643,7 +3763,7 @@ "letter_bank" ], "factIds": [ - "fact-jo-m49-region" + "fact-jo-land-neighbours" ], "review": { "state": "draft", @@ -3685,7 +3805,7 @@ "letter_bank" ], "factIds": [ - "fact-kz-m49-region" + "fact-kz-land-neighbours" ], "review": { "state": "draft", @@ -3727,7 +3847,7 @@ "letter_bank" ], "factIds": [ - "fact-ke-m49-region" + "fact-ke-land-neighbours" ], "review": { "state": "draft", @@ -3769,7 +3889,7 @@ "letter_bank" ], "factIds": [ - "fact-ki-m49-region" + "fact-ki-land-neighbours" ], "review": { "state": "draft", @@ -3811,7 +3931,7 @@ "letter_bank" ], "factIds": [ - "fact-kw-m49-region" + "fact-kw-land-neighbours" ], "review": { "state": "draft", @@ -3853,7 +3973,7 @@ "letter_bank" ], "factIds": [ - "fact-kg-m49-region" + "fact-kg-land-neighbours" ], "review": { "state": "draft", @@ -3897,7 +4017,7 @@ "letter_bank" ], "factIds": [ - "fact-la-m49-region" + "fact-la-land-neighbours" ], "review": { "state": "draft", @@ -3939,7 +4059,7 @@ "letter_bank" ], "factIds": [ - "fact-lv-m49-region" + "fact-lv-land-neighbours" ], "review": { "state": "draft", @@ -3981,7 +4101,7 @@ "letter_bank" ], "factIds": [ - "fact-lb-m49-region" + "fact-lb-land-neighbours" ], "review": { "state": "draft", @@ -4023,7 +4143,7 @@ "letter_bank" ], "factIds": [ - "fact-ls-m49-region" + "fact-ls-land-neighbours" ], "review": { "state": "draft", @@ -4065,7 +4185,7 @@ "letter_bank" ], "factIds": [ - "fact-lr-m49-region" + "fact-lr-land-neighbours" ], "review": { "state": "draft", @@ -4107,7 +4227,7 @@ "letter_bank" ], "factIds": [ - "fact-ly-m49-region" + "fact-ly-land-neighbours" ], "review": { "state": "draft", @@ -4149,7 +4269,7 @@ "letter_bank" ], "factIds": [ - "fact-li-m49-region" + "fact-li-land-neighbours" ], "review": { "state": "draft", @@ -4191,7 +4311,7 @@ "letter_bank" ], "factIds": [ - "fact-lt-m49-region" + "fact-lt-land-neighbours" ], "review": { "state": "draft", @@ -4233,7 +4353,7 @@ "letter_bank" ], "factIds": [ - "fact-lu-m49-region" + "fact-lu-land-neighbours" ], "review": { "state": "draft", @@ -4275,7 +4395,7 @@ "letter_bank" ], "factIds": [ - "fact-mg-m49-region" + "fact-mg-land-neighbours" ], "review": { "state": "draft", @@ -4317,7 +4437,7 @@ "letter_bank" ], "factIds": [ - "fact-mw-m49-region" + "fact-mw-land-neighbours" ], "review": { "state": "draft", @@ -4360,7 +4480,7 @@ "letter_bank" ], "factIds": [ - "fact-my-m49-region" + "fact-my-land-neighbours" ], "review": { "state": "draft", @@ -4402,7 +4522,7 @@ "letter_bank" ], "factIds": [ - "fact-mv-m49-region" + "fact-mv-land-neighbours" ], "review": { "state": "draft", @@ -4444,7 +4564,7 @@ "letter_bank" ], "factIds": [ - "fact-ml-m49-region" + "fact-ml-land-neighbours" ], "review": { "state": "draft", @@ -4486,7 +4606,7 @@ "letter_bank" ], "factIds": [ - "fact-mt-m49-region" + "fact-mt-land-neighbours" ], "review": { "state": "draft", @@ -4528,7 +4648,7 @@ "letter_bank" ], "factIds": [ - "fact-mh-m49-region" + "fact-mh-land-neighbours" ], "review": { "state": "draft", @@ -4570,7 +4690,7 @@ "letter_bank" ], "factIds": [ - "fact-mr-m49-region" + "fact-mr-land-neighbours" ], "review": { "state": "draft", @@ -4612,7 +4732,7 @@ "letter_bank" ], "factIds": [ - "fact-mu-m49-region" + "fact-mu-land-neighbours" ], "review": { "state": "draft", @@ -4654,7 +4774,7 @@ "letter_bank" ], "factIds": [ - "fact-mx-m49-region" + "fact-mx-land-neighbours" ], "review": { "state": "draft", @@ -4698,7 +4818,7 @@ "letter_bank" ], "factIds": [ - "fact-fm-m49-region" + "fact-fm-land-neighbours" ], "review": { "state": "draft", @@ -4742,7 +4862,7 @@ "letter_bank" ], "factIds": [ - "fact-md-m49-region" + "fact-md-land-neighbours" ], "review": { "state": "draft", @@ -4784,7 +4904,7 @@ "letter_bank" ], "factIds": [ - "fact-mc-m49-region" + "fact-mc-land-neighbours" ], "review": { "state": "draft", @@ -4826,7 +4946,7 @@ "letter_bank" ], "factIds": [ - "fact-mn-m49-region" + "fact-mn-land-neighbours" ], "review": { "state": "draft", @@ -4868,7 +4988,7 @@ "letter_bank" ], "factIds": [ - "fact-me-m49-region" + "fact-me-land-neighbours" ], "review": { "state": "draft", @@ -4910,7 +5030,7 @@ "letter_bank" ], "factIds": [ - "fact-ma-m49-region" + "fact-ma-land-neighbours" ], "review": { "state": "draft", @@ -4952,7 +5072,7 @@ "letter_bank" ], "factIds": [ - "fact-mz-m49-region" + "fact-mz-land-neighbours" ], "review": { "state": "draft", @@ -4994,7 +5114,7 @@ "letter_bank" ], "factIds": [ - "fact-mm-m49-region" + "fact-mm-land-neighbours" ], "review": { "state": "draft", @@ -5036,7 +5156,7 @@ "letter_bank" ], "factIds": [ - "fact-na-m49-region" + "fact-na-land-neighbours" ], "review": { "state": "draft", @@ -5078,7 +5198,7 @@ "letter_bank" ], "factIds": [ - "fact-nr-m49-region" + "fact-nr-land-neighbours" ], "review": { "state": "draft", @@ -5120,7 +5240,7 @@ "letter_bank" ], "factIds": [ - "fact-np-m49-region" + "fact-np-land-neighbours" ], "review": { "state": "draft", @@ -5163,7 +5283,7 @@ "letter_bank" ], "factIds": [ - "fact-nl-m49-region" + "fact-nl-land-neighbours" ], "review": { "state": "draft", @@ -5205,7 +5325,7 @@ "letter_bank" ], "factIds": [ - "fact-nz-m49-region" + "fact-nz-land-neighbours" ], "review": { "state": "draft", @@ -5247,7 +5367,7 @@ "letter_bank" ], "factIds": [ - "fact-ni-m49-region" + "fact-ni-land-neighbours" ], "review": { "state": "draft", @@ -5289,7 +5409,7 @@ "letter_bank" ], "factIds": [ - "fact-ne-m49-region" + "fact-ne-land-neighbours" ], "review": { "state": "draft", @@ -5331,7 +5451,7 @@ "letter_bank" ], "factIds": [ - "fact-ng-m49-region" + "fact-ng-land-neighbours" ], "review": { "state": "draft", @@ -5375,7 +5495,7 @@ "letter_bank" ], "factIds": [ - "fact-kp-m49-region" + "fact-kp-land-neighbours" ], "review": { "state": "draft", @@ -5417,7 +5537,7 @@ "letter_bank" ], "factIds": [ - "fact-mk-m49-region" + "fact-mk-land-neighbours" ], "review": { "state": "draft", @@ -5459,7 +5579,7 @@ "letter_bank" ], "factIds": [ - "fact-no-m49-region" + "fact-no-land-neighbours" ], "review": { "state": "draft", @@ -5501,7 +5621,7 @@ "letter_bank" ], "factIds": [ - "fact-om-m49-region" + "fact-om-musandam" ], "review": { "state": "draft", @@ -5543,7 +5663,7 @@ "letter_bank" ], "factIds": [ - "fact-pk-m49-region" + "fact-pk-land-neighbours" ], "review": { "state": "draft", @@ -5585,7 +5705,7 @@ "letter_bank" ], "factIds": [ - "fact-pw-m49-region" + "fact-pw-land-neighbours" ], "review": { "state": "draft", @@ -5630,7 +5750,7 @@ "letter_bank" ], "factIds": [ - "fact-ps-m49-region" + "fact-ps-land-neighbours" ], "review": { "state": "draft", @@ -5672,7 +5792,7 @@ "letter_bank" ], "factIds": [ - "fact-pa-m49-region" + "fact-pa-land-neighbours" ], "review": { "state": "draft", @@ -5714,7 +5834,7 @@ "letter_bank" ], "factIds": [ - "fact-pg-m49-region" + "fact-pg-land-neighbours" ], "review": { "state": "draft", @@ -5756,7 +5876,7 @@ "letter_bank" ], "factIds": [ - "fact-py-m49-region" + "fact-py-land-neighbours" ], "review": { "state": "draft", @@ -5798,7 +5918,7 @@ "letter_bank" ], "factIds": [ - "fact-pe-m49-region" + "fact-pe-land-neighbours" ], "review": { "state": "draft", @@ -5840,7 +5960,7 @@ "letter_bank" ], "factIds": [ - "fact-ph-m49-region" + "fact-ph-islands" ], "review": { "state": "draft", @@ -5882,7 +6002,7 @@ "letter_bank" ], "factIds": [ - "fact-pl-m49-region" + "fact-pl-land-neighbours" ], "review": { "state": "draft", @@ -5924,7 +6044,7 @@ "letter_bank" ], "factIds": [ - "fact-pt-m49-region" + "fact-pt-land-neighbours" ], "review": { "state": "draft", @@ -5966,7 +6086,7 @@ "letter_bank" ], "factIds": [ - "fact-qa-m49-region" + "fact-qa-land-neighbours" ], "review": { "state": "draft", @@ -6012,7 +6132,7 @@ "letter_bank" ], "factIds": [ - "fact-cg-m49-region" + "fact-cg-land-neighbours" ], "review": { "state": "draft", @@ -6054,7 +6174,7 @@ "letter_bank" ], "factIds": [ - "fact-ro-m49-region" + "fact-ro-land-neighbours" ], "review": { "state": "draft", @@ -6098,7 +6218,7 @@ "letter_bank" ], "factIds": [ - "fact-ru-m49-region" + "fact-ru-land-neighbours" ], "review": { "state": "draft", @@ -6140,7 +6260,7 @@ "letter_bank" ], "factIds": [ - "fact-rw-m49-region" + "fact-rw-land-neighbours" ], "review": { "state": "draft", @@ -6184,7 +6304,7 @@ "letter_bank" ], "factIds": [ - "fact-kn-m49-region" + "fact-kn-land-neighbours" ], "review": { "state": "draft", @@ -6228,7 +6348,7 @@ "letter_bank" ], "factIds": [ - "fact-lc-m49-region" + "fact-lc-land-neighbours" ], "review": { "state": "draft", @@ -6272,7 +6392,7 @@ "letter_bank" ], "factIds": [ - "fact-vc-m49-region" + "fact-vc-land-neighbours" ], "review": { "state": "draft", @@ -6314,7 +6434,7 @@ "letter_bank" ], "factIds": [ - "fact-ws-m49-region" + "fact-ws-land-neighbours" ], "review": { "state": "draft", @@ -6356,7 +6476,7 @@ "letter_bank" ], "factIds": [ - "fact-sm-m49-region" + "fact-sm-land-neighbours" ], "review": { "state": "draft", @@ -6398,7 +6518,7 @@ "letter_bank" ], "factIds": [ - "fact-sa-m49-region" + "fact-sa-land-neighbours" ], "review": { "state": "draft", @@ -6440,7 +6560,7 @@ "letter_bank" ], "factIds": [ - "fact-sn-m49-region" + "fact-sn-land-neighbours" ], "review": { "state": "draft", @@ -6482,7 +6602,7 @@ "letter_bank" ], "factIds": [ - "fact-rs-m49-region" + "fact-rs-land-neighbours" ], "review": { "state": "draft", @@ -6524,7 +6644,7 @@ "letter_bank" ], "factIds": [ - "fact-sc-m49-region" + "fact-sc-land-neighbours" ], "review": { "state": "draft", @@ -6566,7 +6686,7 @@ "letter_bank" ], "factIds": [ - "fact-sl-m49-region" + "fact-sl-land-neighbours" ], "review": { "state": "draft", @@ -6608,7 +6728,7 @@ "letter_bank" ], "factIds": [ - "fact-sg-m49-region" + "fact-sg-land-neighbours" ], "review": { "state": "draft", @@ -6650,7 +6770,7 @@ "letter_bank" ], "factIds": [ - "fact-sk-m49-region" + "fact-sk-land-neighbours" ], "review": { "state": "draft", @@ -6692,7 +6812,7 @@ "letter_bank" ], "factIds": [ - "fact-si-m49-region" + "fact-si-land-neighbours" ], "review": { "state": "draft", @@ -6734,7 +6854,7 @@ "letter_bank" ], "factIds": [ - "fact-sb-m49-region" + "fact-sb-land-neighbours" ], "review": { "state": "draft", @@ -6776,7 +6896,7 @@ "letter_bank" ], "factIds": [ - "fact-so-m49-region" + "fact-so-land-neighbours" ], "review": { "state": "draft", @@ -6820,7 +6940,7 @@ "letter_bank" ], "factIds": [ - "fact-za-m49-region" + "fact-za-land-neighbours" ], "review": { "state": "draft", @@ -6864,7 +6984,7 @@ "letter_bank" ], "factIds": [ - "fact-kr-m49-region" + "fact-kr-land-neighbours" ], "review": { "state": "draft", @@ -6906,7 +7026,7 @@ "letter_bank" ], "factIds": [ - "fact-ss-m49-region" + "fact-ss-land-neighbours" ], "review": { "state": "draft", @@ -6948,7 +7068,7 @@ "letter_bank" ], "factIds": [ - "fact-es-m49-region" + "fact-es-land-neighbours" ], "review": { "state": "draft", @@ -6991,7 +7111,7 @@ "letter_bank" ], "factIds": [ - "fact-lk-m49-region" + "fact-lk-land-neighbours" ], "review": { "state": "draft", @@ -7033,7 +7153,7 @@ "letter_bank" ], "factIds": [ - "fact-sd-m49-region" + "fact-sd-land-neighbours" ], "review": { "state": "draft", @@ -7075,7 +7195,7 @@ "letter_bank" ], "factIds": [ - "fact-sr-m49-region" + "fact-sr-land-neighbours" ], "review": { "state": "draft", @@ -7117,7 +7237,7 @@ "letter_bank" ], "factIds": [ - "fact-se-m49-region" + "fact-se-land-neighbours" ], "review": { "state": "draft", @@ -7159,7 +7279,7 @@ "letter_bank" ], "factIds": [ - "fact-ch-m49-region" + "fact-ch-land-neighbours" ], "review": { "state": "draft", @@ -7203,7 +7323,7 @@ "letter_bank" ], "factIds": [ - "fact-sy-m49-region" + "fact-sy-land-neighbours" ], "review": { "state": "draft", @@ -7247,7 +7367,7 @@ "letter_bank" ], "factIds": [ - "fact-st-m49-region" + "fact-st-land-neighbours" ], "review": { "state": "draft", @@ -7289,7 +7409,7 @@ "letter_bank" ], "factIds": [ - "fact-tj-m49-region" + "fact-tj-land-neighbours" ], "review": { "state": "draft", @@ -7333,7 +7453,7 @@ "letter_bank" ], "factIds": [ - "fact-tz-m49-region" + "fact-tz-land-neighbours" ], "review": { "state": "draft", @@ -7375,7 +7495,7 @@ "letter_bank" ], "factIds": [ - "fact-th-m49-region" + "fact-th-land-neighbours" ], "review": { "state": "draft", @@ -7417,7 +7537,7 @@ "letter_bank" ], "factIds": [ - "fact-tl-m49-region" + "fact-tl-land-neighbours" ], "review": { "state": "draft", @@ -7459,7 +7579,7 @@ "letter_bank" ], "factIds": [ - "fact-tg-m49-region" + "fact-tg-land-neighbours" ], "review": { "state": "draft", @@ -7501,7 +7621,7 @@ "letter_bank" ], "factIds": [ - "fact-to-m49-region" + "fact-to-land-neighbours" ], "review": { "state": "draft", @@ -7545,7 +7665,7 @@ "letter_bank" ], "factIds": [ - "fact-tt-m49-region" + "fact-tt-land-neighbours" ], "review": { "state": "draft", @@ -7587,7 +7707,7 @@ "letter_bank" ], "factIds": [ - "fact-tn-m49-region" + "fact-tn-land-neighbours" ], "review": { "state": "draft", @@ -7629,7 +7749,7 @@ "letter_bank" ], "factIds": [ - "fact-tm-m49-region" + "fact-tm-land-neighbours" ], "review": { "state": "draft", @@ -7671,7 +7791,7 @@ "letter_bank" ], "factIds": [ - "fact-tv-m49-region" + "fact-tv-land-neighbours" ], "review": { "state": "draft", @@ -7713,7 +7833,7 @@ "letter_bank" ], "factIds": [ - "fact-tr-m49-region" + "fact-tr-land-neighbours" ], "review": { "state": "draft", @@ -7755,7 +7875,7 @@ "letter_bank" ], "factIds": [ - "fact-ug-m49-region" + "fact-ug-land-neighbours" ], "review": { "state": "draft", @@ -7797,7 +7917,7 @@ "letter_bank" ], "factIds": [ - "fact-ua-m49-region" + "fact-ua-land-neighbours" ], "review": { "state": "draft", @@ -7839,7 +7959,7 @@ "letter_bank" ], "factIds": [ - "fact-ae-m49-region" + "fact-ae-land-neighbours" ], "review": { "state": "draft", @@ -7884,7 +8004,7 @@ "letter_bank" ], "factIds": [ - "fact-gb-m49-region" + "fact-gb-land-neighbours" ], "review": { "state": "draft", @@ -7930,7 +8050,7 @@ "letter_bank" ], "factIds": [ - "fact-us-m49-region" + "fact-us-land-neighbours" ], "review": { "state": "draft", @@ -7972,7 +8092,7 @@ "letter_bank" ], "factIds": [ - "fact-uy-m49-region" + "fact-uy-land-neighbours" ], "review": { "state": "draft", @@ -8014,7 +8134,7 @@ "letter_bank" ], "factIds": [ - "fact-uz-m49-region" + "fact-uz-land-neighbours" ], "review": { "state": "draft", @@ -8056,7 +8176,7 @@ "letter_bank" ], "factIds": [ - "fact-vu-m49-region" + "fact-vu-land-neighbours" ], "review": { "state": "draft", @@ -8100,7 +8220,7 @@ "letter_bank" ], "factIds": [ - "fact-va-m49-region" + "fact-va-land-neighbours" ], "review": { "state": "draft", @@ -8144,7 +8264,7 @@ "letter_bank" ], "factIds": [ - "fact-ve-m49-region" + "fact-ve-land-neighbours" ], "review": { "state": "draft", @@ -8188,7 +8308,7 @@ "letter_bank" ], "factIds": [ - "fact-vn-m49-region" + "fact-vn-land-neighbours" ], "review": { "state": "draft", @@ -8230,7 +8350,7 @@ "letter_bank" ], "factIds": [ - "fact-ye-m49-region" + "fact-ye-land-neighbours" ], "review": { "state": "draft", @@ -8272,7 +8392,7 @@ "letter_bank" ], "factIds": [ - "fact-zm-m49-region" + "fact-zm-land-neighbours" ], "review": { "state": "draft", @@ -8314,7 +8434,7 @@ "letter_bank" ], "factIds": [ - "fact-zw-m49-region" + "fact-zw-land-neighbours" ], "review": { "state": "draft", @@ -8325,17 +8445,17 @@ ], "facts": [ { - "id": "fact-af-m49-region", + "id": "fact-af-land-neighbours", "schemaVersion": 1, "countryIds": [ "af" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Afghanistan", - "text": "The UN M49 standard groups Afghanistan in Southern Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Afghanistan is landlocked, yet it touches six other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8345,17 +8465,17 @@ } }, { - "id": "fact-al-m49-region", + "id": "fact-al-land-neighbours", "schemaVersion": 1, "countryIds": [ "al" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Albania", - "text": "The UN M49 standard groups Albania in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Albania shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8365,17 +8485,17 @@ } }, { - "id": "fact-dz-m49-region", + "id": "fact-dz-land-neighbours", "schemaVersion": 1, "countryIds": [ "dz" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Algeria", - "text": "The UN M49 standard groups Algeria in Northern Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Algeria shares land borders with seven countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8385,17 +8505,17 @@ } }, { - "id": "fact-ad-m49-region", + "id": "fact-ad-land-neighbours", "schemaVersion": 1, "countryIds": [ "ad" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Andorra", - "text": "The UN M49 standard groups Andorra in Southern Europe.", + "heading": "Landlocked, not isolated", + "text": "Andorra is landlocked, yet it touches two other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8405,17 +8525,17 @@ } }, { - "id": "fact-ao-m49-region", + "id": "fact-ao-land-neighbours", "schemaVersion": 1, "countryIds": [ "ao" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Angola", - "text": "The UN M49 standard groups Angola in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Angola shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8425,17 +8545,17 @@ } }, { - "id": "fact-ag-m49-region", + "id": "fact-ag-land-neighbours", "schemaVersion": 1, "countryIds": [ "ag" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Antigua and Barbuda", - "text": "The UN M49 standard groups Antigua and Barbuda in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Antigua and Barbuda shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8445,17 +8565,17 @@ } }, { - "id": "fact-ar-m49-region", + "id": "fact-ar-land-neighbours", "schemaVersion": 1, "countryIds": [ "ar" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Argentina", - "text": "The UN M49 standard groups Argentina in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Argentina shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8465,17 +8585,17 @@ } }, { - "id": "fact-am-m49-region", + "id": "fact-am-land-neighbours", "schemaVersion": 1, "countryIds": [ "am" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Armenia", - "text": "The UN M49 standard groups Armenia in Western Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Armenia is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8485,17 +8605,17 @@ } }, { - "id": "fact-au-m49-region", + "id": "fact-au-land-neighbours", "schemaVersion": 1, "countryIds": [ "au" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Australia", - "text": "The UN M49 standard groups Australia in Australia and New Zealand.", + "heading": "A continent of its own", + "text": "Australia is both a country and the smallest continental landmass.", "sourceIds": [ - "source-un-m49" + "source-geoscience-australia" ], "asOf": null, "review": { @@ -8505,17 +8625,17 @@ } }, { - "id": "fact-at-m49-region", + "id": "fact-at-land-neighbours", "schemaVersion": 1, "countryIds": [ "at" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Austria", - "text": "The UN M49 standard groups Austria in Western Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Austria is landlocked, yet it touches eight other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8525,17 +8645,17 @@ } }, { - "id": "fact-az-m49-region", + "id": "fact-az-land-neighbours", "schemaVersion": 1, "countryIds": [ "az" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Azerbaijan", - "text": "The UN M49 standard groups Azerbaijan in Western Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Azerbaijan is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8545,17 +8665,17 @@ } }, { - "id": "fact-bs-m49-region", + "id": "fact-bs-land-neighbours", "schemaVersion": 1, "countryIds": [ "bs" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Bahamas", - "text": "The UN M49 standard groups Bahamas in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Bahamas shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8565,17 +8685,17 @@ } }, { - "id": "fact-bh-m49-region", + "id": "fact-bh-land-neighbours", "schemaVersion": 1, "countryIds": [ "bh" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Bahrain", - "text": "The UN M49 standard groups Bahrain in Western Asia.", + "heading": "No neighbours by land", + "text": "Bahrain shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8585,17 +8705,17 @@ } }, { - "id": "fact-bd-m49-region", + "id": "fact-bd-land-neighbours", "schemaVersion": 1, "countryIds": [ "bd" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Bangladesh", - "text": "The UN M49 standard groups Bangladesh in Southern Asia.", + "heading": "Across the border", + "text": "Bangladesh shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8605,17 +8725,17 @@ } }, { - "id": "fact-bb-m49-region", + "id": "fact-bb-land-neighbours", "schemaVersion": 1, "countryIds": [ "bb" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Barbados", - "text": "The UN M49 standard groups Barbados in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Barbados shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8625,17 +8745,17 @@ } }, { - "id": "fact-by-m49-region", + "id": "fact-by-land-neighbours", "schemaVersion": 1, "countryIds": [ "by" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Belarus", - "text": "The UN M49 standard groups Belarus in Eastern Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Belarus is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8645,17 +8765,17 @@ } }, { - "id": "fact-be-m49-region", + "id": "fact-be-land-neighbours", "schemaVersion": 1, "countryIds": [ "be" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Belgium", - "text": "The UN M49 standard groups Belgium in Western Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Belgium shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8665,17 +8785,17 @@ } }, { - "id": "fact-bz-m49-region", + "id": "fact-bz-land-neighbours", "schemaVersion": 1, "countryIds": [ "bz" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Belize", - "text": "The UN M49 standard groups Belize in Latin America and the Caribbean.", + "heading": "Across the border", + "text": "Belize shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8685,17 +8805,17 @@ } }, { - "id": "fact-bj-m49-region", + "id": "fact-bj-land-neighbours", "schemaVersion": 1, "countryIds": [ "bj" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Benin", - "text": "The UN M49 standard groups Benin in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Benin shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8705,17 +8825,17 @@ } }, { - "id": "fact-bt-m49-region", + "id": "fact-bt-land-neighbours", "schemaVersion": 1, "countryIds": [ "bt" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Bhutan", - "text": "The UN M49 standard groups Bhutan in Southern Asia.", + "heading": "Landlocked, not isolated", + "text": "Bhutan is landlocked, yet it touches two other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8725,17 +8845,17 @@ } }, { - "id": "fact-bo-m49-region", + "id": "fact-bo-land-neighbours", "schemaVersion": 1, "countryIds": [ "bo" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Bolivia", - "text": "The UN M49 standard groups Bolivia in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Bolivia is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8745,17 +8865,17 @@ } }, { - "id": "fact-ba-m49-region", + "id": "fact-ba-land-neighbours", "schemaVersion": 1, "countryIds": [ "ba" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Bosnia and Herzegovina", - "text": "The UN M49 standard groups Bosnia and Herzegovina in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Bosnia and Herzegovina shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8765,17 +8885,17 @@ } }, { - "id": "fact-bw-m49-region", + "id": "fact-bw-land-neighbours", "schemaVersion": 1, "countryIds": [ "bw" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Botswana", - "text": "The UN M49 standard groups Botswana in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Botswana is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8785,17 +8905,17 @@ } }, { - "id": "fact-br-m49-region", + "id": "fact-br-land-neighbours", "schemaVersion": 1, "countryIds": [ "br" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Brazil", - "text": "The UN M49 standard groups Brazil in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Two landmark lines", + "text": "Both the Equator and the Tropic of Capricorn cross Brazil.", "sourceIds": [ - "source-un-m49" + "source-ibge-brazil" ], "asOf": null, "review": { @@ -8805,17 +8925,17 @@ } }, { - "id": "fact-bn-m49-region", + "id": "fact-bn-land-neighbours", "schemaVersion": 1, "countryIds": [ "bn" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Brunei", - "text": "The UN M49 standard groups Brunei in South-eastern Asia.", + "heading": "A short border list", + "text": "Brunei has just one land neighbour: Malaysia.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8825,17 +8945,17 @@ } }, { - "id": "fact-bg-m49-region", + "id": "fact-bg-land-neighbours", "schemaVersion": 1, "countryIds": [ "bg" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Bulgaria", - "text": "The UN M49 standard groups Bulgaria in Eastern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Bulgaria shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8845,17 +8965,17 @@ } }, { - "id": "fact-bf-m49-region", + "id": "fact-bf-land-neighbours", "schemaVersion": 1, "countryIds": [ "bf" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Burkina Faso", - "text": "The UN M49 standard groups Burkina Faso in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Burkina Faso is landlocked, yet it touches six other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8865,17 +8985,17 @@ } }, { - "id": "fact-bi-m49-region", + "id": "fact-bi-land-neighbours", "schemaVersion": 1, "countryIds": [ "bi" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Burundi", - "text": "The UN M49 standard groups Burundi in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Burundi is landlocked, yet it touches three other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8885,17 +9005,17 @@ } }, { - "id": "fact-kh-m49-region", + "id": "fact-kh-land-neighbours", "schemaVersion": 1, "countryIds": [ "kh" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Cambodia", - "text": "The UN M49 standard groups Cambodia in South-eastern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Cambodia shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8905,17 +9025,17 @@ } }, { - "id": "fact-cm-m49-region", + "id": "fact-cm-land-neighbours", "schemaVersion": 1, "countryIds": [ "cm" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Cameroon", - "text": "The UN M49 standard groups Cameroon in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Cameroon shares land borders with six countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8925,17 +9045,17 @@ } }, { - "id": "fact-ca-m49-region", + "id": "fact-ca-land-neighbours", "schemaVersion": 1, "countryIds": [ "ca" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Canada", - "text": "The UN M49 standard groups Canada in Northern America.", + "heading": "Three-ocean shores", + "text": "Canada's coastline reaches the Atlantic, Arctic and Pacific oceans.", "sourceIds": [ - "source-un-m49" + "source-canada-coastlines" ], "asOf": null, "review": { @@ -8945,17 +9065,17 @@ } }, { - "id": "fact-cv-m49-region", + "id": "fact-cv-land-neighbours", "schemaVersion": 1, "countryIds": [ "cv" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Cape Verde", - "text": "The UN M49 standard groups Cape Verde in Sub-Saharan Africa.", + "heading": "No neighbours by land", + "text": "Cape Verde shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8965,17 +9085,17 @@ } }, { - "id": "fact-cf-m49-region", + "id": "fact-cf-land-neighbours", "schemaVersion": 1, "countryIds": [ "cf" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Central African Republic", - "text": "The UN M49 standard groups Central African Republic in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Central African Republic is landlocked, yet it touches six other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -8985,17 +9105,17 @@ } }, { - "id": "fact-td-m49-region", + "id": "fact-td-land-neighbours", "schemaVersion": 1, "countryIds": [ "td" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Chad", - "text": "The UN M49 standard groups Chad in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Chad is landlocked, yet it touches six other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9005,17 +9125,17 @@ } }, { - "id": "fact-cl-m49-region", + "id": "fact-cl-land-neighbours", "schemaVersion": 1, "countryIds": [ "cl" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Chile", - "text": "The UN M49 standard groups Chile in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Chile shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9025,17 +9145,17 @@ } }, { - "id": "fact-cn-m49-region", + "id": "fact-cn-land-neighbours", "schemaVersion": 1, "countryIds": [ "cn" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: China", - "text": "The UN M49 standard groups China in Eastern Asia.", + "difficulty": "tricky", + "heading": "Many ways next door", + "text": "China shares land borders with 16 countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9045,17 +9165,17 @@ } }, { - "id": "fact-co-m49-region", + "id": "fact-co-land-neighbours", "schemaVersion": 1, "countryIds": [ "co" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Colombia", - "text": "The UN M49 standard groups Colombia in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Colombia shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9065,17 +9185,17 @@ } }, { - "id": "fact-km-m49-region", + "id": "fact-km-land-neighbours", "schemaVersion": 1, "countryIds": [ "km" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Comoros", - "text": "The UN M49 standard groups Comoros in Sub-Saharan Africa.", + "heading": "No neighbours by land", + "text": "Comoros shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9085,17 +9205,17 @@ } }, { - "id": "fact-cr-m49-region", + "id": "fact-cr-land-neighbours", "schemaVersion": 1, "countryIds": [ "cr" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Costa Rica", - "text": "The UN M49 standard groups Costa Rica in Latin America and the Caribbean.", + "heading": "Across the border", + "text": "Costa Rica shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9105,17 +9225,17 @@ } }, { - "id": "fact-hr-m49-region", + "id": "fact-hr-land-neighbours", "schemaVersion": 1, "countryIds": [ "hr" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Croatia", - "text": "The UN M49 standard groups Croatia in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Croatia shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9125,17 +9245,17 @@ } }, { - "id": "fact-cu-m49-region", + "id": "fact-cu-land-neighbours", "schemaVersion": 1, "countryIds": [ "cu" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Cuba", - "text": "The UN M49 standard groups Cuba in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Cuba shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9145,17 +9265,17 @@ } }, { - "id": "fact-cy-m49-region", + "id": "fact-cy-land-neighbours", "schemaVersion": 1, "countryIds": [ "cy" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Cyprus", - "text": "The UN M49 standard groups Cyprus in Western Asia.", + "heading": "No neighbours by land", + "text": "Cyprus shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9165,17 +9285,17 @@ } }, { - "id": "fact-cz-m49-region", + "id": "fact-cz-land-neighbours", "schemaVersion": 1, "countryIds": [ "cz" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Czechia", - "text": "The UN M49 standard groups Czechia in Eastern Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Czechia is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9185,17 +9305,17 @@ } }, { - "id": "fact-ci-m49-region", + "id": "fact-ci-land-neighbours", "schemaVersion": 1, "countryIds": [ "ci" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Côte d’Ivoire", - "text": "The UN M49 standard groups Côte d’Ivoire in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Côte d’Ivoire shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9205,17 +9325,17 @@ } }, { - "id": "fact-cd-m49-region", + "id": "fact-cd-land-neighbours", "schemaVersion": 1, "countryIds": [ "cd" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Democratic Republic of the Congo", - "text": "The UN M49 standard groups Democratic Republic of the Congo in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Many ways next door", + "text": "Democratic Republic of the Congo shares land borders with nine countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9225,17 +9345,17 @@ } }, { - "id": "fact-dk-m49-region", + "id": "fact-dk-land-neighbours", "schemaVersion": 1, "countryIds": [ "dk" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Denmark", - "text": "The UN M49 standard groups Denmark in Northern Europe.", + "heading": "A short border list", + "text": "Denmark has just one land neighbour: Germany.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9245,17 +9365,17 @@ } }, { - "id": "fact-dj-m49-region", + "id": "fact-dj-land-neighbours", "schemaVersion": 1, "countryIds": [ "dj" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Djibouti", - "text": "The UN M49 standard groups Djibouti in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Djibouti shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9265,17 +9385,17 @@ } }, { - "id": "fact-dm-m49-region", + "id": "fact-dm-land-neighbours", "schemaVersion": 1, "countryIds": [ "dm" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Dominica", - "text": "The UN M49 standard groups Dominica in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Dominica shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9285,17 +9405,17 @@ } }, { - "id": "fact-do-m49-region", + "id": "fact-do-land-neighbours", "schemaVersion": 1, "countryIds": [ "do" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Dominican Republic", - "text": "The UN M49 standard groups Dominican Republic in Latin America and the Caribbean.", + "heading": "A short border list", + "text": "Dominican Republic has just one land neighbour: Haiti.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9305,17 +9425,17 @@ } }, { - "id": "fact-ec-m49-region", + "id": "fact-ec-land-neighbours", "schemaVersion": 1, "countryIds": [ "ec" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Ecuador", - "text": "The UN M49 standard groups Ecuador in Latin America and the Caribbean.", + "heading": "Across the border", + "text": "Ecuador shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9325,17 +9445,17 @@ } }, { - "id": "fact-eg-m49-region", + "id": "fact-eg-land-neighbours", "schemaVersion": 1, "countryIds": [ "eg" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Egypt", - "text": "The UN M49 standard groups Egypt in Northern Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Egypt shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9345,17 +9465,17 @@ } }, { - "id": "fact-sv-m49-region", + "id": "fact-sv-land-neighbours", "schemaVersion": 1, "countryIds": [ "sv" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: El Salvador", - "text": "The UN M49 standard groups El Salvador in Latin America and the Caribbean.", + "heading": "Across the border", + "text": "El Salvador shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9365,17 +9485,17 @@ } }, { - "id": "fact-gq-m49-region", + "id": "fact-gq-land-neighbours", "schemaVersion": 1, "countryIds": [ "gq" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Equatorial Guinea", - "text": "The UN M49 standard groups Equatorial Guinea in Sub-Saharan Africa.", + "heading": "Across the border", + "text": "Equatorial Guinea shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9385,17 +9505,17 @@ } }, { - "id": "fact-er-m49-region", + "id": "fact-er-land-neighbours", "schemaVersion": 1, "countryIds": [ "er" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Eritrea", - "text": "The UN M49 standard groups Eritrea in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Eritrea shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9405,17 +9525,17 @@ } }, { - "id": "fact-ee-m49-region", + "id": "fact-ee-land-neighbours", "schemaVersion": 1, "countryIds": [ "ee" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Estonia", - "text": "The UN M49 standard groups Estonia in Northern Europe.", + "heading": "Across the border", + "text": "Estonia shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9425,17 +9545,17 @@ } }, { - "id": "fact-sz-m49-region", + "id": "fact-sz-land-neighbours", "schemaVersion": 1, "countryIds": [ "sz" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Eswatini", - "text": "The UN M49 standard groups Eswatini in Sub-Saharan Africa.", + "heading": "Landlocked, not isolated", + "text": "Eswatini is landlocked, yet it touches two other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9445,17 +9565,17 @@ } }, { - "id": "fact-et-m49-region", + "id": "fact-et-land-neighbours", "schemaVersion": 1, "countryIds": [ "et" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Ethiopia", - "text": "The UN M49 standard groups Ethiopia in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Ethiopia is landlocked, yet it touches six other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9465,17 +9585,17 @@ } }, { - "id": "fact-fj-m49-region", + "id": "fact-fj-land-neighbours", "schemaVersion": 1, "countryIds": [ "fj" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Fiji", - "text": "The UN M49 standard groups Fiji in Melanesia.", + "heading": "No neighbours by land", + "text": "Fiji shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9485,17 +9605,17 @@ } }, { - "id": "fact-fi-m49-region", + "id": "fact-fi-land-neighbours", "schemaVersion": 1, "countryIds": [ "fi" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Finland", - "text": "The UN M49 standard groups Finland in Northern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Finland shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9505,17 +9625,17 @@ } }, { - "id": "fact-fr-m49-region", + "id": "fact-fr-land-neighbours", "schemaVersion": 1, "countryIds": [ "fr" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: France", - "text": "The UN M49 standard groups France in Western Europe.", + "difficulty": "tricky", + "heading": "Many ways next door", + "text": "Counting its overseas territory in South America, France shares land borders with eight countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9525,17 +9645,17 @@ } }, { - "id": "fact-ga-m49-region", + "id": "fact-ga-land-neighbours", "schemaVersion": 1, "countryIds": [ "ga" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Gabon", - "text": "The UN M49 standard groups Gabon in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Gabon shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9545,17 +9665,17 @@ } }, { - "id": "fact-gm-m49-region", + "id": "fact-gm-land-neighbours", "schemaVersion": 1, "countryIds": [ "gm" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Gambia", - "text": "The UN M49 standard groups Gambia in Sub-Saharan Africa.", + "heading": "A short border list", + "text": "Gambia has just one land neighbour: Senegal.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9565,17 +9685,17 @@ } }, { - "id": "fact-ge-m49-region", + "id": "fact-ge-land-neighbours", "schemaVersion": 1, "countryIds": [ "ge" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Georgia", - "text": "The UN M49 standard groups Georgia in Western Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Georgia shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9585,17 +9705,17 @@ } }, { - "id": "fact-de-m49-region", + "id": "fact-de-land-neighbours", "schemaVersion": 1, "countryIds": [ "de" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Germany", - "text": "The UN M49 standard groups Germany in Western Europe.", + "difficulty": "tricky", + "heading": "Many ways next door", + "text": "Germany shares land borders with nine countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9605,17 +9725,17 @@ } }, { - "id": "fact-gh-m49-region", + "id": "fact-gh-land-neighbours", "schemaVersion": 1, "countryIds": [ "gh" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Ghana", - "text": "The UN M49 standard groups Ghana in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Ghana shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9625,17 +9745,17 @@ } }, { - "id": "fact-gr-m49-region", + "id": "fact-gr-land-neighbours", "schemaVersion": 1, "countryIds": [ "gr" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Greece", - "text": "The UN M49 standard groups Greece in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Greece shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9645,17 +9765,17 @@ } }, { - "id": "fact-gd-m49-region", + "id": "fact-gd-land-neighbours", "schemaVersion": 1, "countryIds": [ "gd" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Grenada", - "text": "The UN M49 standard groups Grenada in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Grenada shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9665,17 +9785,17 @@ } }, { - "id": "fact-gt-m49-region", + "id": "fact-gt-land-neighbours", "schemaVersion": 1, "countryIds": [ "gt" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Guatemala", - "text": "The UN M49 standard groups Guatemala in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Guatemala shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9685,17 +9805,17 @@ } }, { - "id": "fact-gn-m49-region", + "id": "fact-gn-land-neighbours", "schemaVersion": 1, "countryIds": [ "gn" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Guinea", - "text": "The UN M49 standard groups Guinea in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Guinea shares land borders with six countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9705,17 +9825,17 @@ } }, { - "id": "fact-gw-m49-region", + "id": "fact-gw-land-neighbours", "schemaVersion": 1, "countryIds": [ "gw" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Guinea-Bissau", - "text": "The UN M49 standard groups Guinea-Bissau in Sub-Saharan Africa.", + "heading": "Across the border", + "text": "Guinea-Bissau shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9725,17 +9845,17 @@ } }, { - "id": "fact-gy-m49-region", + "id": "fact-gy-land-neighbours", "schemaVersion": 1, "countryIds": [ "gy" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Guyana", - "text": "The UN M49 standard groups Guyana in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Guyana shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9745,17 +9865,17 @@ } }, { - "id": "fact-ht-m49-region", + "id": "fact-ht-land-neighbours", "schemaVersion": 1, "countryIds": [ "ht" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Haiti", - "text": "The UN M49 standard groups Haiti in Latin America and the Caribbean.", + "heading": "A short border list", + "text": "Haiti has just one land neighbour: Dominican Republic.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9765,17 +9885,17 @@ } }, { - "id": "fact-hn-m49-region", + "id": "fact-hn-land-neighbours", "schemaVersion": 1, "countryIds": [ "hn" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Honduras", - "text": "The UN M49 standard groups Honduras in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Honduras shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9785,17 +9905,17 @@ } }, { - "id": "fact-hu-m49-region", + "id": "fact-hu-land-neighbours", "schemaVersion": 1, "countryIds": [ "hu" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Hungary", - "text": "The UN M49 standard groups Hungary in Eastern Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Hungary is landlocked, yet it touches seven other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9805,17 +9925,17 @@ } }, { - "id": "fact-is-m49-region", + "id": "fact-is-land-neighbours", "schemaVersion": 1, "countryIds": [ "is" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Iceland", - "text": "The UN M49 standard groups Iceland in Northern Europe.", + "heading": "No neighbours by land", + "text": "Iceland shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9825,17 +9945,17 @@ } }, { - "id": "fact-in-m49-region", + "id": "fact-in-land-neighbours", "schemaVersion": 1, "countryIds": [ "in" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: India", - "text": "The UN M49 standard groups India in Southern Asia.", + "difficulty": "tricky", + "heading": "Post from the lake", + "text": "A floating post office on Srinagar's Dal Lake even has a small philately museum.", "sourceIds": [ - "source-un-m49" + "source-incredible-india-dal-lake" ], "asOf": null, "review": { @@ -9845,17 +9965,17 @@ } }, { - "id": "fact-id-m49-region", + "id": "fact-id-land-neighbours", "schemaVersion": 1, "countryIds": [ "id" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Indonesia", - "text": "The UN M49 standard groups Indonesia in South-eastern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Indonesia shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9865,17 +9985,17 @@ } }, { - "id": "fact-ir-m49-region", + "id": "fact-ir-land-neighbours", "schemaVersion": 1, "countryIds": [ "ir" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Iran", - "text": "The UN M49 standard groups Iran in Southern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Iran shares land borders with seven countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9885,17 +10005,17 @@ } }, { - "id": "fact-iq-m49-region", + "id": "fact-iq-land-neighbours", "schemaVersion": 1, "countryIds": [ "iq" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Iraq", - "text": "The UN M49 standard groups Iraq in Western Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Iraq shares land borders with six countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9905,17 +10025,17 @@ } }, { - "id": "fact-ie-m49-region", + "id": "fact-ie-land-neighbours", "schemaVersion": 1, "countryIds": [ "ie" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Ireland", - "text": "The UN M49 standard groups Ireland in Northern Europe.", + "heading": "A short border list", + "text": "Ireland has just one land neighbour: United Kingdom.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9925,17 +10045,17 @@ } }, { - "id": "fact-il-m49-region", + "id": "fact-il-land-neighbours", "schemaVersion": 1, "countryIds": [ "il" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Israel", - "text": "The UN M49 standard groups Israel in Western Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Israel shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9945,17 +10065,17 @@ } }, { - "id": "fact-it-m49-region", + "id": "fact-it-land-neighbours", "schemaVersion": 1, "countryIds": [ "it" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Italy", - "text": "The UN M49 standard groups Italy in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Italy shares land borders with six countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9965,17 +10085,17 @@ } }, { - "id": "fact-jm-m49-region", + "id": "fact-jm-land-neighbours", "schemaVersion": 1, "countryIds": [ "jm" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Jamaica", - "text": "The UN M49 standard groups Jamaica in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Jamaica shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -9985,19 +10105,19 @@ } }, { - "id": "fact-jp-m49-region", + "id": "fact-jp-land-neighbours", "schemaVersion": 1, "countryIds": [ "jp" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Japan", - "text": "The UN M49 standard groups Japan in Eastern Asia.", + "heading": "An island recount", + "text": "Using its 2022 digital maps, Japan's mapping authority counted 14,125 islands.", "sourceIds": [ - "source-un-m49" + "source-gsi-japan-islands" ], - "asOf": null, + "asOf": "2022-01", "review": { "state": "draft", "reviewer": null, @@ -10005,17 +10125,17 @@ } }, { - "id": "fact-jo-m49-region", + "id": "fact-jo-land-neighbours", "schemaVersion": 1, "countryIds": [ "jo" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Jordan", - "text": "The UN M49 standard groups Jordan in Western Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Jordan shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10025,17 +10145,17 @@ } }, { - "id": "fact-kz-m49-region", + "id": "fact-kz-land-neighbours", "schemaVersion": 1, "countryIds": [ "kz" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Kazakhstan", - "text": "The UN M49 standard groups Kazakhstan in Central Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Kazakhstan is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10045,17 +10165,17 @@ } }, { - "id": "fact-ke-m49-region", + "id": "fact-ke-land-neighbours", "schemaVersion": 1, "countryIds": [ "ke" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Kenya", - "text": "The UN M49 standard groups Kenya in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Kenya shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10065,17 +10185,17 @@ } }, { - "id": "fact-ki-m49-region", + "id": "fact-ki-land-neighbours", "schemaVersion": 1, "countryIds": [ "ki" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Kiribati", - "text": "The UN M49 standard groups Kiribati in Micronesia.", + "heading": "No neighbours by land", + "text": "Kiribati shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10085,17 +10205,17 @@ } }, { - "id": "fact-kw-m49-region", + "id": "fact-kw-land-neighbours", "schemaVersion": 1, "countryIds": [ "kw" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Kuwait", - "text": "The UN M49 standard groups Kuwait in Western Asia.", + "heading": "Across the border", + "text": "Kuwait shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10105,17 +10225,17 @@ } }, { - "id": "fact-kg-m49-region", + "id": "fact-kg-land-neighbours", "schemaVersion": 1, "countryIds": [ "kg" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Kyrgyzstan", - "text": "The UN M49 standard groups Kyrgyzstan in Central Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Kyrgyzstan is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10125,17 +10245,17 @@ } }, { - "id": "fact-la-m49-region", + "id": "fact-la-land-neighbours", "schemaVersion": 1, "countryIds": [ "la" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Laos", - "text": "The UN M49 standard groups Laos in South-eastern Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Laos is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10145,17 +10265,17 @@ } }, { - "id": "fact-lv-m49-region", + "id": "fact-lv-land-neighbours", "schemaVersion": 1, "countryIds": [ "lv" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Latvia", - "text": "The UN M49 standard groups Latvia in Northern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Latvia shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10165,17 +10285,17 @@ } }, { - "id": "fact-lb-m49-region", + "id": "fact-lb-land-neighbours", "schemaVersion": 1, "countryIds": [ "lb" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Lebanon", - "text": "The UN M49 standard groups Lebanon in Western Asia.", + "heading": "Across the border", + "text": "Lebanon shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10185,17 +10305,17 @@ } }, { - "id": "fact-ls-m49-region", + "id": "fact-ls-land-neighbours", "schemaVersion": 1, "countryIds": [ "ls" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Lesotho", - "text": "The UN M49 standard groups Lesotho in Sub-Saharan Africa.", + "heading": "A short border list", + "text": "Lesotho has just one land neighbour: South Africa.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10205,17 +10325,17 @@ } }, { - "id": "fact-lr-m49-region", + "id": "fact-lr-land-neighbours", "schemaVersion": 1, "countryIds": [ "lr" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Liberia", - "text": "The UN M49 standard groups Liberia in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Liberia shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10225,17 +10345,17 @@ } }, { - "id": "fact-ly-m49-region", + "id": "fact-ly-land-neighbours", "schemaVersion": 1, "countryIds": [ "ly" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Libya", - "text": "The UN M49 standard groups Libya in Northern Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Libya shares land borders with six countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10245,17 +10365,17 @@ } }, { - "id": "fact-li-m49-region", + "id": "fact-li-land-neighbours", "schemaVersion": 1, "countryIds": [ "li" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Liechtenstein", - "text": "The UN M49 standard groups Liechtenstein in Western Europe.", + "heading": "Landlocked, not isolated", + "text": "Liechtenstein is landlocked, yet it touches two other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10265,17 +10385,17 @@ } }, { - "id": "fact-lt-m49-region", + "id": "fact-lt-land-neighbours", "schemaVersion": 1, "countryIds": [ "lt" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Lithuania", - "text": "The UN M49 standard groups Lithuania in Northern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Lithuania shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10285,17 +10405,17 @@ } }, { - "id": "fact-lu-m49-region", + "id": "fact-lu-land-neighbours", "schemaVersion": 1, "countryIds": [ "lu" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Luxembourg", - "text": "The UN M49 standard groups Luxembourg in Western Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Luxembourg is landlocked, yet it touches three other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10305,17 +10425,17 @@ } }, { - "id": "fact-mg-m49-region", + "id": "fact-mg-land-neighbours", "schemaVersion": 1, "countryIds": [ "mg" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Madagascar", - "text": "The UN M49 standard groups Madagascar in Sub-Saharan Africa.", + "heading": "No neighbours by land", + "text": "Madagascar shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10325,17 +10445,17 @@ } }, { - "id": "fact-mw-m49-region", + "id": "fact-mw-land-neighbours", "schemaVersion": 1, "countryIds": [ "mw" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Malawi", - "text": "The UN M49 standard groups Malawi in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Malawi is landlocked, yet it touches three other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10345,17 +10465,17 @@ } }, { - "id": "fact-my-m49-region", + "id": "fact-my-land-neighbours", "schemaVersion": 1, "countryIds": [ "my" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Malaysia", - "text": "The UN M49 standard groups Malaysia in South-eastern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Malaysia shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10365,17 +10485,17 @@ } }, { - "id": "fact-mv-m49-region", + "id": "fact-mv-land-neighbours", "schemaVersion": 1, "countryIds": [ "mv" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Maldives", - "text": "The UN M49 standard groups Maldives in Southern Asia.", + "heading": "No neighbours by land", + "text": "Maldives shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10385,17 +10505,17 @@ } }, { - "id": "fact-ml-m49-region", + "id": "fact-ml-land-neighbours", "schemaVersion": 1, "countryIds": [ "ml" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Mali", - "text": "The UN M49 standard groups Mali in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Mali is landlocked, yet it touches seven other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10405,17 +10525,17 @@ } }, { - "id": "fact-mt-m49-region", + "id": "fact-mt-land-neighbours", "schemaVersion": 1, "countryIds": [ "mt" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Malta", - "text": "The UN M49 standard groups Malta in Southern Europe.", + "heading": "No neighbours by land", + "text": "Malta shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10425,17 +10545,17 @@ } }, { - "id": "fact-mh-m49-region", + "id": "fact-mh-land-neighbours", "schemaVersion": 1, "countryIds": [ "mh" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Marshall Islands", - "text": "The UN M49 standard groups Marshall Islands in Micronesia.", + "heading": "No neighbours by land", + "text": "Marshall Islands shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10445,17 +10565,17 @@ } }, { - "id": "fact-mr-m49-region", + "id": "fact-mr-land-neighbours", "schemaVersion": 1, "countryIds": [ "mr" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Mauritania", - "text": "The UN M49 standard groups Mauritania in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Mauritania shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10465,17 +10585,17 @@ } }, { - "id": "fact-mu-m49-region", + "id": "fact-mu-land-neighbours", "schemaVersion": 1, "countryIds": [ "mu" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Mauritius", - "text": "The UN M49 standard groups Mauritius in Sub-Saharan Africa.", + "heading": "No neighbours by land", + "text": "Mauritius shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10485,17 +10605,17 @@ } }, { - "id": "fact-mx-m49-region", + "id": "fact-mx-land-neighbours", "schemaVersion": 1, "countryIds": [ "mx" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Mexico", - "text": "The UN M49 standard groups Mexico in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "An origin story on a flag", + "text": "The eagle, cactus and snake on Mexico's flag recall the founding of Tenochtitlan.", "sourceIds": [ - "source-un-m49" + "source-mexico-flag-history" ], "asOf": null, "review": { @@ -10505,17 +10625,17 @@ } }, { - "id": "fact-fm-m49-region", + "id": "fact-fm-land-neighbours", "schemaVersion": 1, "countryIds": [ "fm" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Micronesia", - "text": "The UN M49 standard groups Micronesia in Micronesia.", + "heading": "No neighbours by land", + "text": "Micronesia shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10525,17 +10645,17 @@ } }, { - "id": "fact-md-m49-region", + "id": "fact-md-land-neighbours", "schemaVersion": 1, "countryIds": [ "md" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Moldova", - "text": "The UN M49 standard groups Moldova in Eastern Europe.", + "heading": "Landlocked, not isolated", + "text": "Moldova is landlocked, yet it touches two other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10545,19 +10665,19 @@ } }, { - "id": "fact-mc-m49-region", + "id": "fact-mc-land-neighbours", "schemaVersion": 1, "countryIds": [ "mc" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Monaco", - "text": "The UN M49 standard groups Monaco in Western Europe.", + "heading": "Centuries in two colours", + "text": "Monaco's red-and-white flag colours have been associated with its ruling house since 1339.", "sourceIds": [ - "source-un-m49" + "source-cia-monaco-flag-archive" ], - "asOf": null, + "asOf": "1339", "review": { "state": "draft", "reviewer": null, @@ -10565,17 +10685,17 @@ } }, { - "id": "fact-mn-m49-region", + "id": "fact-mn-land-neighbours", "schemaVersion": 1, "countryIds": [ "mn" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Mongolia", - "text": "The UN M49 standard groups Mongolia in Eastern Asia.", + "heading": "Landlocked, not isolated", + "text": "Mongolia is landlocked, yet it touches two other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10585,17 +10705,17 @@ } }, { - "id": "fact-me-m49-region", + "id": "fact-me-land-neighbours", "schemaVersion": 1, "countryIds": [ "me" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Montenegro", - "text": "The UN M49 standard groups Montenegro in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Montenegro shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10605,17 +10725,17 @@ } }, { - "id": "fact-ma-m49-region", + "id": "fact-ma-land-neighbours", "schemaVersion": 1, "countryIds": [ "ma" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Morocco", - "text": "The UN M49 standard groups Morocco in Northern Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Morocco shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10625,17 +10745,17 @@ } }, { - "id": "fact-mz-m49-region", + "id": "fact-mz-land-neighbours", "schemaVersion": 1, "countryIds": [ "mz" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Mozambique", - "text": "The UN M49 standard groups Mozambique in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Mozambique shares land borders with six countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10645,17 +10765,17 @@ } }, { - "id": "fact-mm-m49-region", + "id": "fact-mm-land-neighbours", "schemaVersion": 1, "countryIds": [ "mm" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Myanmar", - "text": "The UN M49 standard groups Myanmar in South-eastern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Myanmar shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10665,17 +10785,17 @@ } }, { - "id": "fact-na-m49-region", + "id": "fact-na-land-neighbours", "schemaVersion": 1, "countryIds": [ "na" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Namibia", - "text": "The UN M49 standard groups Namibia in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Namibia shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10685,17 +10805,17 @@ } }, { - "id": "fact-nr-m49-region", + "id": "fact-nr-land-neighbours", "schemaVersion": 1, "countryIds": [ "nr" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Nauru", - "text": "The UN M49 standard groups Nauru in Micronesia.", + "heading": "No neighbours by land", + "text": "Nauru shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10705,17 +10825,17 @@ } }, { - "id": "fact-np-m49-region", + "id": "fact-np-land-neighbours", "schemaVersion": 1, "countryIds": [ "np" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Nepal", - "text": "The UN M49 standard groups Nepal in Southern Asia.", + "heading": "A flag with points", + "text": "Nepal has the world's only national flag that is not quadrilateral: it is formed from two joined pennants.", "sourceIds": [ - "source-un-m49" + "source-nepal-film-guide" ], "asOf": null, "review": { @@ -10725,19 +10845,19 @@ } }, { - "id": "fact-nl-m49-region", + "id": "fact-nl-land-neighbours", "schemaVersion": 1, "countryIds": [ "nl" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Netherlands", - "text": "The UN M49 standard groups Netherlands in Western Europe.", + "heading": "Living below the waves", + "text": "About 17 percent of the Netherlands lies below sea level.", "sourceIds": [ - "source-un-m49" + "source-netherlands-govtech" ], - "asOf": null, + "asOf": "2021-06", "review": { "state": "draft", "reviewer": null, @@ -10745,19 +10865,19 @@ } }, { - "id": "fact-nz-m49-region", + "id": "fact-nz-land-neighbours", "schemaVersion": 1, "countryIds": [ "nz" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: New Zealand", - "text": "The UN M49 standard groups New Zealand in Australia and New Zealand.", + "heading": "A voting first", + "text": "In 1893, New Zealand became the first self-governing country where women could vote in parliamentary elections.", "sourceIds": [ - "source-un-m49" + "source-nz-womens-suffrage" ], - "asOf": null, + "asOf": "1893", "review": { "state": "draft", "reviewer": null, @@ -10765,17 +10885,17 @@ } }, { - "id": "fact-ni-m49-region", + "id": "fact-ni-land-neighbours", "schemaVersion": 1, "countryIds": [ "ni" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Nicaragua", - "text": "The UN M49 standard groups Nicaragua in Latin America and the Caribbean.", + "heading": "Across the border", + "text": "Nicaragua shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10785,17 +10905,17 @@ } }, { - "id": "fact-ne-m49-region", + "id": "fact-ne-land-neighbours", "schemaVersion": 1, "countryIds": [ "ne" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Niger", - "text": "The UN M49 standard groups Niger in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Niger is landlocked, yet it touches seven other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10805,17 +10925,17 @@ } }, { - "id": "fact-ng-m49-region", + "id": "fact-ng-land-neighbours", "schemaVersion": 1, "countryIds": [ "ng" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Nigeria", - "text": "The UN M49 standard groups Nigeria in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Nigeria shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10825,17 +10945,17 @@ } }, { - "id": "fact-kp-m49-region", + "id": "fact-kp-land-neighbours", "schemaVersion": 1, "countryIds": [ "kp" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: North Korea", - "text": "The UN M49 standard groups North Korea in Eastern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "North Korea shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10845,17 +10965,17 @@ } }, { - "id": "fact-mk-m49-region", + "id": "fact-mk-land-neighbours", "schemaVersion": 1, "countryIds": [ "mk" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: North Macedonia", - "text": "The UN M49 standard groups North Macedonia in Southern Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "North Macedonia is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10865,17 +10985,17 @@ } }, { - "id": "fact-no-m49-region", + "id": "fact-no-land-neighbours", "schemaVersion": 1, "countryIds": [ "no" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Norway", - "text": "The UN M49 standard groups Norway in Northern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Norway shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10885,17 +11005,17 @@ } }, { - "id": "fact-om-m49-region", + "id": "fact-om-musandam", "schemaVersion": 1, "countryIds": [ "om" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Oman", - "text": "The UN M49 standard groups Oman in Western Asia.", + "difficulty": "tricky", + "heading": "A peninsula apart", + "text": "Oman's Musandam Peninsula is separated from the rest of the country by the United Arab Emirates.", "sourceIds": [ - "source-un-m49" + "source-experience-oman-musandam" ], "asOf": null, "review": { @@ -10905,17 +11025,17 @@ } }, { - "id": "fact-pk-m49-region", + "id": "fact-pk-land-neighbours", "schemaVersion": 1, "countryIds": [ "pk" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Pakistan", - "text": "The UN M49 standard groups Pakistan in Southern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Pakistan shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10925,17 +11045,17 @@ } }, { - "id": "fact-pw-m49-region", + "id": "fact-pw-land-neighbours", "schemaVersion": 1, "countryIds": [ "pw" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Palau", - "text": "The UN M49 standard groups Palau in Micronesia.", + "heading": "No neighbours by land", + "text": "Palau shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10945,17 +11065,17 @@ } }, { - "id": "fact-ps-m49-region", + "id": "fact-ps-land-neighbours", "schemaVersion": 1, "countryIds": [ "ps" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Palestine", - "text": "The UN M49 standard groups Palestine in Western Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Palestine shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10965,17 +11085,17 @@ } }, { - "id": "fact-pa-m49-region", + "id": "fact-pa-land-neighbours", "schemaVersion": 1, "countryIds": [ "pa" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Panama", - "text": "The UN M49 standard groups Panama in Latin America and the Caribbean.", + "heading": "Across the border", + "text": "Panama shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -10985,17 +11105,17 @@ } }, { - "id": "fact-pg-m49-region", + "id": "fact-pg-land-neighbours", "schemaVersion": 1, "countryIds": [ "pg" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Papua New Guinea", - "text": "The UN M49 standard groups Papua New Guinea in Melanesia.", + "heading": "A short border list", + "text": "Papua New Guinea has just one land neighbour: Indonesia.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11005,17 +11125,17 @@ } }, { - "id": "fact-py-m49-region", + "id": "fact-py-land-neighbours", "schemaVersion": 1, "countryIds": [ "py" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Paraguay", - "text": "The UN M49 standard groups Paraguay in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Paraguay is landlocked, yet it touches three other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11025,17 +11145,17 @@ } }, { - "id": "fact-pe-m49-region", + "id": "fact-pe-land-neighbours", "schemaVersion": 1, "countryIds": [ "pe" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Peru", - "text": "The UN M49 standard groups Peru in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Peru shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11045,17 +11165,17 @@ } }, { - "id": "fact-ph-m49-region", + "id": "fact-ph-islands", "schemaVersion": 1, "countryIds": [ "ph" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Philippines", - "text": "The UN M49 standard groups Philippines in South-eastern Asia.", + "heading": "Thousands of islands", + "text": "The Philippines has 7,641 islands. enough to visit a different one every day for almost 21 years.", "sourceIds": [ - "source-un-m49" + "source-philippines-tourism-islands" ], "asOf": null, "review": { @@ -11065,17 +11185,17 @@ } }, { - "id": "fact-pl-m49-region", + "id": "fact-pl-land-neighbours", "schemaVersion": 1, "countryIds": [ "pl" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Poland", - "text": "The UN M49 standard groups Poland in Eastern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Poland shares land borders with seven countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11085,17 +11205,17 @@ } }, { - "id": "fact-pt-m49-region", + "id": "fact-pt-land-neighbours", "schemaVersion": 1, "countryIds": [ "pt" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Portugal", - "text": "The UN M49 standard groups Portugal in Southern Europe.", + "heading": "A short border list", + "text": "Portugal has just one land neighbour: Spain.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11105,17 +11225,17 @@ } }, { - "id": "fact-qa-m49-region", + "id": "fact-qa-land-neighbours", "schemaVersion": 1, "countryIds": [ "qa" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Qatar", - "text": "The UN M49 standard groups Qatar in Western Asia.", + "heading": "A short border list", + "text": "Qatar has just one land neighbour: Saudi Arabia.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11125,17 +11245,17 @@ } }, { - "id": "fact-cg-m49-region", + "id": "fact-cg-land-neighbours", "schemaVersion": 1, "countryIds": [ "cg" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Republic of the Congo", - "text": "The UN M49 standard groups Republic of the Congo in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Republic of the Congo shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11145,17 +11265,17 @@ } }, { - "id": "fact-ro-m49-region", + "id": "fact-ro-land-neighbours", "schemaVersion": 1, "countryIds": [ "ro" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Romania", - "text": "The UN M49 standard groups Romania in Eastern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Romania shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11165,17 +11285,17 @@ } }, { - "id": "fact-ru-m49-region", + "id": "fact-ru-land-neighbours", "schemaVersion": 1, "countryIds": [ "ru" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Russia", - "text": "The UN M49 standard groups Russia in Eastern Europe.", + "difficulty": "tricky", + "heading": "Many ways next door", + "text": "Russia shares land borders with fourteen countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11185,17 +11305,17 @@ } }, { - "id": "fact-rw-m49-region", + "id": "fact-rw-land-neighbours", "schemaVersion": 1, "countryIds": [ "rw" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Rwanda", - "text": "The UN M49 standard groups Rwanda in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Rwanda is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11205,17 +11325,17 @@ } }, { - "id": "fact-kn-m49-region", + "id": "fact-kn-land-neighbours", "schemaVersion": 1, "countryIds": [ "kn" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Saint Kitts and Nevis", - "text": "The UN M49 standard groups Saint Kitts and Nevis in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Saint Kitts and Nevis shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11225,17 +11345,17 @@ } }, { - "id": "fact-lc-m49-region", + "id": "fact-lc-land-neighbours", "schemaVersion": 1, "countryIds": [ "lc" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Saint Lucia", - "text": "The UN M49 standard groups Saint Lucia in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Saint Lucia shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11245,17 +11365,17 @@ } }, { - "id": "fact-vc-m49-region", + "id": "fact-vc-land-neighbours", "schemaVersion": 1, "countryIds": [ "vc" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Saint Vincent and the Grenadines", - "text": "The UN M49 standard groups Saint Vincent and the Grenadines in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Saint Vincent and the Grenadines shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11265,17 +11385,17 @@ } }, { - "id": "fact-ws-m49-region", + "id": "fact-ws-land-neighbours", "schemaVersion": 1, "countryIds": [ "ws" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Samoa", - "text": "The UN M49 standard groups Samoa in Polynesia.", + "heading": "No neighbours by land", + "text": "Samoa shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11285,17 +11405,17 @@ } }, { - "id": "fact-sm-m49-region", + "id": "fact-sm-land-neighbours", "schemaVersion": 1, "countryIds": [ "sm" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: San Marino", - "text": "The UN M49 standard groups San Marino in Southern Europe.", + "heading": "A short border list", + "text": "San Marino has just one land neighbour: Italy.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11305,17 +11425,17 @@ } }, { - "id": "fact-sa-m49-region", + "id": "fact-sa-land-neighbours", "schemaVersion": 1, "countryIds": [ "sa" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Saudi Arabia", - "text": "The UN M49 standard groups Saudi Arabia in Western Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Saudi Arabia shares land borders with seven countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11325,17 +11445,17 @@ } }, { - "id": "fact-sn-m49-region", + "id": "fact-sn-land-neighbours", "schemaVersion": 1, "countryIds": [ "sn" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Senegal", - "text": "The UN M49 standard groups Senegal in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Senegal shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11345,17 +11465,17 @@ } }, { - "id": "fact-rs-m49-region", + "id": "fact-rs-land-neighbours", "schemaVersion": 1, "countryIds": [ "rs" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Serbia", - "text": "The UN M49 standard groups Serbia in Southern Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Serbia is landlocked, yet it touches eight other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11365,17 +11485,17 @@ } }, { - "id": "fact-sc-m49-region", + "id": "fact-sc-land-neighbours", "schemaVersion": 1, "countryIds": [ "sc" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Seychelles", - "text": "The UN M49 standard groups Seychelles in Sub-Saharan Africa.", + "heading": "No neighbours by land", + "text": "Seychelles shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11385,17 +11505,17 @@ } }, { - "id": "fact-sl-m49-region", + "id": "fact-sl-land-neighbours", "schemaVersion": 1, "countryIds": [ "sl" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Sierra Leone", - "text": "The UN M49 standard groups Sierra Leone in Sub-Saharan Africa.", + "heading": "Across the border", + "text": "Sierra Leone shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11405,17 +11525,17 @@ } }, { - "id": "fact-sg-m49-region", + "id": "fact-sg-land-neighbours", "schemaVersion": 1, "countryIds": [ "sg" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Singapore", - "text": "The UN M49 standard groups Singapore in South-eastern Asia.", + "heading": "No neighbours by land", + "text": "Singapore shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11425,17 +11545,17 @@ } }, { - "id": "fact-sk-m49-region", + "id": "fact-sk-land-neighbours", "schemaVersion": 1, "countryIds": [ "sk" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Slovakia", - "text": "The UN M49 standard groups Slovakia in Eastern Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Slovakia is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11445,17 +11565,17 @@ } }, { - "id": "fact-si-m49-region", + "id": "fact-si-land-neighbours", "schemaVersion": 1, "countryIds": [ "si" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Slovenia", - "text": "The UN M49 standard groups Slovenia in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Slovenia shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11465,17 +11585,17 @@ } }, { - "id": "fact-sb-m49-region", + "id": "fact-sb-land-neighbours", "schemaVersion": 1, "countryIds": [ "sb" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Solomon Islands", - "text": "The UN M49 standard groups Solomon Islands in Melanesia.", + "heading": "No neighbours by land", + "text": "Solomon Islands shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11485,17 +11605,17 @@ } }, { - "id": "fact-so-m49-region", + "id": "fact-so-land-neighbours", "schemaVersion": 1, "countryIds": [ "so" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Somalia", - "text": "The UN M49 standard groups Somalia in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Somalia shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11505,17 +11625,17 @@ } }, { - "id": "fact-za-m49-region", + "id": "fact-za-land-neighbours", "schemaVersion": 1, "countryIds": [ "za" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: South Africa", - "text": "The UN M49 standard groups South Africa in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Three capital cities", + "text": "South Africa divides national capital duties among Pretoria, Cape Town and Bloemfontein.", "sourceIds": [ - "source-un-m49" + "source-south-africa-provinces" ], "asOf": null, "review": { @@ -11525,17 +11645,17 @@ } }, { - "id": "fact-kr-m49-region", + "id": "fact-kr-land-neighbours", "schemaVersion": 1, "countryIds": [ "kr" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: South Korea", - "text": "The UN M49 standard groups South Korea in Eastern Asia.", + "heading": "A short border list", + "text": "South Korea has just one land neighbour: North Korea.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11545,17 +11665,17 @@ } }, { - "id": "fact-ss-m49-region", + "id": "fact-ss-land-neighbours", "schemaVersion": 1, "countryIds": [ "ss" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: South Sudan", - "text": "The UN M49 standard groups South Sudan in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "South Sudan is landlocked, yet it touches six other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11565,17 +11685,17 @@ } }, { - "id": "fact-es-m49-region", + "id": "fact-es-land-neighbours", "schemaVersion": 1, "countryIds": [ "es" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Spain", - "text": "The UN M49 standard groups Spain in Southern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Spain shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11585,17 +11705,17 @@ } }, { - "id": "fact-lk-m49-region", + "id": "fact-lk-land-neighbours", "schemaVersion": 1, "countryIds": [ "lk" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Sri Lanka", - "text": "The UN M49 standard groups Sri Lanka in Southern Asia.", + "heading": "No neighbours by land", + "text": "Sri Lanka shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11605,17 +11725,17 @@ } }, { - "id": "fact-sd-m49-region", + "id": "fact-sd-land-neighbours", "schemaVersion": 1, "countryIds": [ "sd" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Sudan", - "text": "The UN M49 standard groups Sudan in Northern Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Sudan shares land borders with seven countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11625,17 +11745,17 @@ } }, { - "id": "fact-sr-m49-region", + "id": "fact-sr-land-neighbours", "schemaVersion": 1, "countryIds": [ "sr" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Suriname", - "text": "The UN M49 standard groups Suriname in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Suriname shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11645,17 +11765,17 @@ } }, { - "id": "fact-se-m49-region", + "id": "fact-se-land-neighbours", "schemaVersion": 1, "countryIds": [ "se" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Sweden", - "text": "The UN M49 standard groups Sweden in Northern Europe.", + "heading": "Across the border", + "text": "Sweden shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11665,17 +11785,17 @@ } }, { - "id": "fact-ch-m49-region", + "id": "fact-ch-land-neighbours", "schemaVersion": 1, "countryIds": [ "ch" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Switzerland", - "text": "The UN M49 standard groups Switzerland in Western Europe.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Switzerland is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11685,17 +11805,17 @@ } }, { - "id": "fact-sy-m49-region", + "id": "fact-sy-land-neighbours", "schemaVersion": 1, "countryIds": [ "sy" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Syria", - "text": "The UN M49 standard groups Syria in Western Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Syria shares land borders with five countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11705,17 +11825,17 @@ } }, { - "id": "fact-st-m49-region", + "id": "fact-st-land-neighbours", "schemaVersion": 1, "countryIds": [ "st" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: São Tomé and Príncipe", - "text": "The UN M49 standard groups São Tomé and Príncipe in Sub-Saharan Africa.", + "heading": "No neighbours by land", + "text": "São Tomé and Príncipe shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11725,17 +11845,17 @@ } }, { - "id": "fact-tj-m49-region", + "id": "fact-tj-land-neighbours", "schemaVersion": 1, "countryIds": [ "tj" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Tajikistan", - "text": "The UN M49 standard groups Tajikistan in Central Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Tajikistan is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11745,17 +11865,17 @@ } }, { - "id": "fact-tz-m49-region", + "id": "fact-tz-land-neighbours", "schemaVersion": 1, "countryIds": [ "tz" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Tanzania", - "text": "The UN M49 standard groups Tanzania in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Many ways next door", + "text": "Tanzania shares land borders with eight countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11765,17 +11885,17 @@ } }, { - "id": "fact-th-m49-region", + "id": "fact-th-land-neighbours", "schemaVersion": 1, "countryIds": [ "th" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Thailand", - "text": "The UN M49 standard groups Thailand in South-eastern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Thailand shares land borders with four countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11785,17 +11905,17 @@ } }, { - "id": "fact-tl-m49-region", + "id": "fact-tl-land-neighbours", "schemaVersion": 1, "countryIds": [ "tl" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Timor-Leste", - "text": "The UN M49 standard groups Timor-Leste in South-eastern Asia.", + "heading": "A short border list", + "text": "Timor-Leste has just one land neighbour: Indonesia.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11805,17 +11925,17 @@ } }, { - "id": "fact-tg-m49-region", + "id": "fact-tg-land-neighbours", "schemaVersion": 1, "countryIds": [ "tg" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Togo", - "text": "The UN M49 standard groups Togo in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Togo shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11825,17 +11945,17 @@ } }, { - "id": "fact-to-m49-region", + "id": "fact-to-land-neighbours", "schemaVersion": 1, "countryIds": [ "to" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Tonga", - "text": "The UN M49 standard groups Tonga in Polynesia.", + "heading": "No neighbours by land", + "text": "Tonga shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11845,17 +11965,17 @@ } }, { - "id": "fact-tt-m49-region", + "id": "fact-tt-land-neighbours", "schemaVersion": 1, "countryIds": [ "tt" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Trinidad and Tobago", - "text": "The UN M49 standard groups Trinidad and Tobago in Latin America and the Caribbean.", + "heading": "No neighbours by land", + "text": "Trinidad and Tobago shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11865,17 +11985,17 @@ } }, { - "id": "fact-tn-m49-region", + "id": "fact-tn-land-neighbours", "schemaVersion": 1, "countryIds": [ "tn" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Tunisia", - "text": "The UN M49 standard groups Tunisia in Northern Africa.", + "heading": "Across the border", + "text": "Tunisia shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11885,17 +12005,17 @@ } }, { - "id": "fact-tm-m49-region", + "id": "fact-tm-land-neighbours", "schemaVersion": 1, "countryIds": [ "tm" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Turkmenistan", - "text": "The UN M49 standard groups Turkmenistan in Central Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Turkmenistan is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11905,17 +12025,17 @@ } }, { - "id": "fact-tv-m49-region", + "id": "fact-tv-land-neighbours", "schemaVersion": 1, "countryIds": [ "tv" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Tuvalu", - "text": "The UN M49 standard groups Tuvalu in Polynesia.", + "heading": "No neighbours by land", + "text": "Tuvalu shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11925,17 +12045,17 @@ } }, { - "id": "fact-tr-m49-region", + "id": "fact-tr-land-neighbours", "schemaVersion": 1, "countryIds": [ "tr" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Türkiye", - "text": "The UN M49 standard groups Türkiye in Western Asia.", + "difficulty": "tricky", + "heading": "Many ways next door", + "text": "Türkiye shares land borders with eight countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11945,17 +12065,17 @@ } }, { - "id": "fact-ug-m49-region", + "id": "fact-ug-land-neighbours", "schemaVersion": 1, "countryIds": [ "ug" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Uganda", - "text": "The UN M49 standard groups Uganda in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Uganda is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11965,17 +12085,17 @@ } }, { - "id": "fact-ua-m49-region", + "id": "fact-ua-land-neighbours", "schemaVersion": 1, "countryIds": [ "ua" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Ukraine", - "text": "The UN M49 standard groups Ukraine in Eastern Europe.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Ukraine shares land borders with seven countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -11985,17 +12105,17 @@ } }, { - "id": "fact-ae-m49-region", + "id": "fact-ae-land-neighbours", "schemaVersion": 1, "countryIds": [ "ae" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: United Arab Emirates", - "text": "The UN M49 standard groups United Arab Emirates in Western Asia.", + "heading": "Across the border", + "text": "United Arab Emirates shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12005,17 +12125,17 @@ } }, { - "id": "fact-gb-m49-region", + "id": "fact-gb-land-neighbours", "schemaVersion": 1, "countryIds": [ "gb" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: United Kingdom", - "text": "The UN M49 standard groups United Kingdom in Northern Europe.", + "heading": "Where east meets west", + "text": "The historic Prime Meridian at Greenwich marks zero degrees longitude.", "sourceIds": [ - "source-un-m49" + "source-rmg-greenwich" ], "asOf": null, "review": { @@ -12025,17 +12145,17 @@ } }, { - "id": "fact-us-m49-region", + "id": "fact-us-land-neighbours", "schemaVersion": 1, "countryIds": [ "us" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: United States", - "text": "The UN M49 standard groups United States in Northern America.", + "heading": "Across the border", + "text": "United States shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12045,17 +12165,17 @@ } }, { - "id": "fact-uy-m49-region", + "id": "fact-uy-land-neighbours", "schemaVersion": 1, "countryIds": [ "uy" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Uruguay", - "text": "The UN M49 standard groups Uruguay in Latin America and the Caribbean.", + "heading": "Across the border", + "text": "Uruguay shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12065,17 +12185,17 @@ } }, { - "id": "fact-uz-m49-region", + "id": "fact-uz-land-neighbours", "schemaVersion": 1, "countryIds": [ "uz" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Uzbekistan", - "text": "The UN M49 standard groups Uzbekistan in Central Asia.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Uzbekistan is landlocked, yet it touches five other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12085,17 +12205,17 @@ } }, { - "id": "fact-vu-m49-region", + "id": "fact-vu-land-neighbours", "schemaVersion": 1, "countryIds": [ "vu" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Vanuatu", - "text": "The UN M49 standard groups Vanuatu in Melanesia.", + "heading": "No neighbours by land", + "text": "Vanuatu shares no land border with another country.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12105,17 +12225,17 @@ } }, { - "id": "fact-va-m49-region", + "id": "fact-va-land-neighbours", "schemaVersion": 1, "countryIds": [ "va" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Vatican City", - "text": "The UN M49 standard groups Vatican City in Southern Europe.", + "heading": "A short border list", + "text": "Vatican City has just one land neighbour: Italy.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12125,17 +12245,17 @@ } }, { - "id": "fact-ve-m49-region", + "id": "fact-ve-land-neighbours", "schemaVersion": 1, "countryIds": [ "ve" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Venezuela", - "text": "The UN M49 standard groups Venezuela in Latin America and the Caribbean.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Venezuela shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12145,17 +12265,17 @@ } }, { - "id": "fact-vn-m49-region", + "id": "fact-vn-land-neighbours", "schemaVersion": 1, "countryIds": [ "vn" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Vietnam", - "text": "The UN M49 standard groups Vietnam in South-eastern Asia.", + "difficulty": "tricky", + "heading": "Across the border", + "text": "Vietnam shares land borders with three countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12165,17 +12285,17 @@ } }, { - "id": "fact-ye-m49-region", + "id": "fact-ye-land-neighbours", "schemaVersion": 1, "countryIds": [ "ye" ], "topic": "mixed_geography", "difficulty": "casual", - "heading": "Atlas placement: Yemen", - "text": "The UN M49 standard groups Yemen in Western Asia.", + "heading": "Across the border", + "text": "Yemen shares land borders with two countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12185,17 +12305,17 @@ } }, { - "id": "fact-zm-m49-region", + "id": "fact-zm-land-neighbours", "schemaVersion": 1, "countryIds": [ "zm" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Zambia", - "text": "The UN M49 standard groups Zambia in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Zambia is landlocked, yet it touches eight other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12205,17 +12325,17 @@ } }, { - "id": "fact-zw-m49-region", + "id": "fact-zw-land-neighbours", "schemaVersion": 1, "countryIds": [ "zw" ], "topic": "mixed_geography", - "difficulty": "casual", - "heading": "Atlas placement: Zimbabwe", - "text": "The UN M49 standard groups Zimbabwe in Sub-Saharan Africa.", + "difficulty": "tricky", + "heading": "Landlocked, not isolated", + "text": "Zimbabwe is landlocked, yet it touches four other countries.", "sourceIds": [ - "source-un-m49" + "source-rest-countries" ], "asOf": null, "review": { @@ -12249,8 +12369,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Algiers is the capital of Algeria.", "review": { @@ -12282,8 +12401,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Kabul is the capital of Afghanistan.", "review": { @@ -12315,8 +12433,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Tirana is the capital of Albania.", "review": { @@ -12348,8 +12465,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "St. John's is the capital of Antigua and Barbuda.", "review": { @@ -12381,8 +12497,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Buenos Aires is the capital of Argentina.", "review": { @@ -12414,8 +12529,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Canberra is the capital of Australia.", "review": { @@ -12447,8 +12561,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Luanda is the capital of Angola.", "review": { @@ -12480,8 +12593,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Yerevan is the capital of Armenia.", "review": { @@ -12513,8 +12625,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Andorra la Vella is the capital of Andorra.", "review": { @@ -12546,8 +12657,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Nassau is the capital of Bahamas.", "review": { @@ -12579,8 +12689,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Brasilia is the capital of Brazil.", "review": { @@ -12612,8 +12721,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Suva is the capital of Fiji.", "review": { @@ -12645,8 +12753,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Gaborone is the capital of Botswana.", "review": { @@ -12678,8 +12785,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Baku is the capital of Azerbaijan.", "review": { @@ -12711,8 +12817,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Vienna is the capital of Austria.", "review": { @@ -12744,8 +12849,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Bridgetown is the capital of Barbados.", "review": { @@ -12777,8 +12881,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Santiago is the capital of Chile.", "review": { @@ -12810,8 +12913,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Tarawa is the capital of Kiribati.", "review": { @@ -12843,8 +12945,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Ouagadougou is the capital of Burkina Faso.", "review": { @@ -12876,8 +12977,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Manama is the capital of Bahrain.", "review": { @@ -12909,8 +13009,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Minsk is the capital of Belarus.", "review": { @@ -12942,8 +13041,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Belmopan is the capital of Belize.", "review": { @@ -12975,8 +13073,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Bogota is the capital of Colombia.", "review": { @@ -13008,8 +13105,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Majuro is the capital of Marshall Islands.", "review": { @@ -13041,8 +13137,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Gitega is the capital of Burundi.", "review": { @@ -13074,8 +13169,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Dhaka is the capital of Bangladesh.", "review": { @@ -13107,8 +13201,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Brussels is the capital of Belgium.", "review": { @@ -13140,8 +13233,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Ottawa is the capital of Canada.", "review": { @@ -13173,8 +13265,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Quito is the capital of Ecuador.", "review": { @@ -13206,8 +13297,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Palikir is the capital of Micronesia.", "review": { @@ -13239,8 +13329,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Yaounde is the capital of Cameroon.", "review": { @@ -13272,8 +13361,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Thimphu is the capital of Bhutan.", "review": { @@ -13305,8 +13393,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Sarajevo is the capital of Bosnia and Herzegovina.", "review": { @@ -13338,8 +13425,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "San Jose is the capital of Costa Rica.", "review": { @@ -13371,8 +13457,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Georgetown is the capital of Guyana.", "review": { @@ -13404,8 +13489,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Yaren is the capital of Nauru.", "review": { @@ -13437,8 +13521,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Praia is the capital of Cape Verde.", "review": { @@ -13470,8 +13553,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Bandar Seri Begawan is the capital of Brunei.", "review": { @@ -13503,8 +13585,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Sofia is the capital of Bulgaria.", "review": { @@ -13536,8 +13617,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Havana is the capital of Cuba.", "review": { @@ -13569,8 +13649,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Asuncion is the capital of Paraguay.", "review": { @@ -13602,8 +13681,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Wellington is the capital of New Zealand.", "review": { @@ -13635,8 +13713,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Bangui is the capital of Central African Republic.", "review": { @@ -13668,8 +13745,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Phnom Penh is the capital of Cambodia.", "review": { @@ -13701,8 +13777,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Zagreb is the capital of Croatia.", "review": { @@ -13734,8 +13809,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Roseau is the capital of Dominica.", "review": { @@ -13767,8 +13841,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Lima is the capital of Peru.", "review": { @@ -13800,8 +13873,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Melekeok is the capital of Palau.", "review": { @@ -13833,8 +13905,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "N'Djamena is the capital of Chad.", "review": { @@ -13866,8 +13937,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Beijing is the capital of China.", "review": { @@ -13899,8 +13969,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Prague is the capital of Czechia.", "review": { @@ -13932,8 +14001,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Santo Domingo is the capital of Dominican Republic.", "review": { @@ -13965,8 +14033,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Paramaribo is the capital of Suriname.", "review": { @@ -13998,8 +14065,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Port Moresby is the capital of Papua New Guinea.", "review": { @@ -14031,8 +14097,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Moroni is the capital of Comoros.", "review": { @@ -14064,8 +14129,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Nicosia is the capital of Cyprus.", "review": { @@ -14097,8 +14161,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Copenhagen is the capital of Denmark.", "review": { @@ -14130,8 +14193,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "San Salvador is the capital of El Salvador.", "review": { @@ -14163,8 +14225,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Montevideo is the capital of Uruguay.", "review": { @@ -14196,8 +14257,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Apia is the capital of Samoa.", "review": { @@ -14229,8 +14289,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Kinshasa is the capital of Democratic Republic of the Congo.", "review": { @@ -14262,8 +14321,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Tbilisi is the capital of Georgia.", "review": { @@ -14295,8 +14353,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Tallinn is the capital of Estonia.", "review": { @@ -14328,8 +14385,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "St. George's is the capital of Grenada.", "review": { @@ -14361,8 +14417,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Caracas is the capital of Venezuela.", "review": { @@ -14394,8 +14449,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Honiara is the capital of Solomon Islands.", "review": { @@ -14427,8 +14481,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Djibouti is the capital of Djibouti.", "review": { @@ -14460,8 +14513,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "New Delhi is the capital of India.", "review": { @@ -14493,8 +14545,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Helsinki is the capital of Finland.", "review": { @@ -14526,8 +14577,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Guatemala City is the capital of Guatemala.", "review": { @@ -14559,8 +14609,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Nuku'alofa is the capital of Tonga.", "review": { @@ -14592,8 +14641,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Cairo is the capital of Egypt.", "review": { @@ -14625,8 +14673,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Tehran is the capital of Iran.", "review": { @@ -14658,8 +14705,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Paris is the capital of France.", "review": { @@ -14691,8 +14737,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Port-au-Prince is the capital of Haiti.", "review": { @@ -14724,8 +14769,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Funafuti is the capital of Tuvalu.", "review": { @@ -14757,8 +14801,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Ciudad de la Paz is the capital of Equatorial Guinea.", "review": { @@ -14790,8 +14833,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Baghdad is the capital of Iraq.", "review": { @@ -14823,8 +14865,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Berlin is the capital of Germany.", "review": { @@ -14856,8 +14897,7 @@ ], "supportedHints": [ "remove_choices", - "reveal_continent", - "factual_clue" + "reveal_continent" ], "explanation": "Tegucigalpa is the capital of Honduras.", "review": { @@ -15176,7 +15216,7 @@ "at", "au" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Austria: Vienna; Australia: Canberra.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Austria: Vienna; Australia: Canberra.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15192,7 +15232,7 @@ "ne", "ng" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Niger: Niamey; Nigeria: Abuja.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Niger: Niamey; Nigeria: Abuja.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15208,7 +15248,7 @@ "dm", "do" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Dominica: Roseau; Dominican Republic: Santo Domingo.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Dominica: Roseau; Dominican Republic: Santo Domingo.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15224,7 +15264,7 @@ "sk", "si" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Slovakia: Bratislava; Slovenia: Ljubljana.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Slovakia: Bratislava; Slovenia: Ljubljana.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15240,7 +15280,7 @@ "ie", "is" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Ireland: Dublin; Iceland: Reykjavik.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Ireland: Dublin; Iceland: Reykjavik.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15256,7 +15296,7 @@ "se", "ch" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Sweden: Stockholm; Switzerland: Bern.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Sweden: Stockholm; Switzerland: Bern.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15272,7 +15312,7 @@ "gm", "zm" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Gambia: Banjul; Zambia: Lusaka.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Gambia: Banjul; Zambia: Lusaka.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15288,7 +15328,7 @@ "mr", "mu" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Mauritania: Nouakchott; Mauritius: Port Louis.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Mauritania: Nouakchott; Mauritius: Port Louis.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15306,7 +15346,7 @@ "gq", "pg" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Guinea: Conakry; Guinea-Bissau: Bissau; Equatorial Guinea: Ciudad de la Paz; Papua New Guinea: Port Moresby.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Guinea: Conakry; Guinea-Bissau: Bissau; Equatorial Guinea: Ciudad de la Paz; Papua New Guinea: Port Moresby.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15322,7 +15362,7 @@ "cg", "cd" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Republic of the Congo: Brazzaville; Democratic Republic of the Congo: Kinshasa.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Republic of the Congo: Brazzaville; Democratic Republic of the Congo: Kinshasa.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15338,7 +15378,7 @@ "kp", "kr" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — North Korea: Pyongyang; South Korea: Seoul.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. North Korea: Pyongyang; South Korea: Seoul.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15354,7 +15394,7 @@ "sd", "ss" ], - "text": "These similarly named countries are separate catalog entries. Capital memory hook — Sudan: Khartoum; South Sudan: Juba.", + "text": "These similarly named countries are separate catalog entries. Capital memory hook. Sudan: Khartoum; South Sudan: Juba.", "sourceIds": [ "source-country-codes-snapshot" ], @@ -15386,7 +15426,7 @@ "id", "mc" ], - "text": "Both are red over white; their official proportions differ, so quiz choices keep the original ratios.", + "text": "Both are red over white; their official proportions differ, so trivia choices keep the original ratios.", "sourceIds": [ "source-country-flags" ], @@ -17651,6 +17691,6 @@ "state": "draft", "reviewer": null, "reviewedAt": null, - "note": "Machine assembled from pinned sources. Every record still requires human fact, copy, sensitivity, and asset review." + "note": "Machine assembled from pinned sources. Land-border facts require human fact, copy, ambiguity, and sensitivity review; every flag requires visual audit." } } diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/App.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/App.kt index 6bd0f47..c712ba3 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/App.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/App.kt @@ -16,6 +16,7 @@ import app.bertsatlas.domain.model.HintType import app.bertsatlas.domain.model.Question import app.bertsatlas.domain.model.QuizDuration import app.bertsatlas.domain.model.RunPhase +import app.bertsatlas.domain.model.RunStatus import app.bertsatlas.ui.AnswerState import app.bertsatlas.ui.BertsAtlasTheme import app.bertsatlas.ui.BootstrapScreen @@ -27,6 +28,7 @@ import app.bertsatlas.ui.FlagUi import app.bertsatlas.ui.FunFactScreen import app.bertsatlas.ui.HintChooserScreen import app.bertsatlas.ui.HomeScreen +import app.bertsatlas.ui.LeaveQuizConfirmationScreen import app.bertsatlas.ui.LetterSlotUi import app.bertsatlas.ui.LetterTileUi import app.bertsatlas.ui.OptionsScreen @@ -66,12 +68,21 @@ fun App( @Composable private fun AtlasDestination(controller: AppController, state: AtlasAppState) { val run = state.run + if (state.leaveConfirmationRequested) { + LeaveQuizConfirmationScreen( + onCancel = controller::cancelLeave, + onLeave = controller::confirmLeave, + ) + return + } when (val destination = state.destination) { Destination.Bootstrap -> BootstrapScreen() Destination.Home -> HomeScreen( isFirstLaunch = state.firstLaunch, streakDays = state.streak.currentDays, progressSummary = "${state.attempts.size} questions explored", + totalPoints = state.attempts.sumOf { it.pointsAwarded } - state.attempts.sumOf { it.hintPointsDeducted }, + hasStartedTrivia = !state.firstLaunch, onStart = controller::start, onOptions = controller::openOptions, onProgress = { controller.navigate(Destination.Progress) }, @@ -88,7 +99,7 @@ private fun AtlasDestination(controller: AppController, state: AtlasAppState) { score = run.score, timeRemaining = run.sessionRemainingMillis.formatTime(), onResume = controller::resume, - onAbandon = controller::abandon, + onAbandon = controller::requestLeave, ) Destination.Quiz -> if (run == null) BootstrapScreen() else RunDestination(controller, state) Destination.Results -> { @@ -98,19 +109,20 @@ private fun AtlasDestination(controller: AppController, state: AtlasAppState) { correctAnswers = result.correctAnswers, answeredQuestions = result.answeredQuestions, accuracy = "${result.accuracyPercent}%", - averageResponseTime = "${result.averageResponseMillis / 1_000.0}s", + averageResponseTime = result.averageResponseMillis.formatSeconds(), bestCorrectStreak = result.bestCorrectStreak, masteryImproved = result.improvedMasteryItems, revisitCountries = result.revisitCountryIds.mapNotNull { id -> controller.catalog.countries.firstOrNull { it.id == id }?.displayName }, newPersonalBest = state.newPersonalBest, onPlayAgain = controller::start, onHome = { controller.navigate(Destination.Home) }, + abandoned = result.status == RunStatus.Abandoned, ) } Destination.Progress -> ProgressScreen( currentStreak = state.streak.currentDays, bestStreak = state.streak.bestDays, - overallAccuracy = if (state.attempts.isEmpty()) "—" else "${state.attempts.count { it.correct } * 100 / state.attempts.size}%", + overallAccuracy = if (state.attempts.isEmpty()) "Not yet" else "${state.attempts.count { it.correct } * 100 / state.attempts.size}%", totalAnswered = state.attempts.size, masteryGroups = controller.progressGroups, strongCountries = countryNames(controller, state.mastery.filter { it.level.name in setOf("Familiar", "Mastered") }.map { it.countryId }), @@ -163,28 +175,32 @@ private fun RunDestination(controller: AppController, state: AtlasAppState) { prompt = question.prompt, imageDescription = if (promptFlag == null) null else "A flag to identify", score = run.score, - sessionTime = if (run.configuration.duration == QuizDuration.Unlimited) { - (run.attempts.sumOf { it.responseMillis } + run.activeQuestion.questionElapsedMillis).formatTime() - } else run.sessionRemainingMillis.formatTime(), questionTime = run.activeQuestion.questionRemainingMillis.formatTime(), timerExpired = run.activeQuestion.questionRemainingMillis == 0L, choices = (question as? Question.MultipleChoice)?.let { run.activeQuestion.orderedChoices.map { choice -> - val choiceCountry = controller.catalog.countries.firstOrNull { it.id == choice.id } ChoiceUi( id = choice.id, label = choice.label, state = if (choice.id in run.activeQuestion.removedChoiceIds) AnswerState.Eliminated else AnswerState.Available, enabled = choice.id !in run.activeQuestion.removedChoiceIds && run.phase == RunPhase.Answering, - flag = choice.assetId?.let { FlagUi(it.removePrefix("flag-"), choiceCountry?.displayName ?: choice.label) }, + flag = choice.assetId?.let { FlagUi(it.removePrefix("flag-"), choice.label) }, ) } }.orEmpty(), - letterSlots = letterSlots(question, run.activeQuestion.placements, run.activeQuestion.letterTiles), + letterSlots = letterSlots( + question, + run.activeQuestion.placements, + run.activeQuestion.letterTiles, + run.activeQuestion.appliedHints.mapNotNullTo(linkedSetOf()) { it.revealedTileId }, + ), letterTiles = run.activeQuestion.letterTiles.map { tile -> LetterTileUi(tile.id, tile.character.toString(), run.activeQuestion.placements.firstOrNull { it.tileId == tile.id }?.slotIndex, tile.id !in run.activeQuestion.removedTileIds) }, - hintAvailable = run.activeQuestion.hint == null && run.phase == RunPhase.Answering, + appliedHintMessages = run.activeQuestion.appliedHints.mapNotNull { it.message }, + hintAvailable = run.activeQuestion.question.supportedHints.any { + it !in run.activeQuestion.usedHintTypes + } && run.phase == RunPhase.Answering, unlimited = run.configuration.duration == QuizDuration.Unlimited, canSubmit = question is Question.LetterBank && run.activeQuestion.placements.size == question.displayAnswer.count { it.isLetter() }, onChoice = controller::answerChoice, @@ -196,11 +212,25 @@ private fun RunDestination(controller: AppController, state: AtlasAppState) { onFinish = controller::finishUnlimited, onLeave = controller::requestLeave, promptFlag = promptFlag, + runProgress = run.configuration.duration.activeDurationMillis?.let { duration -> + 1f - ((run.sessionRemainingMillis ?: duration).toFloat() / duration.toFloat()) + }, + runProgressDescription = "Trivia time remaining ${run.sessionRemainingMillis.formatTime()}", ) } RunPhase.HintChooser -> HintChooserScreen( score = run.score, - hints = run.activeQuestion.question.supportedHints.map { ChoiceUi(it.name, it.displayName()) }, + hints = run.activeQuestion.question.supportedHints.map { hint -> + val used = hint in run.activeQuestion.usedHintTypes + ChoiceUi( + id = hint.name, + label = hint.displayName(), + supportingText = if (used) "✓ Used" else null, + state = if (used) AnswerState.Selected else AnswerState.Available, + enabled = !used, + ) + }, + chargedHintLabel = "−${run.score / 2} points", onChooseHint = { id -> controller.chooseHint(HintType.valueOf(id)) }, onCancel = controller::cancelHint, ) @@ -227,7 +257,7 @@ private fun RunDestination(controller: AppController, state: AtlasAppState) { RunPhase.FunFact -> requireNotNull(run.visibleFact).let { fact -> FunFactScreen(fact.heading, fact.body, countryNames(controller, fact.countryIds).joinToString(), onContinue = controller::continueRun) } - RunPhase.Completed -> BootstrapScreen("Finishing your field notes…") + RunPhase.Completed -> BootstrapScreen("Preparing your summary…") } } @@ -241,7 +271,12 @@ private fun topicChoices() = listOf( ChoiceUi("MixedGeography", "Mixed geography", "A broader grab bag"), ) -private fun letterSlots(question: Question, placements: List, tiles: List): List { +private fun letterSlots( + question: Question, + placements: List, + tiles: List, + revealedTileIds: Set, +): List { val letterBank = question as? Question.LetterBank ?: return emptyList() val tileById = tiles.associateBy { it.id } var editableIndex = 0 @@ -249,7 +284,12 @@ private fun letterSlots(question: Question, placements: List): List = ids.distinct().mapNotNull { id -> controller.catalog.countries.firstOrNull { it.id == id }?.displayName } diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/AppController.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/AppController.kt index a5f2b74..856340f 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/AppController.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/AppController.kt @@ -43,6 +43,7 @@ import app.bertsatlas.ui.StampUi import kotlin.time.Clock import kotlin.time.TimeSource import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -85,6 +86,8 @@ data class AtlasAppState( val completedRuns: List = emptyList(), val streak: Streak = Streak(), val stamps: List = emptyList(), + val leaveConfirmationRequested: Boolean = false, + val resumeRunAfterLeaveCancellation: Boolean = false, ) class AppController( @@ -97,6 +100,7 @@ class AppController( ) { private val json = Json { ignoreUnknownKeys = true } private val mutex = Mutex() + private var lastRunPersistence: Job? = null private val monotonicOrigin = TimeSource.Monotonic.markNow() private val monotonicClock = MonotonicClock { monotonicOrigin.elapsedNow().inWholeMilliseconds } private val wallClock = WallClock { Clock.System.now().toEpochMilliseconds() } @@ -114,7 +118,9 @@ class AppController( delay(100) val current = _state.value.run if (current?.phase == RunPhase.Answering) { - _state.value = _state.value.copy(run = engine.advanceClocks(current)) + val advanced = engine.advanceClocks(current) + if (advanced.sessionExpired) acceptTransition(engine.finishExpiredRun(advanced)) + else _state.value = _state.value.copy(run = advanced) } } } @@ -132,9 +138,11 @@ class AppController( runCatching { engine.restoreRun(json.decodeFromString(it.snapshotJson)) }.getOrNull() } if (snapshot != null && restored == null) database.runWriter().clearActiveRun() + val hasStartedTrivia = preferences.hasStartedTrivia || restored != null || attempts.isNotEmpty() || runs.isNotEmpty() + if (hasStartedTrivia && !preferences.hasStartedTrivia) preferencesStore.markTriviaStarted() _state.value = AtlasAppState( destination = if (restored == null) Destination.Home else Destination.Recovery, - firstLaunch = !preferences.hasPreviouslyLaunched, + firstLaunch = !hasStartedTrivia, preferences = preferences, selectedDuration = enumValueOrDefault(preferences.duration, QuizDuration.FiveMinutes), run = restored, @@ -144,7 +152,6 @@ class AppController( streak = streak, stamps = stamps, ) - preferencesStore.markLaunched() } fun start() { @@ -184,11 +191,38 @@ class AppController( } } - fun back() { - _state.value = when (val destination = _state.value.destination) { - is Destination.Options -> _state.value.copy(destination = if (destination.step == 2) Destination.Options(1) else Destination.Home) - Destination.Privacy, Destination.Credits, Destination.Reset, Destination.Debug -> _state.value.copy(destination = Destination.Settings) - else -> _state.value.copy(destination = Destination.Home) + fun back() { handleBack() } + + fun handleBack(): Boolean { + val current = _state.value + if (current.leaveConfirmationRequested) { + cancelLeave() + return true + } + return when (val destination = current.destination) { + Destination.Bootstrap, Destination.Home -> false + is Destination.Options -> { + _state.value = current.copy( + destination = if (destination.step == 2) Destination.Options(1) else Destination.Home, + ) + true + } + Destination.Recovery -> { + requestLeave() + true + } + Destination.Quiz -> { + if (current.run?.phase == RunPhase.HintChooser) cancelHint() else requestLeave() + true + } + Destination.Results, Destination.Progress, Destination.Settings -> { + _state.value = current.copy(destination = Destination.Home) + true + } + Destination.Privacy, Destination.Credits, Destination.Reset, Destination.Debug -> { + _state.value = current.copy(destination = Destination.Settings) + true + } } } @@ -196,6 +230,7 @@ class AppController( val now = wallClock.nowEpochMillis() val run = engine.startRun("run-$now", configuration, now xor monotonicClock.nowMillis(), _state.value.mastery) _state.value = _state.value.copy(destination = Destination.Quiz, firstLaunch = false, run = run, result = null) + scope.launch { preferencesStore.markTriviaStarted() } persistSnapshot(run) } @@ -206,9 +241,11 @@ class AppController( persistSnapshot(resumed) } - fun answerChoice(choiceId: String) = mutateRun { engine.answerQuestion(it, SubmittedAnswer.ChoiceAnswer(choiceId)) }.also { - playAnswerFeedback() - persistAcceptedAnswer() + fun answerChoice(choiceId: String) { + if (mutateRun { engine.answerQuestion(it, SubmittedAnswer.ChoiceAnswer(choiceId)) }) { + playAnswerFeedback() + persistAcceptedAnswer() + } } fun placeLetter(tileId: String) = mutateRun { engine.placeLetter(it, tileId) } @@ -220,14 +257,18 @@ class AppController( fun clearLetters() = mutateRun(engine::clearLetters) - fun submitLetters() = mutateRun(engine::submitLetters).also { - playAnswerFeedback() - persistAcceptedAnswer() + fun submitLetters() { + if (mutateRun(engine::submitLetters)) { + playAnswerFeedback() + persistAcceptedAnswer() + } } fun openHint() = mutateRun(engine::openHintChooser) fun cancelHint() = mutateRun { state -> engine.resumeRun(state.copy(phase = RunPhase.Paused, pausedFromPhase = RunPhase.Answering)) } - fun chooseHint(type: HintType) = mutateRun { engine.chooseHint(it, type) }.also { persistSnapshot(_state.value.run) } + fun chooseHint(type: HintType) { + if (mutateRun { engine.chooseHint(it, type) }) persistSnapshot(_state.value.run) + } fun continueRun() { val before = _state.value.run ?: return @@ -244,17 +285,65 @@ class AppController( acceptTransition(engine.finishUnlimitedRun(run, _state.value.mastery)) } - fun requestLeave() { abandon() } + fun requestLeave() { + val current = _state.value + val run = current.run ?: return + if (run.status != RunStatus.Active || current.leaveConfirmationRequested) return + val shouldResumeOnCancel = run.phase != RunPhase.Paused + val paused = if (shouldResumeOnCancel) engine.pauseForInterruption(run) else run + _state.value = current.copy( + run = paused, + leaveConfirmationRequested = true, + resumeRunAfterLeaveCancellation = shouldResumeOnCancel, + ) + persistSnapshot(paused) + } + + fun cancelLeave() { + val current = _state.value + if (!current.leaveConfirmationRequested) return + val run = current.run + val resumed = if ( + current.resumeRunAfterLeaveCancellation && + run?.status == RunStatus.Active && + run.phase == RunPhase.Paused + ) { + engine.resumeRun(run) + } else { + run + } + _state.value = current.copy( + run = resumed, + leaveConfirmationRequested = false, + resumeRunAfterLeaveCancellation = false, + ) + persistSnapshot(resumed) + } + + fun confirmLeave() { abandon() } fun abandon() { val run = _state.value.run ?: return val abandoned = if (run.status == RunStatus.Active) engine.abandonRun(run) else run - _state.value = _state.value.copy(destination = Destination.Home, run = null) - scope.launch { - mutex.withLock { - database.runWriter().abandonRun(abandoned.toRunEntity()) - refreshProgress() - } + if (abandoned.status != RunStatus.Abandoned) return + val summary = engine.abandonedRunSummary( + abandoned, + improvedMasteryItems = abandoned.attempts + .flatMap { attempt -> attempt.countryIds.map { it to attempt.skill } } + .distinct() + .size, + ) + _state.value = _state.value.copy( + destination = Destination.Results, + run = abandoned, + result = summary, + newPersonalBest = false, + leaveConfirmationRequested = false, + resumeRunAfterLeaveCancellation = false, + ) + enqueueRunPersistence { + database.runWriter().abandonRun(abandoned.toRunEntity(summary)) + refreshProgress() } } @@ -268,16 +357,18 @@ class AppController( fun resumeAfterInterruption() { val run = _state.value.run ?: return + if (_state.value.leaveConfirmationRequested) return if (_state.value.destination != Destination.Quiz || run.phase != RunPhase.Paused) return val resumed = engine.resumeRun(run) _state.value = _state.value.copy(run = resumed) persistSnapshot(resumed) } - private fun mutateRun(change: (QuizRunState) -> QuizRunState) { - val current = _state.value.run ?: return - val next = runCatching { change(current) }.getOrNull() ?: return + private fun mutateRun(change: (QuizRunState) -> QuizRunState): Boolean { + val current = _state.value.run ?: return false + val next = runCatching { change(current) }.getOrNull() ?: return false _state.value = _state.value.copy(destination = destinationFor(next), run = next) + return true } private fun acceptTransition(next: QuizRunState) { @@ -291,18 +382,16 @@ class AppController( private fun persistAcceptedAnswer() { val run = _state.value.run ?: return val attempt = run.attempts.lastOrNull() ?: return - scope.launch { - mutex.withLock { - val before = _state.value.mastery - val after = MasteryRules.applyAttemptToCountries(before, attempt) - val changed = after.filter { updated -> before.none { it.countryId == updated.countryId && it.skill == updated.skill && it == updated } } - database.runWriter().recordAcceptedAnswer( - attempt.toEntity(json), - changed.map { it.toEntity() }, - run.toSnapshotEntity(engine, json), - ) - _state.value = _state.value.copy(mastery = after) - } + enqueueRunPersistence { + val before = _state.value.mastery + val after = MasteryRules.applyAttemptToCountries(before, attempt) + val changed = after.filter { updated -> before.none { it.countryId == updated.countryId && it.skill == updated.skill && it == updated } } + database.runWriter().recordAcceptedAnswer( + attempt.toEntity(json), + changed.map { it.toEntity() }, + run.toSnapshotEntity(engine, json), + ) + _state.value = _state.value.copy(mastery = after) } } @@ -314,18 +403,15 @@ class AppController( private fun persistSnapshot(run: QuizRunState?) { if (run == null || run.status != RunStatus.Active) return - scope.launch { - mutex.withLock { - database.runWriter().saveActiveRun(run.toSnapshotEntity(engine, json)) - } + enqueueRunPersistence { + database.runWriter().saveActiveRun(run.toSnapshotEntity(engine, json)) } } private fun complete(run: QuizRunState) { val result = engine.runResult(run, improvedMasteryItems = run.attempts.map { it.countryIds.firstOrNull() to it.skill }.distinct().size) _state.value = _state.value.copy(destination = Destination.Results, run = run, result = result) - scope.launch { - mutex.withLock { + enqueueRunPersistence { val reader = database.progressReader() val oldBest = reader.personalBestsNow().firstOrNull { it.duration == run.configuration.duration.name && it.topic == run.configuration.topic.name @@ -357,7 +443,14 @@ class AppController( ) _state.value = _state.value.copy(newPersonalBest = isBest, streak = newStreak, stamps = existingStamps + newlyEarned) refreshProgress() - } + } + } + + private fun enqueueRunPersistence(block: suspend () -> Unit) { + val precedingWrite = lastRunPersistence + lastRunPersistence = scope.launch { + precedingWrite?.join() + mutex.withLock { block() } } } @@ -387,7 +480,7 @@ class AppController( } fun resetAll() { - scope.launch { + enqueueRunPersistence { database.runWriter().resetProgress() preferencesStore.resetAll() _state.value = AtlasAppState(destination = Destination.Home) @@ -398,10 +491,12 @@ class AppController( if (!debugBuild) return when (id) { "reset-first" -> scope.launch { preferencesStore.resetFirstLaunchAndConfiguration(); _state.value = _state.value.copy(firstLaunch = true, destination = Destination.Home) } - "reset-progress" -> scope.launch { database.runWriter().resetProgress(); refreshProgress(); _state.value = _state.value.copy(destination = Destination.Home, run = null) } + "reset-progress" -> enqueueRunPersistence { database.runWriter().resetProgress(); refreshProgress(); _state.value = _state.value.copy(destination = Destination.Home, run = null) } "reset-all" -> resetAll() "question-zero" -> updateRunDebug { it.copy(activeQuestion = it.activeQuestion.copy(questionRemainingMillis = 0)) } - "session-zero" -> updateRunDebug { it.copy(sessionRemainingMillis = 0, sessionExpired = true) } + "session-zero" -> updateRunDebug { + it.copy(sessionRemainingMillis = 0, sessionExpired = true, runCompletionPending = true) + } "score-100" -> updateRunDebug { it.copy(score = 100) } } } diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/catalog/CatalogLoader.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/catalog/CatalogLoader.kt index 5d45cb7..1a70185 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/catalog/CatalogLoader.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/catalog/CatalogLoader.kt @@ -42,6 +42,9 @@ object CatalogLoader { val similarityGroupsByCountry = source.similarityGroups .flatMap { group -> group.countryIds.map { it to group.id } } .groupBy({ it.first }, { it.second }) + val curatedCapitalCountryIds = source.curatedQuestions + .filter { it.id.startsWith("mixed-capital-") } + .flatMapTo(mutableSetOf()) { it.countryIds } val domainCountries = source.countries.map { country -> country.toDomain(similarityGroupsByCountry[country.id].orEmpty()) } @@ -53,11 +56,10 @@ object CatalogLoader { similarityGroupIds = similarityGroupsByCountry[country.id].orEmpty(), similarityGroups = source.similarityGroups, relatedFacts = factsByCountry[country.id].orEmpty(), + hasCuratedCapitalQuestion = country.id in curatedCapitalCountryIds, ) } - val curatedQuestions = source.curatedQuestions.map { question -> - question.toDomain(countriesById) - } + val curatedQuestions = source.curatedQuestions.map { question -> question.toDomain(countriesById) } return Catalog( schemaVersion = source.schemaVersion, contentVersion = source.catalogVersion, @@ -76,6 +78,7 @@ object CatalogLoader { similarityGroupIds: List, similarityGroups: List, relatedFacts: List, + hasCuratedCapitalQuestion: Boolean, ): List { val distractors = chooseDistractors( country = country, @@ -94,13 +97,22 @@ object CatalogLoader { require(capitalChoices.size == 4) { "Not enough unique capital choices for ${country.id}" } val difficulty = generatedDifficulty(countryIndex) val continentHint = country.region.toContinent().displayName() - val factualClue = relatedFacts.firstOrNull()?.text + val factualClue = relatedFacts.firstOrNull()?.text?.withoutAnswer(country) + val capitalFactualClue = factualClue?.takeUnless { clue -> + country.capital.names.any { capital -> clue.contains(capital, ignoreCase = true) } + } val commonHintText = buildMap { put(HintType.RevealContinent, continentHint) factualClue?.let { put(HintType.FunnyFactualClue, it) } } val factualHint = if (factualClue == null) emptySet() else setOf(HintType.FunnyFactualClue) val sharedHints = setOf(HintType.RevealContinent) + factualHint + val capitalHintText = buildMap { + put(HintType.RevealContinent, continentHint) + capitalFactualClue?.let { put(HintType.FunnyFactualClue, it) } + } + val capitalHints = setOf(HintType.RevealContinent) + + if (capitalFactualClue == null) emptySet() else setOf(HintType.FunnyFactualClue) val flagToCountry = Question.MultipleChoice( id = "generated-flag-to-country-${country.id}", prompt = "Which country does this flag belong to?", @@ -125,8 +137,12 @@ object CatalogLoader { explanation = "This is the flag of ${country.displayName}.", supportedHints = sharedHints + HintType.RemoveIncorrectOptions, hintText = commonHintText, - choices = choices.map { - Choice(id = it.id, label = it.displayName, assetId = it.flagAssetId) + choices = choices.mapIndexed { choiceIndex, choice -> + Choice( + id = choice.id, + label = "Flag ${('A'.code + choiceIndex).toChar()}", + assetId = choice.flagAssetId, + ) }, correctChoiceId = country.id, ) @@ -155,26 +171,34 @@ object CatalogLoader { skill = Skill.CapitalRecall, topic = QuizTopic.Capitals, difficulty = difficulty, - explanation = country.capital.note ?: "${country.capital.acceptedAnswer} is the capital answer used for ${country.displayName}.", - supportedHints = sharedHints + HintType.RemoveIncorrectOptions, - hintText = commonHintText, + explanation = country.capital.note ?: "${country.capital.acceptedAnswer} is the capital of ${country.displayName}.", + supportedHints = capitalHints + HintType.RemoveIncorrectOptions, + hintText = capitalHintText, choices = capitalChoices.map { Choice(it.id, it.capital.acceptedAnswer) }, correctChoiceId = country.id, ) val capitalToCountry = Question.MultipleChoice( id = "generated-capital-to-country-${country.id}", - prompt = "${country.capital.acceptedAnswer} is the capital answer for which country?", + prompt = "${country.capital.acceptedAnswer} is the capital of which country?", countryIds = listOf(country.id), skill = Skill.CapitalRecognition, topic = QuizTopic.Capitals, difficulty = difficulty, - explanation = country.capital.note ?: "${country.capital.acceptedAnswer} is the capital answer used for ${country.displayName}.", - supportedHints = sharedHints + HintType.RemoveIncorrectOptions, - hintText = commonHintText, + explanation = country.capital.note ?: "${country.capital.acceptedAnswer} is the capital of ${country.displayName}.", + supportedHints = capitalHints + HintType.RemoveIncorrectOptions, + hintText = capitalHintText, choices = choices.map { Choice(it.id, it.displayName) }, correctChoiceId = country.id, ) - return listOf(flagToCountry, countryToFlag, letterBank, countryToCapital, capitalToCountry) + return buildList { + add(flagToCountry) + add(countryToFlag) + add(letterBank) + if (country.capital.questionEligible) { + if (!hasCuratedCapitalQuestion) add(countryToCapital) + add(capitalToCountry) + } + } } private fun chooseDistractors( @@ -209,7 +233,11 @@ object CatalogLoader { val answerIndex = choices.indexOf(answer) require(answerIndex >= 0) { "Curated question $id does not contain its answer" } val primaryCountry = countryIds.firstNotNullOfOrNull(countriesById::get) - val mappedHints = supportedHints.mapTo(linkedSetOf()) { it.toHintType() } + val mappedHints = supportedHints + .mapTo(linkedSetOf()) { it.toHintType() } + .apply { + if (explanation.contains(answer, ignoreCase = true)) remove(HintType.FunnyFactualClue) + } val hintText = buildMap { if (HintType.RevealContinent in mappedHints && primaryCountry != null) { put(HintType.RevealContinent, primaryCountry.region.toContinent().displayName()) @@ -335,6 +363,14 @@ object CatalogLoader { private const val DECOY_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" private val decoder = Json { ignoreUnknownKeys = true } + + private fun String.withoutAnswer(country: SourceCountry): String { + val names = (country.acceptedAliases + country.displayName).sortedByDescending(String::length) + val redacted = names.fold(this) { text, name -> + text.replace(Regex(Regex.escape(name), RegexOption.IGNORE_CASE), "this country") + } + return redacted.replaceFirstChar { it.uppercase() } + } } @Serializable @@ -373,6 +409,7 @@ private data class SourceCapital( val kind: String, val names: List, val acceptedAnswer: String, + val questionEligible: Boolean, val note: String?, ) diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/data/PreferencesStore.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/data/PreferencesStore.kt index e2a5f23..e33d68f 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/data/PreferencesStore.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/data/PreferencesStore.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.CancellationException import okio.Path.Companion.toPath data class SavedPreferences( - val hasPreviouslyLaunched: Boolean = false, + val hasStartedTrivia: Boolean = false, val duration: String = "FiveMinutes", val topic: String = "MostlyFlags", val soundEnabled: Boolean = true, @@ -41,7 +41,7 @@ class PreferencesStore( } .map { preferences -> SavedPreferences( - hasPreviouslyLaunched = preferences[Keys.previouslyLaunched] ?: false, + hasStartedTrivia = preferences[Keys.hasStartedTrivia] ?: false, duration = preferences[Keys.duration] ?: "FiveMinutes", topic = preferences[Keys.topic] ?: "MostlyFlags", soundEnabled = preferences[Keys.soundEnabled] ?: true, @@ -51,10 +51,10 @@ class PreferencesStore( ) } - override suspend fun hasLaunchedBefore(): Boolean = values.first().hasPreviouslyLaunched + override suspend fun hasStartedTrivia(): Boolean = values.first().hasStartedTrivia - override suspend fun markLaunched() { - dataStore.edit { it[Keys.previouslyLaunched] = true } + override suspend fun markTriviaStarted() { + dataStore.edit { it[Keys.hasStartedTrivia] = true } } override suspend fun loadConfiguration(): QuizConfiguration { @@ -70,8 +70,8 @@ class PreferencesStore( } suspend fun saveConfiguration(duration: String, topic: String) { - require(QuizDuration.entries.any { it.name == duration }) { "Unknown quiz duration: $duration" } - require(QuizTopic.entries.any { it.name == topic }) { "Unknown quiz topic: $topic" } + require(QuizDuration.entries.any { it.name == duration }) { "Unknown trivia duration: $duration" } + require(QuizTopic.entries.any { it.name == topic }) { "Unknown trivia topic: $topic" } dataStore.edit { it[Keys.duration] = duration it[Keys.topic] = topic @@ -114,7 +114,7 @@ class PreferencesStore( } suspend fun resetFirstLaunch() { - dataStore.edit { it.remove(Keys.previouslyLaunched) } + dataStore.edit { it.remove(Keys.hasStartedTrivia) } } suspend fun resetConfiguration() { @@ -135,7 +135,7 @@ class PreferencesStore( suspend fun resetFirstLaunchAndConfiguration() { dataStore.edit { - it.remove(Keys.previouslyLaunched) + it.remove(Keys.hasStartedTrivia) it.remove(Keys.duration) it.remove(Keys.topic) } @@ -146,7 +146,7 @@ class PreferencesStore( } private object Keys { - val previouslyLaunched = booleanPreferencesKey("previously_launched") + val hasStartedTrivia = booleanPreferencesKey("has_started_trivia") val duration = stringPreferencesKey("quiz_duration") val topic = stringPreferencesKey("quiz_topic") val soundEnabled = booleanPreferencesKey("sound_enabled") diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasComponents.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasComponents.kt index 1cb5b4f..639763c 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasComponents.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasComponents.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn @@ -23,14 +24,22 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset @@ -59,12 +68,6 @@ import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.InternalResourceApi import org.jetbrains.compose.resources.ResourceItem import org.jetbrains.compose.resources.painterResource -import berts_atlas.composeapp.generated.resources.Res -import io.github.alexzhirkevich.compottie.Compottie -import io.github.alexzhirkevich.compottie.LottieCompositionSpec -import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState -import io.github.alexzhirkevich.compottie.rememberLottieComposition -import io.github.alexzhirkevich.compottie.rememberLottiePainter private val ScreenShape = RoundedCornerShape(24.dp) private val ControlShape = RoundedCornerShape(16.dp) @@ -77,12 +80,16 @@ fun AtlasScreen( content: @Composable BoxScope.() -> Unit, ) { Box( - modifier = modifier.fillMaxSize().background(AtlasTheme.colors.parchment), + modifier = modifier + .fillMaxSize() + .background(AtlasTheme.colors.parchment) + .windowInsetsPadding(WindowInsets.safeDrawing), contentAlignment = Alignment.TopCenter, ) { val contentModifier = Modifier .fillMaxWidth() - .widthIn(max = 760.dp) + .fillMaxHeight() + .widthIn(max = 720.dp) .padding(contentPadding) .let { if (scrollable) it.verticalScroll(rememberScrollState()) else it } Box(modifier = contentModifier, content = content) @@ -134,12 +141,22 @@ fun AtlasButton( AtlasButtonStyle.Primary, AtlasButtonStyle.Destructive -> colors.white AtlasButtonStyle.Secondary, AtlasButtonStyle.Quiet -> colors.plum } - val border = if (style == AtlasButtonStyle.Secondary) Modifier.border(2.dp, colors.atlasBlue, ControlShape) else Modifier + val border = when (style) { + AtlasButtonStyle.Secondary -> Modifier.border(1.5.dp, colors.atlasBlue.copy(alpha = .75f), ControlShape) + AtlasButtonStyle.Destructive -> Modifier.border(1.5.dp, colors.orangeAccent, ControlShape) + else -> Modifier + } + val resolvedBackground = when { + !enabled -> colors.outline.copy(alpha = .45f) + style == AtlasButtonStyle.Destructive -> colors.paper + else -> background + } + val resolvedForeground = if (style == AtlasButtonStyle.Destructive) colors.error else foreground Box( modifier = modifier .heightIn(min = 52.dp) .then(border) - .background(if (enabled) background else colors.outline, ControlShape) + .background(resolvedBackground, ControlShape) .semantics { role = Role.Button this.contentDescription = contentDescription @@ -149,7 +166,7 @@ fun AtlasButton( .padding(horizontal = 22.dp, vertical = 14.dp), contentAlignment = Alignment.Center, ) { - AtlasText(text, style = AtlasTheme.typography.label, color = foreground, textAlign = TextAlign.Center) + AtlasText(text, style = AtlasTheme.typography.label, color = resolvedForeground, textAlign = TextAlign.Center) } } @@ -163,7 +180,7 @@ fun AtlasCard( modifier = modifier .fillMaxWidth() .background(AtlasTheme.colors.paper, ScreenShape) - .border(1.dp, AtlasTheme.colors.outline.copy(alpha = .45f), ScreenShape) + .border(1.dp, AtlasTheme.colors.outline.copy(alpha = .25f), ScreenShape) .then(if (contentDescription == null) Modifier else Modifier.semantics(mergeDescendants = true) { this.contentDescription = contentDescription }) .padding(18.dp), verticalArrangement = Arrangement.spacedBy(10.dp), @@ -180,24 +197,25 @@ fun AtlasChoice( val colors = AtlasTheme.colors val background = when (choice.state) { AnswerState.Available -> colors.paper - AnswerState.Selected -> colors.mustard.copy(alpha = .28f) + AnswerState.Selected -> colors.seaGlass AnswerState.Correct -> colors.success.copy(alpha = .16f) - AnswerState.Incorrect -> colors.error.copy(alpha = .14f) + AnswerState.Incorrect -> colors.orangeAccent.copy(alpha = .18f) AnswerState.Eliminated -> colors.outline.copy(alpha = .12f) } val outline = when (choice.state) { AnswerState.Available -> colors.outline AnswerState.Selected -> colors.mustard AnswerState.Correct -> colors.success - AnswerState.Incorrect -> colors.error + AnswerState.Incorrect -> colors.orangeAccent AnswerState.Eliminated -> colors.outline.copy(alpha = .5f) } - val stateText = when (choice.state) { - AnswerState.Available -> "Available" - AnswerState.Selected -> "Selected" - AnswerState.Correct -> "Correct answer" - AnswerState.Incorrect -> "Incorrect answer" - AnswerState.Eliminated -> "Eliminated" + val stateText = when { + !choice.enabled && choice.state == AnswerState.Selected -> "Used" + choice.state == AnswerState.Available -> "Available" + choice.state == AnswerState.Selected -> "Selected" + choice.state == AnswerState.Correct -> "Correct answer" + choice.state == AnswerState.Incorrect -> "Incorrect answer" + else -> "Eliminated" } Row( modifier = modifier @@ -258,12 +276,13 @@ fun FlagImage( ), ) } + val accessibleLabel = if (flag.countryName.startsWith("Flag ", ignoreCase = true)) flag.countryName else "Flag of ${flag.countryName}" val imageModifier = modifier .aspectRatio(3f / 2f) .clip(RoundedCornerShape(8.dp)) .background(AtlasTheme.colors.paper, RoundedCornerShape(8.dp)) .border(1.dp, AtlasTheme.colors.outline.copy(alpha = .55f), RoundedCornerShape(8.dp)) - .then(if (accessible) Modifier.semantics { contentDescription = "Flag of ${flag.countryName}" } else Modifier) + .then(if (accessible) Modifier.semantics { contentDescription = accessibleLabel } else Modifier) if (validCode) { Image( painter = painterResource(resource), @@ -314,7 +333,7 @@ fun AtlasToggle(setting: SettingToggleUi, onToggle: (String, Boolean) -> Unit) { } Box( Modifier.size(width = 54.dp, height = 32.dp) - .background(if (setting.checked) AtlasTheme.colors.success else AtlasTheme.colors.outline, CircleShape) + .background(if (setting.checked) AtlasTheme.colors.success else AtlasTheme.colors.seaGlass, CircleShape) .padding(4.dp), contentAlignment = if (setting.checked) Alignment.CenterEnd else Alignment.CenterStart, ) { @@ -351,50 +370,22 @@ fun BertIllustration( StaticBertIllustration(pose, modifier, contentDescription) return } - val compositionResult = rememberLottieComposition(pose) { - LottieCompositionSpec.JsonString( - Res.readBytes("files/${pose.animationFileName}").decodeToString(), - ) - } - val composition = compositionResult.value - if (composition == null || compositionResult.isFailure) { - StaticBertIllustration(pose, modifier, contentDescription) - return - } - val progress by animateLottieCompositionAsState( - composition = composition, - iterations = Compottie.IterateForever, + val transition = rememberInfiniteTransition(label = "Bert bulb") + val rayLength by transition.animateFloat( + initialValue = .82f, + targetValue = 1.08f, + animationSpec = infiniteRepeatable(tween(800), RepeatMode.Reverse), + label = "Bulb ray length", ) - Box( - modifier = modifier.size(180.dp).semantics { this.contentDescription = contentDescription }, - ) { - StaticBertIllustration(pose, Modifier.matchParentSize(), contentDescription = null) - Image( - painter = rememberLottiePainter(composition = composition, progress = { progress }), - contentDescription = null, - modifier = Modifier.matchParentSize(), - ) - } + StaticBertIllustration(pose, modifier, contentDescription, rayLength) } -private val BertPose.animationFileName: String - get() = when (this) { - BertPose.Idle -> "bert_idle.json" - BertPose.Correct -> "bert_correct.json" - BertPose.Incorrect -> "bert_incorrect.json" - BertPose.Overtime -> "bert_overtime.json" - BertPose.Hint -> "bert_hint.json" - BertPose.Fact -> "bert_fact.json" - BertPose.Results -> "bert_results.json" - BertPose.Streak -> "bert_streak.json" - BertPose.Stamp -> "bert_stamp.json" - } - @Composable private fun StaticBertIllustration( pose: BertPose, modifier: Modifier, contentDescription: String?, + rayLength: Float = 1f, ) { val colors = AtlasTheme.colors val accessibleModifier = if (contentDescription == null) Modifier else Modifier.semantics { this.contentDescription = contentDescription } @@ -437,24 +428,75 @@ private fun StaticBertIllustration( quadraticTo(90f * scale, (if (pose == BertPose.Incorrect) 106f else 130f) * scale, 109f * scale, mouthY * scale) } drawPath(mouth, colors.plum, style = Stroke(4f * scale, cap = StrokeCap.Round)) - val accent = when (pose) { - BertPose.Correct, BertPose.Streak, BertPose.Stamp -> colors.success - BertPose.Incorrect -> colors.error - BertPose.Overtime, BertPose.Hint -> colors.mustard - BertPose.Fact, BertPose.Results, BertPose.Idle -> colors.atlasBlue - } + val accent = colors.orangeAccent drawCircle(accent, 10f * scale, p(145f, 39f)) - drawLine(accent, p(145f, 17f), p(145f, 7f), 4f * scale, StrokeCap.Round) - drawLine(accent, p(161f, 25f), p(169f, 18f), 4f * scale, StrokeCap.Round) + drawLine(accent, p(145f, 17f), p(145f, 17f - 10f * rayLength), 4f * scale, StrokeCap.Round) + drawLine(accent, p(161f, 25f), p(161f + 8f * rayLength, 25f - 7f * rayLength), 4f * scale, StrokeCap.Round) } } @Composable fun ScreenTopBar(title: String, onBack: (() -> Unit)? = null, trailing: (@Composable () -> Unit)? = null) { - Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { - if (onBack != null) AtlasButton("Back", onBack, style = AtlasButtonStyle.Quiet) - AtlasText(title, Modifier.weight(1f).semantics { heading() }, style = AtlasTheme.typography.heading) - trailing?.invoke() + Row(Modifier.fillMaxWidth().heightIn(min = 52.dp), verticalAlignment = Alignment.CenterVertically) { + if (onBack != null) AtlasBackButton(onBack) else Spacer(Modifier.size(48.dp)) + AtlasText( + title, + Modifier.weight(1f).semantics { heading() }, + style = AtlasTheme.typography.heading, + textAlign = TextAlign.Center, + ) + if (trailing == null) Spacer(Modifier.size(48.dp)) else Box(Modifier.widthIn(min = 48.dp), contentAlignment = Alignment.CenterEnd) { trailing() } + } +} + +@Composable +fun AtlasBackButton(onClick: () -> Unit, contentDescription: String = "Back") { + val color = AtlasTheme.colors.plum + Box( + Modifier.size(48.dp) + .semantics { role = Role.Button; this.contentDescription = contentDescription } + .clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Canvas(Modifier.size(24.dp)) { + drawLine(color, Offset(size.width * .7f, size.height * .16f), Offset(size.width * .3f, size.height * .5f), 3.2.dp.toPx(), StrokeCap.Round) + drawLine(color, Offset(size.width * .3f, size.height * .5f), Offset(size.width * .7f, size.height * .84f), 3.2.dp.toPx(), StrokeCap.Round) + } + } +} + +@Composable +fun AtlasExitButton(onClick: () -> Unit, contentDescription: String = "Leave trivia") { + val color = AtlasTheme.colors.plum + Box( + Modifier.size(48.dp) + .semantics { role = Role.Button; this.contentDescription = contentDescription } + .clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Canvas(Modifier.size(26.dp)) { + val stroke = 2.6.dp.toPx() + drawLine(color, Offset(size.width * .18f, size.height * .12f), Offset(size.width * .18f, size.height * .88f), stroke, StrokeCap.Round) + drawLine(color, Offset(size.width * .18f, size.height * .12f), Offset(size.width * .56f, size.height * .12f), stroke, StrokeCap.Round) + drawLine(color, Offset(size.width * .18f, size.height * .88f), Offset(size.width * .56f, size.height * .88f), stroke, StrokeCap.Round) + drawLine(color, Offset(size.width * .48f, size.height * .5f), Offset(size.width * .9f, size.height * .5f), stroke, StrokeCap.Round) + drawLine(color, Offset(size.width * .73f, size.height * .32f), Offset(size.width * .9f, size.height * .5f), stroke, StrokeCap.Round) + drawLine(color, Offset(size.width * .9f, size.height * .5f), Offset(size.width * .73f, size.height * .68f), stroke, StrokeCap.Round) + } + } +} + +@Composable +fun AtlasLinkRow(text: String, onClick: () -> Unit, destructive: Boolean = false) { + Row( + Modifier.fillMaxWidth().heightIn(min = 56.dp) + .semantics { role = Role.Button; contentDescription = text } + .clickable(onClick = onClick) + .padding(horizontal = 4.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + AtlasText(text, Modifier.weight(1f), style = AtlasTheme.typography.label, color = if (destructive) AtlasTheme.colors.error else AtlasTheme.colors.plum) + AtlasText("›", style = AtlasTheme.typography.heading, color = AtlasTheme.colors.mutedPlum) } } diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasTheme.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasTheme.kt index e22207e..ec1efa1 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasTheme.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/AtlasTheme.kt @@ -23,6 +23,8 @@ data class AtlasColors( val mutedPlum: Color, val atlasBlue: Color, val atlasBluePressed: Color, + val seaGlass: Color, + val orangeAccent: Color, val coral: Color, val mustard: Color, val success: Color, @@ -42,19 +44,30 @@ data class AtlasTypography( val timer: TextStyle, ) +private val AtlasDeepGreen = Color(0xFF27635B) +private val AtlasBlueGray = Color(0xFF778BA5) +private val AtlasSeaGlass = Color(0xFFB7CFC8) +private val AtlasShell = Color(0xFFF6E9E1) +private val AtlasSand = Color(0xFFF1DCC7) +private val AtlasPink = Color(0xFFF7BFBE) +private val AtlasPeach = Color(0xFFE9AF8B) +private val AtlasOrange = Color(0xFFE1814E) + private val LightAtlasColors = AtlasColors( - parchment = Color(0xFFF7F0DF), - paper = Color(0xFFFFFBF2), - plum = Color(0xFF3E2447), - mutedPlum = Color(0xFF69536F), - atlasBlue = Color(0xFF17647E), - atlasBluePressed = Color(0xFF0E4B60), - coral = Color(0xFFE96E5B), - mustard = Color(0xFFE3AD35), - success = Color(0xFF147568), - error = Color(0xFF9C3E32), - outline = Color(0xFF8A748E), - white = Color(0xFFFFFFFF), + parchment = AtlasShell, + paper = AtlasSand, + plum = AtlasDeepGreen, + mutedPlum = AtlasDeepGreen, + atlasBlue = AtlasDeepGreen, + atlasBluePressed = AtlasDeepGreen, + seaGlass = AtlasSeaGlass, + orangeAccent = AtlasOrange, + coral = AtlasPink, + mustard = AtlasPeach, + success = AtlasDeepGreen, + error = AtlasDeepGreen, + outline = AtlasBlueGray, + white = AtlasShell, ) private fun atlasTypography(fontFamily: FontFamily) = AtlasTypography( diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/HomeAndOptionsScreens.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/HomeAndOptionsScreens.kt index 1776015..86970a0 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/HomeAndOptionsScreens.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/HomeAndOptionsScreens.kt @@ -1,5 +1,7 @@ package app.bertsatlas.ui +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,10 +10,18 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -21,6 +31,8 @@ fun HomeScreen( isFirstLaunch: Boolean, streakDays: Int = 0, progressSummary: String = "", + totalPoints: Int = 0, + hasStartedTrivia: Boolean = !isFirstLaunch, onStart: () -> Unit, onOptions: () -> Unit = {}, onProgress: () -> Unit = {}, @@ -31,9 +43,16 @@ fun HomeScreen( Column( Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(14.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), ) { - BertIllustration(BertPose.Idle, contentDescription = "Bert smiles behind oversized glasses") + if (hasStartedTrivia) { + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + PointsBadge(totalPoints, Modifier.weight(1f)) + SettingsIconButton(onSettings) + } + } + Spacer(Modifier.height(if (hasStartedTrivia) 8.dp else 24.dp)) + HomeBertIllustration(large = !hasStartedTrivia) AtlasText( "Bert's Atlas", Modifier.semantics { heading() }, @@ -41,21 +60,85 @@ fun HomeScreen( textAlign = TextAlign.Center, ) AtlasText("Getting nerdier every day.", color = AtlasTheme.colors.mutedPlum, textAlign = TextAlign.Center) - Spacer(Modifier.height(12.dp)) + Spacer(Modifier.height(20.dp)) AtlasButton("Start", onStart, Modifier.fillMaxWidth().widthIn(max = 420.dp)) - if (!isFirstLaunch) { + if (hasStartedTrivia) { AtlasButton("Options", onOptions, Modifier.fillMaxWidth().widthIn(max = 420.dp), AtlasButtonStyle.Secondary) + SectionGap() AtlasCard(Modifier.widthIn(max = 420.dp), "Current streak: $streakDays days. $progressSummary") { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - AtlasText("$streakDays", style = AtlasTheme.typography.title, color = AtlasTheme.colors.coral) + AtlasText("✦", style = AtlasTheme.typography.title, color = AtlasTheme.colors.coral) Column(Modifier.weight(1f).padding(horizontal = 12.dp)) { - AtlasText(if (streakDays == 1) "day curious" else "days curious", style = AtlasTheme.typography.label) + AtlasText(if (streakDays == 1) "1 curious day" else "$streakDays curious days", style = AtlasTheme.typography.label) AtlasText(progressSummary, style = AtlasTheme.typography.detail, color = AtlasTheme.colors.mutedPlum) } - AtlasButton("Progress", onProgress, style = AtlasButtonStyle.Quiet) } } - AtlasButton("Settings", onSettings, style = AtlasButtonStyle.Quiet) + AtlasButton("See your progress", onProgress, style = AtlasButtonStyle.Quiet) + } + } + } +} + +@Composable +private fun HomeBertIllustration(large: Boolean) { + val size = if (large) 270.dp else 200.dp + val colors = AtlasTheme.colors + androidx.compose.foundation.layout.Box(Modifier.size(size), contentAlignment = Alignment.Center) { + BertIllustration(BertPose.Idle, Modifier.fillMaxWidth(), contentDescription = "Bert smiles with a bright idea and a folded map") + Canvas(Modifier.fillMaxWidth().size(size)) { + val w = this.size.width + val h = this.size.height + val outline = 4.dp.toPx() + val map = Path().apply { + moveTo(w * .04f, h * .68f) + lineTo(w * .14f, h * .63f) + lineTo(w * .25f, h * .68f) + lineTo(w * .36f, h * .63f) + lineTo(w * .36f, h * .88f) + lineTo(w * .25f, h * .93f) + lineTo(w * .14f, h * .88f) + lineTo(w * .04f, h * .93f) + close() + } + drawPath(map, colors.paper) + drawPath(map, colors.plum, style = Stroke(outline, join = androidx.compose.ui.graphics.StrokeJoin.Round)) + drawLine(colors.plum, Offset(w * .14f, h * .63f), Offset(w * .14f, h * .88f), outline) + drawLine(colors.plum, Offset(w * .25f, h * .68f), Offset(w * .25f, h * .93f), outline) + drawLine(colors.orangeAccent, Offset(w * .06f, h * .83f), Offset(w * .34f, h * .76f), outline * .7f, StrokeCap.Round) + } + } +} + +@Composable +private fun PointsBadge(points: Int, modifier: Modifier = Modifier) { + val colors = AtlasTheme.colors + Row(modifier, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Canvas(Modifier.size(24.dp)) { + drawCircle(colors.mustard, size.minDimension * .40f) + drawCircle(colors.plum, size.minDimension * .40f, style = Stroke(2.4.dp.toPx())) + drawCircle(colors.plum, size.minDimension * .10f) + } + AtlasText(points.toString(), style = AtlasTheme.typography.heading, color = colors.plum) + } +} + +@Composable +private fun SettingsIconButton(onClick: () -> Unit) { + val color = AtlasTheme.colors.plum + androidx.compose.foundation.layout.Box( + Modifier.size(48.dp).semantics { role = Role.Button; contentDescription = "Open settings" }.clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Canvas(Modifier.size(26.dp)) { + val center = Offset(size.width / 2, size.height / 2) + drawCircle(color, size.minDimension * .27f, center, style = Stroke(3.dp.toPx())) + drawCircle(color, size.minDimension * .07f, center) + repeat(8) { index -> + val angle = index * kotlin.math.PI.toFloat() / 4f + val start = Offset(center.x + kotlin.math.cos(angle) * size.width * .31f, center.y + kotlin.math.sin(angle) * size.height * .31f) + val end = Offset(center.x + kotlin.math.cos(angle) * size.width * .43f, center.y + kotlin.math.sin(angle) * size.height * .43f) + drawLine(color, start, end, 3.dp.toPx(), StrokeCap.Round) } } } @@ -120,7 +203,7 @@ fun RecoveryScreen( AtlasText(questionSummary, style = AtlasTheme.typography.detail, color = AtlasTheme.colors.mutedPlum) } AtlasButton("Resume run", onResume, Modifier.fillMaxWidth()) - AtlasButton("Leave this run", onAbandon, Modifier.fillMaxWidth(), AtlasButtonStyle.Quiet) + AtlasButton("Leave this trivia run", onAbandon, Modifier.fillMaxWidth(), AtlasButtonStyle.Quiet) AtlasText("Answered questions still count toward what Bert revisits next.", style = AtlasTheme.typography.detail, color = AtlasTheme.colors.mutedPlum, textAlign = TextAlign.Center) } } diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/QuizScreens.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/QuizScreens.kt index b64fdfe..9fa6cc0 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/QuizScreens.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/QuizScreens.kt @@ -7,7 +7,9 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding @@ -31,12 +33,12 @@ fun QuizScreen( prompt: String, imageDescription: String?, score: Int, - sessionTime: String, questionTime: String, timerExpired: Boolean, choices: List = emptyList(), letterSlots: List = emptyList(), letterTiles: List = emptyList(), + appliedHintMessages: List = emptyList(), hintAvailable: Boolean, unlimited: Boolean, canSubmit: Boolean = false, @@ -50,46 +52,46 @@ fun QuizScreen( onLeave: () -> Unit, modifier: Modifier = Modifier, promptFlag: FlagUi? = null, + runProgress: Float? = null, + runProgressDescription: String = "Trivia progress", + overtimeMessage: String = "Time's up. Keep going for half points", ) { - AtlasScreen(modifier) { - Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(14.dp)) { - BoxWithConstraints(Modifier.fillMaxWidth()) { - if (maxWidth < 480.dp) { - Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { - AtlasButton("Leave", onLeave, style = AtlasButtonStyle.Quiet) - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - QuizMetric("Score", score.toString(), Modifier.weight(1f)) - QuizMetric(if (unlimited) "Elapsed" else "Run", sessionTime, Modifier.weight(1f)) - QuizMetric("Question", questionTime, Modifier.weight(1f), warning = timerExpired) - } - } - } else { - Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { - AtlasButton("Leave", onLeave, style = AtlasButtonStyle.Quiet) - QuizMetric("Score", score.toString(), Modifier.weight(1f)) - QuizMetric(if (unlimited) "Elapsed" else "Run", sessionTime, Modifier.weight(1f)) - QuizMetric("Question", questionTime, Modifier.weight(1f), warning = timerExpired) - } - } + AtlasScreen(modifier, contentPadding = PaddingValues(0.dp)) { + Column(Modifier.fillMaxWidth()) { + runProgress?.let { SubtleRunProgress(it, runProgressDescription) } + Column( + Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Row(Modifier.fillMaxWidth().heightIn(min = 56.dp), verticalAlignment = Alignment.CenterVertically) { + QuizMetric("Score", score.toString(), Modifier.weight(1f)) + QuizMetric("Question", questionTime, Modifier.weight(1f), warning = timerExpired) + AtlasExitButton(onLeave) } if (timerExpired) { - AtlasText("Overtime · you can still answer for half points", color = AtlasTheme.colors.error, style = AtlasTheme.typography.label) + AtlasText(overtimeMessage, Modifier.fillMaxWidth(), color = AtlasTheme.colors.error, style = AtlasTheme.typography.label, textAlign = TextAlign.Center) } if (promptFlag != null || imageDescription != null) { - AtlasCard(contentDescription = imageDescription ?: "Flag of ${promptFlag?.countryName}") { - Box(Modifier.fillMaxWidth().heightIn(min = 116.dp), contentAlignment = Alignment.Center) { + Box( + Modifier.fillMaxWidth().heightIn(min = 148.dp) + .semantics { contentDescription = imageDescription ?: "Flag to identify" }, + contentAlignment = Alignment.Center, + ) { + Box(Modifier.width(230.dp).padding(8.dp), contentAlignment = Alignment.Center) { if (promptFlag != null) { - FlagImage(promptFlag, Modifier.fillMaxWidth().heightIn(min = 116.dp)) + FlagImage(promptFlag, Modifier.fillMaxWidth(), accessible = false, showFallbackLabel = false) } else { AtlasText("⚑", style = AtlasTheme.typography.display, color = AtlasTheme.colors.atlasBlue) } } - imageDescription?.let { - AtlasText(it, Modifier.fillMaxWidth(), style = AtlasTheme.typography.detail, color = AtlasTheme.colors.mutedPlum, textAlign = TextAlign.Center) - } } } - AtlasHeading(prompt) + AtlasHeading(prompt, Modifier.fillMaxWidth(), centered = true) + appliedHintMessages.forEach { message -> + AtlasCard(contentDescription = "Applied hint: $message") { + AtlasText("Hint: $message", color = AtlasTheme.colors.atlasBlue) + } + } if (choices.isNotEmpty()) ResponsiveChoices(choices, onChoice) if (letterSlots.isNotEmpty() || letterTiles.isNotEmpty()) { LetterBank(letterSlots, letterTiles, onLetter, onRemoveLetter) @@ -99,21 +101,42 @@ fun QuizScreen( } } Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - AtlasButton("Hint", onHint, Modifier.weight(1f), AtlasButtonStyle.Secondary, enabled = hintAvailable) + AtlasButton("Get a hint", onHint, Modifier.weight(1f), AtlasButtonStyle.Secondary, enabled = hintAvailable) if (unlimited) AtlasButton("Finish", onFinish, Modifier.weight(1f), AtlasButtonStyle.Quiet) } + } } } } @Composable private fun QuizMetric(label: String, value: String, modifier: Modifier, warning: Boolean = false) { - Column(modifier.semantics(mergeDescendants = true) { contentDescription = "$label: $value" }, horizontalAlignment = Alignment.CenterHorizontally) { - AtlasText(value, style = AtlasTheme.typography.timer, color = if (warning) AtlasTheme.colors.error else AtlasTheme.colors.plum, textAlign = TextAlign.Center) + Column( + modifier.semantics(mergeDescendants = true) { contentDescription = "$label: $value" }, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AtlasText( + value, + style = AtlasTheme.typography.timer, + color = if (warning) AtlasTheme.colors.error else AtlasTheme.colors.plum, + textAlign = TextAlign.Center, + ) AtlasText(label, style = AtlasTheme.typography.detail, color = AtlasTheme.colors.mutedPlum, textAlign = TextAlign.Center) } } +@Composable +private fun SubtleRunProgress(progress: Float, description: String) { + val value = progress.coerceIn(0f, 1f) + Box( + Modifier.fillMaxWidth().heightIn(min = 4.dp) + .background(AtlasTheme.colors.outline.copy(alpha = .14f), RoundedCornerShape(4.dp)) + .semantics { contentDescription = description; stateDescription = "${(value * 100).toInt()} percent" }, + ) { + Box(Modifier.fillMaxWidth(value).heightIn(min = 4.dp).background(AtlasTheme.colors.coral, RoundedCornerShape(4.dp))) + } +} + @Composable private fun ResponsiveChoices(choices: List, onChoice: (String) -> Unit) { BoxWithConstraints(Modifier.fillMaxWidth()) { @@ -144,27 +167,51 @@ private fun LetterBank( val slot = slots[index] val label = slot.character ?: "blank" Box( - Modifier.size(52.dp).background(AtlasTheme.colors.paper, RoundedCornerShape(12.dp)) - .border(2.dp, AtlasTheme.colors.atlasBlue, RoundedCornerShape(12.dp)) + Modifier.size(48.dp) .semantics { contentDescription = "Answer position ${slot.position + 1}: $label" } .clickable(enabled = slot.tileId != null && !slot.fixed) { slot.tileId?.let(onRemoveLetter) }, contentAlignment = Alignment.Center, - ) { AtlasText(slot.character ?: "_", style = AtlasTheme.typography.heading) } + ) { + Box( + Modifier.size(38.dp) + .background(AtlasTheme.colors.paper, RoundedCornerShape(10.dp)) + .border(2.dp, AtlasTheme.colors.atlasBlue, RoundedCornerShape(10.dp)), + contentAlignment = Alignment.Center, + ) { + AtlasText(slot.character ?: "_", style = AtlasTheme.typography.heading) + } + } } WrappingCells(tiles.size) { index -> val tile = tiles[index] val available = tile.enabled && tile.placedSlot == null Box( - Modifier.size(52.dp).background(if (available) AtlasTheme.colors.mustard.copy(alpha = .3f) else AtlasTheme.colors.outline.copy(alpha = .12f), RoundedCornerShape(12.dp)) - .border(1.dp, AtlasTheme.colors.outline, RoundedCornerShape(12.dp)) + Modifier.size(48.dp) .semantics { role = Role.Button contentDescription = "Letter ${tile.letter}" - stateDescription = if (available) "Available" else "Placed" + stateDescription = when { + available -> "Available" + !tile.enabled -> "Removed" + else -> "Placed" + } } .clickable(enabled = available) { onLetter(tile.id) }, contentAlignment = Alignment.Center, - ) { AtlasText(tile.letter, style = AtlasTheme.typography.label) } + ) { + Box( + Modifier.size(44.dp) + .background( + if (available) AtlasTheme.colors.mustard.copy(alpha = .3f) + else AtlasTheme.colors.outline.copy(alpha = .12f), + RoundedCornerShape(11.dp), + ) + .border(1.dp, AtlasTheme.colors.outline, RoundedCornerShape(11.dp)), + contentAlignment = Alignment.Center, + ) { + AtlasText(tile.letter, style = AtlasTheme.typography.label) + } + } } } } @@ -172,7 +219,7 @@ private fun LetterBank( @Composable private fun WrappingCells(count: Int, content: @Composable (Int) -> Unit) { BoxWithConstraints(Modifier.fillMaxWidth()) { - val columns = (maxWidth / 60.dp).toInt().coerceAtLeast(1) + val columns = (maxWidth / 56.dp).toInt().coerceAtLeast(1) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { (0 until count).chunked(columns).forEach { indices -> Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { @@ -190,15 +237,17 @@ fun HintChooserScreen( onChooseHint: (String) -> Unit, onCancel: () -> Unit, modifier: Modifier = Modifier, + introText: String = "Choose the clue that sounds most useful.", + chargedHintLabel: String = "−25 points", ) { AtlasScreen(modifier) { - Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(14.dp), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally) { ScreenTopBar("Choose a hint", onCancel) - BertIllustration(BertPose.Hint) + BertIllustration(BertPose.Hint, Modifier.size(150.dp)) AtlasHeading("A little nudge?", Modifier.fillMaxWidth(), centered = true) - AtlasText("Every hint costs up to 25 points. Your score can never drop below zero.", textAlign = TextAlign.Center) - AtlasCard(contentDescription = "Current score $score. Score after hint ${maxOf(0, score - 25)}") { - StatRow(listOf(StatUi("Now", score.toString()), StatUi("After hint", maxOf(0, score - 25).toString()))) + AtlasText(introText, textAlign = TextAlign.Center, color = AtlasTheme.colors.mutedPlum) + if (score > 0) { + AtlasText(chargedHintLabel, style = AtlasTheme.typography.label, color = AtlasTheme.colors.outline, textAlign = TextAlign.Center) } hints.forEach { AtlasChoice(it, onChooseHint) } } @@ -217,12 +266,14 @@ fun FeedbackScreen( autoAdvanceMessage: String?, onContinue: () -> Unit, modifier: Modifier = Modifier, + continueLabel: String = "Continue", ) { val pose = when { overtime && correct -> BertPose.Overtime; correct -> BertPose.Correct; else -> BertPose.Incorrect } val status = when { overtime && correct -> "Overtime correct"; correct -> "Correct"; else -> "Incorrect" } AtlasScreen(modifier) { - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(14.dp)) { - BertIllustration(pose, contentDescription = "Bert reacts: $status") + Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp)) { + Spacer(Modifier.heightIn(min = 8.dp)) + BertIllustration(pose, Modifier.size(190.dp), contentDescription = "Bert reacts: $status") AtlasHeading(heading, Modifier.fillMaxWidth(), centered = true) AtlasText( if (correct) "+$pointsAwarded points" else "No points this time", @@ -235,7 +286,7 @@ fun FeedbackScreen( explanation?.let { AtlasText(it) } } if (overtime && correct) AtlasText("The clock gave up. You didn't. Half points.", textAlign = TextAlign.Center) - AtlasButton("Continue", onContinue, Modifier.fillMaxWidth()) + AtlasButton(continueLabel, onContinue, Modifier.fillMaxWidth()) autoAdvanceMessage?.let { AtlasText(it, style = AtlasTheme.typography.detail, color = AtlasTheme.colors.mutedPlum, textAlign = TextAlign.Center) } } } @@ -249,14 +300,39 @@ fun FunFactScreen( artDescription: String? = null, onContinue: () -> Unit, modifier: Modifier = Modifier, + kicker: String = "Fun fact about", + continueLabel: String = "Continue", ) { AtlasScreen(modifier) { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp)) { BertIllustration(BertPose.Fact, contentDescription = "Bert has found an oddly specific fact") - AtlasText("FIELD NOTE · $relatedTo", style = AtlasTheme.typography.label, color = AtlasTheme.colors.atlasBlue) + AtlasText("${kicker} $relatedTo", style = AtlasTheme.typography.label, color = AtlasTheme.colors.atlasBlue) AtlasHeading(heading, Modifier.fillMaxWidth(), centered = true) AtlasCard(contentDescription = artDescription) { AtlasText(fact, style = AtlasTheme.typography.heading, textAlign = TextAlign.Center) } - AtlasButton("Back to the atlas", onContinue, Modifier.fillMaxWidth()) + AtlasButton(continueLabel, onContinue, Modifier.fillMaxWidth()) + } + } +} + +@Composable +fun LeaveQuizConfirmationScreen( + onCancel: () -> Unit, + onLeave: () -> Unit, + modifier: Modifier = Modifier, + title: String = "Leave this trivia run?", + body: String = "Your answered questions will stay in your practice progress. Ending now won't count toward your personal best or daily streak.", +) { + AtlasScreen(modifier, scrollable = false) { + Column( + Modifier.fillMaxWidth().align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + BertIllustration(BertPose.Hint, Modifier.size(160.dp), contentDescription = "Bert keeps your practice progress safe") + AtlasHeading(title, Modifier.fillMaxWidth(), centered = true) + AtlasText(body, Modifier.fillMaxWidth(), color = AtlasTheme.colors.mutedPlum, textAlign = TextAlign.Center) + AtlasButton("Keep playing", onCancel, Modifier.fillMaxWidth()) + AtlasButton("Leave trivia", onLeave, Modifier.fillMaxWidth(), AtlasButtonStyle.Destructive) } } } diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/ResultsAndProgressScreens.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/ResultsAndProgressScreens.kt index 44367c8..13818b6 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/ResultsAndProgressScreens.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/ResultsAndProgressScreens.kt @@ -31,11 +31,28 @@ fun ResultsScreen( onPlayAgain: () -> Unit, onHome: () -> Unit, modifier: Modifier = Modifier, + abandoned: Boolean = false, ) { AtlasScreen(modifier) { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp)) { - BertIllustration(BertPose.Results, contentDescription = "Bert celebrates a completed atlas run") - AtlasHeading(if (newPersonalBest) "New personal best" else "Run complete", Modifier.fillMaxWidth(), centered = true) + BertIllustration(if (abandoned) BertPose.Hint else BertPose.Results, contentDescription = if (abandoned) "Bert keeps your practice progress" else "Bert celebrates completed trivia") + AtlasHeading( + when { + abandoned -> "Trivia ended" + newPersonalBest -> "New personal best" + else -> "Trivia complete" + }, + Modifier.fillMaxWidth(), + centered = true, + ) + if (abandoned) { + AtlasText( + "Your answers still shaped what you'll practise next. This trivia run didn't count toward your personal best or daily streak.", + Modifier.fillMaxWidth(), + color = AtlasTheme.colors.mutedPlum, + textAlign = TextAlign.Center, + ) + } AtlasText(score.toString(), style = AtlasTheme.typography.display, color = AtlasTheme.colors.atlasBlue) AtlasText("points", color = AtlasTheme.colors.mutedPlum) StatRow( @@ -46,7 +63,7 @@ fun ResultsScreen( ), ) AtlasCard { - AtlasText("Run notes", style = AtlasTheme.typography.heading) + AtlasText("Highlights", style = AtlasTheme.typography.heading) AtlasText("Average response · $averageResponseTime") AtlasText("New or improved knowledge · $masteryImproved") } @@ -56,7 +73,7 @@ fun ResultsScreen( AtlasText(revisitCountries.joinToString(" · "), color = AtlasTheme.colors.mutedPlum) } } - AtlasButton("Play again", onPlayAgain, Modifier.fillMaxWidth()) + AtlasButton(if (abandoned) "Start new trivia" else "Play again", onPlayAgain, Modifier.fillMaxWidth()) AtlasButton("Home", onHome, Modifier.fillMaxWidth(), AtlasButtonStyle.Secondary) } } diff --git a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/SettingsScreens.kt b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/SettingsScreens.kt index 12f18db..844c6dd 100644 --- a/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/SettingsScreens.kt +++ b/composeApp/src/commonMain/kotlin/app/bertsatlas/ui/SettingsScreens.kt @@ -33,12 +33,15 @@ fun SettingsScreen( AtlasScreen(modifier) { Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) { ScreenTopBar("Settings", onBack) + SectionGap() AtlasCard { settings.forEach { AtlasToggle(it, onToggle) } } - AtlasButton("Privacy", onPrivacy, Modifier.fillMaxWidth(), AtlasButtonStyle.Secondary) - AtlasButton("Credits and licenses", onCredits, Modifier.fillMaxWidth(), AtlasButtonStyle.Secondary) - AtlasButton("Reset local data", onReset, Modifier.fillMaxWidth(), AtlasButtonStyle.Destructive) + AtlasCard { + AtlasLinkRow("Privacy", onPrivacy) + AtlasLinkRow("Credits and licenses", onCredits) + AtlasLinkRow("Reset local data", onReset, destructive = true) + } AtlasText( "Bert's Atlas $version", Modifier.fillMaxWidth() @@ -84,7 +87,7 @@ fun PrivacyScreen(onBack: () -> Unit, modifier: Modifier = Modifier) { title = "Privacy", paragraphs = listOf( "Bert's Atlas works entirely offline. It does not ask for an account or personal information.", - "Your quiz history, progress, streaks, stamps, and preferences stay on this device. The app has no advertising, analytics, remote configuration, or product telemetry.", + "Your trivia history, progress, streaks, stamps, and preferences stay on this device. The app has no advertising, analytics, remote configuration, or product telemetry.", "Clearing app data or deleting the app may permanently remove your progress. Operating-system and store diagnostics are controlled by your platform settings.", ), onBack = onBack, diff --git a/content/assets-and-sources.md b/content/assets-and-sources.md index 8e7a674..0d4bfb7 100644 --- a/content/assets-and-sources.md +++ b/content/assets-and-sources.md @@ -16,6 +16,10 @@ The initial draft was normalized from datasets/country-codes at commit `6a595f1a The aggregate commit is a reproducibility aid, not an editorial signoff. Capital arrangements with ambiguity or recent change are explicitly marked and removed from one-answer question eligibility. +Land-border draft facts use the REST Countries V4 data pinned at commit `43f6260db10c54df9972e6e554ee14c2e7c85ef5`. These are concise fallback copy, not inferred lessons about UN regions. Editors must check disputed boundaries and whether overseas territories affect a claim before approval. + +The early-play pool has twelve more varied draft facts sourced directly from public institutions: Geoscience Australia, Brazil's IBGE, the governments of Canada, India, Japan, Mexico, South Africa, New Zealand, Nepal and the Netherlands, Royal Museums Greenwich, and the archived CIA World Factbook. Their exact page URLs and retrieval snapshots are recorded in the catalog. A source link proves provenance; it does not constitute fact or copy approval. + Primary review locations: - United Nations member states: https://www.un.org/en/about-us/member-states diff --git a/content/brand/berts-atlas-mark.svg b/content/brand/berts-atlas-mark.svg new file mode 100644 index 0000000..08af621 --- /dev/null +++ b/content/brand/berts-atlas-mark.svg @@ -0,0 +1,14 @@ + + Bert's Atlas mark + Smiling Bert in green glasses with a small folded paper map at his top right. + + + + + + + + + + + diff --git a/content/brand/berts-atlas-splash.svg b/content/brand/berts-atlas-splash.svg new file mode 100644 index 0000000..e31cb2b --- /dev/null +++ b/content/brand/berts-atlas-splash.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/content/voice-and-feedback-recommendations.md b/content/voice-and-feedback-recommendations.md new file mode 100644 index 0000000..65c3691 --- /dev/null +++ b/content/voice-and-feedback-recommendations.md @@ -0,0 +1,24 @@ +# Feedback voice recommendations + +Duolingo's public product writing consistently puts the answer first, keeps the immediate interaction small, offers explanation as a second layer, and makes the end of a session encouraging while still naming what needs practice. That pattern is useful here, but Bert's Atlas should keep an original travel-and-curiosity voice rather than imitate Duolingo's phrases. + +Research basis: + +- Duolingo, “Explain My Answer is now free for all learners”: https://blog.duolingo.com/explain-my-answer-now-free/ +- Duolingo, “Introducing Duolingo Flashcards”: https://blog.duolingo.com/duolingo-flashcards/ +- Duolingo 101: https://blog.duolingo.com/duolingo-101-how-to-learn-a-language-on-duolingo/ +- Duolingo, “How we improved the streak”: https://blog.duolingo.com/improving-the-streak/ + +Recommended original patterns: + +- Correct: “Flag filed. Nice work.” +- Incorrect: “That was {answer}. Look for {short distinction}.” +- Overtime: “Time's up. You still found it.” +- Continent hint: “Map clue: {continent}.” +- Choice hint: “Two decoys are leaving.” +- Fact hint: “Bert's clue: {answer-safe fact}.” +- Results: “Atlas packed: {score} points · {accuracy}%.” +- Review: “Worth another look: {countries}.” +- Streak: “{days} curious days in a row.” + +Keep feedback to one idea per line. Name the correct answer after a miss, but never include it in a pre-answer clue. Avoid shame, exaggerated celebration, and claims that a streak proves mastery. diff --git a/domain/src/commonMain/kotlin/app/bertsatlas/domain/Contracts.kt b/domain/src/commonMain/kotlin/app/bertsatlas/domain/Contracts.kt index 3707f46..1e5e662 100644 --- a/domain/src/commonMain/kotlin/app/bertsatlas/domain/Contracts.kt +++ b/domain/src/commonMain/kotlin/app/bertsatlas/domain/Contracts.kt @@ -55,8 +55,8 @@ data class PlayerSettings( ) interface LaunchPreferences { - suspend fun hasLaunchedBefore(): Boolean - suspend fun markLaunched() + suspend fun hasStartedTrivia(): Boolean + suspend fun markTriviaStarted() } interface QuizConfigurationPreferences { diff --git a/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuestionSelector.kt b/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuestionSelector.kt index 145c6eb..6f48ca6 100644 --- a/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuestionSelector.kt +++ b/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuestionSelector.kt @@ -27,10 +27,22 @@ class QuestionSelector( val topicQuestions = questions.filter { matchesTopic(it, topic) } require(topicQuestions.isNotEmpty()) { "No questions are available for $topic" } - val withoutRecentFive = topicQuestions.filter { it.id !in recentQuestionIds.takeLast(5) } - val withoutImmediateRepeat = topicQuestions.filter { it.id != recentQuestionIds.lastOrNull() } + val priorQuestionIds = recentQuestionIds.takeLast(RECENT_QUESTION_WINDOW).toSet() + val priorQuestions = questions.filter { it.id in priorQuestionIds } + val immediateQuestion = questions.firstOrNull { it.id == recentQuestionIds.lastOrNull() } + val withoutRecentConcepts = topicQuestions.filter { candidate -> + candidate.id !in priorQuestionIds && priorQuestions.none { recent -> candidate.repeatsConceptFrom(recent) } + } + val withoutImmediateConcept = topicQuestions.filter { candidate -> + candidate.id != immediateQuestion?.id && + (immediateQuestion == null || !candidate.repeatsConceptFrom(immediateQuestion)) + } + val withoutPriorQuestions = topicQuestions.filter { it.id !in priorQuestionIds } + val withoutImmediateRepeat = topicQuestions.filter { it.id != immediateQuestion?.id } val candidates = when { - withoutRecentFive.isNotEmpty() -> withoutRecentFive + withoutRecentConcepts.isNotEmpty() -> withoutRecentConcepts + withoutImmediateConcept.isNotEmpty() -> withoutImmediateConcept + withoutPriorQuestions.isNotEmpty() -> withoutPriorQuestions withoutImmediateRepeat.isNotEmpty() -> withoutImmediateRepeat else -> topicQuestions } @@ -41,6 +53,24 @@ class QuestionSelector( return SelectedQuestion(weighted[random.value], random.nextState) } + private fun Question.repeatsConceptFrom(other: Question): Boolean { + if (countryIds.none { it in other.countryIds }) return false + val family = skill.selectionFamily() + return family != SelectionFamily.Mixed && family == other.skill.selectionFamily() + } + + private fun Skill.selectionFamily(): SelectionFamily = when (this) { + Skill.FlagRecognition, + Skill.CountryToFlag, + -> SelectionFamily.Flag + Skill.CapitalRecall, + Skill.CapitalRecognition, + -> SelectionFamily.Capital + Skill.MixedGeography, + Skill.FlagSymbolism, + -> SelectionFamily.Mixed + } + private fun matchesTopic(question: Question, topic: QuizTopic): Boolean = when (topic) { QuizTopic.MostlyFlags -> true QuizTopic.Capitals -> question.skill == Skill.CapitalRecall || @@ -71,14 +101,14 @@ class QuestionSelector( } val topicWeight = when (topic) { QuizTopic.MostlyFlags -> when (question.skill) { - Skill.FlagRecognition -> 4 - Skill.CountryToFlag -> 2 + Skill.FlagRecognition -> 8 + Skill.CountryToFlag -> 6 Skill.CapitalRecall, Skill.CapitalRecognition, - -> 8 + -> 1 Skill.MixedGeography, Skill.FlagSymbolism, - -> 8 + -> 2 } QuizTopic.Capitals -> 1 QuizTopic.MixedGeography -> when (question.skill) { @@ -96,3 +126,11 @@ class QuestionSelector( return (difficultyWeight + reviewWeight) * topicWeight } } + +private const val RECENT_QUESTION_WINDOW = 5 + +private enum class SelectionFamily { + Flag, + Capital, + Mixed, +} diff --git a/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuizEngine.kt b/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuizEngine.kt index b093f80..68f1764 100644 --- a/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuizEngine.kt +++ b/domain/src/commonMain/kotlin/app/bertsatlas/domain/QuizEngine.kt @@ -107,13 +107,20 @@ class QuizEngine( require(state.sessionRemainingMillis == null || state.sessionRemainingMillis >= 0) { "A snapshot has an invalid session timer" } + val completionPending = state.runCompletionPending || + state.sessionExpired || + (state.configuration.duration != QuizDuration.Unlimited && state.sessionRemainingMillis == 0L) if (state.phase == RunPhase.Paused) { require(state.pausedFromPhase in PAUSABLE_PHASES) { "A paused snapshot has no resumable phase" } - return state.copy(tickingSinceMillis = null) + return state.copy( + runCompletionPending = completionPending, + tickingSinceMillis = null, + ) } return state.copy( phase = RunPhase.Paused, pausedFromPhase = state.phase, + runCompletionPending = completionPending, tickingSinceMillis = null, ) } @@ -130,7 +137,7 @@ class QuizEngine( } else { 0 } - val hinted = timed.activeQuestion.hint != null + val hinted = timed.activeQuestion.appliedHints.isNotEmpty() val overtime = evaluation.correct && timed.activeQuestion.questionRemainingMillis == 0L val attempt = Attempt( id = "${timed.runId}:${timed.attempts.size + 1}", @@ -147,10 +154,11 @@ class QuizEngine( pointsAwarded = points, answeredAtEpochMillis = wallClock.nowEpochMillis(), localEpochDay = localDateProvider.currentEpochDay(), - hintType = timed.activeQuestion.hint?.type, + hintType = timed.activeQuestion.appliedHints.lastOrNull()?.type, hintPointsDeducted = timed.activeQuestion.hintPointsDeducted, questionWindowMillis = timed.activeQuestion.questionWindowMillis, questionRemainingMillis = timed.activeQuestion.questionRemainingMillis, + hintTypes = timed.activeQuestion.usedHintTypes, ) val correctStreak = if (evaluation.correct) timed.currentCorrectStreak + 1 else 0 return timed.copy( @@ -180,8 +188,9 @@ class QuizEngine( fun openHintChooser(state: QuizRunState): QuizRunState { requireAnswering(state) - require(state.activeQuestion.hint == null) { "A hint has already been used for this question" } - require(state.activeQuestion.question.supportedHints.isNotEmpty()) { "This question does not offer hints" } + require(state.activeQuestion.question.supportedHints.any { it !in state.activeQuestion.usedHintTypes }) { + "Every available hint has already been used for this question" + } return freezeClocks(state).copy(phase = RunPhase.HintChooser) } @@ -189,11 +198,14 @@ class QuizEngine( requireActive(state) require(state.phase == RunPhase.HintChooser) { "Hints can only be chosen from the hint chooser" } require(type in state.activeQuestion.question.supportedHints) { "This question does not support $type" } - require(state.activeQuestion.hint == null) { "A hint has already been used for this question" } + require(type !in state.activeQuestion.usedHintTypes) { "$type has already been used for this question" } val scoreAfterHint = Scoring.deductHint(state.score) - val hintedQuestion = applyHint(state.activeQuestion, type).copy( - hintPointsDeducted = state.score - scoreAfterHint, + val appliedQuestion = applyHint(state.activeQuestion, type) + val chosenHint = requireNotNull(appliedQuestion.hint) + val hintedQuestion = appliedQuestion.copy( + usedHints = state.activeQuestion.appliedHints + chosenHint, + hintPointsDeducted = state.activeQuestion.hintPointsDeducted + state.score - scoreAfterHint, ) return state.copy( phase = RunPhase.Answering, @@ -207,8 +219,9 @@ class QuizEngine( requireActive(state) require(state.phase == RunPhase.Feedback) { "Feedback is not visible" } if (state.runAbandonmentPending) return abandonRun(state) - if (state.runCompletionPending || shouldShowScheduledFact(state)) { - return showRelatedFact(state, finalFact = state.runCompletionPending, mastery = mastery) + val timedRunComplete = state.runCompletionPending || state.sessionExpired + if (timedRunComplete || shouldShowScheduledFact(state)) { + return showRelatedFact(state, finalFact = timedRunComplete, mastery = mastery) } return moveToNextQuestion(state, mastery) } @@ -292,6 +305,23 @@ class QuizEngine( } } + fun finishExpiredRun(state: QuizRunState): QuizRunState { + requireActive(state) + require(state.configuration.duration != QuizDuration.Unlimited) { "Unlimited trivia does not expire" } + val frozen = freezeClocks(state) + require(frozen.sessionExpired || frozen.sessionRemainingMillis == 0L) { "Trivia time has not expired" } + return frozen.copy( + phase = RunPhase.Completed, + status = RunStatus.Completed, + feedback = null, + visibleFact = null, + finalFactPending = false, + runCompletionPending = false, + runAbandonmentPending = false, + tickingSinceMillis = null, + ) + } + fun abandonRun(state: QuizRunState): QuizRunState { requireActive(state) return freezeClocks(state).copy( @@ -329,7 +359,11 @@ class QuizEngine( requireAnswering(state) val timed = advanceClocks(state) require(timed.activeQuestion.question is Question.LetterBank) { "The active question is not a letter bank" } - require(timed.activeQuestion.placements.any { it.slotIndex == slotIndex }) { "The answer slot is empty" } + val placement = timed.activeQuestion.placements.firstOrNull { it.slotIndex == slotIndex } + ?: error("The answer slot is empty") + require(placement.tileId !in timed.activeQuestion.revealedTileIds()) { + "A revealed letter cannot be removed" + } return timed.copy( activeQuestion = timed.activeQuestion.copy( placements = timed.activeQuestion.placements.filterNot { it.slotIndex == slotIndex }, @@ -341,7 +375,12 @@ class QuizEngine( requireAnswering(state) val timed = advanceClocks(state) require(timed.activeQuestion.question is Question.LetterBank) { "The active question is not a letter bank" } - return timed.copy(activeQuestion = timed.activeQuestion.copy(placements = emptyList())) + val revealedTileIds = timed.activeQuestion.revealedTileIds() + return timed.copy( + activeQuestion = timed.activeQuestion.copy( + placements = timed.activeQuestion.placements.filter { it.tileId in revealedTileIds }, + ), + ) } fun submitLetters(state: QuizRunState): QuizRunState { @@ -356,6 +395,15 @@ class QuizEngine( fun runResult(state: QuizRunState, improvedMasteryItems: Int): RunResult { require(state.status == RunStatus.Completed) { "Only a completed run has results" } + return buildRunSummary(state, improvedMasteryItems) + } + + fun abandonedRunSummary(state: QuizRunState, improvedMasteryItems: Int): RunResult { + require(state.status == RunStatus.Abandoned) { "Only an abandoned run has an abandoned summary" } + return buildRunSummary(state, improvedMasteryItems) + } + + private fun buildRunSummary(state: QuizRunState, improvedMasteryItems: Int): RunResult { val correct = state.attempts.count { it.correct } val averageResponse = if (state.attempts.isEmpty()) 0 else { state.attempts.sumOf { it.responseMillis } / state.attempts.size @@ -374,6 +422,7 @@ class QuizEngine( .flatMap { it.countryIds } .distinct(), completedAtEpochMillis = wallClock.nowEpochMillis(), + status = state.status, ) } @@ -540,6 +589,9 @@ class QuizEngine( } } + private fun ActiveQuestion.revealedTileIds(): Set = + appliedHints.mapNotNullTo(linkedSetOf()) { it.revealedTileId } + private fun correctAnswer(question: Question): String = when (question) { is Question.MultipleChoice -> question.choices.first { it.id == question.correctChoiceId }.label is Question.LetterBank -> question.displayAnswer diff --git a/domain/src/commonMain/kotlin/app/bertsatlas/domain/Scoring.kt b/domain/src/commonMain/kotlin/app/bertsatlas/domain/Scoring.kt index f200450..a19b2c0 100644 --- a/domain/src/commonMain/kotlin/app/bertsatlas/domain/Scoring.kt +++ b/domain/src/commonMain/kotlin/app/bertsatlas/domain/Scoring.kt @@ -1,8 +1,6 @@ package app.bertsatlas.domain object Scoring { - const val HINT_COST = 25 - fun pointsForCorrectAnswer(remainingMillis: Long, responseWindowMillis: Long): Int { require(responseWindowMillis > 0) if (remainingMillis <= 0) return 50 @@ -10,5 +8,7 @@ object Scoring { return (50 + speedPoints).coerceAtMost(100) } - fun deductHint(score: Int): Int = (score - HINT_COST).coerceAtLeast(0) + fun hintCost(score: Int): Int = score.coerceAtLeast(0) / 2 + + fun deductHint(score: Int): Int = score.coerceAtLeast(0) - hintCost(score) } diff --git a/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Progress.kt b/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Progress.kt index 93ae206..aabc059 100644 --- a/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Progress.kt +++ b/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Progress.kt @@ -22,6 +22,7 @@ data class Attempt( val hintPointsDeducted: Int = 0, val questionWindowMillis: Long = 0, val questionRemainingMillis: Long = 0, + val hintTypes: Set = emptySet(), ) @Serializable @@ -88,6 +89,7 @@ data class RunResult( val improvedMasteryItems: Int, val revisitCountryIds: List, val completedAtEpochMillis: Long, + val status: RunStatus = RunStatus.Completed, ) { val accuracyPercent: Int get() = if (answeredQuestions == 0) 0 else (correctAnswers * 100) / answeredQuestions diff --git a/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Run.kt b/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Run.kt index b8e0f68..3c747ec 100644 --- a/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Run.kt +++ b/domain/src/commonMain/kotlin/app/bertsatlas/domain/model/Run.kt @@ -52,10 +52,17 @@ data class ActiveQuestion( val questionRemainingMillis: Long, val questionElapsedMillis: Long = 0, val hint: HintPresentation? = null, + val usedHints: List = emptyList(), val hintPointsDeducted: Int = 0, val removedChoiceIds: Set = emptySet(), val removedTileIds: Set = emptySet(), -) +) { + val appliedHints: List + get() = if (usedHints.isEmpty()) listOfNotNull(hint) else usedHints + + val usedHintTypes: Set + get() = appliedHints.mapTo(linkedSetOf()) { it.type } +} @Serializable data class QuizRunState( diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 95196b6..589f6ab 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 09FED4B446BD58DF150C455A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = C6D2DD7FE2E0BF5DB2A96B4B /* PrivacyInfo.xcprivacy */; }; + 22EA1CB33A17DCC6FC82406D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F543015C899423D4B01CEB2E /* Assets.xcassets */; }; 307C2654CD5972A25FB7FD87 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668CECA83ACC03A83C85A623 /* iOSApp.swift */; }; 8799EE5CA40A935F0C1BF0B6 /* ComposeApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE64B79D6154E4181215B83C /* ComposeApp.framework */; }; /* End PBXBuildFile section */ @@ -18,6 +19,7 @@ C117AD187833E2513BD2F553 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; C6D2DD7FE2E0BF5DB2A96B4B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; CE64B79D6154E4181215B83C /* ComposeApp.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ComposeApp.framework; path = ../composeApp/build/bin/iosSimulatorArm64/debugFramework/ComposeApp.framework; sourceTree = ""; }; + F543015C899423D4B01CEB2E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -35,6 +37,7 @@ 1DB4B685CAEE8BEFEBBAEF94 /* iosApp */ = { isa = PBXGroup; children = ( + F543015C899423D4B01CEB2E /* Assets.xcassets */, C117AD187833E2513BD2F553 /* Info.plist */, 668CECA83ACC03A83C85A623 /* iOSApp.swift */, C6D2DD7FE2E0BF5DB2A96B4B /* PrivacyInfo.xcprivacy */, @@ -125,6 +128,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 22EA1CB33A17DCC6FC82406D /* Assets.xcassets in Resources */, 09FED4B446BD58DF150C455A /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb11ebffcf54e5c2135aee903ed042f7622cd51 GIT binary patch literal 64799 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 literal 0 HcmV?d00001 diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..f22e10c --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIcon-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/LaunchBackground.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/LaunchBackground.colorset/Contents.json new file mode 100644 index 0000000..032af09 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/LaunchBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE1", + "green" : "0xE9", + "red" : "0xF6" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/Contents.json b/iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/Contents.json new file mode 100644 index 0000000..65b63ff --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "LaunchLogo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "LaunchLogo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "LaunchLogo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/LaunchLogo.png b/iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/LaunchLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..2b46023728536bcc17363b5d64ae37602a2985eb GIT binary patch literal 6361 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 literal 0 HcmV?d00001 diff --git a/iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@3x.png b/iosApp/iosApp/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3d69c27e6f3a2c1867c60a18ad524aff7276dbb4 GIT binary patch literal 19997 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 literal 0 HcmV?d00001 diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index c7a1805..a13b28b 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -25,7 +25,14 @@ LSRequiresIPhoneOS UILaunchScreen - + + UIColorName + LaunchBackground + UIImageName + LaunchLogo + UIImageRespectsSafeAreaInsets + + UIApplicationSupportsIndirectInputEvents UISupportedInterfaceOrientations diff --git a/spec.md b/spec.md index 688c837..04cd1df 100644 --- a/spec.md +++ b/spec.md @@ -102,25 +102,25 @@ The first screen contains: `Start` is the only interactive choice on the first-launch screen. System accessibility and silent-mode preferences are honored without requiring setup. -Tapping `Start` immediately begins a quiz with these defaults: +Tapping `Start` immediately begins a trivia run with these defaults: - Duration: 5 minutes - Geography: worldwide - Question mix: flags-heavy mixed geography - Difficulty: adaptive -No configuration questionnaire or modal tutorial appears before the first quiz. The interface teaches mechanics contextually without delaying the first question. +No configuration questionnaire or modal tutorial appears before the first trivia run. The interface teaches mechanics contextually without delaying the first question. ### 6.2 Returning launch From the second app launch onward, the home screen contains: -- `Start`, which immediately uses the most recently saved quiz configuration +- `Start`, which immediately uses the most recently saved trivia configuration - `Options`, a secondary action - Current streak and a compact recent-progress summary - Settings -The app must distinguish first launch from later launches using local state. Completing a quiz is not required for the returning layout to appear on the next launch. +The minimal first-use Home remains until the player starts their first trivia run. Merely opening and closing the app does not reveal returning-player controls. Completing the run is not required. ### 6.3 Options flow @@ -138,13 +138,13 @@ The initial V1 sequence is: - Capitals - Mixed geography -The final answer is saved and immediately starts the quiz. All answers are saved as one configuration. Abandoning the flow leaves the previously saved configuration unchanged. +The final answer is saved and immediately starts the trivia. All answers are saved as one configuration. Abandoning the flow leaves the previously saved configuration unchanged. Difficulty is always adaptive in V1 and has no player-facing configuration. V1 does not ask the player to choose a question count or continent. These controls may be added later only if evidence shows they improve play without adding friction. -### 6.4 Quiz run +### 6.4 Trivia run A run repeatedly moves through: @@ -258,14 +258,14 @@ Examples for either question format are 100 points at the initial instant, 75 po ### 8.3 Hints -Each question permits at most one hint. Opening the hint chooser pauses the question timer. The player chooses among the hint types supported by that question: +Each supported hint type may be used once per question. Opening the hint chooser pauses the question timer. Used hints remain visible, disabled, and marked with a check. The player may return for another unused hint among the types supported by that question: - Reveal one correct letter - Remove decoy letters or incorrect choices - Reveal the continent - Show a funny factual clue -All hint types use the same debit rule. When a hint is chosen, the app first deducts up to 25 points from the score already earned in the current run. The score can never become negative: a zero balance makes the hint free, while a balance below 25 is reduced to zero. A subsequent correct answer then awards points using the normal speed or overtime formula. An incorrect hinted answer awards zero. +All hint types use the same debit rule. Choosing a hint deducts half of the currently available score using whole points. The chooser displays the exact deduction as a compact negative-points label and hides it at a zero balance. A subsequent correct answer then awards points using the normal speed or overtime formula. An incorrect hinted answer awards zero. A hinted correct answer is recorded as correct for accuracy and keeps the item in `learning`, but it does not satisfy the unhinted thresholds for `familiar` or `mastered`. Using a hint never blocks progression or triggers negative copy. @@ -561,7 +561,7 @@ V1 is ready only when: - A fresh install can start and finish every quiz configuration in airplane mode on Android and iOS. - First launch shows only the immediate-start experience; later launches show `Start` and `Options`. - `Start` correctly reuses the most recently completed options configuration. -- Each timed duration ends after the current question, and unlimited mode ends only by player choice. +- Each timed duration moves directly to Results when the overall trivia timer expires, and unlimited mode ends only by player choice. - Question and session clocks pause in every specified non-answering state. - Abandoned and completed runs apply mastery, results, personal-best, and streak rules correctly. - Correct-before-timeout, correct-after-timeout, incorrect, and hinted scoring follow the specified rules.