All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 37s
- voeg acht educatieve en klaswidgets toe\n- verbeter galerij met zoeken, favorieten en lessjablonen\n- voeg dupliceren, vergrendelen en voorbeeldweergave toe\n- voeg geschiedenis en begrensde bordexport/import toe\n- verbeter responsiviteit, toegankelijkheid en vertalingen
47 lines
8.2 KiB
JavaScript
47 lines
8.2 KiB
JavaScript
/* Acht compacte educatieve en klaswidgets. Alle invoer blijft tekstContent-safe. */
|
||
function learningShell(root, settings, stage){
|
||
root.innerHTML='<div class="edu"><details class="edu-settings"><summary>⚙ <span></span></summary><div class="edu-controls"></div></details><div class="edu-stage"></div></div>';
|
||
root.querySelector('summary span').textContent=T('widgetSettings');
|
||
root.querySelector('.edu-controls').append(...settings);
|
||
root.querySelector('.edu-stage').append(stage);
|
||
}
|
||
function field(label, el){const w=document.createElement('label');const s=document.createElement('span');s.textContent=label;w.append(s,el);return w}
|
||
function btn(text,fn,kind='ghost'){const b=document.createElement('button');b.type='button';b.className='tbtn '+kind;b.textContent=text;b.onclick=fn;return b}
|
||
function num(value,min,max){const i=document.createElement('input');i.type='number';i.value=value;i.min=min;i.max=max;return i}
|
||
|
||
function mountFractions(root,init){
|
||
const S=Object.assign({den:4,show:true},init||{}), den=num(S.den,2,12), show=document.createElement('input'), stage=document.createElement('div');show.type='checkbox';show.checked=S.show;
|
||
const draw=()=>{S.den=Math.max(2,Math.min(12,+den.value||4));S.show=show.checked;stage.className='fraction-wall';stage.innerHTML='';for(let d=1;d<=S.den;d++){const row=document.createElement('div');for(let n=0;n<d;n++){const c=document.createElement('button');c.type='button';c.style.setProperty('--fraction-hue',d*31);c.textContent=S.show?`1/${d}`:'';c.className='fraction-piece';c.onclick=()=>c.classList.toggle('selected');row.append(c)}stage.append(row)}};
|
||
den.onchange=show.onchange=draw;learningShell(root,[field(T('fractionRows'),den),field(T('fractionLabels'),show)],stage);draw();return{getState:()=>({...S})};
|
||
}
|
||
function mountClock(root,init){
|
||
const S=Object.assign({h:10,m:10,task:false,targetH:3,targetM:30},init||{}), h=num(S.h,1,12),m=document.createElement('select'),stage=document.createElement('div');[0,5,10,15,20,25,30,35,40,45,50,55].forEach(v=>m.add(new Option(String(v).padStart(2,'0'),v)));m.value=S.m;
|
||
const draw=()=>{S.h=+h.value;S.m=+m.value;stage.className='clock-wrap';stage.innerHTML=`<div class="clock"><i class="clock-hour"></i><i class="clock-min"></i><b>12</b><b>3</b><b>6</b><b>9</b></div><output>${S.h}:${String(S.m).padStart(2,'0')}</output>`;stage.querySelector('.clock-hour').style.transform=`rotate(${S.h*30+S.m/2}deg)`;stage.querySelector('.clock-min').style.transform=`rotate(${S.m*6}deg)`};
|
||
const task=btn(T('clockTask'),()=>{S.targetH=1+Math.floor(Math.random()*12);S.targetM=[0,15,30,45][Math.floor(Math.random()*4)];S.task=true;alert(T('clockSet').replace('{time}',`${S.targetH}:${String(S.targetM).padStart(2,'0')}`))});
|
||
const check=btn(T('numberlineCheck'),()=>{if(!S.task)return;const ok=S.h===S.targetH&&S.m===S.targetM;ok?sndGood():sndBad();stage.querySelector('output').textContent=T(ok?'numberlineGood':'numberlineTry')},'');h.oninput=m.onchange=draw;learningShell(root,[field(T('clockHour'),h),field(T('clockMinute'),m),task,check],stage);draw();return{getState:()=>({...S})};
|
||
}
|
||
function mountPlacevalue(root,init){
|
||
const S=Object.assign({number:1342},init||{}),input=num(S.number,0,999999),stage=document.createElement('div');
|
||
const draw=()=>{S.number=Math.max(0,Math.min(999999,+input.value||0));const digits=String(S.number).padStart(6,'0').split('');stage.className='place-grid';stage.innerHTML='';['HD','TD','D','H','T','E'].forEach((x,i)=>{const d=document.createElement('div');const b=document.createElement('b');b.textContent=x;const o=document.createElement('output');o.textContent=digits[i];d.append(b,o);stage.append(d)})};input.oninput=draw;learningShell(root,[field(T('placeNumber'),input),btn('🎲',()=>{input.value=Math.floor(Math.random()*999999);draw()})],stage);draw();return{getState:()=>({...S})};
|
||
}
|
||
function mountMoney(root,init){
|
||
const S=Object.assign({target:3.75,total:0},init||{}),stage=document.createElement('div'),out=document.createElement('output');
|
||
const draw=()=>{stage.className='money';stage.innerHTML='';const task=document.createElement('h3');task.textContent=T('moneyMake').replace('{amount}',S.target.toFixed(2));stage.append(task);[2,1,.5,.2,.1,.05].forEach(v=>stage.append(btn('€ '+v.toFixed(2),()=>{S.total=Math.round((S.total+v)*100)/100;renderTotal()})));stage.append(out,btn(T('sentenceClear'),()=>{S.total=0;renderTotal()}),btn(T('numberlineCheck'),()=>{S.total===S.target?sndGood():sndBad()},''));renderTotal()};const renderTotal=()=>out.textContent='€ '+S.total.toFixed(2);learningShell(root,[btn(T('numberlineNew'),()=>{S.target=(50+Math.floor(Math.random()*950))/100;S.total=0;draw()})],stage);draw();return{getState:()=>({...S})};
|
||
}
|
||
function mountWordtypes(root,init){
|
||
const S=Object.assign({sentence:'De vrolijke hond rent snel',answers:{}},init||{}),input=document.createElement('input'),stage=document.createElement('div');input.value=S.sentence;
|
||
const types=['noun','verb','adjective','other'],draw=()=>{S.sentence=input.value.slice(0,120);stage.className='wordtypes';stage.innerHTML='';S.sentence.split(/\s+/).filter(Boolean).forEach((word,i)=>{const w=document.createElement('button');w.type='button';w.textContent=word;const type=S.answers[i]||'other';w.dataset.type=type;w.onclick=()=>{S.answers[i]=types[(types.indexOf(w.dataset.type)+1)%types.length];draw()};stage.append(w)});const legend=document.createElement('p');legend.textContent=T('wordtypeHint');stage.append(legend)};input.oninput=draw;learningShell(root,[field(T('wordtypeSentence'),input)],stage);draw();return{getState:()=>({...S})};
|
||
}
|
||
function mountSchedule(root,init){
|
||
const S=Object.assign({items:[{time:'08:30',text:'Start'},{time:'10:15',text:'Pauze'}]},init||{}),time=document.createElement('input'),text=document.createElement('input'),stage=document.createElement('div');time.type='time';time.value='09:00';text.placeholder=T('scheduleActivity');
|
||
const draw=()=>{stage.className='schedule';stage.innerHTML='';S.items.sort((a,b)=>a.time.localeCompare(b.time)).forEach((it,i)=>{const row=document.createElement('div');const t=document.createElement('time');t.textContent=it.time;const s=document.createElement('span');s.textContent=it.text;row.append(t,s,btn('×',()=>{S.items.splice(i,1);draw()}));stage.append(row)});if(!S.items.length){const e=document.createElement('p');e.className='edu-empty';e.textContent=T('scheduleEmpty');stage.append(e)}};
|
||
learningShell(root,[time,text,btn(T('addWord'),()=>{if(text.value.trim()){S.items.push({time:time.value,text:text.value.trim().slice(0,50)});text.value='';draw()}})],stage);draw();return{getState:()=>({...S,items:S.items.map(x=>({...x}))})};
|
||
}
|
||
function mountMood(root,init){
|
||
const S=Object.assign({votes:[0,0,0,0,0]},init||{}),stage=document.createElement('div'),faces=['😢','😕','😐','🙂','🤩'];
|
||
const draw=()=>{stage.className='mood';stage.innerHTML='';faces.forEach((f,i)=>{const b=btn(f,()=>{S.votes[i]++;draw()});const n=document.createElement('span');n.textContent=S.votes[i];b.append(n);stage.append(b)})};learningShell(root,[btn(T('pollReset'),()=>{S.votes=[0,0,0,0,0];draw()})],stage);draw();return{getState:()=>({...S,votes:[...S.votes]})};
|
||
}
|
||
function mountPoll(root,init){
|
||
const S=Object.assign({question:'Wat kies jij?',options:[{text:'A',votes:0},{text:'B',votes:0}]},init||{}),question=document.createElement('input'),options=document.createElement('textarea'),stage=document.createElement('div');question.value=S.question;options.value=S.options.map(x=>x.text).join('\n');
|
||
const sync=()=>{S.question=question.value.slice(0,100);const old=new Map(S.options.map(x=>[x.text,x.votes]));S.options=options.value.split('\n').map(x=>x.trim()).filter(Boolean).slice(0,6).map(text=>({text,votes:old.get(text)||0}));draw()},draw=()=>{stage.className='poll';stage.innerHTML='';const h=document.createElement('h3');h.textContent=S.question;stage.append(h);const max=Math.max(1,...S.options.map(x=>x.votes));S.options.forEach(o=>{const b=btn(o.text,()=>{o.votes++;draw()});const bar=document.createElement('i');bar.style.width=(o.votes/max*100)+'%';const n=document.createElement('span');n.textContent=o.votes;b.append(bar,n);stage.append(b)})};question.oninput=options.oninput=sync;learningShell(root,[field(T('pollQuestion'),question),field(T('pollOptions'),options),btn(T('pollReset'),()=>{S.options.forEach(x=>x.votes=0);draw()})],stage);draw();return{getState:()=>({...S,options:S.options.map(x=>({...x}))})};
|
||
}
|