Family Chore Wheel rotates suitable household tasks from an editable list. Agree on what each chore means, match jobs to age and ability, and use the result as a fair starting point rather than a punishment.
family choreshousehold taskschore rotationkids chore wheel
Wheel of Names
Elimination: OFFAccumulation: OFFAuto-Spin: OFFFair Rotation: OFF
Add entries and spin the wheel.
(()=>{
const r=document.currentScript.closest(‘.wnx-spinner’);
if(!r||r.dataset.ready)return;
r.dataset.ready=’1′;
const $=s=>r.querySelector(s),$$=s=>[…r.querySelectorAll(s)];
const params=new URLSearchParams(location.search),cfg=document.querySelector(‘.wnx-config’);
const mode=cfg?.dataset.mode||r.dataset.mode||’standard’;
const title=cfg?.dataset.title||r.dataset.title||document.querySelector(‘.wn-page h1′)?.textContent||’Wheel of Names’;
const raw=params.get(‘wnitems’)||cfg?.dataset.items||r.dataset.items||’Alice~Bob~Charlie~David~Eve~Frank’;
const palettes=[
((cfg?.dataset.palette)||r.dataset.palette||”).split(‘,’).filter(Boolean),
[‘#6366f1′,’#8b5cf6′,’#ec4899′,’#f97316′,’#14b8a6′,’#3b82f6’],
[‘#ef476f’,’#f78c6b’,’#ffd166′,’#06d6a0′,’#118ab2′,’#7353ba’],
[‘#22c55e’,’#06b6d4′,’#3b82f6′,’#8b5cf6′,’#ec4899′,’#f59e0b’]
].filter(x=>x.length>2);
let colors=palettes[0];
let entries=raw.split(‘~’).map(v=>{try{return decodeURIComponent(v)}catch(e){return v}}).filter(Boolean).map(v=>{
const p=v.split(‘|’);
const weight=mode===’weighted’?Math.max(1,Number(p.pop())||1):1;
return{name:p.join(‘|’),weight,count:0,last:-999,image:”};
});
let angle=0,busy=false,history=[],undo=[],redo=[],spinNumber=0,lastReceipt=null,preSpin=[];
let backgroundImage=”,centerImage=”,backgroundOpacity=.45,centerRadius=76,pendingEntryImage=”,pendingEntryFilename=”;
const imageCache=new Map();
const canvas=$(‘.wnx-canvas’),ctx=canvas.getContext(‘2d’),spin=$(‘.wnx-spin’),result=$(‘.wnx-result’),list=$(‘.wnx-list’);
const elimination=$(‘.wnx-elimination’),accumulation=$(‘.wnx-accumulation’),auto=$(‘.wnx-auto’),winnerCount=$(‘.wnx-winner-count’),duration=$(‘.wnx-duration’);
const balance=$(‘.wnx-smart-balance’),cooldown=$(‘.wnx-smart-cooldown’);
$(‘.wnx-title’).textContent=title;
elimination.checked=mode===’norepeat’||params.get(‘wnremove’)===’1′;
winnerCount.value=mode===’multi’?Math.max(1,Number(cfg?.dataset.count||r.dataset.count)||3):1;
$(‘.wnx-multi-field’).classList.toggle(‘wnx-hidden’,mode!==’multi’);
const escapeHtml=s=>String(s).replace(/[&”‘]/g,m=>({‘&’:’&’,”:’>’,'”‘:’"’,”‘”:’'’}[m]));
const snapshot=()=>JSON.stringify(entries);
const save=()=>{undo.push(snapshot());if(undo.length>25)undo.shift();redo=[];};
const randomInt=n=>{if(n=limit);return a[0]%n;};
function getImage(src){
if(!src)return null;
if(imageCache.has(src))return imageCache.get(src);
const img=new Image();
img.onload=draw;
img.src=src;
imageCache.set(src,img);
return img;
}
function drawCover(img,x,y,w,h){
if(!img||!img.complete||!img.naturalWidth)return;
const scale=Math.max(w/img.naturalWidth,h/img.naturalHeight);
const dw=img.naturalWidth*scale,dh=img.naturalHeight*scale;
ctx.drawImage(img,x+(w-dw)/2,y+(h-dh)/2,dw,dh);
}
function readOptimizedImage(file,maxSize=1000){
return new Promise((resolve,reject)=>{
if(!file||!file.type.startsWith(‘image/’)){reject(new Error(‘Please choose an image file.’));return;}
const reader=new FileReader();
reader.onerror=()=>reject(new Error(‘The image could not be read.’));
reader.onload=()=>{
const img=new Image();
img.onerror=()=>reject(new Error(‘The image format is not supported.’));
img.onload=()=>{
const scale=Math.min(1,maxSize/Math.max(img.naturalWidth,img.naturalHeight));
const w=Math.max(1,Math.round(img.naturalWidth*scale)),h=Math.max(1,Math.round(img.naturalHeight*scale));
const temp=document.createElement(‘canvas’);temp.width=w;temp.height=h;
const t=temp.getContext(‘2d’);t.drawImage(img,0,0,w,h);
const type=file.type===’image/png’?’image/png’:’image/jpeg’;
resolve(temp.toDataURL(type,type===’image/jpeg’?.86:undefined));
};
img.src=String(reader.result||”);
};
reader.readAsDataURL(file);
});
}
const smartOn=()=>!elimination.checked&&(balance.checked||Number(cooldown.value)>0);
function eligibleEntries(){
let pool=[…entries];
if(!smartOn())return pool;
const cd=Number(cooldown.value);
if(cd>0){const cooled=pool.filter(e=>spinNumber-e.last>=cd);if(cooled.length)pool=cooled;}
if(balance.checked&&pool.length){const min=Math.min(…pool.map(e=>e.count));pool=pool.filter(e=>e.count===min);}
return pool.length?pool:[…entries];
}
function segments(){
const total=entries.reduce((s,e)=>s+e.weight,0)||1;
let used=0;
return entries.map(entry=>{const start=used/total*Math.PI*2;used+=entry.weight;return{entry,start,end:used/total*Math.PI*2};});
}
function draw(){
const size=900,center=450,radius=418,segs=segments(),allowed=new Set(eligibleEntries());
ctx.clearRect(0,0,size,size);
if(backgroundImage){
const img=getImage(backgroundImage);
ctx.save();ctx.beginPath();ctx.arc(center,center,radius,0,Math.PI*2);ctx.clip();ctx.globalAlpha=backgroundOpacity;drawCover(img,center-radius,center-radius,radius*2,radius*2);ctx.restore();
}
if(!segs.length){
ctx.beginPath();ctx.arc(center,center,radius,0,Math.PI*2);ctx.fillStyle=’#0b1628′;ctx.fill();ctx.strokeStyle=’rgba(129,140,248,.24)’;ctx.lineWidth=6;ctx.stroke();ctx.fillStyle=’#94a3b8′;ctx.font=’800 34px system-ui’;ctx.textAlign=’center’;ctx.fillText(‘Add entries’,center,center);return;
}
segs.forEach((seg,index)=>{
const dim=smartOn()&&!allowed.has(seg.entry);
const start=seg.start+angle,end=seg.end+angle;
ctx.save();
ctx.globalAlpha=dim?.2:1;
ctx.beginPath();ctx.moveTo(center,center);ctx.arc(center,center,radius,start,end);ctx.closePath();
if(seg.entry.image){
ctx.save();ctx.clip();drawCover(getImage(seg.entry.image),center-radius,center-radius,radius*2,radius*2);ctx.restore();
ctx.globalAlpha=dim?.12:.22;ctx.fillStyle=colors[index%colors.length];ctx.fill();ctx.globalAlpha=dim?.2:1;
}else{
ctx.fillStyle=colors[index%colors.length];
if(backgroundImage)ctx.globalAlpha=dim?.14:.72;
ctx.fill();ctx.globalAlpha=dim?.2:1;
}
ctx.strokeStyle=’rgba(255,255,255,.82)’;ctx.lineWidth=5;ctx.stroke();
const mid=(seg.start+seg.end)/2+angle;
ctx.translate(center,center);ctx.rotate(mid);ctx.textAlign=’right’;ctx.fillStyle=’#fff’;ctx.shadowColor=’rgba(0,0,0,.55)’;ctx.shadowBlur=8;
ctx.font=`850 ${Math.max(18,Math.min(33,350/entries.length+16))}px system-ui`;
const label=seg.entry.name.length>22?seg.entry.name.slice(0,20)+’…’:seg.entry.name;
ctx.fillText(label,radius-34,10);ctx.restore();
});
ctx.save();ctx.beginPath();ctx.arc(center,center,centerRadius,0,Math.PI*2);ctx.clip();
if(centerImage){drawCover(getImage(centerImage),center-centerRadius,center-centerRadius,centerRadius*2,centerRadius*2);}else{ctx.fillStyle=’#0b1628′;ctx.fillRect(center-centerRadius,center-centerRadius,centerRadius*2,centerRadius*2);}
ctx.restore();ctx.beginPath();ctx.arc(center,center,centerRadius,0,Math.PI*2);ctx.strokeStyle=’rgba(255,255,255,.24)’;ctx.lineWidth=10;ctx.stroke();
}
function updateBadges(){
[[ $(‘.wnx-badge-elimination’),elimination,’Elimination’],[$(‘.wnx-badge-accumulation’),accumulation,’Accumulation’],[$(‘.wnx-badge-auto’),auto,’Auto-Spin’],[$(‘.wnx-badge-smart’),{checked:smartOn()},’Fair Rotation’]].forEach(([el,input,label])=>{el.textContent=`${label}: ${input.checked?’ON’:’OFF’}`;el.classList.toggle(‘is-on’,input.checked);});
}
function balanceScore(){
if(!entries.length)return 100;
const total=entries.reduce((s,e)=>s+e.count,0),mean=total/entries.length;
if(!mean)return 100;
const deviation=entries.reduce((s,e)=>s+Math.abs(e.count-mean),0)/entries.length;
return Math.max(0,Math.round(100-deviation/mean*100));
}
function updateDashboard(){
$(‘.wnx-balance-score’).textContent=balanceScore()+’%’;$(‘.wnx-smart-spins’).textContent=String(spinNumber);
$(‘.wnx-smart-counts’).innerHTML=[…entries].sort((a,b)=>a.count-b.count||a.name.localeCompare(b.name)).map(e=>`
${escapeHtml(e.name)}${e.count}
`).join(”)||’
No entries yet.
‘;
const status=$(‘.wnx-smart-status’);
if(elimination.checked&&(balance.checked||Number(cooldown.value)>0))status.textContent=’Smart Fair Rotation is paused while Elimination mode is active.’;
else if(!smartOn())status.textContent=’Pure random mode is active.’;
else{const parts=[];if(balance.checked)parts.push(‘least-selected priority’);if(Number(cooldown.value)>0)parts.push(`${cooldown.value}-spin cooldown`);status.textContent=`Smart mode active: ${parts.join(‘ + ‘)}. Dimmed slices are temporarily ineligible.`;}
}
function render(){
list.innerHTML=entries.map((entry,index)=>`
Family Chore Wheel is a free browser-based household chore rotator for sharing suitable recurring responsibilities without immediate repeats. Edit the sample entries, confirm the eligible list, choose the mode that matches the activity, and spin for a visible random result. Match tasks to age, ability, schedule, equipment, and safety; the result starts a fair conversation and should not override reasonable adjustments.
Write tasks in clear, small steps. Remove jobs involving dangerous chemicals, hot equipment, heavy lifting, heights, sharp tools, traffic, or anything beyond the person’s age, ability, training, or supervision.
Define “done”
Explain the area, standard, supplies, and approximate time for each task.
Provide the tools
Make safe equipment available and show how it should be used.
Balance effort
Break large jobs into smaller parts so one spin does not create an unfair burden.
Use a rotation, not a surprise penalty
Agree on the list
Review the chores together and remove anything unsuitable before spinning.
Assign or pair people separately
Use a name wheel, schedule, or family agreement to decide who completes each task.
Allow reasonable swaps
Support changes when homework, health, age, access, or other responsibilities differ.