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 discrete chain of mass points strung along an arc beneath the shaft; each point is tied to the shaft tip by a rib spring, to its neighbours by a hoop spring, and softly to a target arc that represents the fabric’s resting shape. Inversion is a state change: once sustained wind pressure crosses a threshold, an interpolation flips the sign of the canopy’s vertical offset, turning the dome into a bowl, and the umbrella stays broken even if the wind later drops. A separate, higher wind threshold starts a grip timer; if the gale is held long enough, the handle snaps from the hand and the umbrella becomes a wind-blown projectile.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Umbrella in the Gale</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #9db8d2; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
#canvas { display: block; width: 100%; height: 100%; }
#ui { position: absolute; top: 16px; left: 16px; pointer-events: none; }
#ui > * { pointer-events: auto; }
.hud { background: rgba(255,255,255,0.9); border-radius: 10px; padding: 14px 18px; box-shadow: 0 4px 14px rgba(0,0,0,0.18); border: 1px solid rgba(255,255,255,0.6); }
.wind-row { display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; }
.wind-big { font-size: 34px; font-weight: 900; color: #1d4e89; }
.wind-unit { font-size: 13px; color: #666; margin-left: 2px; }
.control { font-size: 13px; color: #333; margin-bottom: 6px; }
.control label { display: block; margin: 3px 0; cursor: pointer; }
input[type=range] { width: 190px; vertical-align: middle; }
.hint { font-size: 11.5px; color: #777; margin-top: 6px; line-height: 1.4; }
.status { margin-top: 8px; font-weight: 700; font-size: 13px; }
.ok { color: #2e7d32; }
.bad { color: #c62828; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="ui">
<div class="hud">
<div class="wind-row">
<span>WIND</span>
<span id="windVal" class="wind-big">0.0</span>
<span class="wind-unit">m/s</span>
</div>
<div class="control">
<label><input type="range" id="windSlider" min="0" max="140" value="0"> <span id="sliderVal">0</span></label>
</div>
<div class="control">
<label><input type="checkbox" id="autoWind" checked> Let the gale rise on its own</label>
</div>
<div class="hint">↑ / → to push up · ↓ / ← to ease back · grab the slider to seize control</div>
<div id="status" class="status ok">CANOPY: HOLDING</div>
</div>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d', { alpha: false });
let W, H;
function resize(){ W = canvas.width = innerWidth; H = canvas.height = innerHeight; }
addEventListener('resize', resize);
resize();
/* ---------- State ---------- */
let wind = 0;
let targetWind = 0;
let windAuto = true;
const WIND_MAX = 140;
const umbrella = {
attached: true,
inverted: false,
invProg: 0,
handle: { x: 0, y: 0 },
top: { x: 0, y: 0, vx: 0, vy: 0 },
shaftAng: 0,
canopy: [],
ptCount: 25,
radius: 78,
stiffness: 0.11,
ribStiff: 0.13,
hoopStiff: 0.05,
damping: 0.89,
gravity: 0.12,
windForce: 0.07,
liftForce: 0.04,
detachTimer: 0,
DETACH_FRAMES: 45,
INVERT_THRESH: 72,
DETACH_THRESH: 112
};
const person = {
cx: 0, footY: 0,
lean: 0,
shoulder: { x: 0, y: 0 },
hand: { x: 0, y: 0 },
armAng: -0.35
};
const rain = [];
const RAIN_N = 150;
for (let i = 0; i < RAIN_N; i++) {
rain.push({
x: Math.random() * W,
y: Math.random() * H,
vx: 0,
vy: 2 + Math.random() * 3,
len: 5 + Math.random() * 8
});
}
/* ---------- Input ---------- */
const $s = document.getElementById('windSlider');
const $sv = document.getElementById('sliderVal');
const $wv = document.getElementById('windVal');
const $auto = document.getElementById('autoWind');
const $status = document.getElementById('status');
function setTarget(v){
targetWind = Math.max(0, Math.min(WIND_MAX, v));
$s.value = targetWind;
$sv.textContent = Math.round(targetWind);
}
$s.addEventListener('input', e => {
setTarget(parseFloat(e.target.value));
windAuto = false;
$auto.checked = false;
});
$auto.addEventListener('change', e => { windAuto = e.target.checked; });
addEventListener('keydown', e => {
const k = e.key;
if (k === 'ArrowUp' || k === 'ArrowRight') { setTarget(targetWind + 4); windAuto = false; $auto.checked = false; e.preventDefault(); }
if (k === 'ArrowDown' || k === 'ArrowLeft') { setTarget(targetWind - 4); windAuto = false; $auto.checked = false; e.preventDefault(); }
});
/* ---------- Canopy init ---------- */
function buildCanopy(){
umbrella.canopy = [];
const step = Math.PI / (umbrella.ptCount - 1);
for (let i = 0; i < umbrella.ptCount; i++) {
umbrella.canopy.push({
a: step * i,
x: 0, y: 0,
vx: 0, vy: 0
});
}
}
buildCanopy();
/* ---------- Update ---------- */
function update(){
/* Wind */
if (windAuto) {
targetWind += 0.014;
if (targetWind > WIND_MAX) targetWind = WIND_MAX;
}
wind += (targetWind - wind) * 0.05;
$wv.textContent = wind.toFixed(1);
/* Person anchor */
person.cx = W * 0.46;
person.footY = H - 52;
const hip = { x: person.cx, y: person.footY - 112 };
/* Lean into wind (wind blows +x, so lean -x) */
const targetLean = -Math.min(0.85, wind * 0.010);
person.lean += (targetLean - person.lean) * 0.08;
const sLen = 112;
person.shoulder.x = hip.x + Math.sin(person.lean) * sLen;
person.shoulder.y = hip.y - Math.cos(person.lean) * sLen;
/* Arm reaches up and slightly downwind */
person.armAng = -0.4 + person.lean * 0.5 + Math.min(0.5, wind * 0.005);
person.hand.x = person.shoulder.x + Math.sin(person.armAng) * 98;
person.hand.y = person.shoulder.y - Math.cos(person.armAng) * 98;
/* Umbrella handle pinned to hand while attached */
if (umbrella.attached) {
umbrella.handle.x = person.hand.x;
umbrella.handle.y = person.hand.y;
}
/* Shaft bends downwind */
const targetShaft = Math.min(1.2, wind * 0.009);
umbrella.shaftAng += (targetShaft - umbrella.shaftAng) * 0.06;
const shaftLen = 156;
if (umbrella.attached) {
umbrella.top.x = umbrella.handle.x + Math.sin(umbrella.shaftAng) * shaftLen;
umbrella.top.y = umbrella.handle.y - Math.cos(umbrella.shaftAng) * shaftLen;
umbrella.top.vx = umbrella.top.vy = 0;
} else {
/* Flying projectile */
umbrella.top.vx += wind * 0.025;
umbrella.top.vy += umbrella.gravity;
umbrella.top.x += umbrella.top.vx;
umbrella.top.y += umbrella.top.vy;
umbrella.top.vx *= 0.97;
umbrella.top.vy *= 0.97;
umbrella.shaftAng += 0.06;
}
/* Inversion state change */
if (!umbrella.inverted && wind > umbrella.INVERT_THRESH) {
umbrella.invProg += 0.014;
if (umbrella.invProg >= 1) { umbrella.invProg = 1; umbrella.inverted = true; }
}
const sign = 1 - 2 * umbrella.invProg; /* +1 dome, -1 bowl */
/* Canopy targets */
const cx = umbrella.top.x, cy = umbrella.top.y;
for (const p of umbrella.canopy) {
p.tx = cx + Math.cos(p.a) * umbrella.radius;
p.ty = cy - Math.sin(p.a) * umbrella.radius * sign;
}
/* Canopy physics */
const n = umbrella.canopy.length;
for (let i = 0; i < n; i++) {
const p = umbrella.canopy[i];
let fx = wind * umbrella.windForce;
let fy = umbrella.gravity - wind * umbrella.liftForce * Math.sin(p.a);
fx += (p.tx - p.x) * umbrella.stiffness;
fy += (p.ty - p.y) * umbrella.stiffness;
fx += (cx - p.x) * umbrella.ribStiff;
fy += (cy - p.y) * umbrella.ribStiff;
const prev = umbrella.canopy[(i - 1 + n) % n];
const next = umbrella.canopy[(i + 1) % n];
fx += (prev.x - p.x) * umbrella.hoopStiff + (next.x - p.x) * umbrella.hoopStiff;
fy += (prev.y - p.y) * umbrella.hoopStiff + (next.y - p.y) * umbrella.hoopStiff;
p.vx += fx; p.vy += fy;
p.vx *= umbrella.damping; p.vy *= umbrella.damping;
p.x += p.vx; p.y += p.vy;
}
/* Detachment */
if (umbrella.attached) {
if (wind > umbrella.DETACH_THRESH) {
umbrella.detachTimer++;
if (umbrella.detachTimer > umbrella.DETACH_FRAMES) {
umbrella.attached = false;
umbrella.top.vx = wind * 0.45;
umbrella.top.vy = -6;
}
} else {
umbrella.detachTimer = Math.max(0, umbrella.detachTimer - 1);
}
}
/* Rain driven by wind */
for (const r of rain) {
r.vx = wind * 0.38;
r.vy = 5.5 + wind * 0.05;
r.x += r.vx;
r.y += r.vy;
if (r.y > H) { r.y = -20; r.x = Math.random() * W; }
if (r.x > W + 20) r.x = -20;
if (r.x < -20) r.x = W + 20;
}
/* Status text */
if (!umbrella.attached) $status.textContent = 'CANOPY: GONE';
else if (umbrella.inverted) $status.textContent = 'CANOPY: INSIDE OUT';
else if (wind > umbrella.INVERT_THRESH * 0.8) $status.textContent = 'CANOPY: STRAINING';
else $status.textContent = 'CANOPY: HOLDING';
$status.className = 'status ' + (!umbrella.attached || umbrella.inverted ? 'bad' : 'ok');
}
/* ---------- Draw ---------- */
function draw(){
/* Sky darkens with wind */
const sky = Math.min(1, wind / 140);
ctx.fillStyle = `rgb(${Math.round(155 - 70*sky)},${Math.round(185 - 90*sky)},${Math.round(215 - 110*sky)})`;
ctx.fillRect(0, 0, W, H);
/* Rain */
ctx.strokeStyle = `rgba(170, 205, 250, ${0.25 + 0.45*sky})`;
ctx.lineWidth = 1.6;
ctx.lineCap = 'round';
ctx.beginPath();
for (const r of rain) {
ctx.moveTo(r.x, r.y);
ctx.lineTo(r.x - r.vx * 1.8, r.y - r.vy * 1.8);
}
ctx.stroke();
/* Ground */
ctx.strokeStyle = '#6b7a52';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(0, person.footY);
ctx.lineTo(W, person.footY);
ctx.stroke();
/* Feet */
ctx.fillStyle = '#333';
const spread = Math.min(34, wind * 0.28);
ctx.fillRect(person.cx - 20 - spread, person.footY, 24, 11);
ctx.fillRect(person.cx + 6 + spread, person.footY, 24, 11);
/* Legs */
ctx.strokeStyle = '#5a4430';
ctx.lineWidth = 8;
ctx.lineCap = 'round';
const hip = { x: person.cx, y: person.footY - 112 };
ctx.beginPath();
ctx.moveTo(hip.x, hip.y);
ctx.lineTo(person.cx - 20 - spread, person.footY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(hip.x, hip.y);
ctx.lineTo(person.cx + 6 + spread, person.footY);
ctx.stroke();
/* Torso */
ctx.beginPath();
ctx.moveTo(hip.x, hip.y);
ctx.lineTo(person.shoulder.x, person.shoulder.y);
ctx.stroke();
/* Head */
const hx = person.shoulder.x + Math.sin(person.lean) * 12;
const hy = person.shoulder.y - 30;
ctx.fillStyle = '#e8cfa8';
ctx.beginPath();
ctx.arc(hx, hy, 16, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#5d4037';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(hx - 7, hy - 2); ctx.lineTo(hx - 3, hy + 2);
ctx.moveTo(hx + 3, hy - 2); ctx.lineTo(hx + 7, hy + 2);
ctx.stroke();
/* Arm */
ctx.beginPath();
ctx.moveTo(person.shoulder.x, person.shoulder.y);
ctx.lineTo(person.hand.x, person.hand.y);
ctx.stroke();
/* Hand */
ctx.fillStyle = umbrella.attached ? '#c99070' : '#e8cfa8';
ctx.beginPath();
ctx.arc(person.hand.x, person.hand.y, 8, 0, Math.PI * 2);
ctx.fill();
/* Grip strain marks */
if (wind > 45 && umbrella.attached) {
ctx.strokeStyle = 'rgba(190, 40, 40, 0.35)';
ctx.lineWidth = 2;
for (let i = 0; i < Math.min(6, wind / 18); i++) {
const jitter = (Math.random() - 0.5) * 8;
ctx.beginPath();
ctx.moveTo(person.hand.x + jitter, person.hand.y + jitter);
ctx.lineTo(person.hand.x + Math.sin(person.armAng) * 14 + jitter,
person.hand.y - Math.cos(person.armAng) * 14 + jitter);
ctx.stroke();
}
}
/* Umbrella */
if (umbrella.attached) {
/* Shaft */
ctx.strokeStyle = '#777';
ctx.lineWidth = 4.5;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(umbrella.handle.x, umbrella.handle.y);
ctx.lineTo(umbrella.top.x, umbrella.top.y);
ctx.stroke();
/* Ribs */
ctx.strokeStyle = '#555';
ctx.lineWidth = 1.4;
for (const p of umbrella.canopy) {
ctx.beginPath();
ctx.moveTo(umbrella.top.x, umbrella.top.y);
ctx.lineTo(p.x, p.y);
ctx.stroke();
}
/* Canopy fabric */
const col = umbrella.inverted ? '#e4572e' : '#f2c94c';
const fill = umbrella.inverted ? 'rgba(228,87,46,0.4)' : 'rgba(242,201,76,0.45)';
ctx.strokeStyle = col;
ctx.fillStyle = fill;
ctx.lineWidth = 2.2;
ctx.beginPath();
ctx.moveTo(umbrella.canopy[0].x, umbrella.canopy[0].y);
for (let i = 1; i < umbrella.canopy.length; i++) {
ctx.lineTo(umbrella.canopy[i].x, umbrella.canopy[i].y);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
} else {
/* Flying away */
ctx.save();
ctx.translate(umbrella.top.x, umbrella.top.y);
ctx.rotate(umbrella.shaftAng);
ctx.strokeStyle = '#777';
ctx.lineWidth = 4.5;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -156);
ctx.stroke();
ctx.strokeStyle = '#e4572e';
ctx.fillStyle = 'rgba(228,87,46,0.4)';
ctx.lineWidth = 2.2;
ctx.beginPath();
ctx.moveTo(0, -156);
const ca = Math.cos(umbrella.shaftAng), sa = Math.sin(umbrella.shaftAng);
for (const p of umbrella.canopy) {
const dx = p.x - umbrella.top.x;
const dy = p.y - umbrella.top.y;
ctx.lineTo(dx * ca - dy * sa, dx * sa + dy * ca);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
/* ---------- Loop ---------- */
function loop(){
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
It looks right in the broad beats: the canopy billows and loads up as the slider rises, the shaft leans downwind, the person leans into the gale and widens their stance, rain slants harder as the wind builds, and past roughly 72 m/s the canopy flips inside out and stays that way. Hold past roughly 112 m/s and the grip breaks, sending the umbrella tumbling away as a projectile while the person’s arm is left empty.
It looks wrong in the details you’d spot if you’d actually been caught in a gale. Because this is a side view, the umbrella is a 2D arc rather than a real fabric dome, so “inside out” reads as a curve snapping backward rather than a canopy collapsing and re-inflating inside-out. The rain is cosmetic streaks that never collide with the person or the umbrella. The physics is explicit Euler with springs, so at extreme wind the canopy can poke through the shaft or the person’s head, and there is no real friction or collision—just spring tension and damping. The auto-gale rises monotonically; there are no gusts, lulls, or sudden shifts in direction. Once the umbrella is gone or inverted, it never recovers, and the hand-to-handle connection is visual rather than a true constraint, so under violent load the handle can appear to separate slightly from the fingers before the detachment timer fires.