Voeg thematische kleurplaten en creatieve niveaus toe (v0.4.65-beta) #3
4 changed files with 323 additions and 16 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.4.17-beta
|
||||
0.4.18-beta
|
||||
|
|
|
|||
|
|
@ -1733,6 +1733,25 @@ body.dark .bd-stage{background:radial-gradient(circle at 50% 20%,var(--surface-2
|
|||
.world-answers .tbtn.wrong{background:color-mix(in srgb,var(--red) 14%,var(--surface));color:var(--red);border-color:var(--red)}.world-clue{width:min(100%,560px);padding:18px 20px;border:1px solid color-mix(in srgb,var(--accent) 35%,var(--line));border-radius:18px;background:linear-gradient(135deg,var(--accent-soft),var(--surface));color:var(--accent-ink);font-size:clamp(16px,3.2cqi,21px);font-weight:850;line-height:1.5;text-align:center}.world-clue-latin{font-family:Georgia,serif;font-size:clamp(23px,4.6cqi,34px);font-style:italic;font-weight:700}
|
||||
.world-photo-answers{width:min(100%,640px);display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}.world-photo-choice{position:relative;min-width:0;margin:0;overflow:hidden;border:3px solid transparent;border-radius:18px;background:#152432;box-shadow:0 8px 18px rgba(20,36,52,.16);transition:border-color .15s,transform .15s,box-shadow .15s}.world-image-button{display:block;width:100%;margin:0;padding:0;border:0;background:#152432;cursor:pointer}.world-image-button .world-picture{transition:transform .18s,filter .18s}.world-photo-choice:hover{transform:translateY(-2px);box-shadow:0 12px 22px rgba(20,36,52,.2)}.world-image-button:hover .world-picture{transform:scale(1.025)}.world-image-button:focus-visible{outline:4px solid var(--accent);outline-offset:-4px}.world-choice-number{position:absolute;left:8px;top:8px;display:grid;width:28px;height:28px;place-items:center;border:2px solid rgba(255,255,255,.7);border-radius:50%;background:rgba(10,27,42,.86);color:#fff;font-size:13px;font-weight:950;pointer-events:none}.world-photo-choice.correct{border-color:var(--green);box-shadow:0 0 0 3px color-mix(in srgb,var(--green) 25%,transparent)}.world-photo-choice.wrong{border-color:var(--red)}.world-photo-choice.wrong .world-picture{filter:saturate(.65) brightness(.82)}
|
||||
.world-gallery{width:100%;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.world-specimen{min-width:0;display:flex;flex-direction:column;align-items:center;gap:9px;padding:11px;border:1px solid var(--line);border-radius:20px;background:linear-gradient(155deg,var(--surface),var(--surface-2));box-shadow:0 8px 22px rgba(20,36,52,.08)}.world-specimen .world-visual{width:100%}.world-specimen p{margin:0;padding:0 5px 3px;color:var(--muted);font-size:12px;font-weight:720;line-height:1.4;text-align:center}
|
||||
/* flitskaarten: zelfde opbouw als flits.js (naam kort tonen, dan zelftoets),
|
||||
hier met de foto/kaart altijd zichtbaar en de naam geflitst i.p.v. andersom */
|
||||
.world-flash-card{width:100%;display:flex;flex-direction:column;align-items:center;gap:10px}
|
||||
.world-flash-name{font-size:clamp(24px,5vw,34px);font-weight:900;letter-spacing:1px;color:var(--blue);min-height:44px;display:flex;align-items:center}
|
||||
.world-flash-name.dim{color:var(--muted)}
|
||||
.world-flash-hint{margin:0;color:var(--muted);font-weight:800;text-align:center}
|
||||
.world-flash-reflect{width:100%;padding:10px 14px;border-radius:14px;background:var(--surface-2);text-align:center}
|
||||
.world-flash-reflect>p{margin:0 0 8px;font-weight:900}
|
||||
/* letterblokken: hergebruikt de blok/vak/dots-stijl van letters.js/de rekenwidgets */
|
||||
.world-spell-hint{margin:0;color:var(--muted);font-weight:800;text-align:center}
|
||||
.world-spell-answer{width:100%;display:flex;flex-direction:column;align-items:center;gap:12px}
|
||||
.world-spell-word{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;max-width:100%}
|
||||
.world-spell-gap{width:14px;flex:none}
|
||||
.world-letter-slot.slot.sm,.world-letter-block.blok.sm{width:40px;height:60px;padding-top:3px}
|
||||
.world-letter-slot.slot.sm .ghost{height:24px;font-size:19px}
|
||||
.world-letter-slot.slot.sm .slot-dots{margin-bottom:4px}
|
||||
.world-letter-block.blok.sm .bl{height:26px;font-size:22px}
|
||||
.world-letter-block.blok.sm .blok-dots{margin-bottom:4px}
|
||||
.world-spell-tray.lg-tray{max-width:min(100%,480px)}
|
||||
.topo-card{width:min(100%,740px)}.topo-map-wrap{width:100%;display:flex;flex-direction:column;align-items:center;gap:7px;overflow:hidden;padding:10px;border:1px solid var(--line);border-radius:20px;background:linear-gradient(155deg,#edf8fd,#dff2fb)}.topo-map{display:block;width:min(100%,510px);height:auto;max-height:590px;touch-action:manipulation;filter:drop-shadow(0 9px 13px rgba(29,60,76,.13))}.topo-water{fill:#dff3fb}.topo-province{stroke:#fff;stroke-width:4.5;stroke-linejoin:round;vector-effect:non-scaling-stroke;transition:filter .15s,opacity .15s,fill .15s}.topo-province[role=button]{cursor:pointer}.topo-province[role=button]:hover,.topo-province[role=button]:focus{filter:brightness(.88) saturate(1.12);outline:none}.topo-province[role=button]:focus-visible{stroke:var(--navy);stroke-width:8}.topo-province.picked{fill:var(--red)!important}.topo-province.target{fill:var(--green)!important}.topo-label,.topo-city-label{fill:#18324e;paint-order:stroke;stroke:#fff;stroke-width:5px;stroke-linejoin:round;font-size:18px;font-weight:950;text-anchor:middle;pointer-events:none}.topo-city-label{text-anchor:start;font-size:16px}.topo-city-label.left{text-anchor:end}.topo-marker{cursor:pointer}.topo-hit{fill:transparent}.topo-dot{fill:var(--navy);stroke:#fff;stroke-width:4;transition:fill .15s,stroke-width .15s}.topo-marker:hover .topo-dot,.topo-marker:focus .topo-dot{fill:var(--orange);outline:none}.topo-marker:focus-visible .topo-dot{stroke:var(--navy);stroke-width:8}.topo-marker.picked .topo-dot{fill:var(--red)}.topo-marker.target .topo-dot{fill:var(--green);stroke-width:7}.topo-source{align-self:flex-end;color:#3b657b;font-size:9px;font-weight:750;text-decoration:none}.topo-source:hover,.topo-source:focus-visible{text-decoration:underline}
|
||||
.topo-province.inactive{opacity:.32}.topo-region[role=button],.topo-river[role=button],.world-continent[role=button]{cursor:pointer}.topo-region.picked .topo-province{fill:var(--red)!important}.topo-region.target .topo-province{fill:var(--green)!important}.topo-region:hover .topo-province,.topo-region:focus .topo-province{filter:brightness(.88) saturate(1.12)}.topo-region:focus-visible .topo-province{stroke:var(--navy);stroke-width:8}.topo-region-label{font-size:20px}
|
||||
.topo-river.inactive{opacity:.22}.topo-river-hit,.topo-river-line{fill:none;stroke-linecap:round;stroke-linejoin:round}.topo-river-hit{stroke:transparent;stroke-width:38;pointer-events:stroke}.topo-river-line{stroke:var(--river-color);stroke-width:10;pointer-events:none;transition:stroke .15s,stroke-width .15s}.topo-river:hover .topo-river-line,.topo-river:focus .topo-river-line{stroke-width:15}.topo-river:focus-visible .topo-river-line{stroke:var(--navy);stroke-width:18}.topo-river.picked .topo-river-line{stroke:var(--red)}.topo-river.target .topo-river-line{stroke:var(--green);stroke-width:16}.topo-river-label{fill:#18324e;paint-order:stroke;stroke:#fff;stroke-width:5px;stroke-linejoin:round;font-size:20px;font-style:italic;font-weight:950;text-anchor:middle;pointer-events:none}
|
||||
|
|
|
|||
|
|
@ -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.17-beta";
|
||||
const VERSION = "0.4.18-beta";
|
||||
(function(){
|
||||
const tag = document.getElementById("verTag");
|
||||
tag.textContent = "v"+VERSION;
|
||||
|
|
@ -124,7 +124,8 @@ const I18N = {
|
|||
wg_birds:"Vogels herkennen", wg_birds_d:"Leer achttien Nederlandse vogels met vier foto-, kenmerk- en naamoefeningen.",
|
||||
wg_plants:"Wilde planten", wg_plants_d:"Herken twaalf algemene wilde planten aan echte foto's en duidelijke veldkenmerken.",
|
||||
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", worldExercise:"Oefening", worldLevel:"Niveau", worldTreeView:"Wat zie je?", worldTreeWhole:"Hele boom", worldTreeLeaf:"Blad", worldTreeMixed:"Boom en blad",
|
||||
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! 🌟",
|
||||
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}.",
|
||||
|
|
@ -431,7 +432,8 @@ const I18N = {
|
|||
wg_birds:"Bird identification", wg_birds_d:"Learn eighteen Dutch birds with four photo, feature and naming exercises.",
|
||||
wg_plants:"Wild plants", wg_plants_d:"Recognise twelve common wild plants from real photographs and clear field marks.",
|
||||
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", worldExercise:"Exercise", worldLevel:"Level", worldTreeView:"What do you see?", worldTreeWhole:"Whole tree", worldTreeLeaf:"Leaf", worldTreeMixed:"Tree and leaf",
|
||||
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! 🌟",
|
||||
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}.",
|
||||
|
|
|
|||
|
|
@ -323,6 +323,182 @@ function worldIdentity(item){
|
|||
return identity;
|
||||
}
|
||||
|
||||
/* ---- flitskaarten: naam kort tonen, dan het beeld + zelftoets, zoals
|
||||
flits.js - hier gevoed door de wereldoriëntatie-content i.p.v. woordkaarten.
|
||||
Hergebruikt de generieke fl*-teksten (zelfde vraag/knoppen als flits.js). ---- */
|
||||
function worldFlashCard(card,name,visual,S,redraw,onNext,onAssess){
|
||||
const wrap=document.createElement("div");
|
||||
wrap.className="world-flash-card";
|
||||
wrap.append(visual);
|
||||
const nameZone=document.createElement("div");
|
||||
nameZone.className="world-flash-name"+(S.flashRevealed?"":" dim");
|
||||
nameZone.textContent=S.flashRevealed?name:"• • •";
|
||||
wrap.append(nameZone);
|
||||
card.append(wrap);
|
||||
if(!S.flashRevealed){
|
||||
const hint=document.createElement("p");
|
||||
hint.className="world-flash-hint";
|
||||
hint.textContent=T("flMission");
|
||||
card.append(hint);
|
||||
const actions=document.createElement("div");
|
||||
actions.className="world-actions";
|
||||
actions.append(btn(T("flShow"),()=>{S.flashRevealed=true;redraw()}));
|
||||
actions.append(btn(T("worldNext"),onNext,"ghost"));
|
||||
card.append(actions);
|
||||
}else if(!S.solved){
|
||||
const reflect=document.createElement("div");
|
||||
reflect.className="world-flash-reflect";
|
||||
const q=document.createElement("p");
|
||||
q.textContent=T("flQuestion");
|
||||
reflect.append(q);
|
||||
const row=document.createElement("div");
|
||||
row.className="world-actions";
|
||||
row.append(btn(T("flGotIt"),()=>onAssess(true)));
|
||||
row.append(btn(T("flAgain"),()=>onAssess(false),"ghost"));
|
||||
reflect.append(row);
|
||||
card.append(reflect);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- letterblokken: spel de naam met dezelfde sleep-blokken-mechaniek als
|
||||
letters.js, hier gevoed door de naam van de soort/plaats. Elk vak toont
|
||||
meteen het puntenpatroon (en de letter) van het juiste antwoord - bewust
|
||||
de makkelijkste ondersteuning, want deze namen zijn voor de meeste
|
||||
kinderen onbekende spelling; het quiz/foto/latijn-werk hierboven test
|
||||
het herkennen al veel strenger. */
|
||||
const worldIsLetter=ch=>/\p{L}/u.test(ch);
|
||||
const worldSpellPool=name=>[...name.toLowerCase()].filter(worldIsLetter);
|
||||
function worldSpellNeedsNewTray(S,name){
|
||||
const letters=worldSpellPool(name);
|
||||
return !Array.isArray(S.answerLetters)||S.answerLetters.length!==letters.length||!Array.isArray(S.trayLetters);
|
||||
}
|
||||
function worldSpellNewTray(S,name){
|
||||
const letters=worldSpellPool(name);
|
||||
const distractPool="abcdefghijklmnopqrstuvwxyz".split("").filter(c=>!letters.includes(c));
|
||||
S.trayLetters=worldShuffle([...letters,...worldShuffle(distractPool).slice(0,4)]);
|
||||
S.answerLetters=letters.map(()=>null);
|
||||
S.spellSolved=false;
|
||||
}
|
||||
function worldLetterBlock(ch,trayIndex,letters,S,redraw){
|
||||
const b=document.createElement("div");
|
||||
b.className="blok sm world-letter-block "+(VOWELS.includes(ch)?"vowel":"cons");
|
||||
b.dataset.letter=ch;
|
||||
const label=document.createElement("div");
|
||||
label.className="bl";
|
||||
label.textContent=ch;
|
||||
b.append(label);
|
||||
const dg=dotGrid(ch);
|
||||
dg.classList.add("blok-dots");
|
||||
b.append(dg);
|
||||
b.setAttribute("role","button");
|
||||
b.setAttribute("aria-label",T("clockDigit").replace("{digit}",ch));
|
||||
b.tabIndex=0;
|
||||
const settle=idx=>{S.answerLetters[idx]=trayIndex;sndGood();redraw()};
|
||||
b.onkeydown=e=>{
|
||||
if((e.key==="Enter"||e.key===" ")&&!S.spellSolved){
|
||||
e.preventDefault();
|
||||
const idx=letters.findIndex((need,i)=>need===ch&&S.answerLetters[i]===null);
|
||||
if(idx>=0)settle(idx);
|
||||
}
|
||||
};
|
||||
b.onpointerdown=e=>{
|
||||
if(S.spellSolved||(e.pointerType==="mouse"&&e.button!==0))return;
|
||||
e.preventDefault();
|
||||
try{b.releasePointerCapture(e.pointerId)}catch(_){}
|
||||
const clone=b.cloneNode(true);
|
||||
clone.id="dragClone";
|
||||
document.body.append(clone);
|
||||
b.classList.add("dragging");
|
||||
const moveTo=ev=>{const zoom=VZ();clone.style.left=ev.clientX/zoom+"px";clone.style.top=ev.clientY/zoom+"px"};
|
||||
moveTo(e);
|
||||
let hovered=null;
|
||||
const hit=ev=>{
|
||||
let found=null;
|
||||
document.querySelectorAll(".world-letter-slot:not(.filled)").forEach(slot=>{
|
||||
const r=slot.getBoundingClientRect(),pad=8;
|
||||
if(ev.clientX>=r.left-pad&&ev.clientX<=r.right+pad&&ev.clientY>=r.top-pad&&ev.clientY<=r.bottom+pad)found=slot;
|
||||
});
|
||||
return found;
|
||||
};
|
||||
const move=ev=>{
|
||||
moveTo(ev);
|
||||
const target=hit(ev);
|
||||
if(hovered&&hovered!==target)hovered.classList.remove("droptarget");
|
||||
hovered=target;
|
||||
if(hovered)hovered.classList.add("droptarget");
|
||||
};
|
||||
const clean=()=>{
|
||||
document.removeEventListener("pointermove",move);
|
||||
document.removeEventListener("pointerup",up);
|
||||
document.removeEventListener("pointercancel",cancel);
|
||||
clone.remove();
|
||||
b.classList.remove("dragging");
|
||||
if(hovered)hovered.classList.remove("droptarget");
|
||||
};
|
||||
const up=ev=>{
|
||||
const target=hit(ev);
|
||||
clean();
|
||||
if(target){
|
||||
const idx=+target.dataset.index;
|
||||
if(letters[idx]===ch)settle(idx);
|
||||
else{target.classList.add("shake");setTimeout(()=>target.classList.remove("shake"),400);sndBad()}
|
||||
}
|
||||
};
|
||||
const cancel=clean;
|
||||
document.addEventListener("pointermove",move);
|
||||
document.addEventListener("pointerup",up);
|
||||
document.addEventListener("pointercancel",cancel);
|
||||
};
|
||||
return b;
|
||||
}
|
||||
function worldSpellAnswer(name,S,redraw){
|
||||
if(worldSpellNeedsNewTray(S,name))worldSpellNewTray(S,name);
|
||||
const letters=worldSpellPool(name);
|
||||
const wrap=document.createElement("div");
|
||||
wrap.className="world-spell-answer";
|
||||
const wordRow=document.createElement("div");
|
||||
wordRow.className="world-spell-word";
|
||||
let slotIdx=0;
|
||||
[...name.toLowerCase()].forEach(ch=>{
|
||||
if(!worldIsLetter(ch)){
|
||||
const gap=document.createElement("span");
|
||||
gap.className="world-spell-gap";
|
||||
wordRow.append(gap);
|
||||
return;
|
||||
}
|
||||
const idx=slotIdx++;
|
||||
const slot=document.createElement("div");
|
||||
slot.className="slot sm world-letter-slot"+(S.answerLetters[idx]!==null?" filled":"");
|
||||
slot.dataset.index=idx;
|
||||
const ghost=document.createElement("div");
|
||||
ghost.className="ghost";
|
||||
ghost.textContent=letters[idx];
|
||||
const dots=dotGrid(letters[idx]);
|
||||
dots.classList.add("slot-dots");
|
||||
slot.append(ghost,dots);
|
||||
if(S.answerLetters[idx]!==null){
|
||||
const trayIndex=S.answerLetters[idx];
|
||||
const placed=worldLetterBlock(S.trayLetters[trayIndex],trayIndex,letters,S,redraw);
|
||||
placed.classList.add("in-slot");
|
||||
placed.tabIndex=-1;
|
||||
placed.onpointerdown=null;
|
||||
placed.onkeydown=null;
|
||||
slot.append(placed);
|
||||
slot.onclick=()=>{if(!S.spellSolved){S.answerLetters[idx]=null;redraw()}};
|
||||
}
|
||||
wordRow.append(slot);
|
||||
});
|
||||
wrap.append(wordRow);
|
||||
const tray=document.createElement("div");
|
||||
tray.className="lg-tray world-spell-tray";
|
||||
S.trayLetters.forEach((ch,trayIndex)=>{
|
||||
if(S.answerLetters.includes(trayIndex))return;
|
||||
tray.append(worldLetterBlock(ch,trayIndex,letters,S,redraw));
|
||||
});
|
||||
wrap.append(tray);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function mountWorldQuiz(root,init,config){
|
||||
const exerciseOptions=config.type==="trees"
|
||||
?[["recognize","worldExerciseRecognize"],["pair","worldExercisePair"],["clue","worldExerciseClue"],["photo","worldExercisePhoto"],["latin","worldExerciseLatin"]]
|
||||
|
|
@ -330,10 +506,11 @@ function mountWorldQuiz(root,init,config){
|
|||
const S=Object.assign({
|
||||
activity:"quiz",exercise:"recognize",level:1,view:config.type==="trees"?"mixed":config.type==="birds"?"bird":"plant",
|
||||
score:0,streak:0,target:"",taskView:"",options:[],picked:"",solved:false,feedback:"",
|
||||
flashRevealed:false,answerLetters:null,trayLetters:null,spellSolved:false,
|
||||
},init||{});
|
||||
S.level=Math.max(1,Math.min(3,+S.level||1));
|
||||
if(!exerciseOptions.some(([id])=>id===S.exercise))S.exercise="recognize";
|
||||
const activity=worldSelect(S.activity,[["learn","worldLearn"],["quiz","worldQuiz"]]);
|
||||
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");
|
||||
|
|
@ -359,6 +536,10 @@ function mountWorldQuiz(root,init,config){
|
|||
S.picked="";
|
||||
S.solved=false;
|
||||
S.feedback="";
|
||||
S.flashRevealed=false;
|
||||
S.answerLetters=null;
|
||||
S.trayLetters=null;
|
||||
S.spellSolved=false;
|
||||
draw();
|
||||
};
|
||||
const answer=id=>{
|
||||
|
|
@ -371,6 +552,14 @@ function mountWorldQuiz(root,init,config){
|
|||
}
|
||||
draw();
|
||||
};
|
||||
const assessFlash=ok=>{
|
||||
if(S.solved)return;
|
||||
S.solved=true;
|
||||
if(ok){S.feedback="good";S.score++;S.streak++;sndGood()}
|
||||
else{S.feedback="bad";S.streak=0;sndBad()}
|
||||
draw();
|
||||
};
|
||||
const worldViewFor=()=>config.type==="trees"?(S.view==="mixed"?"tree":S.view):config.type==="birds"?"bird":"plant";
|
||||
const gallery=()=>{
|
||||
const grid=document.createElement("div");
|
||||
grid.className="world-gallery";
|
||||
|
|
@ -390,7 +579,7 @@ function mountWorldQuiz(root,init,config){
|
|||
S.exercise=exercise.value;
|
||||
S.level=+level.value;
|
||||
if(view)S.view=view.value;
|
||||
exerciseSetting.hidden=S.activity==="learn";
|
||||
exerciseSetting.hidden=S.activity!=="quiz";
|
||||
if(viewSetting)viewSetting.hidden=S.activity==="quiz"&&!["recognize","photo"].includes(S.exercise);
|
||||
stage.className="world-app";
|
||||
stage.innerHTML="";
|
||||
|
|
@ -411,6 +600,60 @@ function mountWorldQuiz(root,init,config){
|
|||
}
|
||||
const item=current(),card=document.createElement("section");
|
||||
card.className="world-task-card";
|
||||
if(S.activity==="flash"){
|
||||
worldFlashCard(card,worldName(item),worldVisual(item,config.type,worldViewFor()),S,draw,()=>{S.flashRevealed=true;draw()},assessFlash);
|
||||
if(S.solved){
|
||||
const fact=document.createElement("div");
|
||||
fact.className="world-fact";
|
||||
fact.append(worldIdentity(item));
|
||||
const factHint=document.createElement("p");
|
||||
factHint.textContent=worldHint(item);
|
||||
fact.append(factHint);
|
||||
card.append(fact);
|
||||
}
|
||||
const flashFeedback=document.createElement("p");
|
||||
flashFeedback.className="world-feedback "+(S.feedback==="good"?"ok":S.feedback?"bad":"");
|
||||
flashFeedback.setAttribute("role","status");
|
||||
if(S.feedback)flashFeedback.textContent=T(S.feedback==="good"?"worldCorrect":"worldTryAgain");
|
||||
card.append(flashFeedback);
|
||||
const flashActions=document.createElement("div");
|
||||
flashActions.className="world-actions";
|
||||
flashActions.append(btn(T("worldNext"),newTask,"ghost"));
|
||||
card.append(flashActions);
|
||||
stage.append(card);
|
||||
return;
|
||||
}
|
||||
if(S.activity==="spell"){
|
||||
card.append(worldVisual(item,config.type,worldViewFor()));
|
||||
const spellHint=document.createElement("p");
|
||||
spellHint.className="world-spell-hint";
|
||||
spellHint.textContent=T("worldSpellHint");
|
||||
card.append(spellHint);
|
||||
card.append(worldSpellAnswer(worldName(item),S,draw));
|
||||
if(!S.spellSolved&&S.answerLetters.every(v=>v!==null)){
|
||||
S.spellSolved=true;S.score++;S.streak++;sndGood();
|
||||
}
|
||||
const spellFeedback=document.createElement("p");
|
||||
spellFeedback.className="world-feedback "+(S.spellSolved?"ok":"");
|
||||
spellFeedback.setAttribute("role","status");
|
||||
if(S.spellSolved)spellFeedback.textContent=T("worldSpellComplete");
|
||||
card.append(spellFeedback);
|
||||
if(S.spellSolved){
|
||||
const fact=document.createElement("div");
|
||||
fact.className="world-fact";
|
||||
fact.append(worldIdentity(item));
|
||||
const factHint=document.createElement("p");
|
||||
factHint.textContent=worldHint(item);
|
||||
fact.append(factHint);
|
||||
card.append(fact);
|
||||
}
|
||||
const spellActions=document.createElement("div");
|
||||
spellActions.className="world-actions";
|
||||
spellActions.append(btn(T("worldNext"),newTask,"ghost"));
|
||||
card.append(spellActions);
|
||||
stage.append(card);
|
||||
return;
|
||||
}
|
||||
const question=document.createElement("h3");
|
||||
if(S.exercise==="photo"){
|
||||
const photoQuestion=config.type==="birds"?"worldFindBirdPhoto":config.type==="plants"?"worldFindPlantPhoto":"worldFindTreePhoto";
|
||||
|
|
@ -477,12 +720,13 @@ function mountWorldQuiz(root,init,config){
|
|||
}
|
||||
stage.append(card);
|
||||
}
|
||||
activity.onchange=()=>{S.activity=activity.value;if(S.activity==="quiz")newTask();else draw()};
|
||||
exercise.onchange=()=>{S.exercise=exercise.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()};
|
||||
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(S.activity==="quiz")newTask();else draw();
|
||||
if(isTaskActivity())newTask();else draw();
|
||||
return{getState:()=>({...S,options:[...S.options]})};
|
||||
}
|
||||
|
||||
|
|
@ -673,10 +917,11 @@ function mountTopography(root,init){
|
|||
const S=Object.assign({
|
||||
activity:"quiz",mode:"provinces",level:1,score:0,streak:0,
|
||||
target:"",picked:"",solved:false,feedback:"",
|
||||
flashRevealed:false,answerLetters:null,trayLetters:null,spellSolved:false,
|
||||
},init||{});
|
||||
S.level=Math.max(1,Math.min(3,+S.level||1));
|
||||
if(!modeOptions.some(([id])=>id===S.mode))S.mode="provinces";
|
||||
const activity=worldSelect(S.activity,[["learn","worldLearn"],["quiz","worldQuiz"]]);
|
||||
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");
|
||||
|
|
@ -696,6 +941,7 @@ function mountTopography(root,init){
|
|||
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="";
|
||||
S.flashRevealed=false;S.answerLetters=null;S.trayLetters=null;S.spellSolved=false;
|
||||
draw();
|
||||
};
|
||||
const answer=id=>{
|
||||
|
|
@ -708,6 +954,13 @@ function mountTopography(root,init){
|
|||
}
|
||||
draw();
|
||||
};
|
||||
const assessFlash=ok=>{
|
||||
if(S.solved)return;
|
||||
S.solved=true;
|
||||
if(ok){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;
|
||||
levelSetting.hidden=S.mode==="regions";
|
||||
|
|
@ -731,6 +984,38 @@ function mountTopography(root,init){
|
|||
intro.className="world-intro";
|
||||
intro.textContent=T(S.mode==="regions"?"topoExploreRegions":S.mode==="rivers"?"topoExploreRivers":S.mode==="continents"?"topoExploreContinents":"topoExploreHint");
|
||||
card.append(intro,renderMap(null,true,"",""));
|
||||
}else if(S.activity==="flash"){
|
||||
const item=current();
|
||||
worldFlashCard(card,worldName(item),renderMap(null,S.flashRevealed,"",S.flashRevealed?S.target:""),S,draw,()=>{S.flashRevealed=true;draw()},assessFlash);
|
||||
const flashFeedback=document.createElement("p");
|
||||
flashFeedback.className="world-feedback "+(S.feedback==="good"?"ok":S.feedback?"bad":"");
|
||||
flashFeedback.setAttribute("role","status");
|
||||
if(S.feedback)flashFeedback.textContent=T(S.feedback==="good"?"worldCorrect":"worldTryAgain");
|
||||
card.append(flashFeedback);
|
||||
const flashActions=document.createElement("div");
|
||||
flashActions.className="world-actions";
|
||||
flashActions.append(btn(T("worldNext"),newTask,"ghost"));
|
||||
card.append(flashActions);
|
||||
}else if(S.activity==="spell"){
|
||||
const item=current();
|
||||
card.append(renderMap(null,true,"",S.target));
|
||||
const spellHint=document.createElement("p");
|
||||
spellHint.className="world-spell-hint";
|
||||
spellHint.textContent=T("worldSpellHint");
|
||||
card.append(spellHint);
|
||||
card.append(worldSpellAnswer(worldName(item),S,draw));
|
||||
if(!S.spellSolved&&S.answerLetters.every(v=>v!==null)){
|
||||
S.spellSolved=true;S.score++;S.streak++;sndGood();
|
||||
}
|
||||
const spellFeedback=document.createElement("p");
|
||||
spellFeedback.className="world-feedback "+(S.spellSolved?"ok":"");
|
||||
spellFeedback.setAttribute("role","status");
|
||||
if(S.spellSolved)spellFeedback.textContent=T("worldSpellComplete");
|
||||
card.append(spellFeedback);
|
||||
const spellActions=document.createElement("div");
|
||||
spellActions.className="world-actions";
|
||||
spellActions.append(btn(T("worldNext"),newTask,"ghost"));
|
||||
card.append(spellActions);
|
||||
}else{
|
||||
const item=current(),question=document.createElement("h3");
|
||||
const questionKey=S.mode==="provinces"?"topoFindProvince"
|
||||
|
|
@ -752,14 +1037,15 @@ function mountTopography(root,init){
|
|||
}
|
||||
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()};
|
||||
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(S.activity==="quiz")newTask();else draw();
|
||||
if(isTaskActivity())newTask();else draw();
|
||||
return{getState:()=>({...S})};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue