Voeg thematische kleurplaten en creatieve niveaus toe (v0.4.65-beta) #3

Open
bes-r wants to merge 207 commits from bes-r/kleurplaten-v0-4-65 into main AGit
4 changed files with 107 additions and 4 deletions
Showing only changes of commit 6f593e05b2 - Show all commits

View file

@ -1 +1 @@
0.4.43-beta
0.4.44-beta

View file

@ -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.43-beta";
const VERSION = "0.4.44-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;

View file

@ -411,6 +411,85 @@ const LANGUAGE_CONCEPTS = [
["season","time","seizoen","season","🍁"]
].map(([id,theme,nl,en,pic])=>({id,theme,nl,en,pic}));
/* Gecontroleerde Nederlandse beeldcorrecties. Bestaande handgekozen emoji
blijven de terugval; deze lijst corrigeert polyseme woorden en geeft veel
voorkomende concrete woorden een specifieker beeld. */
const LANGUAGE_PICTURE_EXACT=Object.freeze({
groentekraam:"🥕🏪",veehouderij:"🐄",wastafel:"🚰",woonkamermeubel:"🛋️",badkamertegel:"🚿",prullenbak:"🗑️",
tekstproductie:"✍️📄",verdrietverwerking:"😢🧠",bezorgd:"😟",ouderverzorging:"👩❤️",spierkracht:"💪",
schoolslag:"🏊",wedstrijdreglement:"🏀📋",toneelspeler:"🎭",spellingspatroon:"🔤",meetinstrument:"📐",
lentebloem:"🌼",zomerdag:"🌞",herfstblad:"🍂",winterjas:"⛄",jaargetijde:"🍁",gitaarspel:"🎸",
ruimtestation:"🛰️",boekje:"📖",muziek:"🎶",onderzoeksverslag:"🧪📋",grondrecht:"⚖️",
bol:"🥯",toast:"🍞",korst:"🍞",kruimel:"🍞",karnemelk:"🥛",room:"🥛",boter:"🧈",vla:"🍮",
brie:"🧀",plak:"🧀",friet:"🍟",puree:"🥔",schil:"🥔",knol:"🥔",prei:"🥬",kool:"🥬",boon:"🫘",erwtje:"🫛",
koek:"🍪",snoep:"🍬",wafel:"🧇",gebakje:"🧁",slagroom:"🍰",
stam:"🌳",tak:"🌿",wortel:"🌱",stengel:"🌱",knop:"🌷",boeket:"💐",druppel:"💧",plas:"💦",sloot:"🏞️",beek:"🏞️",
licht:"☀️",schaduw:"◐",bui:"🌦️",vorst:"🥶",hagel:"🌨️",vlok:"❄️",aarde:"🌍",grond:"🟫",kiem:"🌱",
bus:"🚌",taxi:"🚕",rit:"🛣️",band:"🛞",step:"🛴",pedaal:"🚲",bel:"🔔",stuur:"🚲",rail:"🛤️",spoor:"🛤️",
roeispaan:"🚣",vleugel:"✈️",piloot:"🧑‍✈️",vlucht:"✈️",pad:"🥾",bocht:"↪️",streep:"",file:"🚗🚗",route:"🗺️",
kast:"🗄️",nachtkast:"🗄️",slaapkamer:"🛏️",slaapkamerraam:"🪟",flat:"🏢",hut:"🛖",dak:"🏠",muur:"🧱",deken:"🛏️",kussen:"🛏️",kruk:"🪑",slot:"🔒",sleutel:"🔑",
pan:"🍳",pot:"🍲",kom:"🥣",lepel:"🥄",bad:"🛁",douche:"🚿",wc:"🚽",kraan:"🚰",zeep:"🧼",
stof:"🫧",vuil:"🗑️",bezem:"🧹",dweil:"🧽",spons:"🧽",
duim:"👍",vinger:"☝️",nagel:"💅",pols:"⌚",teen:"🦶",hiel:"🦶",enkel:"🦵",knie:"🦵",
wang:"🙂",kin:"🙂",nek:"🧣",keel:"🗣️",lip:"👄",kies:"🦷",tong:"👅",speeksel:"💧",gebit:"😁",
trui:"👕",broek:"👖",rok:"👗",long:"🫁",bloed:"🩸",bot:"🦴",spier:"💪",skelet:"🦴",
bewegingsapparaat:"🦴",bewegingsrichting:"🧭",lichaamsbeweging:"🤸",bewegingsreeks:"🔄",bewegingsoefening:"🤸",bewegingspatroon:"💃",gaan:"🚶",stap:"👣",staan:"🧍",zitten:"🪑",racen:"🏁",sprinten:"🏃",haasten:"⏩",vluchten:"🏃",
hupsen:"🐇",bukken:"🙇",rollen:"🤸",draaien:"🔄",blad:"📄",zin:"📝",tekst:"📄",schrift:"📓",
praten:"🗣️",zeggen:"💬",vragen:"❓",roepen:"📣",fluisteren:"🤫",delen:"🤲",geven:"🎁",halen:"📥",brengen:"📤",
leuk:"😀",fijn:"😊",glimlach:"🙂",sip:"😔",verdriet:"😢",pijn:"🤕",kwaad:"😠",ruzie:"💢",zorgen:"😟",
rust:"🧘",kalm:"🙂",stil:"🤫",zacht:"🪶",ademen:"🌬️",dapper:"🦸",durven:"💪",winnen:"🏆",raar:"🤔",wauw:"🤩",
gezin:"👨‍👩‍👧‍👦",ouders:"👪",mama:"👩",tante:"👩",dochter:"👧",papa:"👨",oom:"👨",zoon:"👦",
jongen:"👦",meisje:"👧",kleuter:"🧒",puber:"🧑",maat:"🧑‍🤝‍🧑",buur:"🏘️",gast:"🧑",juf:"👩‍🏫",meester:"👨‍🏫",
leraar:"🧑‍🏫",mentor:"🧑‍🏫",coach:"🧑‍🏫",team:"👥",klas:"🏫",paar:"👫",kring:"⭕",
kaart:"🃏",doel:"🥅",trap:"⚽",veld:"🏟️",basket:"🏀",korf:"🧺",worp:"🤾",score:"💯",punt:"🔵",stuk:"🧩",rand:"🧩",
plaat:"🖼️",beer:"🧸",blok:"🧱",pion:"♟️",plons:"💦",slag:"🏊",prijs:"🏆",beker:"🏆",medaille:"🏅",
straat:"🛣️",landingsbaan:"🛬",plattegrond:"🗺️",landbouwer:"🧑‍🌾",plein:"🏘️",les:"📚",gym:"🤸",kassa:"🧾",kar:"🛒",mand:"🧺",klant:"🧑",arts:"🧑‍⚕️",zaal:"🚪",zorg:"❤️",
boer:"🧑‍🌾",stal:"🏚️",schuur:"🏚️",land:"🌾",erf:"🏡",balie:"🛎️",lezer:"📖",buurt:"🏘️",vijver:"🏞️",
dag:"☀️",uur:"🕐",tijd:"⏰",middag:"🌤️",vroeg:"🌅",wakker:"⏰",ontbijt:"🥣",avond:"🌇",laat:"🌙",
donker:"🌑",week:"🗓️",maand:"📆",jaar:"🎆",datum:"📅",agenda:"📒",feest:"🥳",kaars:"🕯️",ballon:"🎈",
lente:"🌼",zomer:"🌞",herfst:"🍂",winter:"⛄",seconde:"⏱️",kwart:"¼",half:"½",duur:"⏳",
kleur:"🌈",verf:"🎨",kwast:"🖌️",doek:"🖼️",palet:"🎨",tekening:"🖍️",lijn:"",vorm:"🔷",vak:"◻️",
toon:"🎵",lied:"🎶",stem:"🗣️",klank:"🔊",trom:"🥁",fluit:"🪈",snaar:"🎸",toets:"🎹",
dans:"💃",sprong:"🤸",draai:"🔄",toneel:"🎭",rol:"🎭",pak:"👔",podium:"🎭",foto:"📷",film:"🎬",lens:"📷",
zoeken:"🔎",vraag:"❓",vondst:"💡",proef:"🧪",test:"✅",buis:"🧪",mengen:"🥣",
magneet:"🧲",ijzer:"🔩",pool:"🧲",kracht:"💪",wiel:"🛞",as:"⚙️",tand:"🦷",veer:"🪶",hefboom:"⚖️",
stroom:"⚡",draad:"🧵",stekker:"🔌",digitaal:"💻",internet:"🌐",programmeren:"🧑‍💻",computer:"💻",muis:"🖱️",scherm:"🖥️",bestand:"📄",ruimte:"🌌",
planeet:"🪐",raket:"🚀",satelliet:"🛰️",heelal:"🌌",astronaut:"🧑‍🚀",
letter:"🔤",woord:"🔠",naam:"🏷️",teken:"✍️",komma:",",titel:"🏷️",prent:"🖼️",taal:"🗣️",
spraak:"💬",verhaal:"📖",nul:"0⃣",een:"1⃣",twee:"2⃣",drie:"3⃣",getal:"🔢",cijfer:"🔣",reeks:"🔢",
som:"",plus:"",min:"",keer:"✖️",hoek:"📐",vlak:"◻️",lengte:"📏",
samen:"🤝",hulp:"🆘",beurt:"🔄",afspraak:"🤝",regel:"📋",wijk:"🏘️",wereld:"🌍",grens:"🚧",
noord:"⬆️",zuid:"⬇️",oost:"➡️",west:"⬅️",plek:"📍",richting:"🧭",nieuws:"📰",krant:"🗞️",bericht:"💬",
radio:"📻",media:"📺",afval:"🗑️",bak:"🗑️",glas:"♻️",papier:"📄",recht:"⚖️",plicht:"📋",keus:"🔀",
wet:"⚖️",eerlijk:"⚖️",mening:"💭",besluit:"✅"
});
/* Betekenisdragende delen voor samenstellingen. Het familiebeeld blijft
voorop staan; maximaal één tweede symbool verduidelijkt het onderscheid. */
const LANGUAGE_PICTURE_HINTS=Object.freeze([
["bescherming","🛡️"],["veiligheid","🛡️"],["verzorging","🧼"],["vaardigheid","🎯"],["onderzoek","🔬"],
["bereiding","🍳"],["productie","🏭"],["verwerking","🏭"],["organisatie","📋"],["ontwikkeling","📈"],
["geschiedenis","📜"],["industrie","🏭"],["maatschappij","🌍"],["informatie",""],["technologie","💻"],
["wedstrijd","🏆"],["training","🏋️"],["strategie","♟️"],["berekening","🧮"],["verdeling","📊"],
["winkel","🏪"],["fabriek","🏭"],["bedrijf","🏭"],["centrum","🏙️"],["gebouw","🏢"],["kamer","🚪"],
["boek","📕"],["schrift","📓"],["kaart","🗺️"],["verslag","📋"],["bericht","💬"],["programma","💻"],
["verlichting","💡"],["licht","💡"],["water","💧"],["lucht","🌬️"],["grond","🟫"],["bloem","🌷"],
["boom","🌳"],["tuin","🌱"],["weide","🌿"],["storm","🌪️"],["regen","🌧️"],["sneeuw","❄️"],
["bak","📦"],["voer","🍽️"],["hok","🏠"],["stal","🏚️"],["riem","🦮"],["kooi","🪹"],
["boot","⛵"],["reis","🧳"],["straat","🏘️"],["station","🚉"],["haven","⚓"],
["deur","🚪"],["raam","🪟"],["kast","🗄️"],["tafel","🪑"],["stoel","🪑"],["bed","🛏️"],
["zorg","❤️"],["hulp","🤝"],["school","🏫"],["leer","📚"],["taal","🔤"],["reken","🧮"],
["spel","🎲"],["sport","🏅"],["feest","🥳"],["dag","📅"],["tijd","⏰"],["jaar","🗓️"],
["kracht","💪"],["energie","⚡"],["stroom","⚡"],["machine","⚙️"],["instrument","🎵"],["muziek","🎶"],
["groep","👥"],["team","👥"],["familie","👨‍👩‍👧"],["ouder","👪"],["kind","🧒"],["mens","🧑"],
["wereld","🌍"],["milieu","♻️"],["afval","🗑️"],["bestuur","🏛️"]
]);
function languageProgressionPicture(word,fallback){
if(LANGUAGE_PICTURE_EXACT[word])return LANGUAGE_PICTURE_EXACT[word];
const match=LANGUAGE_PICTURE_HINTS.filter(([part])=>word.includes(part)).sort((a,b)=>b[0].length-a[0].length)
.find(([,emoji])=>emoji!==fallback);
return match?fallback+match[1]:fallback
}
/* Ruim duizend aanvullende Nederlandse woorden, geordend in woordfamilies.
Iedere regel loopt bewust van een kort, concreet basiswoord naar langere
afleidingen, samenstellingen en schooltaal. De positie op de regel bepaalt
@ -444,7 +523,7 @@ const LANGUAGE_NL_PROGRESSION_WORDS=(()=>{
const word=raw.trim();if(!word||seen.has(word))return;seen.add(word);
const slug=word.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/[^a-z]/g,"");
const yearGroup=LANGUAGE_PROGRESSION_YEARS[Math.min(familyPosition++,LANGUAGE_PROGRESSION_YEARS.length-1)];
out.push({id:"nl-"+theme+"-"+slug,theme,nl:word,pic,yearGroup});
out.push({id:"nl-"+theme+"-"+slug,theme,nl:word,pic:languageProgressionPicture(word,pic),yearGroup,pictureChecked:true});
});
}));
return out
@ -474,7 +553,8 @@ function languageThemeList(lang=LANG){
})).filter(theme=>theme.words.length);
}
function languageEmojiDataUrl(emoji,colors=["#f4f8ff","#dce9fa"]){
const svg=`<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320"><defs><linearGradient id="g" x2="1" y2="1"><stop stop-color="${colors[0]}"/><stop offset="1" stop-color="${colors[1]}"/></linearGradient></defs><rect width="320" height="320" rx="48" fill="url(#g)"/><circle cx="160" cy="160" r="112" fill="#fff" opacity=".92"/><text x="160" y="177" text-anchor="middle" dominant-baseline="middle" font-family="Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif" font-size="148">${emoji}</text></svg>`;
const fontSize=emoji.length>4?104:emoji.length>2?122:148;
const svg=`<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320"><defs><linearGradient id="g" x2="1" y2="1"><stop stop-color="${colors[0]}"/><stop offset="1" stop-color="${colors[1]}"/></linearGradient></defs><rect width="320" height="320" rx="48" fill="url(#g)"/><circle cx="160" cy="160" r="112" fill="#fff" opacity=".92"/><text x="160" y="177" text-anchor="middle" dominant-baseline="middle" font-family="Apple Color Emoji,Segoe UI Emoji,Noto Color Emoji,sans-serif" font-size="${fontSize}">${emoji}</text></svg>`;
return "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(svg);
}
function languagePictureCatalog(lang=LANG){

View file

@ -48,6 +48,29 @@ test('taalwidgets delen ruim duizend extra Nederlandse woorden over leerjaar en
assert.equal(data.themesEn.reduce((sum,theme)=>sum+theme.words.length,0),data.concepts.length);
});
test('alle aanvullende woorden hebben een gecontroleerd en correct gekoppeld woordplaatje',async()=>{
const data=await languageData();
const builtinByName=new Map(data.catalogNl.images.filter(image=>image.builtin).map(image=>[image.name,image]));
assert.ok(new Set(data.progression.map(word=>word.pic)).size>=400,'verwacht brede beeldvariatie');
for(const word of data.progression){
assert.equal(word.pictureChecked,true,word.nl);
const image=builtinByName.get(word.nl);
assert.ok(image,word.nl);
assert.equal(image.value,word.pic,word.nl);
assert.ok(image.src.startsWith('data:image/svg+xml;charset=utf-8,'),word.nl);
}
const expected={
kattenbak:'🐈📦',kattenvoer:'🐈🍽️',groentekraam:'🥕🏪',landingsbaan:'🛬',
kast:'🗄️',slaapkamerraam:'🪟',bewegingsapparaat:'🦴',bewegingsrichting:'🧭',
digitaal:'💻',kaart:'🃏',landbouwer:'🧑‍🌾',straat:'🛣️',plattegrond:'🗺️',
veehouderij:'🐄',wastafel:'🚰',tekstproductie:'✍️📄',verdrietverwerking:'😢🧠',
bezorgd:'😟',schoolslag:'🏊',toneelspeler:'🎭',spellingspatroon:'🔤',
meetinstrument:'📐',ruimtestation:'🛰️',grondrecht:'⚖️'
};
for(const [word,picture] of Object.entries(expected))
assert.equal(data.progression.find(item=>item.nl===word)?.pic,picture,word);
});
test('alle oude startwoorden blijven staan en krijgen ook een bibliotheekplaatje',async()=>{
const data=await languageData();
const oldNl=['aap','vis','zon','kat','bal','ei','boom','huis','maan','boot','kip','muis','boek','ster','roos','vuur','fiets','taart','jas','bril','klok','peer','kaas','eend','hand','voet','neus','oog','oor','mond'];