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 a lumped parabolic dome, not a fabric mesh. Its shape is one scalar d (+1 normal, −1 inverted), and its ribs are drawn from that shape, so bending and inversion read as one motion. The umbrella is one rigid body hinged at the hand, with a tilt angle φ. Wind drag acts at a centre of pressure above the hand, so it produces a tipping torque. The wrist is a stiff spring toward a brace target, saturated at a torque limit. Inversion is a sticky two-state flip, not a continuous deformation: when the wind pushes from underneath with a pressure 0.6·(W·sinφ)·|W·sinφ| Pa above a threshold of 70 Pa (scaled down as the ribs fatigue), the dome snaps from +1 to −1 in a fraction of a second. It stays inverted while underside pressure remains above 30% of the threshold, or pops back if over-pressure exceeds 90%. What decides it is therefore the tilt, the wind speed, and the rib health: a canopy tilted into the wind never inverts, while one that the wind has pushed downwind does. Rib fatigue accumulates whenever the load exceeds the rib limit, and once the ribs reach zero the canopy is permanently weakened.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Umbrella in a gale</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#131a24;
font:13px/1.45 system-ui,-apple-system,"Segoe UI",sans-serif;color:#d9e2ec}
canvas{position:fixed;inset:0;width:100%;height:100%;display:block}
#big{position:fixed;left:14px;top:8px;font-size:34px;font-weight:600;
text-shadow:0 1px 6px #000;pointer-events:none}
#big small{display:block;font-size:14px;font-weight:400;opacity:.85}
#tel{position:fixed;left:14px;top:86px;white-space:pre;pointer-events:none;
font:12px/1.5 ui-monospace,Menlo,Consolas,monospace;
background:rgba(8,12,18,.55);padding:6px 9px;border-radius:6px}
#log{position:fixed;right:14px;top:10px;text-align:right;color:#ffd38a;
background:rgba(8,12,18,.45);padding:6px 9px;border-radius:6px;
min-width:220px;pointer-events:none}
#ctl{position:fixed;left:50%;bottom:14px;transform:translateX(-50%);
display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center;
background:rgba(8,12,18,.72);padding:8px 12px;border-radius:9px;max-width:96vw}
#ctl input[type=range]{width:280px}
button{background:#2c3a4d;color:#e6edf5;border:0;padding:6px 11px;
border-radius:6px;cursor:pointer;font:inherit}
button:hover{background:#3a4c63}
#keys{opacity:.6;font-size:11px}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="big"></div>
<div id="tel"></div>
<div id="log"></div>
<div id="ctl">
<button id="auto">Auto ramp: on</button>
<input id="w" type="range" min="0" max="45" step="0.5" value="0" aria-label="target wind speed">
<button id="pause">Pause</button>
<button id="reset">Reset</button>
<span id="keys">↑↓ wind ±1 (shift ±5) · A auto · I flip canopy · Space pause · R reset</span>
</div>
<script>
(() => {
'use strict';
const $ = id => document.getElementById(id);
const cv = $('c'), ctx = cv.getContext('2d');
const elBig = $('big'), elTel = $('tel'), elLog = $('log');
const elW = $('w'), elAuto = $('auto'), elPause = $('pause');
// ---- constants (SI units; drawing scale is pixels per metre) ----
const RHO = 1.2, GRAV = 9.81;
const SHAFT = 0.85, RC = 0.5, HD = 0.3; // shaft length, canopy radius, dome depth
const CD = 1.2, ASIDE = 0.25, ADISK = 0.785; // drag coeff, side-on area, disc area
const A_FREE = 0.4; // free-flying canopy drag area
const M_U = 0.5, I_U = 0.25, LCG = 0.45, C_HINGE = 0.5;
const P_BREAK0 = 150, P_INV0 = 70; // Pa: rib limit, inversion threshold
const GMAX0 = 140, TMAX0 = 40; // N: grip holding force, N·m: wrist/arm torque
const M_P = 70, A_P = 0.5, CD_P = 1.0, MU = 0.55; // person mass, side area, drag, footing friction
const FIX = 1/240, WMAX = 45, RAMP = 0.6;
const BF = [0.3,1.6,3.4,5.5,8.0,10.8,13.9,17.2,20.8,24.5,28.5,32.7];
const DEG = 180/Math.PI;
const NRAIN = 280;
const clamp = (v,a,b) => v < a ? a : (v > b ? b : v);
let W_px = 1, H_px = 1, ppm = 100, OX = 0, GY = 0;
const rain = [], streaks = [];
let S;
function mk(){
return {
t:0, wTarget:0, wMean:0, wInst:0, auto:true, paused:false,
lean:-0.05, px:0, pvx:0,
phi:0, om:0, d:1, dEff:1, inv:false, frame:1, snapped:false,
attached:true, b:0.6, grip:0.88, gmax:GMAX0*0.88,
load:0, Fx:0, Fn:0, pu:0, po:0, tauH:0, slip:false, wasSlip:false,
fx:0, fy:0, fvx:0, fvy:0, fa:0, fom:0,
log:[]
};
}
function note(s){
S.log.unshift({s, t:S.t});
if (S.log.length > 5) S.log.length = 5;
}
// Body kinematics (metres). Feet on ground at x = px, hips at y = 0.95.
function body(){
const L = S.lean, sL = Math.sin(L), cL = Math.cos(L);
const sx = S.px + 0.6*sL, sy = 0.95 + 0.6*cL; // shoulder
const hdx = sx + 0.26*sL, hdy = sy + 0.26*cL; // head centre
const a = L + 0.9;
const ax = sx + 0.5*Math.sin(a), ay = sy + 0.5*Math.cos(a); // hand = umbrella pivot
return {sx, sy, hdx, hdy, ax, ay};
}
function release(){
if (!S.attached) return;
const b = body();
S.attached = false;
S.Fx = 0;
S.fx = b.ax; S.fy = b.ay;
S.fvx = 0.3*S.wInst; S.fvy = 0.5;
S.fa = S.phi; S.fom = S.om;
note('UMBRELLA GONE: it left the hand');
}
function step(dt){
S.t += dt;
if (S.auto) S.wTarget = Math.min(WMAX, S.wTarget + RAMP*dt);
S.wMean += (S.wTarget - S.wMean) * Math.min(1, dt*1.5);
const t = S.t;
const gust = 1 + 0.12*Math.sin(t*1.9) + 0.07*Math.sin(t*4.7+1.3) + 0.05*Math.sin(t*11.3+0.4);
S.wInst = Math.max(0, S.wMean*gust);
const W = S.wInst;
if (S.attached){
const sp = Math.sin(S.phi), cp = Math.cos(S.phi);
// drag: side-on area grows as the canopy turns toward the flow
const Aproj = ASIDE + ADISK*0.6*Math.abs(sp);
const cdEff = CD * (S.inv ? 1.35 : 1) * (S.snapped ? 1.5 : 1);
const Fx = 0.5*RHO*cdEff*Aproj*W*W; // always downwind
// pressure on the canopy along its axis: + = wind underneath
const p = 0.6*W*sp*Math.abs(W*sp);
const pu = Math.max(0, p), po = Math.max(0, -p);
const Fn = p*ADISK;
const load = Math.hypot(Fx, Fn);
// bracing and grip respond to load
const stress = load / GMAX0;
S.b = clamp(0.6 + 0.4*stress, 0.6, 1);
S.grip = 0.7 + 0.3*S.b;
const tmax = TMAX0*S.grip;
S.gmax = GMAX0*S.grip*(S.snapped ? 0.6 : 1);
// drag torque about the hand; centre of pressure higher when inverted
const rcp = SHAFT*(S.inv ? 1.1 : 0.9);
const tauD = rcp*cp*Fx;
// the hand tries to hold the brace target, and the wrist saturates
const phiT = -0.1 - 0.25*(S.b-0.6)/0.4;
let tauH = -70*S.b*(S.phi - phiT) - 9*S.b*S.om;
S.slip = Math.abs(tauH) > tmax;
tauH = clamp(tauH, -tmax, tmax);
if (S.slip && !S.wasSlip) note('Wrist giving: the umbrella swings downwind');
S.wasSlip = S.slip;
const tauG = -M_U*GRAV*LCG*sp;
const acc = (tauD + tauG + tauH - C_HINGE*S.om) / I_U;
S.om += acc*dt;
S.phi = clamp(S.phi + S.om*dt, -2.4, 2.4);
// inversion: sticky two-state flip with hysteresis
const PINV = P_INV0*(0.5 + 0.5*S.frame);
if (!S.inv && pu > PINV){
S.inv = true; note('Canopy inverted: the wind got underneath');
} else if (S.inv && (pu < 0.3*PINV || po > 0.9*PINV)){
S.inv = false; note('Canopy popped back');
}
const dT = S.inv ? -1 : 1;
S.d += (dT - S.d) * Math.min(1, dt*(S.inv ? 8 : 5));
S.dEff = S.d * (1 - 0.5*Math.min(1, po/120));
// ribs load up; overload fatigues them until they snap
const Lr = Math.max(pu*(S.inv ? 1.6 : 1), po*0.7) / (P_BREAK0*(0.4 + 0.6*S.frame));
if (Lr > 1 && !S.snapped){
S.frame = Math.max(0, S.frame - dt*(Lr-1)*1.6);
if (S.frame <= 0.02){ S.frame = 0; S.snapped = true; note('A rib snapped'); }
}
S.load = load; S.Fx = Fx; S.Fn = Fn; S.pu = pu; S.po = po; S.tauH = tauH;
if (load > S.gmax) release();
} else {
// free canopy: drag relative to air, gravity, ground, lazy tumble
const Wr = W - S.fvx;
const ax = 0.5*RHO*CD*A_FREE*Wr*Math.abs(Wr)/M_U;
S.fvx += ax*dt;
S.fvy -= GRAV*dt;
S.fx += S.fvx*dt; S.fy += S.fvy*dt;
if (S.fy < 0.05){
S.fy = 0.05;
if (S.fvy < 0) S.fvy = -S.fvy*0.3;
S.fvx *= Math.exp(-1.5*dt);
}
S.fa += S.fom*dt;
S.fom += (0.08*Wr - 3*Math.sin(S.fa) - 0.5*S.fom)*dt;
S.dEff = S.inv ? -1 : 1;
}
// person: lean, footing
const Fp = 0.5*RHO*CD_P*A_P*W*W;
const Fh = Fp + (S.attached ? S.Fx : 0);
const fr = MU*M_P*GRAV;
const sgnv = S.pvx > 0.01 ? 1 : (S.pvx < -0.01 ? -1 : 0);
if (sgnv !== 0 || Math.abs(Fh) > fr){
const s = sgnv || Math.sign(Fh);
let nv = S.pvx + (Fh - s*fr)/M_P*dt;
if (sgnv !== 0 && Math.sign(nv) !== sgnv && Math.abs(Fh) <= fr) nv = 0;
S.pvx = nv;
} else {
S.pvx = 0;
}
S.px = clamp(S.px + S.pvx*dt, -3, 3);
if (Math.abs(S.px) >= 3) S.pvx = 0;
const Lt = -0.04 - 0.012*W + 0.1*Math.tanh(S.pvx) + (S.inv ? 0.05 : 0);
S.lean = clamp(S.lean + (Lt - S.lean)*Math.min(1, dt*2.5), -0.7, 0.6);
}
// ---- rain and background streaks ----
function initRain(){
rain.length = 0; streaks.length = 0;
for (let i=0;i<NRAIN;i++) rain.push({x:Math.random()*W_px, y:Math.random()*H_px,
z:0.6+Math.random()*0.6, vx:0, vy:0});
for (let i=0;i<50;i++) streaks.push({x:Math.random()*W_px, y:Math.random()*GY,
l:40+Math.random()*120, z:0.5+Math.random()});
}
function updRain(dt){
const W = S.wInst;
for (const p of rain){
p.vy = 6.5*ppm*p.z; // terminal fall
p.vx = 0.4*W*ppm*p.z; // drops are carried by the wind
p.x += p.vx*dt; p.y += p.vy*dt;
if (p.y > GY){ p.y = -Math.random()*80; p.x = Math.random()*(W_px+200) - 100; }
if (p.x > W_px+60) p.x -= W_px+160;
}
for (const s of streaks){
s.x += W*ppm*0.9*s.z*dt;
if (s.x - s.l > W_px){ s.x = -s.l; s.y = Math.random()*GY; }
}
}
// ---- drawing ----
const X = x => OX + x*ppm;
const Y = y => GY - y*ppm;
function seg(x1,y1,x2,y2,wpx,color){
ctx.strokeStyle = color; ctx.lineWidth = wpx;
ctx.beginPath(); ctx.moveTo(X(x1),Y(y1)); ctx.lineTo(X(x2),Y(y2)); ctx.stroke();
}
function drawBG(){
const g = ctx.createLinearGradient(0,0,0,H_px);
g.addColorStop(0,'#2b3648'); g.addColorStop(0.7,'#1c2433'); g.addColorStop(1,'#10151e');
ctx.fillStyle = g; ctx.fillRect(0,0,W_px,H_px);
ctx.strokeStyle = 'rgba(200,215,235,0.10)'; ctx.lineWidth = 1.2;
ctx.beginPath();
for (const s of streaks){ ctx.moveTo(s.x - s.l, s.y); ctx.lineTo(s.x, s.y); }
ctx.stroke();
ctx.fillStyle = '#0d1219'; ctx.fillRect(0,GY,W_px,H_px-GY);
ctx.strokeStyle = '#3c4d62'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(0,GY); ctx.lineTo(W_px,GY); ctx.stroke();
}
function drawPerson(b){
ctx.lineCap = 'round';
const px = S.px;
seg(px-0.14, 0.02, px, 0.95, 0.16*ppm, '#252f3d');
seg(px+0.14, 0.02, px, 0.95, 0.16*ppm, '#2f3a4b');
seg(px, 0.95, b.sx, b.sy, 0.32*ppm, '#3d5a80');
seg(b.sx, b.sy, b.ax, b.ay, 0.11*ppm, '#3d5a80');
ctx.fillStyle = '#e0b48e';
ctx.beginPath(); ctx.arc(X(b.hdx), Y(b.hdy), 0.12*ppm, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = '#2a2420';
ctx.beginPath(); ctx.arc(X(b.hdx), Y(b.hdy)+0.02*ppm, 0.125*ppm, Math.PI, 2*Math.PI); ctx.fill();
}
function drawUmb(hx, hy, phi){
const sp = Math.sin(phi), cp = Math.cos(phi);
const apx = hx + SHAFT*sp, apy = hy + SHAFT*cp;
const N = 28, top = [], bot = [];
const amp = (S.attached ? 1 : 0.6) * 0.012 * clamp(S.wInst/10, 0, 3);
for (let i=0;i<=N;i++){
const u = -1 + 2*i/N;
const fl = amp*Math.sin(S.t*19 + u*5.5);
const a = HD*S.dEff*(0.5 - u*u) + fl;
const a2 = a - 0.025;
top.push([X(apx + RC*u*cp + a*sp), Y(apy - RC*u*sp + a*cp)]);
bot.push([X(apx + RC*u*cp + a2*sp), Y(apy - RC*u*sp + a2*cp)]);
}
// shaft and handle
seg(hx, hy, apx, apy, 3, '#9aa7b8');
seg(hx, hy, hx - 0.14*sp, hy - 0.14*cp, 5, '#6b5240');
// panels, alternating colours; inverted canopy is darker and shows its underside
const c1 = S.inv ? '#6b2b36' : '#d24a3c';
const c2 = S.inv ? '#8f7a62' : '#f3e7cf';
for (let k=0;k<N;k++){
ctx.beginPath();
ctx.moveTo(top[k][0], top[k][1]);
ctx.lineTo(top[k+1][0], top[k+1][1]);
ctx.lineTo(bot[k+1][0], bot[k+1][1]);
ctx.lineTo(bot[k][0], bot[k][1]);
ctx.closePath();
ctx.fillStyle = (k%2) ? c2 : c1;
ctx.fill();
}
// outline
ctx.strokeStyle = 'rgba(0,0,0,0.45)'; ctx.lineWidth = 1;
ctx.beginPath(); top.forEach((p,i)=> i ? ctx.lineTo(p[0],p[1]) : ctx.moveTo(p[0],p[1]));
ctx.stroke();
// ribs: colour shows how loaded the frame is
const rc = S.snapped ? '#ff2a2a' : ((1 - S.frame) > 0.4 ? '#ff7a4a' : '#dfe6ee');
ctx.strokeStyle = rc; ctx.lineWidth = 1.6;
ctx.beginPath();
for (const k of [0,7,14,21,28]){
ctx.moveTo(X(apx), Y(apy)); ctx.lineTo(top[k][0], top[k][1]);
}
ctx.stroke();
}
function drawRain(){
ctx.strokeStyle = 'rgba(190,210,235,0.42)'; ctx.lineWidth = 1.2;
ctx.beginPath();
for (const p of rain){
const k = 0.035;
ctx.moveTo(p.x, p.y); ctx.lineTo(p.x - p.vx*k, p.y - p.vy*k);
}
ctx.stroke();
}
function draw(){
drawBG();
if (S.attached){
const b = body();
drawPerson(b);
drawUmb(b.ax, b.ay, S.phi);
} else {
drawPerson(body());
drawUmb(S.fx, S.fy, S.fa);
}
drawRain();
}
// ---- HUD ----
function hud(){
const W = S.wInst;
let bf = BF.findIndex(v => W < v); if (bf < 0) bf = 12;
elBig.innerHTML = `→ ${W.toFixed(1)} m/s<small>${(W*3.6).toFixed(0)} km/h · Beaufort ${bf} · target ${S.wTarget.toFixed(1)}</small>`;
if (document.activeElement !== elW) elW.value = S.wTarget;
elAuto.textContent = 'Auto ramp: ' + (S.auto ? 'on' : 'off');
elPause.textContent = S.paused ? 'Resume' : 'Pause';
const lines = [
`umbrella ${S.attached ? 'held, tilt ' + (S.phi*DEG).toFixed(0) + '°' : 'GONE'}`,
`canopy ${S.inv ? 'INVERTED' : 'normal'} ribs ${(S.frame*100).toFixed(0)}%${S.snapped ? ' (snapped)' : ''}`,
`hand load ${S.load.toFixed(0)} N / ${S.gmax.toFixed(0)} N grip ${(S.grip*100).toFixed(0)}% brace ${(S.b*100).toFixed(0)}%`,
`wrist ${S.slip ? 'SLIPPING' : 'holding'} torque ${S.tauH.toFixed(0)} N·m`,
`body lean ${(S.lean*DEG).toFixed(0)}° footing ${Math.abs(S.pvx) > 0.02 ? 'skidding' : 'holding'} (${S.px.toFixed(2)} m)`
];
elTel.textContent = lines.join('\n');
elLog.innerHTML = S.log.filter(e => S.t - e.t < 8).map(e => e.s).join('<br>');
}
// ---- control ----
function setTarget(v){ S.wTarget = clamp(v, 0, WMAX); }
function reset(){
S = mk();
elW.value = 0;
note('Reset: umbrella back in hand');
}
elW.addEventListener('input', () => { S.wTarget = +elW.value; S.auto = false; });
elAuto.addEventListener('click', () => { S.auto = !S.auto; });
elPause.addEventListener('click', () => { S.paused = !S.paused; });
$('reset').addEventListener('click', reset);
addEventListener('keydown', e => {
if (e.key === 'ArrowUp' || e.key === 'ArrowDown'){
e.preventDefault();
S.auto = false;
setTarget(S.wTarget + (e.key === 'ArrowUp' ? 1 : -1)*(e.shiftKey ? 5 : 1));
} else if (e.key === ' '){
e.preventDefault(); S.paused = !S.paused;
} else if (e.key === 'a' || e.key === 'A'){
S.auto = !S.auto;
} else if (e.key === 'i' || e.key === 'I'){
if (S.attached){ S.inv = !S.inv; note('Flipped by hand'); }
} else if (e.key === 'r' || e.key === 'R'){
reset();
}
});
function resize(){
const dpr = Math.min(2, window.devicePixelRatio || 1);
W_px = innerWidth; H_px = innerHeight;
cv.width = Math.round(W_px*dpr); cv.height = Math.round(H_px*dpr);
ctx.setTransform(dpr,0,0,dpr,0,0);
ppm = Math.min(W_px/9.5, H_px/5.6);
OX = W_px*0.5; GY = H_px*0.86;
}
addEventListener('resize', resize);
// ---- main loop: fixed physics step, variable render ----
S = mk();
resize();
initRain();
let last = performance.now(), acc = 0;
function frame(now){
const dt = Math.min(0.05, (now - last)/1000); last = now;
if (!S.paused){
acc += dt;
let n = 0;
while (acc >= FIX && n < 40){ step(FIX); acc -= FIX; n++; }
if (n === 40) acc = 0;
}
updRain(dt);
draw();
hud();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Looks right:
Looks wrong, or is a fudge:
0.25 + 0.47·|sinφ|) with a fixed Cd, not a real Cd for the cup or dome shape. The side-on view also makes the umbrella's yaw invisible.