Random Choice Picker Wheel turns a shortlist of acceptable options into a visible decision spinner. Add only choices you can genuinely accept, remove unavailable options, and spin to break a low-risk tie.
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)=>`
A Random Choice Picker Wheel selects one active option from your list. In standard mode every unique entry has an equal chance; duplicate rows or weights change that probability.
How to use the Random Choice Picker Wheel
Replace the example entries
Add the exact choices that meet your activity rules, or keep the prepared list when it already matches your goal.
Review the active list and mode
Remove unavailable entries, check duplicates, and choose standard or elimination according to whether repeats are allowed.
Spin and confirm the result
Press Spin the Wheel, read the text result, and record it when another person or later task depends on the outcome.
Best uses for this Random Choice Picker Wheel
Everyday choices
Choose an activity, errand order, destination, meal category, or entertainment option.
Group decisions
Let a team or family choose among pre-approved ideas without repeating the same debate.
Creative work
Select a theme, constraint, topic, color direction, or next small action.
Backlog order
Randomize comparable low-risk tasks when priority and urgency are already equal.
Random Choice Picker Wheel setup tips
Add only options that meet cost, safety, time, and availability limits.
Use short labels so every wheel slice remains readable.
Remove duplicate choices unless extra chances are intentional.
Agree on a redraw rule before spinning.
Responsible use: A random wheel is suitable for low-risk choices and prepared activities. It should not replace consent, safety checks, accessibility adjustments, professional judgment, or legal requirements.