{"$schema":"https://ui.shadcn.com/schema/registry.json","author":"Kaiyu Hsu <uicapsule@kyh.io>","categories":["effects","colorful"],"dependencies":["motion"],"description":"The image-gen loading ritual done honestly — pure noise resolves through coarse color blobs into the sharp frame, the way a denoiser actually converges.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\n/**\n * Diffusion reveal — the image-gen loading ritual done honestly: pure\n * noise resolves through coarse color blobs into the sharp frame, the way\n * a denoiser actually converges. Canvas compositing: the target scene is\n * painted once offscreen, then each frame draws it at climbing resolution\n * under decaying noise.\n */\n\nconst WIDTH = 520;\nconst HEIGHT = 300;\nconst DURATION_MS = 6200;\nconst STEPS = 30;\n\n/** Paint the target \"generation\" — a dusk valley — onto a canvas. */\nconst paintScene = (ctx: CanvasRenderingContext2D, hueShift: number) => {\n  const h = (base: number) => (base + hueShift) % 360;\n\n  // Sky\n  const sky = ctx.createLinearGradient(0, 0, 0, HEIGHT);\n  sky.addColorStop(0, `oklch(0.35 0.09 ${String(h(280))})`);\n  sky.addColorStop(0.55, `oklch(0.62 0.15 ${String(h(35))})`);\n  sky.addColorStop(0.78, `oklch(0.5 0.12 ${String(h(25))})`);\n  sky.addColorStop(1, `oklch(0.3 0.06 ${String(h(300))})`);\n  ctx.fillStyle = sky;\n  ctx.fillRect(0, 0, WIDTH, HEIGHT);\n\n  // Sun\n  const sun = ctx.createRadialGradient(\n    WIDTH * 0.62,\n    HEIGHT * 0.52,\n    4,\n    WIDTH * 0.62,\n    HEIGHT * 0.52,\n    70,\n  );\n  sun.addColorStop(0, \"rgba(255,240,200,0.95)\");\n  sun.addColorStop(0.35, \"rgba(255,200,120,0.55)\");\n  sun.addColorStop(1, \"rgba(255,200,120,0)\");\n  ctx.fillStyle = sun;\n  ctx.fillRect(0, 0, WIDTH, HEIGHT);\n\n  // Ridges\n  const ridge = (baseY: number, amp: number, color: string, seed: number) => {\n    ctx.beginPath();\n    ctx.moveTo(0, HEIGHT);\n    for (let x = 0; x <= WIDTH; x += 8) {\n      const y =\n        baseY + Math.sin(x * 0.012 + seed) * amp + Math.sin(x * 0.031 + seed * 2.7) * amp * 0.45;\n      ctx.lineTo(x, y);\n    }\n    ctx.lineTo(WIDTH, HEIGHT);\n    ctx.closePath();\n    ctx.fillStyle = color;\n    ctx.fill();\n  };\n  ridge(HEIGHT * 0.58, 18, `oklch(0.32 0.06 ${String(h(270))})`, 1.3);\n  ridge(HEIGHT * 0.7, 24, `oklch(0.24 0.05 ${String(h(265))})`, 4.1);\n  ridge(HEIGHT * 0.84, 16, `oklch(0.17 0.04 ${String(h(260))})`, 7.9);\n\n  // Fog band\n  const fog = ctx.createLinearGradient(0, HEIGHT * 0.62, 0, HEIGHT * 0.8);\n  fog.addColorStop(0, \"rgba(255,220,190,0)\");\n  fog.addColorStop(0.5, \"rgba(255,220,190,0.16)\");\n  fog.addColorStop(1, \"rgba(255,220,190,0)\");\n  ctx.fillStyle = fog;\n  ctx.fillRect(0, HEIGHT * 0.55, WIDTH, HEIGHT * 0.3);\n};\n\nexport const DiffusionReveal = () => {\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n  const [progress, setProgress] = useState(0);\n  const [seed, setSeed] = useState(0);\n  const [elapsed, setElapsed] = useState<number | null>(null);\n  const reduceMotion = useReducedMotion() ?? false;\n\n  const regenerate = () => {\n    setElapsed(null);\n    setProgress(0);\n    setSeed((s) => s + 1);\n  };\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const ctx = canvas?.getContext(\"2d\");\n    if (!canvas || !ctx) {\n      return;\n    }\n\n    // Target scene, painted once.\n    const scene = document.createElement(\"canvas\");\n    scene.width = WIDTH;\n    scene.height = HEIGHT;\n    const sceneCtx = scene.getContext(\"2d\");\n    if (!sceneCtx) {\n      return;\n    }\n    paintScene(sceneCtx, seed * 73);\n\n    // Small noise tile, regenerated every few frames.\n    const noise = document.createElement(\"canvas\");\n    noise.width = 128;\n    noise.height = 128;\n    const noiseCtx = noise.getContext(\"2d\");\n    if (!noiseCtx) {\n      return;\n    }\n    const refreshNoise = () => {\n      const data = noiseCtx.createImageData(128, 128);\n      for (let i = 0; i < data.data.length; i += 4) {\n        const v = Math.random() * 255;\n        data.data[i] = v;\n        data.data[i + 1] = v * (0.85 + Math.random() * 0.3);\n        data.data[i + 2] = v * (0.9 + Math.random() * 0.3);\n        data.data[i + 3] = 255;\n      }\n      noiseCtx.putImageData(data, 0, 0);\n    };\n\n    let raf = 0;\n    let frame = 0;\n    const start = performance.now();\n\n    const tick = (now: number) => {\n      const t = Math.min(1, (now - start) / DURATION_MS);\n      // Ease the convergence — denoisers sprint at the end.\n      const eased = t * t * (3 - 2 * t);\n      setProgress(t);\n      if (t >= 1) {\n        setElapsed((now - start) / 1000);\n      }\n\n      frame += 1;\n      // Reduced motion keeps the grain frozen: the convergence still reads, without the flicker.\n      if (!reduceMotion && frame % 3 === 0 && t < 1) {\n        refreshNoise();\n      }\n\n      // 1) coarse reconstruction: draw scene at low res, upscale smooth\n      const res = Math.max(0.012, eased ** 1.6);\n      const rw = Math.max(6, Math.round(WIDTH * res));\n      const rh = Math.max(4, Math.round(HEIGHT * res));\n      ctx.imageSmoothingEnabled = true;\n      ctx.clearRect(0, 0, WIDTH, HEIGHT);\n      // downscale into corner then upscale full\n      ctx.drawImage(scene, 0, 0, WIDTH, HEIGHT, 0, 0, rw, rh);\n      ctx.drawImage(canvas, 0, 0, rw, rh, 0, 0, WIDTH, HEIGHT);\n\n      // 2) noise veil, decaying hard near the end\n      const noiseAlpha = (1 - eased) ** 1.35;\n      if (noiseAlpha > 0.004) {\n        ctx.globalAlpha = noiseAlpha;\n        for (let x = 0; x < WIDTH; x += 128) {\n          for (let y = 0; y < HEIGHT; y += 128) {\n            ctx.drawImage(noise, x, y);\n          }\n        }\n        ctx.globalAlpha = 1;\n      }\n\n      if (t < 1) {\n        raf = requestAnimationFrame(tick);\n      }\n    };\n\n    refreshNoise();\n    raf = requestAnimationFrame(tick);\n    return () => cancelAnimationFrame(raf);\n  }, [seed, reduceMotion]);\n\n  const step = Math.min(STEPS, Math.max(1, Math.ceil(progress * STEPS)));\n  const done = progress >= 1;\n\n  return (\n    <div className=\"w-[560px] rounded-3xl bg-[#150f22] p-5 shadow-2xl shadow-black/60 ring-1 ring-white/10 select-none\">\n      <div className=\"mb-4 flex items-center justify-between gap-3 px-1.5\">\n        <p className=\"truncate text-[12.5px] text-white/65\">\n          <span className=\"mr-1.5 text-white/30\">Prompt</span>a quiet valley at dusk, low fog, oil\n          painting\n        </p>\n        <button\n          type=\"button\"\n          onClick={regenerate}\n          className=\"shrink-0 rounded-full bg-white/[0.07] px-3.5 py-1.5 text-[11px] font-medium text-white/70 transition-colors hover:bg-white/[0.12]\"\n        >\n          Regenerate\n        </button>\n      </div>\n\n      <motion.div\n        animate={{ scale: done ? 1 : 0.995 }}\n        transition={{ damping: 24, stiffness: 300, type: \"spring\" }}\n        className=\"relative overflow-hidden rounded-2xl ring-1 ring-white/[0.08]\"\n      >\n        <canvas ref={canvasRef} width={WIDTH} height={HEIGHT} className=\"block w-full\" />\n        <div className=\"absolute top-3 left-3 rounded-full bg-black/55 px-2.5 py-1 text-[10px] font-medium text-white/85 backdrop-blur tabular-nums\">\n          {done && elapsed !== null\n            ? `Done in ${elapsed.toFixed(1)}s`\n            : `Denoising · step ${String(step)}/${String(STEPS)}`}\n        </div>\n      </motion.div>\n\n      <div className=\"mt-4 px-1.5 pb-1\">\n        <div className=\"h-[4px] overflow-hidden rounded-full bg-white/[0.07]\">\n          <motion.div\n            animate={{ width: `${String(progress * 100)}%` }}\n            transition={{ duration: 0.1, ease: \"linear\" }}\n            className={`h-full rounded-full ${done ? \"bg-emerald-400/80\" : \"bg-white/60\"}`}\n          />\n        </div>\n      </div>\n    </div>\n  );\n};\n","path":"/diffusion-reveal.tsx","target":"uicapsule/diffusion-reveal/diffusion-reveal.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport { DiffusionReveal } from \"./diffusion-reveal\";\n\nconst Preview = () => (\n  <main className=\"flex h-dvh items-center justify-center overflow-hidden bg-[radial-gradient(ellipse_75%_55%_at_50%_0%,rgb(167_139_250/0.22)_0%,transparent_70%),radial-gradient(ellipse_90%_45%_at_50%_105%,rgb(236_72_153/0.14)_0%,transparent_70%),linear-gradient(180deg,#1c1233_0%,#130c24_55%,#0a0614_100%)] p-5\">\n    <DiffusionReveal />\n  </main>\n);\n\nexport default Preview;\n","path":"/preview.tsx","target":"uicapsule/diffusion-reveal/preview.tsx","type":"registry:file"}],"homepage":"https://www.uicapsule.com/ui/diffusion-reveal","name":"diffusion-reveal","registryDependencies":[],"title":"Diffusion Reveal","type":"registry:block"}