/* Wereldoriëntatie: echte soortfoto's en geografisch correcte topografie. */ "use strict"; const WORLD_MEDIA_ROOT="img/world/"; const worldMedia=(file,author,license,source)=>Object.freeze({file,author,license,source}); const WORLD_MEDIA=Object.freeze({ oak:{ tree:worldMedia("trees/oak.webp","AnemoneProjectors","CC BY-SA 2.0","https://commons.wikimedia.org/wiki/File:1._English_Oak_(Quercus_robur)_(3607652442).jpg"), leaf:worldMedia("leaves/oak.webp","Rasbak","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Zomereik_blad_Quercus_robur.jpg"), }, birch:{ tree:worldMedia("trees/birch.webp","Maplan","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Betula_pendula_winter.jpg"), leaf:worldMedia("leaves/birch.webp","Joanna Boisse","CC BY-SA 4.0","https://commons.wikimedia.org/wiki/File:Atlas_roslin_pl_Brzoza_brodawkowata_1393_6470.jpg"), }, horse_chestnut:{ tree:worldMedia("trees/horse-chestnut.webp","Toubib","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Paardenkastanje2.jpg"), leaf:worldMedia("leaves/horse-chestnut.webp","Tournasol7","CC BY 4.0","https://commons.wikimedia.org/wiki/File:Aesculus_hippocastanum_in_botanical_garden_of_UKW_Bydgoszcz_(2).jpg"), }, pine:{ tree:worldMedia("trees/pine.webp","Hello, I am Bruce","CC BY-SA 2.0","https://commons.wikimedia.org/wiki/File:Pinus_sylvestris_Glenmuick.jpg"), leaf:worldMedia("leaves/pine.webp","AnRo0002","CC0 1.0","https://commons.wikimedia.org/wiki/File:20151215Pinus_sylvestris2.jpg"), }, beech:{ tree:worldMedia("trees/beech.webp","Emőke Dénes","CC BY-SA 4.0","https://commons.wikimedia.org/wiki/File:Fagales_-_Fagus_sylvatica.jpg"), leaf:worldMedia("leaves/beech.webp","AnRo0002","CC0 1.0","https://commons.wikimedia.org/wiki/File:20130530Hain_Rotbuche.jpg"), }, maple:{ tree:worldMedia("trees/maple.webp","MPF","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Acer-pseudoplatanus.jpg"), leaf:worldMedia("leaves/maple.webp","Willow","CC BY 2.5","https://commons.wikimedia.org/wiki/File:Acer_pseudoplatanus_003.jpg"), }, willow:{ tree:worldMedia("trees/willow.webp","Burkhard Mücke","CC BY-SA 4.0","https://commons.wikimedia.org/wiki/File:Salix_alba_an_der_Isar.jpg"), leaf:worldMedia("leaves/willow.webp","AnRo0002","CC0 1.0","https://commons.wikimedia.org/wiki/File:20160721Salix_alba1.jpg"), }, alder:{ tree:worldMedia("trees/alder.webp","Willow","CC BY 2.5","https://commons.wikimedia.org/wiki/File:Alnus_glutinosa_011.jpg"), leaf:worldMedia("leaves/alder.webp","AnRo0002","CC0 1.0","https://commons.wikimedia.org/wiki/File:20200531Alnus_glutinosa2.jpg"), }, robin:{bird:worldMedia("birds/robin.webp","Rasbak","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Erithacus_rubecula,_Roodborstje.jpg")}, great_tit:{bird:worldMedia("birds/great-tit.webp","Sławek Staszczuk","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Parus_major_m.jpg")}, blackbird:{bird:worldMedia("birds/blackbird.webp","Malene Thyssen","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Solsort.jpg")}, mallard:{bird:worldMedia("birds/mallard.webp","Mbdortmund","CC BY 3.0","https://commons.wikimedia.org/wiki/File:Duisburg_10401IMG_0401.JPG")}, blue_tit:{bird:worldMedia("birds/blue-tit.webp","Luc Viatour","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Cyanistes_caeruleus_3_Luc_Viatour.jpg")}, house_sparrow:{bird:worldMedia("birds/house-sparrow.webp","JrPol","CC BY-SA 4.0","https://commons.wikimedia.org/wiki/File:Huismus,_man.jpg")}, magpie:{bird:worldMedia("birds/magpie.webp","Dion Art","CC BY-SA 4.0","https://commons.wikimedia.org/wiki/File:%D0%A1%D0%BE%D1%80%D0%BE%D0%BA%D0%B0_(Pica_pica),_%D0%9A%D0%B0%D0%BB%D0%B8%D0%BD%D0%B8%D0%BD%D0%B3%D1%80%D0%B0%D0%B4.jpg")}, woodpecker:{bird:worldMedia("birds/woodpecker.webp","Usitea","CC BY-SA 3.0","https://commons.wikimedia.org/wiki/File:Grote_bonte_specht.JPG")}, }); const WORLD_TREES=Object.freeze([ {id:"oak",nl:"Zomereik",en:"Pedunculate oak",latin:"Quercus robur",tier:1,hintNl:"Brede kroon, diep gelobde bladeren en eikels aan een steeltje.",hintEn:"Broad crown, deeply lobed leaves and acorns on a stalk."}, {id:"birch",nl:"Ruwe berk",en:"Silver birch",latin:"Betula pendula",tier:1,hintNl:"Witte bast met donkere strepen en driehoekige, gezaagde blaadjes.",hintEn:"White bark with dark markings and triangular, serrated leaves."}, {id:"horse_chestnut",nl:"Witte paardenkastanje",en:"Horse chestnut",latin:"Aesculus hippocastanum",tier:1,hintNl:"Vijf tot zeven grote blaadjes vormen samen één handvormig blad.",hintEn:"Five to seven large leaflets form one hand-shaped leaf."}, {id:"pine",nl:"Grove den",en:"Scots pine",latin:"Pinus sylvestris",tier:1,hintNl:"Oranjebruine schors bovenin; de blauwgroene naalden staan per twee.",hintEn:"Orange-brown upper bark; blue-green needles grow in pairs."}, {id:"beech",nl:"Beuk",en:"European beech",latin:"Fagus sylvatica",tier:2,hintNl:"Gladde grijze stam en glanzende ovale bladeren met een golvende rand.",hintEn:"Smooth grey trunk and glossy oval leaves with a wavy edge."}, {id:"maple",nl:"Gewone esdoorn",en:"Sycamore maple",latin:"Acer pseudoplatanus",tier:2,hintNl:"Groot handvormig blad met vijf puntige lobben en gevleugelde zaden.",hintEn:"Large hand-shaped leaf with five pointed lobes and winged seeds."}, {id:"willow",nl:"Schietwilg",en:"White willow",latin:"Salix alba",tier:3,hintNl:"Brede, losse kroon en lange smalle bladeren met een zilverige onderkant.",hintEn:"Broad open crown and long narrow leaves with a silvery underside."}, {id:"alder",nl:"Zwarte els",en:"Black alder",latin:"Alnus glutinosa",tier:3,hintNl:"Ronde bladeren met een ingedeukte top en kleine houtige elzenproppen.",hintEn:"Rounded leaves with a notched tip and small woody alder cones."}, ]); const WORLD_BIRDS=Object.freeze([ {id:"robin",nl:"Roodborst",en:"European robin",latin:"Erithacus rubecula",tier:1,hintNl:"Oranjerode borst en gezicht, met een olijfbruine rug.",hintEn:"Orange-red breast and face with an olive-brown back."}, {id:"great_tit",nl:"Koolmees",en:"Great tit",latin:"Parus major",tier:1,hintNl:"Zwarte kop, witte wangen en een zwarte streep over de gele buik.",hintEn:"Black head, white cheeks and a black stripe down the yellow belly."}, {id:"blackbird",nl:"Merel",en:"Common blackbird",latin:"Turdus merula",tier:1,hintNl:"Het mannetje is helemaal zwart met een gele snavel en gele oogring.",hintEn:"The male is all black with a yellow bill and yellow eye-ring."}, {id:"mallard",nl:"Wilde eend",en:"Mallard",latin:"Anas platyrhynchos",tier:1,hintNl:"Het mannetje heeft een groene kop, witte halsring en kastanjebruine borst.",hintEn:"The male has a green head, white neck ring and chestnut breast."}, {id:"blue_tit",nl:"Pimpelmees",en:"Blue tit",latin:"Cyanistes caeruleus",tier:2,hintNl:"Blauw petje en vleugels, witte wangen en een gele buik.",hintEn:"Blue cap and wings, white cheeks and a yellow belly."}, {id:"house_sparrow",nl:"Huismus",en:"House sparrow",latin:"Passer domesticus",tier:2,hintNl:"Bruin gestreepte rug; het mannetje heeft een grijze kruin en zwarte bef.",hintEn:"Brown streaked back; the male has a grey crown and black bib."}, {id:"magpie",nl:"Ekster",en:"Eurasian magpie",latin:"Pica pica",tier:3,hintNl:"Zwart-wit verenkleed met blauwgroene glans en een opvallend lange staart.",hintEn:"Black-and-white plumage with a blue-green sheen and a very long tail."}, {id:"woodpecker",nl:"Grote bonte specht",en:"Great spotted woodpecker",latin:"Dendrocopos major",tier:3,hintNl:"Zwart-wit met rode veren onder de staart; het mannetje heeft rood in de nek.",hintEn:"Black and white with red beneath the tail; the male has red on the nape."}, ]); const NL_PROVINCES=Object.freeze([ {id:"friesland",shape:"friesland",nl:"Friesland",en:"Friesland",capital:"leeuwarden",tier:2,color:"#93c5fd",x:518,y:166}, {id:"groningen",shape:"groningen",nl:"Groningen",en:"Groningen",capital:"groningen_city",tier:2,color:"#fca5a5",x:615,y:72}, {id:"drenthe",shape:"drenthe",nl:"Drenthe",en:"Drenthe",capital:"assen",tier:3,color:"#fde68a",x:611,y:222}, {id:"overijssel",shape:"overijssel",nl:"Overijssel",en:"Overijssel",capital:"zwolle",tier:2,color:"#c4b5fd",x:598,y:351}, {id:"flevoland",shape:"flevoland",nl:"Flevoland",en:"Flevoland",capital:"lelystad",tier:3,color:"#fdba74",x:465,y:272}, {id:"noord_holland",shape:"noord-holland",nl:"Noord-Holland",en:"North Holland",capital:"haarlem",tier:1,color:"#f9a8d4",x:308,y:244}, {id:"utrecht",shape:"utrecht",nl:"Utrecht",en:"Utrecht",capital:"utrecht_city",tier:1,color:"#d8b4fe",x:393,y:471}, {id:"gelderland",shape:"gelderland",nl:"Gelderland",en:"Gelderland",capital:"arnhem",tier:1,color:"#86efac",x:493,y:452}, {id:"zuid_holland",shape:"zuid-holland",nl:"Zuid-Holland",en:"South Holland",capital:"den_haag",tier:1,color:"#67e8f9",x:245,y:451}, {id:"zeeland",shape:"zeeland",nl:"Zeeland",en:"Zeeland",capital:"middelburg",tier:3,color:"#5eead4",x:132,y:698}, {id:"noord_brabant",shape:"noord-brabant",nl:"Noord-Brabant",en:"North Brabant",capital:"den_bosch",tier:1,color:"#fcd34d",x:367,y:599}, {id:"limburg",shape:"limburg",nl:"Limburg",en:"Limburg",capital:"maastricht",tier:1,color:"#a7f3d0",x:489,y:714}, ]); const NL_CITIES=Object.freeze([ {id:"leeuwarden",nl:"Leeuwarden",en:"Leeuwarden",province:"friesland",capital:true,tier:2,x:467,y:119}, {id:"groningen_city",nl:"Groningen",en:"Groningen",province:"groningen",capital:true,tier:2,x:612,y:112}, {id:"assen",nl:"Assen",en:"Assen",province:"drenthe",capital:true,tier:3,x:612,y:175}, {id:"zwolle",nl:"Zwolle",en:"Zwolle",province:"overijssel",capital:true,tier:2,x:538,y:332}, {id:"lelystad",nl:"Lelystad",en:"Lelystad",province:"flevoland",capital:true,tier:3,x:417,y:327}, {id:"haarlem",nl:"Haarlem",en:"Haarlem",province:"noord_holland",capital:true,tier:1,x:261,y:371}, {id:"utrecht_city",nl:"Utrecht",en:"Utrecht",province:"utrecht",capital:true,tier:1,x:355,y:460}, {id:"arnhem",nl:"Arnhem",en:"Arnhem",province:"gelderland",capital:true,tier:1,x:500,y:486}, {id:"den_haag",nl:"Den Haag",en:"The Hague",province:"zuid_holland",capital:true,tier:1,x:192,y:460,side:"left"}, {id:"middelburg",nl:"Middelburg",en:"Middelburg",province:"zeeland",capital:true,tier:3,x:65,y:635}, {id:"den_bosch",nl:"'s-Hertogenbosch",en:"'s-Hertogenbosch",province:"noord_brabant",capital:true,tier:1,x:388,y:577}, {id:"maastricht",nl:"Maastricht",en:"Maastricht",province:"limburg",capital:true,tier:1,x:463,y:836}, {id:"amsterdam",nl:"Amsterdam",en:"Amsterdam",province:"noord_holland",capital:false,tier:1,x:311,y:373}, {id:"rotterdam",nl:"Rotterdam",en:"Rotterdam",province:"zuid_holland",capital:false,tier:1,x:234,y:508}, {id:"eindhoven",nl:"Eindhoven",en:"Eindhoven",province:"noord_brabant",capital:false,tier:1,x:424,y:653}, {id:"nijmegen",nl:"Nijmegen",en:"Nijmegen",province:"gelderland",capital:false,tier:1,x:497,y:536}, {id:"enschede",nl:"Enschede",en:"Enschede",province:"overijssel",capital:false,tier:2,x:688,y:409,side:"left"}, {id:"alkmaar",nl:"Alkmaar",en:"Alkmaar",province:"noord_holland",capital:false,tier:2,x:283,y:296}, {id:"breda",nl:"Breda",en:"Breda",province:"noord_brabant",capital:false,tier:3,x:286,y:611,side:"left"}, {id:"tilburg",nl:"Tilburg",en:"Tilburg",province:"noord_brabant",capital:false,tier:3,x:347,y:625}, ]); const worldName=item=>LANG==="nl"?item.nl:item.en; const worldHint=item=>LANG==="nl"?item.hintNl:item.hintEn; const worldShuffle=items=>{ const copy=[...items]; for(let i=copy.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1)); [copy[i],copy[j]]=[copy[j],copy[i]]; } return copy; }; const worldSelect=(value,items)=>{ const select=document.createElement("select"); items.forEach(([id,key])=>select.add(new Option(T(key),id))); select.value=value; return select; }; const worldLevelPool=(items,level)=>items.filter(item=>item.tier<=level); function worldPhoto(item,type,view){ const kind=type==="birds"?"bird":view; const media=WORLD_MEDIA[item.id]?.[kind]; const figure=document.createElement("figure"); figure.className="world-photo world-photo-"+kind; const image=document.createElement("img"); image.className="world-picture"; image.alt=T(kind==="bird"?"worldPhotoBird":kind==="leaf"?"worldPhotoLeaf":"worldPhotoTree"); image.draggable=false; image.loading="lazy"; image.decoding="async"; image.src=WORLD_MEDIA_ROOT+media.file; const badge=document.createElement("span"); badge.className="world-photo-badge"; badge.textContent=T(kind==="bird"?"worldPhotoBird":kind==="leaf"?"worldPhotoLeaf":"worldPhotoTree"); const credit=document.createElement("a"); credit.className="world-photo-credit"; credit.href=media.source; credit.target="_blank"; credit.rel="noopener noreferrer"; credit.textContent=T("worldPhotoCredit").replace("{author}",media.author).replace("{license}",media.license); figure.append(image,badge,credit); return figure; } function worldVisual(item,type,view){ const visual=document.createElement("div"); visual.className="world-visual "+type+(view==="mixed"?" mixed":""); if(type==="trees"&&view==="mixed")visual.append(worldPhoto(item,type,"tree"),worldPhoto(item,type,"leaf")); else visual.append(worldPhoto(item,type,view)); return visual; } function worldIdentity(item){ const identity=document.createElement("div"); identity.className="world-identity"; const name=document.createElement("h3"); name.textContent=worldName(item); const latin=document.createElement("em"); latin.textContent=item.latin; identity.append(name,latin); return identity; } function mountWorldQuiz(root,init,config){ const S=Object.assign({ activity:"quiz",level:1,view:config.type==="trees"?"mixed":"bird", score:0,streak:0,target:"",taskView:"",options:[],solved:false,feedback:"", },init||{}); S.level=Math.max(1,Math.min(3,+S.level||1)); const activity=worldSelect(S.activity,[["learn","worldLearn"],["quiz","worldQuiz"]]); const level=worldSelect(String(S.level),[["1","lvl1"],["2","lvl2"],["3","lvl3"]]); const stage=document.createElement("div"); const settings=[field(T("worldActivity"),activity),field(T("worldLevel"),level)]; let view; if(config.type==="trees"){ view=worldSelect(S.view,[["tree","worldTreeWhole"],["leaf","worldTreeLeaf"],["mixed","worldTreeMixed"]]); settings.push(field(T("worldTreeView"),view)); } const pool=()=>worldLevelPool(config.items,S.level); const current=()=>config.items.find(item=>item.id===S.target)||pool()[0]; const newTask=()=>{ const items=pool(),previous=S.target; let target=items[Math.floor(Math.random()*items.length)]; if(items.length>1)while(target.id===previous)target=items[Math.floor(Math.random()*items.length)]; S.target=target.id; S.taskView=config.type==="trees"?(S.view==="mixed"?(Math.random()<.5?"tree":"leaf"):S.view):"bird"; S.options=worldShuffle([target,...worldShuffle(items.filter(item=>item.id!==target.id)).slice(0,3)]).map(item=>item.id); S.solved=false; S.feedback=""; draw(); }; const answer=id=>{ if(S.solved)return; if(id===S.target){ S.solved=true;S.feedback="good";S.score++;S.streak++;sndGood(); }else{ S.feedback="bad";S.streak=0;sndBad(); } draw(); }; const gallery=()=>{ const grid=document.createElement("div"); grid.className="world-gallery"; pool().forEach(item=>{ const card=document.createElement("article"); card.className="world-specimen"; card.append(worldVisual(item,config.type,config.type==="trees"?S.view:"bird"),worldIdentity(item)); const hint=document.createElement("p"); hint.textContent=worldHint(item); card.append(hint); grid.append(card); }); return grid; }; function draw(){ S.activity=activity.value; S.level=+level.value; if(view)S.view=view.value; stage.className="world-app"; stage.innerHTML=""; const top=document.createElement("div"); top.className="world-top"; const title=document.createElement("strong"); title.textContent=T("wg_"+config.type); const score=document.createElement("span"); score.textContent=T("worldScore").replace("{score}",S.score).replace("{streak}",S.streak); top.append(title,score); stage.append(top); if(S.activity==="learn"){ const intro=document.createElement("p"); intro.className="world-intro"; intro.textContent=T("worldExploreHint"); stage.append(intro,gallery()); return; } const item=current(),card=document.createElement("section"); card.className="world-task-card"; const question=document.createElement("h3"); question.textContent=T(config.type==="birds"?"worldQuestionBird":S.taskView==="leaf"?"worldQuestionLeaf":"worldQuestionTree"); card.append(question,worldVisual(item,config.type,S.taskView)); const answers=document.createElement("div"); answers.className="world-answers"; S.options.forEach(id=>{ const option=config.items.find(entry=>entry.id===id); if(!option)return; const button=btn(worldName(option),()=>answer(id)); if(S.solved&&id===S.target)button.classList.add("correct"); answers.append(button); }); card.append(answers); const feedback=document.createElement("p"); feedback.className="world-feedback "+(S.feedback==="good"?"ok":S.feedback?"bad":""); feedback.setAttribute("role","status"); if(S.feedback)feedback.textContent=T(S.feedback==="good"?"worldCorrect":"worldTryAgain"); card.append(feedback); const actions=document.createElement("div"); actions.className="world-actions"; actions.append(btn(T("worldNext"),newTask,"ghost")); card.append(actions); if(S.solved){ const fact=document.createElement("div"); fact.className="world-fact"; fact.append(worldIdentity(item)); const hint=document.createElement("p"); hint.textContent=worldHint(item); fact.append(hint); card.append(fact); } stage.append(card); } activity.onchange=()=>{S.activity=activity.value;if(S.activity==="quiz")newTask();else draw()}; level.onchange=()=>{S.level=+level.value;if(S.activity==="quiz")newTask();else draw()}; if(view)view.onchange=()=>{S.view=view.value;if(S.activity==="quiz")newTask();else draw()}; learningShell(root,settings,stage); if(S.activity==="quiz")newTask();else draw(); return{getState:()=>({...S,options:[...S.options]})}; } function mountTrees(root,init){return mountWorldQuiz(root,init,{type:"trees",items:WORLD_TREES})} function mountBirds(root,init){return mountWorldQuiz(root,init,{type:"birds",items:WORLD_BIRDS})} const SVG_NS="http://www.w3.org/2000/svg"; const TOPO_MAP_ASSET=WORLD_MEDIA_ROOT+"netherlands-provinces.svg"; const TOPO_MAP_SOURCE="https://commons.wikimedia.org/wiki/File:Netherlands_provinces_map_blank.svg"; function svgNode(name,attributes={}){ const node=document.createElementNS(SVG_NS,name); Object.entries(attributes).forEach(([key,value])=>node.setAttribute(key,value)); return node; } function svgAction(node,label,action){ node.setAttribute("role","button"); node.setAttribute("tabindex","0"); node.setAttribute("aria-label",label); node.addEventListener("click",action); node.addEventListener("keydown",event=>{ if(event.key==="Enter"||event.key===" "){ event.preventDefault(); action(); } }); } function makeTopoMap(mode,pool,onPick,showLabels,picked,target){ const wrap=document.createElement("div"); wrap.className="topo-map-wrap"; const svg=svgNode("svg",{ class:"topo-map",viewBox:"0 0 743 881", role:onPick?"group":"img","aria-label":T("topoMapLabel"), }); svg.append(svgNode("rect",{x:0,y:0,width:743,height:881,rx:34,class:"topo-water"})); NL_PROVINCES.forEach(province=>{ const shape=svgNode("use",{ href:TOPO_MAP_ASSET+"#"+province.shape, fill:province.color, class:"topo-province"+(province.id===target?" target":"")+(province.id===picked?" picked":""), "data-id":province.id, }); const title=svgNode("title"); title.textContent=worldName(province); shape.append(title); if(onPick&&mode==="provinces")svgAction(shape,worldName(province),()=>onPick(province.id)); svg.append(shape); if(showLabels&&mode==="provinces"&&pool.some(item=>item.id===province.id)){ const label=svgNode("text",{x:province.x,y:province.y,class:"topo-label"}); label.textContent=worldName(province); svg.append(label); } }); if(mode!=="provinces")pool.forEach(city=>{ const group=svgNode("g",{ class:"topo-marker"+(city.id===target?" target":"")+(city.id===picked?" picked":""), }); const hit=svgNode("circle",{cx:city.x,cy:city.y,r:24,class:"topo-hit"}); const dot=svgNode("circle",{cx:city.x,cy:city.y,r:10,class:"topo-dot"}); group.append(hit,dot); if(showLabels){ const left=city.side==="left"; const label=svgNode("text",{ x:city.x+(left?-16:16),y:city.y-13, class:"topo-city-label"+(left?" left":""), }); label.textContent=worldName(city); group.append(label); } if(onPick)svgAction(group,worldName(city),()=>onPick(city.id)); svg.append(group); }); const source=document.createElement("a"); source.className="topo-source"; source.href=TOPO_MAP_SOURCE; source.target="_blank"; source.rel="noopener noreferrer"; source.textContent=T("topoMapSource"); wrap.append(svg,source); return wrap; } function mountTopography(root,init){ const S=Object.assign({ activity:"quiz",mode:"provinces",level:1,score:0,streak:0, target:"",picked:"",solved:false,feedback:"", },init||{}); S.level=Math.max(1,Math.min(3,+S.level||1)); const activity=worldSelect(S.activity,[["learn","worldLearn"],["quiz","worldQuiz"]]); const mode=worldSelect(S.mode,[["provinces","topoProvinces"],["capitals","topoCapitals"],["cities","topoCities"]]); const level=worldSelect(String(S.level),[["1","lvl1"],["2","lvl2"],["3","lvl3"]]); const stage=document.createElement("div"); const pool=()=>S.mode==="provinces" ?NL_PROVINCES.filter(item=>item.tier<=S.level) :S.mode==="capitals" ?NL_CITIES.filter(item=>item.capital&&item.tier<=S.level) :NL_CITIES.filter(item=>!item.capital&&item.tier<=S.level); const current=()=>pool().find(item=>item.id===S.target)||pool()[0]; const newTask=()=>{ const items=pool(),previous=S.target; let target=items[Math.floor(Math.random()*items.length)]; if(items.length>1)while(target.id===previous)target=items[Math.floor(Math.random()*items.length)]; S.target=target.id;S.picked="";S.solved=false;S.feedback=""; draw(); }; const answer=id=>{ if(S.solved)return; S.picked=id; if(id===S.target){ S.solved=true;S.feedback="good";S.score++;S.streak++;sndGood(); }else{ S.feedback="bad";S.streak=0;sndBad(); } draw(); }; function draw(){ S.activity=activity.value;S.mode=mode.value;S.level=+level.value; stage.className="world-app topo-app"; stage.innerHTML=""; const top=document.createElement("div"); top.className="world-top"; const title=document.createElement("strong"); title.textContent=T("wg_topography"); const score=document.createElement("span"); score.textContent=T("worldScore").replace("{score}",S.score).replace("{streak}",S.streak); top.append(title,score); stage.append(top); const card=document.createElement("section"); card.className="world-task-card topo-card"; if(S.activity==="learn"){ const intro=document.createElement("p"); intro.className="world-intro"; intro.textContent=T("topoExploreHint"); card.append(intro,makeTopoMap(S.mode,pool(),null,true,"","")); }else{ const item=current(),question=document.createElement("h3"); question.textContent=T(S.mode==="provinces"?"topoFindProvince":S.mode==="capitals"?"topoFindCapital":"topoFindCity").replace("{name}",worldName(item)); card.append(question,makeTopoMap(S.mode,pool(),answer,S.solved,S.picked,S.solved?S.target:"")); const feedback=document.createElement("p"); feedback.className="world-feedback "+(S.feedback==="good"?"ok":S.feedback?"bad":""); feedback.setAttribute("role","status"); if(S.feedback)feedback.textContent=T(S.feedback==="good"?"worldCorrect":"worldTryAgain"); const actions=document.createElement("div"); actions.className="world-actions"; actions.append(btn(T("worldNext"),newTask,"ghost")); card.append(feedback,actions); } stage.append(card); } activity.onchange=()=>{S.activity=activity.value;if(S.activity==="quiz")newTask();else draw()}; mode.onchange=()=>{S.mode=mode.value;if(S.activity==="quiz")newTask();else draw()}; level.onchange=()=>{S.level=+level.value;if(S.activity==="quiz")newTask();else draw()}; learningShell(root,[ field(T("worldActivity"),activity), field(T("topoMode"),mode), field(T("worldLevel"),level), ],stage); if(S.activity==="quiz")newTask();else draw(); return{getState:()=>({...S})}; }