feat: verfijn grafische kwaliteit kleurplaten (v0.4.68-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 34s

Gewijzigde functionaliteiten:

- Brengt een rustige lijnhiërarchie aan met subtiele buitenkaders, stevige kleurvlakken en lichtere binnenlijnen.
- Voegt herkenbare textuurdetails toe aan onder meer vlinder, herfstboom, taart, zeedieren, molen, raket en kasteel.
- Verbetert het fietsframe in de duurzame-stadplaat en markeert kleurvlakken bij aanwijzen met de verfemmer.
- Borgt het consistente SVG-profiel voor alle 28 kleurplaten met aanvullende tests.

Tests: 201 geslaagd (npm test); 5 gerichte kleurplaattests geslaagd.
This commit is contained in:
Ramon 2026-07-29 08:37:32 +02:00
parent 3aafd50d62
commit 1e0f0c6d03
5 changed files with 34 additions and 18 deletions

View file

@ -1 +1 @@
0.4.67-beta
0.4.68-beta

View file

@ -23,14 +23,17 @@
.coloring-art svg{width:100%;height:100%;display:block;background:#fff}
.coloring-art [hidden]{display:none}
.coloring-svg-lines{shape-rendering:geometricPrecision}
.coloring-svg-lines .coloring-region[data-level="2"]{stroke-width:6}
.coloring-svg-lines .coloring-region[data-level="3"]{stroke-width:5.5}
.coloring-svg-lines .coloring-detail{stroke-width:3.75;pointer-events:none}
.coloring-svg-lines .coloring-detail[data-level="3"]{stroke-width:3.25}
.coloring-svg-lines .coloring-region{paint-order:fill stroke;stroke-opacity:.98}
.coloring-svg-lines .coloring-region[data-role="backdrop"]{stroke-width:4.25}
.coloring-svg-lines .coloring-region[data-level="2"]{stroke-width:5.9}
.coloring-svg-lines .coloring-region[data-level="3"]{stroke-width:5.15}
.coloring-svg-lines .coloring-detail{stroke:#41516a;stroke-width:3.6;stroke-opacity:.9;pointer-events:none}
.coloring-svg-lines .coloring-detail[data-level="3"]{stroke:#526079;stroke-width:3;stroke-opacity:.82}
.coloring-svg-lines .coloring-recognition{pointer-events:none}
.coloring-paper canvas{z-index:2;touch-action:none;cursor:crosshair;mix-blend-mode:multiply}
.coloring-app[data-tool="bucket"] .coloring-art{cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Ctext y='24' font-size='23'%3E%F0%9F%AA%A3%3C/text%3E%3C/svg%3E") 6 24,pointer}
.coloring-app[data-tool="bucket"] .coloring-art [data-fillable]{pointer-events:all}
.coloring-app[data-tool="bucket"] .coloring-art [data-fillable]:hover{stroke:#d56a1f;stroke-opacity:1}
.coloring-hint{min-height:30px;padding:7px 11px;color:var(--muted);font-size:11px;font-weight:700;text-align:center;background:var(--surface)}
.widget[data-widget="coloring"]{--widget-accent:var(--orange)}

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.67-beta";
const VERSION = "0.4.68-beta";
/* Safari en oudere mobiele browsers behandelen 100vh/100% soms als de hoogte
achter hun adres- en navigatiebalk. visualViewport geeft de werkelijk
zichtbare hoogte; de CSS gebruikt deze pixelwaarde als betrouwbare bron en

View file

@ -11,14 +11,15 @@ const COLORING_COLORS=Object.freeze([
]);
function coloringPart(tag,id,attrs="",level=1){
return `<${tag} class="coloring-region" data-fillable="true" data-part="${id}"${level>1?` data-level="${level}"`:""} ${attrs}/>`;
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 viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<g class="coloring-svg-lines" fill="#fff" stroke="#26344a" stroke-width="6.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="2">
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="6.5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="2">
${content}
</g>
</svg>`;
@ -190,7 +191,7 @@ function sceneEcoCity(){return coloringScene(
[[135,235],[205,235],[135,305],[205,305],[135,375],[205,375],[600,185],[670,185],[600,255],[670,255],[600,325],[670,325]].map(([x,y],i)=>coloringPart("rect",`city-window-${i}`,`x="${x-22}" y="${y-25}" width="44" height="50"`,i>5?2:1)).join("")+
coloringPart("rect","school-door",'x="375" y="365" width="55" height="90" rx="12"')+
coloringPart("circle","tree-crown",'cx="60" cy="410" r="55"',2)+coloringPart("rect","tree-trunk",'x="48" y="430" width="24" height="75"',2)+
coloringLine("path",'d="M 250 515 L 315 515 L 285 470 L 250 515 L 330 465" fill="none"',3)+
coloringLine("path",'d="M 240 520 L 280 470 L 330 520 H 270 L 300 480 M 275 462 h 26 M 300 480 l 28 -25 h 18" fill="none"',3)+
coloringLine("circle",'cx="240" cy="520" r="34" fill="none"',3)+coloringLine("circle",'cx="330" cy="520" r="34" fill="none"',3)+
coloringPart("path","solar-a",'d="M 100 190 L 125 135 L 245 135 L 225 190 Z"',2)+coloringPart("path","solar-b",'d="M 580 135 L 600 80 L 705 80 L 690 135 Z"',2)+
coloringLine("path",'d="M 125 160 L 235 160 M 165 135 L 155 190 M 205 135 L 195 190 M 600 105 L 700 105 M 635 80 L 625 135 M 670 80 L 660 135" fill="none"',3)
@ -232,51 +233,58 @@ const COLORING_REFINEMENTS=Object.freeze({
coloringLine("circle",'cx="415" cy="150" r="5" fill="#26344a" stroke="none"')+
coloringLine("path",'d="M 375 250 Q 315 275 270 330 M 425 250 Q 485 275 530 330" fill="none"',2)+
coloringLine("path",'d="M 372 300 L 428 300 M 368 345 L 432 345 M 375 390 L 425 390" fill="none"',2)+
coloringLine("path",'d="M 255 190 Q 300 245 330 315 M 545 190 Q 500 245 470 315 M 285 355 Q 325 390 355 420 M 515 355 Q 475 390 445 420" fill="none"',2)+
coloringLine("path",'d="M 95 430 l -18 -28 M 145 425 l 18 -30 M 645 435 l -15 -30 M 700 430 l 20 -28" fill="none"',3),
autumn:
coloringLine("path",'d="M 370 470 Q 385 390 382 305 M 425 470 Q 405 390 410 305" fill="none"')+
coloringLine("path",'d="M 385 320 Q 320 270 275 235 M 410 300 Q 475 255 535 235" fill="none"',2)+
coloringLine("path",'d="M 90 175 L 140 175 M 635 165 L 685 165 M 150 355 L 200 355 M 585 390 L 635 390" fill="none"',2)+
coloringLine("path",'d="M 350 505 q 18 -20 35 0 M 410 510 q 18 -22 38 0" fill="none"',3),
coloringLine("ellipse",'cx="397" cy="402" rx="17" ry="25" fill="none"',2)+
coloringLine("path",'d="M 350 505 q 18 -20 35 0 M 410 510 q 18 -22 38 0 M 365 350 q 20 -15 38 2" fill="none"',3),
cake:
coloringLine("ellipse",'cx="400" cy="492" rx="255" ry="24" fill="none"')+
coloringLine("path",'d="M 235 445 Q 275 410 315 445 Q 355 410 395 445 Q 440 410 485 445 Q 525 410 565 445" fill="none"',2)+
coloringLine("path",'d="M 325 245 H 475 M 270 340 H 530" fill="none"',2)+
coloringLine("path",'d="M 382 115 L 418 135 M 382 145 L 418 165" fill="none"',3)+
coloringLine("circle",'cx="340" cy="320" r="9" fill="none"',3)+coloringLine("circle",'cx="460" cy="320" r="9" fill="none"',3),
coloringLine("circle",'cx="340" cy="320" r="9" fill="none"',3)+coloringLine("circle",'cx="460" cy="320" r="9" fill="none"',3)+
coloringLine("path",'d="M 285 420 l 12 9 M 350 432 l 12 -10 M 420 418 l 13 10 M 490 432 l 12 -10 M 535 410 l 12 9" fill="none"',3),
ocean:
coloringLine("path",'d="M 430 270 Q 455 285 475 270 M 375 200 Q 360 260 375 315" fill="none"')+
coloringLine("path",'d="M 250 250 Q 290 215 330 250 Q 290 285 250 250 M 300 305 Q 335 275 365 305" fill="none"',2)+
coloringLine("circle",'cx="732" cy="372" r="5" fill="#26344a" stroke="none"')+
coloringLine("path",'d="M 728 398 Q 744 408 758 395" fill="none"',2)+
coloringLine("path",'d="M 275 240 q 18 -18 36 0 M 305 280 q 18 -18 36 0 M 345 245 q 18 -18 36 0" fill="none"',3)+
coloringLine("path",'d="M 60 545 Q 115 520 170 545 M 625 545 Q 680 520 740 545" fill="none"',3),
garden:
coloringLine("path",'d="M 175 405 C 175 340 265 340 265 405 C 265 455 205 455 205 410 C 205 382 240 382 240 407" fill="none"')+
coloringLine("circle",'cx="394" cy="402" r="5" fill="#26344a" stroke="none"')+coloringLine("circle",'cx="425" cy="402" r="5" fill="#26344a" stroke="none"')+
coloringLine("path",'d="M 393 427 Q 410 440 428 426 M 565 292 Q 545 265 530 260 M 635 292 Q 655 265 670 260" fill="none"',2)+
coloringLine("path",'d="M 525 365 L 485 345 M 525 410 L 480 420 M 675 365 L 715 345 M 675 410 L 720 420" fill="none"',3),
coloringLine("path",'d="M 525 365 L 485 345 M 525 410 L 480 420 M 675 365 L 715 345 M 675 410 L 720 420 M 552 333 q 48 30 96 0 M 550 438 q 50 -28 100 0" fill="none"',3),
windmill:
coloringLine("path",'d="M 380 250 H 455 V 320 H 380 Z M 417 250 V 320 M 380 285 H 455" fill="none"')+
coloringLine("path",'d="M 328 65 L 414 210 M 625 135 L 445 220 M 500 410 L 422 245 M 200 310 L 392 220" fill="none"',2)+
coloringLine("path",'d="M 325 82 L 345 68 M 605 135 L 615 160 M 500 388 L 475 402 M 218 304 L 230 330" fill="none"',3)+
coloringLine("path",'d="M 337 345 H 505 M 330 390 H 515 M 365 300 v 45 M 455 345 v 45" fill="none"',3)+
coloringLine("path",'d="M 65 520 Q 130 500 195 520 M 545 520 Q 620 495 700 520" fill="none"',2),
rocket:
coloringLine("path",'d="M 335 330 H 465 M 352 385 H 448" fill="none"')+
coloringLine("circle",'cx="400" cy="225" r="32" fill="none"',2)+
coloringLine("path",'d="M 383 225 Q 400 240 417 225" fill="none"',3)+
coloringLine("circle",'cx="382" cy="210" r="4" fill="#26344a" stroke="none"',3)+coloringLine("circle",'cx="418" cy="210" r="4" fill="#26344a" stroke="none"',3)+
coloringLine("path",'d="M 350 170 L 370 182 M 450 170 L 430 182 M 375 455 L 400 510 L 425 455" fill="none"',2),
coloringLine("path",'d="M 350 170 L 370 182 M 450 170 L 430 182 M 375 455 L 400 510 L 425 455" fill="none"',2)+
coloringLine("circle",'cx="350" cy="365" r="7" fill="none"',3)+coloringLine("circle",'cx="450" cy="365" r="7" fill="none"',3),
rainforest:
coloringLine("path",'d="M 385 285 Q 415 315 445 345 M 375 315 Q 410 345 440 370" fill="none"',2)+
coloringLine("path",'d="M 510 225 L 545 238 M 400 395 L 392 430 M 445 395 L 448 430" fill="none"')+
coloringLine("path",'d="M 392 430 l -20 10 M 392 430 l 18 10 M 448 430 l -18 10 M 448 430 l 20 10" fill="none"',2)+
coloringLine("path",'d="M 115 80 Q 135 165 110 250 M 680 65 Q 700 165 675 260" fill="none"',3)+
coloringLine("path",'d="M 465 207 Q 478 215 488 204 M 520 228 L 548 238" fill="none"',3),
coloringLine("path",'d="M 465 207 Q 478 215 488 204 M 520 228 L 548 238 M 105 150 q 35 20 68 0 M 642 145 q 38 22 75 0" fill="none"',3),
castle:
coloringLine("path",'d="M 365 400 V 490 M 400 365 V 490 M 435 400 V 490" fill="none"')+
coloringLine("path",'d="M 240 280 H 330 M 470 280 H 560 M 280 370 H 350 M 450 370 H 525 M 140 330 H 245 M 555 330 H 665" fill="none"',2)+
coloringLine("path",'d="M 160 255 V 290 M 225 255 V 290 M 575 255 V 290 M 640 255 V 290" fill="none"',2)+
coloringLine("path",'d="M 193 55 V 25 M 608 55 V 25 M 193 80 H 285 M 608 80 H 700" fill="none"',3)+
coloringLine("path",'d="M 250 410 h 55 M 495 410 h 55 M 135 445 h 55 M 610 445 h 55" fill="none"',3)+
coloringLine("path",'d="M 365 535 Q 400 515 435 535 M 350 570 Q 400 545 450 570" fill="none"',3),
"four-seasons":
coloringLine("path",'d="M 395 230 Q 325 180 265 135 M 400 230 Q 485 175 565 135 M 405 250 Q 505 300 565 355 M 385 250 Q 300 315 225 355" fill="none"')+

View file

@ -68,6 +68,8 @@ test('elk leerjaar heeft zeven herkenbare kleurplaten met diverse themas en d
assert.equal(new Set(parts).size,parts.length,page.id+' heeft unieke kleurvakken');
assert.deepEqual(openFillablePaths,[],page.id+' heeft alleen gesloten aanklikbare paden');
assert.ok(details>=4,page.id+' heeft voldoende herkenbaar binnenlijnwerk');
assert.match(page.svg,/class="coloring-sheet"[^>]*preserveAspectRatio="xMidYMid meet"/);
assert.match(page.svg,/data-role="backdrop"/,page.id+' heeft een subtiel buitenkader');
assert.match(page.svg,new RegExp(`data-scene="${page.id}"`));
assert.match(page.svg,/data-level="2"/);
assert.match(page.svg,/data-level="3"/);
@ -104,10 +106,13 @@ test('potlood reageert op echte pendruk en bouwt kleur transparant op',async()=>
const css=await readFile('public/css/coloring.css','utf8');
assert.match(css,/mix-blend-mode:multiply/);
assert.match(source,/class="coloring-region" data-fillable="true"/);
assert.match(source,/stroke-width="6\.5"[^>]*stroke-miterlimit="2"/);
assert.match(source,/class="coloring-sheet"[^>]*preserveAspectRatio="xMidYMid meet"/);
assert.match(source,/fill-rule="evenodd" clip-rule="evenodd"[^>]*stroke-width="6\.5"[^>]*stroke-miterlimit="2"/);
assert.match(css,/\.coloring-svg-lines\{shape-rendering:geometricPrecision\}/);
assert.match(css,/\.coloring-region\[data-level="3"\]\{stroke-width:5\.5\}/);
assert.match(css,/\.coloring-detail\{stroke-width:3\.75;pointer-events:none\}/);
assert.match(css,/\.coloring-region\[data-role="backdrop"\]\{stroke-width:4\.25\}/);
assert.match(css,/\.coloring-region\[data-level="3"\]\{stroke-width:5\.15\}/);
assert.match(css,/\.coloring-detail\{stroke:#41516a;stroke-width:3\.6;stroke-opacity:\.9;pointer-events:none\}/);
assert.match(css,/\[data-fillable\]:hover\{stroke:#d56a1f;stroke-opacity:1\}/);
});
test('verfemmer vult een SVG-vak in één keer en state bewaart potlood én vlakkleuren',async()=>{