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 30 equal, massive segments. A tridiagonal constraint solve projects neighboring particles back to their fixed separation each step; a bending penalty couples successive segments, and a fixed ghost point gives the spigot a preferred outlet direction. The nozzle reaction is a follower force, F = 0.06 × psi newtons, opposite the current nozzle tangent. Lawn friction removes up to μgΔt of each particle’s speed per step, with μ = 0.005; a small velocity drag is also present.
11 psi. This is the ideal straight, undamped cantilever estimate, using the follower-load threshold (20.05EI/L^2), with (EI=1.2), (L=6), and 0.06 N/psi. Friction, discretization, and the initially curved hose can shift the observed onset.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>The hose got away</title>
<style>
:root {
color-scheme: dark;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #14251c;
color: #f2f3df;
}
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; }
main { max-width: 1120px; margin: auto; padding: 24px; }
header {
display: flex; flex-wrap: wrap; align-items: end;
justify-content: space-between; gap: 12px; margin-bottom: 18px;
}
.eyebrow {
text-transform: uppercase; letter-spacing: .16em; font-size: .72rem;
color: #b9d59a; font-weight: 750;
}
h1 { margin: 4px 0 0; font-size: clamp(1.7rem, 4vw, 2.8rem); line-height: 1; }
.hint { color: #c3cfb6; font-size: .9rem; margin: 0; }
.panel {
background: #203b29; border: 1px solid #486b45; border-radius: 18px;
box-shadow: 0 14px 45px #07140c66; overflow: hidden;
}
.controls {
display: flex; align-items: center; flex-wrap: wrap; gap: 12px 18px;
padding: 16px 20px; background: #294932;
}
.controls label { font-weight: 700; white-space: nowrap; }
input[type=range] { flex: 1 1 220px; accent-color: #f4cc5d; cursor: pointer; }
.reading {
display: flex; align-items: baseline; gap: 5px; color: #f8de86;
font-variant-numeric: tabular-nums; font-weight: 750;
}
input[type=number] {
width: 68px; border: 1px solid #749064; border-radius: 7px;
padding: 6px; background: #172c1d; color: #fff0b7;
font: inherit; text-align: right;
}
button {
border: 1px solid #79956d; border-radius: 8px; background: #36593d;
color: #f2f3df; font: inherit; padding: 8px 12px; cursor: pointer;
}
button:hover { background: #426b49; }
button:focus-visible, input:focus-visible { outline: 3px solid #ffe285; outline-offset: 2px; }
.scene { position: relative; background: #729452; }
canvas { display: block; width: 100%; aspect-ratio: 13 / 8; touch-action: none; cursor: grab; }
canvas.dragging { cursor: grabbing; }
.badge {
position: absolute; left: 16px; bottom: 14px; pointer-events: none;
border-radius: 20px; padding: 6px 10px; background: #142c1bc9;
color: #e3eed5; font-size: .75rem; letter-spacing: .025em;
}
footer { padding: 13px 20px 16px; color: #c3d4ba; font-size: .82rem; }
@media (max-width: 600px) {
main { padding: 14px; }
.controls { padding: 13px; gap: 10px; }
.controls label { width: 100%; }
footer { padding: 12px; }
}
</style>
</head>
<body>
<main>
<header>
<div><div class="eyebrow">A little lawn experiment</div><h1>The hose got away.</h1></div>
<p class="hint">Turn up the spigot. Grab the hose anywhere.</p>
</header>
<section class="panel" aria-label="Hose simulation">
<div class="controls">
<label for="pressure">Water pressure</label>
<input id="pressure" type="range" min="0" max="100" step="1" value="0">
<div class="reading">
<input id="number" type="number" min="0" max="100" step="1" value="0" aria-label="Pressure in psi">
<span>psi</span>
</div>
<button id="reset" type="button">Reset hose</button>
</div>
<div class="scene">
<canvas id="lawn" aria-label="Top-down view of a hose on a lawn. Drag the hose to hold it."></canvas>
<div class="badge">TOP VIEW · DRAG TO HOLD</div>
</div>
<footer>Yellow end = free nozzle. Release your pointer to let go. The pressure range is 0–100 psi.</footer>
</section>
</main>
<script>
(() => {
"use strict";
const canvas = document.getElementById("lawn");
const ctx = canvas.getContext("2d");
const slider = document.getElementById("pressure");
const number = document.getElementById("number");
const resetButton = document.getElementById("reset");
const W = 13, H = 8;
const N = 30, count = N + 1, length = 6, ds = length / N;
const EI = 1.2, mass = 0.044, dt = 1 / 240;
const bx = 2.1, by = 4;
const bendK = EI / (ds * ds * ds);
const x = new Float64Array(count), y = new Float64Array(count);
const vx = new Float64Array(count), vy = new Float64Array(count);
const ox = new Float64Array(count), oy = new Float64Array(count);
const fx = new Float64Array(count), fy = new Float64Array(count);
const weight = new Float64Array(count);
const nx = new Float64Array(N), ny = new Float64Array(N);
const diag = new Float64Array(N), upper = new Float64Array(N);
const rhs = new Float64Array(N), cp = new Float64Array(N);
const dp = new Float64Array(N), lambda = new Float64Array(N);
let pressure = 0, held = -1, targetX = 0, targetY = 0;
let particles = [], drops = [], emission = 0;
let accumulator = 0, previousTime = 0;
let scale = 1, offsetX = 0, offsetY = 0, viewWidth = 0, viewHeight = 0;
function setPressure(value) {
value = Number(value);
if (!Number.isFinite(value)) value = 0;
pressure = Math.max(0, Math.min(100, value));
slider.value = String(pressure);
number.value = String(pressure);
}
function resetHose() {
held = -1;
canvas.classList.remove("dragging");
x[0] = bx; y[0] = by;
vx.fill(0); vy.fill(0);
// A small initial bend supplies a disturbance without forcing an oscillation.
for (let i = 1; i <= N; i++) {
const a = 0.28 * Math.sin(Math.PI * (i - 0.5) / N * 1.5);
x[i] = x[i - 1] + ds * Math.cos(a);
y[i] = y[i - 1] + ds * Math.sin(a);
}
particles = [];
drops = [];
emission = 0;
}
function resize() {
const rect = canvas.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.max(1, Math.round(rect.width * dpr));
canvas.height = Math.max(1, Math.round(rect.height * dpr));
viewWidth = canvas.width;
viewHeight = canvas.height;
scale = Math.min(viewWidth / W, viewHeight / H);
offsetX = (viewWidth - W * scale) / 2;
offsetY = (viewHeight - H * scale) / 2;
}
function pointerWorld(e) {
const r = canvas.getBoundingClientRect();
const px = (e.clientX - r.left) * canvas.width / r.width;
const py = (e.clientY - r.top) * canvas.height / r.height;
return [(px - offsetX) / scale, (py - offsetY) / scale];
}
function limitTarget() {
if (held < 1) return;
let dx = targetX - bx, dy = targetY - by;
let d = Math.hypot(dx, dy);
const reach = held * ds;
if (held === 1) {
if (d < 1e-8) { dx = 1; dy = 0; d = 1; }
targetX = bx + dx / d * reach;
targetY = by + dy / d * reach;
} else if (d > reach * 0.9999) {
targetX = bx + dx / d * reach * 0.9999;
targetY = by + dy / d * reach * 0.9999;
}
targetX = Math.max(0.3, Math.min(W - 0.3, targetX));
targetY = Math.max(0.3, Math.min(H - 0.3, targetY));
}
function solveConstraints() {
// Newton projection of all distance constraints at once. The constraint
// matrix is tridiagonal because only adjacent links share a particle.
for (let iteration = 0; iteration < 8; iteration++) {
x[0] = bx; y[0] = by;
if (held >= 1) { x[held] = targetX; y[held] = targetY; }
for (let i = 0; i < N; i++) {
const dx = x[i + 1] - x[i], dy = y[i + 1] - y[i];
const d = Math.max(1e-9, Math.hypot(dx, dy));
nx[i] = dx / d; ny[i] = dy / d;
diag[i] = weight[i] + weight[i + 1];
rhs[i] = -(d - ds);
}
for (let i = 0; i < N - 1; i++) {
upper[i] = -weight[i + 1] * (nx[i] * nx[i + 1] + ny[i] * ny[i + 1]);
}
// A link with both endpoints fixed has no adjustable degrees of freedom.
for (let i = 0; i < N; i++) {
if (diag[i] < 1e-10) { diag[i] = 1; rhs[i] = 0; }
}
let denom = diag[0];
cp[0] = upper[0] / denom;
dp[0] = rhs[0] / denom;
for (let i = 1; i < N; i++) {
denom = diag[i] - upper[i - 1] * cp[i - 1];
if (Math.abs(denom) < 1e-9) denom = 1e-9;
cp[i] = i < N - 1 ? upper[i] / denom : 0;
dp[i] = (rhs[i] - upper[i - 1] * dp[i - 1]) / denom;
}
lambda[N - 1] = dp[N - 1];
for (let i = N - 2; i >= 0; i--) lambda[i] = dp[i] - cp[i] * lambda[i + 1];
for (let i = 0; i < N; i++) {
const a = lambda[i];
x[i] -= weight[i] * nx[i] * a;
y[i] -= weight[i] * ny[i] * a;
x[i + 1] += weight[i + 1] * nx[i] * a;
y[i + 1] += weight[i + 1] * ny[i] * a;
}
// Invisible lawn-edge barriers.
for (let i = 1; i <= N; i++) {
if (i === held) continue;
x[i] = Math.max(0.22, Math.min(W - 0.22, x[i]));
y[i] = Math.max(0.22, Math.min(H - 0.22, y[i]));
}
}
}
function step() {
fx.fill(0); fy.fill(0);
// Bending energy: (EI / 2 ds^3) times squared second differences.
// The ghost point behind the spigot supplies a preferred base direction.
let qx = x[1] - 2 * x[0] + (bx - ds);
let qy = y[1] - 2 * y[0] + by;
fx[1] -= bendK * qx; fy[1] -= bendK * qy;
for (let i = 0; i <= N - 2; i++) {
qx = x[i] - 2 * x[i + 1] + x[i + 2];
qy = y[i] - 2 * y[i + 1] + y[i + 2];
const ax = bendK * qx, ay = bendK * qy;
fx[i] -= ax; fy[i] -= ay;
fx[i + 1] += 2 * ax; fy[i + 1] += 2 * ay;
fx[i + 2] -= ax; fy[i + 2] -= ay;
}
let tx = x[N] - x[N - 1], ty = y[N] - y[N - 1];
const tl = Math.max(1e-9, Math.hypot(tx, ty));
tx /= tl; ty /= tl;
fx[N] -= 0.06 * pressure * tx;
fy[N] -= 0.06 * pressure * ty;
limitTarget();
for (let i = 0; i <= N; i++) {
ox[i] = x[i]; oy[i] = y[i];
weight[i] = (i === 0 || i === held) ? 0 : 1;
if (!weight[i]) continue;
vx[i] += fx[i] / mass * dt;
vy[i] += fy[i] / mass * dt;
// Coulomb-like lawn friction, plus a small linear drag.
const speed = Math.hypot(vx[i], vy[i]);
if (speed > 0) {
const factor = Math.max(0, speed - 0.005 * 9.81 * dt) / speed;
vx[i] *= factor * Math.exp(-0.08 * dt);
vy[i] *= factor * Math.exp(-0.08 * dt);
}
x[i] += vx[i] * dt;
y[i] += vy[i] * dt;
}
solveConstraints();
for (let i = 1; i <= N; i++) {
if (i === held) { vx[i] = 0; vy[i] = 0; continue; }
vx[i] = (x[i] - ox[i]) / dt;
vy[i] = (y[i] - oy[i]) / dt;
// Protect the simulation from extreme pointer jumps.
const speed = Math.hypot(vx[i], vy[i]);
if (speed > 18) { vx[i] *= 18 / speed; vy[i] *= 18 / speed; }
}
if (pressure > 0) {
emission += dt * (15 + 0.8 * pressure);
while (emission >= 1) {
emission--;
const jetSpeed = 0.6 + 0.6 * Math.sqrt(pressure);
particles.push({
x: x[N] + tx * 0.13, y: y[N] + ty * 0.13, z: 0.18,
vx: tx * jetSpeed + vx[N] * 0.3,
vy: ty * jetSpeed + vy[N] * 0.3,
vz: 1.15, life: 0
});
}
} 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; p.life += dt;
if (p.z <= 0 || p.life > 2) {
if (p.x >= 0 && p.x <= W && p.y >= 0 && p.y <= H) {
drops.push({ x: p.x, y: p.y, age: 0 });
}
particles.splice(i, 1);
}
}
for (let i = drops.length - 1; i >= 0; i--) {
drops[i].age += dt;
if (drops[i].age > 2.4) drops.splice(i, 1);
}
if (drops.length > 250) drops.splice(0, drops.length - 250);
}
// Fixed decorative grass positions, unrelated to the physics.
let seed = 13579;
function rand() {
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0;
return seed / 4294967296;
}
const grass = Array.from({ length: 360 }, () => ({
x: rand() * W, y: rand() * H, s: 0.035 + rand() * 0.055, a: rand()
}));
function draw() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, viewWidth, viewHeight);
ctx.fillStyle = "#557a42";
ctx.fillRect(0, 0, viewWidth, viewHeight);
ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
ctx.fillStyle = "#739652";
ctx.fillRect(0, 0, W, H);
for (let i = 0; i < 8; i++) {
ctx.fillStyle = i % 2 ? "rgba(39,82,38,.07)" : "rgba(224,236,158,.055)";
ctx.fillRect(0, i, W, 1);
}
ctx.lineWidth = 0.014;
for (const g of grass) {
ctx.strokeStyle = g.a > 0.5 ? "rgba(37,87,40,.22)" : "rgba(235,240,161,.23)";
ctx.beginPath();
ctx.moveTo(g.x - g.s * 0.4, g.y + g.s * 0.3);
ctx.lineTo(g.x, g.y - g.s);
ctx.lineTo(g.x + g.s * 0.45, g.y + g.s * 0.2);
ctx.stroke();
}
for (const d of drops) {
ctx.fillStyle = `rgba(167,222,230,${0.37 * (1 - d.age / 2.4)})`;
ctx.beginPath();
ctx.ellipse(d.x, d.y, 0.045 + d.age * 0.025, 0.022 + d.age * 0.012, 0, 0, Math.PI * 2);
ctx.fill();
}
// Spigot and its little mounting stone.
ctx.fillStyle = "#a6a58e";
ctx.beginPath(); ctx.roundRect(1.35, 3.59, 0.66, 0.82, 0.09); ctx.fill();
ctx.fillStyle = "#7c7c6c";
ctx.fillRect(1.8, 3.92, 0.28, 0.16);
ctx.strokeStyle = "#d0ba78"; ctx.lineWidth = 0.16; ctx.lineCap = "round";
ctx.beginPath(); ctx.moveTo(1.74, 4); ctx.lineTo(2.12, 4); ctx.stroke();
ctx.strokeStyle = "#efcf71"; ctx.lineWidth = 0.07;
ctx.beginPath(); ctx.moveTo(1.69, 3.77); ctx.lineTo(1.69, 4.23);
ctx.moveTo(1.56, 3.82); ctx.lineTo(1.82, 3.82); ctx.stroke();
function hosePath() {
ctx.beginPath(); ctx.moveTo(x[0], y[0]);
for (let i = 1; i <= N; i++) ctx.lineTo(x[i], y[i]);
}
ctx.lineJoin = "round"; ctx.lineCap = "round";
ctx.save(); ctx.translate(0.035, 0.065);
ctx.strokeStyle = "rgba(20,45,22,.28)"; ctx.lineWidth = 0.22;
hosePath(); ctx.stroke(); ctx.restore();
ctx.strokeStyle = "#244c32"; ctx.lineWidth = 0.19; hosePath(); ctx.stroke();
ctx.strokeStyle = "#4a8252"; ctx.lineWidth = 0.115; hosePath(); ctx.stroke();
ctx.strokeStyle = "rgba(175,207,119,.42)"; ctx.lineWidth = 0.025; hosePath(); ctx.stroke();
let tx = x[N] - x[N - 1], ty = y[N] - y[N - 1];
const l = Math.max(1e-9, Math.hypot(tx, ty)); tx /= l; ty /= l;
ctx.strokeStyle = "#3a3a28"; ctx.lineWidth = 0.22;
ctx.beginPath(); ctx.moveTo(x[N] - tx * 0.12, y[N] - ty * 0.12);
ctx.lineTo(x[N] + tx * 0.14, y[N] + ty * 0.14); ctx.stroke();
ctx.strokeStyle = "#e3b54a"; ctx.lineWidth = 0.15;
ctx.beginPath(); ctx.moveTo(x[N] - tx * 0.08, y[N] - ty * 0.08);
ctx.lineTo(x[N] + tx * 0.13, y[N] + ty * 0.13); ctx.stroke();
if (held >= 1) {
ctx.strokeStyle = "rgba(255,240,173,.9)"; ctx.lineWidth = 0.035;
ctx.beginPath(); ctx.arc(x[held], y[held], 0.2, 0, Math.PI * 2); ctx.stroke();
}
for (const p of particles) {
ctx.fillStyle = "rgba(32,74,63,.17)";
ctx.beginPath(); ctx.ellipse(p.x, p.y, 0.035, 0.02, 0, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = "#d3f6f6";
ctx.beginPath(); ctx.arc(p.x, p.y - p.z * 0.35, 0.035, 0, Math.PI * 2); ctx.fill();
}
}
canvas.addEventListener("pointerdown", e => {
const [px, py] = pointerWorld(e);
let best = 0.36, index = -1;
for (let i = 1; i <= N; i++) {
const d = Math.hypot(x[i] - px, y[i] - py);
if (d < best) { best = d; index = i; }
}
if (index >= 0) {
held = index; targetX = px; targetY = py;
limitTarget();
canvas.setPointerCapture(e.pointerId);
canvas.classList.add("dragging");
e.preventDefault();
}
});
canvas.addEventListener("pointermove", e => {
if (held < 0) return;
[targetX, targetY] = pointerWorld(e);
limitTarget();
});
function release() {
held = -1;
canvas.classList.remove("dragging");
}
canvas.addEventListener("pointerup", release);
canvas.addEventListener("pointercancel", release);
canvas.addEventListener("lostpointercapture", release);
slider.addEventListener("input", () => setPressure(slider.value));
number.addEventListener("change", () => setPressure(number.value));
number.addEventListener("input", () => {
if (number.value !== "") {
const v = Number(number.value);
if (Number.isFinite(v)) {
pressure = Math.max(0, Math.min(100, v));
slider.value = String(pressure);
}
}
});
resetButton.addEventListener("click", resetHose);
window.addEventListener("resize", resize);
const hash = new URLSearchParams(location.hash.slice(1));
if (hash.has("psi")) setPressure(hash.get("psi"));
else setPressure(0);
resetHose();
resize();
function frame(time) {
if (!previousTime) previousTime = time;
const elapsed = Math.min(0.05, (time - previousTime) / 1000);
previousTime = time;
accumulator += elapsed;
while (accumulator >= dt) { step(); accumulator -= dt; }
draw();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
The nozzle thrust follows the hose, so bends can grow and the free end can sweep around; dragging and releasing it changes the subsequent motion. This is a top-down toy, not a full fluid-and-hose simulation: the lawn is unusually slippery, the spigot has a preferred direction, and the water droplets are visual tracers rather than a fluid coupled back to the hose.
There is artificial linear damping, constraint-solver damping, invisible lawn-edge barriers, and a speed cap for pointer jumps. The initial bend is a disturbance, but there is no injected motion noise or scripted oscillation. The onset estimate is not a measured sharp threshold: ground friction and the finite chain change it.