<!-- SOLID CITY · part 28 -->
unction () {
    D.open = !D.open;
    panel.style.display = D.open ? 'block' : 'none';
    if (D.open) {
      if (!D.mod) {
        import('/city/triangle-seam-auditor.js?v=20261013a').then(function (m) { D.mod = m; renderButtons(); renderLines(); })
          .catch(function (e) { if (window.console) console.warn('[city] auditor failed to load', e); });
      } else { renderButtons(); renderLines(); }
    }
  });
  renderLines();
}
fetch('/api/city/me').then(function (r) { return r.json(); }).then(function (me) {
  if (me && me.king && me.royal) build();
}).catch(function () {});
})();


===== FILE: public/city/avatar-factory.js =====
/* ------------------------------------------------------------------
 * avatar-factory.js — the Avatar Forge's builder, held by the city.
 * GENERATED from the shipped Forge page (avatar-builder v4) by
 * build-avatar-factory.js. The builder core is verbatim; it is
 * wrapped in a factory so the city builds citizen figures with its
 * own THREE — one engine in the scene, as the law requires.
 * ------------------------------------------------------------------ */
export function createAvatarFactory(THREE) {
const VERSION=1, KEY='epistles.avatar.v1';
const defaults={name:'Citizen',height:1.75,shoulders:0.46,torso:0.64,armLength:0.60,legLength:0.83,build:1,headSize:1,skin:'#ba825f',eyes:'#63d9e7',hair:'#18202a',hairStyle:'short',face:'round',top:'jacket',bottom:'pants',shoes:'boots',armor:'none',accessory:'none',primary:'#1ba6bd',secondary:'#e2b15b',metalness:0.35,roughness:0.55,animation:'idle',showRig:false};
const options={hairStyle:['none','short','long','mohawk'],face:['round','angular','oval'],top:['shirt','jacket','armor'],bottom:['pants','shorts'],shoes:['sneakers','boots'],armor:['none','shoulders','full'],accessory:['none','visor','halo'],animation:['idle','walk','run','wave','tpose']};
const sections=[['Identity',[['name','text']]],['Anatomy',[['height','range',1.35,2.1,0.01],['shoulders','range',0.32,0.70,0.01],['torso','range',0.48,0.84,0.01],['armLength','range',0.42,0.78,0.01],['legLength','range',0.64,1.06,0.01],['build','range',0.75,1.4,0.01],['headSize','range',0.75,1.3,0.01],['face','select']]],['Appearance',[['skin','color'],['eyes','color'],['hair','color'],['hairStyle','select']]],['Wardrobe',[['top','select'],['bottom','select'],['shoes','select'],['armor','select'],['accessory','select'],['primary','color'],['secondary','color']]],['Material',[['metalness','range',0,1,0.01],['roughness','range',0.1,1,0.01]]],['Preview',[['animation','select'],['showRig','checkbox']]]];
let avatar = null, parts = {}, rigMarkers = [];
const scene = new THREE.Group(); // preview builds land here, inert; citizens use detached builds
function material(color,metalness=.1,roughness=.65){return new THREE.MeshStandardMaterial({color,metalness,roughness});}
function mesh(parent,geo,mat,x=0,y=0,z=0){const m=new THREE.Mesh(geo,mat);m.position.set(x,y,z);m.castShadow=true;m.receiveShadow=true;parent.add(m);return m;}
function sphere(parent,r,mat,x=0,y=0,z=0,sx=1,sy=1,sz=1){const m=mesh(parent,new THREE.SphereGeometry(r,18,12),mat,x,y,z);m.scale.set(sx,sy,sz);return m;}
function box(parent,w,h,d,mat,x=0,y=0,z=0){return mesh(parent,new THREE.BoxGeometry(w,h,d),mat,x,y,z);}
function capsule(parent,r,length,mat,x=0,y=0,z=0){return mesh(parent,new THREE.CapsuleGeometry(r,Math.max(.001,length),5,12),mat,x,y,z);}
function group(parent,x=0,y=0,z=0){const g=new THREE.Group();g.position.set(x,y,z);parent.add(g);return g;}
function disposeTree(root){root.traverse(o=>{if(o.geometry)o.geometry.dispose();if(o.material){for(const m of Array.isArray(o.material)?o.material:[o.material])m.dispose();}});}
function sculptTorso(width,height,depth){const g=new THREE.CylinderGeometry(width*.46,width*.35,height,20,8);const p=g.attributes.position;for(let i=0;i<p.count;i++){const y=p.getY(i)/height+.5;const waist=1-.13*Math.sin(Math.PI*Math.max(0,Math.min(1,y)));p.setX(i,p.getX(i)*waist);p.setZ(i,p.getZ(i)*depth/width*1.7);}g.computeVertexNormals();return g;}
function buildAvatar(d,opts){const preview=!opts||opts.preview!==false;if(preview&&avatar){scene.remove(avatar);disposeTree(avatar);}const root=new THREE.Group();root.name='EpistlesAvatar';const P={},RM=[];if(preview){avatar=root;parts=P;rigMarkers=RM;scene.add(root);}
 const scale=d.height/1.75, hipY=d.legLength*scale,torsoH=d.torso*scale,armL=d.armLength*scale,legL=d.legLength*scale,shoulderW=d.shoulders*scale,bulk=d.build;
 const skin=material(d.skin),cloth=material(d.primary,d.metalness,d.roughness),accent=material(d.secondary,d.metalness,.4),hair=material(d.hair),eye=material(d.eyes,.2,.2),dark=material('#132032',.25,.45);
 const hips=group(root,0,hipY,0);P.hips=hips;box(hips,.32*bulk,.18,.21*bulk,cloth);
 const spine=group(hips,0,.08,0);P.spine=spine;const torso=mesh(spine,sculptTorso(shoulderW*1.85*bulk,torsoH,.25*bulk),cloth,0,torsoH/2,0);
 if(d.top==='jacket'){box(spine,.09,torsoH*.86,.27*bulk,accent,-.09,tor