27:18Gemini 4 Hydrogen: Cheaper Tokens, More Expensive Answers?
Nov 13, 2026 · YouTube
36:22Fable 5.2 robbing me blind, plus: Nex AGI's N2.6 vs GLM 5.3 Flash
Nov 11, 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 dropped a garden hose with the water on full.
Build me a single HTML page: a garden hose lying on a lawn, the spigot on, and the nozzle released.
#psi=40 sets the pressure to 40 psi with the nozzle free on load. Any number works.data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file: how the hose discretization and inextensibility constraints are implemented, the formula for the nozzle reaction force as a function of psi, and ground friction handling.
The pressure in psi at which the hose transitions from stationary or settling to continuous whipping, as one number, derived from your model parameters.
The single fenced html block.
Where the motion looks like a real hose and where it does not. Name any artificial damping, injected noise or scripted oscillation. Everyone has watched a hose get away from somebody; say what yours gets wrong before I see it.
The hose is a chain of 35 equal-length, massive segments, with a short spigot fitting holding the first segment’s direction. Fixed-distance constraints enforce inextensibility; compliant angular constraints supply bending stiffness (B=0.70\ \mathrm{N,m^2}). The wet hose is 4 m long and weighs 1.4 kg. For a 2.5 mm nozzle with discharge coefficient (C_d=0.85), the backward, tangent-following reaction is (F=2C_d^2A(6894.76,p)=0.04891p\ \mathrm N), where (p) is psi. Ground contact uses Coulomb friction with (\mu=0.018), plus velocity-dependent drag. An engineering onset estimate is ([20.05B/L^2+\mu Mg]/0.04891\approx23) psi; the beam coefficient is an approximation, not an exact stability solution for the discrete, frictional hose.
23
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Loose End — a hose experiment</title>
<style>
:root{color-scheme:light;--ink:#203829;--paper:#f5f2df;--accent:#ec6038}
*{box-sizing:border-box}
html,body{margin:0;height:100%;overflow:hidden}
body{background:var(--paper);color:var(--ink);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;display:flex;flex-direction:column}
header{padding:20px 26px 16px;display:flex;align-items:center;justify-content:space-between;gap:18px;border-bottom:1px solid #20382924}
h1{font-family:Georgia,serif;font-size:30px;font-weight:normal;letter-spacing:-1px;margin:0}
.eyebrow{text-transform:uppercase;letter-spacing:2px;font-size:10px;font-weight:750;margin-bottom:4px}
.subtitle{font-size:12px;opacity:.7;margin-top:4px}
.badge{font-size:11px;text-align:right;line-height:1.7;white-space:nowrap}
.badge b{color:var(--accent)}
main{position:relative;flex:1;min-height:0;overflow:hidden;background:#6b914a}
canvas{display:block;width:100%;height:100%;touch-action:none;cursor:grab}
canvas:active{cursor:grabbing}
.note{position:absolute;left:18px;top:15px;pointer-events:none;color:#f9f5d9;font-size:11px;letter-spacing:.25px;text-shadow:0 1px 3px #294c31}
.key{position:absolute;right:16px;bottom:13px;color:#f9f5d9;font-size:10px;pointer-events:none;opacity:.85}
footer{padding:16px 26px 18px;border-top:1px solid #20382922}
.controls{display:flex;align-items:center;gap:18px}
.readout{width:90px;flex:none}
#psiOut{font-size:32px;letter-spacing:-1px;font-variant-numeric:tabular-nums;line-height:1}
.unit{font-size:12px;margin-left:4px}
.track{flex:1}
input[type=range]{width:100%;accent-color:var(--accent);cursor:pointer}
.ends{display:flex;justify-content:space-between;font-size:10px;opacity:.6;margin-top:3px}
button{border:1px solid #20382944;background:transparent;color:var(--ink);border-radius:30px;padding:9px 13px;cursor:pointer;font:inherit;font-size:11px;white-space:nowrap}
button:hover{background:#2038290b}
.bottom{display:flex;justify-content:space-between;gap:12px;font-size:10px;opacity:.68;margin-top:13px}
#state{font-weight:750;text-transform:uppercase;letter-spacing:1px}
@media(max-width:540px){
header{padding:14px 16px}h1{font-size:26px}.badge{font-size:9px}
footer{padding:13px 16px}.controls{gap:10px}.readout{width:73px}
#psiOut{font-size:28px}.subtitle{font-size:10px}.bottom{font-size:9px}
button{padding:8px 10px}#pause{display:none}
}
</style>
</head>
<body>
<header>
<div>
<div class="eyebrow">Backyard dynamics / No. 01</div>
<h1>Loose end.</h1>
<div class="subtitle">A running hose. Nobody holding the nozzle.</div>
</div>
<div class="badge">4 m wet hose · 2.5 mm jet<br>Estimated onset <b>23 psi</b></div>
</header>
<main>
<canvas id="lawn" aria-label="Interactive hose on a lawn. Drag any point of the hose to hold it."></canvas>
<div class="note">Grab the hose anywhere. Let go to release.</div>
<div class="key">1 m ━━━━━</div>
</main>
<footer>
<div class="controls">
<div class="readout"><output id="psiOut">40</output><span class="unit">psi</span></div>
<div class="track">
<input id="pressure" type="range" min="0" max="100" step="0.1" value="40" aria-label="Water pressure in psi">
<div class="ends"><span>OFF</span><span id="maxLabel">100 PSI</span></div>
</div>
<button id="pause" type="button">Pause</button>
<button id="reset" type="button">Lay it down</button>
</div>
<div class="bottom">
<span id="state" role="status">Released</span>
<span id="force">Reaction 1.96 N</span>
<span>No wind. No hidden motor.</span>
</div>
</footer>
<script>
"use strict";
const canvas=document.getElementById("lawn"),ctx=canvas.getContext("2d");
const slider=document.getElementById("pressure");
const psiOut=document.getElementById("psiOut"),forceOut=document.getElementById("force");
const stateOut=document.getElementById("state");
const N=36,L=4,ds=L/(N-1),massPerLength=.35;
const mass=massPerLength*ds,B=.70,g=9.81,mu=.018,dragPerLength=.045;
const Cd=.85,area=Math.PI*.00125**2,forcePerPsi=2*Cd*Cd*area*6894.76;
const dt=1/240,iterations=24;
const anchor={x:-.35,y:.12};
let points=[],bendLambda=new Float64Array(N),grab=-1;
let mouse={x:0,y:0},water=[],splashes=[],emission=0;
let psi=40,paused=false,accumulator=0,last=performance.now(),speed=0;
let W=1,H=1,scale=1,dpr=1,grass,heldPointer=null;
// Rendering randomness never enters the hose simulation.
let seed=41234;
function random(){seed=(Math.imul(seed,1664525)+1013904223)>>>0;return seed/4294967296}
function setPressure(v){
psi=Math.max(0,Number.isFinite(v)?v:40);
slider.max=Math.max(100,Math.ceil(psi));
slider.value=psi;
document.getElementById("maxLabel").textContent=slider.max+" PSI";
psiOut.textContent=psi.toFixed(psi%1?1:0);
forceOut.textContent="Reaction "+(forcePerPsi*psi).toFixed(2)+" N";
}
const hashValue=new URLSearchParams(location.hash.slice(1)).get("psi");
if(hashValue!==null&&hashValue.trim()!==""&&Number.isFinite(Number(hashValue)))setPressure(Number(hashValue));
else setPressure(40);
slider.addEventListener("input",()=>setPressure(Number(slider.value)));
addEventListener("hashchange",()=>{
const v=new URLSearchParams(location.hash.slice(1)).get("psi");
if(v!==null&&v.trim()!==""&&Number.isFinite(Number(v)))setPressure(Number(v));
});
function reset(){
points=[];
// A small initial bend breaks exact symmetry; no continuing excitation.
let x=anchor.x,y=anchor.y;
for(let i=0;i<N;i++){
if(i){
const u=(i-1)/(N-2);
const angle=.22*Math.sin(Math.PI*u)+.07*Math.sin(3*Math.PI*u);
x+=ds*Math.cos(angle);y+=ds*Math.sin(angle);
}
points.push({x,y,ox:x,oy:y,w:i<2?0:1/mass});
}
grab=-1;water=[];splashes=[];speed=0;accumulator=0;
}
reset();
function resize(){
const r=canvas.getBoundingClientRect();
W=r.width;H=r.height;dpr=Math.min(devicePixelRatio||1,2);
canvas.width=Math.round(W*dpr);canvas.height=Math.round(H*dpr);
scale=Math.min(W/9.5,H/8.8);
grass=document.createElement("canvas");
grass.width=canvas.width;grass.height=canvas.height;
const c=grass.getContext("2d");
c.scale(dpr,dpr);
c.fillStyle="#71964f";c.fillRect(0,0,W,H);
for(let x=-H;x<W+H;x+=90){
c.fillStyle="rgba(40,80,30,.055)";
c.beginPath();c.moveTo(x,0);c.lineTo(x+45,0);
c.lineTo(x+45+H*.45,H);c.lineTo(x+H*.45,H);c.fill();
}
seed=4003;
for(let i=0;i<W*H/85;i++){
const x=random()*W,y=random()*H;
c.strokeStyle=random()<.5?"rgba(225,232,151,.22)":"rgba(34,73,33,.18)";
c.lineWidth=.7;c.beginPath();c.moveTo(x,y);
c.lineTo(x+(random()-.5)*4,y-2-random()*4);c.stroke();
}
}
new ResizeObserver(resize).observe(canvas);
function screen(x,y,z=0){return {x:W/2+x*scale,y:H/2+(y-z*.72)*scale}}
function world(e){
const r=canvas.getBoundingClientRect();
return {x:(e.clientX-r.left-W/2)/scale,y:(e.clientY-r.top-H/2)/scale};
}
canvas.addEventListener("pointerdown",e=>{
if(heldPointer!==null)return;
mouse=world(e);
let best=Infinity,index=-1;
// Select the nearest segment, not merely a visible vertex.
for(let i=1;i<N-1;i++){
const a=points[i],b=points[i+1],dx=b.x-a.x,dy=b.y-a.y;
const t=Math.max(0,Math.min(1,((mouse.x-a.x)*dx+(mouse.y-a.y)*dy)/(dx*dx+dy*dy||1)));
const distance=Math.hypot(mouse.x-a.x-t*dx,mouse.y-a.y-t*dy);
if(distance<best){best=distance;index=t<.5?i:i+1}
}
if(best*scale<24){
grab=Math.max(2,index);heldPointer=e.pointerId;
canvas.setPointerCapture(e.pointerId);e.preventDefault();
}
});
canvas.addEventListener("pointermove",e=>{if(e.pointerId===heldPointer)mouse=world(e)});
function release(e){
if(e.pointerId===heldPointer){grab=-1;heldPointer=null}
}
canvas.addEventListener("pointerup",release);
canvas.addEventListener("pointercancel",release);
canvas.addEventListener("lostpointercapture",release);
document.getElementById("reset").onclick=reset;
document.getElementById("pause").onclick=()=>{
paused=!paused;
document.getElementById("pause").textContent=paused?"Resume":"Pause";
};
function pin(){
points[0].x=anchor.x;points[0].y=anchor.y;
points[1].x=anchor.x+ds;points[1].y=anchor.y;
if(grab>=2){
// A grabbed point cannot be pulled beyond the available hose length.
const base=points[1],dx=mouse.x-base.x,dy=mouse.y-base.y;
const d=Math.hypot(dx,dy),reach=(grab-1)*ds*.998;
const q=d>reach?reach/d:1;
points[grab].x=base.x+dx*q;points[grab].y=base.y+dy*q;
}
}
function weight(i){return i===grab?0:points[i].w}
function lengths(reverse){
for(let k=0;k<N-1;k++){
const i=reverse?N-2-k:k,a=points[i],b=points[i+1];
const wa=weight(i),wb=weight(i+1),sum=wa+wb;
if(!sum)continue;
const dx=b.x-a.x,dy=b.y-a.y,len=Math.hypot(dx,dy);
if(len<1e-10)continue;
const correction=(len-ds)/(len*sum);
a.x+=wa*dx*correction;a.y+=wa*dy*correction;
b.x-=wb*dx*correction;b.y-=wb*dy*correction;
}
}
function bend(){
// XPBD angular energy: E = B/(2 ds) * turningAngle^2.
const alpha=ds/B/(dt*dt);
for(let i=1;i<N-1;i++){
const a=points[i-1],b=points[i],c=points[i+1];
const ux=b.x-a.x,uy=b.y-a.y,vx=c.x-b.x,vy=c.y-b.y;
const u2=ux*ux+uy*uy,v2=vx*vx+vy*vy;
if(u2<1e-12||v2<1e-12)continue;
const angle=Math.atan2(ux*vy-uy*vx,ux*vx+uy*vy);
const ax=-uy/u2,ay=ux/u2,cx=-vy/v2,cy=vx/v2;
const bx=-ax-cx,by=-ay-cy;
const wa=weight(i-1),wb=weight(i),wc=weight(i+1);
const denom=wa*(ax*ax+ay*ay)+wb*(bx*bx+by*by)+wc*(cx*cx+cy*cy)+alpha;
const dl=(-angle-alpha*bendLambda[i])/denom;
bendLambda[i]+=dl;
a.x+=wa*ax*dl;a.y+=wa*ay*dl;
b.x+=wb*bx*dl;b.y+=wb*by*dl;
c.x+=wc*cx*dl;c.y+=wc*cy*dl;
}
}
function step(){
const tip=points[N-1],before=points[N-2];
let tx=tip.x-before.x,ty=tip.y-before.y;
const tl=Math.hypot(tx,ty)||1;tx/=tl;ty/=tl;
const F=forcePerPsi*psi;
for(let i=2;i<N;i++){
const p=points[i];
let vx=(p.x-p.ox)/dt,vy=(p.y-p.oy)/dt;
if(i===N-1){vx-=F*tx/mass*dt;vy-=F*ty/mass*dt}
const damping=Math.exp(-dragPerLength/massPerLength*dt);
vx*=damping;vy*=damping;
const v=Math.hypot(vx,vy),factor=v>0?Math.max(0,1-mu*g*dt/v):0;
p.ox=p.x;p.oy=p.y;
p.x+=vx*factor*dt;p.y+=vy*factor*dt;
}
bendLambda.fill(0);
pin();
for(let j=0;j<iterations;j++){
bend();lengths(j%2===1);pin();
}
// Extra distance-only sweeps keep visible extension very small.
for(let j=0;j<8;j++){lengths(j%2===1);pin()}
if(grab>=2){points[grab].ox=points[grab].x;points[grab].oy=points[grab].y}
let s=0;
for(let i=2;i<N;i++)s+=Math.hypot(points[i].x-points[i].ox,points[i].y-points[i].oy)/dt;
speed+=(s/(N-2)-speed)*dt*2;
// Ballistic spray: horizontal nozzle vector, with a shallow upward tilt.
const jetSpeed=Cd*Math.sqrt(2*6894.76*psi/1000);
emission+=Math.min(160,psi*5)*dt;
while(emission>=1){
emission--;
const a=Math.atan2(ty,tx)+(random()-.5)*.065;
const v=jetSpeed*(.92+random()*.12);
water.push({x:tip.x,y:tip.y,z:.055,vx:Math.cos(a)*v,vy:Math.sin(a)*v,vz:v*.025,life:0});
}
for(let i=water.length-1;i>=0;i--){
const p=water[i];
p.x+=p.vx*dt;p.y+=p.vy*dt;p.z+=p.vz*dt;
p.vz-=g*dt;p.life+=dt;
if(p.z<=0||p.life>3){
if(p.z<=0)splashes.push({x:p.x,y:p.y,age:0});
water.splice(i,1);
}
}
for(let i=splashes.length-1;i>=0;i--){
splashes[i].age+=dt;
if(splashes[i].age>.45)splashes.splice(i,1);
}
}
function pathHose(){
ctx.beginPath();
points.forEach((p,i)=>{const q=screen(p.x,p.y);i?ctx.lineTo(q.x,q.y):ctx.moveTo(q.x,q.y)});
}
function draw(){
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.drawImage(grass,0,0,grass.width,grass.height,0,0,W,H);
for(const p of splashes){
const q=screen(p.x,p.y);
ctx.strokeStyle=`rgba(219,249,235,${.42*(1-p.age/.45)})`;
ctx.lineWidth=1;ctx.beginPath();
ctx.ellipse(q.x,q.y,(.035+p.age*.22)*scale,(.02+p.age*.12)*scale,0,0,Math.PI*2);ctx.stroke();
}
// Small concrete pad and brass garden tap, in plan view.
const a=screen(anchor.x,anchor.y);
ctx.save();ctx.translate(a.x,a.y);
ctx.fillStyle="#36543155";ctx.fillRect(-22,-17,40,38);
ctx.fillStyle="#d1cfb5";ctx.fillRect(-25,-23,38,37);
ctx.strokeStyle="#b5b49d";ctx.lineWidth=1;ctx.strokeRect(-25,-23,38,37);
ctx.strokeStyle="#866532";ctx.lineWidth=10;ctx.lineCap="round";
ctx.beginPath();ctx.moveTo(-15,-17);ctx.lineTo(-15,0);ctx.lineTo(4,0);ctx.stroke();
ctx.strokeStyle="#c7a65f";ctx.lineWidth=6;ctx.stroke();
ctx.strokeStyle="#843f34";ctx.lineWidth=4;
ctx.beginPath();ctx.moveTo(-23,-14);ctx.lineTo(-7,-14);ctx.stroke();
ctx.restore();
ctx.lineCap="round";ctx.lineJoin="round";
ctx.save();ctx.translate(2,3);pathHose();ctx.lineWidth=Math.max(7,.045*scale);
ctx.strokeStyle="#233b3266";ctx.stroke();ctx.restore();
pathHose();ctx.lineWidth=Math.max(6,.036*scale);ctx.strokeStyle="#173d3d";ctx.stroke();
pathHose();ctx.lineWidth=Math.max(3.5,.024*scale);ctx.strokeStyle="#e6b951";ctx.stroke();
ctx.save();ctx.translate(-.6,-.8);pathHose();ctx.lineWidth=1;
ctx.strokeStyle="#ffe292aa";ctx.stroke();ctx.restore();
const p=points[N-1],b=points[N-2],q=screen(p.x,p.y);
ctx.save();ctx.translate(q.x,q.y);ctx.rotate(Math.atan2(p.y-b.y,p.x-b.x));
ctx.fillStyle="#293a37";ctx.fillRect(-8,-4,11,8);
ctx.fillStyle="#c6aa6c";ctx.fillRect(1,-3,6,6);
ctx.fillStyle="#f0d798";ctx.fillRect(4,-2,3,4);ctx.restore();
ctx.lineWidth=1.5;ctx.lineCap="round";
for(const p of water){
const q=screen(p.x,p.y,p.z);
const back=screen(p.x-p.vx*.007,p.y-p.vy*.007,p.z);
ctx.strokeStyle="rgba(221,250,252,.77)";
ctx.beginPath();ctx.moveTo(back.x,back.y);ctx.lineTo(q.x,q.y);ctx.stroke();
}
if(grab>=2){
const q=screen(points[grab].x,points[grab].y);
ctx.strokeStyle="#fff8d7";ctx.lineWidth=1.5;ctx.setLineDash([3,4]);
ctx.beginPath();ctx.arc(q.x,q.y,15,0,Math.PI*2);ctx.stroke();ctx.setLineDash([]);
}
stateOut.textContent=paused?"Paused":grab>=2?"Held":speed>.24?"Whipping":speed>.025?"Settling":"At rest";
}
function frame(now){
const elapsed=Math.min((now-last)/1000,.05);last=now;
if(!paused){
accumulator+=elapsed;
let steps=0;
while(accumulator>=dt&&steps<12){step();accumulator-=dt;steps++}
}
draw();requestAnimationFrame(frame);
}
resize();requestAnimationFrame(frame);
</script>
</body>
</html>
The nozzle’s reaction follows its changing direction, so buckling, sweeping loops, and reversals emerge from the simulation—not an animated path. Holding the hose changes its effective free length; reducing pressure lets ground friction dissipate the motion.
The 23 psi prediction is approximate. Dry friction, initial curvature, numerical damping, and the short clamped fitting shift the actual onset. Near it, expect intermittent movement rather than a perfectly sharp transition.
This is a flat-lawn model: the hose cannot jump, twist, collide with itself, or snag. Crossings pass through each other. It omits pressure surges and water motion inside the hose, so it misses some of the violent, three-dimensional snapping of a real runaway hose. Ground drag and constraint-solver damping are artificial approximations. There is a small initial bend, but no ongoing force noise or scripted oscillation; randomness affects only grass and spray. Water lands visibly but does not wet the lawn or change its friction.