refactor: personage-tekening volledig herbouwd als SVG (v0.4.26-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m40s

- Kernprobleem: de CSS-laagjesversie (losse divs met percentage-posities
  en z-index) had onderdelen die los van het lichaam hingen en een
  tekenvolgorde die niet klopte (soort-specifieke delen soms achter
  hoofd/lijf verstopt, soms er los voor zwevend). SVG lost dit
  structureel op: tekenvolgorde = laagvolgorde, geen z-index nodig, en
  elk onderdeel is een gebogen path/ellips die het silhouet van het
  vorige onderdeel ruim overlapt in plaats van ernaast te zweven.
- Onderzocht hoe gangbare avatar-generators (DiceBear, Avataaars) dit
  aanpakken: vaste 100x100 viewBox, onderdelen als SVG-groepen in de
  juiste volgorde, kleur via directe fill-waarden i.p.v. CSS-variabelen
  in SVG. Dat patroon hier toegepast, geen code overgenomen.
- Elke soort (dino/monster/dier) is nu een samenhangend lichaam: poten,
  armen, staart/oren, buikvlek en gezicht overlappen allemaal duidelijk
  met het hoofd/lijf. Dino: rugstekels, snuit met neusgaten, glimlach.
  Monster: hoorntjes, brede mond met slagtandjes, asymmetrische ogen.
  Dier: ronde oren, beer-snuit, kleine staart.
- Haar (spits/krullen/paardenstaart/mohawk) en accessoires (pet/kroon/
  bril/strik/sjaal/cape/badge) opnieuw getekend zodat ze ook echt op
  hun naam lijken, i.p.v. simpele gekleurde blokjes.
- Regel toegevoegd: een pet of kroon dekt het hele bovenhoofd af, dus
  onderliggend haar wordt dan niet getekend (gaf anders rommelige,
  half verstopte plukjes rond de rand) - een paardenstaart blijft wel
  zichtbaar, want die hangt naar opzij.
- Kleuren (lichter voor buik/snuit, donkerder voor poten/staart/
  rugstekels) worden nu berekend uit de gekozen lichaamskleur zelf
  (avatarShade-helper), i.p.v. via CSS filter:brightness().
- CSS: alle oude .av-* laag-regels (~65 regels) verwijderd; alleen
  afmetingen (sm/md/lg) en de bestaande instellingen/widget/
  aanmoedigings-CSS blijven staan.
- Geverifieerd: alle vijf bestaande scenario's (opslaan, widget,
  School-navigator-badge met een echte klas vol verschillende
  personages, math.js-aanmoediging, ongeldige input), plus uitgebreide
  screenshot-rondes van losse onderdelen, alle soort/haar/accessoire-
  combinaties en alle drie weergavegroottes (24/44/180px) vóór het
  committen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
This commit is contained in:
Ramon 2026-07-19 10:19:19 +02:00
parent 4438fad52b
commit 46696b0d7d
4 changed files with 166 additions and 92 deletions

View file

@ -1 +1 @@
0.4.25-beta
0.4.26-beta

View file

@ -1674,69 +1674,10 @@ body.hc #penbar .pen-current,body.hc #penbar .pen-popover{border:2px solid #000;
.bd-plate{width:min(94%,420px); height:30px; border-radius:50%; margin-top:-12px; box-shadow:0 8px 12px rgba(29,52,97,.22); z-index:1;}
/* =========== Personage (avatar): gelaagde div's + CSS custom properties,
zelfde aanpak als de taart hierboven (--cake/--icing), geen SVG/afbeeldingen. ==== */
.avatar-face{position:relative; display:inline-block; flex:none;}
.avatar-face{display:inline-block; flex:none; overflow:visible;}
.avatar-face.sm{width:24px; height:24px;}
.avatar-face.md{width:44px; height:44px;}
.avatar-face.lg{width:180px; height:180px;}
.av-body{position:absolute; left:20%; right:20%; bottom:2%; top:52%; border-radius:45% 45% 40% 40%; background:var(--av-body); z-index:1;}
.av-head{position:absolute; left:8%; right:8%; top:4%; height:56%; border-radius:50%; background:var(--av-body); z-index:3; box-shadow:inset 0 -6px 10px rgba(0,0,0,.12);}
.av-part-head{position:absolute; z-index:6; display:none;}
.av-species-monster .av-part-head{display:block; left:28%; right:28%; top:0; height:14%;}
.av-species-monster .av-part-head::before,.av-species-monster .av-part-head::after{content:""; position:absolute; width:16%; height:100%; background:var(--av-body); filter:brightness(.85); clip-path:polygon(50% 0,0% 100%,100% 100%);}
.av-species-monster .av-part-head::before{left:6%;}
.av-species-monster .av-part-head::after{right:6%;}
.av-species-animal .av-part-head{display:block; left:0; right:0; top:-2%; height:22%;}
.av-species-animal .av-part-head::before,.av-species-animal .av-part-head::after{content:""; position:absolute; width:22%; height:100%; border-radius:50%; background:var(--av-body);}
.av-species-animal .av-part-head::before{left:2%;}
.av-species-animal .av-part-head::after{right:2%;}
.av-part-body{position:absolute; z-index:6; display:none;}
.av-species-dino .av-part-body{display:block; left:22%; right:22%; top:36%; height:26%;}
.av-species-dino .av-part-body::before,.av-species-dino .av-part-body::after{content:""; position:absolute; width:22%; height:82%; bottom:0; background:var(--av-body); filter:brightness(.68); clip-path:polygon(50% 0%,100% 65%,100% 100%,0% 100%,0% 65%); border-radius:3px 3px 0 0;}
.av-species-dino .av-part-body::before{left:4%;}
.av-species-dino .av-part-body::after{right:4%;}
.av-spike-mid{display:none; position:absolute;}
.av-species-dino .av-spike-mid{display:block; left:39%; width:22%; height:100%; bottom:0; background:var(--av-body); filter:brightness(.68); clip-path:polygon(50% 0%,100% 65%,100% 100%,0% 100%,0% 65%); border-radius:3px 3px 0 0;}
.av-species-animal .av-part-body{display:block; right:2%; bottom:8%; width:16%; height:30%; border-radius:50% 50% 50% 10%; background:var(--av-body); transform:rotate(20deg); filter:brightness(.9);}
.av-tail{position:absolute; z-index:0; display:none;}
.av-species-dino .av-tail{display:block; left:-14%; bottom:10%; width:30%; height:16%; background:var(--av-body); filter:brightness(.93); clip-path:polygon(100% 15%,100% 85%,35% 100%,0% 55%,30% 0%); transform:rotate(-8deg);}
.av-leg{position:absolute; z-index:0; display:none; border-radius:45% 45% 55% 55%;}
.av-species-dino .av-leg{display:block; top:82%; height:16%; width:17%; background:var(--av-body); filter:brightness(.78);}
.av-leg-l{left:16%;}
.av-leg-r{right:16%;}
.av-arm{position:absolute; z-index:2; display:none; border-radius:50%;}
.av-species-dino .av-arm{display:block; top:66%; height:15%; width:13%; background:var(--av-body); filter:brightness(.88);}
.av-arm-l{left:9%; transform:rotate(20deg);}
.av-arm-r{right:9%; transform:rotate(-20deg);}
.av-belly{position:absolute; z-index:2; display:none; border-radius:50%;}
.av-species-dino .av-belly{display:block; left:35%; right:35%; top:66%; height:28%; background:var(--av-body); filter:brightness(1.4);}
.av-eye{position:absolute; z-index:4; top:30%; width:16%; height:16%; border-radius:50%; background:#fff; box-shadow:inset 0 0 0 1.5px rgba(20,20,30,.15);}
.av-eye::after{content:""; position:absolute; width:46%; height:46%; left:27%; top:30%; border-radius:50%; background:#232733;}
.av-eye-l{left:25%;}
.av-eye-r{right:25%;}
.av-snout{position:absolute; z-index:7; display:none;}
.av-species-dino .av-snout{display:block; left:30%; right:30%; top:46%; height:20%; border-radius:45%; background:var(--av-body); filter:brightness(1.22);}
.av-species-dino .av-snout::before,.av-species-dino .av-snout::after{content:""; position:absolute; width:14%; height:22%; top:28%; border-radius:50%; background:#2b2b2b; opacity:.6;}
.av-species-dino .av-snout::before{left:26%;}
.av-species-dino .av-snout::after{right:26%;}
.av-hair{position:absolute; z-index:4; display:none; background:var(--av-hair);}
.av-hair-spiky{display:block; left:14%; right:14%; top:-2%; height:22%; clip-path:polygon(0% 100%,10% 20%,25% 90%,35% 10%,50% 90%,65% 10%,75% 90%,90% 20%,100% 100%);}
.av-hair-curly{display:block; left:6%; right:6%; top:-4%; height:26%; border-radius:50% 50% 40% 40%;}
.av-hair-ponytail{display:block; left:10%; right:10%; top:-2%; height:20%; border-radius:50% 50% 30% 30%;}
.av-hair-ponytail::after{content:""; position:absolute; right:-6%; top:40%; width:20%; height:45%; border-radius:0 50% 50% 50%; background:var(--av-hair);}
.av-hair-mohawk{display:block; left:42%; right:42%; top:-8%; height:30%; border-radius:40% 40% 0 0;}
.av-hair-none{display:none;}
.av-acc-head{position:absolute; z-index:5; display:none;}
.av-acc-head-cap{display:block; left:6%; right:6%; top:2%; height:20%; border-radius:50% 50% 0 0; background:var(--blue);}
.av-acc-head-crown{display:block; left:14%; right:14%; top:-4%; height:18%; background:var(--yellow); clip-path:polygon(0% 100%,0% 40%,20% 70%,35% 10%,50% 60%,65% 10%,80% 70%,100% 40%,100% 100%);}
.av-acc-head-glasses{display:block; left:14%; right:14%; top:32%; height:16%;}
.av-acc-head-glasses::before,.av-acc-head-glasses::after{content:""; position:absolute; width:38%; height:100%; border-radius:50%; border:3px solid var(--ink); background:rgba(255,255,255,.35);}
.av-acc-head-glasses::before{left:0;}
.av-acc-head-glasses::after{right:0;}
.av-acc-head-bow{display:block; right:8%; top:8%; width:18%; height:14%; background:var(--pink); clip-path:polygon(0% 0%,50% 40%,100% 0%,100% 100%,50% 60%,0% 100%);}
.av-acc-body{position:absolute; z-index:4; display:none;}
.av-acc-body-scarf{display:block; left:18%; right:18%; top:48%; height:10%; border-radius:8px; background:var(--red);}
.av-acc-body-cape{display:block; left:10%; right:10%; top:54%; bottom:0; z-index:0; border-radius:0 0 40% 40%; background:var(--purple); opacity:.9;}
.av-acc-body-badge{display:block; left:32%; width:16%; top:62%; height:12%; border-radius:50%; background:var(--yellow); border:2px solid #fff;}
.avatar-editor{display:flex; flex-direction:column; gap:12px; padding:14px; height:100%; min-height:0; overflow:auto;}
.avatar-preview{display:flex; justify-content:center; padding:10px; background:var(--surface-2); border-radius:16px; flex:none;}
.avatar-controls{display:flex; flex-direction:column; gap:10px;}

View file

@ -1,7 +1,9 @@
/* teach - personage (avatar): eigen figuur samenstellen (soort, kleuren, haar,
accessoires), gedeeld tussen de Instellingen-sectie "Mijn personage" en het
avatar-widget. Gelaagde div's + CSS custom properties, zelfde aanpak als de
verjaardagstaart-widget (--cake/--icing) - geen SVG/afbeeldingen. */
avatar-widget. Getekend als één SVG (viewBox 0 0 100 100, tekenvolgorde =
laagvolgorde - geen z-index-gepuzzel zoals bij de eerdere CSS-laagjesversie)
zodat elk onderdeel z'n vaste plek op het silhouet overlapt in plaats van
los ernaast te zweven. */
"use strict";
const AVATAR_SPECIES = ["dino", "monster", "animal"];
const AVATAR_HAIR = ["none", "spiky", "curly", "ponytail", "mohawk"];
@ -11,6 +13,7 @@ const AVATAR_DEFAULT = Object.freeze({
species: "dino", bodyColor: "#3fae6a", hairStyle: "none", hairColor: "#5b3a29",
accessoryHead: "none", accessoryBody: "none",
});
const AVATAR_SVG_NS = "http://www.w3.org/2000/svg";
function avatarSanitize(config){
const c = config || {};
@ -24,36 +27,166 @@ function avatarSanitize(config){
};
}
/* bouwt de gelaagde weergave van een personage; config==null neutrale
standaardweergave (nooit een lege/kapotte weergave). */
/* mengt een hex-kleur amt (-1..1) richting zwart (negatief) of wit (positief) -
gebruikt voor buik/snuit-highlights en pootjes/staart-schaduw, zodat die
altijd bij de gekozen lichaamskleur passen zonder een aparte kleurkiezer. */
function avatarShade(hex, amt){
const n = parseInt(hex.slice(1), 16);
let r = (n>>16)&255, g = (n>>8)&255, b = n&255;
if(amt < 0){ r *= 1+amt; g *= 1+amt; b *= 1+amt; }
else{ r += (255-r)*amt; g += (255-g)*amt; b += (255-b)*amt; }
const clamp = v => Math.max(0, Math.min(255, Math.round(v)));
return "#" + [r,g,b].map(v=>clamp(v).toString(16).padStart(2,"0")).join("");
}
/* per-soort silhouet: elk onderdeel overlapt het vorige ruim (poten/armen/
staart tot ver ín het lijf, rugstekels/hoorns pas de kop getekend) zodat
niets los oogt. Volgorde = tekenvolgorde = zichtbare laagvolgorde. */
function avatarBodyMarkup(species, bodyColor){
const dark = avatarShade(bodyColor, -0.35);
const light = avatarShade(bodyColor, 0.4);
let m = "";
if(species === "dino"){
m += `<path d="M 38 88 C 18 92, 2 82, 1 64 C 3 78, 18 86, 35 76 Z" fill="${dark}"/>`;
}
if(species === "animal"){
m += `<circle cx="79" cy="82" r="10" fill="${dark}"/>`;
}
m += `<ellipse cx="34" cy="92" rx="10" ry="8" fill="${dark}"/>`;
m += `<ellipse cx="66" cy="92" rx="10" ry="8" fill="${dark}"/>`;
m += `<ellipse cx="50" cy="72" rx="24" ry="19" fill="${bodyColor}"/>`;
m += `<ellipse cx="50" cy="75" rx="13" ry="13" fill="${light}"/>`;
m += `<ellipse cx="25" cy="68" rx="10" ry="8" fill="${dark}"/>`;
m += `<ellipse cx="75" cy="68" rx="10" ry="8" fill="${dark}"/>`;
if(species === "animal"){
m += `<circle cx="27" cy="10" r="11" fill="${bodyColor}"/>`;
m += `<circle cx="73" cy="10" r="11" fill="${bodyColor}"/>`;
m += `<circle cx="27" cy="10" r="6" fill="${light}"/>`;
m += `<circle cx="73" cy="10" r="6" fill="${light}"/>`;
}
if(species === "monster"){
const darker = avatarShade(bodyColor, -0.5);
m += `<path d="M 30 20 C 26 12, 27 4, 32 0 C 33 8, 34 15, 38 20 Z" fill="${darker}"/>`;
m += `<path d="M 70 20 C 74 12, 73 4, 68 0 C 67 8, 66 15, 62 20 Z" fill="${darker}"/>`;
}
m += `<ellipse cx="50" cy="28" rx="26" ry="26" fill="${bodyColor}"/>`;
if(species === "dino"){
m += `<ellipse cx="50" cy="43" rx="15" ry="10" fill="${light}"/>`;
m += `<ellipse cx="44.5" cy="41" rx="2.1" ry="3" fill="#234"/>`;
m += `<ellipse cx="55.5" cy="41" rx="2.1" ry="3" fill="#234"/>`;
}
if(species === "animal"){
m += `<ellipse cx="50" cy="42" rx="14" ry="10" fill="${light}"/>`;
m += `<ellipse cx="50" cy="38" rx="4.4" ry="3.4" fill="#3a2a18"/>`;
}
if(species === "monster"){
m += `<circle cx="37" cy="25" r="8.4" fill="#fff"/>`;
m += `<circle cx="63" cy="25" r="6.8" fill="#fff"/>`;
m += `<circle cx="38.6" cy="27" r="3.6" fill="#232733"/>`;
m += `<circle cx="64.2" cy="27" r="2.9" fill="#232733"/>`;
}else{
m += `<circle cx="38" cy="24" r="7.6" fill="#fff"/>`;
m += `<circle cx="62" cy="24" r="7.6" fill="#fff"/>`;
m += `<circle cx="39.4" cy="26" r="3.2" fill="#232733"/>`;
m += `<circle cx="60.4" cy="26" r="3.2" fill="#232733"/>`;
}
if(species === "dino"){
m += `<path d="M 43 51 Q 50 55.5 57 51" stroke="${avatarShade(bodyColor,-0.55)}" stroke-width="1.6" fill="none" stroke-linecap="round"/>`;
const plate = avatarShade(bodyColor, -0.45);
m += `<path d="M 35 68 L 39 53 L 43 66 Q 39 72 35 68 Z" fill="${plate}"/>`;
m += `<path d="M 46 66 L 50 50 L 54 66 Q 50 73 46 66 Z" fill="${plate}"/>`;
m += `<path d="M 57 68 L 61 53 L 65 66 Q 61 72 57 68 Z" fill="${plate}"/>`;
}
if(species === "animal"){
m += `<path d="M 50 41 L 50 45 M 44 49 Q 50 52 56 49" stroke="#3a2a18" stroke-width="1.6" fill="none" stroke-linecap="round"/>`;
}
if(species === "monster"){
m += `<path d="M 34 42 Q 50 54 66 42 Q 50 50 34 42 Z" fill="#3c2158"/>`;
m += `<path d="M 42 43 L 44 49 L 46 43 Z" fill="#fff"/>`;
m += `<path d="M 54 43 L 56 49 L 58 43 Z" fill="#fff"/>`;
}
return m;
}
const AVATAR_HAIR_MARKUP = {
none: () => "",
spiky: (c) => `
<path d="M 27 18 L 24 4 L 32 15 Z" fill="${c}"/>
<path d="M 37 12 L 36 -1 L 44 10 Z" fill="${c}"/>
<path d="M 48 10 L 50 -3 L 55 10 Z" fill="${c}"/>
<path d="M 60 10 L 64 -1 L 63 12 Z" fill="${c}"/>
<path d="M 68 15 L 76 4 L 73 18 Z" fill="${c}"/>`,
curly: (c) => `
<circle cx="26" cy="14" r="9" fill="${c}"/>
<circle cx="37" cy="6" r="10" fill="${c}"/>
<circle cx="50" cy="3" r="10.5" fill="${c}"/>
<circle cx="63" cy="6" r="10" fill="${c}"/>
<circle cx="74" cy="14" r="9" fill="${c}"/>`,
ponytail: (c) => `
<path d="M 22 20 Q 24 -6 50 -6 Q 76 -6 78 20 Q 62 2 50 2 Q 38 2 22 20 Z" fill="${c}"/>
<path d="M 71 9 Q 89 12 87 28 Q 85 42 72 47 Q 81 34 77 22 Q 75 13 67 11 Z" fill="${c}"/>
<ellipse cx="72" cy="13" rx="4.2" ry="3.2" fill="${avatarShade(c,-0.25)}"/>`,
mohawk: (c) => `<path d="M 44 20 Q 43 4 50 1 Q 57 4 56 20 Q 50 15 44 20 Z" fill="${c}"/>`,
};
const AVATAR_HEAD_ACC_MARKUP = {
none: () => "",
cap: () => `
<path d="M 22 14 Q 24 -6 50 -6 Q 76 -6 78 14 Q 64 4 50 4 Q 36 4 22 14 Z" fill="#3d7ddb"/>
<path d="M 22 14 Q 34 8 50 8 L 50 14 Q 34 15 21 20 Z" fill="#2f63b3"/>`,
crown: () => `
<path d="M 26 16 L 30 0 L 38 12 L 50 -4 L 62 12 L 70 0 L 74 16 Q 50 8 26 16 Z" fill="#f0b73a"/>
<circle cx="30" cy="1" r="2.6" fill="#e0483c"/>
<circle cx="50" cy="-3" r="2.6" fill="#3a8ee0"/>
<circle cx="70" cy="1" r="2.6" fill="#e0483c"/>`,
glasses: () => `
<circle cx="38" cy="25" r="9.5" fill="rgba(255,255,255,.3)" stroke="#232733" stroke-width="2.4"/>
<circle cx="62" cy="25" r="9.5" fill="rgba(255,255,255,.3)" stroke="#232733" stroke-width="2.4"/>
<path d="M 47.5 25 L 52.5 25" stroke="#232733" stroke-width="2.4"/>
<path d="M 28.5 23 L 22 20" stroke="#232733" stroke-width="2.2" stroke-linecap="round"/>
<path d="M 71.5 23 L 78 20" stroke="#232733" stroke-width="2.2" stroke-linecap="round"/>`,
bow: () => `
<path d="M 68 14 Q 58 8 58 16 Q 58 22 68 18 Q 74 24 78 18 Q 80 12 74 10 Q 78 6 74 2 Q 68 2 68 14 Z" fill="#e05a8a"/>
<circle cx="69" cy="14" r="3" fill="#c23f6e"/>`,
};
const AVATAR_BODY_ACC_MARKUP = {
none: () => "",
scarf: () => `
<path d="M 33 58 Q 50 68 67 58 L 65 66 Q 50 74 35 66 Z" fill="#d94f4f"/>
<path d="M 46 65 L 43 80 L 50 76 Z" fill="#c23f3f"/>`,
cape: () => `
<path d="M 30 55 Q 10 75 16 100 L 30 90 Q 26 70 40 58 Z" fill="#7a4bc7" opacity=".92"/>
<path d="M 70 55 Q 90 75 84 100 L 70 90 Q 74 70 60 58 Z" fill="#7a4bc7" opacity=".92"/>`,
badge: () => `<circle cx="50" cy="78" r="6" fill="#f0b73a" stroke="#fff" stroke-width="1.6"/>`,
};
/* een pet/kroon dekt het hele bovenhoofd af - loshangend haar eronder tonen
gaf rommelige, half-verstopte plukjes rond de rand (zie roadmap-memory).
Een paardenstaart mag wel blijven: die hangt naar opzij, niet over de kop. */
function avatarHairSuppressed(c){
return (c.accessoryHead === "cap" || c.accessoryHead === "crown") && c.hairStyle !== "ponytail";
}
/* bouwt de SVG-weergave van een personage; config==null neutrale
standaardweergave (nooit een lege/kapotte weergave). Tekenvolgorde bepaalt
de laagvolgorde (SVG kent geen z-index) - vandaar cape/staart/poten eerst,
accessoires als laatste. */
function renderAvatarFace(config, size){
const c = avatarSanitize(config);
const face = document.createElement("div");
face.className = "avatar-face " + (size || "md") + " av-species-" + c.species;
face.style.setProperty("--av-body", c.bodyColor);
face.style.setProperty("--av-hair", c.hairColor);
face.setAttribute("role", "img");
face.setAttribute("aria-label", T("avatarAlt"));
const partHead = document.createElement("div"); partHead.className = "av-part-head";
const hair = document.createElement("div"); hair.className = "av-hair av-hair-" + c.hairStyle;
const head = document.createElement("div"); head.className = "av-head";
const eyeL = document.createElement("div"); eyeL.className = "av-eye av-eye-l";
const eyeR = document.createElement("div"); eyeR.className = "av-eye av-eye-r";
const snout = document.createElement("div"); snout.className = "av-snout";
const accHead = document.createElement("div"); accHead.className = "av-acc-head av-acc-head-" + c.accessoryHead;
const tail = document.createElement("div"); tail.className = "av-tail";
const legL = document.createElement("div"); legL.className = "av-leg av-leg-l";
const legR = document.createElement("div"); legR.className = "av-leg av-leg-r";
const partBody = document.createElement("div"); partBody.className = "av-part-body";
const spikeMid = document.createElement("div"); spikeMid.className = "av-spike-mid";
partBody.appendChild(spikeMid);
const body = document.createElement("div"); body.className = "av-body";
const belly = document.createElement("div"); belly.className = "av-belly";
const armL = document.createElement("div"); armL.className = "av-arm av-arm-l";
const armR = document.createElement("div"); armR.className = "av-arm av-arm-r";
const accBody = document.createElement("div"); accBody.className = "av-acc-body av-acc-body-" + c.accessoryBody;
face.append(tail, legL, legR, partBody, body, belly, armL, armR, accBody, partHead, head, eyeL, eyeR, snout, accHead, hair);
return face;
const svg = document.createElementNS(AVATAR_SVG_NS, "svg");
svg.setAttribute("viewBox", "0 0 100 100");
svg.setAttribute("class", "avatar-face " + (size || "md") + " av-species-" + c.species);
svg.setAttribute("role", "img");
svg.setAttribute("aria-label", T("avatarAlt"));
let markup = "";
if(c.accessoryBody === "cape") markup += AVATAR_BODY_ACC_MARKUP.cape();
markup += avatarBodyMarkup(c.species, c.bodyColor);
if(!avatarHairSuppressed(c)) markup += AVATAR_HAIR_MARKUP[c.hairStyle](c.hairColor);
markup += AVATAR_HEAD_ACC_MARKUP[c.accessoryHead]();
if(c.accessoryBody !== "cape") markup += AVATAR_BODY_ACC_MARKUP[c.accessoryBody]();
svg.innerHTML = markup;
return svg;
}
/* bouwt de interactieve kiezer + live voorbeeld; roept onChange(config) aan

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