Build a single self-contained HTML file (no build step, no external assets) that renders a real-time, interactive 2D fluid simulation solving the incompressible Navier-Stokes equations on the GPU via WebGL. The user drags the mouse to inject dye and velocity into the fluid. The simulation must match the following specifications.
1. Method

Implement Jos Stam's "Stable Fluids" semi-Lagrangian approach, GPU-accelerated with fragment shaders operating on floating-point textures (use a ping-pong framebuffer scheme). Do NOT do a CPU/JS grid loop — it must run in shaders.
Solve the incompressible Navier-Stokes equations via the standard operator-splitting steps each frame: advection → diffusion (viscosity, optional) → force/dye injection → pressure projection (divergence + Jacobi pressure solve + gradient subtraction) → boundary conditions.
Use a Jacobi iterative solver for the pressure Poisson equation with a configurable iteration count (default 20–40).

2. WebGL Setup

Use WebGL2 if available (fall back to WebGL1 with the required float-texture extensions: OES_texture_float / EXT_color_buffer_float; detect and warn if unsupported).
Simulation runs on lower-resolution velocity/pressure textures (e.g. 256×256) while dye is advected on a higher-resolution texture (e.g. 512×512 or 1024×1024) for crisp visuals. Make both resolutions constants at the top.
Ping-pong between framebuffer pairs for velocity, dye, pressure, and divergence fields.

3. Required Shader Passes (each a separate fragment shader)

Advection — semi-Lagrangian backtrace with bilinear sampling, applied to both velocity and dye, with a dissipation/decay factor so dye and motion fade over time.
Splat/force — on mouse drag, add a Gaussian-falloff blob of velocity (in the drag direction, scaled by pointer speed) and colored dye at the pointer location.
Divergence — compute velocity field divergence.
Pressure (Jacobi) — iterate N times to solve for pressure.
Gradient subtraction — subtract pressure gradient from velocity to enforce incompressibility (mass conservation).
Display — render the dye texture to screen, full-viewport.

4. Interaction

Mouse/touch drag injects velocity + dye; pointer velocity controls splat strength and direction.
Each new splat cycles through vivid colors (HSV hue rotation) so the fluid is colorful.
Multi-touch support if feasible (multiple simultaneous splats).
Clicking with no drag still produces a small puff.

5. Tunable Parameters (constants at the top, clearly labeled)

Curl/vorticity-confinement strength (add a vorticity-confinement pass to reintroduce small-scale swirling detail — color #N/A, this is a force term), dye dissipation rate, velocity dissipation rate, pressure iteration count, splat radius, and splat force. Provide sensible defaults that produce smoky, swirling motion.

6. Visuals

Black background; dye rendered additively or directly so colors glow against it.
Smooth, high-frame-rate motion (target 60fps at the chosen resolutions).
Optional subtle bloom/brightness on the display pass is welcome but not required.

7. UI Overlay

A minimal HUD (top-left, non-blocking pointer events) showing live FPS.
A small control panel (top-right) with sliders for: viscosity/velocity dissipation, dye dissipation, vorticity strength, and pressure iterations — updating the sim live. A "Reset" button clears all fields to black.

8. Constraints

Single .html file. All JS and GLSL inline (shaders as template strings). Must run by opening directly in a browser.
Must handle window resize: recreate framebuffers at the new aspect ratio without crashing.
Properly manage WebGL resources; no per-frame allocation of textures/buffers inside the render loop.
No memory leaks; no console errors on a WebGL2-capable browser.
Do NOT use localStorage/sessionStorage.
Code commented at each major section, especially each shader's role in the Navier-Stokes solve.

Success criteria for comparison: correctness of the Navier-Stokes solve (visible incompressible swirling, no obvious mass gain/loss or grid artifacts); whether the GPU ping-pong pipeline is implemented properly rather than faked; quality and stability of the vorticity/advection (rich, persistent swirls vs. mushy decay); smoothness and frame rate; robustness of resize and controls; clean, well-commented code.