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
47:51Ranking major U.S. Open-Weights Models. (Where does Ripple 65B fit in?)
Nov 6, 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
31:28Is this company reselling GPT Sol 6.1 at a loss (for non-agentic/text-only)?🤔 Is this sustainable?
Oct 20, 2026 · YouTube
17:58Nemotron 3.5 Super VL vs Opus 5.5: Testing NVIDIA’s new coding juggernaut
Oct 15, 2026 · YouTube
16:04Testing a *massive* new open-weights model 🤯: Muse Spark Open (vs. Nemotron, Opus 5.5, and more)
Oct 15, 2026 · YouTube
16:43Gemini 4 Argon: Great 🐈 Animation, but a broken escalator. (Coding challenge vs Opus 5.5, Sol 6.1)
Oct 13, 2026 · YouTube
16:37Meta's new "Muse Wonder" AI is impressive... but can it compete with Anthropic's models?
Oct 12, 2026 · YouTube
11:12WOW... Haiku 5.5 is very impressive. RIP OpenAI?
Oct 7, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
22:02Is Pareto 26.10 Preview Just Routing to GPT Sol? I Tested It Against Sol 6.1 Pro
Oct 1, 2026 · YouTube
11:22I Tested Sonnet 5.5 Against Opus 5.5. Is It Actually Cheaper?
Sep 28, 2026 · YouTube
19:21I Tested the Stealth "Space Bunny Alpha" Model Before It Went Offline
Sep 23, 2026 · YouTube
You build physics toys, and you have held an umbrella in a gale.
Build me a single HTML page: a person holding an umbrella in wind that keeps getting stronger.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file: what the canopy is made of in your code, what inversion is as a state change, and what decides when it happens.
The single fenced html block.
Where it looks right and where it looks wrong. Everyone who has held an umbrella knows what one does in wind, so say what yours gets wrong before I find it.
The canopy is 11 rib-tips generated every frame from two springs — a flattening flex spring driven toward q/450 and a flutter oscillator forced by wind — draped over a shaft that is itself a damped torsional pendulum pivoted at the hand, so gusts visibly bend the shaft, flatten the dome and rattle the edge; inversion is a separate bistable state variable with a fast snap-through morph invertT that re-targets the dome height from positive to negative and exposes the ribs, and it fires when the effective dynamic pressure q = ½ρv² times angle-of-attack, sustained in a charge integrator for ~0.45s to ignore flicker, exceeds a critical frame load of about 150 Pa (≈55 km/h) lowered 25% if the shaft is already wrenched sideways, with hysteresis after that — it never pops back by itself and greatly raises drag so the grip-slip integrator that compares shaft torque against one-hand/two-hand strength is far more likely to let go and release the umbrella as a free tumbling flyer.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Gale — an umbrella toy</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#1a2230;font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;}
#c{position:fixed;inset:0;width:100%;height:100%;display:block;}
#hud{position:fixed;top:12px;left:12px;background:rgba(10,16,26,.78);color:#eef3fa;border:1px solid rgba(255,255,255,.14);
border-radius:12px;padding:12px 14px;width:262px;backdrop-filter:blur(6px);user-select:none;}
#hud h1{font-size:13px;letter-spacing:.14em;margin:0 0 6px;font-weight:700;color:#9fc2ff;}
#windRow{display:flex;align-items:baseline;gap:8px;}
#windNum{font-size:44px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums;}
#unit{font-size:13px;opacity:.7;}
#desc{font-size:12px;margin:2px 0 8px;color:#ffd88a;min-height:16px;font-weight:600;}
#loadWrap{background:#243044;border-radius:6px;height:8px;overflow:hidden;margin:6px 0;}
#loadBar{height:100%;width:0%;background:linear-gradient(90deg,#7CFC9A,#ffd23f,#ff5a5a);}
#loadTxt{font-size:11px;opacity:.8;margin-bottom:8px;}
input[type=range]{width:100%;accent-color:#6aa8ff;}
.row{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap;}
button{background:#2b3c5c;color:#fff;border:1px solid rgba(255,255,255,.2);border-radius:8px;padding:6px 9px;font-size:12px;cursor:pointer;}
button:hover{background:#3a507a;}
button:disabled{opacity:.35;cursor:default;}
label{font-size:12px;display:flex;align-items:center;gap:6px;margin-top:8px;cursor:pointer;}
#msg{font-size:12px;margin-top:8px;min-height:18px;color:#ff9d9d;font-weight:700;}
#hint{font-size:11px;opacity:.65;margin-top:6px;line-height:1.5;}
#big{position:fixed;left:50%;top:22%;transform:translate(-50%,-50%) rotate(-6deg);font-size:64px;font-weight:900;color:#ff5a5a;
text-shadow:0 4px 0 #5a0e0e,0 10px 30px rgba(0,0,0,.6);display:none;pointer-events:none;letter-spacing:.02em;}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="hud">
<h1>🌂 GALE TOY</h1>
<div id="windRow"><span id="windNum">0</span><span id="unit">km/h<br>wind</span></div>
<div id="desc">Calm</div>
<div id="loadWrap"><div id="loadBar"></div></div>
<div id="loadTxt">frame load 0% · grip 0%</div>
<input id="slider" type="range" min="0" max="125" value="10" step="1">
<label><input id="auto" type="checkbox" checked> auto gale (keeps rising)</label>
<div class="row">
<button id="gust">Gust! (G)</button>
<button id="fix">Fix (X)</button>
<button id="new">New ☂ (R)</button>
</div>
<div id="msg"></div>
<div id="hint">↑/↓ wind · G gust · X fix · R new umbrella<br>drag slider down to fight the auto-gale</div>
</div>
<div id="big">INVERTED!</div>
<script>
(function(){
"use strict";
var canvas=document.getElementById('c'),ctx=canvas.getContext('2d');
var windNum=document.getElementById('windNum'),descEl=document.getElementById('desc'),
loadBar=document.getElementById('loadBar'),loadTxt=document.getElementById('loadTxt'),
slider=document.getElementById('slider'),autoBox=document.getElementById('auto'),
gustBtn=document.getElementById('gust'),fixBtn=document.getElementById('fix'),
newBtn=document.getElementById('new'),msgEl=document.getElementById('msg'),bigEl=document.getElementById('big');
var W=0,H=0,DPR=1,groundY=0;
function resize(){DPR=Math.min(2,window.devicePixelRatio||1);W=window.innerWidth;H=window.innerHeight;
canvas.width=W*DPR;canvas.height=H*DPR;ctx.setTransform(DPR,0,0,DPR,0,0);groundY=H*0.84;}
window.addEventListener('resize',resize);resize();
// state
var S={t:0,windTarget:10,wind:10,gustImp:0,windEff:10,q:10,
lean:0.05,leanV:0,shaft:-0.25,shaftV:0,flex:0,flexV:0,charge:0,slip:0,
inverted:false,invertT:0,detached:false,ux:0,uy:0,uvx:0,uvy:0,uang:0,uangV:0,fixFlash:0,lostT:0};
var rain=[],clouds=[],streaks=[];
var i;
for(i=0;i<380;i++)rain.push({x:Math.random(),y:Math.random(),s:0.7+Math.random()*0.7});
for(i=0;i<6;i++)clouds.push({x:Math.random(),y:0.05+Math.random()*0.3,s:0.6+Math.random()*1.4});
for(i=0;i<40;i++)streaks.push({x:Math.random(),y:Math.random(),l:40+Math.random()*120});
function beaufort(k){
if(k<12)return"Calm · Beaufort 1–2";if(k<30)return"Breezy · Beaufort 4";if(k<45)return"Windy · Beaufort 6";
if(k<62)return"Gale · Beaufort 7–8";if(k<80)return"Severe gale · 9";if(k<100)return"Storm · 10";return"Violent storm · 11+";
}
slider.addEventListener('input',function(){S.windTarget=+slider.value;});
autoBox.addEventListener('change',function(){});
function doGust(){S.gustImp+=14+Math.random()*16;msg("gust!");}
function msg(m){msgEl.textContent=m;clearTimeout(msg._t);msg._t=setTimeout(function(){if(msgEl.textContent===m)msgEl.textContent="";},2600);}
gustBtn.addEventListener('click',doGust);
fixBtn.addEventListener('click',tryFix);
newBtn.addEventListener('click',resetUmb);
function tryFix(){
if(!S.inverted||S.detached){msg("nothing to fix");return;}
if(S.windEff<58){S.inverted=false;msg("popped back!");bigEl.style.display="none";}
else{msg("too windy to fix — lower the wind!");S.fixFlash=1;}
}
function resetUmb(){
S.detached=false;S.inverted=false;S.invertT=0;S.charge=0;S.slip=0;
S.shaft=-0.28;S.shaftV=0;S.flexV=0;S.lostT=0;bigEl.style.display="none";msg("fresh umbrella");
}
window.addEventListener('keydown',function(e){
if(e.key==="ArrowUp"){S.windTarget=Math.min(125,S.windTarget+3);slider.value=S.windTarget;e.preventDefault();}
else if(e.key==="ArrowDown"){S.windTarget=Math.max(0,S.windTarget-3);slider.value=S.windTarget;e.preventDefault();}
else if(e.key==="g"||e.key==="G")doGust();
else if(e.key==="x"||e.key==="X")tryFix();
else if(e.key==="r"||e.key==="R")resetUmb();
});
var last=performance.now();
function frame(now){
var dt=Math.min(0.033,(now-last)/1000);last=now;S.t+=dt;update(dt);draw();requestAnimationFrame(frame);
}
function update(dt){
var t=S.t;
if(autoBox.checked){S.windTarget=Math.min(118,S.windTarget+dt*1.6);slider.value=S.windTarget;}
S.wind+=(S.windTarget-S.wind)*Math.min(1,dt*0.9);
var osc=(Math.sin(t*0.7)+0.5*Math.sin(t*1.73+1.3)+0.35*Math.sin(t*3.3+0.5))*(2.5+S.windTarget*0.13);
S.gustImp*=Math.exp(-dt*0.7);
S.windEff=Math.max(0,S.wind+osc+S.gustImp);
var v=S.windEff/3.6;S.q=0.5*1.225*v*v;
// person lean spring
var leanT=0.04+(S.windEff/125)*0.52;
var wob=Math.sin(t*7.3)*0.012*(S.windEff/50)+Math.sin(t*13.7)*0.006*(S.windEff/50);
S.leanV+=((leanT+ wob -S.lean)*22 - S.leanV*7)*dt;S.lean+=S.leanV*dt;
if(!S.detached){
var two=S.windEff>38;
var dragM=S.inverted?1.38:1.0;
var windTorque=S.q*0.026*dragM;
var gripK=two?17:11;
var flut=Math.sin(t*27)*S.q*0.0022+Math.sin(t*41+1)*S.q*0.0012;
if(S.inverted)flut*=2.2;
var desired=-0.28;
var acc=(desired-S.shaft)*gripK - S.shaftV*3.2 + windTorque + flut;
S.shaftV+=acc*dt;S.shaft+=S.shaftV*dt;
S.shaft=Math.max(-0.9,Math.min(1.65,S.shaft));
// inversion charge
var angF=0.75+0.25*Math.cos(S.shaft*1.4);
var qEff=S.q*angF;
var crit=150*(S.shaft>0.6?0.72:1.0);
S.crit=crit;S.qEff=qEff;
if(!S.inverted){
if(qEff>crit)S.charge+=dt;else S.charge=Math.max(0,S.charge-dt*2.2);
if(S.charge>0.45){S.inverted=true;bigEl.style.display="block";bigEl.textContent="INVERTED!";msg("it turned inside out!");}
}
var tgt=S.invertT;var goal=S.inverted?1:0;
S.invertT+=(goal-S.invertT)*Math.min(1,dt*(goal>S.invertT?10:4));
// flex spring
var flexT=Math.min(1,S.q/450);if(S.inverted)flexT=0.85;
S.flexV+=((flexT-S.flex)*26 - S.flexV*8)*dt;S.flex+=S.flexV*dt;
// grip slip
var strain=Math.abs((S.shaft-desired))*gripK + (S.inverted?2.5:0);
var gmax=two?14.5:9.0;
S.strain=strain;S.gmax=gmax;
if(strain>gmax)S.slip=Math.min(1,S.slip+(strain-gmax)*dt*0.35);
else S.slip=Math.max(0,S.slip-dt*0.7);
if(S.slip>=1){ // let go
S.detached=true;S.lostT=t;
var sc=scaleF();var g= gripPos();var tip=canopyCenter(g.x,g.y,S.shaft,sc);
S.ux=tip.x;S.uy=tip.y;S.uvx=(v*22)*0.55+120;S.uvy=-260-Math.random()*120;
S.uang=S.shaft;S.uangV=4+Math.random()*6;
bigEl.style.display="block";bigEl.textContent="GONE!";msg("wrenched away!");
}
S.fixFlash=Math.max(0,S.fixFlash-dt*2);
}else{
S.invertT+=(1-S.invertT)*Math.min(1,dt*3);
var wpx=v*22;
var rvx=wpx-S.uvx,rvy=0-S.uvy;
var k=1.4;
var ax=rvx*k, ay=900 + rvy*k*0.6 - 320 - S.q*0.35;
ay+=Math.sin(t*9)*220;
S.uvx+=ax*dt;S.uvy+=ay*dt;S.ux+=S.uvx*dt;S.uy+=S.uvy*dt;
S.uangV+=(rvx*0.004 - S.uangV*0.4)*dt*4 + Math.sin(t*7)*3*dt;S.uang+=S.uangV*dt;
if(S.uy>groundY-6){S.uy=groundY-6;S.uvy*=-0.35;S.uvx*=0.7;S.uangV*=0.6;}
}
// rain uses normalized coords
for(i=0;i<streaks.length;i++){var st=streaks[i];st.x+=dt*(0.15+S.windEff*0.012);if(st.x>1.15){st.x=-0.15;st.y=Math.random();}}
for(i=0;i<clouds.length;i++){var cl=clouds[i];cl.x+=dt*(0.01+S.windEff*0.0016)*cl.s;if(cl.x>1.3)cl.x=-0.3;}
// HUD
windNum.textContent=Math.round(S.windEff);
descEl.textContent=beaufort(S.windEff)+(S.detached?" · umbrella lost":(S.inverted?" · INVERTED":""));
var load=S.qEff&&!S.detached?Math.min(1,S.qEff/(S.crit||150)):0;
loadBar.style.width=Math.round(load*100)+"%";
var grip=S.detached?100:Math.round((S.slip||0)*100);
loadTxt.textContent="frame load "+Math.round(load*100)+"% · grip slip "+grip+"%"+(S.windEff>38?" · TWO HANDS":" · one hand");
fixBtn.disabled=!(S.inverted&&!S.detached);
}
function scaleF(){return Math.max(0.7,Math.min(1.3,Math.min(W,H)/700));}
function personBase(){
var sc=scaleF();var px=W*0.38;
var stance=Math.min(95,30+S.windEff*0.62)*sc;
var crouch=Math.min(1,S.windEff/120)*20*sc;
var legL=88*sc-crouch*0.4, torsoL=72*sc;
var frontX=px-stance*0.62, backX=px+stance*0.55+Math.sin(S.t*9)*Math.min(4,S.windEff*0.05);
var hipX=px-Math.sin(S.lean)*30*sc, hipY=groundY-legL;
var shX=hipX-Math.sin(S.lean)*torsoL, shY=hipY-Math.cos(S.lean)*torsoL;
return{sc:sc,px:px,frontX:frontX,backX:backX,hipX:hipX,hipY:hipY,shX:shX,shY:shY,torsoL:torsoL};
}
function gripPos(){
var b=personBase();
return{x:b.shX-52*b.sc+Math.sin(S.lean)*8, y:b.shY+18*b.sc, b:b};
}
function canopyCenter(gx,gy,ang,sc){var dx=Math.sin(ang),dy=-Math.cos(ang);var L=98*sc;return{x:gx+dx*L,y:gy+dy*L};}
function draw(){
var t=S.t,sc=scaleF();
// sky
var dark=Math.min(1,S.windEff/110);
var g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,"rgb("+(58-dark*38)+","+(72-dark*42)+","+(95-dark*45)+")");
g.addColorStop(0.7,"rgb("+(90-dark*55)+","+(105-dark*60)+","+(125-dark*60)+")");
g.addColorStop(1,"rgb("+(70-dark*30)+","+(80-dark*30)+","+(90-dark*30)+")");
ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
// clouds
ctx.fillStyle="rgba(20,26,38,0.55)";
for(var ci=0;ci<clouds.length;ci++){var cl=clouds[ci];var cx=cl.x*W,cy=cl.y*H;
ctx.beginPath();ctx.ellipse(cx,cy,120*cl.s,26*cl.s,0,0,7);ctx.ellipse(cx+60*cl.s,cy+8,80*cl.s,20*cl.s,0,0,7);ctx.fill();}
// far houses
ctx.fillStyle="rgba(25,32,46,0.9)";
var hy=groundY-60*sc;
ctx.fillRect(0,hy,W,60*sc);
ctx.fillStyle="rgba(255,210,120,0.5)";
for(var wx=20;wx<W;wx+=70){if(((wx*7)%3)!==0)ctx.fillRect(wx,hy+18,10,12);}
// tree (right side, bends)
var tx=W*0.82,ty=groundY;
var bend=(S.windEff/125)*70*sc+Math.sin(t*6)*4*(S.windEff/60);
ctx.strokeStyle="#2a2118";ctx.lineCap="round";
ctx.lineWidth=12*sc;ctx.beginPath();ctx.moveTo(tx,ty);ctx.quadraticCurveTo(tx+bend*0.3,ty-70*sc,tx+bend*0.55,ty-120*sc);ctx.stroke();
ctx.lineWidth=5*sc;ctx.strokeStyle="#33291d";
for(var b=-1;b<=1;b++){ctx.beginPath();ctx.moveTo(tx+bend*0.4,ty-90*sc);
ctx.quadraticCurveTo(tx+bend*0.8+b*30*sc,ty-130*sc,tx+bend+b*55*sc,ty-150*sc+b*10);ctx.stroke();}
ctx.fillStyle="rgba(40,70,45,0.9)";
for(var l=0;l<5;l++){ctx.beginPath();ctx.ellipse(tx+bend+(l-2)*22*sc,ty-150*sc+((l%2)*14),34*sc,20*sc,l*0.5,0,7);ctx.fill();}
// lamp post bending? static
ctx.strokeStyle="#11161f";ctx.lineWidth=6*sc;
ctx.beginPath();ctx.moveTo(W*0.08,groundY);ctx.lineTo(W*0.08,groundY-170*sc);ctx.stroke();
ctx.fillStyle=(S.windEff>20?"rgba(255,220,150,0.9)":"rgba(255,220,150,0.35)");
ctx.beginPath();ctx.arc(W*0.08,groundY-175*sc,8*sc,0,7);ctx.fill();
// ground
ctx.fillStyle="#2e3542";ctx.fillRect(0,groundY,W,H-groundY);
ctx.fillStyle="#3a4354";ctx.fillRect(0,groundY,W,4);
// puddle streaks
ctx.strokeStyle="rgba(150,180,220,0.25)";ctx.lineWidth=2;
for(var p=0;p<8;p++){var pxx=((p*197)%W)+Math.sin(t*2+p)*4;ctx.beginPath();ctx.ellipse(pxx,groundY+18+((p*37)%60),26,4,0,0,7);ctx.stroke();}
drawPerson(t,sc);
if(!S.detached){var gp=gripPos();var cc=canopyCenter(gp.x,gp.y,S.shaft,sc);drawUmbrella(gp.x,gp.y,cc.x,cc.y,S.shaft,t,sc,false);}
else{drawUmbrella(S.ux,S.uy-40*sc,S.ux,S.uy,S.uang,t,sc,true);}
drawRain();drawStreaks();
if(S.fixFlash>0){ctx.save();ctx.translate(Math.random()*6-3,0);ctx.restore();}
}
function drawPerson(t,sc){
var b=personBase();
var lean=S.lean;
// shadow
ctx.fillStyle="rgba(0,0,0,0.3)";ctx.beginPath();ctx.ellipse(b.px,groundY+8,46*sc,8*sc,0,0,7);ctx.fill();
// legs
ctx.strokeStyle="#1c2330";ctx.lineWidth=11*sc;ctx.lineCap="round";
ctx.beginPath();ctx.moveTo(b.hipX,b.hipY);ctx.lineTo(b.frontX,groundY-4);ctx.stroke();
ctx.beginPath();ctx.moveTo(b.hipX,b.hipY);ctx.lineTo(b.backX,groundY-4);ctx.stroke();
// shoes
ctx.fillStyle="#0e1118";
ctx.fillRect(b.frontX-14*sc,groundY-9,24*sc,8*sc);
ctx.fillRect(b.backX-8*sc,groundY-9,24*sc,8*sc);
// torso / coat
var flap=(S.windEff/125)*46*sc+Math.sin(t*(8+S.windEff*0.15))* (4+S.windEff*0.12)*sc;
ctx.fillStyle="#c8a13a";
ctx.beginPath();
ctx.moveTo(b.hipX-12*sc,b.hipY+4);ctx.lineTo(b.shX-13*sc,b.shY);
ctx.lineTo(b.shX+13*sc,b.shY);ctx.lineTo(b.hipX+12*sc,b.hipY+4);
ctx.lineTo(b.hipX+12*sc+flap,b.hipY-6+Math.sin(t*11)*6*sc);
ctx.lineTo(b.hipX+4*sc,b.hipY+10);ctx.closePath();ctx.fill();
ctx.strokeStyle="#7a5f1e";ctx.lineWidth=2*sc;ctx.stroke();
// scarf flapping downwind
var sx=b.shX,sy=b.shY+6*sc;
ctx.strokeStyle="#b33a3a";ctx.lineWidth=7*sc;
ctx.beginPath();ctx.moveTo(sx,sy);
ctx.quadraticCurveTo(sx+40*sc+flap*0.7,sy+6*sc+Math.sin(t*12)*8*sc,sx+70*sc+flap+Math.sin(t*15)*10*sc,sy+14*sc+Math.sin(t*13+1)*12*sc);ctx.stroke();
// head
var hx=b.shX-Math.sin(lean)*14*sc-6*sc, hy=b.shY-16*sc;
ctx.fillStyle="#e8b98d";ctx.beginPath();ctx.arc(hx,hy,11*sc,0,7);ctx.fill();
ctx.fillStyle="#3a2c22";ctx.beginPath();ctx.arc(hx-2*sc,hy-4*sc,11*sc,Math.PI*0.9,Math.PI*1.9);ctx.fill();
// arms to grip
var gp=gripPos();
var two=S.windEff>38;
ctx.strokeStyle="#8a6d24";ctx.lineWidth=9*sc;
ctx.beginPath();ctx.moveTo(b.shX,b.shY+8*sc);ctx.lineTo((b.shX+gp.x)/2+4*sc,(b.shY+gp.y)/2+8*sc);ctx.lineTo(gp.x,gp.y);ctx.stroke();
if(two||S.detached){
if(!S.detached){ctx.beginPath();ctx.moveTo(b.shX+4*sc,b.shY+12*sc);ctx.lineTo(gp.x+4*sc,gp.y-6*sc);ctx.stroke();}
else{ // reaching after loss
var rx=b.shX+50*sc+Math.sin(t*6)*8*sc, ry=b.shY+10*sc-10*sc;
ctx.beginPath();ctx.moveTo(b.shX,b.shY+10*sc);ctx.lineTo(rx,ry);ctx.stroke();
}
}else{
var bx2=b.shX+44*sc+Math.sin(t*5)*8*sc, by2=b.shY+26*sc+Math.sin(t*7)*6*sc;
ctx.beginPath();ctx.moveTo(b.shX,b.shY+10*sc);ctx.lineTo(bx2,by2);ctx.stroke();
}
// hands
ctx.fillStyle="#e8b98d";ctx.beginPath();ctx.arc(gp.x,gp.y,6*sc,0,7);ctx.fill();
}
function drawUmbrella(gx,gy,cx,cy,ang,t,sc,flying){
var bend=Math.min(22,Math.abs(S.q*0.03))*(S.shaft>0?1:-0.3);
// shaft (bent)
ctx.strokeStyle="#3b3b3b";ctx.lineWidth=5*sc;ctx.lineCap="round";
ctx.beginPath();ctx.moveTo(gx,gy);
var mx=(gx+cx)/2+bend,my=(gy+cy)/2;
ctx.quadraticCurveTo(mx,my,cx,cy);ctx.stroke();
// tip
var dx=Math.sin(ang),dy=-Math.cos(ang);
ctx.strokeStyle="#222";ctx.lineWidth=3*sc;
ctx.beginPath();ctx.moveTo(cx,cy);ctx.lineTo(cx+dx*16*sc,cy+dy*16*sc);ctx.stroke();
// handle
if(!flying){ctx.strokeStyle="#5a3a22";ctx.lineWidth=6*sc;
ctx.beginPath();ctx.moveTo(gx,gy);ctx.quadraticCurveTo(gx+16*sc,gy+14*sc,gx+4*sc,gy+22*sc);ctx.stroke();}
var N=11,Wd=168*sc;
var inv=S.invertT;
var domeN=(34-24*S.flex)*sc, domeI=(-30+8*S.flex)*sc;
var dome=domeN*(1-inv)+domeI*inv;
var amp=(2+S.flex*7+inv*11)*sc;
if(flying)amp+=3*sc;
var px2=Math.cos(ang),py2=Math.sin(ang); // across dir (perp of shaft)
var pts=[];
for(var k=0;k<N;k++){
var tt=(k/(N-1)-0.5)*2;
var across=tt*Wd/2;
var shape=(1-tt*tt);
var fl=Math.sin(t*(18+S.windEff*0.25)+tt*4.5)*amp*(0.4+0.6*Math.abs(tt));
var along=dome*shape+fl;
if(inv>0.5)along+=-Math.abs(tt)*6*sc*inv; // jagged inverted rim
pts.push({x:cx+px2*across+dx*along, y:cy+py2*across+dy*along, tt:tt});
}
// fabric
ctx.beginPath();ctx.moveTo(pts[0].x,pts[0].y);
for(var k2=1;k2<N;k2++){
var a=pts[k2-1],bb=pts[k2];
ctx.quadraticCurveTo((a.x+bb.x)/2,(a.y+bb.y)/2+ (inv>0.5?-7*sc:7*sc),bb.x,bb.y);
}
ctx.closePath();
var fg=ctx.createLinearGradient(cx,cy-40*sc,cx,cy+30*sc);
if(inv<0.5){fg.addColorStop(0,"#d63a3a");fg.addColorStop(1,"#8f1d1d");}
else{fg.addColorStop(0,"#7a5a5a");fg.addColorStop(1,"#c98a8a");}
ctx.fillStyle=fg;ctx.fill();
ctx.strokeStyle="#4a1010";ctx.lineWidth=2*sc;ctx.stroke();
// gore seams + ribs
ctx.strokeStyle=inv>0.5?"rgba(60,10,10,0.9)":"rgba(255,255,255,0.35)";ctx.lineWidth=1.5*sc;
var apex={x:cx+dx*dome,y:cy+dy*dome};
for(var r=0;r<N;r+=2){ctx.beginPath();ctx.moveTo(apex.x,apex.y);ctx.lineTo(pts[r].x,pts[r].y);ctx.stroke();}
// notch
ctx.fillStyle="#222";ctx.beginPath();ctx.arc(apex.x,apex.y,4*sc,0,7);ctx.fill();
}
function drawRain(){
var v=S.windEff/3.6, wpx=v*22, fpx=560;
ctx.strokeStyle="rgba(170,200,235,0.55)";ctx.lineWidth=1.4;
ctx.beginPath();
for(var k=0;k<rain.length;k++){
var d=rain[k];
d.x+=((wpx*d.s)/W)*0.016; d.y+=(fpx*d.s/H)*0.016;
d.x+=Math.sin(S.t*6+d.y*20)*0.0004;
if(d.y>1.02||d.x>1.05){d.y=-0.02;d.x=Math.random()*1.1-0.05;}
var x=d.x*W,y=d.y*H;
var vx=wpx*d.s,vy=fpx*d.s;
var L=0.028;
ctx.moveTo(x,y);ctx.lineTo(x-vx*L,y-vy*L);
}
ctx.stroke();
// splashes
ctx.strokeStyle="rgba(170,200,235,0.3)";
ctx.beginPath();
for(var s2=0;s2<30;s2++){var sx=((s2*173+S.t*300*(1+S.windEff*0.05))%W);
var sy=groundY+10+((s2*53)%50);ctx.moveTo(sx-5,sy);ctx.lineTo(sx+5,sy);}
ctx.stroke();
}
function drawStreaks(){
if(S.windEff<35)return;
ctx.strokeStyle="rgba(220,235,255,"+Math.min(0.35,(S.windEff-35)/200)+")";ctx.lineWidth=2;
ctx.beginPath();
for(var k=0;k<streaks.length;k++){var s=streaks[k];var y=s.y*H,x=s.x*W;
ctx.moveTo(x,y);ctx.lineTo(x+s.l*(S.windEff/100),y);}
ctx.stroke();
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Where it looks right: the loop reads like a real gale — rain slants from drizzle to near-horizontal, the shaft bends and shudders, the dome flattens and buzzes before it snaps through with a snap, the walker leans in, widens stance, crouches, goes to two hands, scarf and coat stream, and after inversion the drag jump almost always tears the handle away into a tumbling flyer that bounces down the street. Where it is wrong: a real canopy is 8 separate gores on hinged ribs that luff, collapse asymmetrically and snag — mine is a symmetric 2D arc that only flattens and sine-flutters, so it never half-collapses, skews, or turns inside-out one side first; real inversion needs a gust catching the windward lip at a bad angle and then stays crumpled, while mine uses a single averaged pressure threshold and morphs cleanly; real loss is fingers peeling off a wet handle with rotation at the wrist, mine is just a torque-vs-strength slip meter; and the walker never steps, slides, or gets dragged — feet are glued to the ground by design, so there is no stumbling, hopping puddles, or being pulled off balance the way a real body is.