{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"fluid-ink-grid-advection-own","type":"registry:block","title":"fluid ink (grid advection, own mini-runtime)","description":"\"hero is 91\" — ink that swirls and settles like liquid. (Full GPU Navier-Stokes = the WebGPU upgrade on radar.)","author":"Motion Menu","categories":["role-hero","heavy","simulation"],"dependencies":["gsap"],"registryDependencies":[],"meta":{"number":"91","slug":"fluid-ink-grid-advection-own","framework":"react","docs":"https://motion-menu-two.vercel.app/p/fluid-ink-grid-advection-own.md"},"files":[{"path":"motion-menu/fluid-ink-grid-advection-own.html","type":"registry:file","target":"motion-menu/fluid-ink-grid-advection-own.html","content":"<article class=\"card\">\n        <header><span class=\"num\">91</span><h3>Fluid ink</h3><div class=\"tags\"><i>simulation</i></div><a class=\"format-action\" data-format-link data-pattern-slug=\"fluid-ink-grid-advection-own\" href=\"/r/fluid-ink-grid-advection-own.json?framework=react\" title=\"Open the React registry output\">React ↗</a></header>\n        <div class=\"demo\"><canvas class=\"fluid-canvas\" id=\"fluid-canvas\"></canvas><p class=\"demo-hint\">stir it with your cursor</p></div>\n        <p class=\"order\">Say: <b>\"hero is 91\"</b> — ink that swirls and settles like liquid. (Full GPU Navier-Stokes = the WebGPU upgrade on radar.)</p>\n      </article>"},{"path":"motion-menu/fluid-ink-grid-advection-own.css","type":"registry:file","target":"motion-menu/fluid-ink-grid-advection-own.css","content":".card {\n    border: 1px solid var(--line); border-radius: 14px; background: var(--card);\n    overflow: hidden; display: flex; flex-direction: column;\n  }\n\n.card > header { display: flex; align-items: baseline; gap: .8rem; padding: 1.1rem 1.2rem .9rem; border-bottom: 1px solid var(--line); }\n\n.card .num { font-family: var(--mono); color: var(--acc); font-size: 1.05rem; }\n\n.card h3 { font-size: 1.02rem; font-weight: 600; letter-spacing: -0.01em; flex: 1; }\n\n.tags { display: flex; gap: .35rem; }\n\n.tags i {\n    font: 400 .62rem/1 var(--mono); font-style: normal; color: var(--dim);\n    border: 1px solid var(--line); border-radius: 99px; padding: .28em .6em; white-space: nowrap;\n  }\n\n.demo {\n    min-height: 250px; position: relative; display: flex; align-items: center; justify-content: center;\n    background: var(--bg2); overflow: hidden; flex: 1;\n  }\n\n.order { padding: .85rem 1.2rem; font-family: var(--mono); font-size: .72rem; color: var(--dim); border-top: 1px solid var(--line); }\n\n.order b { color: var(--txt); font-weight: 500; }\n\n.demo .btn.corner { position: absolute; top: 12px; right: 12px; padding: .55em 1em; font-size: .68rem; z-index: 5; }\n\n.demo-hint { position: absolute; bottom: 10px; left: 0; right: 0; text-align: center; font: 400 .65rem var(--mono); color: var(--dim); pointer-events: none; z-index: 4; }\n\n.ai-card .num { color: var(--acc2) !important; }\n\n/* ---------- Section M · heavy hitters ---------- */\n  .fluid-canvas { position: absolute; inset: 0; width: 100%; height: 100%; image-rendering: auto; }\n\n.demo:has(.hascheck input:checked) .hasbtn { border-color: var(--acc); color: var(--acc); pointer-events: auto; opacity: 1; }"},{"path":"components/motion-menu/fluid-ink-grid-advection-own.tsx","type":"registry:component","content":"// @ts-nocheck — generated wrapper; inlined pattern JS is imperative DOM code, not authored TS.\n\"use client\";\n// fluid ink (grid advection, own mini-runtime) — Motion Menu\nimport { useEffect, useRef } from \"react\";\n\nconst MARKUP = \"<article class=\\\"card\\\">\\n        <header><span class=\\\"num\\\">91</span><h3>Fluid ink</h3><div class=\\\"tags\\\"><i>simulation</i></div><a class=\\\"format-action\\\" data-format-link data-pattern-slug=\\\"fluid-ink-grid-advection-own\\\" href=\\\"/r/fluid-ink-grid-advection-own.json?framework=react\\\" title=\\\"Open the React registry output\\\">React \\u2197</a></header>\\n        <div class=\\\"demo\\\"><canvas class=\\\"fluid-canvas\\\" id=\\\"fluid-canvas\\\"></canvas><p class=\\\"demo-hint\\\">stir it with your cursor</p></div>\\n        <p class=\\\"order\\\">Say: <b>\\\"hero is 91\\\"</b> \\u2014 ink that swirls and settles like liquid. (Full GPU Navier-Stokes = the WebGPU upgrade on radar.)</p>\\n      </article>\";\n\nexport function FluidInkGridAdvectionOwn() {\n  const hostRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const host = hostRef.current;\n    if (!host) return;\n    const $ = (s: string) => host.querySelector(s) as HTMLElement | null;\n    let disposed = false;\n    const cleanup: Array<() => void> = [];\n\n    (async () => {\n      if (disposed) return;\n      try {\n        /* ---- 91 · fluid ink (grid advection, own mini-runtime) ---- */\n        {\n          const canvas = document.getElementById('fluid-canvas');\n          const W = 144, H = 84;\n          canvas.width = W; canvas.height = H;\n          const ctx = canvas.getContext('2d');\n          const img = ctx.createImageData(W, H);\n          const vx = new Float32Array(W * H), vy = new Float32Array(W * H), dye = new Float32Array(W * H);\n          const idx = (x, y) => y * W + x;\n          const sample = (arr, x, y) => {\n            x = Math.max(0, Math.min(W - 1.001, x)); y = Math.max(0, Math.min(H - 1.001, y));\n            const x0 = x | 0, y0 = y | 0, fx = x - x0, fy = y - y0;\n            return arr[idx(x0, y0)] * (1 - fx) * (1 - fy) + arr[idx(x0 + 1, y0)] * fx * (1 - fy)\n              + arr[idx(x0, y0 + 1)] * (1 - fx) * fy + arr[idx(x0 + 1, y0 + 1)] * fx * fy;\n          };\n          let last = null;\n          canvas.addEventListener('pointermove', (e) => {\n            const r = canvas.getBoundingClientRect();\n            const gx = (e.clientX - r.left) / r.width * W;\n            const gy = (e.clientY - r.top) / r.height * H;\n            const dx = last ? gx - last.x : 0, dy = last ? gy - last.y : 0;\n            last = { x: gx, y: gy };\n            for (let oy = -3; oy <= 3; oy++) for (let ox = -3; ox <= 3; ox++) {\n              const px = (gx + ox) | 0, py = (gy + oy) | 0;\n              if (px < 0 || px >= W || py < 0 || py >= H) continue;\n              const fall = Math.max(0, 1 - Math.hypot(ox, oy) / 3.5);\n              const i = idx(px, py);\n              vx[i] += dx * fall * 1.6; vy[i] += dy * fall * 1.6;\n              dye[i] = Math.min(1, dye[i] + fall * 0.5);\n            }\n          });\n          canvas.addEventListener('pointerleave', () => (last = null));\n          let fluidActive = false;\n          new IntersectionObserver((es) => (fluidActive = es[0].isIntersecting), { rootMargin: '100px' }).observe(canvas);\n          const nvx = new Float32Array(W * H), nvy = new Float32Array(W * H), ndye = new Float32Array(W * H);\n          let lastPointer = -9999;\n          canvas.addEventListener('pointermove', () => (lastPointer = performance.now()));\n          function autoStir(t) {\n            // an invisible finger draws slow lissajous curves while nobody is touching it\n            const gx = W * (0.5 + 0.34 * Math.sin(t * 0.7)), gy = H * (0.5 + 0.3 * Math.sin(t * 1.13 + 1.7));\n            const dx = Math.cos(t * 0.7) * 1.6, dy = Math.cos(t * 1.13 + 1.7) * 1.4;\n            for (let oy = -2; oy <= 2; oy++) for (let ox = -2; ox <= 2; ox++) {\n              const px = (gx + ox) | 0, py = (gy + oy) | 0;\n              if (px < 0 || px >= W || py < 0 || py >= H) continue;\n              const fall = Math.max(0, 1 - Math.hypot(ox, oy) / 2.5);\n              const i = idx(px, py);\n              vx[i] += dx * fall; vy[i] += dy * fall;\n              dye[i] = Math.min(1, dye[i] + fall * 0.2);\n            }\n          }\n          gsap.ticker.add((time, deltaMS) => {\n            if (!fluidActive) return;\n            if (performance.now() - lastPointer > 2000) autoStir(time);\n            const dt = Math.min(deltaMS / 1000, 0.04) * 34;\n            for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {\n              const i = idx(x, y);\n              const sx = x - vx[i] * dt, sy = y - vy[i] * dt;\n              nvx[i] = sample(vx, sx, sy) * 0.985;\n              nvy[i] = sample(vy, sx, sy) * 0.985;\n              ndye[i] = sample(dye, sx, sy) * 0.988;\n            }\n            vx.set(nvx); vy.set(nvy); dye.set(ndye);\n            const d = img.data;\n            for (let i = 0; i < W * H; i++) {\n              const v = dye[i];\n              const p = i * 4;\n              d[p] = 16 + v * (v > 0.5 ? 183 : 122) * v;\n              d[p + 1] = 16 + v * 255 * v * v;\n              d[p + 2] = 24 + v * 140 * (1 - v);\n              d[p + 3] = 255;\n            }\n            ctx.putImageData(img, 0, 0);\n          });\n        }\n      } catch (err: any) {\n        console.warn(\"[motion-menu] fluid-ink-grid-advection-own failed to start:\", err);\n      }\n    })();\n\n    return () => {\n      disposed = true;\n      cleanup.forEach((fn) => { try { fn(); } catch (_) {} });\n    };\n  }, []);\n\n  return <div ref={hostRef} dangerouslySetInnerHTML={{ __html: MARKUP }} />;\n}\n\nexport default FluidInkGridAdvectionOwn;\n"}]}