.cm-wrap { position: relative; width: 100%; max-width: 1100px; margin: 0 auto; aspect-ratio: 16/9; background:#fafafa; border:1px solid #e6e6e6; border-radius:16px; overflow:hidden; }
/* Top bar with jumbo chips + reset (chips are ~3×) */
.cm-topbar { position:absolute; inset:0 0 auto 0; height:140px; display:grid; grid-template-columns: 1fr auto; align-items:center; padding:8px 12px; pointer-events:none; }
.cm-palette { display:flex; gap:12px; padding-left:6px; pointer-events:auto; }
.cm-chip { width:120px; height:120px; border-radius:14px; background:#fff; border:1px solid #ddd; display:grid; place-items:center; cursor:grab; box-shadow:0 1px 2px rgba(0,0,0,.06); }
.cm-chip:active { cursor:grabbing; }
.cm-reset { pointer-events:auto; user-select:none; font-family:system-ui, sans-serif; font-size:14px; padding:8px 14px; border-radius:999px; border:1px solid #ddd; background:#fff; box-shadow:0 1px 2px rgba(0,0,0,.06); cursor:pointer; }
.cm-reset:active { transform: translateY(1px); }
/* Stage and zones */
.cm-stage { position:absolute; inset:140px 0 0 0; }
.cm-brainstorm { fill:#e9e9ee; stroke:#d5d5db; }
.cm-guide { stroke:#bdbdbd; stroke-dasharray:8 10; fill:none; }
/* Bin */
.cm-bin { position:absolute; left:12px; bottom:12px; width:56px; height:56px; border-radius:999px; border:1px solid #ddd; background:#fff; display:grid; place-items:center; box-shadow:0 1px 2px rgba(0,0,0,.08); }
.cm-bin svg { width:28px; height:28px; opacity:.85; }
.cm-bin.cm-hot { outline:3px dashed #222; }
/* Draggables / shapes */
.cm-draggable { cursor:grab; }
.cm-draggable:active { cursor:grabbing; }
.cm-shape { stroke:#111; stroke-width:2; }

Reset

(() => {
const STAGE = document.getElementById(‘cm-stage’);
const PALETTE = document.getElementById(‘cm-palette’);
const RESET = document.getElementById(‘cm-reset’);
const BIN = document.getElementById(‘cm-bin’);
const BRAIN = document.getElementById(‘cm-brainstorm’);

const BASE = [
{ name:’circle’, color:’#e63946′ },
{ name:’square’, color:’#ffd166′ },
{ name:’triangle’, color:’#118ab2′ },
{ name:’star’, color:’#ffffff’ },
{ name:’trapezoid’, color:’#000000′ }
];

const N = 128;
const FIXED_SIZE = 50;
const COMBINE_RADIUS = FIXED_SIZE * 1.2;

const hexToRgb = (hex) => { let h=hex.replace(‘#’,”); if(h.length===3) h=h.split(”).map(c=>c+c).join(”); const n=parseInt(h,16); return {r:(n>>16)&255,g:(n>>8)&255,b:n&255}; };
const rgbToHex = (r,g,b) => ‘#’ + [r,g,b].map(v=>Math.max(0,Math.min(255,Math.round(v))).toString(16).padStart(2,’0′)).join(”);
const isBW = (x)=>/^(#?000000|#?000|#?ffffff|#?fff)$/i.test(x||”);
function mix50(a,b){ const A=hexToRgb(a), B=hexToRgb(b); if(isBW(a)||isBW(b)){ const t=isBW(a)?A:B, f=isBW(a)?B:A; return rgbToHex(f.r+0.5*(t.r-f.r), f.g+0.5*(t.g-f.g), f.b+0.5*(t.b-f.b)); } return rgbToHex((A.r+B.r)/2,(A.g+B.g)/2,(A.b+B.b)/2); }

function dCircle(){ const r=FIXED_SIZE*0.9,k=0.5522847498,ox=r*k,oy=r*k,cx=0,cy=0; return `M ${cx-r} ${cy} C ${cx-r} ${cy-oy} ${cx-ox} ${cy-r} ${cx} ${cy-r} C ${cx+ox} ${cy-r} ${cx+r} ${cy-oy} ${cx+r} ${cy} C ${cx+r} ${cy+oy} ${cx+ox} ${cy+r} ${cx} ${cy+r} C ${cx-ox} ${cy+r} ${cx-r} ${cy+oy} ${cx-r} ${cy} Z`; }
function dSquare(){ const s=FIXED_SIZE; return `M ${-s} ${-s} L ${s} ${-s} L ${s} ${s} L ${-s} ${s} Z`; }
function dTriangle(){ const h=FIXED_SIZE*1.2,w=FIXED_SIZE*1.0; return `M 0 ${-h} L ${w} ${h} L ${-w} ${h} Z`; }
function dStar(){ const o=FIXED_SIZE*1.2,i=FIXED_SIZE*0.5; let p=”; for(let k=0;k{ const pt=pathEl.getPointAtLength(t*len); return [pt.x, pt.y]; }; }
function smoothClosed(pts){ const n=pts.length; let d=”; for(let i=0;i<n;i++){ const p0=pts[(i-1+n)%n], p1=pts[i], p2=pts[(i+1)%n], p3=pts[(i+2)%n]; if(i===0) d+=`M ${p1[0]} ${p1[1]}`; const c1=[p1[0]+(p2[0]-p0[0])/6, p1[1]+(p2[1]-p0[1])/6]; const c2=[p2[0]-(p3[0]-p1[0])/6, p2[1]-(p3[1]-p1[1])/6]; d+=` C ${c1[0]} ${c1[1]} ${c2[0]} ${c2[1]} ${p2[0]} ${p2[1]}`; } return d+' Z'; }
function morphToPath(sA,sB){ const pts=[]; for(let i=0;i<N;i++){ const t=i/N; const a=sA(t), b=sB(t); pts.push([(a[0]+b[0])/2,(a[1]+b[1])/2]); } return smoothClosed(pts); }
function wobbleVariantFromSampler(s,str=0.12){ const freq=3+Math.floor(Math.random()*4), phase=Math.random()*Math.PI*2; const pts=[]; for(let i=0;istartDrag(g,e)); g.addEventListener(‘dblclick’,(e)=>{ e.preventDefault(); const d=g.querySelector(‘path’).getAttribute(‘d’); spawnShape(g.dataset.shape, g.dataset.color, +g.dataset.x+24, +g.dataset.y+24, d); }); return g; }

let drag=null;
function svgPoint(x,y){ const pt=STAGE.createSVGPoint(); pt.x=x; pt.y=y; return pt.matrixTransform(STAGE.getScreenCTM().inverse()); }
function startDrag(node,e){ e.preventDefault(); const s=svgPoint(e.clientX,e.clientY); drag={node,sx:s.x,sy:s.y,ox:+node.dataset.x,oy:+node.dataset.y}; node.setPointerCapture(e.pointerId); const move=(ev)=>{ const p=svgPoint(ev.clientX,ev.clientY); const x=drag.ox+(p.x-drag.sx), y=drag.oy+(p.y-drag.sy); node.dataset.x=x; node.dataset.y=y; node.setAttribute(‘transform’,`translate(${x},${y})`); toggleBinHot(ev); }; const up=(ev)=>{ node.releasePointerCapture(ev.pointerId); window.removeEventListener(‘pointermove’,move); window.removeEventListener(‘pointerup’,up); const over=binOver(ev); BIN.classList.remove(‘cm-hot’); if(over){ node.remove(); return; } combineIfClose(node); }; window.addEventListener(‘pointermove’,move); window.addEventListener(‘pointerup’,up); }
function binRect(){ return BIN.getBoundingClientRect(); }
function binOver(ev){ const r=binRect(); return ev.clientX>=r.left && ev.clientX=r.top && ev.clientY=bx && x=by && yn!==node); const c1=nodeCenter(node); let best=null, bestD=Infinity; for(const o of others){ const d=dist(c1,nodeCenter(o)); if(d<COMBINE_RADIUS && d{ const chip=document.createElement(‘div’); chip.className=’cm-chip’; chip.dataset.color=b.color; chip.dataset.shape=b.name; const svg=document.createElementNS(‘http://www.w3.org/2000/svg&#8217;,’svg’); svg.setAttribute(‘viewBox’,’-200 -200 400 400′); svg.setAttribute(‘width’,’120′); svg.setAttribute(‘height’,’120′); const p=document.createElementNS(‘http://www.w3.org/2000/svg&#8217;,’path’); p.setAttribute(‘d’, (BASE_PATH[b.name]||dCircle)()); p.setAttribute(‘fill’, b.color); p.setAttribute(‘class’,’cm-shape’); p.setAttribute(‘transform’,’scale(1.6)’); svg.appendChild(p); chip.appendChild(svg); chip.addEventListener(‘pointerdown’,(e)=>{ e.preventDefault(); const pt=svgPoint(e.clientX,e.clientY); spawnShape(b.name, b.color, pt.x, pt.y); }); PALETTE.appendChild(chip); }); }

function clearStage(){ STAGE.innerHTML = `

`;
const X=180; const ys=[160,300,440,580,720];
BASE.forEach((b,i)=> spawnShape(b.name, b.color, X + (i%2)*40, ys[i]));
}

RESET.addEventListener(‘click’, clearStage);
buildPalette();
clearStage();
})();