{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "not-found-knockout",
  "title": "Not Found Knockout",
  "description": "404 numerals rendered as literal absence: destination-out punches the glyphs through a token surface plane to reveal a deep grain void with drifting motes, defined by a bright stencil bevel rim and an inner wall-light glow so the digits read instantly in both themes, edges springing outward near the cursor.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/loud/not-found-knockout/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Knockout404 — the 404 rendered as literal absence: every frame, a freshly\n// refilled --surface plane is carved with destination-out so the numerals\n// become holes revealing a deep grain void beneath (precomputed value-noise\n// tile + 12 drifting motes), with an inner wall-light glow clipped to the\n// holes so the cut reads as depth. A bright stencil bevel rims the cutout,\n// rebuilt each frame from ~400 traced edge points whose spring-loaded\n// normals bulge outward near the cursor (gaussian falloff, one small\n// overshoot, forced-settle deadline). Because the frame starts from an\n// explicit full clear and the carve always composites against a fresh fill,\n// destination-out alpha can never accumulate or quantize — by construction.\n// Canvas 2D, refs-only hot path, sleeps at cursor idle + settled springs.\n// Copy and CTAs are DOM, offset-transformed from the container center.\n// ---------------------------------------------------------------------------\n\ntype Vec3 = [number, number, number];\n\nfunction parseColor(raw: string): Vec3 | null {\n  const s = raw.trim();\n  if (s.startsWith(\"#\")) {\n    const hex = s.slice(1);\n    if (hex.length === 3) {\n      const r = parseInt(hex.slice(0, 1) + hex.slice(0, 1), 16);\n      const g = parseInt(hex.slice(1, 2) + hex.slice(1, 2), 16);\n      const b = parseInt(hex.slice(2, 3) + hex.slice(2, 3), 16);\n      return Number.isNaN(r + g + b) ? null : [r, g, b];\n    }\n    if (hex.length >= 6) {\n      const r = parseInt(hex.slice(0, 2), 16);\n      const g = parseInt(hex.slice(2, 4), 16);\n      const b = parseInt(hex.slice(4, 6), 16);\n      return Number.isNaN(r + g + b) ? null : [r, g, b];\n    }\n    return null;\n  }\n  const m = s.match(/rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);\n  return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;\n}\n\nfunction mix(a: Vec3, b: Vec3, t: number): Vec3 {\n  return [\n    Math.round(a[0] + (b[0] - a[0]) * t),\n    Math.round(a[1] + (b[1] - a[1]) * t),\n    Math.round(a[2] + (b[2] - a[2]) * t),\n  ];\n}\n\nfunction css(c: Vec3): string {\n  return `rgb(${c[0]},${c[1]},${c[2]})`;\n}\n\n// deterministic prng — grain field + mote seeding stay stable across themes\nfunction mulberry32(a: number) {\n  return () => {\n    a |= 0;\n    a = (a + 0x6d2b79f5) | 0;\n    let t = Math.imul(a ^ (a >>> 15), 1 | a);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\n// -- seamless 128px value-noise field, computed once per module ------------\nconst GRAIN = 128;\nlet noiseCache: Float32Array | null = null;\nfunction noiseField(): Float32Array {\n  if (noiseCache) return noiseCache;\n  const f = new Float32Array(GRAIN * GRAIN);\n  const rand = mulberry32(0x1a2b3c);\n  const octave = (cells: number, weight: number) => {\n    const lat = new Float32Array(cells * cells);\n    for (let i = 0; i < lat.length; i++) lat[i] = rand();\n    const sm = (t: number) => t * t * (3 - 2 * t);\n    for (let y = 0; y < GRAIN; y++) {\n      const gy = (y / GRAIN) * cells;\n      const y0 = Math.floor(gy) % cells;\n      const y1 = (y0 + 1) % cells;\n      const ty = sm(gy - Math.floor(gy));\n      for (let x = 0; x < GRAIN; x++) {\n        const gx = (x / GRAIN) * cells;\n        const x0 = Math.floor(gx) % cells;\n        const x1 = (x0 + 1) % cells;\n        const tx = sm(gx - Math.floor(gx));\n        const a = lat[y0 * cells + x0] + (lat[y0 * cells + x1] - lat[y0 * cells + x0]) * tx;\n        const b = lat[y1 * cells + x0] + (lat[y1 * cells + x1] - lat[y1 * cells + x0]) * tx;\n        f[y * GRAIN + x] += weight * (a + (b - a) * ty);\n      }\n    }\n  };\n  octave(8, 0.5); // slow undulation\n  octave(32, 0.3); // mid grain\n  for (let i = 0; i < f.length; i++) f[i] += 0.2 * rand(); // fine speckle\n  noiseCache = f;\n  return f;\n}\n\n// -- Moore-neighbor contour tracing over a binary glyph mask ----------------\nconst DX = [1, 1, 0, -1, -1, -1, 0, 1];\nconst DY = [0, 1, 1, 1, 0, -1, -1, -1];\n\nfunction traceLoops(mask: Uint8Array, gw: number, gh: number): number[][] {\n  const visited = new Uint8Array(gw * gh);\n  const loops: number[][] = [];\n  const at = (x: number, y: number) =>\n    x >= 0 && x < gw && y >= 0 && y < gh && mask[y * gw + x] === 1;\n  for (let y = 0; y < gh; y++) {\n    for (let x = 0; x < gw; x++) {\n      const idx = y * gw + x;\n      // a trace starts at an unvisited ink pixel with void to its left —\n      // finds outer contours and inner counters (the 0's hole) alike\n      if (mask[idx] !== 1 || visited[idx] || at(x - 1, y)) continue;\n      const loop: number[] = [];\n      let cx = x;\n      let cy = y;\n      let bdir = 4; // came from the west (the background we scanned across)\n      const cap = gw * gh;\n      for (let step = 0; step < cap; step++) {\n        visited[cy * gw + cx] = 1;\n        loop.push(cx, cy);\n        let found = -1;\n        for (let k = 1; k <= 8; k++) {\n          const d = (bdir + k) % 8;\n          if (at(cx + DX[d], cy + DY[d])) {\n            found = d;\n            break;\n          }\n        }\n        if (found < 0) break; // isolated pixel\n        cx += DX[found];\n        cy += DY[found];\n        bdir = (found + 6) % 8;\n        if (cx === x && cy === y) break;\n      }\n      if (loop.length >= 16) loops.push(loop);\n    }\n  }\n  return loops;\n}\n\n// resample traced loops to ~total arc-length-even points with outward\n// normals; returns (bx,by,nx,ny,d,v) stride-6 floats + loop index ranges\nfunction buildEdgePoints(\n  mask: Uint8Array,\n  gw: number,\n  gh: number,\n  scale: number,\n  total: number\n): { pts: Float32Array; ranges: number[] } {\n  const raw = traceLoops(mask, gw, gh);\n  const loops: { xs: Float32Array; ys: Float32Array; len: number; cum: Float32Array }[] = [];\n  let totalLen = 0;\n  for (const flat of raw) {\n    const n = flat.length / 2;\n    const xs = new Float32Array(n);\n    const ys = new Float32Array(n);\n    for (let i = 0; i < n; i++) {\n      xs[i] = (flat[i * 2] + 0.5) * scale;\n      ys[i] = (flat[i * 2 + 1] + 0.5) * scale;\n    }\n    const cum = new Float32Array(n + 1);\n    for (let i = 0; i < n; i++) {\n      const j = (i + 1) % n;\n      cum[i + 1] = cum[i] + Math.hypot(xs[j] - xs[i], ys[j] - ys[i]);\n    }\n    const len = cum[n];\n    if (len < 48) continue; // drop specks\n    loops.push({ xs, ys, len, cum });\n    totalLen += len;\n  }\n  if (!totalLen) return { pts: new Float32Array(0), ranges: [] };\n\n  const out: number[] = [];\n  const ranges: number[] = [];\n  for (const loop of loops) {\n    const n = Math.max(12, Math.round((loop.len / totalLen) * total));\n    const step = loop.len / n;\n    const m = loop.xs.length;\n    const rx = new Float32Array(n);\n    const ry = new Float32Array(n);\n    let j = 0;\n    for (let k = 0; k < n; k++) {\n      const t = k * step;\n      while (j < m - 1 && loop.cum[j + 1] < t) j++;\n      const segLen = loop.cum[j + 1] - loop.cum[j];\n      const u = segLen > 1e-6 ? (t - loop.cum[j]) / segLen : 0;\n      const a = j;\n      const b = (j + 1) % m;\n      rx[k] = loop.xs[a] + (loop.xs[b] - loop.xs[a]) * u;\n      ry[k] = loop.ys[a] + (loop.ys[b] - loop.ys[a]) * u;\n    }\n    // two closed-loop smoothing passes kill raster stairsteps\n    for (let pass = 0; pass < 2; pass++) {\n      const px = Float32Array.from(rx);\n      const py = Float32Array.from(ry);\n      for (let i = 0; i < n; i++) {\n        const p = (i - 1 + n) % n;\n        const q = (i + 1) % n;\n        rx[i] = (px[p] + 2 * px[i] + px[q]) / 4;\n        ry[i] = (py[p] + 2 * py[i] + py[q]) / 4;\n      }\n    }\n    const base = out.length / 6;\n    ranges.push(base, base + n);\n    for (let i = 0; i < n; i++) {\n      const p = (i - 1 + n) % n;\n      const q = (i + 1) % n;\n      const tx = rx[q] - rx[p];\n      const ty = ry[q] - ry[p];\n      const L = Math.hypot(tx, ty);\n      let nx = L > 1e-6 ? ty / L : 1;\n      let ny = L > 1e-6 ? -tx / L : 0;\n      // orient outward — away from glyph ink, so displacement widens the hole\n      const sx = Math.round((rx[i] + nx * 3) / scale - 0.5);\n      const sy = Math.round((ry[i] + ny * 3) / scale - 0.5);\n      if (sx >= 0 && sx < gw && sy >= 0 && sy < gh && mask[sy * gw + sx] === 1) {\n        nx = -nx;\n        ny = -ny;\n      }\n      out.push(rx[i], ry[i], nx, ny, 0, 0);\n    }\n  }\n  return { pts: Float32Array.from(out), ranges };\n}\n\n// -- tuning -----------------------------------------------------------------\nconst EDGE_POINTS = 400;\nconst SIGMA = 120; // px — gaussian falloff of cursor influence\nconst INFLUENCE_R2 = (SIGMA * 3) * (SIGMA * 3);\nconst MAX_PUSH = 6; // px — outward displacement ceiling\nconst SPRING_K = 520; // s⁻² — settle < 400ms\nconst SPRING_C = 32.8; // ζ ≈ 0.72 → one ~3% overshoot\nconst SLEEP_MS = 500; // cursor idle before the loop may sleep\nconst INPUT_CUTOFF_MS = 600; // forced-settle deadline: influence dies here\nconst HARD_ZERO_MS = 1400; // absolute deadline — springs snapped to rest\nconst INTRO_MS = 2200; // ambient mote drift on mount before first sleep\nconst MOTES = 12;\nconst MOTE_SPEED = 4; // px/s\nconst MOTE_ALPHA = 0.3;\nconst GRAIN_ALPHA = 0.2;\nconst EPS_D = 0.05; // px\nconst EPS_V = 1; // px/s\nconst DT_MAX = 0.05;\n\nexport function Knockout404({\n  glyph = \"404\",\n  message = \"This page drifted into the void.\",\n  primaryLabel = \"Take me home\",\n  primaryHref = \"/\",\n  secondaryLabel = \"Contact support\",\n  secondaryHref = \"#support\",\n  className = \"\",\n}: {\n  /** the carved numerals */\n  glyph?: string;\n  /** muted line floating in the punched negative space */\n  message?: string;\n  /** primary CTA text */\n  primaryLabel?: string;\n  /** primary CTA href */\n  primaryHref?: string;\n  /** secondary CTA text */\n  secondaryLabel?: string;\n  /** secondary CTA href */\n  secondaryHref?: string;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const contentRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const root = rootRef.current;\n    const canvas = canvasRef.current;\n    const content = contentRef.current;\n    if (!root || !canvas || !content) return;\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n\n    const reduced = window.matchMedia(\n      \"(prefers-reduced-motion: reduce)\"\n    ).matches;\n\n    // offscreen layers: carved plane (dpr-sized), glyph mask (dpr-sized),\n    // half-res sampler for edge tracing, 128px grain tile\n    const plane = document.createElement(\"canvas\");\n    const glyphCv = document.createElement(\"canvas\");\n    const sampler = document.createElement(\"canvas\");\n    const grainCv = document.createElement(\"canvas\");\n    const glowCv = document.createElement(\"canvas\");\n    const pctx = plane.getContext(\"2d\");\n    const gctx = glyphCv.getContext(\"2d\");\n    const sctx = sampler.getContext(\"2d\", { willReadFrequently: true });\n    const grctx = grainCv.getContext(\"2d\");\n    const glctx = glowCv.getContext(\"2d\");\n    if (!pctx || !gctx || !sctx || !grctx || !glctx) return;\n\n    const fontFamily = getComputedStyle(root).fontFamily || \"sans-serif\";\n\n    // -- token inks: read at mount, re-derived live on theme flips ----------\n    let surfaceCss = \"#171717\";\n    let borderCss = \"#2e2e2e\";\n    let mutedCss = \"#8f8f8f\";\n    let voidCss = \"#050505\";\n    let rimShadowCss = \"#1c1c1c\";\n    let rimCatchCss = \"#b5b5b5\";\n    let edgeCss = \"#9a9a9a\";\n    let glowCss = \"#b0b0b0\";\n    let mutedRgb: Vec3 = [143, 143, 143];\n    const derive = () => {\n      const cs = getComputedStyle(document.documentElement);\n      const bg = parseColor(cs.getPropertyValue(\"--background\")) ?? [10, 10, 10];\n      const fg = parseColor(cs.getPropertyValue(\"--foreground\")) ?? [237, 237, 237];\n      const surface = parseColor(cs.getPropertyValue(\"--surface\")) ?? [23, 23, 23];\n      const border = parseColor(cs.getPropertyValue(\"--border\")) ?? [46, 46, 46];\n      mutedRgb = parseColor(cs.getPropertyValue(\"--ns-muted\")) ?? [143, 143, 143];\n      const lum = (0.2126 * bg[0] + 0.7152 * bg[1] + 0.0722 * bg[2]) / 255;\n      // the void must read CLEARLY dimmer than the surface in BOTH themes:\n      // dark crushes --background toward black; light pulls it a solid step\n      // toward --foreground — the interior/surface gap is the legibility floor\n      voidCss = css(lum < 0.5 ? mix(bg, [0, 0, 0], 0.6) : mix(bg, fg, 0.16));\n      surfaceCss = css(surface);\n      borderCss = css(border);\n      mutedCss = css(mutedRgb);\n      // stencil bevel, engraved in both themes: the tone mixed toward the\n      // darker token is the top-left inner shadow, the lighter one the\n      // bottom-right catch-light — a hole, never an emboss\n      const towardFg = mix(border, fg, 0.8);\n      const towardBg = mix(border, bg, 0.55);\n      rimShadowCss = css(lum < 0.5 ? towardBg : towardFg);\n      rimCatchCss = css(lum < 0.5 ? towardFg : towardBg);\n      // crisp definition stroke + inner wall-light, both toward --foreground:\n      // brighter than the surface in dark, darker than the surface in light\n      edgeCss = css(mix(border, fg, 0.55));\n      glowCss = css(mix(mutedRgb, fg, 0.45));\n    };\n    derive();\n\n    // grain tile: recolor the cached noise field in --ns-muted per theme\n    let grainPattern: CanvasPattern | null = null;\n    const buildGrain = () => {\n      const f = noiseField();\n      grainCv.width = GRAIN;\n      grainCv.height = GRAIN;\n      const img = grctx.createImageData(GRAIN, GRAIN);\n      const d = img.data;\n      for (let i = 0; i < f.length; i++) {\n        const v = Math.min(1, Math.max(0, f[i]));\n        d[i * 4] = mutedRgb[0];\n        d[i * 4 + 1] = mutedRgb[1];\n        d[i * 4 + 2] = mutedRgb[2];\n        d[i * 4 + 3] = Math.round(255 * GRAIN_ALPHA * (0.3 + 0.7 * v));\n      }\n      grctx.putImageData(img, 0, 0);\n      grainPattern = ctx.createPattern(grainCv, \"repeat\");\n    };\n    buildGrain();\n\n    // -- sizing: explicit style.width/height — inset does not size a canvas -\n    let w = 0;\n    let h = 0;\n    let dpr = 1;\n    let fs = 0; // glyph font size in css px\n    let glyphW = 0;\n    let sized = false;\n    let ready = false; // fonts loaded + assets built\n\n    const buildGlyph = () => {\n      fs = Math.min(0.38 * w, 0.62 * h);\n      gctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      gctx.clearRect(0, 0, w, h);\n      gctx.font = `600 ${fs}px ${fontFamily}`;\n      gctx.textAlign = \"center\";\n      gctx.textBaseline = \"middle\";\n      gctx.fillStyle = \"#fff\"; // alpha mask — only coverage matters\n      gctx.fillText(glyph, w / 2, h * 0.46);\n      glyphW = gctx.measureText(glyph).width;\n    };\n\n    // -- edge points: glyph outline sampled once per resize -----------------\n    let pts: Float32Array = new Float32Array(0);\n    let ranges: number[] = [];\n    const buildEdges = () => {\n      const scale = 2; // half-res mask is plenty after smoothing\n      const gw = Math.max(1, Math.ceil(w / scale));\n      const gh = Math.max(1, Math.ceil(h / scale));\n      sampler.width = gw;\n      sampler.height = gh;\n      sctx.font = `600 ${fs / scale}px ${fontFamily}`;\n      sctx.textAlign = \"center\";\n      sctx.textBaseline = \"middle\";\n      sctx.fillStyle = \"#fff\";\n      sctx.fillText(glyph, w / 2 / scale, (h * 0.46) / scale);\n      const data = sctx.getImageData(0, 0, gw, gh).data;\n      const mask = new Uint8Array(gw * gh);\n      for (let i = 0; i < mask.length; i++) mask[i] = data[i * 4 + 3] > 127 ? 1 : 0;\n      const res = buildEdgePoints(mask, gw, gh, scale, EDGE_POINTS);\n      pts = res.pts;\n      ranges = res.ranges;\n    };\n\n    // -- inner wall-light: soft glow hugging the cut edge, clipped to the\n    // holes via destination-in against the glyph mask — static per\n    // resize/theme, so draw() just blits it. This is what makes the digit\n    // interiors read as carved depth instead of vanishing into the void.\n    const buildGlow = () => {\n      if (!ranges.length) return;\n      glowCv.width = canvas.width;\n      glowCv.height = canvas.height;\n      glctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      glctx.clearRect(0, 0, w, h);\n      glctx.strokeStyle = glowCss;\n      glctx.lineJoin = \"round\";\n      glctx.beginPath();\n      for (let r = 0; r < ranges.length; r += 2) {\n        const s = ranges[r];\n        const e = ranges[r + 1];\n        for (let i = s; i < e; i++) {\n          const o = i * 6;\n          if (i === s) glctx.moveTo(pts[o], pts[o + 1]);\n          else glctx.lineTo(pts[o], pts[o + 1]);\n        }\n        glctx.closePath();\n      }\n      const blur = Math.max(14, fs * 0.07);\n      glctx.shadowColor = glowCss;\n      glctx.lineWidth = 3;\n      glctx.shadowBlur = blur;\n      glctx.globalAlpha = 0.85;\n      glctx.stroke();\n      glctx.shadowBlur = blur * 2.4;\n      glctx.globalAlpha = 0.5;\n      glctx.stroke();\n      glctx.shadowBlur = 0;\n      glctx.globalAlpha = 1;\n      glctx.globalCompositeOperation = \"destination-in\";\n      glctx.setTransform(1, 0, 0, 1, 0, 0);\n      glctx.drawImage(glyphCv, 0, 0);\n      glctx.globalCompositeOperation = \"source-over\";\n    };\n\n    // -- motes: 12 slow sparks wrapped inside the glyph's bounding box ------\n    const moteX = new Float32Array(MOTES);\n    const moteY = new Float32Array(MOTES);\n    const moteVX = new Float32Array(MOTES);\n    const moteVY = new Float32Array(MOTES);\n    const moteS = new Float32Array(MOTES);\n    let bx0 = 0;\n    let bx1 = 0;\n    let by0 = 0;\n    let by1 = 0;\n    const initMotes = () => {\n      const pad = 32;\n      bx0 = Math.max(0, w / 2 - glyphW / 2 - pad);\n      bx1 = Math.min(w, w / 2 + glyphW / 2 + pad);\n      by0 = Math.max(0, h * 0.46 - fs * 0.38 - pad);\n      by1 = Math.min(h, h * 0.46 + fs * 0.38 + pad);\n      const rand = mulberry32(0x404);\n      for (let i = 0; i < MOTES; i++) {\n        moteX[i] = bx0 + rand() * (bx1 - bx0);\n        moteY[i] = by0 + rand() * (by1 - by0);\n        const a = rand() * Math.PI * 2;\n        moteVX[i] = Math.cos(a) * MOTE_SPEED;\n        moteVY[i] = Math.sin(a) * MOTE_SPEED;\n        moteS[i] = i % 3 === 0 ? 2 : 1;\n      }\n    };\n\n    const placeContent = () => {\n      // copy/CTAs hang below the numerals: OFFSET transform from container\n      // center — never absolute canvas coords\n      content.style.transform = `translate(-50%, ${Math.round(fs * 0.38 + 28)}px)`;\n    };\n\n    const resize = () => {\n      const rect = root.getBoundingClientRect();\n      if (rect.width < 2 || rect.height < 2) {\n        sized = false; // zero-size guard — loop no-ops until real\n        return;\n      }\n      w = rect.width;\n      h = rect.height;\n      dpr = Math.min(2, window.devicePixelRatio || 1);\n      canvas.style.width = `${w}px`; // replaced element: size explicitly\n      canvas.style.height = `${h}px`;\n      canvas.width = Math.max(1, Math.round(w * dpr));\n      canvas.height = Math.max(1, Math.round(h * dpr));\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      plane.width = canvas.width;\n      plane.height = canvas.height;\n      glyphCv.width = canvas.width;\n      glyphCv.height = canvas.height;\n      sized = true;\n      if (ready) {\n        buildGlyph();\n        buildEdges();\n        buildGlow();\n        initMotes();\n        placeContent();\n      }\n    };\n    resize();\n\n    const strokeRim = (\n      ox: number,\n      oy: number,\n      color: string,\n      alpha: number,\n      width = 1\n    ) => {\n      if (!ranges.length) return;\n      ctx.save();\n      ctx.translate(ox, oy);\n      ctx.strokeStyle = color;\n      ctx.globalAlpha = alpha;\n      ctx.lineWidth = width;\n      ctx.lineJoin = \"round\";\n      ctx.beginPath();\n      for (let r = 0; r < ranges.length; r += 2) {\n        const s = ranges[r];\n        const e = ranges[r + 1];\n        for (let i = s; i < e; i++) {\n          const o = i * 6;\n          const x = pts[o] + pts[o + 2] * pts[o + 4];\n          const y = pts[o + 1] + pts[o + 3] * pts[o + 4];\n          if (i === s) ctx.moveTo(x, y);\n          else ctx.lineTo(x, y);\n        }\n        ctx.closePath();\n      }\n      ctx.stroke();\n      ctx.restore();\n    };\n\n    const draw = () => {\n      if (!sized || !ready) return;\n      // explicit full clear — nothing survives from the previous frame\n      ctx.clearRect(0, 0, w, h);\n      // (a) void backdrop: dim base, tiled grain, drifting motes\n      ctx.fillStyle = voidCss;\n      ctx.fillRect(0, 0, w, h);\n      if (grainPattern) {\n        ctx.save();\n        ctx.setTransform(1, 0, 0, 1, 0, 0); // device px → crisp grain\n        ctx.fillStyle = grainPattern;\n        ctx.fillRect(0, 0, canvas.width, canvas.height);\n        ctx.restore();\n      }\n      ctx.globalAlpha = MOTE_ALPHA;\n      ctx.fillStyle = mutedCss;\n      for (let i = 0; i < MOTES; i++) {\n        const s = moteS[i];\n        ctx.fillRect(moteX[i] - s / 2, moteY[i] - s / 2, s, s);\n      }\n      ctx.globalAlpha = 1;\n      // (b)+(c) surface plane refilled fresh, then carved: destination-out\n      // only ever composites against this frame's fill — no accumulation\n      pctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      pctx.clearRect(0, 0, w, h);\n      pctx.fillStyle = surfaceCss;\n      pctx.fillRect(0, 0, w, h);\n      pctx.globalCompositeOperation = \"destination-out\";\n      pctx.drawImage(glyphCv, 0, 0, w, h);\n      pctx.globalCompositeOperation = \"source-over\";\n      ctx.drawImage(plane, 0, 0, w, h);\n      // (c2) inner wall-light blit — the holes catch light along their edges\n      if (glowCv.width > 0) ctx.drawImage(glowCv, 0, 0, w, h);\n      // (d) stencil bevel rim, restroked from the displaced point list —\n      // bright catch + 1.5px definition stroke keep the digits unmistakable\n      strokeRim(-0.75, -0.75, rimShadowCss, 0.95);\n      strokeRim(0.75, 0.75, rimCatchCss, 1);\n      strokeRim(0, 0, edgeCss, 1, 1.5);\n    };\n\n    // -- hot-path state: locals only, never React state ---------------------\n    let raf = 0;\n    let last = 0;\n    let visible = true;\n    let px = 0;\n    let py = 0;\n    let pointerActive = false;\n    let lastMove = -1e9;\n    let disposed = false;\n    const mountedAt = performance.now();\n\n    const loop = (now: number) => {\n      raf = 0;\n      if (!visible || document.hidden || !sized || !ready) {\n        last = 0;\n        return; // observers/visibilitychange wake us\n      }\n      const dt = last === 0 ? 1 / 60 : Math.min(DT_MAX, (now - last) / 1000);\n      last = now;\n      const idle = now - lastMove;\n\n      // edge springs — cursor influence dies at the forced-settle deadline,\n      // and a hard deadline snaps everything to rest so sleep is guaranteed\n      let settled = true;\n      const n = pts.length / 6;\n      if (idle > HARD_ZERO_MS) {\n        for (let i = 0; i < n; i++) {\n          pts[i * 6 + 4] = 0;\n          pts[i * 6 + 5] = 0;\n        }\n      } else {\n        const influence = pointerActive && idle < INPUT_CUTOFF_MS;\n        for (let i = 0; i < n; i++) {\n          const o = i * 6;\n          let target = 0;\n          if (influence) {\n            const dx = px - pts[o];\n            const dy = py - pts[o + 1];\n            const d2 = dx * dx + dy * dy;\n            if (d2 < INFLUENCE_R2) {\n              target = MAX_PUSH * Math.exp(-d2 / (2 * SIGMA * SIGMA));\n            }\n          }\n          let d = pts[o + 4];\n          let v = pts[o + 5];\n          const a = SPRING_K * (target - d) - SPRING_C * v;\n          v += a * dt;\n          d += v * dt;\n          pts[o + 4] = d;\n          pts[o + 5] = v;\n          if (Math.abs(d - target) > EPS_D || Math.abs(v) > EPS_V || target > EPS_D) {\n            settled = false;\n          }\n        }\n      }\n\n      // motes: 4px/s ambient drift on wrapped paths inside the glyph bbox\n      const bw = bx1 - bx0;\n      const bh = by1 - by0;\n      if (bw > 1 && bh > 1) {\n        for (let i = 0; i < MOTES; i++) {\n          let x = moteX[i] + moteVX[i] * dt;\n          let y = moteY[i] + moteVY[i] * dt;\n          if (x < bx0) x += bw;\n          else if (x > bx1) x -= bw;\n          if (y < by0) y += bh;\n          else if (y > by1) y -= bh;\n          moteX[i] = x;\n          moteY[i] = y;\n        }\n      }\n\n      draw();\n\n      // sleep: cursor idle past threshold, springs under epsilon, intro done\n      if (idle > SLEEP_MS && settled && now - mountedAt > INTRO_MS) {\n        last = 0;\n        return; // motes freeze with the loop — redraw resumes on wake\n      }\n      raf = requestAnimationFrame(loop);\n    };\n\n    const wake = () => {\n      if (reduced || raf || disposed) return;\n      last = 0;\n      raf = requestAnimationFrame(loop);\n    };\n\n    const onMove = (e: PointerEvent) => {\n      const rect = root.getBoundingClientRect();\n      px = e.clientX - rect.left;\n      py = e.clientY - rect.top;\n      pointerActive = true;\n      lastMove = performance.now();\n      wake();\n    };\n    const onLeave = () => {\n      pointerActive = false;\n      wake(); // springs animate home, then the loop sleeps\n    };\n\n    // -- observers ----------------------------------------------------------\n    const ro = new ResizeObserver(() => {\n      resize();\n      draw();\n      wake();\n    });\n    ro.observe(root);\n    const io = new IntersectionObserver((entries) => {\n      visible = entries[0]?.isIntersecting ?? true;\n      if (visible) wake();\n    });\n    io.observe(root);\n    // live theme re-derive: regenerate inks, grain tile AND glyph canvas\n    const mo = new MutationObserver(() => {\n      derive();\n      buildGrain();\n      if (sized && ready) {\n        buildGlyph();\n        buildGlow();\n      }\n      draw();\n      wake();\n    });\n    mo.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: [\"class\"],\n    });\n    const onVis = () => {\n      if (!document.hidden) wake();\n    };\n    document.addEventListener(\"visibilitychange\", onVis);\n\n    if (!reduced) {\n      root.addEventListener(\"pointermove\", onMove);\n      root.addEventListener(\"pointerdown\", onMove);\n      root.addEventListener(\"pointerleave\", onLeave);\n    }\n\n    // rasterize only once Geist is loaded — a fallback-font carve is wrong.\n    // reduced motion: this single draw IS the static carved frame.\n    document.fonts.ready.then(() => {\n      if (disposed) return;\n      ready = true;\n      if (sized) {\n        buildGlyph();\n        buildEdges();\n        buildGlow();\n        initMotes();\n        placeContent();\n      }\n      draw();\n      wake();\n    });\n\n    return () => {\n      disposed = true;\n      cancelAnimationFrame(raf);\n      raf = 0;\n      ro.disconnect();\n      io.disconnect();\n      mo.disconnect();\n      document.removeEventListener(\"visibilitychange\", onVis);\n      root.removeEventListener(\"pointermove\", onMove);\n      root.removeEventListener(\"pointerdown\", onMove);\n      root.removeEventListener(\"pointerleave\", onLeave);\n    };\n  }, [glyph]);\n\n  return (\n    <div\n      ref={rootRef}\n      className={`relative isolate min-h-[480px] w-full overflow-hidden bg-background font-sans ${className}`}\n    >\n      <canvas\n        ref={canvasRef}\n        aria-hidden\n        className=\"pointer-events-none absolute left-0 top-0\"\n      />\n      <h1 className=\"sr-only\">{glyph} — page not found</h1>\n      {/* copy + CTAs float in the punched negative space — DOM, offset from center */}\n      <div\n        ref={contentRef}\n        className=\"absolute left-1/2 top-[46%] flex w-full max-w-md flex-col items-center gap-6 px-6 text-center\"\n        style={{ transform: \"translate(-50%, calc(13vw + 28px))\" }}\n      >\n        <p className=\"font-mono text-sm text-ns-muted\">{message}</p>\n        <div className=\"flex flex-wrap items-center justify-center gap-3\">\n          <a\n            href={primaryHref}\n            className=\"inline-flex items-center justify-center rounded-sm bg-ns-accent px-4 py-2 text-sm font-medium text-white transition-colors duration-150 hover:bg-ns-accent-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ns-accent\"\n          >\n            {primaryLabel}\n          </a>\n          <a\n            href={secondaryHref}\n            className=\"inline-flex items-center justify-center rounded-sm border border-border px-4 py-2 text-sm text-ns-muted transition-colors duration-150 hover:border-foreground/40 hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ns-accent\"\n          >\n            {secondaryLabel}\n          </a>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/not-found-knockout.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-ns-muted": "var(--ns-muted)",
      "color-ns-accent": "var(--ns-accent)",
      "color-ns-accent-hover": "var(--ns-accent-hover)",
      "color-surface": "var(--surface)"
    },
    "light": {
      "ns-muted": "#4d4d4d",
      "ns-accent": "#006bff",
      "ns-accent-hover": "#0059d1",
      "surface": "#fafafa"
    },
    "dark": {
      "ns-muted": "#8f8f8f",
      "surface": "#171717"
    }
  },
  "meta": {
    "collection": "loud",
    "tags": [
      "404",
      "typography",
      "negative-space",
      "canvas",
      "cursor",
      "page"
    ],
    "instruction": "Build a full 404 page where the numerals are literal absence: a single full-viewport Canvas 2D layer redrawn with an EXPLICIT FULL CLEAR every frame in this order — (a) void backdrop: a 128px seamless value-noise grain tile precomputed once per theme (recolored in --ns-muted, alpha 0.2, tiled at device resolution) over a deep token base (--background crushed 60% toward black in dark, background mixed 16% toward foreground in light so the void reads CLEARLY dimmer than the surface in BOTH themes — the interior/surface gap is the legibility floor), plus 12 drifting motes (1-2px squares, --ns-muted alpha 0.3, 4px/s on wrapped paths inside the glyph bounding box); (b) surface plane: an offscreen plane canvas refilled solid --surface; (c) carve: destination-out drawImage of an offscreen glyph canvas ('404', Geist Sans 600, font-size min(38vw, 62vh), rasterized after document.fonts.ready, centered at 46% height) so the numerals become holes showing layer (a) — because the plane is refilled fresh each frame, destination-out alpha can never accumulate or quantize; (c2) inner wall-light: a static offscreen glow layer — the undisplaced outline stroked in muted-mixed-45%-toward-foreground with layered shadowBlur (max(14px, 7% of font-size) and 2.4x that) then clipped to the holes via destination-in against the glyph mask, rebuilt per resize/theme — so the cut walls catch light and the digit interiors read as carved depth; (d) rim: the glyph outline restroked at 1.5px in border-mixed-55%-toward-foreground as the crisp definition stroke, plus two ±0.75px offset strokes in border-mixed-80%-toward-foreground (catch-light) and border-mixed-55%-toward-background (inner shadow) tones for an engraved stencil bevel bright enough that 404 reads at a glance. EDGE INTERACTION: threshold a half-res alpha mask of the glyph, Moore-neighbor trace its contours (outer loops and the 0's counter), arc-length resample to ~400 points with two smoothing passes and outward normals (oriented away from ink via mask probe); cursor proximity with gaussian falloff sigma 120px displaces points outward up to 6px along their normals; each point returns on a spring (k=520 s^-2, zeta~0.72, one ~3% overshoot, settle < 400ms) with a forced-settle deadline: cursor influence dies at 600ms idle and a hard deadline at 1400ms snaps displacement and velocity to zero; the displaced rim is restroked from the point list every frame. INTERACTION MODEL: pointermove/pointerdown nudge edges; DOM copy ('This page drifted into the void.') and two CTAs — 'Take me home' (accent primary, rounded-sm, hover accent-hover) and 'Contact support' (ghost bordered, hover border-foreground/40) — float below the punched area, positioned by an OFFSET transform from the container center (never absolute canvas coords), both keyboard-focusable with token-relative accent focus rings; an sr-only h1 keeps semantics. HOUSEKEEPING: all inks parsed from getComputedStyle tokens at mount with a MutationObserver on documentElement class that re-derives colors AND regenerates the grain tile + glyph canvas per theme; canvas sized via explicit style.width/style.height plus a dpr-clamped (2) backing store and setTransform; rAF is direct-DOM with zero React state on the hot path and sleeps when cursor idle > 500ms and all edge springs are under epsilon (motes freeze on sleep, redraw resumes on wake) after a 2.2s ambient intro; IntersectionObserver and document.hidden pause the loop; zero-size containers guarded; every listener, observer, and rAF torn down. REDUCED MOTION: one static carved frame, motes frozen, no edge displacement, no pointer listeners. Demo composes the complete page: top strip with mono logotype and one nav link, carved 404 centered, muted copy line, the two CTAs, and a hairline footer with mono status text 'ERR 404 / route unresolved'.",
    "rank": 20
  },
  "type": "registry:ui"
}