From 705bb7800abe000f8d09e3667fa8aa1c1f54d701 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 19 Jul 2026 00:16:09 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20niveaus=20bij=20wereldori=C3=ABntatie=20?= =?UTF-8?q?nu=20als=20knoppenrij,=20net=20als=20de=20andere=20oefenwidgets?= =?UTF-8?q?=20(v0.4.19-beta)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bomen/vogels/planten/topografie verstopten "Niveau" als een los keuzeveld onderin het inklapbare Instellingen-paneel; de oefenklok, het geldwidget, plaatswaarde en rekenwidgets tonen niveau altijd als een knoppenrij bovenaan, direct zichtbaar tijdens het spelen. - Niveau is nu overal dezelfde knoppenrij (Niveau 1/2/3, met uitleg per knop via title, net als bij de oefenklok) direct in de bovenbalk naast de score - geen Instellingen meer nodig om niveau te wisselen. - Niveau wisselen zet score en reeks terug naar 0, zoals bij de andere oefenwidgets (voorheen bleef de score gewoon doorlopen). - Topografie: de knoppenrij verdwijnt bij "Landsdelen", waar niveau toch al niet van toepassing was - zelfde gedrag als voorheen, nu alleen consistent met de rest. --- VERSION | 2 +- public/css/teach.css | 5 ++-- public/js/core.js | 4 ++- public/js/widgets/world.js | 52 ++++++++++++++++++++++++++++++-------- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/VERSION b/VERSION index 49c141e..d5be2c3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.18-beta +0.4.19-beta diff --git a/public/css/teach.css b/public/css/teach.css index dd0d2ad..282673a 100644 --- a/public/css/teach.css +++ b/public/css/teach.css @@ -1725,7 +1725,8 @@ body.dark .bd-stage{background:radial-gradient(circle at 50% 20%,var(--surface-2 /* Wereldoriëntatie: natuurherkenning en topografie */ .world-app{width:100%;min-height:100%;align-self:flex-start;display:flex;flex-direction:column;justify-content:flex-start;color:var(--ink)} -.world-top{position:sticky;top:-16px;z-index:6;width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px;padding:9px 4px;background:color-mix(in srgb,var(--surface) 94%,transparent);border-bottom:1px solid var(--line);backdrop-filter:blur(10px)}.world-top>strong{font-size:15px}.world-top>span{padding:6px 10px;border-radius:999px;background:var(--surface-2);color:var(--muted);font-size:12px;font-weight:900;white-space:nowrap} +.world-top{position:sticky;top:-16px;z-index:6;width:100%;display:flex;align-items:center;flex-wrap:wrap;justify-content:space-between;gap:8px;margin-bottom:10px;padding:9px 4px;background:color-mix(in srgb,var(--surface) 94%,transparent);border-bottom:1px solid var(--line);backdrop-filter:blur(10px)}.world-top>strong{font-size:15px}.world-top>span{padding:6px 10px;border-radius:999px;background:var(--surface-2);color:var(--muted);font-size:12px;font-weight:900;white-space:nowrap} +.world-level-control{display:flex;align-items:center;gap:6px}.world-level-control>span{color:var(--muted);font-size:12px;font-weight:900}.world-levels button{min-width:31px;padding:6px 9px;white-space:nowrap} .world-task-card{width:min(100%,680px);margin:0 auto;display:flex;flex-direction:column;align-items:center;gap:13px;padding:18px;border:1px solid color-mix(in srgb,var(--line) 80%,var(--accent));border-radius:24px;background:linear-gradient(155deg,var(--surface),color-mix(in srgb,var(--surface-2) 68%,var(--surface)));box-shadow:0 18px 40px rgba(24,42,66,.12)}.world-task-card>h3{max-width:560px;margin:0;text-align:center;font-size:clamp(19px,4cqi,27px);line-height:1.2}.world-intro{width:100%;margin:0;padding:10px 14px;border-radius:14px;background:linear-gradient(120deg,var(--accent-soft),color-mix(in srgb,var(--accent-soft) 55%,var(--surface)));color:var(--accent-ink);font-weight:850;line-height:1.4;text-align:center} .world-visual{width:min(100%,460px);display:flex;align-items:flex-start;justify-content:center;gap:12px}.world-visual.mixed{width:min(100%,620px);display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}.world-photo{position:relative;min-width:0;margin:0;overflow:hidden;border:1px solid color-mix(in srgb,var(--line) 75%,var(--ink));border-radius:19px;background:#152432;box-shadow:0 10px 24px rgba(20,36,52,.18)}.world-picture{display:block;width:100%;height:auto;aspect-ratio:4/3;object-fit:cover}.world-photo-badge{position:absolute;left:9px;top:9px;padding:5px 9px;border:1px solid rgba(255,255,255,.35);border-radius:999px;background:rgba(10,27,42,.78);color:#fff;font-size:10px;font-weight:950;letter-spacing:.45px;text-transform:uppercase;box-shadow:0 2px 8px rgba(0,0,0,.18);backdrop-filter:blur(6px)}.world-photo-credit{position:absolute;right:7px;bottom:7px;max-width:calc(100% - 14px);overflow:hidden;padding:4px 7px;border-radius:8px;background:rgba(10,27,42,.8);color:#fff!important;font-size:9px;font-weight:750;line-height:1.2;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;backdrop-filter:blur(6px)}.world-photo-credit:hover,.world-photo-credit:focus-visible{text-decoration:underline;outline:2px solid #fff;outline-offset:1px} .world-identity{display:flex;flex-direction:column;align-items:center;gap:2px;text-align:center}.world-identity h3{margin:0;font-size:18px;line-height:1.2}.world-identity em{color:var(--muted);font-size:12px;font-family:Georgia,serif;font-weight:650} @@ -1961,7 +1962,7 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo .topo-world-legend{gap:4px} .topo-world-legend span{padding:4px 6px;font-size:10px} .money-top,.clock-top,.place-top{top:-10px;gap:5px} - .money-level-control>span,.clock-level-control>span,.place-level-control>span{display:none} + .money-level-control>span,.clock-level-control>span,.place-level-control>span,.world-level-control>span{display:none} .money-shop-task{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:7px} .money-product,.money-paid{min-height:112px;padding:8px 6px;gap:4px} .money-product-icon{font-size:46px} diff --git a/public/js/core.js b/public/js/core.js index cae3ad3..2c35348 100644 --- a/public/js/core.js +++ b/public/js/core.js @@ -2,7 +2,7 @@ "use strict"; /* version — shown until /api/version resolves (or if the fetch fails, e.g. offline). Kept in sync by hand with the VERSION file at the repo root on every release. */ -const VERSION = "0.4.18-beta"; +const VERSION = "0.4.19-beta"; (function(){ const tag = document.getElementById("verTag"); tag.textContent = "v"+VERSION; @@ -126,6 +126,7 @@ const I18N = { wg_topography:"Topografie", wg_topography_d:"Oefen Nederland én de wereld: provincies, plaatsen, landsdelen, rivieren en werelddelen op nauwkeurige kaarten.", worldActivity:"Werkvorm", worldLearn:"Leren", worldQuiz:"Quiz", worldFlash:"Flitskaarten", worldSpell:"Letterblokken", worldExercise:"Oefening", worldLevel:"Niveau", worldTreeView:"Wat zie je?", worldTreeWhole:"Hele boom", worldTreeLeaf:"Blad", worldTreeMixed:"Boom en blad", worldSpellHint:"Sleep de letters naar de vakken en spel de naam.", worldSpellComplete:"Goed gespeld! 🌟", + worldLevel1Hint:"Niveau 1: de bekendste soorten en plekken", worldLevel2Hint:"Niveau 2: ook wat minder bekende soorten en plekken", worldLevel3Hint:"Niveau 3: alle soorten en plekken, ook de zeldzame", worldExerciseRecognize:"Herken de soort", worldExercisePair:"Koppel boom en blad", worldExerciseClue:"Raad met kenmerken", worldExercisePhoto:"Kies de juiste foto", worldExerciseLatin:"Wetenschappelijke naam", worldScore:"Score: {score} · reeks: {streak}", worldExploreHint:"Bekijk de kenmerken en vergelijk de soorten.", worldQuestionTree:"Welke boom zie je?", worldQuestionLeaf:"Bij welke boom hoort dit blad?", worldQuestionBird:"Welke vogel zie je?", worldQuestionPlant:"Welke wilde plant zie je?", worldQuestionPair:"Bij welke boom horen deze boom en dit blad?", worldQuestionClue:"Welke soort hoort bij deze kenmerken?", worldQuestionLatin:"Welke soort hoort bij deze wetenschappelijke naam?", worldFindTreePhoto:"Tik op de foto van de {name}.", worldFindBirdPhoto:"Tik op de foto van de {name}.", worldFindPlantPhoto:"Tik op de foto van de {name}.", @@ -434,6 +435,7 @@ const I18N = { wg_topography:"Topography", wg_topography_d:"Practise the Netherlands and the world: provinces, places, regions, rivers and continents on accurate maps.", worldActivity:"Activity", worldLearn:"Learn", worldQuiz:"Quiz", worldFlash:"Flashcards", worldSpell:"Letter blocks", worldExercise:"Exercise", worldLevel:"Level", worldTreeView:"What do you see?", worldTreeWhole:"Whole tree", worldTreeLeaf:"Leaf", worldTreeMixed:"Tree and leaf", worldSpellHint:"Drag the letters into the slots to spell the name.", worldSpellComplete:"Well spelled! 🌟", + worldLevel1Hint:"Level 1: the most familiar species and places", worldLevel2Hint:"Level 2: also some less familiar species and places", worldLevel3Hint:"Level 3: all species and places, including the rare ones", worldExerciseRecognize:"Recognise the species", worldExercisePair:"Match tree and leaf", worldExerciseClue:"Guess from features", worldExercisePhoto:"Choose the correct photo", worldExerciseLatin:"Scientific name", worldScore:"Score: {score} · streak: {streak}", worldExploreHint:"Study the features and compare the species.", worldQuestionTree:"Which tree do you see?", worldQuestionLeaf:"Which tree does this leaf belong to?", worldQuestionBird:"Which bird do you see?", worldQuestionPlant:"Which wild plant do you see?", worldQuestionPair:"Which species do this tree and leaf belong to?", worldQuestionClue:"Which species matches these features?", worldQuestionLatin:"Which species matches this scientific name?", worldFindTreePhoto:"Tap the photograph of the {name}.", worldFindBirdPhoto:"Tap the photograph of the {name}.", worldFindPlantPhoto:"Tap the photograph of the {name}.", diff --git a/public/js/widgets/world.js b/public/js/widgets/world.js index d052abf..14ea601 100644 --- a/public/js/widgets/world.js +++ b/public/js/widgets/world.js @@ -512,10 +512,9 @@ function mountWorldQuiz(root,init,config){ if(!exerciseOptions.some(([id])=>id===S.exercise))S.exercise="recognize"; const activity=worldSelect(S.activity,[["learn","worldLearn"],["quiz","worldQuiz"],["flash","worldFlash"],["spell","worldSpell"]]); const exercise=worldSelect(S.exercise,exerciseOptions); - const level=worldSelect(String(S.level),[["1","lvl1"],["2","lvl2"],["3","lvl3"]]); const stage=document.createElement("div"); const exerciseSetting=field(T("worldExercise"),exercise); - const settings=[field(T("worldActivity"),activity),exerciseSetting,field(T("worldLevel"),level)]; + const settings=[field(T("worldActivity"),activity),exerciseSetting]; let view,viewSetting; if(config.type==="trees"){ view=worldSelect(S.view,[["tree","worldTreeWhole"],["leaf","worldTreeLeaf"],["mixed","worldTreeMixed"]]); @@ -559,6 +558,7 @@ function mountWorldQuiz(root,init,config){ else{S.feedback="bad";S.streak=0;sndBad()} draw(); }; + const chooseLevel=value=>{S.level=value;S.score=0;S.streak=0;newTask()}; const worldViewFor=()=>config.type==="trees"?(S.view==="mixed"?"tree":S.view):config.type==="birds"?"bird":"plant"; const gallery=()=>{ const grid=document.createElement("div"); @@ -577,7 +577,6 @@ function mountWorldQuiz(root,init,config){ function draw(){ S.activity=activity.value; S.exercise=exercise.value; - S.level=+level.value; if(view)S.view=view.value; exerciseSetting.hidden=S.activity!=="quiz"; if(viewSetting)viewSetting.hidden=S.activity==="quiz"&&!["recognize","photo"].includes(S.exercise); @@ -585,9 +584,27 @@ function mountWorldQuiz(root,init,config){ stage.innerHTML=""; const top=document.createElement("div"); top.className="world-top"; + const levelControl=document.createElement("div"); + levelControl.className="world-level-control"; + const levelLabel=document.createElement("span"); + levelLabel.textContent=T("worldLevel"); + const levels=document.createElement("div"); + levels.className="lg-levels world-levels"; + [1,2,3].forEach(value=>{ + const button=document.createElement("button"); + button.type="button"; + button.textContent=T("lvl"+value); + button.title=T("worldLevel"+value+"Hint"); + button.classList.toggle("active",S.level===value); + button.onclick=()=>chooseLevel(value); + levels.append(button); + }); + levelControl.append(levelLabel,levels); + top.append(levelControl); const title=document.createElement("strong"); title.textContent=T("wg_"+config.type); const score=document.createElement("span"); + score.className="world-score"; score.textContent=T("worldScore").replace("{score}",S.score).replace("{streak}",S.streak); top.append(title,score); stage.append(top); @@ -723,7 +740,6 @@ function mountWorldQuiz(root,init,config){ const isTaskActivity=()=>["quiz","flash","spell"].includes(S.activity); activity.onchange=()=>{S.activity=activity.value;if(isTaskActivity())newTask();else draw()}; exercise.onchange=()=>{S.exercise=exercise.value;if(isTaskActivity())newTask();else draw()}; - level.onchange=()=>{S.level=+level.value;if(isTaskActivity())newTask();else draw()}; if(view)view.onchange=()=>{S.view=view.value;if(isTaskActivity())newTask();else draw()}; learningShell(root,settings,stage); if(isTaskActivity())newTask();else draw(); @@ -923,9 +939,7 @@ function mountTopography(root,init){ if(!modeOptions.some(([id])=>id===S.mode))S.mode="provinces"; const activity=worldSelect(S.activity,[["learn","worldLearn"],["quiz","worldQuiz"],["flash","worldFlash"],["spell","worldSpell"]]); const mode=worldSelect(S.mode,modeOptions); - const level=worldSelect(String(S.level),[["1","lvl1"],["2","lvl2"],["3","lvl3"]]); const stage=document.createElement("div"); - const levelSetting=field(T("worldLevel"),level); const pool=()=>{ const items=S.mode==="provinces"?NL_PROVINCES :S.mode==="capitals"?NL_CITIES.filter(item=>item.capital) @@ -961,16 +975,36 @@ function mountTopography(root,init){ else{S.feedback="bad";S.streak=0;sndBad()} draw(); }; + const chooseLevel=value=>{S.level=value;S.score=0;S.streak=0;newTask()}; function draw(){ - S.activity=activity.value;S.mode=mode.value;S.level=+level.value; - levelSetting.hidden=S.mode==="regions"; + S.activity=activity.value;S.mode=mode.value; stage.className="world-app topo-app"; stage.innerHTML=""; const top=document.createElement("div"); top.className="world-top"; + if(S.mode!=="regions"){ + const levelControl=document.createElement("div"); + levelControl.className="world-level-control"; + const levelLabel=document.createElement("span"); + levelLabel.textContent=T("worldLevel"); + const levels=document.createElement("div"); + levels.className="lg-levels world-levels"; + [1,2,3].forEach(value=>{ + const button=document.createElement("button"); + button.type="button"; + button.textContent=T("lvl"+value); + button.title=T("worldLevel"+value+"Hint"); + button.classList.toggle("active",S.level===value); + button.onclick=()=>chooseLevel(value); + levels.append(button); + }); + levelControl.append(levelLabel,levels); + top.append(levelControl); + } const title=document.createElement("strong"); title.textContent=T("wg_topography"); const score=document.createElement("span"); + score.className="world-score"; score.textContent=T("worldScore").replace("{score}",S.score).replace("{streak}",S.streak); top.append(title,score); stage.append(top); @@ -1040,11 +1074,9 @@ function mountTopography(root,init){ const isTaskActivity=()=>["quiz","flash","spell"].includes(S.activity); activity.onchange=()=>{S.activity=activity.value;if(isTaskActivity())newTask();else draw()}; mode.onchange=()=>{S.mode=mode.value;if(isTaskActivity())newTask();else draw()}; - level.onchange=()=>{S.level=+level.value;if(isTaskActivity())newTask();else draw()}; learningShell(root,[ field(T("worldActivity"),activity), field(T("topoMode"),mode), - levelSetting, ],stage); if(isTaskActivity())newTask();else draw(); return{getState:()=>({...S})};