diff --git a/VERSION b/VERSION
index 341341e..241f716 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.4.47-beta
+0.4.48-beta
diff --git a/public/js/avatar.js b/public/js/avatar.js
index 2e61b8c..ef0824c 100644
--- a/public/js/avatar.js
+++ b/public/js/avatar.js
@@ -147,45 +147,91 @@ const AVATAR_HAIR_MARKUP = {
none: { back: () => "", front: () => "" },
tuft: {
back: () => "",
- front: (c) => ``,
+ front: (c) => `
+
+
+ `,
},
spiky: {
back: () => "",
- front: (c) => ``,
+ front: (c) => `
+
+
+
+ `,
},
curly: {
- back: () => "",
- front: (c) => `
-
-
+ back: (c) => ``,
+ front: (c) => `
+
+
+
+
+
`,
},
wavy: {
- back: (c) => ``,
- front: (c) => ``,
+ back: (c) => ``,
+ front: (c) => `
+
+
+
+
+
+ `,
},
bob: {
- back: (c) => ``,
- front: (c) => ``,
+ back: (c) => ``,
+ front: (c) => `
+
+
+
+
+
+ `,
},
ponytail: {
- back: (c) => ``,
- front: (c) => ``,
+ back: (c) => `
+
+
+
+ `,
+ front: (c) => `
+
+
+
+ `,
},
braids: {
- back: (c) => `
-
-
-
+ back: (c) => `
+
+
+
+
+ `,
+ front: (c) => `
+
+
+
`,
- front: (c) => ``,
},
mohawk: {
back: () => "",
- front: (c) => ``,
+ front: (c) => `
+
+
+ `,
},
};
+/* Iedere kop gebruikt dezelfde haarankers, met een minimale correctie voor
+ het asymmetrische dinosaurusprofiel. De wrapper maakt de pasvorm expliciet
+ en houdt alle lokken als een laag bij elkaar. */
+function avatarHairLayer(markup, species, layer){
+ if(!markup) return "";
+ const transform = species === "dino" ? "translate(1 0)" : species === "monster" ? "translate(0 -1)" : "translate(0 0)";
+ return `${markup}`;
+}
const AVATAR_HEAD_ACC_MARKUP = {
none: () => "",
cap: () => `
@@ -328,9 +374,9 @@ function renderAvatarFace(config, size){
const bodyAccessory = AVATAR_BODY_ACC_MARKUP[c.accessoryBody];
const hair = AVATAR_HAIR_MARKUP[c.hairStyle];
let markup = bodyAccessory.back();
- markup += hair.back(c.hairColor);
+ markup += avatarHairLayer(hair.back(c.hairColor), c.species, "back");
markup += avatarBodyMarkup(c.species, c.bodyColor);
- if(!avatarHairSuppressed(c)) markup += hair.front(c.hairColor);
+ if(!avatarHairSuppressed(c)) markup += avatarHairLayer(hair.front(c.hairColor), c.species, "front");
markup += AVATAR_HEAD_ACC_MARKUP[c.accessoryHead]();
markup += bodyAccessory.front();
svg.innerHTML = markup;
diff --git a/public/js/core.js b/public/js/core.js
index 0ded29b..b7b7ca9 100644
--- a/public/js/core.js
+++ b/public/js/core.js
@@ -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.47-beta";
+const VERSION = "0.4.48-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;
diff --git a/test/avatar.test.js b/test/avatar.test.js
index 8f3dd6f..4400b4c 100644
--- a/test/avatar.test.js
+++ b/test/avatar.test.js
@@ -58,34 +58,61 @@ test('avatars combineren een grote kop met een compact soortspecifiek lijf',asyn
assert.notEqual(bodies.monster,bodies.animal);
});
-test('haar heeft natuurlijke voor- en achterlagen en vier nieuwe stijlen',async()=>{
+test("haar volgt de kop met natuurlijke details en correcte lagen",async()=>{
const api=await loadAvatarRenderer();
- assert.deepEqual(Array.from(api.hairIds),['none','tuft','spiky','curly','wavy','bob','ponytail','braids','mohawk']);
+ assert.deepEqual(Array.from(api.hairIds),["none","tuft","spiky","curly","wavy","bob","ponytail","braids","mohawk"]);
for(const id of api.hairIds){
- assert.equal(typeof api.hair[id].back,'function',id+' mist haar-achterlaag');
- assert.equal(typeof api.hair[id].front,'function',id+' mist haar-voorlaag');
- }
- for(const id of ['tuft','wavy','bob','braids']){
- const markup=api.renderAvatarFace({...base,hairStyle:id,hairColor:'#123456'},'lg').innerHTML;
- assert.ok(markup.includes('av-hair-'+id),id+' wordt niet getekend');
- assert.ok(markup.includes('#123456'),id+' gebruikt de gekozen haarkleur niet');
+ assert.equal(typeof api.hair[id].back,"function",id+" mist haar-achterlaag");
+ assert.equal(typeof api.hair[id].front,"function",id+" mist haar-voorlaag");
}
- const bob=api.renderAvatarFace({...base,hairStyle:'bob'},'lg').innerHTML;
- assert.ok(bob.indexOf('av-hair-bob')bob.indexOf('av-head-large'));
- const braids=api.renderAvatarFace({...base,hairStyle:'braids'},'lg').innerHTML;
- assert.ok(braids.indexOf('av-hair-braids')braids.indexOf('av-head-large'));
+ for(const id of api.hairIds.filter(id=>id!=="none")){
+ const front=api.hair[id].front("#123456");
+ const back=api.hair[id].back("#123456");
+ const combined=back+front;
+ assert.ok(front.includes("av-hair-"+id),id+" mist een herkenbare voorlaag");
+ assert.ok(combined.includes("av-hair-texture"),id+" mist natuurlijke haartextuur");
+ assert.doesNotMatch(combined,/undefined|NaN/,id+" bevat ongeldige SVG-waarden");
+ }
+ for(const id of ["curly","wavy","bob","ponytail","braids"]){
+ assert.notEqual(api.hair[id].back("#123456"),"",id+" mist volume achter de kop");
+ }
+ for(const id of ["tuft","spiky","mohawk"]){
+ assert.equal(api.hair[id].back("#123456"),"",id+" hoort geen onzichtbare achterlaag te hebben");
+ }
+ for(const id of ["wavy","bob","ponytail","braids"]){
+ assert.ok(api.hair[id].front("#123456").includes("av-hair-part"),id+" mist een natuurlijke scheiding");
+ }
+ assert.ok(api.hair.wavy.front("#123456").includes("av-side-lock"));
+ assert.ok(api.hair.bob.front("#123456").includes("av-side-lock"));
+ assert.ok(api.hair.ponytail.back("#123456").includes("av-hair-tie"));
+ assert.ok(api.hair.braids.back("#123456").includes("av-hair-tie"));
- const cap=api.renderAvatarFace({...base,hairStyle:'spiky',hairColor:'#123456',accessoryHead:'cap'},'lg').innerHTML;
- const beanie=api.renderAvatarFace({...base,hairStyle:'spiky',hairColor:'#123456',accessoryHead:'beanie'},'lg').innerHTML;
- const crown=api.renderAvatarFace({...base,hairStyle:'spiky',hairColor:'#123456',accessoryHead:'crown'},'lg').innerHTML;
- const cappedBraids=api.renderAvatarFace({...base,hairStyle:'braids',hairColor:'#123456',accessoryHead:'cap'},'lg').innerHTML;
- assert.ok(!cap.includes('#123456'),'pet hoort de voorste haarlaag af te dekken');
- assert.ok(!beanie.includes('#123456'),'muts hoort de voorste haarlaag af te dekken');
- assert.ok(crown.includes('#123456'),'kroon hoort op het haar te rusten');
- assert.ok(cappedBraids.includes('#123456'),'vlechten horen naast een pet zichtbaar te blijven');
+ const transforms={dino:"translate(1 0)",monster:"translate(0 -1)",animal:"translate(0 0)"};
+ for(const [species,transform] of Object.entries(transforms)){
+ const markup=api.renderAvatarFace({...base,species,hairStyle:"wavy",hairColor:"#123456"},"lg").innerHTML;
+ assert.equal((markup.match(new RegExp("av-hair-fit-"+species,"g"))||[]).length,2,species+" mist voor- of achterpasvorm");
+ assert.ok(markup.includes(`transform="${transform}"`),species+" gebruikt een verkeerde kruincorrectie");
+ assert.ok(markup.indexOf("av-hair-back-layer")markup.indexOf("av-head-large"));
+ }
+
+ const bob=api.renderAvatarFace({...base,hairStyle:"bob"},"lg").innerHTML;
+ assert.ok(bob.indexOf("av-hair-bob")bob.indexOf("av-head-large"));
+ const braids=api.renderAvatarFace({...base,hairStyle:"braids"},"lg").innerHTML;
+ assert.ok(braids.indexOf("av-hair-braids")braids.indexOf("av-head-large"));
+
+ const cap=api.renderAvatarFace({...base,hairStyle:"spiky",hairColor:"#123456",accessoryHead:"cap"},"lg").innerHTML;
+ const beanie=api.renderAvatarFace({...base,hairStyle:"spiky",hairColor:"#123456",accessoryHead:"beanie"},"lg").innerHTML;
+ const crown=api.renderAvatarFace({...base,hairStyle:"spiky",hairColor:"#123456",accessoryHead:"crown"},"lg").innerHTML;
+ const cappedBraids=api.renderAvatarFace({...base,hairStyle:"braids",hairColor:"#123456",accessoryHead:"cap"},"lg").innerHTML;
+ assert.ok(!cap.includes("av-hair-front-layer"),"pet hoort kort haar af te dekken");
+ assert.ok(!beanie.includes("av-hair-front-layer"),"muts hoort kort haar af te dekken");
+ assert.ok(crown.includes("av-hair-front-layer"),"kroon hoort op het haar te rusten");
+ assert.ok(cappedBraids.includes("av-hair-back-layer"),"vlechten horen naast een pet zichtbaar te blijven");
+ assert.ok(!cappedBraids.includes("av-hair-front-layer"),"pet hoort de bovenkant van vlechten af te dekken");
});
test('accessoires gebruiken vaste ankers en correcte voor- en achterlagen',async()=>{