All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 34s
- Teken alle 28 scènes vanaf een leeg canvas opnieuw met samenhangende silhouetten en detailniveaus per leerjaar. - Geef dieren soortspecifieke anatomie, natuurlijke verhoudingen en vloeiende realistische contouren. - Borg gesloten vulvakken, correcte laagvolgorde en blijvend zichtbare contour- en binnenlijnen. - Verwijder de oude reparatielaag en voeg anatomische en functionele regressietests toe.
250 lines
20 KiB
JavaScript
250 lines
20 KiB
JavaScript
/* teach - thematische kleurplaten met kwast en vlakvulling */
|
|
"use strict";
|
|
|
|
const COLORING_THEME_KEYS=Object.freeze({
|
|
all:"colorThemeAll",animals:"colorThemeAnimals",seasons:"colorThemeSeasons",
|
|
nature:"colorThemeNature",science:"colorThemeScience",culture:"colorThemeCulture"
|
|
});
|
|
const COLORING_COLORS=Object.freeze([
|
|
"#26344a","#e0554d","#f28c3c","#f7c948","#3fae6a","#2fb7a8",
|
|
"#3b7dd8","#7557c8","#d85aa5","#8b5a3c","#f2b38f","#ffffff"
|
|
]);
|
|
|
|
function coloringPart(tag,id,attrs="",level=1){
|
|
const backdrop=tag==="rect"&&attrs==='x="4" y="4" width="792" height="592" rx="28"';
|
|
return `<${tag} class="coloring-region" data-fillable="true" data-part="${id}"${backdrop?' data-role="backdrop"':""}${level>1?` data-level="${level}"`:""} ${attrs}/>`;
|
|
}
|
|
function coloringLine(tag,attrs="",level=1){
|
|
return `<${tag} class="coloring-detail"${level>1?` data-level="${level}"`:""} ${attrs}/>`;
|
|
}
|
|
function coloringScene(content){
|
|
return `<svg class="coloring-sheet" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
<g class="coloring-svg-lines" fill="#fff" fill-rule="evenodd" clip-rule="evenodd" stroke="#26344a" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="2">
|
|
${content}
|
|
</g>
|
|
</svg>`;
|
|
}
|
|
function coloringFigure(id,content,attrs=""){
|
|
return `<g class="coloring-figure" data-figure="${id}"${attrs?` ${attrs}`:""}>${content}</g>`;
|
|
}
|
|
function coloringDot(cx,cy,r=5,level=1){
|
|
return coloringLine("circle",`cx="${cx}" cy="${cy}" r="${r}" fill="#26344a" stroke="none"`,level);
|
|
}
|
|
function coloringCloud(id,x,y,scale=1,level=1){
|
|
return coloringPart("path",id,`d="M 0 38 C 2 18 20 6 39 13 C 48 -8 82 -10 94 13 C 118 4 140 18 141 40 C 139 57 125 64 105 64 H 28 C 10 64 -1 55 0 38 Z" transform="translate(${x} ${y}) scale(${scale})"`,level);
|
|
}
|
|
function coloringLeaf(id,x,y,angle=0,scale=1,level=2){
|
|
return coloringPart("path",id,`d="M -27 0 C -12 -24 16 -25 30 0 C 14 24 -12 24 -27 0 Z" transform="translate(${x} ${y}) rotate(${angle}) scale(${scale})"`,level);
|
|
}
|
|
function coloringFlower(cx,cy,prefix,scale=1,level=2){
|
|
const petals=[-90,-18,54,126,198].map((angle,index)=>{
|
|
const rad=angle*Math.PI/180,x=cx+Math.cos(rad)*25*scale,y=cy+Math.sin(rad)*25*scale;
|
|
return coloringPart("path",`${prefix}-petal-${index}`,`d="M ${cx} ${cy} C ${(cx-17*scale).toFixed(1)} ${(cy-20*scale).toFixed(1)} ${(x-15*scale).toFixed(1)} ${(y-16*scale).toFixed(1)} ${x.toFixed(1)} ${y.toFixed(1)} C ${(x+15*scale).toFixed(1)} ${(y+16*scale).toFixed(1)} ${(cx+17*scale).toFixed(1)} ${(cy+20*scale).toFixed(1)} ${cx} ${cy} Z"`,level);
|
|
}).join("");
|
|
return petals+coloringPart("circle",`${prefix}-heart`,`cx="${cx}" cy="${cy}" r="${15*scale}"`,level);
|
|
}
|
|
function coloringPetals(cx,cy,prefix,level=2){
|
|
return [0,60,120,180,240,300].map((angle,index)=>{
|
|
const rad=angle*Math.PI/180,x=(cx+Math.cos(rad)*27).toFixed(1),y=(cy+Math.sin(rad)*27).toFixed(1);
|
|
return coloringPart("circle",`${prefix}-petal-${index}`,`cx="${x}" cy="${y}" r="20"`,level);
|
|
}).join("")+coloringPart("circle",`${prefix}-heart`,`cx="${cx}" cy="${cy}" r="17"`,level);
|
|
}
|
|
function coloringStars(points,prefix,level=2){
|
|
return points.map(([x,y,r],index)=>coloringPart("path",`${prefix}-${index}`,
|
|
`d="M ${x} ${y-r} L ${x+r*.24} ${y-r*.25} L ${x+r} ${y} L ${x+r*.24} ${y+r*.25} L ${x} ${y+r} L ${x-r*.24} ${y+r*.25} L ${x-r} ${y} L ${x-r*.24} ${y-r*.25} Z"`,level)).join("");
|
|
}
|
|
|
|
function coloringRefined(id,svg){
|
|
return svg.replace('class="coloring-sheet"',`class="coloring-sheet" data-scene="${id}"`);
|
|
}
|
|
|
|
const COLORING_PAGES=Object.freeze([
|
|
{id:"butterfly",theme:"animals",years:[1,2],title:{nl:"Blije vlinder",en:"Happy butterfly"},svg:coloringRefined("butterfly",proButterfly())},
|
|
{id:"autumn",theme:"seasons",years:[1,2],title:{nl:"Herfstboom",en:"Autumn tree"},svg:coloringRefined("autumn",proAutumn())},
|
|
{id:"cake",theme:"culture",years:[1,2],title:{nl:"Feesttaart",en:"Party cake"},svg:coloringRefined("cake",proCake())},
|
|
{id:"farmyard",theme:"animals",years:[1,2],title:{nl:"Boerderijvrienden",en:"Farmyard friends"},svg:coloringRefined("farmyard",proFarmyard())},
|
|
{id:"rainbow-weather",theme:"seasons",years:[1,2],title:{nl:"Regenboogweer",en:"Rainbow weather"},svg:coloringRefined("rainbow-weather",proRainbowWeather())},
|
|
{id:"construction-site",theme:"science",years:[1,2],title:{nl:"Grote graafmachine",en:"Big excavator"},svg:coloringRefined("construction-site",proConstruction())},
|
|
{id:"forest-picnic",theme:"nature",years:[1,2],title:{nl:"Picknick in het bos",en:"Forest picnic"},svg:coloringRefined("forest-picnic",proForestPicnic())},
|
|
{id:"ocean",theme:"animals",years:[3,4],title:{nl:"Vrienden in zee",en:"Ocean friends"},svg:coloringRefined("ocean",proOcean())},
|
|
{id:"garden",theme:"nature",years:[3,4],title:{nl:"Kleine tuindieren",en:"Little garden animals"},svg:coloringRefined("garden",proGarden())},
|
|
{id:"windmill",theme:"culture",years:[3,4],title:{nl:"Molen in de polder",en:"Windmill in the polder"},svg:coloringRefined("windmill",proWindmill())},
|
|
{id:"dinosaur-valley",theme:"science",years:[3,4],title:{nl:"Dinosaurusvallei",en:"Dinosaur valley"},svg:coloringRefined("dinosaur-valley",proDinosaurValley())},
|
|
{id:"spring-nest",theme:"seasons",years:[3,4],title:{nl:"Vogelnest in de lente",en:"Spring bird nest"},svg:coloringRefined("spring-nest",proSpringNest())},
|
|
{id:"fire-station",theme:"culture",years:[3,4],title:{nl:"Brandweerkazerne",en:"Fire station"},svg:coloringRefined("fire-station",proFireStation())},
|
|
{id:"bee-meadow",theme:"nature",years:[3,4],title:{nl:"Bijen in de bloemenwei",en:"Bees in the meadow"},svg:coloringRefined("bee-meadow",proBeeMeadow())},
|
|
{id:"rocket",theme:"science",years:[5,6],title:{nl:"Raket door de ruimte",en:"Rocket through space"},svg:coloringRefined("rocket",proRocket())},
|
|
{id:"rainforest",theme:"nature",years:[5,6],title:{nl:"Regenwoud",en:"Rainforest"},svg:coloringRefined("rainforest",proRainforest())},
|
|
{id:"castle",theme:"culture",years:[5,6],title:{nl:"Kasteel en dorp",en:"Castle and village"},svg:coloringRefined("castle",proCastle())},
|
|
{id:"coral-reef",theme:"animals",years:[5,6],title:{nl:"Koraalrif",en:"Coral reef"},svg:coloringRefined("coral-reef",proCoralReef())},
|
|
{id:"mountain-camp",theme:"seasons",years:[5,6],title:{nl:"Bergkamp",en:"Mountain camp"},svg:coloringRefined("mountain-camp",proMountainCamp())},
|
|
{id:"robot-workshop",theme:"science",years:[5,6],title:{nl:"Robotwerkplaats",en:"Robot workshop"},svg:coloringRefined("robot-workshop",proRobotWorkshop())},
|
|
{id:"canal-street",theme:"culture",years:[5,6],title:{nl:"Straat langs de gracht",en:"Canal street"},svg:coloringRefined("canal-street",proCanalStreet())},
|
|
{id:"four-seasons",theme:"seasons",years:[7,8],title:{nl:"Boom van vier seizoenen",en:"Four seasons tree"},svg:coloringRefined("four-seasons",proFourSeasons())},
|
|
{id:"eco-city",theme:"science",years:[7,8],title:{nl:"Duurzame stad",en:"Sustainable city"},svg:coloringRefined("eco-city",proEcoCity())},
|
|
{id:"mandala",theme:"culture",years:[7,8],title:{nl:"Geometrische mandala",en:"Geometric mandala"},svg:coloringRefined("mandala",proMandala())},
|
|
{id:"ecosystem",theme:"nature",years:[7,8],title:{nl:"Leven rond de vijver",en:"Life around the pond"},svg:coloringRefined("ecosystem",proEcosystem())},
|
|
{id:"solar-observatory",theme:"science",years:[7,8],title:{nl:"Sterrenwacht",en:"Solar observatory"},svg:coloringRefined("solar-observatory",proSolarObservatory())},
|
|
{id:"savanna-web",theme:"animals",years:[7,8],title:{nl:"Leven op de savanne",en:"Life on the savanna"},svg:coloringRefined("savanna-web",proSavannaWeb())},
|
|
{id:"japanese-garden",theme:"culture",years:[7,8],title:{nl:"Japanse tuin",en:"Japanese garden"},svg:coloringRefined("japanese-garden",proJapaneseGarden())}
|
|
]);
|
|
|
|
function coloringPagesFor(yearGroup,theme="all"){
|
|
const year=Math.max(1,Math.min(8,Number(yearGroup)||4));
|
|
return COLORING_PAGES.filter(page=>year>=page.years[0]&&year<=page.years[1]&&(theme==="all"||page.theme===theme));
|
|
}
|
|
function coloringBrushWidth(size,pressure,pointerType){
|
|
const base=Math.max(2,Math.min(18,Number(size)||6));
|
|
return pointerType==="pen"&&pressure>0?base*(.45+pressure*1.2):base;
|
|
}
|
|
function coloringSafeColor(value,fallback=COLORING_COLORS[1]){
|
|
return /^#[0-9a-f]{6}$/i.test(String(value||""))?String(value).toLowerCase():fallback;
|
|
}
|
|
function coloringInkOverlay(svg){
|
|
const overlay=svg.cloneNode(true);overlay.setAttribute("class","coloring-ink-svg coloring-ink-overlay");
|
|
overlay.querySelectorAll(".coloring-recognition").forEach(node=>node.remove());
|
|
overlay.querySelectorAll("[data-fillable]").forEach(node=>{
|
|
node.removeAttribute("data-fillable");node.removeAttribute("data-part");node.setAttribute("fill","#fff");
|
|
});
|
|
return overlay;
|
|
}
|
|
|
|
function mountColoring(root,initState,opts={}){
|
|
const yearGroup=Math.max(1,Math.min(8,Number(initState&&initState.yearGroup)||4));
|
|
const level=Math.max(1,Math.min(3,Number(initState&&initState.level)||2));
|
|
const savedTool=initState&&initState.tool==="pencil"?"brush":initState&&initState.tool;
|
|
let tool=["brush","bucket","eraser"].includes(savedTool)?savedTool:"brush";
|
|
let color=coloringSafeColor(initState&&initState.color);
|
|
let size=[4,7,12].includes(Number(initState&&initState.size))?Number(initState.size):7;
|
|
let theme=COLORING_THEME_KEYS[initState&&initState.theme]?initState.theme:"all";
|
|
let pageId=String(initState&&initState.pageId||"");
|
|
let fills=initState&&initState.pageId===pageId&&initState.fills&&typeof initState.fills==="object"?{...initState.fills}:{};
|
|
let completed=!!(initState&&initState.completed),restoreInk=initState&&initState.ink;
|
|
|
|
root.innerHTML=`<div class="coloring-app">
|
|
<div class="coloring-choices">
|
|
<label><span class="coloring-theme-label"></span><select class="coloring-theme"></select></label>
|
|
<label><span class="coloring-page-label"></span><select class="coloring-page"></select></label>
|
|
<span class="coloring-fit"></span>
|
|
</div>
|
|
<div class="coloring-tools">
|
|
<div class="coloring-tool-buttons"></div>
|
|
<div class="coloring-palette"></div>
|
|
<label class="coloring-custom"><span></span><input type="color"></label>
|
|
<div class="coloring-sizes"></div>
|
|
<button class="coloring-reset" type="button">↺</button>
|
|
<button class="coloring-done tbtn" type="button"></button>
|
|
</div>
|
|
<div class="coloring-stage"><div class="coloring-paper" role="img"><div class="coloring-art"></div><canvas width="800" height="600"></canvas><div class="coloring-ink" aria-hidden="true"></div></div></div>
|
|
<div class="coloring-hint" aria-live="polite"></div>
|
|
</div>`;
|
|
const app=root.querySelector(".coloring-app"),themeSel=root.querySelector(".coloring-theme"),pageSel=root.querySelector(".coloring-page");
|
|
const art=root.querySelector(".coloring-art"),inkHost=root.querySelector(".coloring-ink"),paper=root.querySelector(".coloring-paper"),canvas=root.querySelector("canvas"),ctx=canvas.getContext("2d");
|
|
const toolHost=root.querySelector(".coloring-tool-buttons"),palette=root.querySelector(".coloring-palette"),sizes=root.querySelector(".coloring-sizes");
|
|
const custom=root.querySelector(".coloring-custom input"),reset=root.querySelector(".coloring-reset"),done=root.querySelector(".coloring-done"),hint=root.querySelector(".coloring-hint");
|
|
const TOOLS=[["brush","🖌️","colorToolBrush"],["bucket","🪣","colorToolBucket"],["eraser","🧽","eraser"]];
|
|
|
|
TOOLS.forEach(([id,icon])=>{const button=document.createElement("button"),symbol=document.createElement("span"),label=document.createElement("span");button.type="button";button.dataset.tool=id;symbol.className="coloring-tool-icon";symbol.textContent=icon;symbol.setAttribute("aria-hidden","true");label.className="coloring-tool-label";button.append(symbol,label);button.onclick=()=>{tool=id;refreshTools()};toolHost.appendChild(button)});
|
|
COLORING_COLORS.forEach((value,index)=>{const button=document.createElement("button");button.type="button";button.className="coloring-color";button.dataset.color=value;button.dataset.index=index+1;const dot=document.createElement("i");dot.style.background=value;button.appendChild(dot);button.onclick=()=>{color=value;custom.value=value;if(tool==="eraser")tool="brush";refreshTools()};palette.appendChild(button)});
|
|
[4,7,12].forEach(value=>{const button=document.createElement("button");button.type="button";button.className="coloring-size";button.dataset.size=value;const dot=document.createElement("i");dot.style.width=dot.style.height=(value+5)+"px";button.appendChild(dot);button.onclick=()=>{size=value;refreshTools()};sizes.appendChild(button)});
|
|
custom.value=color;custom.oninput=()=>{color=coloringSafeColor(custom.value,color);if(tool==="eraser")tool="brush";refreshTools()};
|
|
|
|
function pageTitle(page){return page.title[LANG]||page.title.nl}
|
|
function eligibleThemes(){return [...new Set(coloringPagesFor(yearGroup).map(page=>page.theme))]}
|
|
function currentPages(){return coloringPagesFor(yearGroup,theme)}
|
|
function rebuildThemeOptions(){
|
|
const previous=theme;themeSel.replaceChildren();
|
|
const all=new Option(T("colorThemeAll"),"all");themeSel.add(all);
|
|
eligibleThemes().forEach(id=>themeSel.add(new Option(T(COLORING_THEME_KEYS[id]),id)));
|
|
theme=themeSel.querySelector(`option[value="${previous}"]`)?previous:"all";themeSel.value=theme;
|
|
}
|
|
function rebuildPageOptions(prefer=pageId){
|
|
const pages=currentPages();pageSel.replaceChildren();
|
|
pages.forEach(page=>pageSel.add(new Option(pageTitle(page),page.id)));
|
|
pageId=pages.some(page=>page.id===prefer)?prefer:(pages[0]&&pages[0].id)||"";pageSel.value=pageId;
|
|
}
|
|
function clearInk(){ctx.clearRect(0,0,canvas.width,canvas.height)}
|
|
function loadInk(url){
|
|
clearInk();if(!url)return;
|
|
const image=new Image();image.onload=()=>{clearInk();ctx.drawImage(image,0,0,canvas.width,canvas.height)};image.src=url;
|
|
}
|
|
function renderPage(keepState){
|
|
const page=COLORING_PAGES.find(item=>item.id===pageId)||currentPages()[0];if(!page)return;
|
|
pageId=page.id;art.innerHTML=page.svg;
|
|
const svg=art.querySelector("svg");paper.setAttribute("aria-label",pageTitle(page));
|
|
svg.querySelectorAll("[data-level]").forEach(node=>node.toggleAttribute("hidden",Number(node.dataset.level)>level));
|
|
if(!keepState){fills={};completed=false;restoreInk=null;clearInk()}
|
|
svg.querySelectorAll("[data-fillable][data-part]").forEach(node=>{
|
|
const saved=coloringSafeColor(fills[node.dataset.part],"");if(saved)node.setAttribute("fill",saved);
|
|
});
|
|
inkHost.replaceChildren(coloringInkOverlay(svg));
|
|
if(keepState&&restoreInk){loadInk(restoreInk);restoreInk=null}
|
|
refreshTools();updateTexts();
|
|
}
|
|
function updateTexts(){
|
|
root.querySelector(".coloring-theme-label").textContent=T("colorTheme");
|
|
root.querySelector(".coloring-page-label").textContent=T("colorPage");
|
|
root.querySelector(".coloring-fit").textContent=T("colorProfile").replace("{year}",yearGroup).replace("{level}",level);
|
|
toolHost.setAttribute("role","toolbar");toolHost.setAttribute("aria-label",T("penExtras"));palette.setAttribute("role","group");palette.setAttribute("aria-label",T("paletteColors"));sizes.setAttribute("role","group");sizes.setAttribute("aria-label",T("penSize"));
|
|
TOOLS.forEach(([id,,key])=>{const button=toolHost.querySelector(`[data-tool="${id}"]`),text=T(key);button.title=text;button.setAttribute("aria-label",text);button.querySelector(".coloring-tool-label").textContent=text});
|
|
palette.querySelectorAll("button").forEach(button=>button.setAttribute("aria-label",`${T("penColor")} ${button.dataset.index}`));
|
|
custom.parentElement.querySelector("span").textContent=T("customColor");
|
|
reset.title=T("colorReset");reset.setAttribute("aria-label",T("colorReset"));
|
|
done.textContent=completed?"✓ "+T("colorFinished"):T("colorDone");
|
|
hint.textContent=T(tool==="bucket"?"colorBucketHint":tool==="eraser"?"colorEraserHint":"colorBrushHint");
|
|
const selected=pageId;rebuildPageOptions(selected);
|
|
}
|
|
function refreshTools(){
|
|
toolHost.querySelectorAll("button").forEach(button=>{const on=button.dataset.tool===tool;button.classList.toggle("on",on);button.setAttribute("aria-pressed",String(on))});
|
|
palette.querySelectorAll("button").forEach(button=>button.classList.toggle("on",button.dataset.color===color&&tool!=="eraser"));
|
|
sizes.querySelectorAll("button").forEach(button=>{button.classList.toggle("on",Number(button.dataset.size)===size);button.title=T("penSize")});
|
|
app.dataset.tool=tool;canvas.style.pointerEvents=tool==="bucket"?"none":"auto";art.style.pointerEvents=tool==="bucket"?"auto":"none";
|
|
hint.textContent=T(tool==="bucket"?"colorBucketHint":tool==="eraser"?"colorEraserHint":"colorBrushHint");
|
|
}
|
|
themeSel.onchange=()=>{theme=themeSel.value;rebuildPageOptions("");renderPage(false)};
|
|
pageSel.onchange=()=>{pageId=pageSel.value;renderPage(false)};
|
|
art.addEventListener("click",event=>{
|
|
if(tool!=="bucket")return;const part=event.target.closest("[data-fillable][data-part]");if(!part||part.hasAttribute("hidden"))return;
|
|
part.setAttribute("fill",color);fills[part.dataset.part]=color;completed=false;updateTexts();
|
|
});
|
|
|
|
function partBelow(event){
|
|
const canvasEvents=canvas.style.pointerEvents,artEvents=art.style.pointerEvents;
|
|
let part=null;
|
|
try{
|
|
canvas.style.pointerEvents="none";art.style.pointerEvents="auto";
|
|
const target=document.elementFromPoint(event.clientX,event.clientY);
|
|
part=target&&typeof target.closest==="function"?target.closest("[data-fillable][data-part]"):null;
|
|
}finally{
|
|
canvas.style.pointerEvents=canvasEvents;art.style.pointerEvents=artEvents;
|
|
}
|
|
return part&&art.contains(part)&&!part.hasAttribute("hidden")?part:null;
|
|
}
|
|
function eraseFillAt(event){
|
|
if(tool!=="eraser")return false;
|
|
const part=partBelow(event);if(!part||!Object.prototype.hasOwnProperty.call(fills,part.dataset.part))return false;
|
|
delete fills[part.dataset.part];part.setAttribute("fill","#fff");completed=false;return true;
|
|
}
|
|
|
|
const point=event=>{const rect=canvas.getBoundingClientRect();return{x:(event.clientX-rect.left)*canvas.width/rect.width,y:(event.clientY-rect.top)*canvas.height/rect.height}};
|
|
let drawing=false,last=null,mid=null;
|
|
function brushSegment(next,event){
|
|
const width=coloringBrushWidth(size,event.pressure,event.pointerType),erase=tool==="eraser";
|
|
if(erase)eraseFillAt(event);
|
|
ctx.save();ctx.globalCompositeOperation=erase?"destination-out":"source-over";ctx.strokeStyle=color;ctx.globalAlpha=erase?1:.72;ctx.lineCap="round";ctx.lineJoin="round";ctx.lineWidth=erase?width*3.5:width;
|
|
const nextMid={x:(last.x+next.x)/2,y:(last.y+next.y)/2};ctx.beginPath();ctx.moveTo(mid?mid.x:last.x,mid?mid.y:last.y);ctx.quadraticCurveTo(last.x,last.y,nextMid.x,nextMid.y);ctx.stroke();
|
|
ctx.restore();last=next;mid=nextMid;
|
|
}
|
|
canvas.addEventListener("pointerdown",event=>{if(tool==="bucket"||(event.pointerType==="mouse"&&event.button!==0))return;event.preventDefault();canvas.setPointerCapture(event.pointerId);drawing=true;last=point(event);mid=null;brushSegment({x:last.x+.01,y:last.y+.01},event);completed=false;updateTexts()});
|
|
canvas.addEventListener("pointermove",event=>{if(!drawing)return;(event.getCoalescedEvents?event.getCoalescedEvents():[event]).forEach(sample=>brushSegment(point(sample),sample))});
|
|
const stop=()=>{drawing=false;last=mid=null};canvas.addEventListener("pointerup",stop);canvas.addEventListener("pointercancel",stop);
|
|
reset.onclick=()=>{fills={};completed=false;clearInk();renderPage(false)};
|
|
done.onclick=()=>{completed=true;updateTexts();if(typeof opts.onProgress==="function")opts.onProgress({attempts:1,correct:1,stars:1,detail:{question:pageTitle(COLORING_PAGES.find(page=>page.id===pageId)),answer:T("colorFinished"),correct:true}})};
|
|
document.addEventListener("langchange",()=>{rebuildThemeOptions();updateTexts();renderPage(true)});
|
|
|
|
rebuildThemeOptions();rebuildPageOptions(pageId);renderPage(true);
|
|
return{getState:()=>{
|
|
let ink=null;try{ink=canvas.toDataURL("image/png")}catch(_){/* een leeg canvas blijft gewoon herstelbaar */}
|
|
return{theme,pageId,fills:{...fills},ink,tool,color,size,completed,yearGroup,level,managedLearningLevel:!!(initState&&initState.managedLearningLevel)};
|
|
}};
|
|
}
|