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 not cloth simulation but a single deformable arch defined by two tips and an apex linked by seven elastic ribs; the fabric between ribs is drawn as quadratic Bézier panels whose curvature is driven by wind pressure, so the whole dome flexes and flutters as a rigid-body with added wobble. Inversion is a discrete bistable state change — inversion 0→1 — with hysteresis and a snap-through animation, not a continuous bend, exposing the ribs and flipping the scalloped edge. It triggers when aerodynamic torque (≈ windSpeed² × effectiveArea × leverArm) overcomes rib torsional stiffness and the shaft angle exceeds ~0.52 rad; above ~17.5 m/s the probability rises sharply and above ~25.5 m/s it snaps instantly, while recovery only becomes possible below ~9 m/s with the shaft near vertical.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Gale Umbrella</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#8aa0b8;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif}
canvas{display:block;width:100%;height:100%}
#ui{position:absolute;top:14px;left:14px;background:rgba(255,255,255,0.92);backdrop-filter:blur(6px);padding:12px 14px;border-radius:12px;box-shadow:0 6px 20px rgba(0,0,0,0.15);min-width:260px}
#ui h1{margin:0 0 6px;font-size:14px;letter-spacing:0.04em;text-transform:uppercase;color:#2c3e50}
#row{display:flex;align-items:center;gap:8px;margin:6px 0}
#windRead{font-weight:800;font-size:22px;color:#1a252f;min-width:140px}
#beaufort{font-size:11px;color:#5d6d7e;font-weight:600;text-transform:uppercase;letter-spacing:0.05em}
input[type=range]{width:100%;accent-color:#2c3e50}
#status{font-size:12px;font-weight:700;padding:4px 8px;border-radius:20px;background:#ecf0f1;display:inline-block;margin-top:4px}
#status.strain{background:#f9e79f}
#status.inverted{background:#f5b7b1}
#status.gone{background:#2c3e50;color:#fff}
small{color:#6b7a8f}
button{border:0;background:#2c3e50;color:#fff;padding:6px 10px;border-radius:8px;font-weight:600;cursor:pointer}
button:active{transform:scale(0.97)}
label{font-size:12px;color:#34495e;display:flex;align-items:center;gap:6px;cursor:pointer}
#hint{font-size:11px;color:#6b7a8f;margin-top:6px;line-height:1.3}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="ui">
<h1>Hold tight</h1>
<div id="row"><div id="windRead">0.0 m/s</div><div id="beaufort">calm</div></div>
<input id="slider" type="range" min="0" max="100" value="6">
<div id="row" style="justify-content:space-between">
<button id="reset">Reset</button>
<label><input type="checkbox" id="auto" checked> auto rise</label>
<span id="status">holding</span>
</div>
<div id="hint">Drag slider or use ↑/↓ ←/→. Space = gust. Wind builds on its own — pull it back down if you can.</div>
</div>
<script>
(() => {
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const slider = document.getElementById('slider');
const windRead = document.getElementById('windRead');
const beauEl = document.getElementById('beaufort');
const autoChk = document.getElementById('auto');
const resetBtn = document.getElementById('reset');
const statusEl = document.getElementById('status');
let W, H, dpr;
function resize(){
dpr = Math.min(2, window.devicePixelRatio || 1);
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr,0,0,dpr,0,0);
}
window.addEventListener('resize', resize);
resize();
let windSpeed = 2;
let windDir = 1;
let autoEnabled = true;
let isDragging = false;
let time = 0;
let last = performance.now();
let umbrella = {
angle: -0.12,
angularVel: 0,
inversion: 0,
inverting: false,
invertDir: 1,
detached: false,
x: 0, y: 0, vx: 0, vy: 0, spin: 0, rotation: 0,
_handX: 0, _handY: 0, _cx: 0, _cy: 0
};
let person = {
lean: -0.05,
leanVel: 0,
stance: 22
};
let drops = [];
let streaks = [];
function initDrops(){
drops = [];
for(let i=0;i<280;i++){
drops.push({
x: Math.random()*W,
y: Math.random()*H,
fall: 220 + Math.random()*180,
turb: (Math.random()-0.5)*12
});
}
streaks = [];
for(let i=0;i<18;i++){
streaks.push({
x: Math.random()*W,
y: Math.random()*H*0.6,
len: 40 + Math.random()*80,
speed: 0.8 + Math.random()*0.7
});
}
}
initDrops();
function beaufort(mps){
if(mps < 0.5) return 'calm';
if(mps < 1.6) return 'light air';
if(mps < 3.4) return 'light breeze';
if(mps < 5.5) return 'gentle breeze';
if(mps < 8) return 'moderate breeze';
if(mps < 10.8) return 'fresh breeze';
if(mps < 13.9) return 'strong breeze';
if(mps < 17.2) return 'near gale';
if(mps < 20.8) return 'gale';
if(mps < 24.5) return 'strong gale';
if(mps < 28.5) return 'storm';
if(mps < 32.7) return 'violent storm';
return 'hurricane';
}
slider.addEventListener('input', () => {
windSpeed = slider.value/100*35;
});
slider.addEventListener('pointerdown', () => isDragging = true);
window.addEventListener('pointerup', () => isDragging = false);
autoChk.addEventListener('change', () => autoEnabled = autoChk.checked);
resetBtn.addEventListener('click', reset);
window.addEventListener('keydown', (e) => {
if(e.code==='ArrowUp' || e.code==='ArrowRight'){
windSpeed = Math.min(35, windSpeed + 1.8);
e.preventDefault();
}
if(e.code==='ArrowDown' || e.code==='ArrowLeft'){
windSpeed = Math.max(0, windSpeed - 1.8);
e.preventDefault();
}
if(e.code==='Space'){
windSpeed = Math.min(35, windSpeed + 7);
umbrella.angularVel += windDir*1.5;
e.preventDefault();
}
});
function reset(){
windSpeed = 2;
umbrella.angle = -0.12;
umbrella.angularVel = 0;
umbrella.inversion = 0;
umbrella.inverting = false;
umbrella.detached = false;
umbrella.x=0; umbrella.y=0; umbrella.vx=0; umbrella.vy=0; umbrella.spin=0; umbrella.rotation=0;
person.lean = -0.05; person.leanVel=0; person.stance=22;
slider.value = windSpeed/35*100;
}
function getPose(){
const groundY = H - 70;
const personX = W * 0.38;
const hipX = personX + Math.sin(person.lean)*38;
const hipY = groundY - 58;
const shoulderX = hipX + Math.sin(person.lean)*36;
const shoulderY = hipY - Math.cos(person.lean)*44;
return {groundY, personX, hipX, hipY, shoulderX, shoulderY};
}
function update(dt){
if(autoEnabled && !isDragging){
let gust = Math.sin(time*0.55)*1.2 + Math.sin(time*1.3)*0.7 + Math.sin(time*2.1)*0.4;
if(Math.random() < 0.009) gust += 6 + Math.random()*9;
if(Math.random() < 0.015) gust += (Math.random()-0.5)*2;
windSpeed += (0.16 + Math.max(0,gust*0.018)) * dt;
windSpeed += (Math.random()-0.5)*0.03;
windSpeed = Math.max(0, Math.min(35, windSpeed));
slider.value = windSpeed/35*100;
} else {
windSpeed = Math.max(0, Math.min(35, windSpeed));
}
windRead.textContent = windSpeed.toFixed(1) + ' m/s \u00B7 ' + (windSpeed*3.6).toFixed(0) + ' km/h';
beauEl.textContent = beaufort(windSpeed);
if(umbrella.detached){
statusEl.textContent = 'LOST!';
statusEl.className = 'gone';
} else if(umbrella.inversion > 0.5){
statusEl.textContent = 'INVERTED!';
statusEl.className = 'inverted';
} else if(windSpeed > 19 || Math.abs(umbrella.angle) > 0.5){
statusEl.textContent = 'STRAINING';
statusEl.className = 'strain';
} else {
statusEl.textContent = 'holding';
statusEl.className = '';
}
if(!umbrella.detached){
let targetLean = -windDir * Math.min(0.68, windSpeed*0.019 + Math.abs(umbrella.angle)*0.14);
if(umbrella.inversion > 0.5) targetLean *= 0.75;
if(windSpeed > 22) targetLean += Math.sin(time*18)*0.02;
let leanAcc = (targetLean - person.lean)*12 - person.leanVel*6;
person.leanVel += leanAcc * dt;
person.lean += person.leanVel * dt;
person.stance = 22 + Math.min(34, windSpeed*1.15 + Math.abs(umbrella.angle)*12);
let windForce = windSpeed*windSpeed * 0.016;
let eff = Math.cos(umbrella.angle*0.65);
eff = Math.max(0.25, eff);
windForce *= eff;
if(umbrella.inversion > 0.5) windForce *= 0.72;
let windTorque = windForce * 0.95 * windDir;
let gripStiffness = 13.5 - windSpeed*0.16 - Math.abs(umbrella.angle)*3.2;
gripStiffness = Math.max(2.2, gripStiffness);
if(windSpeed > 20) gripStiffness -= (windSpeed-20)*0.04;
let restore = gripStiffness * umbrella.angle;
let damping = 3.8 + windSpeed*0.06;
let angAcc = (windTorque - restore - damping*umbrella.angularVel) / 1.15;
umbrella.angularVel += angAcc * dt;
umbrella.angularVel += (Math.random()-0.5)* windSpeed*0.008 * dt*60;
umbrella.angle += umbrella.angularVel * dt;
if(!umbrella.inverting){
if(umbrella.inversion < 0.5){
if(windSpeed > 17.5 && Math.abs(umbrella.angle) > 0.52){
let p = (windSpeed - 17.5)/18 * 0.05;
if(Math.random() < p || windSpeed > 25.5){
umbrella.inverting = true;
umbrella.invertDir = 1;
umbrella.angularVel += windDir*2.5;
}
}
} else {
if(windSpeed < 9 && Math.abs(umbrella.angle) < 0.35){
if(Math.random() < 0.012){
umbrella.inverting = true;
umbrella.invertDir = -1;
}
}
}
}
if(umbrella.inverting){
umbrella.inversion += umbrella.invertDir * dt * 2.4;
if(umbrella.inversion >= 1){ umbrella.inversion = 1; umbrella.inverting = false; }
if(umbrella.inversion <= 0){ umbrella.inversion = 0; umbrella.inverting = false; }
}
let pose = getPose();
let handX = pose.shoulderX -26 + umbrella.angle*14;
let handY = pose.shoulderY + 18 + Math.abs(umbrella.angle)*6;
let shaftLen = 108;
let cx = handX + Math.sin(umbrella.angle)*shaftLen;
let cy = handY - Math.cos(umbrella.angle)*shaftLen;
umbrella._handX = handX; umbrella._handY = handY; umbrella._cx = cx; umbrella._cy = cy;
let strain = Math.abs(umbrella.angle) * windSpeed;
if(strain > 26 && windSpeed > 21){
if(Math.random() < 0.018 * (strain/26)){
umbrella.detached = true;
umbrella.x = cx; umbrella.y = cy;
umbrella.vx = windSpeed*7 + 90 + Math.random()*50;
umbrella.vy = -50 - Math.random()*70 - windSpeed*1.2;
umbrella.spin = windDir * (0.6 + Math.random()*0.8) * (windSpeed*0.07);
umbrella.rotation = umbrella.angle;
}
}
if(windSpeed > 31 && Math.abs(umbrella.angle) > 0.85 && !umbrella.detached){
if(Math.random() < 0.035){
umbrella.detached = true;
umbrella.x = cx; umbrella.y = cy;
umbrella.vx = windSpeed*8 + 100;
umbrella.vy = -60 - windSpeed;
umbrella.spin = windDir * windSpeed*0.09;
umbrella.rotation = umbrella.angle;
}
}
} else {
umbrella.vx += windSpeed*10 * dt;
umbrella.vy += 420 * dt;
umbrella.vy -= windSpeed*1.8 * dt;
umbrella.x += umbrella.vx * dt;
umbrella.y += umbrella.vy * dt;
umbrella.rotation += umbrella.spin * dt;
umbrella.spin += (Math.random()-0.5)*0.6 * dt*60 *0.02;
umbrella.vx *= (1 - 0.015*dt*60);
let targetLean = -windDir * Math.min(0.32, windSpeed*0.009);
let leanAcc = (targetLean - person.lean)*8 - person.leanVel*5;
person.leanVel += leanAcc*dt;
person.lean += person.leanVel*dt;
person.stance = 22 + Math.min(16, windSpeed*0.55);
}
for(let d of drops){
d.x += (windSpeed*8 + d.turb) * dt;
d.y += d.fall * dt;
if(d.y > H+20){
d.y = -20 - Math.random()*80;
d.x = Math.random()*W - windSpeed*22;
}
if(d.x > W+60){
d.x = -50;
d.y = Math.random()*H;
}
if(d.x < -80){
d.x = W+50;
d.y = Math.random()*H;
}
}
for(let s of streaks){
s.x += windSpeed*14 * s.speed * dt;
if(s.x > W+100){ s.x = -100; s.y = Math.random()*H*0.55; }
}
}
function drawBackground(){
let darken = Math.min(0.55, windSpeed/35*0.55);
let g = ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0, `rgb(${158 - darken*70}, ${178 - darken*60}, ${202 - darken*40})`);
g.addColorStop(0.55, `rgb(${205 - darken*50}, ${218 - darken*45}, ${232 - darken*35})`);
g.addColorStop(1, `rgb(${228 - darken*30}, ${232 - darken*30}, ${238 - darken*20})`);
ctx.fillStyle = g;
ctx.fillRect(0,0,W,H);
ctx.fillStyle = `rgba(52,58,70,${0.18 + darken*0.35})`;
ctx.beginPath();
ctx.moveTo(0, H*0.62);
for(let x=0;x<W;x+=36){
let h = 18 + Math.abs(Math.sin(x*0.015))*22 + Math.random()*12;
ctx.lineTo(x, H*0.62 - h);
ctx.lineTo(x+26, H*0.62 - h);
ctx.lineTo(x+26, H*0.62);
}
ctx.lineTo(W, H*0.62);
ctx.lineTo(W, H);
ctx.lineTo(0,H);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = `rgba(255,255,255,${0.22 + windSpeed*0.008})`;
ctx.lineWidth = 1;
for(let s of streaks){
ctx.beginPath();
ctx.moveTo(s.x, s.y);
ctx.lineTo(s.x + s.len, s.y + 2);
ctx.stroke();
}
if(windSpeed > 18){
let alpha = (windSpeed-18)/17*0.18;
ctx.fillStyle = `rgba(40,50,65,${alpha})`;
ctx.fillRect(0,0,W,H);
}
}
function drawGround(){
const groundY = H - 70;
ctx.fillStyle = '#2f3338';
ctx.fillRect(0, groundY, W, H-groundY);
ctx.fillStyle = '#3d4248';
ctx.fillRect(0, groundY, W, 6);
ctx.fillStyle = 'rgba(130,150,170,0.22)';
for(let i=0;i<5;i++){
let x = W*0.08 + i*W*0.19;
ctx.fillRect(x, groundY+14, 86, 3);
ctx.fillRect(x+12, groundY+20, 54, 2);
}
ctx.strokeStyle = 'rgba(0,0,0,0.15)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, groundY);
ctx.lineTo(W, groundY);
ctx.stroke();
}
function drawRain(){
ctx.strokeStyle = 'rgba(165,185,205,0.62)';
ctx.lineWidth = 1.2;
ctx.lineCap = 'round';
for(let d of drops){
let vx = windSpeed*8 + d.turb;
let vy = d.fall;
let dx = vx * 0.055;
let dy = vy * 0.055;
ctx.beginPath();
ctx.moveTo(d.x, d.y);
ctx.lineTo(d.x - dx, d.y - dy);
ctx.stroke();
if(d.y > H-70-2 && d.y < H-70+4 && Math.random()<0.015){
ctx.fillStyle = 'rgba(165,185,205,0.5)';
ctx.beginPath();
ctx.arc(d.x, H-70, 2, 0, Math.PI*2);
ctx.fill();
}
}
}
function drawCanopyShape(inv){
const Wc = 148;
const Hc = 38;
let leftX = -Wc/2 + inv*4;
let leftY = 10*(1-inv) + (-10)*inv;
let rightX = Wc/2 - inv*4;
let rightY = 10*(1-inv) + (-10)*inv;
let apexY = -Hc*(1-inv) + 22*inv;
let flex = windSpeed*0.32*(1 - inv*0.55);
leftY -= flex*0.45;
rightY += flex*0.22;
apexY += Math.sin(time*7.5)*1.2*(1-inv*0.7);
leftY += Math.sin(time*9)* windSpeed*0.03*(1-inv*0.5);
rightY += Math.cos(time*8.5)* windSpeed*0.02*(1-inv*0.5);
ctx.fillStyle = inv > 0.5 ? '#5a6d80' : '#1a2633';
ctx.strokeStyle = 'rgba(0,0,0,0.28)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(leftX, leftY);
ctx.quadraticCurveTo(leftX*0.5, apexY-5, 0, apexY);
ctx.quadraticCurveTo(rightX*0.5, apexY-5, rightX, rightY);
const segs = 7;
const step = (rightX - leftX)/segs;
for(let i=segs-1;i>=0;i--){
let x = leftX + i*step;
let y = leftY + (x - leftX)/(rightX-leftX)*(rightY-leftY);
let dip = (i%2===0 ? 7 : 4) * (1 - inv*0.85);
if(inv > 0.5) dip = -5;
let nx = x + step*0.5;
let ny = y + dip;
let ex = x + step;
if(i===0){
ctx.lineTo(leftX, leftY);
} else {
ctx.quadraticCurveTo(nx, ny, ex, leftY + (ex - leftX)/(rightX-leftX)*(rightY-leftY));
}
}
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(leftX, leftY);
ctx.quadraticCurveTo(leftX*0.5, apexY-5, 0, apexY);
ctx.quadraticCurveTo(rightX*0.5, apexY-5, rightX, rightY);
ctx.stroke();
ctx.strokeStyle = inv > 0.5 ? '#cbd6e2' : '#6b7d90';
ctx.lineWidth = 1.4;
for(let i=-3;i<=3;i++){
let t = i/3;
let ex = t*Wc/2*0.96;
let ey = leftY + (ex - leftX)/(rightX-leftX)*(rightY-leftY);
ctx.beginPath();
ctx.moveTo(0, apexY);
let mx = ex*0.5;
let my = (apexY+ey)*0.5 -4*(1-inv) + 6*inv;
ctx.quadraticCurveTo(mx, my, ex, ey);
ctx.stroke();
if(inv > 0.6){
ctx.beginPath();
ctx.moveTo(ex, ey);
ctx.lineTo(ex + t*7, ey -9);
ctx.stroke();
}
}
ctx.fillStyle = '#8a9bb0';
ctx.beginPath();
ctx.arc(0, apexY-2, 2.8, 0, Math.PI*2);
ctx.fill();
}
function drawUmbrella(){
if(!umbrella.detached){
let hx = umbrella._handX;
let hy = umbrella._handY;
let ang = umbrella.angle;
let bend = ang*0.18 + windSpeed*0.004;
let shaftLen = 108;
let cx = hx + Math.sin(ang+bend)*shaftLen;
let cy = hy - Math.cos(ang+bend)*shaftLen;
ctx.strokeStyle = '#1e2328';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(hx, hy);
let mx = hx + Math.sin(ang+bend*0.5)*shaftLen*0.5;
let my = hy - Math.cos(ang+bend*0.5)*shaftLen*0.5;
ctx.quadraticCurveTo(mx + bend*14, my, cx, cy);
ctx.stroke();
ctx.strokeStyle = '#111417';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(hx, hy);
ctx.quadraticCurveTo(hx-9, hy+13, hx+3, hy+18);
ctx.stroke();
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(ang);
drawCanopyShape(umbrella.inversion);
ctx.restore();
} else {
ctx.save();
ctx.translate(umbrella.x, umbrella.y);
ctx.rotate(umbrella.rotation);
ctx.strokeStyle = '#1e2328';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(0, 16);
ctx.lineTo(0, -92);
ctx.stroke();
ctx.strokeStyle = '#111417';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(0,16);
ctx.quadraticCurveTo(-9,29,3,34);
ctx.stroke();
drawCanopyShape(umbrella.inversion);
ctx.restore();
}
}
function drawPerson(){
const pose = getPose();
const groundY = pose.groundY;
const personX = pose.personX;
ctx.lineCap = 'round';
ctx.strokeStyle = '#22262c';
ctx.lineWidth = 9;
let leftFootX = personX - person.stance/2;
let rightFootX = personX + person.stance/2;
ctx.beginPath();
ctx.moveTo(pose.hipX, pose.hipY);
ctx.lineTo(leftFootX, groundY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(pose.hipX, pose.hipY);
ctx.lineTo(rightFootX, groundY);
ctx.stroke();
ctx.fillStyle = '#0f1215';
ctx.beginPath();
ctx.ellipse(leftFootX, groundY+2, 14,5,0,0,Math.PI*2);
ctx.ellipse(rightFootX, groundY+2, 14,5,0,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle = '#b03a2e';
ctx.lineWidth = 15;
ctx.beginPath();
ctx.moveTo(pose.hipX, pose.hipY);
ctx.lineTo(pose.shoulderX, pose.shoulderY);
ctx.stroke();
let flap = Math.sin(time*11 + windSpeed)* Math.min(13, windSpeed*1.1);
ctx.strokeStyle = '#922b21';
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(pose.hipX, pose.hipY);
ctx.quadraticCurveTo(pose.hipX + flap*0.6, pose.hipY+16, pose.hipX + flap*0.35, pose.hipY+26);
ctx.stroke();
let headX = pose.shoulderX + Math.sin(person.lean)*10;
let headY = pose.shoulderY - Math.cos(person.lean)*16 - 7;
ctx.fillStyle = '#f0c9a8';
ctx.beginPath();
ctx.arc(headX, headY, 12.5, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#d9a88c';
ctx.beginPath();
ctx.arc(headX -5, headY+2, 2.2,0,Math.PI*2);
ctx.fill();
ctx.fillStyle = '#1c2a3a';
ctx.beginPath();
ctx.arc(headX, headY-5, 13.5, Math.PI*1.08, Math.PI*1.92);
ctx.fill();
ctx.strokeStyle = '#f1c40f';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(pose.shoulderX, pose.shoulderY+3);
ctx.quadraticCurveTo(pose.shoulderX + windSpeed*0.9 + Math.sin(time*10)*5, pose.shoulderY+8, pose.shoulderX + windSpeed*1.35, pose.shoulderY+12);
ctx.stroke();
if(!umbrella.detached){
let hx = umbrella._handX;
let hy = umbrella._handY;
ctx.strokeStyle = '#b03a2e';
ctx.lineWidth = 7;
let ex1x = (pose.shoulderX + hx)/2 + 7;
let ex1y = (pose.shoulderY + hy)/2 - 7;
ctx.beginPath();
ctx.moveTo(pose.shoulderX, pose.shoulderY);
ctx.quadraticCurveTo(ex1x, ex1y, hx, hy);
ctx.stroke();
let hx2 = hx + 6;
let hy2 = hy + 9;
let ex2x = (pose.shoulderX + hx2)/2 + 4;
let ex2y = (pose.shoulderY + hy2)/2 -5;
ctx.beginPath();
ctx.moveTo(pose.shoulderX+2, pose.shoulderY+4);
ctx.quadraticCurveTo(ex2x, ex2y, hx2, hy2);
ctx.stroke();
ctx.fillStyle = '#f0c9a8';
ctx.beginPath(); ctx.arc(hx, hy, 5,0,Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(hx2, hy2, 5,0,Math.PI*2); ctx.fill();
} else {
let hx = pose.shoulderX -10;
let hy = pose.shoulderY + 18;
ctx.strokeStyle = '#b03a2e';
ctx.lineWidth = 7;
ctx.beginPath();
ctx.moveTo(pose.shoulderX, pose.shoulderY);
ctx.lineTo(hx, hy);
ctx.stroke();
ctx.fillStyle = '#f0c9a8';
ctx.beginPath(); ctx.arc(hx, hy,5,0,Math.PI*2); ctx.fill();
}
}
function frame(now){
let dt = Math.min(0.033, (now - last)/1000);
last = now;
time += dt;
update(dt);
drawBackground();
drawGround();
drawRain();
drawPerson();
drawUmbrella();
if(windSpeed > 28){
ctx.fillStyle = `rgba(231,76,60,${(windSpeed-28)/7*0.12})`;
ctx.fillRect(0,0,W,H);
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
let p0 = getPose();
umbrella._handX = p0.shoulderX -26;
umbrella._handY = p0.shoulderY + 18;
})();
</script>
</body>
</html>
Looks right: the person widens their stance and leans into the wind, the coat-tail and scarf stream, rain streaks tilt and accelerate with wind speed, the shaft bends and the canopy shivers asymmetrically (windward edge lifts), it snaps inside-out with a visible pop and stays inverted until the wind drops, and when wrenched far enough it tears from the hands and tumbles downwind. Looks wrong: the fabric never billows, luffs or tears — inversion is perfectly symmetric instead of peeling from one side first, ribs don’t buckle individually and the shaft is a simple quadratic curve not an Euler beam; there’s no real aerodynamics, sheltering, or two-way coupling (rain falls through the canopy, the umbrella doesn’t spill vortices), the feet slide instead of stepping, grip is a binary torque threshold rather than fingers slipping, and the flyaway is too clean and buoyant — a real umbrella would cartwheel, snag, and shred.