/* teach - woorddata: algemene kaart, thema's, gedeelde woordbronnen */ /* ========================================================= Letter game data ==========================================================*/ const DOTCODE = { a:[1], b:[1,2], c:[1,4], d:[1,4,5], e:[1,5], f:[1,2,4], g:[1,2,4,5], h:[1,2,5], i:[2,4], j:[2,4,5], k:[1,3], l:[1,2,3], m:[1,3,4], n:[1,3,4,5], o:[1,3,5], p:[1,2,3,4], q:[1,2,3,4,5], r:[1,2,3,5], s:[2,3,4], t:[2,3,4,5], u:[1,3,6], v:[1,2,3,6], w:[2,4,5,6], x:[1,3,4,6], y:[1,3,4,5,6], z:[1,3,5,6], /* digits use the braille number patterns (aโ€“j) */ "1":[1], "2":[1,2], "3":[1,4], "4":[1,4,5], "5":[1,5], "6":[1,2,4], "7":[1,2,4,5], "8":[1,2,5], "9":[2,4], "0":[2,4,5] }; const VOWELS = "aeiou"; function dotGrid(letter){ const code = DOTCODE[letter] || []; const grid = document.createElement("div"); grid.className = "dots"; [1,4,2,5,3,6].forEach(p=>{ const d = document.createElement("i"); d.className = code.includes(p) ? "on" : "off"; grid.appendChild(d); }); return grid; } /* De taalplaatjes hebben รฉรฉn centrale bron. Daardoor gebruiken de spelletjes, de themakiezer en de afbeeldingenbibliotheek altijd dezelfde woorden en verdwijnen oude plaatjes niet wanneer de woordenschat wordt uitgebreid. */ const LANGUAGE_THEME_DEFS = [ {id:"animals", icon:"๐Ÿพ", nl:"Dieren", en:"Animals", colors:["#eaf7df","#cdeeb5"]}, {id:"food", icon:"๐ŸŽ", nl:"Eten & drinken", en:"Food & drink", colors:["#fff0dc","#ffd9aa"]}, {id:"nature", icon:"๐ŸŒฟ", nl:"Natuur & weer", en:"Nature & weather", colors:["#e4f7ee","#bfead4"]}, {id:"travel", icon:"๐Ÿšฒ", nl:"Onderweg", en:"Getting around", colors:["#e6f3ff","#beddf8"]}, {id:"home", icon:"๐ŸŽ’", nl:"Thuis & school", en:"Home & school", colors:["#f1ecff","#d9ccff"]}, {id:"body", icon:"๐Ÿ‘•", nl:"Lichaam & kleding", en:"Body & clothes", colors:["#ffeaf1","#ffcbdc"]}, {id:"actions", icon:"๐Ÿƒ", nl:"Doen", en:"Actions", colors:["#fff8d9","#f6e8a4"]}, {id:"feelings", icon:"๐Ÿ™‚", nl:"Gevoelens", en:"Feelings", colors:["#e9f5ff","#c8e4fa"]} ]; const LANGUAGE_CONCEPTS = [ ["monkey","animals","aap","monkey","๐Ÿต"], ["cat","animals","kat","cat","๐Ÿˆ"], ["dog","animals","hond","dog","๐Ÿ•"], ["fish","animals","vis","fish","๐ŸŸ"], ["hen","animals","kip","hen","๐Ÿ”"], ["mouse","animals","muis","mouse","๐Ÿญ"], ["bear","animals","beer","bear","๐Ÿป"], ["duck","animals","eend","duck","๐Ÿฆ†"], ["frog","animals","kikker","frog","๐Ÿธ"], ["bee","animals","bij","bee","๐Ÿ"], ["cow","animals","koe","cow","๐Ÿ„"], ["pig","animals","varken","pig","๐Ÿท"], ["owl","animals","uil","owl","๐Ÿฆ‰"], ["goat","animals","geit","goat","๐Ÿ"], ["hare","animals","haas","hare","๐Ÿ‡"], ["horse","animals","paard","horse","๐Ÿด"], ["lion","animals","leeuw","lion","๐Ÿฆ"], ["ant","animals","mier","ant","๐Ÿœ"], ["egg","food","ei","egg","๐Ÿฅš"], ["apple","food","appel","apple","๐ŸŽ"], ["pear","food","peer","pear","๐Ÿ"], ["cheese","food","kaas","cheese","๐Ÿง€"], ["milk","food","melk","milk","๐Ÿฅ›"], ["cake","food","taart","cake","๐ŸŽ‚"], ["bread","food","brood","bread","๐Ÿž"], ["soup","food","soep","soup","๐Ÿฅฃ"], ["rice","food","rijst","rice","๐Ÿš"], ["corn","food","mais","corn","๐ŸŒฝ"], ["grape","food","druif","grape","๐Ÿ‡"], ["strawberry","food","aardbei","strawberry","๐Ÿ“"], ["candy","food","snoep","candy","๐Ÿฌ"], ["lemon","food","citroen","lemon","๐Ÿ‹"], ["pizza","food","pizza","pizza","๐Ÿ•"], ["juice","food","sap","juice","๐Ÿงƒ"], ["cookie","food","koek","cookie","๐Ÿช"], ["carrot","food","wortel","carrot","๐Ÿฅ•"], ["sun","nature","zon","sun","โ˜€๏ธ"], ["moon","nature","maan","moon","๐ŸŒ™"], ["star","nature","ster","star","โญ"], ["tree","nature","boom","tree","๐ŸŒณ"], ["rose","nature","roos","rose","๐ŸŒน"], ["fire","nature","vuur","fire","๐Ÿ”ฅ"], ["flower","nature","bloem","flower","๐ŸŒท"], ["leaf","nature","blad","leaf","๐Ÿƒ"], ["cloud","nature","wolk","cloud","โ˜๏ธ"], ["rain","nature","regen","rain","๐ŸŒง๏ธ"], ["snow","nature","sneeuw","snow","โ„๏ธ"], ["water","nature","water","water","๐Ÿ’ง"], ["stone","nature","steen","stone","๐Ÿชจ"], ["sand","nature","zand","sand","๐Ÿ–๏ธ"], ["sea","nature","zee","sea","๐ŸŒŠ"], ["forest","nature","bos","forest","๐ŸŒฒ"], ["grass","nature","gras","grass","๐ŸŒฑ"], ["wind","nature","wind","wind","๐Ÿ’จ"], ["rainbow","nature","regenboog","rainbow","๐ŸŒˆ"], ["car","travel","auto","car","๐Ÿš—"], ["bike","travel","fiets","bike","๐Ÿšฒ"], ["boat","travel","boot","boat","โ›ต"], ["train","travel","trein","train","๐Ÿš‚"], ["bus","travel","bus","bus","๐ŸšŒ"], ["taxi","travel","taxi","taxi","๐Ÿš•"], ["truck","travel","vrachtauto","truck","๐Ÿšš"], ["jet","travel","vliegtuig","plane","โœˆ๏ธ"], ["tram","travel","tram","tram","๐Ÿš‹"], ["ship","travel","schip","ship","๐Ÿšข"], ["rocket","travel","raket","rocket","๐Ÿš€"], ["road","travel","weg","road","๐Ÿ›ฃ๏ธ"], ["wheel","travel","wiel","wheel","๐Ÿ›ž"], ["sail","travel","zeil","sail","โ›ต"], ["scooter","travel","step","scooter","๐Ÿ›ด"], ["tractor","travel","tractor","tractor","๐Ÿšœ"], ["helicopter","travel","helikopter","helicopter","๐Ÿš"], ["house","home","huis","house","๐Ÿ "], ["book","home","boek","book","๐Ÿ“•"], ["pen","home","pen","pen","๐Ÿ–Š๏ธ"], ["pencil","home","potlood","pencil","โœ๏ธ"], ["bag","home","tas","bag","๐ŸŽ’"], ["coat","home","jas","coat","๐Ÿงฅ"], ["chair","home","stoel","chair","๐Ÿช‘"], ["bed","home","bed","bed","๐Ÿ›๏ธ"], ["door","home","deur","door","๐Ÿšช"], ["clock","home","klok","clock","โฐ"], ["glasses","home","bril","glasses","๐Ÿ‘“"], ["ball","home","bal","ball","โšฝ"], ["doll","home","pop","doll","๐Ÿช†"], ["block","home","blok","block","๐Ÿงฑ"], ["key","home","sleutel","key","๐Ÿ”‘"], ["lamp","home","lamp","lamp","๐Ÿ’ก"], ["cup","home","beker","cup","๐Ÿฅค"], ["brush","home","borstel","brush","๐Ÿชฅ"], ["drum","home","trommel","drum","๐Ÿฅ"], ["gift","home","cadeau","gift","๐ŸŽ"], ["paint","home","verf","paint","๐ŸŽจ"], ["scissors","home","schaar","scissors","โœ‚๏ธ"], ["desk","home","bureau","desk","๐Ÿ—„๏ธ"], ["school","home","school","school","๐Ÿซ"], ["hand","body","hand","hand","โœ‹"], ["foot","body","voet","foot","๐Ÿฆถ"], ["nose","body","neus","nose","๐Ÿ‘ƒ"], ["eye","body","oog","eye","๐Ÿ‘๏ธ"], ["ear","body","oor","ear","๐Ÿ‘‚"], ["mouth","body","mond","mouth","๐Ÿ‘„"], ["hair","body","haar","hair","๐Ÿง‘โ€๐Ÿฆฑ"], ["head","body","hoofd","head","๐Ÿ™‚"], ["arm","body","arm","arm","๐Ÿ’ช"], ["leg","body","been","leg","๐Ÿฆต"], ["cap","body","pet","cap","๐Ÿงข"], ["sock","body","sok","sock","๐Ÿงฆ"], ["shoe","body","schoen","shoe","๐Ÿ‘Ÿ"], ["dress","body","jurk","dress","๐Ÿ‘—"], ["shirt","body","shirt","shirt","๐Ÿ‘•"], ["glove","body","want","glove","๐Ÿงค"], ["pants","body","broek","trousers","๐Ÿ‘–"], ["skirt","body","rok","skirt","๐Ÿ‘—"], ["tooth","body","tand","tooth","๐Ÿฆท"], ["read","actions","lezen","read","๐Ÿ“–"], ["eat","actions","eten","eat","๐Ÿฝ๏ธ"], ["drink","actions","drinken","drink","๐Ÿฅค"], ["sleep","actions","slapen","sleep","๐Ÿ˜ด"], ["walk","actions","lopen","walk","๐Ÿšถ"], ["swim","actions","zwemmen","swim","๐ŸŠ"], ["jump","actions","springen","jump","๐Ÿคธ"], ["laugh","actions","lachen","laugh","๐Ÿ˜„"], ["cry","actions","huilen","cry","๐Ÿ˜ข"], ["sing","actions","zingen","sing","๐ŸŽค"], ["dance","actions","dansen","dance","๐Ÿ’ƒ"], ["draw","actions","tekenen","draw","๐Ÿ–๏ธ"], ["write","actions","schrijven","write","โœ๏ธ"], ["play","actions","spelen","play","๐Ÿงธ"], ["sit","actions","zitten","sit","๐ŸงŽ"], ["run","actions","rennen","run","๐Ÿƒ"], ["climb","actions","klimmen","climb","๐Ÿง—"], ["wave","actions","zwaaien","wave","๐Ÿ‘‹"], ["look","actions","kijken","look","๐Ÿ‘€"], ["think","actions","denken","think","๐Ÿค”"], ["happy","feelings","blij","happy","๐Ÿ˜€"], ["sad","feelings","verdrietig","sad","๐Ÿ˜ข"], ["angry","feelings","boos","angry","๐Ÿ˜ "], ["scared","feelings","bang","scared","๐Ÿ˜จ"], ["tired","feelings","moe","tired","๐Ÿฅฑ"], ["sick","feelings","ziek","sick","๐Ÿค’"], ["proud","feelings","trots","proud","๐Ÿ˜Œ"], ["calm","feelings","rustig","calm","๐Ÿ™‚"], ["surprised","feelings","verrast","surprised","๐Ÿ˜ฎ"], ["excited","feelings","enthousiast","excited","๐Ÿคฉ"] ].map(([id,theme,nl,en,pic])=>({id,theme,nl,en,pic})); /* De vertrouwde startkaart blijft exact dezelfde 30 woorden houden. */ const START_WORD_KEYS = { nl:["monkey","fish","sun","cat","ball","egg","tree","house","moon","boat","hen","mouse","book","star","rose","fire","bike","cake","coat","glasses","clock","pear","cheese","duck","hand","foot","nose","eye","ear","mouth"], en:["cat","dog","sun","egg","ball","fish","tree","moon","boat","star","book","cake","house","mouse","apple","train","fire","rose","bear","duck","frog","bee","cow","pig","hand","foot","nose","eye","ear","milk"] }; const WORDS = Object.fromEntries(["nl","en"].map(lang=>[ lang, START_WORD_KEYS[lang].map(id=>{ const concept=LANGUAGE_CONCEPTS.find(item=>item.id===id); return [concept[lang],concept.pic]; }) ])); function languageThemeList(lang=LANG){ return LANGUAGE_THEME_DEFS.map(theme=>({ ...theme, name:theme[lang], words:LANGUAGE_CONCEPTS.filter(item=>item.theme===theme.id).map(item=>[item[lang],item.pic]) })); } function languageEmojiDataUrl(emoji,colors=["#f4f8ff","#dce9fa"]){ const svg=`${emoji}`; return "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(svg); } function languagePictureCatalog(lang=LANG){ const builtinThemes=languageThemeList(lang).map((theme,index)=>({ id:-200-index,nameNl:theme.nl,nameEn:theme.en,scope:"global",filterOnly:true, languageBuiltin:true,wordPicture:true,icon:theme.icon })); const themeIds=new Map(LANGUAGE_THEME_DEFS.map((theme,index)=>[theme.id,-200-index])); const images=LANGUAGE_CONCEPTS.map((item,index)=>{ const theme=LANGUAGE_THEME_DEFS.find(def=>def.id===item.theme); return { id:-1000-index,name:item[lang],folder:`Taal/Woordenschat/${theme[lang]}`, scope:"global",themes:[themeIds.get(item.theme)],tags:[item.nl,item.en,theme.nl,theme.en], src:languageEmojiDataUrl(item.pic,theme.colors),value:item.pic,canManage:false,canDelete:false, wordPicture:true,builtin:true }; }); const ownThemes=[]; const customImages=[]; let customId=-5000; const addOwn=(words,name,folder,themeId)=>{ (words||[]).forEach(([word,pic])=>{ if(!word||!pic)return; customImages.push({ id:customId--,name:word,folder,scope:"personal",themes:[themeId],tags:[word,name], src:isImg(pic)?pic:languageEmojiDataUrl(pic),value:pic,canManage:false,canDelete:false, wordPicture:true,builtin:false }); }); }; const generalThemeId=-400; ownThemes.push({id:generalThemeId,nameNl:"Mijn woordkaarten",nameEn:"My word cards",scope:"personal",filterOnly:true,wordPicture:true,icon:"โญ"}); addOwn(GENERAL_EXTRA[lang],lang==="nl"?"Mijn woordkaarten":"My word cards","Taal/Mijn woordkaarten",generalThemeId); THEMES[lang].forEach((theme,index)=>{ const id=-401-index; ownThemes.push({id,nameNl:theme.name,nameEn:theme.name,scope:"personal",filterOnly:true,wordPicture:true,icon:"๐Ÿ“"}); addOwn(theme.words,theme.name,["Taal/Mijn themaโ€™s",theme.folder,theme.name].filter(Boolean).join("/"),id); }); return {themes:builtinThemes.concat(ownThemes),images:images.concat(customImages)}; } /* teacher's own additions to the general card (persisted per user) */ let GENERAL_EXTRA = {nl:[], en:[]}; function generalList(){ return WORDS[LANG].concat(GENERAL_EXTRA[LANG]); } const shuffle = a => { a=a.slice(); for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]];} return a; }; /* custom themes: folders of own words. {nl:[{name:"Herfst", words:[[word,pic],...]}], en:[...]} */ let THEMES = {nl:[], en:[]}; /* ankerbibliotheek: los van borden bewaarde ankerdiagrammen, herbruikbaar op elk bord. [{id, name, folder?, cells, pics}] - cells/pics in dezelfde vorm als de widget-staat van anchor.js; folder is een pad ("A/B") zoals bij de thema's en borden in de mappenverkenner. */ let ANCHOR_LIB = []; function themesFromData(data){ if(!data) return {nl:[],en:[]}; if(data.themes) return data.themes; /* migrate old flat word lists into one theme */ const t = {nl:[],en:[]}; if(data.words) ["nl","en"].forEach(l=>{ if(Array.isArray(data.words[l]) && data.words[l].length) t[l] = [{name: l==="nl" ? "Mijn woorden" : "My words", words: data.words[l]}]; }); return t; } const isImg = p => typeof p==="string" && /^(?:data:|https?:|blob:|\/)/.test(p); function setPic(el, pic){ if(isImg(pic)){ el.innerHTML = ""; const im = document.createElement("img"); im.src = pic; im.alt=""; el.appendChild(im); } else el.textContent = pic; } /* ---- ARASAAC pictogram lookup (shared by the word editor + anchor grid) ---- */ async function arasaacSearch(term, lang){ const resp = await fetch(`https://api.arasaac.org/api/pictograms/${lang}/search/${encodeURIComponent(term)}`); if(!resp.ok) throw new Error("http "+resp.status); return resp.json(); } const arasaacUrl = id => `https://static.arasaac.org/pictograms/${id}/${id}_300.png`; /* ---- shared word source (general card + themes + anchor grids) ---- */ /* anchor-word widgets register their words here so every language widget can use them as a theme automatically */ let ANCHORS = []; /* [{el, name, words:[[w,pic],...]}] */ function anchorSources(){ ANCHORS = ANCHORS.filter(a=>a.el && document.contains(a.el)); return ANCHORS.filter(a=>a.words.length); } function normWordSource(card){ if(typeof card==="string"){ if(card.startsWith("b:")){ if(languageThemeList().some(theme=>theme.id===card.slice(2))) return card; }else if(card.startsWith("t")){ const th = THEMES[LANG][+card.slice(1)]; if(th && th.words.length) return card; }else if(card.startsWith("a")){ if(anchorSources()[+card.slice(1)]) return card; } } return "g"; } function wordSourceList(card){ card = normWordSource(card); if(card.startsWith("b:")) return languageThemeList().find(theme=>theme.id===card.slice(2)).words; if(card.startsWith("t")) return THEMES[LANG][+card.slice(1)].words; if(card.startsWith("a")) return anchorSources()[+card.slice(1)].words; return generalList(); } function fillWordSourceSel(sel, card){ sel.innerHTML = ""; const og = document.createElement("option"); og.value = "g"; og.textContent = "โญ "+T("cardStart"); sel.appendChild(og); const builtinGroup = document.createElement("optgroup"); builtinGroup.label = "๐Ÿ“š "+T("wordThemes"); languageThemeList().forEach(theme=>{ const option=document.createElement("option"); option.value="b:"+theme.id; option.textContent=`${theme.icon} ${theme.name} (${theme.words.length})`; builtinGroup.appendChild(option); }); sel.appendChild(builtinGroup); /* thema's zonder map bovenaan, daarna per map gegroepeerd (optgroup) - zelfde indeling als de theme-dropdown in de woorden-editor */ const groups = new Map(); THEMES[LANG].forEach((th,i)=>{ if(!th.words.length) return; const f = th.folder || ""; if(!groups.has(f)) groups.set(f, []); groups.get(f).push([th, i]); }); const addOpt = (parent, th, i)=>{ const o = document.createElement("option"); o.value = "t"+i; o.textContent = "โ˜… "+th.name; parent.appendChild(o); }; (groups.get("") || []).forEach(([th,i])=>addOpt(sel, th, i)); [...groups.keys()].filter(f=>f).sort().forEach(f=>{ const grp = document.createElement("optgroup"); grp.label = "๐Ÿ“ " + f; groups.get(f).forEach(([th,i])=>addOpt(grp, th, i)); sel.appendChild(grp); }); anchorSources().forEach((a,i)=>{ const o = document.createElement("option"); o.value = "a"+i; o.textContent = "โš“ "+a.name; sel.appendChild(o); }); sel.value = normWordSource(card); if(sel.selectedIndex < 0) sel.selectedIndex = 0; } /* emoji palette (grouped) */ const splitEmoji = s => { const out = []; for(const ch of s){ if(out.length && (ch==="๏ธ" || ch==="โ€")) out[out.length-1]+=ch; else if(out.length && out[out.length-1].endsWith("โ€")) out[out.length-1]+=ch; else out.push(ch); } return out; }; const EMOJI_CATS = [ splitEmoji("๐Ÿต๐Ÿถ๐Ÿฑ๐Ÿญ๐Ÿน๐Ÿฐ๐ŸฆŠ๐Ÿป๐Ÿผ๐Ÿจ๐Ÿฏ๐Ÿฆ๐Ÿฎ๐Ÿท๐Ÿธ๐Ÿ”๐Ÿง๐Ÿฆ๐Ÿฆ†๐Ÿฆ‰๐Ÿด๐Ÿฆ„๐Ÿ๐Ÿ›๐Ÿฆ‹๐ŸŒ๐Ÿž๐Ÿ ๐ŸŸ๐Ÿฌ๐Ÿณ๐Ÿข๐Ÿ๐ŸฆŽ๐Ÿ˜๐Ÿฆ’"), splitEmoji("๐ŸŽ๐Ÿ๐ŸŠ๐Ÿ‹๐ŸŒ๐Ÿ‰๐Ÿ‡๐Ÿ“๐Ÿ’๐Ÿ‘๐Ÿฅ๐Ÿ…๐Ÿฅ•๐ŸŒฝ๐Ÿฅ”๐Ÿง€๐Ÿฅš๐Ÿž๐Ÿฅ๐Ÿฅž๐Ÿ•๐ŸŸ๐Ÿฆ๐Ÿช๐ŸŽ‚๐Ÿซ๐Ÿฌ๐Ÿฅ›๐Ÿงƒ๐Ÿฏ"), splitEmoji("๐ŸŒณ๐ŸŒฒ๐ŸŒด๐ŸŒต๐ŸŒท๐ŸŒน๐ŸŒป๐ŸŒธ๐Ÿ’๐Ÿ„โญ๐ŸŒ™โ˜€๏ธโ›…๐ŸŒˆโ„๏ธ๐Ÿ”ฅ๐Ÿ’ง๐ŸŒŠ๐Ÿชจ"), splitEmoji("๐Ÿš—๐Ÿš•๐ŸšŒ๐Ÿš‘๐Ÿš’๐Ÿšœ๐Ÿšฒ๐Ÿ›ด๐Ÿ๏ธ๐Ÿš‚โœˆ๏ธ๐Ÿšโ›ต๐Ÿš€๐Ÿ›ถ๐Ÿšข"), splitEmoji("โšฝ๐Ÿ€๐ŸŽˆ๐ŸŽ๐Ÿงธ๐Ÿ“•โœ๏ธ๐Ÿ–๏ธโœ‚๏ธ๐ŸŽจ๐Ÿฅ๐ŸŽธ๐Ÿ‘“โŒšโฐ๐Ÿ’ก๐Ÿ”‘๐Ÿงค๐Ÿงข๐Ÿ‘Ÿ๐Ÿ‘—๐Ÿงฅ๐Ÿ›๏ธ๐Ÿช‘๐Ÿšช๐Ÿ "), splitEmoji("โœ‹๐Ÿ‘‚๐Ÿ‘ƒ๐Ÿ‘๏ธ๐Ÿ‘„๐Ÿฆถ๐Ÿ˜€๐Ÿ˜ข๐Ÿ˜ด๐Ÿคง๐Ÿ‘ถ๐Ÿ‘ฆ๐Ÿ‘ง๐Ÿ‘ต๐Ÿ‘ด") ];