27:18Gemini 4 Hydrogen: Cheaper Tokens, More Expensive Answers?
Nov 13, 2026 · YouTube
18:40ERNIE 5.5 Flash Preview vs GLM 5.3 Flash: Baidu's new model is very impressive!
Nov 11, 2026 · YouTube
36:22Fable 5.2 robbing me blind, plus: Nex AGI's N2.6 vs GLM 5.3 Flash
Nov 11, 2026 · YouTube
14:03Gemini 4 Carbon: Can it handle my challenges? (First look with one-shot coding tests)
Nov 10, 2026 · YouTube
10:57"Crouton Alpha": the latest super-efficient coding model by Meta?
Nov 3, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
22:14GPT-6.1 Terra Pro vs Terra: How Much Better Is Pro?
Oct 27, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
You build physics toys, and you have watched someone try the tablecloth trick at a family dinner.
Build me a single HTML page: a table set for dinner, and a tablecloth I can yank out from under it.
#speed=12 sets the pull speed to 12 ft/s and pulls one second after load. Any number works.data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file: the lowest pull speed at which every object stays on the table, and the speed below which no object stays on the table, as two numbers in ft/s. State the arithmetic using your masses, friction coefficients, and cloth length.
The single fenced html block.
Where it behaves like the physical trick and where it does not. Name any special cases, scripted wobbles, or fall thresholds. Everyone has seen this trick fail; say how yours fails before I find out.
With g=32.2 ft/s², cloth contact μs≈0.30–0.50 and μk≈0.22–0.40 with mean 0.30, bare-table μ≈0.35, cloth length 7 ft starting at x=-0.5 ft over a 6-ft table so uncover travel is s=x+0.5 ft, and weights bowl 2.0 lb, plates 1.2 and 1.0 lb, glasses 0.6 and 0.5 lb, candlestick 0.8 lb whose masses cancel in sliding acceleration a=μg, the slipping impulse is Δv=μk g s/V and the later table stopping distance is Δv²/(2μtable g) which must be less than edge distance 6-x, giving V>μk(x+0.5)√(g/(2μtable(6-x))), largest for the right plate at x=4.8 ft at about 9.8 ft/s and smallest for the left bowl at x=0.8 ft at about 1.2 ft/s, so the lowest pull that keeps every object on the table is about 9.8 ft/s and below about 1.2 ft/s no object stays on the table.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Tablecloth Trick — Physics Toy</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#1a1210;color:#f5e9d6;font-family:Georgia,serif;display:flex;flex-direction:column;align-items:center;min-height:100vh}
h1{margin:14px 8px 4px;font-size:26px;letter-spacing:.5px;color:#ffd98a;text-align:center}
p.sub{margin:0 12px 10px;opacity:.85;text-align:center;max-width:760px;font-size:14px}
#wrap{width:min(960px,96vw);background:#2a1d16;border:2px solid #6b4a2e;border-radius:14px;padding:10px;box-shadow:0 10px 40px #000}
#controls{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:center;background:#3a271b;border-radius:10px;padding:10px}
#controls label{font-size:15px}
#speedVal{font-weight:bold;color:#ffd98a;min-width:90px;display:inline-block}
input[type=range]{width:220px;accent-color:#e8a33d}
button{font-family:inherit;font-size:16px;padding:8px 16px;border-radius:10px;border:2px solid #7a5326;background:#e8a33d;color:#2a1600;cursor:pointer;font-weight:bold}
button:hover{background:#ffbe55}
button:disabled{opacity:.45;cursor:default}
#resetBtn{background:#d9d0bf}
#status{min-height:28px;text-align:center;margin:8px;font-size:17px;color:#c8f0c8}
canvas{width:100%;height:480px;display:block;background:#0f0b09;border-radius:10px;border:1px solid #000}
.legend{font-size:12.5px;opacity:.8;text-align:center;margin-top:6px}
</style>
</head>
<body>
<h1>The Tablecloth Trick</h1>
<p class="sub">Side view physics: friction drags, inertia saves. Slow yank = dinner off the edge. Fast yank = dinner stays. In between, some slide, some tip.</p>
<div id="wrap">
<div id="controls">
<label>Pull speed: <span id="speedVal">8.0 ft/s</span></label>
<input id="speed" type="range" min="1" max="20" step="0.5" value="8">
<button id="yankBtn">YANK!</button>
<button id="resetBtn">Reset</button>
</div>
<div id="status">Set a speed and yank the cloth →</div>
<canvas id="c" width="960" height="480"></canvas>
<div class="legend">6 pieces: bowl • dinner plate • water glass • candlestick • wine glass • side plate — mass = shade, width = contact. Candle tips first, plates slide last.</div>
</div>
<script>
(function(){
"use strict";
var cv=document.getElementById('c'),ctx=cv.getContext('2d');
var speedEl=document.getElementById('speed'),speedVal=document.getElementById('speedVal');
var yankBtn=document.getElementById('yankBtn'),resetBtn=document.getElementById('resetBtn'),statusEl=document.getElementById('status');
var G=32.2,TW=6,TH=2.6,TY=0,FY=-TH,CL=7,CX0=-0.5,CTH=0.035;
var BASE=[
{type:'bowl', x:0.8, w:0.75,h:0.38,wt:2.0, muS:0.35,muK:0.28,muT:0.40,col:'#e07a3f'},
{type:'plate',x:1.7, w:1.00,h:0.14,wt:1.2, muS:0.30,muK:0.22,muT:0.35,col:'#f2ede2'},
{type:'glass',x:2.55,w:0.30,h:0.70,wt:0.6, muS:0.45,muK:0.35,muT:0.40,col:'#9fd4ff'},
{type:'candle',x:3.25,w:0.24,h:1.05,wt:0.8,muS:0.50,muK:0.40,muT:0.45,col:'#f5f0dc'},
{type:'wine', x:4.05,w:0.34,h:0.82,wt:0.5, muS:0.42,muK:0.32,muT:0.40,col:'#c99fff'},
{type:'plate2',x:4.85,w:0.90,h:0.14,wt:1.0,muS:0.30,muK:0.24,muT:0.35,col:'#f2ede2'}
];
var objs=[],cloth={s:0,v:0,target:8,pulling:false,out:false,drop:0,dropV:0};
var simTime=0,pulled=false,done=false,stillT=0;
function makeObjs(){
objs=BASE.map(function(b){
var m=b.wt/G,I=m*(b.w*b.w+b.h*b.h)/12;
return {type:b.type,x:b.x,w:b.w,h:b.h,wt:b.wt,m:m,invM:1/m,I:I,invI:1/I,
muS:b.muS,muK:b.muK,muT:b.muT,col:b.col,
y:TY+CTH+b.h/2+0.002,vx:0,vy:0,a:0,om:0};
});
cloth={s:0,v:0,target:parseFloat(speedEl.value),pulling:false,out:false,drop:0,dropV:0};
simTime=0;pulled=false;done=false;stillT=0;
yankBtn.disabled=false;
statusEl.textContent='Set a speed and yank the cloth →';
}
function covers(x){var L=CX0+cloth.s,R=L+CL;return x>=L&&x<=R;}
function corners(o){
var c=Math.cos(o.a),s=Math.sin(o.a),r=[];
var pts=[[o.w/2,o.h/2],[o.w/2,-o.h/2],[-o.w/2,-o.h/2],[-o.w/2,o.h/2]];
for(var i=0;i<4;i++){var lx=pts[i][0],ly=pts[i][1];
var rx=lx*c-ly*s,ry=lx*s+ly*c;
r.push({rx:rx,ry:ry,cx:o.x+rx,cy:o.y+ry});}
return r;
}
var KP=8000,KD=26;
function supportContact(o,dt){
var cs=corners(o);
for(var i=0;i<cs.length;i++){
var cr=cs[i];
var useTable=(cr.cx>=-0.02&&cr.cx<=TW+0.02&&o.y>-1.0);
var ySup,surfV,muS,muK;
if(useTable){
var cov=covers(cr.cx)&&!cloth.out;
if(cov){ySup=TY+CTH;surfV=cloth.v;muS=o.muS;muK=o.muK;}
else{ySup=TY;surfV=0;muS=o.muT+0.06;muK=o.muT;}
}else{
ySup=FY;surfV=0;muS=o.muT+0.12;muK=o.muT+0.02;
if(cr.cy>ySup+0.6) continue;
}
if(cr.cy>=ySup) continue;
var pen=ySup-cr.cy;
var vcx=o.vx-o.om*cr.ry, vcy=o.vy+o.om*cr.rx;
var N=KP*pen-KD*vcy;
if(N<0)N=0;
if(N===0)continue;
var Jy=N*dt;
o.vy+=Jy*o.invM;
o.om+=o.invI*(cr.rx*Jy);
// refresh corner vx after normal
vcx=o.vx-o.om*cr.ry;
var vrel=vcx-surfV;
var Jneed=-vrel*o.m;
var Jx;
if(Math.abs(Jneed)<=muS*Jy){Jx=Jneed;}
else{Jx=(vrel>0?-1:1)*muK*Jy;}
o.vx+=Jx*o.invM;
o.om+=o.invI*(-cr.ry*Jx);
}
}
function collidePair(a,b){
var ax=[{x:Math.cos(a.a),y:Math.sin(a.a)},{x:-Math.sin(a.a),y:Math.cos(a.a)}];
var bx=[{x:Math.cos(b.a),y:Math.sin(b.a)},{x:-Math.sin(b.a),y:Math.cos(b.a)}];
var ca=corners(a),cb=corners(b);
var axes=ax.concat(bx),best=1e9,bn=null;
for(var k=0;k<4;k++){
var nx=axes[k].x,ny=axes[k].y;
var mna=1e9,mxa=-1e9,mnb=1e9,mxb=-1e9,i;
for(i=0;i<4;i++){var da=ca[i].cx*nx+ca[i].cy*ny;if(da<mna)mna=da;if(da>mxa)mxa=da;}
for(i=0;i<4;i++){var db=cb[i].cx*nx+cb[i].cy*ny;if(db<mnb)mnb=db;if(db>mxb)mxb=db;}
var ov=Math.min(mxa,mxb)-Math.max(mna,mnb);
if(ov<0)return;
if(ov<best){best=ov;bn={x:nx,y:ny};}
}
var dx=b.x-a.x,dy=b.y-a.y;
if(dx*bn.x+dy*bn.y<0){bn.x=-bn.x;bn.y=-bn.y;}
var px=(a.x+b.x)/2,py=(a.y+b.y)/2;
var r1x=px-a.x,r1y=py-a.y,r2x=px-b.x,r2y=py-b.y;
var v1x=a.vx-a.om*r1y,v1y=a.vy+a.om*r1x;
var v2x=b.vx-b.om*r2y,v2y=b.vy+b.om*r2x;
var rvx=v1x-v2x,rvy=v1y-v2y;
var vn=rvx*bn.x+rvy*bn.y;
var totInv=a.invM+b.invM;
if(totInv===0)return;
// positional
var corr=best*0.8;
a.x-=bn.x*corr*(a.invM/totInv);a.y-=bn.y*corr*(a.invM/totInv);
b.x+=bn.x*corr*(b.invM/totInv);b.y+=bn.y*corr*(b.invM/totInv);
if(vn<0){
var c1=r1x*bn.y-r1y*bn.x,c2=r2x*bn.y-r2y*bn.x;
var den=totInv+a.invI*c1*c1+b.invI*c2*c2;
var e=0.18,j=-(1+e)*vn/den;
a.vx+=j*bn.x*a.invM;a.vy+=j*bn.y*a.invM;a.om+=a.invI*c1*j;
b.vx-=j*bn.x*b.invM;b.vy-=j*bn.y*b.invM;b.om-=b.invI*c2*j;
var tx=-bn.y,ty=bn.x;
var vt=rvx*tx+rvy*ty;
var ct1=r1x*ty-r1y*tx,ct2=r2x*ty-r2y*tx;
var dent=totInv+a.invI*ct1*ct1+b.invI*ct2*ct2;
var jt=-vt/dent,mu=0.32,max=j*mu;
if(jt>max)jt=max;if(jt<-max)jt=-max;
a.vx+=jt*tx*a.invM;a.vy+=jt*ty*a.invM;a.om+=a.invI*ct1*jt;
b.vx-=jt*tx*b.invM;b.vy-=jt*ty*b.invM;b.om-=b.invI*ct2*jt;
}
}
function step(dt){
simTime+=dt;
if(cloth.pulling&&!cloth.out){
var k=Math.min(1,dt/0.05);
cloth.v+=(cloth.target-cloth.v)*k;
cloth.s+=cloth.v*dt;
if(CX0+cloth.s>TW){cloth.out=true;cloth.drop=0;cloth.dropV=0;}
}else if(cloth.out){
cloth.s+=cloth.v*dt;
cloth.v*=(1-0.25*dt);
cloth.dropV+=G*dt;
cloth.drop+=cloth.dropV*dt;
if(cloth.drop>TH-0.05){cloth.drop=TH-0.05;cloth.dropV=0;cloth.v*=(1-2*dt);}
}
var i;
for(i=0;i<objs.length;i++){var o=objs[i];o.vy-=G*dt;}
for(i=0;i<objs.length;i++){supportContact(objs[i],dt);}
for(i=0;i<objs.length;i++){var p=objs[i];
p.x+=p.vx*dt;p.y+=p.vy*dt;p.a+=p.om*dt;
p.vx*=(1-0.02*dt);p.om*=(1-0.6*dt);
if(p.y<FY+p.h/2){p.y=FY+p.h/2;if(p.vy<-3)p.vy=-p.vy*0.22;else if(p.vy<0)p.vy=0;
if(Math.abs(p.vx)>0.01){var sgn=p.vx>0?1:-1;var dv=sgn*(p.muT*G*dt);if(Math.abs(dv)>Math.abs(p.vx))p.vx=0;else p.vx-=dv;}
p.om*=(1-3*dt);}
if(p.a>Math.PI)p.a-=2*Math.PI;if(p.a<-Math.PI)p.a+=2*Math.PI;
}
for(i=0;i<objs.length;i++)for(var j=i+1;j<objs.length;j++)collidePair(objs[i],objs[j]);
}
function normA(a){while(a>Math.PI)a-=2*Math.PI;while(a<-Math.PI)a+=2*Math.PI;return a;}
function counts(){
var on=0,up=0,both=0;
for(var i=0;i<objs.length;i++){var o=objs[i];
var isOn=(o.x>-0.25&&o.x<TW+0.25&&o.y>-0.6);
var isUp=Math.abs(normA(o.a))<0.6;
if(isOn)on++;if(isUp)up++;if(isOn&&isUp)both++;}
return {on:on,up:up,both:both,n:objs.length};
}
function checkDone(){
if(!pulled||done)return;
var mx=0,mo=0,i;
for(i=0;i<objs.length;i++){mx=Math.max(mx,Math.abs(objs[i].vx),Math.abs(objs[i].vy));mo=Math.max(mo,Math.abs(objs[i].om));}
if((mx<0.18&&mo<0.6&&(cloth.out||cloth.v<0.05))||simTime>22){
stillT++;if(stillT>90||simTime>22){
done=true;var c=counts();
statusEl.textContent='Settled: '+c.on+' / '+c.n+' still on the table • '+c.up+' / '+c.n+' upright • '+c.both+' on table AND upright.';
yankBtn.disabled=true;
}
}else stillT=0;
}
// rendering
function w2s(x,y,W,H){
var x0=-2.6,x1=9.2,y0=-3.4,y1=2.6;
var sx=(x-x0)/(x1-x0)*W, sy=H-(y-y0)/(y1-y0)*H;
var sc=W/(x1-x0);
return [sx,sy,sc];
}
var flameT=0;
function draw(){
var W=cv.width,H=cv.height;
flameT+=0.05;
var g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,'#3d2a4a');g.addColorStop(0.55,'#241a20');g.addColorStop(1,'#0f0b09');
ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
// floor
var f0=w2s(0,FY,W,H),f1=w2s(1,FY,W,H);var sc=f1[2];
var floorY=f0[1];
ctx.fillStyle='#5a3d24';ctx.fillRect(0,floorY,W,H-floorY);
ctx.fillStyle='#6e4c2c';
for(var fx=0;fx<W;fx+=46)ctx.fillRect(fx,floorY+((fx/46)%2)*14,W,2);
// rug
ctx.fillStyle='#7a2e2e';ctx.fillRect(W*0.08,floorY+18,W*0.84,26);
ctx.fillStyle='#c9a86a';ctx.fillRect(W*0.08,floorY+18,W*0.84,4);ctx.fillRect(W*0.08,floorY+40,W*0.84,4);
// table
function P(x,y){return w2s(x,y,W,H);}
var t0=P(0,TY),t1=P(TW,TY-0.16),legB=P(0,FY);
ctx.fillStyle='#2b1a0e';
ctx.fillRect(t0[0],t0[1],t1[0]-t0[0],(P(0,FY)[1]-t0[1]));
ctx.fillStyle='#8a5a2e';
ctx.fillRect(t0[0],t0[1]-8,t1[0]-t0[0],12);
ctx.fillRect(t0[0]+8,t0[1],14,legB[1]-t0[1]);
ctx.fillRect(t1[0]-22,t0[1],14,legB[1]-t0[1]);
// cloth
var L=CX0+cloth.s,R=L+CL;
var dropPx=cloth.drop*sc;
ctx.save();
// flat part on table
var cx0=Math.max(0,L),cx1=Math.min(TW,R);
if(cx1>cx0&&!cloth.out){
var a=P(cx0,TY+CTH),b=P(cx1,TY+CTH);
ctx.fillStyle='#c8352e';
ctx.fillRect(a[0],a[1]-6,b[0]-a[0],7);
ctx.fillStyle='rgba(255,255,255,.85)';
var stepPx=22;
for(var px=a[0];px<b[0];px+=stepPx)ctx.fillRect(px,a[1]-6,10,7);
}
// right overhang / flying cloth
if(!cloth.out&&R>TW){
var e0=P(TW,TY),e1=P(R,TY);
ctx.strokeStyle='#c8352e';ctx.lineWidth=7;ctx.beginPath();
ctx.moveTo(e0[0],e0[1]-4);
var hang=(R-TW)*sc*0.55;
ctx.quadraticCurveTo(e0[0]+(e1[0]-e0[0])*0.5,e0[1]-4+hang,e1[0],e0[1]-4+hang*0.7);
ctx.stroke();
ctx.strokeStyle='rgba(255,255,255,.7)';ctx.lineWidth=2;ctx.beginPath();
ctx.moveTo(e0[0],e0[1]-4);
ctx.quadraticCurveTo(e0[0]+(e1[0]-e0[0])*0.5,e0[1]-2+hang,e1[0],e0[1]-2+hang*0.7);
ctx.stroke();
}
if(cloth.out){
var pl=P(TW+1.2+cloth.s*0.05,FY+0.12-dropPx/sc+cloth.drop);
var pw=CL*sc*0.55;
ctx.fillStyle='#c8352e';
ctx.beginPath();ctx.ellipse(pl[0]+pw/2,pl[1],pw/2,14+Math.sin(flameT)*1.5,0,0,7);ctx.fill();
ctx.fillStyle='rgba(255,255,255,.6)';
for(var q=0;q<6;q++)ctx.fillRect(pl[0]+10+q*28,pl[1]-8,14,16);
}
// left hanging bit
if(!cloth.out&&L<0){
var l0=P(L,TY),l1=P(Math.min(0,R),TY);
ctx.strokeStyle='#a82822';ctx.lineWidth=7;ctx.beginPath();
ctx.moveTo(l1[0],l1[1]-4);ctx.lineTo(l1[0]-8,l1[1]+44);ctx.stroke();
}
ctx.restore();
// objects sorted by x
var order=objs.slice().sort(function(a,b){return a.x-b.x;});
for(var i=0;i<order.length;i++)drawObj(order[i],W,H,sc);
// speed banner
ctx.fillStyle='rgba(0,0,0,.45)';ctx.fillRect(10,10,250,30);
ctx.fillStyle='#ffd98a';ctx.font='bold 16px Georgia';
var txt=cloth.pulling?(cloth.out?'cloth gone @ ':'pulling @ ')+cloth.target.toFixed(1)+' ft/s':'ready @ '+parseFloat(speedEl.value).toFixed(1)+' ft/s';
ctx.fillText(txt,20,31);
}
function drawObj(o,W,H,sc){
var p=w2s(o.x,o.y,W,H);
ctx.save();ctx.translate(p[0],p[1]);ctx.rotate(-o.a);ctx.scale(sc,sc);
var w=o.w,h=o.h;
ctx.fillStyle='rgba(0,0,0,.35)';
ctx.beginPath();ctx.ellipse(0,h/2+0.03,w*0.55,0.05,0,0,7);ctx.fill();
if(o.type==='plate'||o.type==='plate2'){
ctx.fillStyle='#efe9da';roundRect(-w/2,-h/2,w,h,h*0.45);ctx.fill();
ctx.strokeStyle='#9a958a';ctx.lineWidth=0.02;roundRect(-w/2,-h/2,w,h,h*0.45);ctx.stroke();
ctx.fillStyle='#d8d2c2';ctx.fillRect(-w/2+0.05,-0.02,w-0.1,0.04);
}else if(o.type==='bowl'){
ctx.fillStyle=o.col;ctx.beginPath();
ctx.moveTo(-w/2,-h/2);ctx.lineTo(w/2,-h/2);ctx.lineTo(w/2-0.08,h/2);ctx.quadraticCurveTo(0,h/2+0.12,-w/2+0.08,h/2);ctx.closePath();ctx.fill();
ctx.fillStyle='#7ec850';ctx.beginPath();ctx.ellipse(0,-h/2,w/2*0.9,0.09,0,0,7);ctx.fill();
ctx.fillStyle='#a5e07a';ctx.fillRect(-0.15,-h/2-0.06,0.1,0.08);ctx.fillRect(0.08,-h/2-0.05,0.08,0.07);
}else if(o.type==='glass'){
ctx.fillStyle='rgba(159,212,255,.55)';ctx.strokeStyle='#d8ecff';ctx.lineWidth=0.025;
ctx.beginPath();ctx.moveTo(-w/2+0.02,-h/2);ctx.lineTo(-w/2+0.06,h/2);ctx.lineTo(w/2-0.06,h/2);ctx.lineTo(w/2-0.02,-h/2);ctx.closePath();ctx.fill();ctx.stroke();
ctx.fillStyle='rgba(60,120,200,.5)';ctx.fillRect(-w/2+0.06,0.02,w-0.12,h/2-0.06);
ctx.fillStyle='rgba(255,255,255,.8)';ctx.fillRect(-w/2+0.08,-h/2+0.05,0.04,h-0.1);
}else if(o.type==='wine'){
ctx.fillStyle='rgba(220,190,255,.55)';ctx.strokeStyle='#eee0ff';ctx.lineWidth=0.025;
ctx.beginPath();ctx.ellipse(0,-h/2+0.16,w/2,0.18,0,0,7);ctx.fill();ctx.stroke();
ctx.fillStyle='rgba(150,30,60,.75)';ctx.beginPath();ctx.ellipse(0,-h/2+0.2,w/2*0.8,0.1,0,0,7);ctx.fill();
ctx.fillStyle='#ddd';ctx.fillRect(-0.02,-h/2+0.3,0.04,h/2-0.05);
ctx.fillRect(-w/4,h/2-0.04,w/2,0.04);
}else if(o.type==='candle'){
ctx.fillStyle='#7a4a22';ctx.fillRect(-w/2-0.06,h/2-0.08,w+0.12,0.08);
ctx.fillStyle='#8a5a2e';ctx.fillRect(-0.06,0,w*0.5+0.06,h/2-0.08);
ctx.fillStyle=o.col;ctx.fillRect(-w/2,-h/2+0.12,w,h/2-0.1);
ctx.strokeStyle='#333';ctx.lineWidth=0.02;ctx.beginPath();ctx.moveTo(0,-h/2+0.12);ctx.lineTo(0,-h/2+0.04);ctx.stroke();
var fl=0.10+Math.sin(flameT*3+o.x*5)*0.02;
ctx.fillStyle='#ff9a1f';ctx.beginPath();ctx.ellipse(0,-h/2-0.04,0.055,fl,0,0,7);ctx.fill();
ctx.fillStyle='#ffe36e';ctx.beginPath();ctx.ellipse(0,-h/2-0.02,0.028,fl*0.55,0,0,7);ctx.fill();
}
ctx.restore();
}
function roundRect(x,y,w,h,r){ctx.beginPath();ctx.moveTo(x+r,y);ctx.arcTo(x+w,y,x+w,y+h,r);ctx.arcTo(x+w,y+h,x,y+h,r);ctx.arcTo(x,y+h,x,y,r);ctx.arcTo(x,y,x+w,y,r);ctx.closePath();}
function yank(){
if(pulled&&!done)return;
cloth.target=parseFloat(speedEl.value);
cloth.pulling=true;pulled=true;done=false;stillT=0;simTime=0;
yankBtn.disabled=true;
statusEl.textContent='Pulled at '+cloth.target.toFixed(1)+' ft/s — simulating friction and inertia…';
}
yankBtn.addEventListener('click',yank);
resetBtn.addEventListener('click',makeObjs);
speedEl.addEventListener('input',function(){speedVal.textContent=parseFloat(speedEl.value).toFixed(1)+' ft/s';if(!pulled)cloth.target=parseFloat(speedEl.value);});
makeObjs();
var last=performance.now(),acc=0,HH=1/240;
function frame(t){
var dt=Math.min(0.05,(t-last)/1000);last=t;acc+=dt;
var n=0;
while(acc>HH&&n<20){step(HH);acc-=HH;n++;}
checkDone();draw();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
// hash speed
try{
var m=(location.hash||'').match(/speed\s*=\s*([0-9]*\.?[0-9]+)/i);
if(m){var v=parseFloat(m[1]);if(isFinite(v)){v=Math.max(0.5,Math.min(30,v));speedEl.value=Math.max(1,Math.min(20,v));if(v>20||v<1){var tmp=document.createElement('option');}speedVal.textContent=v.toFixed(1)+' ft/s';cloth.target=v;setTimeout(yank,1000);}}
}catch(e){}
})();
</script>
</body>
</html>
Like the real trick it is pure contact physics, not a speed script: each corner gets a penalty normal plus a stick-or-slip Coulomb impulse against the cloth or table velocity, so a slow pull re-sticks and drags everything right off the edge while a fast pull slips for only uncover-time L/V and leaves a small table-slide that stops before the edge, with tall narrow pieces tipping because bottom friction torques them over. It is not the real trick in that it is a 2D side view with rectangular collision boxes behind fancier drawings, no air drag on the cloth, no shattering, and a visually scripted cloth droop plus a flickering candle flame that are cosmetic only. Special cases and thresholds: upright means normalized angle within 0.6 rad, on-table means center x in [-0.25,6.25] and y above -0.6, done means all speeds under 0.18 ft/s and spin under 0.6 rad/s for ~1.5 s or 22 s timeout, table friction is slightly higher than cloth kinetic friction. How it fails before you find out: the candlestick can still catch a corner numerically and topple even near 12 ft/s, plates can jitter or creep a few inches on fast pulls, a very slow pull can leave the far-left bowl behind because initial ramp acceleration briefly exceeds static grip, box-box contacts can interpenetrate for a frame and pop, and anything that lands half-on the edge will seesaw and then drop rather than balance.