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 20 point masses joined by fixed-length segments. Each physics step applies bending forces, then repeatedly projects neighboring points back to their required separation; the spigot point stays fixed, with a spring resisting rotation at the fitting. The nozzle reaction is (F=2A(6894.76,p)=0.0625p) newtons, opposite the nozzle direction, where (p) is gauge pressure in psi and (A) is the effective jet area. The lawn applies Coulomb-like friction that cannot reverse a point’s velocity in one step, plus a small velocity-proportional drag.
40 psi. This is the ideal clamped-column follower-force estimate, (20.05EI/(L^2\cdot0.0625)), using (EI=4.49\ \mathrm{N,m^2}) and (L=6\ \mathrm m). It is an approximation, not a measured transition for the discrete, frictional simulation.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The escaped hose</title>
<style>
:root {
color-scheme: light;
font-family: system-ui, -apple-system, sans-serif;
background: #e8e9d4;
color: #233a2b;
}
* { box-sizing: border-box; }
body { margin: 0; padding: 24px; }
main { max-width: 1000px; margin: auto; }
header {
display: flex; justify-content: space-between; align-items: end;
gap: 16px; flex-wrap: wrap; margin-bottom: 14px;
}
h1 { font-size: clamp(1.6rem, 4vw, 2.7rem); margin: 0; letter-spacing: -.05em; }
header p { margin: 5px 0 0; color: #5b7058; }
.controls {
display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
padding: 14px 18px; background: #fffdf1; border: 1px solid #c8d0af;
border-radius: 14px 14px 0 0;
}
.controls label { font-weight: 750; }
input[type=range] { flex: 1; min-width: 160px; accent-color: #d2713f; }
output { min-width: 72px; text-align: right; font-weight: 800; font-variant-numeric: tabular-nums; }
.scene {
position: relative; overflow: hidden;
border: 1px solid #93aa78; border-top: 0;
border-radius: 0 0 14px 14px;
background: #a7c984;
}
canvas {
display: block; width: 100%; height: min(68vh, 650px);
min-height: 380px; touch-action: none; cursor: grab;
}
canvas.dragging { cursor: grabbing; }
.hint {
position: absolute; left: 16px; bottom: 14px;
background: #f9f9e5d9; border-radius: 8px; padding: 7px 10px;
font-size: .8rem; pointer-events: none; color: #40553c;
}
footer { color: #5b7058; font-size: .83rem; line-height: 1.5; padding: 12px 2px; }
@media (max-width: 520px) {
body { padding: 12px; }
canvas { height: 58vh; min-height: 350px; }
}
</style>
</head>
<body>
<main>
<header>
<div>
<h1>The escaped hose</h1>
<p>A little patch of lawn, viewed from above.</p>
</div>
</header>
<div class="controls">
<label for="pressure">Water pressure</label>
<input id="pressure" type="range" min="0" max="100" step="1" value="60">
<output id="reading" for="pressure">60 psi</output>
</div>
<div class="scene">
<canvas id="lawn" aria-label="A hose on a lawn. Drag the hose to hold it."></canvas>
<div class="hint">Drag the hose to hold it · release to let go</div>
</div>
<footer>
The spigot stays put. The nozzle is free. Turn the pressure down to let the hose settle.
The scale runs from 0 to 100 psi.
</footer>
</main>
<script>
(() => {
"use strict";
const canvas = document.getElementById("lawn");
const ctx = canvas.getContext("2d");
const slider = document.getElementById("pressure");
const reading = document.getElementById("reading");
const fromHash = new URLSearchParams(location.hash.slice(1)).get("psi");
if (fromHash !== null && fromHash.trim() !== "") {
const value = Number(fromHash);
if (Number.isFinite(value)) slider.value = Math.max(0, Math.min(100, value));
}
let pressure = Number(slider.value);
function updatePressure() {
pressure = Number(slider.value);
reading.textContent = pressure + " psi";
}
slider.addEventListener("input", updatePressure);
updatePressure();
// Metres, kilograms, seconds. The view is a top-down ground plane.
const N = 20, L = 6, ds = L / N, EI = 4.49;
const mass = 0.9 / N;
const bendK = EI / (ds * ds * ds);
const root = { x: 7.5, y: 6.5 };
const nodes = [{ x: root.x, y: root.y, vx: 0, vy: 0 }];
for (let i = 1; i <= N; i++) {
// A slight initial bend gives the follower load something to act on.
const a = 0.18 * Math.sin(i / N * Math.PI * 2)
+ 0.045 * Math.sin(i / N * Math.PI * 5);
const prev = nodes[i - 1];
nodes.push({
x: prev.x + ds * Math.cos(a),
y: prev.y + ds * Math.sin(a),
vx: 0, vy: 0
});
}
const particles = [];
const splashes = [];
let emission = 0;
let held = -1;
let pointerId = null;
let target = { x: 0, y: 0 };
let view = { scale: 1, ox: 0, oy: 0 };
let width = 0, height = 0, dpr = 1;
// Fixed decoration, not forces or noise in the simulation.
let seed = 9127;
function random() {
seed = (seed * 1664525 + 1013904223) >>> 0;
return seed / 4294967296;
}
const grass = Array.from({ length: 440 }, () => ({
x: random() * 15, y: random() * 13,
size: 0.035 + random() * 0.065,
shade: random() > 0.5
}));
function resize() {
const rect = canvas.getBoundingClientRect();
width = rect.width;
height = rect.height;
dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(height * dpr);
// Fit a 15 x 13 metre patch, leaving the whole hose's reach visible.
const scale = Math.min(width / 15, height / 13);
view = {
scale,
ox: (width - 15 * scale) / 2,
oy: (height - 13 * scale) / 2
};
}
window.addEventListener("resize", resize);
resize();
function pointerWorld(event) {
const r = canvas.getBoundingClientRect();
return {
x: (event.clientX - r.left - view.ox) / view.scale,
y: (event.clientY - r.top - view.oy) / view.scale
};
}
function setTarget(p) {
// A grabbed point cannot be farther from the spigot than its arc length.
const dx = p.x - root.x, dy = p.y - root.y;
const distance = Math.hypot(dx, dy);
const reach = held * ds * 0.995;
if (distance > reach && distance > 0) {
target.x = root.x + dx * reach / distance;
target.y = root.y + dy * reach / distance;
} else {
target.x = p.x;
target.y = p.y;
}
}
canvas.addEventListener("pointerdown", event => {
if (held !== -1) return;
const p = pointerWorld(event);
let best = Math.max(0.22, 14 / view.scale);
let index = -1;
for (let i = 1; i <= N; i++) {
const d = Math.hypot(nodes[i].x - p.x, nodes[i].y - p.y);
if (d < best) { best = d; index = i; }
}
if (index < 0) return;
held = index;
pointerId = event.pointerId;
setTarget(p);
canvas.setPointerCapture(pointerId);
canvas.classList.add("dragging");
event.preventDefault();
});
canvas.addEventListener("pointermove", event => {
if (held >= 0 && event.pointerId === pointerId) setTarget(pointerWorld(event));
});
function release(event) {
if (event.pointerId !== pointerId) return;
held = -1;
pointerId = null;
canvas.classList.remove("dragging");
}
canvas.addEventListener("pointerup", release);
canvas.addEventListener("pointercancel", release);
canvas.addEventListener("lostpointercapture", release);
function step(dt) {
const fx = new Float64Array(N + 1);
const fy = new Float64Array(N + 1);
// Discrete curvature energy: EI/(2 ds^3) * sum |x[i-1]-2x[i]+x[i+1]|^2.
for (let i = 1; i < N; i++) {
const qx = nodes[i - 1].x - 2 * nodes[i].x + nodes[i + 1].x;
const qy = nodes[i - 1].y - 2 * nodes[i].y + nodes[i + 1].y;
fx[i - 1] -= bendK * qx; fy[i - 1] -= bendK * qy;
fx[i] += 2 * bendK * qx; fy[i] += 2 * bendK * qy;
fx[i + 1] -= bendK * qx; fy[i + 1] -= bendK * qy;
}
// Rotational restraint at the spigot, initially pointing right.
fy[1] -= 4 * bendK * (nodes[1].y - root.y);
// Jet reaction: 0.0625 N per psi, opposite the current nozzle vector.
const tip = nodes[N], before = nodes[N - 1];
let dx = tip.x - before.x, dy = tip.y - before.y;
const dl = Math.hypot(dx, dy) || 1;
dx /= dl; dy /= dl;
fx[N] -= 0.0625 * pressure * dx;
fy[N] -= 0.0625 * pressure * dy;
const oldX = new Float64Array(N + 1);
const oldY = new Float64Array(N + 1);
const linearDrag = Math.exp(-0.10 * dt);
for (let i = 1; i <= N; i++) {
const p = nodes[i];
oldX[i] = p.x; oldY[i] = p.y;
if (i === held) {
p.x = target.x; p.y = target.y;
continue;
}
p.vx = (p.vx + fx[i] / mass * dt) * linearDrag;
p.vy = (p.vy + fy[i] / mass * dt) * linearDrag;
// Kinetic ground friction, clamped at zero speed.
let speed = Math.hypot(p.vx, p.vy);
if (speed > 0) {
const multiplier = Math.max(0, speed - 0.003 * 9.81 * dt) / speed;
p.vx *= multiplier; p.vy *= multiplier;
}
// Numerical safety limit, not a physical material property.
speed = Math.hypot(p.vx, p.vy);
if (speed > 8) { p.vx *= 8 / speed; p.vy *= 8 / speed; }
p.x += p.vx * dt;
p.y += p.vy * dt;
}
// Project distance constraints. The root and a held point have zero inverse mass.
for (let pass = 0; pass < 48; pass++) {
for (let i = 0; i < N; i++) {
const a = nodes[i], b = nodes[i + 1];
const x = b.x - a.x, y = b.y - a.y;
const length = Math.hypot(x, y) || 1e-9;
const wa = (i === 0 || i === held) ? 0 : 1;
const wb = (i + 1 === held) ? 0 : 1;
const sum = wa + wb;
if (!sum) continue;
const correction = (length - ds) / length / sum;
a.x += x * correction * wa; a.y += y * correction * wa;
b.x -= x * correction * wb; b.y -= y * correction * wb;
}
if (held >= 0) {
nodes[held].x = target.x;
nodes[held].y = target.y;
}
}
for (let i = 1; i <= N; i++) {
nodes[i].vx = (nodes[i].x - oldX[i]) / dt;
nodes[i].vy = (nodes[i].y - oldY[i]) / dt;
}
// Ballistic droplets: horizontal jet direction follows the current tip.
dx = tip.x - before.x; dy = tip.y - before.y;
const len = Math.hypot(dx, dy) || 1;
dx /= len; dy /= len;
if (pressure > 0) {
emission += dt * (8 + pressure * 0.35);
while (emission >= 1) {
emission--;
const speed = Math.sqrt(2 * pressure * 6894.76 / 1000);
particles.push({
x: tip.x + dx * 0.06, y: tip.y + dy * 0.06,
vx: dx * speed, vy: dy * speed,
z: 0.08, vz: 0.7 + pressure * 0.005
});
}
} else {
emission = 0;
}
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * dt; p.y += p.vy * dt;
p.z += p.vz * dt; p.vz -= 9.81 * dt;
if (p.z <= 0) {
splashes.push({ x: p.x, y: p.y, age: 0 });
particles.splice(i, 1);
}
}
for (let i = splashes.length - 1; i >= 0; i--) {
splashes[i].age += dt;
if (splashes[i].age > 0.55) splashes.splice(i, 1);
}
}
function pathHose() {
ctx.beginPath();
ctx.moveTo(nodes[0].x, nodes[0].y);
for (let i = 1; i <= N; i++) ctx.lineTo(nodes[i].x, nodes[i].y);
}
function draw() {
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#a7c984";
ctx.fillRect(0, 0, width, height);
ctx.save();
ctx.translate(view.ox, view.oy);
ctx.scale(view.scale, view.scale);
ctx.fillStyle = "#b2d08e";
ctx.fillRect(0, 0, 15, 13);
for (const g of grass) {
ctx.strokeStyle = g.shade ? "#8fb575" : "#c2d99d";
ctx.lineWidth = 0.018;
ctx.beginPath();
ctx.moveTo(g.x, g.y);
ctx.lineTo(g.x + g.size * 0.35, g.y - g.size);
ctx.stroke();
}
// Faint footprints of water landing on the lawn.
for (const s of splashes) {
ctx.strokeStyle = `rgba(65, 135, 148, ${0.55 * (1 - s.age / 0.55)})`;
ctx.lineWidth = 0.025;
ctx.beginPath();
ctx.arc(s.x, s.y, 0.03 + s.age * 0.18, 0, Math.PI * 2);
ctx.stroke();
}
// Small mounting plate and spigot.
ctx.fillStyle = "#80906e";
ctx.beginPath(); ctx.arc(root.x, root.y, 0.34, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = "#e1d7a7";
ctx.beginPath(); ctx.arc(root.x, root.y, 0.25, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = "#765e3e";
ctx.fillRect(root.x - 0.12, root.y - 0.095, 0.3, 0.19);
// Hose shadow, outline, body and highlight.
ctx.lineCap = "round"; ctx.lineJoin = "round";
ctx.save(); ctx.translate(0.035, 0.055);
pathHose(); ctx.strokeStyle = "rgba(35,65,32,.24)"; ctx.lineWidth = 0.21; ctx.stroke();
ctx.restore();
pathHose(); ctx.strokeStyle = "#374a31"; ctx.lineWidth = 0.19; ctx.stroke();
pathHose(); ctx.strokeStyle = "#d2753e"; ctx.lineWidth = 0.145; ctx.stroke();
pathHose(); ctx.strokeStyle = "rgba(255,210,145,.55)"; ctx.lineWidth = 0.035; ctx.stroke();
const a = nodes[N - 1], b = nodes[N];
const angle = Math.atan2(b.y - a.y, b.x - a.x);
ctx.save();
ctx.translate(b.x, b.y); ctx.rotate(angle);
ctx.fillStyle = "#58615a";
ctx.fillRect(-0.17, -0.095, 0.22, 0.19);
ctx.fillStyle = "#d3d7bd";
ctx.fillRect(0.01, -0.075, 0.13, 0.15);
ctx.restore();
// Droplet shadows and raised droplets; z is drawn as a small upward offset.
for (const p of particles) {
ctx.fillStyle = "rgba(54,104,104,.22)";
ctx.beginPath(); ctx.arc(p.x, p.y, 0.045, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = "#e2f9f7";
ctx.beginPath(); ctx.arc(p.x, p.y - p.z * 0.22, 0.055, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = "#57aabd"; ctx.lineWidth = 0.018; ctx.stroke();
}
if (held >= 0) {
ctx.strokeStyle = "rgba(255,255,234,.9)";
ctx.lineWidth = 0.04;
ctx.beginPath(); ctx.arc(nodes[held].x, nodes[held].y, 0.24, 0, Math.PI * 2); ctx.stroke();
}
ctx.restore();
}
const fixedDt = 1 / 240;
let accumulator = 0;
let last = null;
function frame(now) {
if (last !== null) {
// Modestly accelerated simulation time; discard long background-tab gaps.
accumulator += Math.min((now - last) / 1000, 0.05) * 1.5;
let steps = 0;
while (accumulator >= fixedDt && steps < 25) {
step(fixedDt);
accumulator -= fixedDt;
steps++;
}
if (steps === 25) accumulator = 0;
}
last = now;
draw();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
The follower force can bend and destabilize the hose, and the jet follows the moving nozzle. But this is a top-down, ground-bound chain: it cannot lift, twist, kink, or collide with itself. The 40 psi prediction is an idealized estimate; friction, the discrete solver, and the spigot spring can shift or obscure the transition.
There is artificial linear damping, numerical damping from constraint projection, a velocity cap, and 1.5× simulation time. The initial bend is deliberate, but there is no injected force noise or scripted oscillation. The droplets are illustrative and do not interact with the hose.